@snowtop/ent 0.2.9 → 0.2.11

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.
@@ -41,17 +41,22 @@ const glob = __importStar(require("glob"));
41
41
  const path = __importStar(require("path"));
42
42
  const fs = __importStar(require("fs"));
43
43
  const typescript_1 = __importDefault(require("typescript"));
44
+ const url_1 = require("url");
44
45
  const graphql_1 = require("../graphql/graphql");
45
46
  const readline = __importStar(require("readline"));
46
47
  const imports_1 = require("../imports");
47
48
  const process_1 = require("process");
48
49
  const child_process_1 = require("child_process");
49
50
  const const_1 = require("../core/const");
51
+ const stdout_1 = require("./stdout");
50
52
  const { parseArgs } = require("./parse_args");
51
53
  // need to use the GQLCapture from the package so that when we call GQLCapture.enable()
52
54
  // we're affecting the local paths as opposed to a different instance
53
55
  // life is hard
54
56
  const MODULE_PATH = const_1.GRAPHQL_PATH;
57
+ function isBunRuntime() {
58
+ return process.env.ENT_RUNTIME === "bun" || process.versions.bun;
59
+ }
55
60
  function parseJSONC(fileName, text) {
56
61
  const { config, error } = typescript_1.default.parseConfigFileTextToJson(fileName, text);
57
62
  if (error) {
@@ -171,22 +176,28 @@ async function captureDynamic(filePath, gqlCapture) {
171
176
  return;
172
177
  }
173
178
  return new Promise((resolve, reject) => {
174
- let cmd = "";
175
- const args = [];
176
179
  const env = {
177
180
  ...process.env,
178
181
  };
179
- if (process.env.ENABLE_SWC) {
180
- cmd = "node";
181
- // we seem to get tsconfig-paths by default because child process but not 100% sure...
182
- args.push("-r", "@swc-node/register");
183
- env.SWCRC = "true";
182
+ let cmd = "ts-node";
183
+ const args = [];
184
+ const runtime = isBunRuntime() ? "bun" : "node";
185
+ if (runtime === "bun") {
186
+ cmd = "bun";
187
+ args.push(filePath);
184
188
  }
185
189
  else {
186
- cmd = "ts-node";
187
- args.push("--transpileOnly");
190
+ if (process.env.ENABLE_SWC) {
191
+ cmd = "node";
192
+ // we seem to get tsconfig-paths by default because child process but not 100% sure...
193
+ args.push("-r", "@swc-node/register");
194
+ env.SWCRC = "true";
195
+ }
196
+ else {
197
+ args.push("--transpileOnly");
198
+ }
199
+ args.push(filePath);
188
200
  }
189
- args.push(filePath);
190
201
  const r = (0, child_process_1.spawn)(cmd, args, {
191
202
  env,
192
203
  });
@@ -233,33 +244,10 @@ async function processJSON(gqlCapture, json) {
233
244
  processCustomTypes(json.customTypes, gqlCapture);
234
245
  }
235
246
  }
236
- async function captureCustom(filePath, filesCsv, jsonPath, gqlCapture) {
237
- if (jsonPath !== undefined) {
238
- const json = parseJSONC(jsonPath, fs.readFileSync(jsonPath, {
239
- encoding: "utf8",
240
- }));
241
- processJSON(gqlCapture, json);
242
- return;
243
- }
244
- if (filesCsv !== undefined) {
245
- let files = filesCsv.split(",");
246
- for (let i = 0; i < files.length; i++) {
247
- // TODO fix. we have "src" in the path we get here
248
- files[i] = path.join(filePath, "..", files[i]);
249
- }
250
- await requireFiles(files);
251
- return;
252
- }
247
+ async function loadGraphQLDecoratorFiles(filePath) {
253
248
  // TODO delete all of this eventually
254
249
  // TODO configurable paths eventually
255
250
  // for now only files that are in the include path of the roots are allowed
256
- const rootFiles = [
257
- // right now, currently expecting all custom ent stuff to be in the ent object
258
- // eventually, create a path we check e.g. ent/custom_gql/ ent/graphql?
259
- // for now can just go in graphql/resolvers/ (not generated)
260
- path.join(filePath, "ent/index.ts"),
261
- path.join(filePath, "/graphql/resolvers/index.ts"),
262
- ];
263
251
  const ignore = [
264
252
  "**/generated/**",
265
253
  "**/tests/**",
@@ -268,35 +256,64 @@ async function captureCustom(filePath, filesCsv, jsonPath, gqlCapture) {
268
256
  // ignore test files.
269
257
  "**/*.test.ts",
270
258
  ];
259
+ const customGraphQLEntFiles = glob.sync(path.join(filePath, "/ent/**/*.ts"), {
260
+ // not in action files since we can't customize payloads (yet?)
261
+ ignore: [...ignore, "**/actions/**"],
262
+ });
271
263
  const customGQLResolvers = glob.sync(path.join(filePath, "/graphql/resolvers/**/*.ts"), {
272
- // no actions for now to speed things up
273
- // no index.ts or internal file.
274
264
  ignore: ignore,
275
265
  });
276
266
  const customGQLMutations = glob.sync(path.join(filePath, "/graphql/mutations/**/*.ts"), {
277
- // no actions for now to speed things up
278
- // no index.ts or internal file.
279
267
  ignore: ignore,
280
268
  });
281
- const files = rootFiles.concat(customGQLResolvers, customGQLMutations);
269
+ const files = customGraphQLEntFiles
270
+ .concat(customGQLResolvers, customGQLMutations)
271
+ .filter(fileImportsGraphQLDecorators);
282
272
  await requireFiles(files);
283
273
  }
274
+ async function captureCustom(filePath, filesCsv, jsonPath, gqlCapture) {
275
+ if (jsonPath !== undefined) {
276
+ const json = parseJSONC(jsonPath, fs.readFileSync(jsonPath, {
277
+ encoding: "utf8",
278
+ }));
279
+ await processJSON(gqlCapture, json);
280
+ await loadGraphQLDecoratorFiles(filePath);
281
+ return;
282
+ }
283
+ if (filesCsv !== undefined) {
284
+ let files = filesCsv.split(",");
285
+ for (let i = 0; i < files.length; i++) {
286
+ // TODO fix. we have "src" in the path we get here
287
+ files[i] = path.join(filePath, "..", files[i]);
288
+ }
289
+ await requireFiles(files);
290
+ return;
291
+ }
292
+ await loadGraphQLDecoratorFiles(filePath);
293
+ }
294
+ function fileImportsGraphQLDecorators(file) {
295
+ const contents = fs.readFileSync(file, {
296
+ encoding: "utf8",
297
+ });
298
+ return /@snowtop\/ent\/graphql(?:\/graphql)?/.test(contents);
299
+ }
284
300
  async function requireFiles(files) {
285
- await Promise.all(files.map(async (file) => {
286
- if (fs.existsSync(file)) {
287
- try {
288
- await require(file);
301
+ for (const file of files) {
302
+ if (!fs.existsSync(file)) {
303
+ throw new Error(`file ${file} doesn't exist`);
304
+ }
305
+ try {
306
+ if (isBunRuntime()) {
307
+ await Promise.resolve(`${(0, url_1.pathToFileURL)(file).href}`).then(s => __importStar(require(s)));
289
308
  }
290
- catch (e) {
291
- throw new Error(`${e.message} loading ${file}`);
309
+ else {
310
+ await require(file);
292
311
  }
293
312
  }
294
- else {
295
- throw new Error(`file ${file} doesn't exist`);
313
+ catch (e) {
314
+ throw new Error(`${e.message} loading ${file}`);
296
315
  }
297
- })).catch((err) => {
298
- throw new Error(err);
299
- });
316
+ }
300
317
  }
301
318
  // filePath is path-to-src
302
319
  async function parseImports(filePath) {
@@ -350,9 +367,10 @@ async function main() {
350
367
  // for local dev, get the one from the file system. otherwise, get the one
351
368
  // from node_modules
352
369
  let gqlCapture;
353
- if (process.env.LOCAL_SCRIPT_PATH) {
370
+ if (process.env.LOCAL_SCRIPT_PATH || isBunRuntime()) {
354
371
  const r = require("../graphql/graphql");
355
372
  gqlCapture = r.GQLCapture;
373
+ gqlCapture.enable(true);
356
374
  }
357
375
  else {
358
376
  const r = require(gqlPath);
@@ -466,7 +484,7 @@ async function main() {
466
484
  for (const k in fields) {
467
485
  buildClasses(fields[k]);
468
486
  }
469
- console.log(JSON.stringify({
487
+ await (0, stdout_1.writeJSONToStdout)({
470
488
  args,
471
489
  inputs,
472
490
  fields,
@@ -478,7 +496,7 @@ async function main() {
478
496
  unions,
479
497
  files: allFiles,
480
498
  customTypes,
481
- }));
499
+ });
482
500
  }
483
501
  main()
484
502
  .then()
@@ -39,7 +39,8 @@ const { parseArgs } = require("./parse_args");
39
39
  const parse_1 = require("../parse_schema/parse");
40
40
  const ast_1 = require("../tsc/ast");
41
41
  const names_1 = require("../names/names");
42
- function main() {
42
+ const stdout_1 = require("./stdout");
43
+ async function main() {
43
44
  const options = parseArgs(process.argv.slice(2));
44
45
  if (!options.path) {
45
46
  throw new Error("path required");
@@ -80,16 +81,10 @@ function main() {
80
81
  potentialSchemas[(0, names_1.toClassName)(schema)] = s;
81
82
  }
82
83
  // console.log(potentialSchemas);
83
- // NB: do not change this to async/await
84
- // doing so runs it buffer limit on linux (65536 bytes) and we lose data reading in go
85
- (0, parse_1.parseSchema)(potentialSchemas, globalSchema).then((result) => {
86
- console.log(JSON.stringify(result));
87
- });
88
- }
89
- try {
90
- main();
84
+ const result = await (0, parse_1.parseSchema)(potentialSchemas, globalSchema);
85
+ await (0, stdout_1.writeJSONToStdout)(result);
91
86
  }
92
- catch (err) {
87
+ main().catch((err) => {
93
88
  console.error(err);
94
89
  process.exit(1);
95
- }
90
+ });
@@ -0,0 +1 @@
1
+ export declare function writeJSONToStdout(value: unknown): Promise<void>;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.writeJSONToStdout = writeJSONToStdout;
4
+ async function writeJSONToStdout(value) {
5
+ await writeToStdout(JSON.stringify(value));
6
+ }
7
+ async function writeToStdout(payload) {
8
+ // Bun can exit before a large piped stdout write is fully consumed.
9
+ await new Promise((resolve, reject) => {
10
+ process.stdout.write(payload, (err) => {
11
+ if (err) {
12
+ reject(err);
13
+ return;
14
+ }
15
+ resolve();
16
+ });
17
+ });
18
+ }
@@ -2,11 +2,17 @@ import { Express, RequestHandler } from "express";
2
2
  import { GraphQLSchema } from "graphql";
3
3
  import supertest from "supertest";
4
4
  import { Viewer } from "../../core/base";
5
+ export declare function cleanupBunGraphQLTestAgent(agent: any): Promise<void>;
6
+ type BunTestTarget = {
7
+ app: Express;
8
+ url: string;
9
+ };
5
10
  export type Option = [string, any] | [string, any, string];
6
11
  interface queryConfig {
7
12
  viewer?: Viewer;
8
13
  init?: (app: Express) => void;
9
14
  test?: supertest.Agent | ((express: Express) => supertest.Agent);
15
+ bunTest?: (target: BunTestTarget) => supertest.Agent;
10
16
  schema: GraphQLSchema;
11
17
  headers?: object;
12
18
  debugMode?: boolean;
@@ -1,52 +1,46 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
2
  var __importDefault = (this && this.__importDefault) || function (mod) {
36
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
4
  };
38
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.cleanupBunGraphQLTestAgent = cleanupBunGraphQLTestAgent;
39
7
  exports.expectQueryFromRoot = expectQueryFromRoot;
40
8
  exports.expectMutation = expectMutation;
41
9
  // NB: this is copied from ent-graphql-tests package until I have time to figure out how to share code here effectively
42
10
  // the circular dependencies btw this package and ent-graphql-tests seems to imply something needs to change
43
11
  const express_1 = __importDefault(require("express"));
44
- const fs = __importStar(require("fs"));
45
- const graphql_1 = require("graphql");
46
12
  const graphql_helix_1 = require("graphql-helix");
47
13
  const supertest_1 = __importDefault(require("supertest"));
48
14
  const util_1 = require("util");
49
15
  const auth_1 = require("../../auth");
16
+ const bunAgentServerClose = Symbol("bunAgentServerClose");
17
+ const bunAgentServerCleanups = new Set();
18
+ let bunAgentServerCleanupRegistered = false;
19
+ function registerBunAgentServerCleanup(cleanup) {
20
+ let cleanupPromise;
21
+ const cleanupOnce = () => {
22
+ cleanupPromise = cleanupPromise || cleanup();
23
+ return cleanupPromise;
24
+ };
25
+ bunAgentServerCleanups.add(cleanupOnce);
26
+ const afterAllFn = globalThis
27
+ .afterAll;
28
+ if (!bunAgentServerCleanupRegistered && typeof afterAllFn === "function") {
29
+ bunAgentServerCleanupRegistered = true;
30
+ afterAllFn(async () => {
31
+ const cleanups = [...bunAgentServerCleanups];
32
+ bunAgentServerCleanups.clear();
33
+ await Promise.all(cleanups.map((cleanup) => cleanup()));
34
+ });
35
+ }
36
+ return cleanupOnce;
37
+ }
38
+ async function cleanupBunGraphQLTestAgent(agent) {
39
+ const cleanup = agent?.[bunAgentServerClose];
40
+ if (cleanup) {
41
+ await cleanup();
42
+ }
43
+ }
50
44
  function server(config) {
51
45
  const viewer = config.viewer;
52
46
  if (viewer) {
@@ -83,25 +77,113 @@ function server(config) {
83
77
  app.use(config.graphQLPath || "/graphql", ...handlers);
84
78
  return app;
85
79
  }
80
+ // Bun can load GraphQL types from a different module realm than the schema under test,
81
+ // so constructor identity checks are not reliable here.
82
+ function isWrappingTypeLike(typ) {
83
+ return !!typ && typeof typ === "object" && "ofType" in typ;
84
+ }
85
+ function isListTypeLike(typ) {
86
+ return (isWrappingTypeLike(typ) &&
87
+ (typ.constructor?.name === "GraphQLList" || /^\[.*\]$/.test(String(typ))));
88
+ }
89
+ function isObjectTypeLike(typ) {
90
+ return (!!typ && typeof typ === "object" && typeof typ.getFields === "function");
91
+ }
92
+ function isScalarTypeLike(typ) {
93
+ return (!!typ &&
94
+ typeof typ === "object" &&
95
+ typeof typ.serialize === "function" &&
96
+ typeof typ.parseValue === "function" &&
97
+ typeof typ.parseLiteral === "function");
98
+ }
99
+ function isEnumTypeLike(typ) {
100
+ return (!!typ &&
101
+ typeof typ === "object" &&
102
+ typeof typ.getValues === "function" &&
103
+ typeof typ.toConfig === "function");
104
+ }
86
105
  function getInnerType(typ, list) {
87
- if ((0, graphql_1.isWrappingType)(typ)) {
88
- if (typ instanceof graphql_1.GraphQLList) {
106
+ if (isWrappingTypeLike(typ)) {
107
+ if (isListTypeLike(typ)) {
89
108
  return getInnerType(typ.ofType, true);
90
109
  }
91
110
  return getInnerType(typ.ofType, list);
92
111
  }
93
112
  return [typ, list];
94
113
  }
95
- function makeGraphQLRequest(config, query, fieldArgs) {
114
+ async function createBunTestHarness(config, persistentAgent, testFactory) {
115
+ const app = config.server ? config.server : server(config);
116
+ const httpServer = await new Promise((resolve) => {
117
+ const srv = app.listen(0, () => resolve(srv));
118
+ });
119
+ const address = httpServer.address();
120
+ if (!address || typeof address === "string") {
121
+ throw new Error("could not determine test server address");
122
+ }
123
+ const url = `http://127.0.0.1:${address.port}`;
124
+ let closePromise;
125
+ const close = () => {
126
+ closePromise =
127
+ closePromise ||
128
+ new Promise((resolve, reject) => {
129
+ httpServer.close((err) => (err ? reject(err) : resolve()));
130
+ });
131
+ return closePromise;
132
+ };
133
+ if (persistentAgent) {
134
+ try {
135
+ let agent;
136
+ // Keep the existing Express app factory contract. If bunTest is present,
137
+ // it owns the URL-backed agent and testFactory still runs against the app.
138
+ const factoryAgent = testFactory?.(app);
139
+ if (config.bunTest) {
140
+ agent = config.bunTest({ app, url });
141
+ }
142
+ else if (factoryAgent) {
143
+ agent = factoryAgent;
144
+ }
145
+ else {
146
+ agent = supertest_1.default.agent(url);
147
+ }
148
+ agent[bunAgentServerClose] = registerBunAgentServerCleanup(close);
149
+ return { agent };
150
+ }
151
+ catch (err) {
152
+ await close();
153
+ throw err;
154
+ }
155
+ }
156
+ return {
157
+ agent: (0, supertest_1.default)(url),
158
+ cleanup: close,
159
+ };
160
+ }
161
+ async function makeGraphQLRequest(config, query, fieldArgs) {
96
162
  let agent;
163
+ let cleanup;
97
164
  if (config.test) {
98
165
  if (typeof config.test === "function") {
99
- agent = config.test(config.server ? config.server : server(config));
166
+ if (process.versions.bun) {
167
+ ({ agent } = await createBunTestHarness(config, true, config.test));
168
+ }
169
+ else {
170
+ const factoryAgent = config.test(config.server ? config.server : server(config));
171
+ if (!factoryAgent) {
172
+ throw new Error("config.test must return a supertest agent");
173
+ }
174
+ agent = factoryAgent;
175
+ }
100
176
  }
101
177
  else {
102
178
  agent = config.test;
103
179
  }
104
180
  }
181
+ else if (process.versions.bun && config.bunTest) {
182
+ ({ agent } = await createBunTestHarness(config, true));
183
+ }
184
+ else if (process.versions.bun) {
185
+ ({ agent, cleanup } = await createBunTestHarness(config, false));
186
+ }
105
187
  else {
106
188
  agent = (0, supertest_1.default)(config.server ? config.server : server(config));
107
189
  }
@@ -109,7 +191,7 @@ function makeGraphQLRequest(config, query, fieldArgs) {
109
191
  // handle files
110
192
  fieldArgs.forEach((fieldArg) => {
111
193
  let [typ, list] = getInnerType(fieldArg.type, false);
112
- if (typ instanceof graphql_1.GraphQLScalarType && typ.name == "Upload") {
194
+ if (isScalarTypeLike(typ) && typ.name == "Upload") {
113
195
  let value = config.args[fieldArg.name];
114
196
  if (list) {
115
197
  expect(Array.isArray(value)).toBe(true);
@@ -150,31 +232,29 @@ function makeGraphQLRequest(config, query, fieldArgs) {
150
232
  ret.field("map", JSON.stringify(m));
151
233
  idx = 0;
152
234
  for (let [key, val] of files) {
153
- if (typeof val === "string") {
154
- val = fs.createReadStream(val);
155
- }
156
235
  ret.attach(`${idx}`, val, key);
157
236
  idx++;
158
237
  }
159
- return [agent, ret];
238
+ return { agent, request: ret, cleanup };
160
239
  }
161
240
  else {
162
- return [
241
+ return {
163
242
  agent,
164
- agent
243
+ request: agent
165
244
  .post(config.graphQLPath || "/graphql")
166
245
  .set((config.headers || {}))
167
246
  .send({
168
247
  query: query,
169
- variables: JSON.stringify(variables),
248
+ variables,
170
249
  }),
171
- ];
250
+ cleanup,
251
+ };
172
252
  }
173
253
  }
174
254
  function buildTreeFromQueryPaths(schema, fieldType, ...options) {
175
255
  let fields;
176
256
  const [typ] = getInnerType(fieldType, false);
177
- if (typ instanceof graphql_1.GraphQLObjectType) {
257
+ if (isObjectTypeLike(typ)) {
178
258
  fields = typ.getFields();
179
259
  }
180
260
  let topLevelTree = {};
@@ -189,7 +269,7 @@ function buildTreeFromQueryPaths(schema, fieldType, ...options) {
189
269
  if (!typ) {
190
270
  throw new Error(`can't find type for ${match[1]} in schema`);
191
271
  }
192
- if (typ instanceof graphql_1.GraphQLObjectType) {
272
+ if (isObjectTypeLike(typ)) {
193
273
  fields = typ.getFields();
194
274
  }
195
275
  }
@@ -243,7 +323,7 @@ function buildTreeFromQueryPaths(schema, fieldType, ...options) {
243
323
  subField = root?.[p];
244
324
  if (subField) {
245
325
  [subField] = getInnerType(subField.type, false);
246
- if (subField instanceof graphql_1.GraphQLObjectType) {
326
+ if (isObjectTypeLike(subField)) {
247
327
  root = subField.getFields();
248
328
  }
249
329
  }
@@ -251,7 +331,7 @@ function buildTreeFromQueryPaths(schema, fieldType, ...options) {
251
331
  if (!subField) {
252
332
  return false;
253
333
  }
254
- return (0, graphql_1.isScalarType)(subField) || (0, graphql_1.isEnumType)(subField);
334
+ return isScalarTypeLike(subField) || isEnumTypeLike(subField);
255
335
  }
256
336
  if (i === parts.length - 1 && typeof option[1] === "object") {
257
337
  if (!scalarFieldAtLeaf(parts)) {
@@ -382,8 +462,16 @@ async function expectFromRoot(config, ...options) {
382
462
  if (config.debugMode) {
383
463
  console.log(q);
384
464
  }
385
- let [st, temp] = makeGraphQLRequest(config, q, fieldArgs);
386
- const res = await temp.expect("Content-Type", /json/);
465
+ const { agent: st, request, cleanup, } = await makeGraphQLRequest(config, q, fieldArgs);
466
+ let res;
467
+ try {
468
+ res = await request.expect("Content-Type", /json/);
469
+ }
470
+ finally {
471
+ if (cleanup) {
472
+ await cleanup();
473
+ }
474
+ }
387
475
  if (config.debugMode) {
388
476
  console.log((0, util_1.inspect)(res.body, false, 3));
389
477
  }