@effect-gql/cli 1.1.0 → 1.2.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/README.md +27 -1
- package/bin.cjs +1134 -15
- package/bin.cjs.map +1 -1
- package/bin.js +1132 -14
- package/bin.js.map +1 -1
- package/index.cjs +1131 -6
- package/index.cjs.map +1 -1
- package/index.d.cts +72 -2
- package/index.d.ts +72 -2
- package/index.js +1125 -6
- package/index.js.map +1 -1
- package/package.json +1 -1
package/bin.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { Effect, Console } from 'effect';
|
|
2
|
+
import { Effect, Console, Option, pipe } from 'effect';
|
|
3
3
|
import { lexicographicSortSchema, printSchema } from '@effect-gql/core';
|
|
4
|
-
import * as
|
|
5
|
-
import * as
|
|
4
|
+
import * as fs3 from 'fs';
|
|
5
|
+
import * as path2 from 'path';
|
|
6
|
+
import * as readline from 'readline';
|
|
7
|
+
import { spawn } from 'child_process';
|
|
6
8
|
|
|
7
9
|
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
8
10
|
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
@@ -11,8 +13,8 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
11
13
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
12
14
|
});
|
|
13
15
|
var loadSchema = (modulePath) => Effect.gen(function* () {
|
|
14
|
-
const absolutePath =
|
|
15
|
-
if (!
|
|
16
|
+
const absolutePath = path2.resolve(process.cwd(), modulePath);
|
|
17
|
+
if (!fs3.existsSync(absolutePath)) {
|
|
16
18
|
return yield* Effect.fail(new Error(`File not found: ${absolutePath}`));
|
|
17
19
|
}
|
|
18
20
|
const module = yield* Effect.tryPromise({
|
|
@@ -53,20 +55,20 @@ var run = (options) => Effect.gen(function* () {
|
|
|
53
55
|
const schema = yield* loadSchema(options.modulePath);
|
|
54
56
|
const sdl = generateSDL(schema, { sort: options.sort });
|
|
55
57
|
if (options.output) {
|
|
56
|
-
const outputPath =
|
|
57
|
-
|
|
58
|
+
const outputPath = path2.resolve(process.cwd(), options.output);
|
|
59
|
+
fs3.writeFileSync(outputPath, sdl);
|
|
58
60
|
yield* Console.log(`Schema written to ${outputPath}`);
|
|
59
61
|
} else {
|
|
60
62
|
yield* Console.log(sdl);
|
|
61
63
|
}
|
|
62
64
|
});
|
|
63
65
|
var watch2 = (options) => Effect.gen(function* () {
|
|
64
|
-
const absolutePath =
|
|
65
|
-
const dir =
|
|
66
|
+
const absolutePath = path2.resolve(process.cwd(), options.modulePath);
|
|
67
|
+
const dir = path2.dirname(absolutePath);
|
|
66
68
|
yield* Console.log(`Watching for changes in ${dir}...`);
|
|
67
69
|
yield* run(options).pipe(Effect.catchAll((error) => Console.error(`Error: ${error.message}`)));
|
|
68
70
|
yield* Effect.async(() => {
|
|
69
|
-
const watcher =
|
|
71
|
+
const watcher = fs3.watch(dir, { recursive: true }, (_, filename) => {
|
|
70
72
|
if (filename?.endsWith(".ts") || filename?.endsWith(".js")) {
|
|
71
73
|
Effect.runPromise(
|
|
72
74
|
run(options).pipe(
|
|
@@ -156,6 +158,1123 @@ var runGenerateSchema = (args) => Effect.gen(function* () {
|
|
|
156
158
|
}
|
|
157
159
|
});
|
|
158
160
|
|
|
161
|
+
// src/commands/create/types.ts
|
|
162
|
+
var SERVER_TYPES = ["node", "bun", "express", "web"];
|
|
163
|
+
var isValidServerType = (value) => SERVER_TYPES.includes(value);
|
|
164
|
+
var PACKAGE_MANAGERS = ["npm", "pnpm", "yarn", "bun"];
|
|
165
|
+
var isValidPackageManager = (value) => PACKAGE_MANAGERS.includes(value);
|
|
166
|
+
|
|
167
|
+
// src/commands/create/args.ts
|
|
168
|
+
var parseArgs2 = (args) => {
|
|
169
|
+
const positional = [];
|
|
170
|
+
let serverType;
|
|
171
|
+
let directory = Option.none();
|
|
172
|
+
let monorepo = Option.none();
|
|
173
|
+
let skipInstall = false;
|
|
174
|
+
let packageManager = Option.none();
|
|
175
|
+
let interactive = false;
|
|
176
|
+
for (let i = 0; i < args.length; i++) {
|
|
177
|
+
const arg = args[i];
|
|
178
|
+
if (arg === "-h" || arg === "--help") {
|
|
179
|
+
return { help: true };
|
|
180
|
+
} else if (arg === "-i" || arg === "--interactive") {
|
|
181
|
+
interactive = true;
|
|
182
|
+
} else if (arg === "-s" || arg === "--server-type") {
|
|
183
|
+
const value = args[++i];
|
|
184
|
+
if (!value) {
|
|
185
|
+
return { error: "Missing value for --server-type" };
|
|
186
|
+
}
|
|
187
|
+
if (!isValidServerType(value)) {
|
|
188
|
+
return {
|
|
189
|
+
error: `Invalid server type: ${value}. Must be one of: ${SERVER_TYPES.join(", ")}`
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
serverType = value;
|
|
193
|
+
} else if (arg === "-d" || arg === "--directory") {
|
|
194
|
+
const value = args[++i];
|
|
195
|
+
if (!value) {
|
|
196
|
+
return { error: "Missing value for --directory" };
|
|
197
|
+
}
|
|
198
|
+
directory = Option.some(value);
|
|
199
|
+
} else if (arg === "--monorepo") {
|
|
200
|
+
monorepo = Option.some(true);
|
|
201
|
+
} else if (arg === "--skip-install") {
|
|
202
|
+
skipInstall = true;
|
|
203
|
+
} else if (arg === "--package-manager") {
|
|
204
|
+
const value = args[++i];
|
|
205
|
+
if (!value) {
|
|
206
|
+
return { error: "Missing value for --package-manager" };
|
|
207
|
+
}
|
|
208
|
+
if (!isValidPackageManager(value)) {
|
|
209
|
+
return { error: `Invalid package manager: ${value}. Must be one of: npm, pnpm, yarn, bun` };
|
|
210
|
+
}
|
|
211
|
+
packageManager = Option.some(value);
|
|
212
|
+
} else if (!arg.startsWith("-")) {
|
|
213
|
+
positional.push(arg);
|
|
214
|
+
} else {
|
|
215
|
+
return { error: `Unknown option: ${arg}` };
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (interactive) {
|
|
219
|
+
return { interactive: true };
|
|
220
|
+
}
|
|
221
|
+
if (args.length === 0) {
|
|
222
|
+
return { interactive: true };
|
|
223
|
+
}
|
|
224
|
+
if (positional.length === 0) {
|
|
225
|
+
return {
|
|
226
|
+
error: "Missing project name. Use --interactive or provide: effect-gql create <name> --server-type <type>"
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
if (!serverType) {
|
|
230
|
+
return { error: "Missing --server-type. Must be one of: " + SERVER_TYPES.join(", ") };
|
|
231
|
+
}
|
|
232
|
+
const options = {
|
|
233
|
+
name: positional[0],
|
|
234
|
+
serverType,
|
|
235
|
+
directory,
|
|
236
|
+
monorepo,
|
|
237
|
+
skipInstall,
|
|
238
|
+
packageManager
|
|
239
|
+
};
|
|
240
|
+
return { options };
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
// src/commands/create/templates/package-json.ts
|
|
244
|
+
var VERSIONS = {
|
|
245
|
+
core: "^1.1.0",
|
|
246
|
+
node: "^1.1.0",
|
|
247
|
+
bun: "^1.1.0",
|
|
248
|
+
express: "^1.1.0",
|
|
249
|
+
web: "^1.1.0",
|
|
250
|
+
effect: "^3.19.0",
|
|
251
|
+
platform: "^0.94.0",
|
|
252
|
+
platformNode: "^0.104.0",
|
|
253
|
+
platformBun: "^0.87.0",
|
|
254
|
+
graphql: "^16.0.0",
|
|
255
|
+
expressLib: "^5.0.0",
|
|
256
|
+
tsx: "^4.19.0",
|
|
257
|
+
typescript: "^5.0.0"
|
|
258
|
+
};
|
|
259
|
+
var getDependencies = (serverType, isMonorepo) => {
|
|
260
|
+
const toVersion = (pkg, version) => isMonorepo && pkg.startsWith("@effect-gql/") ? "workspace:*" : version;
|
|
261
|
+
const baseDeps = {
|
|
262
|
+
"@effect-gql/core": toVersion("@effect-gql/core", VERSIONS.core),
|
|
263
|
+
"@effect/platform": VERSIONS.platform,
|
|
264
|
+
effect: VERSIONS.effect,
|
|
265
|
+
graphql: VERSIONS.graphql
|
|
266
|
+
};
|
|
267
|
+
const baseDevDeps = {
|
|
268
|
+
tsx: VERSIONS.tsx,
|
|
269
|
+
typescript: VERSIONS.typescript
|
|
270
|
+
};
|
|
271
|
+
switch (serverType) {
|
|
272
|
+
case "node":
|
|
273
|
+
return {
|
|
274
|
+
dependencies: {
|
|
275
|
+
...baseDeps,
|
|
276
|
+
"@effect-gql/node": toVersion("@effect-gql/node", VERSIONS.node),
|
|
277
|
+
"@effect/platform-node": VERSIONS.platformNode
|
|
278
|
+
},
|
|
279
|
+
devDependencies: baseDevDeps
|
|
280
|
+
};
|
|
281
|
+
case "bun":
|
|
282
|
+
return {
|
|
283
|
+
dependencies: {
|
|
284
|
+
...baseDeps,
|
|
285
|
+
"@effect-gql/bun": toVersion("@effect-gql/bun", VERSIONS.bun),
|
|
286
|
+
"@effect/platform-bun": VERSIONS.platformBun
|
|
287
|
+
},
|
|
288
|
+
devDependencies: {
|
|
289
|
+
typescript: VERSIONS.typescript
|
|
290
|
+
// Bun has built-in TypeScript support, no tsx needed
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
case "express":
|
|
294
|
+
return {
|
|
295
|
+
dependencies: {
|
|
296
|
+
...baseDeps,
|
|
297
|
+
"@effect-gql/express": toVersion("@effect-gql/express", VERSIONS.express),
|
|
298
|
+
express: VERSIONS.expressLib
|
|
299
|
+
},
|
|
300
|
+
devDependencies: {
|
|
301
|
+
...baseDevDeps,
|
|
302
|
+
"@types/express": "^5.0.0"
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
case "web":
|
|
306
|
+
return {
|
|
307
|
+
dependencies: {
|
|
308
|
+
...baseDeps,
|
|
309
|
+
"@effect-gql/web": toVersion("@effect-gql/web", VERSIONS.web)
|
|
310
|
+
},
|
|
311
|
+
devDependencies: baseDevDeps
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
var getScripts = (serverType) => {
|
|
316
|
+
switch (serverType) {
|
|
317
|
+
case "node":
|
|
318
|
+
return {
|
|
319
|
+
start: "tsx src/index.ts",
|
|
320
|
+
dev: "tsx watch src/index.ts",
|
|
321
|
+
build: "tsc",
|
|
322
|
+
typecheck: "tsc --noEmit"
|
|
323
|
+
};
|
|
324
|
+
case "bun":
|
|
325
|
+
return {
|
|
326
|
+
start: "bun run src/index.ts",
|
|
327
|
+
dev: "bun --watch src/index.ts",
|
|
328
|
+
build: "bun build src/index.ts --outdir dist --target bun",
|
|
329
|
+
typecheck: "tsc --noEmit"
|
|
330
|
+
};
|
|
331
|
+
case "express":
|
|
332
|
+
return {
|
|
333
|
+
start: "tsx src/index.ts",
|
|
334
|
+
dev: "tsx watch src/index.ts",
|
|
335
|
+
build: "tsc",
|
|
336
|
+
typecheck: "tsc --noEmit"
|
|
337
|
+
};
|
|
338
|
+
case "web":
|
|
339
|
+
return {
|
|
340
|
+
start: "tsx src/index.ts",
|
|
341
|
+
dev: "tsx watch src/index.ts",
|
|
342
|
+
build: "tsc",
|
|
343
|
+
typecheck: "tsc --noEmit"
|
|
344
|
+
// Users will typically add wrangler/deno commands as needed
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
var generatePackageJson = (ctx) => {
|
|
349
|
+
const deps = getDependencies(ctx.serverType, ctx.isMonorepo);
|
|
350
|
+
const pkg = {
|
|
351
|
+
name: ctx.name,
|
|
352
|
+
version: "0.0.1",
|
|
353
|
+
private: true,
|
|
354
|
+
type: "module",
|
|
355
|
+
scripts: getScripts(ctx.serverType),
|
|
356
|
+
dependencies: deps.dependencies,
|
|
357
|
+
devDependencies: deps.devDependencies
|
|
358
|
+
};
|
|
359
|
+
return JSON.stringify(pkg, null, 2) + "\n";
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
// src/commands/create/templates/tsconfig.ts
|
|
363
|
+
var generateTsConfig = (ctx) => {
|
|
364
|
+
const config = ctx.isMonorepo ? {
|
|
365
|
+
// In a monorepo, extend from root tsconfig
|
|
366
|
+
extends: "../../tsconfig.json",
|
|
367
|
+
compilerOptions: {
|
|
368
|
+
outDir: "./dist",
|
|
369
|
+
rootDir: "./src",
|
|
370
|
+
noEmit: true
|
|
371
|
+
},
|
|
372
|
+
include: ["src/**/*"],
|
|
373
|
+
exclude: ["node_modules", "dist"]
|
|
374
|
+
} : {
|
|
375
|
+
// Standalone project config
|
|
376
|
+
compilerOptions: {
|
|
377
|
+
target: "ES2022",
|
|
378
|
+
module: "NodeNext",
|
|
379
|
+
moduleResolution: "NodeNext",
|
|
380
|
+
lib: ["ES2022"],
|
|
381
|
+
outDir: "./dist",
|
|
382
|
+
rootDir: "./src",
|
|
383
|
+
strict: true,
|
|
384
|
+
esModuleInterop: true,
|
|
385
|
+
skipLibCheck: true,
|
|
386
|
+
forceConsistentCasingInFileNames: true,
|
|
387
|
+
declaration: true,
|
|
388
|
+
declarationMap: true,
|
|
389
|
+
sourceMap: true,
|
|
390
|
+
noEmit: true,
|
|
391
|
+
// Effect requires these for decorators
|
|
392
|
+
experimentalDecorators: true,
|
|
393
|
+
emitDecoratorMetadata: true
|
|
394
|
+
},
|
|
395
|
+
include: ["src/**/*"],
|
|
396
|
+
exclude: ["node_modules", "dist"]
|
|
397
|
+
};
|
|
398
|
+
return JSON.stringify(config, null, 2) + "\n";
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
// src/commands/create/templates/server.ts
|
|
402
|
+
var generateNodeServer = (ctx) => `/**
|
|
403
|
+
* ${ctx.name} - GraphQL Server
|
|
404
|
+
*
|
|
405
|
+
* A GraphQL server built with Effect GQL and Node.js.
|
|
406
|
+
*/
|
|
407
|
+
|
|
408
|
+
import { Effect, Layer } from "effect"
|
|
409
|
+
import * as S from "effect/Schema"
|
|
410
|
+
import { HttpRouter, HttpServerResponse } from "@effect/platform"
|
|
411
|
+
import { GraphQLSchemaBuilder, query, mutation, makeGraphQLRouter } from "@effect-gql/core"
|
|
412
|
+
import { serve } from "@effect-gql/node"
|
|
413
|
+
|
|
414
|
+
// =============================================================================
|
|
415
|
+
// Domain Models
|
|
416
|
+
// =============================================================================
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Define your types with Effect Schema.
|
|
420
|
+
* This single definition is used for both TypeScript types AND GraphQL types.
|
|
421
|
+
*/
|
|
422
|
+
const User = S.Struct({
|
|
423
|
+
id: S.String,
|
|
424
|
+
name: S.String,
|
|
425
|
+
email: S.String,
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
type User = S.Schema.Type<typeof User>
|
|
429
|
+
|
|
430
|
+
// =============================================================================
|
|
431
|
+
// In-Memory Data Store (replace with your database)
|
|
432
|
+
// =============================================================================
|
|
433
|
+
|
|
434
|
+
const users: User[] = [
|
|
435
|
+
{ id: "1", name: "Alice", email: "alice@example.com" },
|
|
436
|
+
{ id: "2", name: "Bob", email: "bob@example.com" },
|
|
437
|
+
]
|
|
438
|
+
|
|
439
|
+
// =============================================================================
|
|
440
|
+
// GraphQL Schema
|
|
441
|
+
// =============================================================================
|
|
442
|
+
|
|
443
|
+
const schema = GraphQLSchemaBuilder.empty
|
|
444
|
+
.pipe(
|
|
445
|
+
// Simple query
|
|
446
|
+
query("hello", {
|
|
447
|
+
type: S.String,
|
|
448
|
+
description: "Returns a friendly greeting",
|
|
449
|
+
resolve: () => Effect.succeed("Hello from ${ctx.name}!"),
|
|
450
|
+
}),
|
|
451
|
+
|
|
452
|
+
// Query with arguments
|
|
453
|
+
query("user", {
|
|
454
|
+
args: S.Struct({ id: S.String }),
|
|
455
|
+
type: S.NullOr(User),
|
|
456
|
+
description: "Get a user by ID",
|
|
457
|
+
resolve: (args) => Effect.succeed(users.find((u) => u.id === args.id) ?? null),
|
|
458
|
+
}),
|
|
459
|
+
|
|
460
|
+
// Query that returns a list
|
|
461
|
+
query("users", {
|
|
462
|
+
type: S.Array(User),
|
|
463
|
+
description: "Get all users",
|
|
464
|
+
resolve: () => Effect.succeed(users),
|
|
465
|
+
}),
|
|
466
|
+
|
|
467
|
+
// Mutation
|
|
468
|
+
mutation("createUser", {
|
|
469
|
+
args: S.Struct({
|
|
470
|
+
name: S.String,
|
|
471
|
+
email: S.String,
|
|
472
|
+
}),
|
|
473
|
+
type: User,
|
|
474
|
+
description: "Create a new user",
|
|
475
|
+
resolve: (args) =>
|
|
476
|
+
Effect.sync(() => {
|
|
477
|
+
const newUser: User = {
|
|
478
|
+
id: String(users.length + 1),
|
|
479
|
+
name: args.name,
|
|
480
|
+
email: args.email,
|
|
481
|
+
}
|
|
482
|
+
users.push(newUser)
|
|
483
|
+
return newUser
|
|
484
|
+
}),
|
|
485
|
+
})
|
|
486
|
+
)
|
|
487
|
+
.buildSchema()
|
|
488
|
+
|
|
489
|
+
// =============================================================================
|
|
490
|
+
// HTTP Router
|
|
491
|
+
// =============================================================================
|
|
492
|
+
|
|
493
|
+
const graphqlRouter = makeGraphQLRouter(schema, Layer.empty, {
|
|
494
|
+
path: "/graphql",
|
|
495
|
+
graphiql: {
|
|
496
|
+
path: "/graphiql",
|
|
497
|
+
endpoint: "/graphql",
|
|
498
|
+
},
|
|
499
|
+
})
|
|
500
|
+
|
|
501
|
+
const router = HttpRouter.empty.pipe(
|
|
502
|
+
HttpRouter.get("/health", HttpServerResponse.json({ status: "ok" })),
|
|
503
|
+
HttpRouter.concat(graphqlRouter)
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
// =============================================================================
|
|
507
|
+
// Server Startup
|
|
508
|
+
// =============================================================================
|
|
509
|
+
|
|
510
|
+
serve(router, Layer.empty, {
|
|
511
|
+
port: 4000,
|
|
512
|
+
onStart: (url: string) => {
|
|
513
|
+
console.log(\`Server ready at \${url}\`)
|
|
514
|
+
console.log(\`GraphQL endpoint: \${url}/graphql\`)
|
|
515
|
+
console.log(\`GraphiQL playground: \${url}/graphiql\`)
|
|
516
|
+
console.log(\`Health check: \${url}/health\`)
|
|
517
|
+
},
|
|
518
|
+
})
|
|
519
|
+
`;
|
|
520
|
+
var generateBunServer = (ctx) => `/**
|
|
521
|
+
* ${ctx.name} - GraphQL Server
|
|
522
|
+
*
|
|
523
|
+
* A GraphQL server built with Effect GQL and Bun.
|
|
524
|
+
*/
|
|
525
|
+
|
|
526
|
+
import { Effect, Layer } from "effect"
|
|
527
|
+
import * as S from "effect/Schema"
|
|
528
|
+
import { HttpRouter, HttpServerResponse } from "@effect/platform"
|
|
529
|
+
import { GraphQLSchemaBuilder, query, mutation, makeGraphQLRouter } from "@effect-gql/core"
|
|
530
|
+
import { serve } from "@effect-gql/bun"
|
|
531
|
+
|
|
532
|
+
// =============================================================================
|
|
533
|
+
// Domain Models
|
|
534
|
+
// =============================================================================
|
|
535
|
+
|
|
536
|
+
const User = S.Struct({
|
|
537
|
+
id: S.String,
|
|
538
|
+
name: S.String,
|
|
539
|
+
email: S.String,
|
|
540
|
+
})
|
|
541
|
+
|
|
542
|
+
type User = S.Schema.Type<typeof User>
|
|
543
|
+
|
|
544
|
+
// =============================================================================
|
|
545
|
+
// In-Memory Data Store (replace with your database)
|
|
546
|
+
// =============================================================================
|
|
547
|
+
|
|
548
|
+
const users: User[] = [
|
|
549
|
+
{ id: "1", name: "Alice", email: "alice@example.com" },
|
|
550
|
+
{ id: "2", name: "Bob", email: "bob@example.com" },
|
|
551
|
+
]
|
|
552
|
+
|
|
553
|
+
// =============================================================================
|
|
554
|
+
// GraphQL Schema
|
|
555
|
+
// =============================================================================
|
|
556
|
+
|
|
557
|
+
const schema = GraphQLSchemaBuilder.empty
|
|
558
|
+
.pipe(
|
|
559
|
+
query("hello", {
|
|
560
|
+
type: S.String,
|
|
561
|
+
description: "Returns a friendly greeting",
|
|
562
|
+
resolve: () => Effect.succeed("Hello from ${ctx.name}!"),
|
|
563
|
+
}),
|
|
564
|
+
|
|
565
|
+
query("user", {
|
|
566
|
+
args: S.Struct({ id: S.String }),
|
|
567
|
+
type: S.NullOr(User),
|
|
568
|
+
description: "Get a user by ID",
|
|
569
|
+
resolve: (args) => Effect.succeed(users.find((u) => u.id === args.id) ?? null),
|
|
570
|
+
}),
|
|
571
|
+
|
|
572
|
+
query("users", {
|
|
573
|
+
type: S.Array(User),
|
|
574
|
+
description: "Get all users",
|
|
575
|
+
resolve: () => Effect.succeed(users),
|
|
576
|
+
}),
|
|
577
|
+
|
|
578
|
+
mutation("createUser", {
|
|
579
|
+
args: S.Struct({
|
|
580
|
+
name: S.String,
|
|
581
|
+
email: S.String,
|
|
582
|
+
}),
|
|
583
|
+
type: User,
|
|
584
|
+
description: "Create a new user",
|
|
585
|
+
resolve: (args) =>
|
|
586
|
+
Effect.sync(() => {
|
|
587
|
+
const newUser: User = {
|
|
588
|
+
id: String(users.length + 1),
|
|
589
|
+
name: args.name,
|
|
590
|
+
email: args.email,
|
|
591
|
+
}
|
|
592
|
+
users.push(newUser)
|
|
593
|
+
return newUser
|
|
594
|
+
}),
|
|
595
|
+
})
|
|
596
|
+
)
|
|
597
|
+
.buildSchema()
|
|
598
|
+
|
|
599
|
+
// =============================================================================
|
|
600
|
+
// HTTP Router
|
|
601
|
+
// =============================================================================
|
|
602
|
+
|
|
603
|
+
const graphqlRouter = makeGraphQLRouter(schema, Layer.empty, {
|
|
604
|
+
path: "/graphql",
|
|
605
|
+
graphiql: {
|
|
606
|
+
path: "/graphiql",
|
|
607
|
+
endpoint: "/graphql",
|
|
608
|
+
},
|
|
609
|
+
})
|
|
610
|
+
|
|
611
|
+
const router = HttpRouter.empty.pipe(
|
|
612
|
+
HttpRouter.get("/health", HttpServerResponse.json({ status: "ok" })),
|
|
613
|
+
HttpRouter.concat(graphqlRouter)
|
|
614
|
+
)
|
|
615
|
+
|
|
616
|
+
// =============================================================================
|
|
617
|
+
// Server Startup
|
|
618
|
+
// =============================================================================
|
|
619
|
+
|
|
620
|
+
serve(router, Layer.empty, {
|
|
621
|
+
port: 4000,
|
|
622
|
+
onStart: (url: string) => {
|
|
623
|
+
console.log(\`Server ready at \${url}\`)
|
|
624
|
+
console.log(\`GraphQL endpoint: \${url}/graphql\`)
|
|
625
|
+
console.log(\`GraphiQL playground: \${url}/graphiql\`)
|
|
626
|
+
console.log(\`Health check: \${url}/health\`)
|
|
627
|
+
},
|
|
628
|
+
})
|
|
629
|
+
`;
|
|
630
|
+
var generateExpressServer = (ctx) => `/**
|
|
631
|
+
* ${ctx.name} - GraphQL Server
|
|
632
|
+
*
|
|
633
|
+
* A GraphQL server built with Effect GQL and Express.
|
|
634
|
+
*/
|
|
635
|
+
|
|
636
|
+
import express from "express"
|
|
637
|
+
import { Effect, Layer } from "effect"
|
|
638
|
+
import * as S from "effect/Schema"
|
|
639
|
+
import { GraphQLSchemaBuilder, query, mutation, makeGraphQLRouter } from "@effect-gql/core"
|
|
640
|
+
import { toMiddleware } from "@effect-gql/express"
|
|
641
|
+
|
|
642
|
+
// =============================================================================
|
|
643
|
+
// Domain Models
|
|
644
|
+
// =============================================================================
|
|
645
|
+
|
|
646
|
+
const User = S.Struct({
|
|
647
|
+
id: S.String,
|
|
648
|
+
name: S.String,
|
|
649
|
+
email: S.String,
|
|
650
|
+
})
|
|
651
|
+
|
|
652
|
+
type User = S.Schema.Type<typeof User>
|
|
653
|
+
|
|
654
|
+
// =============================================================================
|
|
655
|
+
// In-Memory Data Store (replace with your database)
|
|
656
|
+
// =============================================================================
|
|
657
|
+
|
|
658
|
+
const users: User[] = [
|
|
659
|
+
{ id: "1", name: "Alice", email: "alice@example.com" },
|
|
660
|
+
{ id: "2", name: "Bob", email: "bob@example.com" },
|
|
661
|
+
]
|
|
662
|
+
|
|
663
|
+
// =============================================================================
|
|
664
|
+
// GraphQL Schema
|
|
665
|
+
// =============================================================================
|
|
666
|
+
|
|
667
|
+
const schema = GraphQLSchemaBuilder.empty
|
|
668
|
+
.pipe(
|
|
669
|
+
query("hello", {
|
|
670
|
+
type: S.String,
|
|
671
|
+
description: "Returns a friendly greeting",
|
|
672
|
+
resolve: () => Effect.succeed("Hello from ${ctx.name}!"),
|
|
673
|
+
}),
|
|
674
|
+
|
|
675
|
+
query("user", {
|
|
676
|
+
args: S.Struct({ id: S.String }),
|
|
677
|
+
type: S.NullOr(User),
|
|
678
|
+
description: "Get a user by ID",
|
|
679
|
+
resolve: (args) => Effect.succeed(users.find((u) => u.id === args.id) ?? null),
|
|
680
|
+
}),
|
|
681
|
+
|
|
682
|
+
query("users", {
|
|
683
|
+
type: S.Array(User),
|
|
684
|
+
description: "Get all users",
|
|
685
|
+
resolve: () => Effect.succeed(users),
|
|
686
|
+
}),
|
|
687
|
+
|
|
688
|
+
mutation("createUser", {
|
|
689
|
+
args: S.Struct({
|
|
690
|
+
name: S.String,
|
|
691
|
+
email: S.String,
|
|
692
|
+
}),
|
|
693
|
+
type: User,
|
|
694
|
+
description: "Create a new user",
|
|
695
|
+
resolve: (args) =>
|
|
696
|
+
Effect.sync(() => {
|
|
697
|
+
const newUser: User = {
|
|
698
|
+
id: String(users.length + 1),
|
|
699
|
+
name: args.name,
|
|
700
|
+
email: args.email,
|
|
701
|
+
}
|
|
702
|
+
users.push(newUser)
|
|
703
|
+
return newUser
|
|
704
|
+
}),
|
|
705
|
+
})
|
|
706
|
+
)
|
|
707
|
+
.buildSchema()
|
|
708
|
+
|
|
709
|
+
// =============================================================================
|
|
710
|
+
// GraphQL Router
|
|
711
|
+
// =============================================================================
|
|
712
|
+
|
|
713
|
+
const graphqlRouter = makeGraphQLRouter(schema, Layer.empty, {
|
|
714
|
+
path: "/graphql",
|
|
715
|
+
graphiql: {
|
|
716
|
+
path: "/graphiql",
|
|
717
|
+
endpoint: "/graphql",
|
|
718
|
+
},
|
|
719
|
+
})
|
|
720
|
+
|
|
721
|
+
// =============================================================================
|
|
722
|
+
// Express App
|
|
723
|
+
// =============================================================================
|
|
724
|
+
|
|
725
|
+
const app = express()
|
|
726
|
+
app.use(express.json())
|
|
727
|
+
|
|
728
|
+
// Health check
|
|
729
|
+
app.get("/health", (_, res) => {
|
|
730
|
+
res.json({ status: "ok" })
|
|
731
|
+
})
|
|
732
|
+
|
|
733
|
+
// Mount GraphQL middleware
|
|
734
|
+
app.use(toMiddleware(graphqlRouter, Layer.empty))
|
|
735
|
+
|
|
736
|
+
// =============================================================================
|
|
737
|
+
// Server Startup
|
|
738
|
+
// =============================================================================
|
|
739
|
+
|
|
740
|
+
const port = process.env.PORT || 4000
|
|
741
|
+
|
|
742
|
+
app.listen(port, () => {
|
|
743
|
+
console.log(\`Server ready at http://localhost:\${port}\`)
|
|
744
|
+
console.log(\`GraphQL endpoint: http://localhost:\${port}/graphql\`)
|
|
745
|
+
console.log(\`GraphiQL playground: http://localhost:\${port}/graphiql\`)
|
|
746
|
+
console.log(\`Health check: http://localhost:\${port}/health\`)
|
|
747
|
+
})
|
|
748
|
+
`;
|
|
749
|
+
var generateWebHandler = (ctx) => `/**
|
|
750
|
+
* ${ctx.name} - GraphQL Server
|
|
751
|
+
*
|
|
752
|
+
* A GraphQL server built with Effect GQL using Web standard APIs.
|
|
753
|
+
* Compatible with Cloudflare Workers, Deno, and other WASM runtimes.
|
|
754
|
+
*/
|
|
755
|
+
|
|
756
|
+
import { Effect, Layer } from "effect"
|
|
757
|
+
import * as S from "effect/Schema"
|
|
758
|
+
import { GraphQLSchemaBuilder, query, mutation, makeGraphQLRouter } from "@effect-gql/core"
|
|
759
|
+
import { toHandler } from "@effect-gql/web"
|
|
760
|
+
|
|
761
|
+
// =============================================================================
|
|
762
|
+
// Domain Models
|
|
763
|
+
// =============================================================================
|
|
764
|
+
|
|
765
|
+
const User = S.Struct({
|
|
766
|
+
id: S.String,
|
|
767
|
+
name: S.String,
|
|
768
|
+
email: S.String,
|
|
769
|
+
})
|
|
770
|
+
|
|
771
|
+
type User = S.Schema.Type<typeof User>
|
|
772
|
+
|
|
773
|
+
// =============================================================================
|
|
774
|
+
// In-Memory Data Store (replace with your database)
|
|
775
|
+
// =============================================================================
|
|
776
|
+
|
|
777
|
+
const users: User[] = [
|
|
778
|
+
{ id: "1", name: "Alice", email: "alice@example.com" },
|
|
779
|
+
{ id: "2", name: "Bob", email: "bob@example.com" },
|
|
780
|
+
]
|
|
781
|
+
|
|
782
|
+
// =============================================================================
|
|
783
|
+
// GraphQL Schema
|
|
784
|
+
// =============================================================================
|
|
785
|
+
|
|
786
|
+
const schema = GraphQLSchemaBuilder.empty
|
|
787
|
+
.pipe(
|
|
788
|
+
query("hello", {
|
|
789
|
+
type: S.String,
|
|
790
|
+
description: "Returns a friendly greeting",
|
|
791
|
+
resolve: () => Effect.succeed("Hello from ${ctx.name}!"),
|
|
792
|
+
}),
|
|
793
|
+
|
|
794
|
+
query("user", {
|
|
795
|
+
args: S.Struct({ id: S.String }),
|
|
796
|
+
type: S.NullOr(User),
|
|
797
|
+
description: "Get a user by ID",
|
|
798
|
+
resolve: (args) => Effect.succeed(users.find((u) => u.id === args.id) ?? null),
|
|
799
|
+
}),
|
|
800
|
+
|
|
801
|
+
query("users", {
|
|
802
|
+
type: S.Array(User),
|
|
803
|
+
description: "Get all users",
|
|
804
|
+
resolve: () => Effect.succeed(users),
|
|
805
|
+
}),
|
|
806
|
+
|
|
807
|
+
mutation("createUser", {
|
|
808
|
+
args: S.Struct({
|
|
809
|
+
name: S.String,
|
|
810
|
+
email: S.String,
|
|
811
|
+
}),
|
|
812
|
+
type: User,
|
|
813
|
+
description: "Create a new user",
|
|
814
|
+
resolve: (args) =>
|
|
815
|
+
Effect.sync(() => {
|
|
816
|
+
const newUser: User = {
|
|
817
|
+
id: String(users.length + 1),
|
|
818
|
+
name: args.name,
|
|
819
|
+
email: args.email,
|
|
820
|
+
}
|
|
821
|
+
users.push(newUser)
|
|
822
|
+
return newUser
|
|
823
|
+
}),
|
|
824
|
+
})
|
|
825
|
+
)
|
|
826
|
+
.buildSchema()
|
|
827
|
+
|
|
828
|
+
// =============================================================================
|
|
829
|
+
// GraphQL Router
|
|
830
|
+
// =============================================================================
|
|
831
|
+
|
|
832
|
+
const graphqlRouter = makeGraphQLRouter(schema, Layer.empty, {
|
|
833
|
+
path: "/graphql",
|
|
834
|
+
graphiql: {
|
|
835
|
+
path: "/graphiql",
|
|
836
|
+
endpoint: "/graphql",
|
|
837
|
+
},
|
|
838
|
+
})
|
|
839
|
+
|
|
840
|
+
// =============================================================================
|
|
841
|
+
// Web Handler
|
|
842
|
+
// =============================================================================
|
|
843
|
+
|
|
844
|
+
const { handler } = toHandler(graphqlRouter, Layer.empty)
|
|
845
|
+
|
|
846
|
+
/**
|
|
847
|
+
* Export for Cloudflare Workers
|
|
848
|
+
*/
|
|
849
|
+
export default {
|
|
850
|
+
async fetch(request: Request): Promise<Response> {
|
|
851
|
+
const url = new URL(request.url)
|
|
852
|
+
|
|
853
|
+
// Health check
|
|
854
|
+
if (url.pathname === "/health") {
|
|
855
|
+
return new Response(JSON.stringify({ status: "ok" }), {
|
|
856
|
+
headers: { "Content-Type": "application/json" },
|
|
857
|
+
})
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
return handler(request)
|
|
861
|
+
},
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
/**
|
|
865
|
+
* For local development with Deno or other runtimes, you can use:
|
|
866
|
+
*
|
|
867
|
+
* Deno.serve((request) => {
|
|
868
|
+
* const url = new URL(request.url)
|
|
869
|
+
* if (url.pathname === "/health") {
|
|
870
|
+
* return new Response(JSON.stringify({ status: "ok" }), {
|
|
871
|
+
* headers: { "Content-Type": "application/json" },
|
|
872
|
+
* })
|
|
873
|
+
* }
|
|
874
|
+
* return handler(request)
|
|
875
|
+
* })
|
|
876
|
+
*/
|
|
877
|
+
`;
|
|
878
|
+
var generateServerTemplate = (ctx) => {
|
|
879
|
+
switch (ctx.serverType) {
|
|
880
|
+
case "node":
|
|
881
|
+
return generateNodeServer(ctx);
|
|
882
|
+
case "bun":
|
|
883
|
+
return generateBunServer(ctx);
|
|
884
|
+
case "express":
|
|
885
|
+
return generateExpressServer(ctx);
|
|
886
|
+
case "web":
|
|
887
|
+
return generateWebHandler(ctx);
|
|
888
|
+
}
|
|
889
|
+
};
|
|
890
|
+
|
|
891
|
+
// src/commands/create/templates/index.ts
|
|
892
|
+
var generateGitignore = () => `# Dependencies
|
|
893
|
+
node_modules/
|
|
894
|
+
|
|
895
|
+
# Build output
|
|
896
|
+
dist/
|
|
897
|
+
*.tsbuildinfo
|
|
898
|
+
|
|
899
|
+
# IDE
|
|
900
|
+
.idea/
|
|
901
|
+
.vscode/
|
|
902
|
+
*.swp
|
|
903
|
+
*.swo
|
|
904
|
+
|
|
905
|
+
# OS
|
|
906
|
+
.DS_Store
|
|
907
|
+
Thumbs.db
|
|
908
|
+
|
|
909
|
+
# Environment
|
|
910
|
+
.env
|
|
911
|
+
.env.local
|
|
912
|
+
.env.*.local
|
|
913
|
+
|
|
914
|
+
# Logs
|
|
915
|
+
*.log
|
|
916
|
+
npm-debug.log*
|
|
917
|
+
yarn-debug.log*
|
|
918
|
+
yarn-error.log*
|
|
919
|
+
|
|
920
|
+
# Testing
|
|
921
|
+
coverage/
|
|
922
|
+
`;
|
|
923
|
+
var generateProject = (ctx) => [
|
|
924
|
+
{
|
|
925
|
+
path: "package.json",
|
|
926
|
+
content: generatePackageJson(ctx)
|
|
927
|
+
},
|
|
928
|
+
{
|
|
929
|
+
path: "tsconfig.json",
|
|
930
|
+
content: generateTsConfig(ctx)
|
|
931
|
+
},
|
|
932
|
+
{
|
|
933
|
+
path: "src/index.ts",
|
|
934
|
+
content: generateServerTemplate(ctx)
|
|
935
|
+
},
|
|
936
|
+
{
|
|
937
|
+
path: ".gitignore",
|
|
938
|
+
content: generateGitignore()
|
|
939
|
+
}
|
|
940
|
+
];
|
|
941
|
+
var fileExists = (filePath) => Effect.sync(() => fs3.existsSync(filePath));
|
|
942
|
+
var readFile = (filePath) => Effect.try({
|
|
943
|
+
try: () => fs3.readFileSync(filePath, "utf-8"),
|
|
944
|
+
catch: (error) => new Error(`Failed to read ${filePath}: ${error}`)
|
|
945
|
+
});
|
|
946
|
+
var detectPackageManager = (dir) => Effect.gen(function* () {
|
|
947
|
+
if (yield* fileExists(path2.join(dir, "pnpm-lock.yaml"))) return "pnpm";
|
|
948
|
+
if (yield* fileExists(path2.join(dir, "bun.lockb"))) return "bun";
|
|
949
|
+
if (yield* fileExists(path2.join(dir, "yarn.lock"))) return "yarn";
|
|
950
|
+
return "npm";
|
|
951
|
+
});
|
|
952
|
+
var detectMonorepo = (targetDir) => Effect.gen(function* () {
|
|
953
|
+
let currentDir = path2.dirname(path2.resolve(targetDir));
|
|
954
|
+
const root = path2.parse(currentDir).root;
|
|
955
|
+
while (currentDir !== root) {
|
|
956
|
+
const pnpmWorkspacePath = path2.join(currentDir, "pnpm-workspace.yaml");
|
|
957
|
+
if (yield* fileExists(pnpmWorkspacePath)) {
|
|
958
|
+
return {
|
|
959
|
+
isMonorepo: true,
|
|
960
|
+
packageManager: Option.some("pnpm"),
|
|
961
|
+
workspacesRoot: Option.some(currentDir)
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
const pkgPath = path2.join(currentDir, "package.json");
|
|
965
|
+
if (yield* fileExists(pkgPath)) {
|
|
966
|
+
const content = yield* readFile(pkgPath).pipe(Effect.catchAll(() => Effect.succeed("")));
|
|
967
|
+
if (content) {
|
|
968
|
+
try {
|
|
969
|
+
const pkg = JSON.parse(content);
|
|
970
|
+
if (pkg.workspaces) {
|
|
971
|
+
const pm = yield* detectPackageManager(currentDir);
|
|
972
|
+
return {
|
|
973
|
+
isMonorepo: true,
|
|
974
|
+
packageManager: Option.some(pm),
|
|
975
|
+
workspacesRoot: Option.some(currentDir)
|
|
976
|
+
};
|
|
977
|
+
}
|
|
978
|
+
} catch {
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
const turboPath = path2.join(currentDir, "turbo.json");
|
|
983
|
+
if (yield* fileExists(turboPath)) {
|
|
984
|
+
const pm = yield* detectPackageManager(currentDir);
|
|
985
|
+
return {
|
|
986
|
+
isMonorepo: true,
|
|
987
|
+
packageManager: Option.some(pm),
|
|
988
|
+
workspacesRoot: Option.some(currentDir)
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
currentDir = path2.dirname(currentDir);
|
|
992
|
+
}
|
|
993
|
+
return {
|
|
994
|
+
isMonorepo: false,
|
|
995
|
+
packageManager: Option.none(),
|
|
996
|
+
workspacesRoot: Option.none()
|
|
997
|
+
};
|
|
998
|
+
});
|
|
999
|
+
var getInstallCommand = (pm) => {
|
|
1000
|
+
switch (pm) {
|
|
1001
|
+
case "npm":
|
|
1002
|
+
return "npm install";
|
|
1003
|
+
case "pnpm":
|
|
1004
|
+
return "pnpm install";
|
|
1005
|
+
case "yarn":
|
|
1006
|
+
return "yarn";
|
|
1007
|
+
case "bun":
|
|
1008
|
+
return "bun install";
|
|
1009
|
+
}
|
|
1010
|
+
};
|
|
1011
|
+
var getRunPrefix = (pm) => {
|
|
1012
|
+
switch (pm) {
|
|
1013
|
+
case "npm":
|
|
1014
|
+
return "npm run";
|
|
1015
|
+
case "pnpm":
|
|
1016
|
+
return "pnpm";
|
|
1017
|
+
case "yarn":
|
|
1018
|
+
return "yarn";
|
|
1019
|
+
case "bun":
|
|
1020
|
+
return "bun run";
|
|
1021
|
+
}
|
|
1022
|
+
};
|
|
1023
|
+
|
|
1024
|
+
// src/commands/create/scaffolder.ts
|
|
1025
|
+
var validateDirectory = (targetDir) => Effect.try({
|
|
1026
|
+
try: () => {
|
|
1027
|
+
if (fs3.existsSync(targetDir)) {
|
|
1028
|
+
const files = fs3.readdirSync(targetDir);
|
|
1029
|
+
if (files.length > 0) {
|
|
1030
|
+
throw new Error(`Directory ${targetDir} is not empty`);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
},
|
|
1034
|
+
catch: (error) => error instanceof Error ? error : new Error(`Failed to validate directory: ${error}`)
|
|
1035
|
+
});
|
|
1036
|
+
var mkdirp = (dir) => Effect.try({
|
|
1037
|
+
try: () => fs3.mkdirSync(dir, { recursive: true }),
|
|
1038
|
+
catch: (error) => new Error(`Failed to create directory ${dir}: ${error}`)
|
|
1039
|
+
});
|
|
1040
|
+
var writeFile = (filePath, content) => Effect.gen(function* () {
|
|
1041
|
+
yield* mkdirp(path2.dirname(filePath));
|
|
1042
|
+
yield* Effect.try({
|
|
1043
|
+
try: () => fs3.writeFileSync(filePath, content, "utf-8"),
|
|
1044
|
+
catch: (error) => new Error(`Failed to write ${filePath}: ${error}`)
|
|
1045
|
+
});
|
|
1046
|
+
});
|
|
1047
|
+
var runCommand = (command, args, cwd) => Effect.async((resume) => {
|
|
1048
|
+
const child = spawn(command, args, {
|
|
1049
|
+
cwd,
|
|
1050
|
+
stdio: "inherit",
|
|
1051
|
+
shell: true
|
|
1052
|
+
});
|
|
1053
|
+
child.on("close", (code) => {
|
|
1054
|
+
if (code === 0) {
|
|
1055
|
+
resume(Effect.succeed(void 0));
|
|
1056
|
+
} else {
|
|
1057
|
+
resume(Effect.fail(new Error(`Command failed with exit code ${code}`)));
|
|
1058
|
+
}
|
|
1059
|
+
});
|
|
1060
|
+
child.on("error", (error) => {
|
|
1061
|
+
resume(Effect.fail(new Error(`Failed to run command: ${error.message}`)));
|
|
1062
|
+
});
|
|
1063
|
+
return Effect.sync(() => {
|
|
1064
|
+
child.kill();
|
|
1065
|
+
});
|
|
1066
|
+
});
|
|
1067
|
+
var installDependencies = (targetDir, packageManager) => Effect.gen(function* () {
|
|
1068
|
+
yield* Console.log("");
|
|
1069
|
+
yield* Console.log("Installing dependencies...");
|
|
1070
|
+
const installCmd = getInstallCommand(packageManager);
|
|
1071
|
+
const [cmd, ...args] = installCmd.split(" ");
|
|
1072
|
+
yield* runCommand(cmd, args, targetDir);
|
|
1073
|
+
yield* Console.log("Dependencies installed successfully!");
|
|
1074
|
+
});
|
|
1075
|
+
var printSuccessMessage = (ctx, targetDir) => Effect.gen(function* () {
|
|
1076
|
+
const relativePath = path2.relative(process.cwd(), targetDir) || ".";
|
|
1077
|
+
const runPrefix = getRunPrefix(ctx.packageManager);
|
|
1078
|
+
yield* Console.log("");
|
|
1079
|
+
yield* Console.log(`Successfully created ${ctx.name}!`);
|
|
1080
|
+
yield* Console.log("");
|
|
1081
|
+
yield* Console.log("Next steps:");
|
|
1082
|
+
yield* Console.log(` cd ${relativePath}`);
|
|
1083
|
+
yield* Console.log(` ${runPrefix} dev`);
|
|
1084
|
+
yield* Console.log("");
|
|
1085
|
+
yield* Console.log("Your GraphQL server will be available at:");
|
|
1086
|
+
yield* Console.log(" http://localhost:4000/graphql");
|
|
1087
|
+
yield* Console.log(" http://localhost:4000/graphiql (playground)");
|
|
1088
|
+
yield* Console.log("");
|
|
1089
|
+
});
|
|
1090
|
+
var scaffold = (options) => Effect.gen(function* () {
|
|
1091
|
+
const targetDir = pipe(
|
|
1092
|
+
options.directory,
|
|
1093
|
+
Option.map((dir) => path2.resolve(process.cwd(), dir)),
|
|
1094
|
+
Option.getOrElse(() => path2.resolve(process.cwd(), options.name))
|
|
1095
|
+
);
|
|
1096
|
+
const monorepoInfo = yield* detectMonorepo(targetDir);
|
|
1097
|
+
const isMonorepo = pipe(
|
|
1098
|
+
options.monorepo,
|
|
1099
|
+
Option.getOrElse(() => monorepoInfo.isMonorepo)
|
|
1100
|
+
);
|
|
1101
|
+
const packageManager = yield* pipe(
|
|
1102
|
+
options.packageManager,
|
|
1103
|
+
Option.orElse(() => monorepoInfo.packageManager),
|
|
1104
|
+
Option.match({
|
|
1105
|
+
onNone: () => detectPackageManager(process.cwd()),
|
|
1106
|
+
onSome: (pm) => Effect.succeed(pm)
|
|
1107
|
+
})
|
|
1108
|
+
);
|
|
1109
|
+
const ctx = {
|
|
1110
|
+
name: options.name,
|
|
1111
|
+
serverType: options.serverType,
|
|
1112
|
+
isMonorepo,
|
|
1113
|
+
packageManager
|
|
1114
|
+
};
|
|
1115
|
+
yield* Console.log("");
|
|
1116
|
+
yield* Console.log(`Creating ${ctx.name} with ${ctx.serverType} server...`);
|
|
1117
|
+
yield* validateDirectory(targetDir);
|
|
1118
|
+
const files = generateProject(ctx);
|
|
1119
|
+
for (const file of files) {
|
|
1120
|
+
const filePath = path2.join(targetDir, file.path);
|
|
1121
|
+
yield* writeFile(filePath, file.content);
|
|
1122
|
+
yield* Console.log(` Created ${file.path}`);
|
|
1123
|
+
}
|
|
1124
|
+
if (!options.skipInstall) {
|
|
1125
|
+
yield* installDependencies(targetDir, packageManager).pipe(
|
|
1126
|
+
Effect.catchAll(
|
|
1127
|
+
(error) => Console.log(`Warning: Failed to install dependencies: ${error.message}`).pipe(
|
|
1128
|
+
Effect.andThen(
|
|
1129
|
+
Console.log(
|
|
1130
|
+
"You can install them manually by running: " + getInstallCommand(packageManager)
|
|
1131
|
+
)
|
|
1132
|
+
)
|
|
1133
|
+
)
|
|
1134
|
+
)
|
|
1135
|
+
);
|
|
1136
|
+
} else {
|
|
1137
|
+
yield* Console.log("");
|
|
1138
|
+
yield* Console.log(
|
|
1139
|
+
`Skipping dependency installation. Run '${getInstallCommand(packageManager)}' to install.`
|
|
1140
|
+
);
|
|
1141
|
+
}
|
|
1142
|
+
yield* printSuccessMessage(ctx, targetDir);
|
|
1143
|
+
});
|
|
1144
|
+
|
|
1145
|
+
// src/commands/create/index.ts
|
|
1146
|
+
var printCreateHelp = () => {
|
|
1147
|
+
console.log(`
|
|
1148
|
+
Usage: effect-gql create <name> --server-type <type> [options]
|
|
1149
|
+
|
|
1150
|
+
Create a new Effect GraphQL project.
|
|
1151
|
+
|
|
1152
|
+
Arguments:
|
|
1153
|
+
name Package/project name
|
|
1154
|
+
|
|
1155
|
+
Required Options:
|
|
1156
|
+
-s, --server-type Server type: node, bun, express, web
|
|
1157
|
+
|
|
1158
|
+
Optional Options:
|
|
1159
|
+
-d, --directory Target directory (default: ./<name>)
|
|
1160
|
+
--monorepo Create as workspace package (auto-detected)
|
|
1161
|
+
--skip-install Skip dependency installation
|
|
1162
|
+
--package-manager Package manager: npm, pnpm, yarn, bun
|
|
1163
|
+
-i, --interactive Interactive mode (prompts for options)
|
|
1164
|
+
-h, --help Show this help message
|
|
1165
|
+
|
|
1166
|
+
Server Types:
|
|
1167
|
+
node Node.js server using @effect-gql/node
|
|
1168
|
+
Best for: General Node.js deployments
|
|
1169
|
+
|
|
1170
|
+
bun Bun server using @effect-gql/bun
|
|
1171
|
+
Best for: Bun runtime with native WebSocket support
|
|
1172
|
+
|
|
1173
|
+
express Express middleware using @effect-gql/express
|
|
1174
|
+
Best for: Integrating into existing Express apps
|
|
1175
|
+
|
|
1176
|
+
web Web standard handler using @effect-gql/web
|
|
1177
|
+
Best for: Cloudflare Workers, Deno, edge runtimes
|
|
1178
|
+
|
|
1179
|
+
Examples:
|
|
1180
|
+
effect-gql create my-api --server-type node
|
|
1181
|
+
effect-gql create my-api -s bun
|
|
1182
|
+
effect-gql create my-api -s express -d ./packages/api --monorepo
|
|
1183
|
+
effect-gql create my-api -s web --skip-install
|
|
1184
|
+
effect-gql create --interactive
|
|
1185
|
+
`);
|
|
1186
|
+
};
|
|
1187
|
+
var prompt = (question) => Effect.async((resume) => {
|
|
1188
|
+
const rl = readline.createInterface({
|
|
1189
|
+
input: process.stdin,
|
|
1190
|
+
output: process.stdout
|
|
1191
|
+
});
|
|
1192
|
+
rl.question(question, (answer) => {
|
|
1193
|
+
rl.close();
|
|
1194
|
+
resume(Effect.succeed(answer.trim()));
|
|
1195
|
+
});
|
|
1196
|
+
rl.on("error", (error) => {
|
|
1197
|
+
rl.close();
|
|
1198
|
+
resume(Effect.fail(new Error(`Input error: ${error.message}`)));
|
|
1199
|
+
});
|
|
1200
|
+
return Effect.sync(() => rl.close());
|
|
1201
|
+
});
|
|
1202
|
+
var selectPrompt = (question, options) => Effect.gen(function* () {
|
|
1203
|
+
yield* Console.log(question);
|
|
1204
|
+
options.forEach((opt, i) => {
|
|
1205
|
+
console.log(` ${i + 1}) ${opt.label}`);
|
|
1206
|
+
});
|
|
1207
|
+
const answer = yield* prompt("Enter number: ");
|
|
1208
|
+
const index = parseInt(answer, 10) - 1;
|
|
1209
|
+
if (isNaN(index) || index < 0 || index >= options.length) {
|
|
1210
|
+
yield* Console.log(`Invalid selection. Please enter 1-${options.length}.`);
|
|
1211
|
+
return yield* selectPrompt(question, options);
|
|
1212
|
+
}
|
|
1213
|
+
return options[index].value;
|
|
1214
|
+
});
|
|
1215
|
+
var confirmPrompt = (question, defaultValue) => Effect.gen(function* () {
|
|
1216
|
+
const hint = "[y/N]";
|
|
1217
|
+
const answer = yield* prompt(`${question} ${hint} `);
|
|
1218
|
+
if (answer === "") return defaultValue;
|
|
1219
|
+
const lower = answer.toLowerCase();
|
|
1220
|
+
if (lower === "y" || lower === "yes") return true;
|
|
1221
|
+
if (lower === "n" || lower === "no") return false;
|
|
1222
|
+
yield* Console.log("Please answer 'y' or 'n'.");
|
|
1223
|
+
return yield* confirmPrompt(question, defaultValue);
|
|
1224
|
+
});
|
|
1225
|
+
var runInteractive = () => Effect.gen(function* () {
|
|
1226
|
+
yield* Console.log("");
|
|
1227
|
+
yield* Console.log("Create a new Effect GraphQL project");
|
|
1228
|
+
yield* Console.log("====================================");
|
|
1229
|
+
yield* Console.log("");
|
|
1230
|
+
const name = yield* prompt("Project name: ");
|
|
1231
|
+
if (!name) {
|
|
1232
|
+
return yield* Effect.fail(new Error("Project name is required"));
|
|
1233
|
+
}
|
|
1234
|
+
if (!/^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(name)) {
|
|
1235
|
+
yield* Console.log("Warning: Name may not be a valid npm package name.");
|
|
1236
|
+
}
|
|
1237
|
+
const serverType = yield* selectPrompt("Select server type:", [
|
|
1238
|
+
{ label: "Node.js (@effect-gql/node)", value: "node" },
|
|
1239
|
+
{ label: "Bun (@effect-gql/bun)", value: "bun" },
|
|
1240
|
+
{ label: "Express (@effect-gql/express)", value: "express" },
|
|
1241
|
+
{ label: "Web/Workers (@effect-gql/web)", value: "web" }
|
|
1242
|
+
]);
|
|
1243
|
+
const monorepo = yield* confirmPrompt("Create as monorepo workspace package?", false);
|
|
1244
|
+
const packageManager = monorepo ? Option.some(
|
|
1245
|
+
yield* selectPrompt("Select package manager:", [
|
|
1246
|
+
{ label: "pnpm", value: "pnpm" },
|
|
1247
|
+
{ label: "npm", value: "npm" },
|
|
1248
|
+
{ label: "yarn", value: "yarn" },
|
|
1249
|
+
{ label: "bun", value: "bun" }
|
|
1250
|
+
])
|
|
1251
|
+
) : Option.none();
|
|
1252
|
+
return {
|
|
1253
|
+
name,
|
|
1254
|
+
serverType,
|
|
1255
|
+
directory: Option.none(),
|
|
1256
|
+
monorepo: Option.some(monorepo),
|
|
1257
|
+
skipInstall: false,
|
|
1258
|
+
packageManager
|
|
1259
|
+
};
|
|
1260
|
+
});
|
|
1261
|
+
var runCreate = (args) => Effect.gen(function* () {
|
|
1262
|
+
const parsed = parseArgs2(args);
|
|
1263
|
+
if ("help" in parsed) {
|
|
1264
|
+
printCreateHelp();
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1267
|
+
if ("error" in parsed) {
|
|
1268
|
+
yield* Console.error(`Error: ${parsed.error}`);
|
|
1269
|
+
yield* Console.log("");
|
|
1270
|
+
printCreateHelp();
|
|
1271
|
+
process.exitCode = 1;
|
|
1272
|
+
return;
|
|
1273
|
+
}
|
|
1274
|
+
const options = "interactive" in parsed ? yield* runInteractive() : parsed.options;
|
|
1275
|
+
yield* scaffold(options);
|
|
1276
|
+
});
|
|
1277
|
+
|
|
159
1278
|
// src/bin.ts
|
|
160
1279
|
var VERSION = "0.1.0";
|
|
161
1280
|
var printHelp = () => {
|
|
@@ -166,7 +1285,7 @@ Usage: effect-gql <command> [options]
|
|
|
166
1285
|
|
|
167
1286
|
Commands:
|
|
168
1287
|
generate-schema Generate GraphQL SDL from a schema module
|
|
169
|
-
create Create a new Effect GraphQL project
|
|
1288
|
+
create Create a new Effect GraphQL project
|
|
170
1289
|
|
|
171
1290
|
Options:
|
|
172
1291
|
-h, --help Show this help message
|
|
@@ -175,7 +1294,7 @@ Options:
|
|
|
175
1294
|
Examples:
|
|
176
1295
|
effect-gql generate-schema ./src/schema.ts
|
|
177
1296
|
effect-gql generate-schema ./src/schema.ts -o schema.graphql
|
|
178
|
-
effect-gql create my-app
|
|
1297
|
+
effect-gql create my-app --server-type node
|
|
179
1298
|
|
|
180
1299
|
Run 'effect-gql <command> --help' for command-specific help.
|
|
181
1300
|
`);
|
|
@@ -200,8 +1319,7 @@ var main = Effect.gen(function* () {
|
|
|
200
1319
|
yield* runGenerateSchema(commandArgs);
|
|
201
1320
|
break;
|
|
202
1321
|
case "create":
|
|
203
|
-
yield*
|
|
204
|
-
yield* Console.log("It will help you scaffold new Effect GraphQL projects.");
|
|
1322
|
+
yield* runCreate(commandArgs);
|
|
205
1323
|
break;
|
|
206
1324
|
default:
|
|
207
1325
|
yield* Console.error(`Unknown command: ${command}`);
|