attio 0.0.1-experimental.20241002.1 → 0.0.1-experimental.20241003
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/lib/build.js +2 -2
- package/lib/commands/dev.js +6 -6
- package/lib/components/BuildError.js +2 -2
- package/lib/components/CodeGenErrors.js +22 -0
- package/lib/graphql/generate-operations.js +51 -16
- package/lib/graphql/parse-schema.js +28 -18
- package/lib/machines/code-gen-machine.js +6 -2
- package/lib/machines/dev-machine.js +3 -7
- package/lib/templates/typescript/tsconfig.json +2 -2
- package/package.json +1 -4
- package/lib/graphql.d.ts +0 -4
- package/lib/templates/typescript/graphql.d.ts +0 -6
- package/lib/util/update-operation-types.js +0 -13
package/lib/build.js
CHANGED
|
@@ -13,6 +13,6 @@ const buildErrorSchema = z.object({
|
|
|
13
13
|
}),
|
|
14
14
|
});
|
|
15
15
|
export const errorSchema = z.object({
|
|
16
|
-
errors: z.array(buildErrorSchema),
|
|
17
|
-
warnings: z.array(buildErrorSchema),
|
|
16
|
+
errors: z.array(buildErrorSchema).optional(),
|
|
17
|
+
warnings: z.array(buildErrorSchema).optional(),
|
|
18
18
|
});
|
package/lib/commands/dev.js
CHANGED
|
@@ -5,6 +5,7 @@ import { option } from "pastel";
|
|
|
5
5
|
import React from "react";
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
import { renderBuildErrors } from "../components/BuildError.js";
|
|
8
|
+
import { CodeGenError } from "../components/CodeGenErrors.js";
|
|
8
9
|
import { InitialInstructions } from "../components/InitialInstructions.js";
|
|
9
10
|
import { ScrollBox } from "../components/ScrollBox.js";
|
|
10
11
|
import { renderTypeScriptErrors } from "../components/TypeScriptErrors.js";
|
|
@@ -28,7 +29,8 @@ export default function Dev({ options: { debug } }) {
|
|
|
28
29
|
const jsTime = snapshot.children.javascript?.getSnapshot().context.time;
|
|
29
30
|
const tsErrors = snapshot.children.typescript?.getSnapshot().context.errors;
|
|
30
31
|
const tsTime = snapshot.children.typescript?.getSnapshot().context.time;
|
|
31
|
-
const
|
|
32
|
+
const codeGenError = snapshot.children["code-gen"]?.getSnapshot().context.error;
|
|
33
|
+
const hasErrors = Boolean(jsError || tsErrors || codeGenError);
|
|
32
34
|
const isNoConfig = snapshot.matches("No Config");
|
|
33
35
|
React.useEffect(() => {
|
|
34
36
|
if (isNoConfig) {
|
|
@@ -57,10 +59,7 @@ export default function Dev({ options: { debug } }) {
|
|
|
57
59
|
React.createElement(Text, null,
|
|
58
60
|
"Code Gen:",
|
|
59
61
|
" ",
|
|
60
|
-
JSON.stringify(snapshot.children["code-gen"]?.getSnapshot()?.value),
|
|
61
|
-
" ",
|
|
62
|
-
snapshot.children["code-gen"]?.getSnapshot().context.error
|
|
63
|
-
?.message)),
|
|
62
|
+
JSON.stringify(snapshot.children["code-gen"]?.getSnapshot()?.value))),
|
|
64
63
|
snapshot.context.devVersion?.app_id && (React.createElement(Box, null,
|
|
65
64
|
React.createElement(Text, null,
|
|
66
65
|
"App ID: ",
|
|
@@ -101,5 +100,6 @@ export default function Dev({ options: { debug } }) {
|
|
|
101
100
|
React.createElement(Text, null, "Press \"o\" to open GraphQL Explorer")))),
|
|
102
101
|
hasErrors && (React.createElement(ScrollBox, { borderStyle: "round", padding: 1 },
|
|
103
102
|
jsError && renderBuildErrors(jsError),
|
|
104
|
-
tsErrors && renderTypeScriptErrors(tsErrors)
|
|
103
|
+
tsErrors && renderTypeScriptErrors(tsErrors),
|
|
104
|
+
codeGenError && React.createElement(CodeGenError, { error: codeGenError })))));
|
|
105
105
|
}
|
|
@@ -34,7 +34,7 @@ const Error = React.forwardRef(({ message, level }, ref) => (React.createElement
|
|
|
34
34
|
React.createElement(Text, { color: "greenBright" }, "^")))))))));
|
|
35
35
|
export function renderBuildErrors({ errors, warnings }) {
|
|
36
36
|
return [
|
|
37
|
-
...errors.map((error, index) => (React.createElement(Error, { key: `BuildError-${index}`, message: error, level: "error" }))),
|
|
38
|
-
...warnings.map((error, index) => (React.createElement(Error, { key: `BuildError-${index}`, message: error, level: "warning" }))),
|
|
37
|
+
...(errors ?? []).map((error, index) => (React.createElement(Error, { key: `BuildError-${index}`, message: error, level: "error" }))),
|
|
38
|
+
...(warnings ?? []).map((error, index) => (React.createElement(Error, { key: `BuildError-${index}`, message: error, level: "warning" }))),
|
|
39
39
|
];
|
|
40
40
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { codeFrameColumns } from "@babel/code-frame";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import React from "react";
|
|
4
|
+
export const CodeGenError = React.forwardRef(({ error }, ref) => {
|
|
5
|
+
return (React.createElement(Box, { flexDirection: "column", ref: ref },
|
|
6
|
+
React.createElement(Box, null,
|
|
7
|
+
React.createElement(Text, { color: "red" },
|
|
8
|
+
React.createElement(Text, { backgroundColor: "red", color: "white" },
|
|
9
|
+
" ",
|
|
10
|
+
"GRAPHQL ERROR",
|
|
11
|
+
" "),
|
|
12
|
+
" ",
|
|
13
|
+
React.createElement(Text, null, error.message))),
|
|
14
|
+
React.createElement(Box, { paddingTop: 1, flexDirection: "column" },
|
|
15
|
+
React.createElement(Box, null,
|
|
16
|
+
React.createElement(Text, null, codeFrameColumns(error.source, {
|
|
17
|
+
start: {
|
|
18
|
+
line: error.locations[0].line,
|
|
19
|
+
column: error.locations[0].column,
|
|
20
|
+
},
|
|
21
|
+
}))))));
|
|
22
|
+
});
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
|
-
import { parse, visit, validate, print, OperationTypeNode, isObjectType, isListType, isNonNullType, isInputObjectType, isEnumType, getNamedType, validateSchema, } from "graphql";
|
|
2
|
+
import { parse, visit, validate, print, OperationTypeNode, GraphQLError as OfficialGraphQLError, isObjectType, isListType, isNonNullType, isInputObjectType, isEnumType, getNamedType, validateSchema, } from "graphql";
|
|
3
3
|
import path from "path";
|
|
4
4
|
import { format } from "prettier";
|
|
5
|
+
import { getLineAndColumn, GraphQLError } from "./parse-schema.js";
|
|
5
6
|
function findGraphQLFiles(dir) {
|
|
6
7
|
const files = [];
|
|
7
8
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
@@ -113,34 +114,68 @@ declare module "./${graphqlFileName}" {
|
|
|
113
114
|
`;
|
|
114
115
|
}
|
|
115
116
|
export async function generateOperationFromQuery(graphqlFileName, query, schema) {
|
|
116
|
-
|
|
117
|
-
|
|
117
|
+
let ast;
|
|
118
|
+
try {
|
|
119
|
+
ast = parse(query);
|
|
120
|
+
}
|
|
121
|
+
catch (e) {
|
|
122
|
+
if (e instanceof OfficialGraphQLError) {
|
|
123
|
+
throw new GraphQLError(e.message, e.locations ?? [{ line: 1, column: 1 }], query, graphqlFileName);
|
|
124
|
+
}
|
|
125
|
+
throw e;
|
|
126
|
+
}
|
|
127
|
+
let operation = "";
|
|
118
128
|
const schemaErrors = validateSchema(schema);
|
|
119
129
|
if (schemaErrors.length > 0) {
|
|
120
130
|
throw new Error(`Schema validation errors: ${schemaErrors.map((e) => e.message).join("\n")}`);
|
|
121
131
|
}
|
|
122
132
|
const validationErrors = validate(schema, ast);
|
|
123
133
|
const filteredErrors = validationErrors.filter((error) => !error.message.includes("is not executable"));
|
|
124
|
-
|
|
125
|
-
|
|
134
|
+
const firstError = filteredErrors[0];
|
|
135
|
+
if (firstError) {
|
|
136
|
+
throw new GraphQLError(firstError.message, firstError.locations ?? [{ line: 1, column: 1 }], query, graphqlFileName);
|
|
126
137
|
}
|
|
127
138
|
visit(ast, {
|
|
128
139
|
OperationDefinition(node) {
|
|
129
|
-
if (node.name
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
140
|
+
if (node.name) {
|
|
141
|
+
switch (node.operation) {
|
|
142
|
+
case OperationTypeNode.MUTATION:
|
|
143
|
+
case OperationTypeNode.SUBSCRIPTION:
|
|
144
|
+
throw new GraphQLError(`Only queries are supported, found ${node.operation}`, node.loc ? [getLineAndColumn(query, node.loc)] : [{ line: 1, column: 1 }], query, graphqlFileName);
|
|
145
|
+
case OperationTypeNode.QUERY:
|
|
146
|
+
if (operation) {
|
|
147
|
+
throw new GraphQLError(`Only one query is allowed per .graphql file`, node.loc
|
|
148
|
+
? [getLineAndColumn(query, node.loc)]
|
|
149
|
+
: [{ line: 1, column: 1 }], query, graphqlFileName);
|
|
150
|
+
}
|
|
151
|
+
const operationName = node.name.value;
|
|
152
|
+
const variableDefinitions = node.variableDefinitions || [];
|
|
153
|
+
operation = generateOperationFunction(graphqlFileName, operationName, variableDefinitions, schema, node.selectionSet);
|
|
154
|
+
break;
|
|
155
|
+
default:
|
|
156
|
+
return node.operation;
|
|
157
|
+
}
|
|
133
158
|
}
|
|
134
159
|
},
|
|
135
160
|
});
|
|
136
|
-
return format(
|
|
161
|
+
return format(operation, { parser: "typescript" });
|
|
137
162
|
}
|
|
138
163
|
export async function generateOperations(rootDir, schema) {
|
|
139
164
|
const graphqlFiles = findGraphQLFiles(rootDir);
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
165
|
+
await Promise.all(graphqlFiles.map(async (file) => {
|
|
166
|
+
const content = await fs.promises.readFile(file, "utf-8");
|
|
167
|
+
const operation = await generateOperationFromQuery(path.basename(file), content, schema);
|
|
168
|
+
const formattedOperation = await format(`/**
|
|
169
|
+
* ****************************************************
|
|
170
|
+
* THIS FILE IS AUTO-GENERATED AT DEVELOPMENT TIME.
|
|
171
|
+
*
|
|
172
|
+
* DO NOT EDIT DIRECTLY OR COMMIT IT TO SOURCE CONTROL.
|
|
173
|
+
* ****************************************************
|
|
174
|
+
*/
|
|
175
|
+
import {Query } from "attio/client"
|
|
176
|
+
|
|
177
|
+
${operation}
|
|
178
|
+
`, { parser: "typescript" });
|
|
179
|
+
await fs.promises.writeFile(`${file}.d.ts`, formattedOperation);
|
|
180
|
+
}));
|
|
146
181
|
}
|
|
@@ -2,27 +2,41 @@ import { codeFrameColumns } from "@babel/code-frame";
|
|
|
2
2
|
import fs from "fs";
|
|
3
3
|
import { parse, validateSchema, buildASTSchema } from "graphql";
|
|
4
4
|
import { validateSDL } from "graphql/validation/validate.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
export class GraphQLError extends Error {
|
|
6
|
+
constructor(message, locations, source, filename) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.locations = locations;
|
|
9
|
+
this.source = source;
|
|
10
|
+
this.filename = filename;
|
|
11
|
+
}
|
|
12
|
+
toString() {
|
|
13
|
+
const { line, column } = this.locations[0];
|
|
14
|
+
return `${this.message}\n\n${this.filename}:${this.locations[0].line}\n\n${codeFrameColumns(this.source, {
|
|
9
15
|
start: { line, column },
|
|
10
|
-
})
|
|
11
|
-
.join(`\n\n`)}\n`;
|
|
16
|
+
})}\n`;
|
|
12
17
|
}
|
|
13
|
-
|
|
14
|
-
|
|
18
|
+
}
|
|
19
|
+
export function getLineAndColumn(source, location) {
|
|
20
|
+
let line = 1;
|
|
21
|
+
let column = 1;
|
|
22
|
+
for (let i = 0; i < location.start; i++) {
|
|
23
|
+
if (source[i] === "\n") {
|
|
24
|
+
line++;
|
|
25
|
+
column = 1;
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
column++;
|
|
29
|
+
}
|
|
15
30
|
}
|
|
31
|
+
return { line, column };
|
|
16
32
|
}
|
|
17
33
|
export function parseSchemaString(schemaString, schemaPath) {
|
|
18
34
|
let parsedSchema;
|
|
19
35
|
try {
|
|
20
36
|
parsedSchema = parse(schemaString);
|
|
21
37
|
}
|
|
22
|
-
catch (
|
|
23
|
-
throw
|
|
24
|
-
code: `GRAPHQL_SYNTAX_ERROR`,
|
|
25
|
-
});
|
|
38
|
+
catch (e) {
|
|
39
|
+
throw new GraphQLError(e.message, e.locations, schemaString, schemaPath);
|
|
26
40
|
}
|
|
27
41
|
parsedSchema = {
|
|
28
42
|
...parsedSchema,
|
|
@@ -42,16 +56,12 @@ export function parseSchemaString(schemaString, schemaPath) {
|
|
|
42
56
|
};
|
|
43
57
|
const sdlValidationErrors = validateSDL(parsedSchema);
|
|
44
58
|
if (sdlValidationErrors.length) {
|
|
45
|
-
throw
|
|
46
|
-
code: `GRAPHQL_SCHEMA_ERROR`,
|
|
47
|
-
});
|
|
59
|
+
throw new GraphQLError(sdlValidationErrors[0].message, sdlValidationErrors[0].locations ?? [{ line: 1, column: 1 }], schemaString, schemaPath);
|
|
48
60
|
}
|
|
49
61
|
const schema = buildASTSchema(parsedSchema, { assumeValid: false, assumeValidSDL: false });
|
|
50
62
|
const schemaValidationErrors = validateSchema(schema);
|
|
51
63
|
if (schemaValidationErrors.length > 0) {
|
|
52
|
-
throw
|
|
53
|
-
code: `GRAPHQL_SCHEMA_ERROR`,
|
|
54
|
-
});
|
|
64
|
+
throw new GraphQLError(schemaValidationErrors[0].message, schemaValidationErrors[0].locations ?? [{ line: 1, column: 1 }], schemaString, schemaPath);
|
|
55
65
|
}
|
|
56
66
|
return {
|
|
57
67
|
source: schemaString,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { sendTo, assign, setup, fromCallback } from "xstate";
|
|
2
|
+
import { generateOperations } from "../graphql/generate-operations.js";
|
|
2
3
|
import { parseSchema } from "../graphql/parse-schema.js";
|
|
3
4
|
import { findNodeModulesPath } from "../util/find-node-modules-path.js";
|
|
4
|
-
import { updateOperationTypes } from "../util/update-operation-types.js";
|
|
5
5
|
export const codeGenMachine = setup({
|
|
6
6
|
types: {
|
|
7
7
|
context: {},
|
|
@@ -27,12 +27,15 @@ export const codeGenMachine = setup({
|
|
|
27
27
|
loadSchema();
|
|
28
28
|
}),
|
|
29
29
|
generateOperations: fromCallback(({ sendBack, input }) => {
|
|
30
|
-
|
|
30
|
+
generateOperations(".", input.schema)
|
|
31
31
|
.then(() => sendBack({ type: "Done" }))
|
|
32
32
|
.catch((error) => sendBack({ type: "Error", error }));
|
|
33
33
|
}),
|
|
34
34
|
},
|
|
35
35
|
actions: {
|
|
36
|
+
clearError: assign({
|
|
37
|
+
error: () => undefined,
|
|
38
|
+
}),
|
|
36
39
|
setError: assign({
|
|
37
40
|
error: (_, params) => params.error,
|
|
38
41
|
}),
|
|
@@ -72,6 +75,7 @@ export const codeGenMachine = setup({
|
|
|
72
75
|
on: {
|
|
73
76
|
Done: {
|
|
74
77
|
target: "Done",
|
|
78
|
+
actions: "clearError",
|
|
75
79
|
},
|
|
76
80
|
Error: {
|
|
77
81
|
target: "Errored",
|
|
@@ -117,13 +117,9 @@ export const devMachine = setup({
|
|
|
117
117
|
upload().catch((error) => sendBack({ type: "Upload Error", error }));
|
|
118
118
|
}),
|
|
119
119
|
"watch": fromCallback(({ sendBack }) => {
|
|
120
|
-
const watcher = chokidar.watch([
|
|
121
|
-
"
|
|
122
|
-
|
|
123
|
-
"src/webhooks",
|
|
124
|
-
"src/events",
|
|
125
|
-
".env",
|
|
126
|
-
]);
|
|
120
|
+
const watcher = chokidar.watch(["src/app", "src/assets", "src/webhooks", "src/events", ".env"], {
|
|
121
|
+
ignored: "**/*.graphql.d.ts",
|
|
122
|
+
});
|
|
127
123
|
watcher.on("ready", () => watcher.on("all", () => {
|
|
128
124
|
sendBack({ type: "Change" });
|
|
129
125
|
}));
|
|
@@ -35,12 +35,12 @@
|
|
|
35
35
|
"strictNullChecks": true,
|
|
36
36
|
"strictPropertyInitialization": true,
|
|
37
37
|
"target": "ES2019",
|
|
38
|
-
"types": ["attio/client", "attio/global"
|
|
38
|
+
"types": ["attio/client", "attio/global"],
|
|
39
39
|
},
|
|
40
40
|
"exclude": [
|
|
41
41
|
"node_modules"
|
|
42
42
|
],
|
|
43
43
|
"include": [
|
|
44
|
-
"src"
|
|
44
|
+
"src"
|
|
45
45
|
]
|
|
46
46
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "attio",
|
|
3
|
-
"version": "0.0.1-experimental.
|
|
3
|
+
"version": "0.0.1-experimental.20241003",
|
|
4
4
|
"bin": "lib/attio.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -19,9 +19,6 @@
|
|
|
19
19
|
"./global": {
|
|
20
20
|
"types": "./global.d.ts"
|
|
21
21
|
},
|
|
22
|
-
"./graphql": {
|
|
23
|
-
"types": "./lib/graphql.d.ts"
|
|
24
|
-
},
|
|
25
22
|
"./server": {
|
|
26
23
|
"types": "./server.d.ts"
|
|
27
24
|
},
|
package/lib/graphql.d.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { promises as fs } from "fs";
|
|
2
|
-
import { format } from "prettier";
|
|
3
|
-
import { generateOperations } from "../graphql/generate-operations.js";
|
|
4
|
-
import { findNodeModulesPath } from "./find-node-modules-path.js";
|
|
5
|
-
export async function updateOperationTypes(schema) {
|
|
6
|
-
const filePath = await findNodeModulesPath(["lib", "graphql.d.ts"]);
|
|
7
|
-
if (!filePath) {
|
|
8
|
-
return;
|
|
9
|
-
}
|
|
10
|
-
const unformattedTypescript = await generateOperations(".", schema);
|
|
11
|
-
const typescript = await format(unformattedTypescript, { parser: "typescript" });
|
|
12
|
-
await fs.writeFile(filePath, `import type { Query } from "./client/index.js";\n\n${typescript}`);
|
|
13
|
-
}
|