attio 0.0.1-experimental.20241002.1 → 0.0.1-experimental.20241003.1
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 +68 -17
- 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 });
|
|
@@ -10,7 +11,22 @@ function findGraphQLFiles(dir) {
|
|
|
10
11
|
if (entry.isDirectory()) {
|
|
11
12
|
files.push(...findGraphQLFiles(fullPath));
|
|
12
13
|
}
|
|
13
|
-
else if (entry.isFile() &&
|
|
14
|
+
else if (entry.isFile() &&
|
|
15
|
+
(path.extname(entry.name) === ".graphql" || path.extname(entry.name) === ".gql")) {
|
|
16
|
+
files.push(fullPath);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return files;
|
|
20
|
+
}
|
|
21
|
+
function findGeneratedFiles(dir) {
|
|
22
|
+
const files = [];
|
|
23
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
24
|
+
for (const entry of entries) {
|
|
25
|
+
const fullPath = path.join(dir, entry.name);
|
|
26
|
+
if (entry.isDirectory()) {
|
|
27
|
+
files.push(...findGraphQLFiles(fullPath));
|
|
28
|
+
}
|
|
29
|
+
else if (entry.isFile() && path.extname(entry.name) === ".graphql.d.ts") {
|
|
14
30
|
files.push(fullPath);
|
|
15
31
|
}
|
|
16
32
|
}
|
|
@@ -113,34 +129,69 @@ declare module "./${graphqlFileName}" {
|
|
|
113
129
|
`;
|
|
114
130
|
}
|
|
115
131
|
export async function generateOperationFromQuery(graphqlFileName, query, schema) {
|
|
116
|
-
|
|
117
|
-
|
|
132
|
+
let ast;
|
|
133
|
+
try {
|
|
134
|
+
ast = parse(query);
|
|
135
|
+
}
|
|
136
|
+
catch (e) {
|
|
137
|
+
if (e instanceof OfficialGraphQLError) {
|
|
138
|
+
throw new GraphQLError(e.message, e.locations ?? [{ line: 1, column: 1 }], query, graphqlFileName);
|
|
139
|
+
}
|
|
140
|
+
throw e;
|
|
141
|
+
}
|
|
142
|
+
let operation = "";
|
|
118
143
|
const schemaErrors = validateSchema(schema);
|
|
119
144
|
if (schemaErrors.length > 0) {
|
|
120
145
|
throw new Error(`Schema validation errors: ${schemaErrors.map((e) => e.message).join("\n")}`);
|
|
121
146
|
}
|
|
122
147
|
const validationErrors = validate(schema, ast);
|
|
123
148
|
const filteredErrors = validationErrors.filter((error) => !error.message.includes("is not executable"));
|
|
124
|
-
|
|
125
|
-
|
|
149
|
+
const firstError = filteredErrors[0];
|
|
150
|
+
if (firstError) {
|
|
151
|
+
throw new GraphQLError(firstError.message, firstError.locations ?? [{ line: 1, column: 1 }], query, graphqlFileName);
|
|
126
152
|
}
|
|
127
153
|
visit(ast, {
|
|
128
154
|
OperationDefinition(node) {
|
|
129
|
-
if (node.name
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
155
|
+
if (node.name) {
|
|
156
|
+
switch (node.operation) {
|
|
157
|
+
case OperationTypeNode.MUTATION:
|
|
158
|
+
case OperationTypeNode.SUBSCRIPTION:
|
|
159
|
+
throw new GraphQLError(`Only queries are supported, found ${node.operation}`, node.loc ? [getLineAndColumn(query, node.loc)] : [{ line: 1, column: 1 }], query, graphqlFileName);
|
|
160
|
+
case OperationTypeNode.QUERY:
|
|
161
|
+
if (operation) {
|
|
162
|
+
throw new GraphQLError(`Only one query is allowed per .graphql file`, node.loc
|
|
163
|
+
? [getLineAndColumn(query, node.loc)]
|
|
164
|
+
: [{ line: 1, column: 1 }], query, graphqlFileName);
|
|
165
|
+
}
|
|
166
|
+
const operationName = node.name.value;
|
|
167
|
+
const variableDefinitions = node.variableDefinitions || [];
|
|
168
|
+
operation = generateOperationFunction(graphqlFileName, operationName, variableDefinitions, schema, node.selectionSet);
|
|
169
|
+
break;
|
|
170
|
+
default:
|
|
171
|
+
return node.operation;
|
|
172
|
+
}
|
|
133
173
|
}
|
|
134
174
|
},
|
|
135
175
|
});
|
|
136
|
-
return format(
|
|
176
|
+
return format(operation, { parser: "typescript" });
|
|
137
177
|
}
|
|
138
178
|
export async function generateOperations(rootDir, schema) {
|
|
179
|
+
await Promise.all(findGeneratedFiles(rootDir).map(async (file) => fs.promises.unlink(file)));
|
|
139
180
|
const graphqlFiles = findGraphQLFiles(rootDir);
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
181
|
+
await Promise.all(graphqlFiles.map(async (file) => {
|
|
182
|
+
const content = await fs.promises.readFile(file, "utf-8");
|
|
183
|
+
const operation = await generateOperationFromQuery(path.basename(file), content, schema);
|
|
184
|
+
const formattedOperation = await format(`/**
|
|
185
|
+
* ****************************************************
|
|
186
|
+
* THIS FILE IS AUTO-GENERATED AT DEVELOPMENT TIME.
|
|
187
|
+
*
|
|
188
|
+
* DO NOT EDIT DIRECTLY OR COMMIT IT TO SOURCE CONTROL.
|
|
189
|
+
* ****************************************************
|
|
190
|
+
*/
|
|
191
|
+
import {Query } from "attio/client"
|
|
192
|
+
|
|
193
|
+
${operation}
|
|
194
|
+
`, { parser: "typescript" });
|
|
195
|
+
await fs.promises.writeFile(`${file}.d.ts`, formattedOperation);
|
|
196
|
+
}));
|
|
146
197
|
}
|
|
@@ -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.1",
|
|
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
|
-
}
|