@snowtop/ent 0.2.8 → 0.2.10

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,6 +41,7 @@ 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");
@@ -52,6 +53,9 @@ const { parseArgs } = require("./parse_args");
52
53
  // we're affecting the local paths as opposed to a different instance
53
54
  // life is hard
54
55
  const MODULE_PATH = const_1.GRAPHQL_PATH;
56
+ function isBunRuntime() {
57
+ return process.env.ENT_RUNTIME === "bun" || process.versions.bun;
58
+ }
55
59
  function parseJSONC(fileName, text) {
56
60
  const { config, error } = typescript_1.default.parseConfigFileTextToJson(fileName, text);
57
61
  if (error) {
@@ -171,22 +175,28 @@ async function captureDynamic(filePath, gqlCapture) {
171
175
  return;
172
176
  }
173
177
  return new Promise((resolve, reject) => {
174
- let cmd = "";
175
- const args = [];
176
178
  const env = {
177
179
  ...process.env,
178
180
  };
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";
181
+ let cmd = "ts-node";
182
+ const args = [];
183
+ const runtime = isBunRuntime() ? "bun" : "node";
184
+ if (runtime === "bun") {
185
+ cmd = "bun";
186
+ args.push(filePath);
184
187
  }
185
188
  else {
186
- cmd = "ts-node";
187
- args.push("--transpileOnly");
189
+ if (process.env.ENABLE_SWC) {
190
+ cmd = "node";
191
+ // we seem to get tsconfig-paths by default because child process but not 100% sure...
192
+ args.push("-r", "@swc-node/register");
193
+ env.SWCRC = "true";
194
+ }
195
+ else {
196
+ args.push("--transpileOnly");
197
+ }
198
+ args.push(filePath);
188
199
  }
189
- args.push(filePath);
190
200
  const r = (0, child_process_1.spawn)(cmd, args, {
191
201
  env,
192
202
  });
@@ -253,13 +263,6 @@ async function captureCustom(filePath, filesCsv, jsonPath, gqlCapture) {
253
263
  // TODO delete all of this eventually
254
264
  // TODO configurable paths eventually
255
265
  // 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
266
  const ignore = [
264
267
  "**/generated/**",
265
268
  "**/tests/**",
@@ -268,35 +271,44 @@ async function captureCustom(filePath, filesCsv, jsonPath, gqlCapture) {
268
271
  // ignore test files.
269
272
  "**/*.test.ts",
270
273
  ];
274
+ const customGraphQLEntFiles = glob.sync(path.join(filePath, "/ent/**/*.ts"), {
275
+ // not in action files since we can't customize payloads (yet?)
276
+ ignore: [...ignore, "**/actions/**"],
277
+ });
271
278
  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
279
  ignore: ignore,
275
280
  });
276
281
  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
282
  ignore: ignore,
280
283
  });
281
- const files = rootFiles.concat(customGQLResolvers, customGQLMutations);
284
+ const files = customGraphQLEntFiles
285
+ .concat(customGQLResolvers, customGQLMutations)
286
+ .filter(fileImportsGraphQLDecorators);
282
287
  await requireFiles(files);
283
288
  }
289
+ function fileImportsGraphQLDecorators(file) {
290
+ const contents = fs.readFileSync(file, {
291
+ encoding: "utf8",
292
+ });
293
+ return /@snowtop\/ent\/graphql(?:\/graphql)?/.test(contents);
294
+ }
284
295
  async function requireFiles(files) {
285
- await Promise.all(files.map(async (file) => {
286
- if (fs.existsSync(file)) {
287
- try {
288
- await require(file);
296
+ for (const file of files) {
297
+ if (!fs.existsSync(file)) {
298
+ throw new Error(`file ${file} doesn't exist`);
299
+ }
300
+ try {
301
+ if (isBunRuntime()) {
302
+ await Promise.resolve(`${(0, url_1.pathToFileURL)(file).href}`).then(s => __importStar(require(s)));
289
303
  }
290
- catch (e) {
291
- throw new Error(`${e.message} loading ${file}`);
304
+ else {
305
+ await require(file);
292
306
  }
293
307
  }
294
- else {
295
- throw new Error(`file ${file} doesn't exist`);
308
+ catch (e) {
309
+ throw new Error(`${e.message} loading ${file}`);
296
310
  }
297
- })).catch((err) => {
298
- throw new Error(err);
299
- });
311
+ }
300
312
  }
301
313
  // filePath is path-to-src
302
314
  async function parseImports(filePath) {
@@ -350,9 +362,10 @@ async function main() {
350
362
  // for local dev, get the one from the file system. otherwise, get the one
351
363
  // from node_modules
352
364
  let gqlCapture;
353
- if (process.env.LOCAL_SCRIPT_PATH) {
365
+ if (process.env.LOCAL_SCRIPT_PATH || isBunRuntime()) {
354
366
  const r = require("../graphql/graphql");
355
367
  gqlCapture = r.GQLCapture;
368
+ gqlCapture.enable(true);
356
369
  }
357
370
  else {
358
371
  const r = require(gqlPath);
@@ -0,0 +1,3 @@
1
+ export function parseArgs(argv: any): {
2
+ _: never[];
3
+ };
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ function coerceValue(value) {
3
+ if (value === "true") {
4
+ return true;
5
+ }
6
+ if (value === "false") {
7
+ return false;
8
+ }
9
+ return value;
10
+ }
11
+ function normalizeKey(key) {
12
+ return key.replace(/-/g, "_");
13
+ }
14
+ function setOption(result, key, value) {
15
+ result[key] = value;
16
+ const normalized = normalizeKey(key);
17
+ if (normalized !== key) {
18
+ result[normalized] = value;
19
+ }
20
+ }
21
+ function parseArgs(argv) {
22
+ const result = { _: [] };
23
+ const args = argv || process.argv.slice(2);
24
+ for (let i = 0; i < args.length; i++) {
25
+ const arg = args[i];
26
+ if (arg === "--") {
27
+ result._.push(...args.slice(i + 1));
28
+ break;
29
+ }
30
+ if (!arg.startsWith("-") || arg === "-") {
31
+ result._.push(arg);
32
+ continue;
33
+ }
34
+ if (arg.startsWith("--no-")) {
35
+ setOption(result, normalizeKey(arg.slice(5)), false);
36
+ continue;
37
+ }
38
+ if (arg.startsWith("--")) {
39
+ const body = arg.slice(2);
40
+ const idx = body.indexOf("=");
41
+ if (idx !== -1) {
42
+ setOption(result, normalizeKey(body.slice(0, idx)), coerceValue(body.slice(idx + 1)));
43
+ continue;
44
+ }
45
+ const key = normalizeKey(body);
46
+ const next = args[i + 1];
47
+ if (next !== undefined && !next.startsWith("-")) {
48
+ setOption(result, key, coerceValue(next));
49
+ i++;
50
+ }
51
+ else {
52
+ setOption(result, key, true);
53
+ }
54
+ continue;
55
+ }
56
+ const shorts = arg.slice(1);
57
+ if (shorts.length === 1) {
58
+ const next = args[i + 1];
59
+ if (next !== undefined && !next.startsWith("-")) {
60
+ setOption(result, shorts, coerceValue(next));
61
+ i++;
62
+ }
63
+ else {
64
+ setOption(result, shorts, true);
65
+ }
66
+ continue;
67
+ }
68
+ for (const ch of shorts) {
69
+ setOption(result, ch, true);
70
+ }
71
+ }
72
+ return result;
73
+ }
74
+ module.exports = { parseArgs };
@@ -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
  }