silgi 0.0.1 → 0.0.2

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/bin/silgi.mjs ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ 'use strict'
3
+ import '../cli/index.mjs'
@@ -0,0 +1,911 @@
1
+ import { loadConfig } from 'c12';
2
+ import { defineCommand } from 'citty';
3
+ import { writeFileSync, promises } from 'node:fs';
4
+ import { dirname, relative, join } from 'node:path';
5
+ import antfu from '@antfu/eslint-config';
6
+ import consola from 'consola';
7
+ import defu from 'defu';
8
+ import { ESLint } from 'eslint';
9
+ import { createJiti } from 'jiti';
10
+ import { resolve } from 'pathe';
11
+ import { camelCase, pascalCase } from 'scule';
12
+ import { parseSync } from '@oxc-parser/wasm';
13
+ import { writeFile } from 'node:fs/promises';
14
+
15
+ async function generateClientGenTS(storage, outputDir) {
16
+ const services = Object.keys(storage);
17
+ let clientGenContent = `
18
+ `;
19
+ const imports = [];
20
+ for (const service of services) {
21
+ const clientStore = storage[service];
22
+ let clientStoreContent = `
23
+ `;
24
+ if (Array.isArray(clientStore?.graphql?.query) && clientStore.graphql.query.length > 0) {
25
+ const queryContent = `
26
+ query: {
27
+ ${clientStore.graphql.query.map((query) => {
28
+ imports.push(`import {${query.key}} from '${query.path}'`);
29
+ return `${query.newKey}: {
30
+ input: VariablesOf<typeof ${query.key}>
31
+ output: ResultOf<typeof ${query.key}>
32
+ }`;
33
+ }).join("\n")}
34
+ }
35
+ `;
36
+ clientStoreContent += queryContent;
37
+ }
38
+ if (Array.isArray(clientStore?.graphql?.mutation) && clientStore.graphql.mutation.length > 0) {
39
+ const mutationContent = `
40
+ mutation: {
41
+ ${clientStore.graphql.mutation.map((mutation) => {
42
+ imports.push(`import {${mutation.key}} from '${mutation.path}'`);
43
+ return `${mutation.newKey}: {
44
+ input: VariablesOf<typeof ${mutation.key}>
45
+ output: ResultOf<typeof ${mutation.key}>
46
+ }`;
47
+ }).join("\n")}
48
+ }
49
+ `;
50
+ clientStoreContent += mutationContent;
51
+ }
52
+ if (Array.isArray(clientStore?.zod) && clientStore.zod.length > 0) {
53
+ const zodContent = `
54
+ zod: {
55
+ ${clientStore.zod.map((zod) => {
56
+ imports.push(`import {${zod.key}} from '${zod.path}'`);
57
+ return `${zod.newKey}: {
58
+ input: Zod.infer<typeof ${zod.key}>
59
+ }`;
60
+ }).join("\n")}
61
+ }
62
+ `;
63
+ clientStoreContent += zodContent;
64
+ }
65
+ clientGenContent += `
66
+ ${service}: {
67
+ ${clientStoreContent}
68
+ }
69
+ `;
70
+ }
71
+ const content = `
72
+ // This file is generated by Silgi. Do not modify it manually.
73
+ ${imports.join("\n")}
74
+
75
+ export interface SilgiClientStore {
76
+ ${clientGenContent}
77
+ }
78
+ `;
79
+ writeFileSync(outputDir, content, "utf-8");
80
+ }
81
+
82
+ class SchemaParser {
83
+ parseExports(content, filePath) {
84
+ const ast = parseSync(content, { sourceType: "module", sourceFilename: filePath });
85
+ return {
86
+ exportTS: this.parseTypeExports(ast),
87
+ exportVariables: this.parseVariableExports(ast)
88
+ };
89
+ }
90
+ parseTypeExports(ast) {
91
+ return ast.program.body.filter((i) => i.type === "ExportNamedDeclaration").filter(
92
+ (i) => i.declaration?.type === "TSInterfaceDeclaration" || i.declaration?.type === "TSTypeAliasDeclaration"
93
+ ).flatMap((node) => node.declaration?.id.name);
94
+ }
95
+ parseVariableExports(ast) {
96
+ return ast.program.body.filter((i) => i.type === "ExportNamedDeclaration").filter((i) => i.declaration?.type === "VariableDeclaration").flatMap((node) => node.declaration?.declarations.map((decl) => decl.id.name));
97
+ }
98
+ }
99
+
100
+ class TypescriptGenerator {
101
+ storage;
102
+ outputDir;
103
+ constructor(storage, outputDir) {
104
+ this.storage = storage;
105
+ this.outputDir = outputDir;
106
+ }
107
+ async generate() {
108
+ try {
109
+ const { scopes, modules } = this.extractScopesAndModules();
110
+ const processedData = this.processStorageData(scopes, modules);
111
+ const content = this.generateInterfaceContent(processedData);
112
+ await this.writeOutputFile(content);
113
+ consola.success(`Server interface in ${this.outputDir}`);
114
+ } catch (error) {
115
+ consola.error("Error generating Silgi interface:", error);
116
+ throw error;
117
+ }
118
+ }
119
+ extractScopesAndModules() {
120
+ const scopes = new Set(
121
+ Object.keys(this.storage.schemaObjects || {}).filter((scope) => scope !== "modules")
122
+ );
123
+ const modules = new Set(
124
+ Object.keys(this.storage.modules || {})
125
+ );
126
+ return { scopes, modules };
127
+ }
128
+ processStorageData(scopes, modules) {
129
+ const zodInterfacesImports = [];
130
+ const importsString = [];
131
+ const scopeDatas = [];
132
+ const modulesData = [];
133
+ for (const scope of scopes) {
134
+ if (!this.storage.schemaObjects)
135
+ throw new Error("No schemaObjects found in storage");
136
+ const scopeData = this.storage.schemaObjects[scope];
137
+ const processed = this.processScopeData(scope, scopeData, zodInterfacesImports, importsString);
138
+ scopeDatas.push(processed);
139
+ }
140
+ for (const module of modules) {
141
+ if (this.storage.modules && this.storage.modules[module]) {
142
+ const moduleData = this.storage.modules[module];
143
+ const processed = this.processModuleData(module, moduleData, importsString);
144
+ modulesData.push(processed);
145
+ }
146
+ }
147
+ return { scopeDatas, modulesData, importsString, zodInterfacesImports };
148
+ }
149
+ processScopeData(scope, scopeData, zodInterfacesImports, importsString) {
150
+ const shareds = [];
151
+ const services = [];
152
+ const context = [];
153
+ if (scopeData.zod) {
154
+ if (scopeData.zod.silgiZodSchema && scopeData.zod.silgiZodSchema?.length > 0) {
155
+ scopeData.zod.silgiZodSchema?.forEach((schema) => {
156
+ const name = camelCase(`${scope}_${schema.serviceName}silgiZodSchema`);
157
+ zodInterfacesImports.push(`import { silgiZodSchema as ${name} } from '${schema.path}'`);
158
+ shareds.push({
159
+ type: "const",
160
+ from: "zod",
161
+ scope: "true",
162
+ key: schema.serviceName,
163
+ value: name
164
+ });
165
+ });
166
+ }
167
+ if (scopeData.zod.SilgiZodSchema && scopeData.zod.SilgiZodSchema?.length > 0) {
168
+ scopeData.zod.SilgiZodSchema.forEach((schema) => {
169
+ const name = pascalCase(`${scope}_${schema.serviceName}SilgiZodSchemaType`);
170
+ zodInterfacesImports.push(`import type { SilgiZodSchema as ${name} } from '${schema.path}'`);
171
+ shareds.push({
172
+ type: "type",
173
+ from: "zod",
174
+ key: schema.serviceName,
175
+ value: name
176
+ });
177
+ });
178
+ }
179
+ }
180
+ if (scopeData.service && scopeData.service.silgiService) {
181
+ if (scopeData.service.silgiService?.length > 0) {
182
+ scopeData.service.silgiService.forEach((schema) => {
183
+ const name = camelCase(`${scope}_${schema.serviceName}silgiService`);
184
+ importsString.push(`import { silgiService as ${name} } from '${schema.path}'`);
185
+ services.push({
186
+ type: "const",
187
+ from: "service",
188
+ key: schema.serviceName,
189
+ value: name
190
+ });
191
+ });
192
+ }
193
+ if (scopeData.service.SilgiService && scopeData.service.SilgiService.length > 0) {
194
+ scopeData.service.SilgiService.forEach((schema) => {
195
+ const name = pascalCase(`${scope}_${schema.serviceName}SilgiServiceType`);
196
+ importsString.push(`import type { SilgiService as ${name} } from '${schema.path}'`);
197
+ services.push({
198
+ type: "type",
199
+ from: "service",
200
+ key: schema.serviceName,
201
+ value: name
202
+ });
203
+ });
204
+ }
205
+ }
206
+ if (scopeData.context) {
207
+ const processedPaths = /* @__PURE__ */ new Set();
208
+ if (scopeData.context?.SilgiContext && scopeData.context.SilgiContext.length > 0) {
209
+ scopeData.context.SilgiContext.forEach((schema) => {
210
+ if (processedPaths.has(schema.path))
211
+ return;
212
+ processedPaths.add(schema.path);
213
+ const pathSegments = schema.path.split("/");
214
+ const uniqueIdentifier = pathSegments.slice(-2).filter((segment) => segment !== "index").join("");
215
+ const name = pascalCase(`${scope}_${uniqueIdentifier}SilgiContextType`);
216
+ importsString.push(`import type { SilgiContext as ${name} } from '${schema.path}'`);
217
+ context.push({
218
+ type: "type",
219
+ from: "context",
220
+ key: scope,
221
+ value: name,
222
+ path: schema.path
223
+ // Store path for debugging
224
+ });
225
+ });
226
+ }
227
+ }
228
+ if (scopeData.shared) {
229
+ if (scopeData.shared.silgiGlobalShared && scopeData.shared.silgiGlobalShared?.length > 0) {
230
+ scopeData.shared.silgiGlobalShared.forEach((schema) => {
231
+ const name = camelCase(`${scope}_${schema.serviceName}silgiGlobalShared`);
232
+ importsString.push(`import { silgiGlobalShared as ${name} } from '${schema.path}'`);
233
+ shareds.push({
234
+ type: "const",
235
+ from: "shared",
236
+ key: schema.serviceName,
237
+ value: name,
238
+ global: schema.global
239
+ });
240
+ });
241
+ }
242
+ if (scopeData.shared.SilgiGlobalShared && scopeData.shared.SilgiGlobalShared?.length > 0) {
243
+ scopeData.shared.SilgiGlobalShared.forEach((schema) => {
244
+ const name = pascalCase(`${scope}_${schema.serviceName}SilgiGlobalSharedType`);
245
+ importsString.push(`import type { SilgiGlobalShared as ${name} } from '${schema.path}'`);
246
+ shareds.push({
247
+ type: "type",
248
+ from: "shared",
249
+ key: schema.serviceName,
250
+ value: name,
251
+ global: schema.global
252
+ });
253
+ });
254
+ }
255
+ }
256
+ return {
257
+ scope,
258
+ shareds,
259
+ services,
260
+ context
261
+ };
262
+ }
263
+ processModuleData(module, moduleData, importsString) {
264
+ const methodsConfig = [];
265
+ const context = [];
266
+ const services = [];
267
+ const modules = [];
268
+ const moduleConfig = [];
269
+ if (moduleData.methodsConfig && moduleData.methodsConfig.silgiMethodConfig) {
270
+ if (moduleData.methodsConfig.silgiMethodConfig?.length > 0) {
271
+ moduleData.methodsConfig.silgiMethodConfig.forEach((schema) => {
272
+ const name = camelCase(`${module}_${schema.serviceName}silgiMethodConfig`);
273
+ importsString.push(`import { silgiMethodConfig as ${name} } from '${schema.path}'`);
274
+ methodsConfig.push({
275
+ type: "const",
276
+ from: "methodsConfig",
277
+ key: schema.serviceName,
278
+ value: name
279
+ });
280
+ });
281
+ }
282
+ if (moduleData.methodsConfig.SilgiMethodConfig && moduleData.methodsConfig.SilgiMethodConfig?.length > 0) {
283
+ moduleData.methodsConfig.SilgiMethodConfig.forEach((schema) => {
284
+ const name = pascalCase(`${module}_${schema.serviceName}SilgiMethodConfigType`);
285
+ importsString.push(`import type { SilgiMethodConfig as ${name} } from '${schema.path}'`);
286
+ methodsConfig.push({
287
+ type: "type",
288
+ from: "methodsConfig",
289
+ key: schema.serviceName,
290
+ value: name
291
+ });
292
+ });
293
+ }
294
+ }
295
+ if (moduleData.context) {
296
+ const processedPaths = /* @__PURE__ */ new Set();
297
+ if (moduleData.context.SilgiContext && moduleData.context.SilgiContext?.length > 0) {
298
+ moduleData.context.SilgiContext.forEach(
299
+ (schema) => {
300
+ if (processedPaths.has(schema.path))
301
+ return;
302
+ processedPaths.add(schema.path);
303
+ const pathSegments = schema.path.split("/");
304
+ const uniqueIdentifier = pathSegments.slice(-2).filter((segment) => segment !== "index").join("");
305
+ const name = pascalCase(`${module}_${uniqueIdentifier}SilgiContextType`);
306
+ importsString.push(`import type { SilgiContext as ${name} } from '${schema.path}'`);
307
+ context.push({
308
+ type: "type",
309
+ from: "context",
310
+ key: module,
311
+ value: name
312
+ });
313
+ }
314
+ );
315
+ }
316
+ }
317
+ if (moduleData.service) {
318
+ if (moduleData.service.silgiService && moduleData.service.silgiService?.length > 0) {
319
+ moduleData.service.silgiService.forEach((schema) => {
320
+ if (schema.type === "module") {
321
+ const name = camelCase(`${schema.serviceName}silgiModule`);
322
+ importsString.push(`import ${name} from '${schema.path}'`);
323
+ modules.push({
324
+ type: "module",
325
+ from: "service",
326
+ key: schema.serviceName,
327
+ value: name
328
+ });
329
+ } else {
330
+ const name = camelCase(`${module}_${schema.serviceName}silgiService`);
331
+ importsString.push(`import { silgiService as ${name} } from '${schema.path}'`);
332
+ services.push({
333
+ type: "const",
334
+ from: "service",
335
+ key: schema.serviceName,
336
+ value: name
337
+ });
338
+ }
339
+ });
340
+ }
341
+ if (moduleData.service.SilgiService && moduleData.service.SilgiService?.length > 0) {
342
+ moduleData.service.SilgiService.forEach((schema) => {
343
+ const name = pascalCase(`${module}_${schema.serviceName}SilgiServiceType`);
344
+ importsString.push(`import type { SilgiService as ${name} } from '${schema.path}'`);
345
+ services.push({
346
+ type: "type",
347
+ from: "service",
348
+ key: schema.serviceName,
349
+ value: name
350
+ });
351
+ });
352
+ }
353
+ }
354
+ if (moduleData.moduleConfig) {
355
+ if (moduleData.moduleConfig.SilgiModuleConfig && moduleData.moduleConfig.SilgiModuleConfig?.length > 0) {
356
+ moduleData.moduleConfig.SilgiModuleConfig.forEach((schema) => {
357
+ if (schema.type === "empty") {
358
+ moduleConfig.push({
359
+ type: "empty",
360
+ from: "moduleConfig",
361
+ key: schema.serviceName,
362
+ value: schema.serviceName
363
+ });
364
+ } else {
365
+ const name = pascalCase(`${module}_${schema.serviceName}SilgiModuleConfigType`);
366
+ importsString.push(`import type { SilgiModuleConfig as ${name} } from '${schema.path}'`);
367
+ moduleConfig.push({
368
+ type: "type",
369
+ from: "moduleConfig",
370
+ key: schema.serviceName,
371
+ value: name
372
+ });
373
+ }
374
+ });
375
+ }
376
+ }
377
+ return {
378
+ module,
379
+ methodsConfig,
380
+ context,
381
+ modules,
382
+ moduleConfig
383
+ };
384
+ }
385
+ generateInterfaceContent({ scopeDatas, modulesData, zodInterfacesImports, importsString }) {
386
+ return (
387
+ /* TS */
388
+ `// This file is auto-generated. Do not edit manually.
389
+ import { createSilgi } from 'silgi'
390
+ import type { OmitSilgiOptions } from 'silgi'
391
+ import type { InitSilgi, DefaultContext, DefaultInterface } from 'silgi'
392
+
393
+ ${zodInterfacesImports.join("\n")}
394
+ ${importsString.join("\n")}
395
+
396
+ declare module 'silgi' {
397
+ type SharedGlobalMerge = ${scopeDatas.some((scope) => scope.context.length > 0) ? `${scopeDatas.filter((scope) => scope.shareds.length > 0).map((scopeData) => {
398
+ return scopeData.shareds.map((shared) => {
399
+ if (shared.global) {
400
+ return shared.value;
401
+ }
402
+ }).filter(Boolean).join(" & ");
403
+ }).filter(Boolean).join(" & ")}` : ""}
404
+
405
+ interface DefaultInterface {
406
+ ${scopeDatas.some((scope) => scope.services.length > 0) ? `scopes: {
407
+ ${scopeDatas.filter((scope) => scope.services.length > 0).map((scopeData) => {
408
+ const services = scopeData.services.map((shared) => {
409
+ if (shared.type === "type") {
410
+ return `${shared.key}: ${shared.value}`;
411
+ }
412
+ }).filter(Boolean).join("\n");
413
+ return services ? `${scopeData.scope}: {
414
+ ${services}
415
+ }` : "";
416
+ }).filter(Boolean).join("\n")}
417
+ }` : ""}
418
+
419
+ ${scopeDatas.some((scope) => scope.shareds.length > 0) ? `shared: {
420
+ ${scopeDatas.map((scopeData) => {
421
+ const zodShared = scopeData.shareds.filter((shared) => shared.from === "zod").map((shared) => {
422
+ if (shared.type === "type") {
423
+ return `${shared.key}: ${shared.value}`;
424
+ }
425
+ }).filter(Boolean).join("\n");
426
+ return `
427
+ ${scopeData.scope}: {
428
+ ${zodShared ? `zod: {
429
+ ${zodShared}
430
+ }
431
+ }` : ""}`;
432
+ }).filter(Boolean).join("\n")}
433
+ } & SharedGlobalMerge` : ""}
434
+
435
+ ${modulesData.some((module) => module.methodsConfig.length > 0) ? `plugins: {
436
+ ${modulesData.map((moduleData) => {
437
+ const methodsConfig = moduleData.methodsConfig.map((shared) => {
438
+ if (shared.type === "type") {
439
+ return `${shared.value}`;
440
+ }
441
+ }).filter(Boolean).join("\n");
442
+ return methodsConfig ? `${moduleData.module}: ${methodsConfig}` : "";
443
+ }).filter(Boolean).join("\n")}
444
+ }` : ""}
445
+ }
446
+
447
+ type ExtendDefaultContext = ${[...scopeDatas, ...modulesData].some((item) => item.context?.length > 0) ? [...scopeDatas, ...modulesData].filter((item) => item.context?.length > 0).map((item) => {
448
+ return item.context.map((shared) => {
449
+ if (shared.type === "type") {
450
+ return shared.value;
451
+ }
452
+ }).filter(Boolean).join(" & ");
453
+ }).filter(Boolean).reduce((acc, curr) => acc ? `${acc} & ${curr}` : curr, "") : ""}
454
+
455
+ interface DefaultContext extends ExtendDefaultContext {}
456
+ }
457
+
458
+ export const silgiServices = [
459
+ ${scopeDatas.flatMap(
460
+ (scopeData) => scopeData.services.filter((service) => service.type === "const").map((service) => service.value)
461
+ ).filter(Boolean).join(",\n ")}
462
+ ]
463
+
464
+ export const silgiShared = {
465
+ ${scopeDatas.filter((scopeData) => scopeData.shareds.length > 0).map((scopeData) => {
466
+ const nonZodShared = scopeData.shareds.filter((shared) => shared.from !== "zod").filter((shared) => shared.type === "const").map((shared) => `...${shared.value}`).filter(Boolean).join(",\n ");
467
+ const zodShared = scopeData.shareds.filter((shared) => shared.from === "zod").filter((shared) => shared.type === "const").map((shared) => `${shared.key}: ${shared.value}`).filter(Boolean).join(",\n ");
468
+ return `
469
+ ${nonZodShared ? `${nonZodShared},` : ""}
470
+ ${scopeData.scope}: {
471
+ ${zodShared ? `zod: {
472
+ ${zodShared}
473
+ }` : ""}
474
+ }`;
475
+ }).join(",\n ")}
476
+ }
477
+
478
+ let _silgi: InitSilgi<DefaultContext, DefaultInterface>
479
+
480
+ interface SilgiCreateOptions {
481
+ version: 'v1'
482
+ silgi?: Omit<OmitSilgiOptions<DefaultContext, DefaultInterface>, 'services' | 'shared' | 'plugins'>
483
+ ${modulesData.flatMap(
484
+ (moduleData) => moduleData.moduleConfig.map((module) => {
485
+ if (module.key === module.value) {
486
+ return `${module.key}: object`;
487
+ }
488
+ return `${module.key}: ${module.value}`;
489
+ })
490
+ ).filter(Boolean).join("\n ")}
491
+ }
492
+
493
+ export function silgi(options: SilgiCreateOptions) {
494
+
495
+ if (!_silgi) {
496
+ _silgi = createSilgi({
497
+ ...options.silgi,
498
+ services: silgiServices,
499
+ shared: silgiShared,
500
+ plugins: [
501
+ ${modulesData.flatMap(
502
+ (moduleData) => moduleData.modules.filter((module) => module.type === "module").map((module) => {
503
+ const plugin = module.value;
504
+ return `${plugin}(options.${module.key})`;
505
+ })
506
+ ).filter(Boolean).join(",\n ")}
507
+ ],
508
+ })
509
+ return _silgi
510
+ }
511
+ return _silgi
512
+ }
513
+ `
514
+ );
515
+ }
516
+ async writeOutputFile(content) {
517
+ await writeFile(this.outputDir, content, "utf-8");
518
+ }
519
+ }
520
+ async function generateSilgiInterface(storage, outputDir) {
521
+ const generator = new TypescriptGenerator(storage, outputDir);
522
+ await generator.generate();
523
+ }
524
+
525
+ function extractNamesFromPath(filePath) {
526
+ const parts = dirname(filePath).split("/");
527
+ return {
528
+ serviceName: parts[parts.length - 1] || "",
529
+ scopeName: parts[parts.length - 3] || ""
530
+ };
531
+ }
532
+ function makeRelativePath(from, to) {
533
+ const relativePath = relative(dirname(from), to);
534
+ return relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
535
+ }
536
+ function cleanPath(path, removeExtensions = true) {
537
+ return removeExtensions ? path.replace(/\.ts$/, "") : path;
538
+ }
539
+
540
+ class SchemaManager {
541
+ constructor(config) {
542
+ this.config = config;
543
+ this.parser = new SchemaParser();
544
+ this.storage = {
545
+ schemaObjects: {},
546
+ path: "",
547
+ files: [],
548
+ modules: {}
549
+ };
550
+ this.clientSchemas ??= {};
551
+ this.schemas ??= {};
552
+ }
553
+ parser;
554
+ storage;
555
+ schemas;
556
+ modulesSchema;
557
+ clientSchemas;
558
+ async serverScan(filePath, scope) {
559
+ try {
560
+ const content = await promises.readFile(filePath, "utf-8");
561
+ const { exportTS, exportVariables } = this.parser.parseExports(content, filePath);
562
+ const { serviceName } = extractNamesFromPath(filePath);
563
+ const scopeName = scope.name;
564
+ const absoluteFilePath = resolve(this.config.rootDir, filePath);
565
+ let relativePath = makeRelativePath(this.config._silgi.outputDirTS, absoluteFilePath);
566
+ relativePath = cleanPath(relativePath, this.config.removeExtensions);
567
+ this.schemas ??= {};
568
+ this.schemas[scopeName] ??= {};
569
+ for (const item of exportTS) {
570
+ if (item.includes("SilgiContext")) {
571
+ this.schemas[scopeName].context ??= {};
572
+ this.schemas[scopeName].context.SilgiContext ??= [];
573
+ this.schemas[scopeName].context.SilgiContext.push({
574
+ type: "interface",
575
+ path: relativePath,
576
+ serviceName
577
+ });
578
+ } else if (item.includes("SilgiZodSchema")) {
579
+ this.schemas[scopeName].zod ??= {};
580
+ this.schemas[scopeName].zod.SilgiZodSchema ??= [];
581
+ this.schemas[scopeName].zod.SilgiZodSchema?.push({
582
+ type: "interface",
583
+ path: relativePath,
584
+ serviceName
585
+ });
586
+ } else if (item.includes("SilgiService")) {
587
+ this.schemas[scopeName].service ??= {};
588
+ this.schemas[scopeName].service.SilgiService ??= [];
589
+ this.schemas[scopeName].service.SilgiService.push({
590
+ type: "interface",
591
+ path: relativePath,
592
+ serviceName
593
+ });
594
+ } else if (item.includes("SilgiGlobalShared")) {
595
+ this.schemas[scopeName].shared ??= {};
596
+ this.schemas[scopeName].shared.SilgiGlobalShared ??= [];
597
+ this.schemas[scopeName].shared.SilgiGlobalShared.push({
598
+ type: "interface",
599
+ global: true,
600
+ path: relativePath,
601
+ serviceName
602
+ });
603
+ }
604
+ }
605
+ for (const item of exportVariables) {
606
+ if (item.includes("silgiContext")) {
607
+ this.schemas[scopeName].context ??= {};
608
+ this.schemas[scopeName].context.SilgiContext ??= [];
609
+ this.schemas[scopeName].context.SilgiContext.push({
610
+ type: "const",
611
+ path: relativePath,
612
+ serviceName
613
+ });
614
+ } else if (item.includes("silgiZodSchema")) {
615
+ this.schemas[scopeName].zod ??= {};
616
+ this.schemas[scopeName].zod.silgiZodSchema ??= [];
617
+ this.schemas[scopeName].zod.silgiZodSchema.push({
618
+ type: "const",
619
+ path: relativePath,
620
+ serviceName
621
+ });
622
+ } else if (item.includes("silgiService")) {
623
+ this.schemas[scopeName].service ??= {};
624
+ this.schemas[scopeName].service.silgiService ??= [];
625
+ this.schemas[scopeName].service.silgiService.push({
626
+ type: "const",
627
+ path: relativePath,
628
+ serviceName
629
+ });
630
+ } else if (item.includes("silgiGlobalShared")) {
631
+ this.schemas[scopeName].shared ??= {};
632
+ this.schemas[scopeName].shared.silgiGlobalShared ??= [];
633
+ this.schemas[scopeName].shared.silgiGlobalShared.push({
634
+ type: "const",
635
+ path: relativePath,
636
+ serviceName
637
+ });
638
+ }
639
+ }
640
+ return Object.keys(this.schemas).length > 0;
641
+ } catch (error) {
642
+ consola.error(`Error processing file ${filePath}:`, error);
643
+ return false;
644
+ }
645
+ }
646
+ async clientScan(filePath, _scope) {
647
+ try {
648
+ const content = await promises.readFile(filePath, "utf-8");
649
+ const { exportVariables } = this.parser.parseExports(content, filePath);
650
+ const { serviceName } = extractNamesFromPath(filePath);
651
+ const absoluteFilePath = resolve(this.config.rootDir, filePath);
652
+ let relativePath = makeRelativePath(this.config._silgi.outputDirTS, absoluteFilePath);
653
+ relativePath = cleanPath(relativePath, this.config.removeExtensions);
654
+ if (!this.clientSchemas[serviceName]) {
655
+ this.clientSchemas[serviceName] = {
656
+ graphql: {
657
+ mutation: [],
658
+ query: []
659
+ },
660
+ zod: []
661
+ };
662
+ }
663
+ for (const item of exportVariables) {
664
+ if (typeof item === "string" && item.toLocaleLowerCase().startsWith("zod")) {
665
+ this.clientSchemas[serviceName].zod.push({
666
+ type: "const",
667
+ path: relativePath,
668
+ serviceName,
669
+ key: item,
670
+ newKey: camelCase(item.replace("zod", ""))
671
+ });
672
+ }
673
+ if (typeof item === "string" && item.toLocaleLowerCase().startsWith("mutation")) {
674
+ this.clientSchemas[serviceName].graphql.mutation.push({
675
+ type: "const",
676
+ path: relativePath,
677
+ serviceName,
678
+ key: item,
679
+ newKey: camelCase(item.replace("mutation", ""))
680
+ });
681
+ }
682
+ if (typeof item === "string" && item.toLocaleLowerCase().startsWith("query")) {
683
+ this.clientSchemas[serviceName].graphql.query.push({
684
+ type: "const",
685
+ path: relativePath,
686
+ serviceName,
687
+ key: item,
688
+ newKey: camelCase(item.replace("query", ""))
689
+ });
690
+ }
691
+ }
692
+ return true;
693
+ } catch (error) {
694
+ consola.error(`Error processing file ${filePath}:`, error);
695
+ return false;
696
+ }
697
+ }
698
+ async scanDirectory(dir, scope) {
699
+ const scan = async (currentDir) => {
700
+ const files = await promises.readdir(currentDir);
701
+ await Promise.all(files.map(async (file) => {
702
+ const filePath = join(currentDir, file);
703
+ const stats = await promises.stat(filePath);
704
+ if (stats.isDirectory()) {
705
+ await scan(filePath);
706
+ } else if (file.endsWith(".ts")) {
707
+ if (scope.type === "client") {
708
+ await this.clientScan(filePath, scope);
709
+ } else {
710
+ const hasSchemas = await this.serverScan(filePath, scope);
711
+ if (hasSchemas) {
712
+ this.storage ??= {};
713
+ this.storage.files ??= [];
714
+ this.storage.schemaObjects = this.schemas;
715
+ this.storage.modules = this.modulesSchema;
716
+ this.storage.files.push(filePath);
717
+ }
718
+ }
719
+ }
720
+ }));
721
+ };
722
+ await scan(dir);
723
+ return this.storage;
724
+ }
725
+ async scanModules(config) {
726
+ const scan = async (currentDir) => {
727
+ const files = await promises.readdir(currentDir);
728
+ const jiti = createJiti(config.rootDir);
729
+ await Promise.all(files.map(async (file) => {
730
+ const filePath = join(currentDir, file);
731
+ const stats = await promises.stat(filePath);
732
+ const absoluteFilePath = resolve(config.rootDir, filePath);
733
+ let relativePath = makeRelativePath(config._silgi.outputDirTS, absoluteFilePath);
734
+ relativePath = cleanPath(relativePath, config.removeExtensions);
735
+ if (stats.isDirectory()) {
736
+ await scan(filePath);
737
+ } else if (file.endsWith(".ts")) {
738
+ try {
739
+ const modDefault = await jiti.import(filePath, { default: true });
740
+ if (typeof modDefault === "function") {
741
+ const result = modDefault();
742
+ const name = result.name;
743
+ this.modulesSchema ??= {};
744
+ this.modulesSchema[name] ??= {};
745
+ if (name) {
746
+ this.modulesSchema[name].service ??= {};
747
+ this.modulesSchema[name].service.silgiService ??= [];
748
+ this.modulesSchema[name].service.silgiService.push({
749
+ type: "module",
750
+ path: relativePath,
751
+ serviceName: name
752
+ });
753
+ if (result.system) {
754
+ if (result.system.context) {
755
+ this.modulesSchema[name].context ??= {};
756
+ this.modulesSchema[name].context.SilgiContext ??= [];
757
+ this.modulesSchema[name].context.SilgiContext.push({
758
+ type: "interface",
759
+ path: relativePath,
760
+ serviceName: "empty"
761
+ });
762
+ }
763
+ if (result.system.shared) {
764
+ this.modulesSchema[name].shared ??= {};
765
+ this.modulesSchema[name].shared.SilgiGlobalShared ??= [];
766
+ this.modulesSchema[name].shared.SilgiGlobalShared.push({
767
+ type: "interface",
768
+ global: true,
769
+ path: relativePath,
770
+ serviceName: "empty"
771
+ });
772
+ }
773
+ if (result.system.services) {
774
+ this.modulesSchema[name].service ??= {};
775
+ this.modulesSchema[name].service.SilgiService ??= [];
776
+ this.modulesSchema[name].service.SilgiService.push({
777
+ type: "interface",
778
+ path: relativePath,
779
+ serviceName: "empty"
780
+ });
781
+ }
782
+ if (result.system.methodConfig) {
783
+ this.modulesSchema[name].methodsConfig ??= {};
784
+ this.modulesSchema[name].methodsConfig.SilgiMethodConfig ??= [];
785
+ this.modulesSchema[name].methodsConfig.SilgiMethodConfig.push({
786
+ type: "interface",
787
+ path: relativePath,
788
+ serviceName: "empty",
789
+ global: true
790
+ });
791
+ }
792
+ if (result.system.moduleConfig) {
793
+ this.modulesSchema[name].moduleConfig ??= {};
794
+ this.modulesSchema[name].moduleConfig.SilgiModuleConfig ??= [];
795
+ this.modulesSchema[name].moduleConfig.SilgiModuleConfig.push({
796
+ type: "interface",
797
+ path: relativePath,
798
+ serviceName: name,
799
+ global: true
800
+ });
801
+ } else {
802
+ this.modulesSchema[name].moduleConfig ??= {};
803
+ this.modulesSchema[name].moduleConfig.SilgiModuleConfig ??= [];
804
+ this.modulesSchema[name].moduleConfig.SilgiModuleConfig.push({
805
+ type: "empty",
806
+ path: relativePath,
807
+ serviceName: name,
808
+ global: true
809
+ });
810
+ }
811
+ }
812
+ consola.log("\u{1F4C1} Scanning module:", this.schemas);
813
+ }
814
+ }
815
+ } catch (error) {
816
+ consola.error(`Error processing file ${filePath}:`, error);
817
+ }
818
+ }
819
+ }));
820
+ };
821
+ if (config.moduleDir) {
822
+ for (const module of config.moduleDir) {
823
+ const resolvedPath = resolve(config.rootDir, module);
824
+ if (module) {
825
+ await scan(resolvedPath);
826
+ }
827
+ }
828
+ }
829
+ }
830
+ async initialize() {
831
+ const outputFileTS = resolve(this.config.rootDir, this.config.output.server, "silgi.generated.ts");
832
+ this.config._silgi.outputDirTS = outputFileTS;
833
+ this.config._silgi.outputDirClientTS = resolve(this.config.rootDir, this.config.output.server, "silgi.generated.ts");
834
+ await promises.mkdir(this.config.output.server, { recursive: true });
835
+ }
836
+ async generateOutput() {
837
+ const resolvedPath = resolve(this.config.rootDir, this.config.output.server, "silgi.generated.ts");
838
+ await generateSilgiInterface(this.storage, resolvedPath);
839
+ if (this.storage?.schemaObjects && this.storage.files) {
840
+ const schemaCount = Object.keys(this.storage.schemaObjects).length;
841
+ consola.log(`Found ${schemaCount} schema objects in ${this.storage.files.length} files`);
842
+ }
843
+ }
844
+ async generateClientOutput() {
845
+ const resolvedPath = resolve(this.config.rootDir, this.config.output.client, "silgi.generated.ts");
846
+ await generateClientGenTS(this.clientSchemas, resolvedPath);
847
+ if (this.storage?.schemaObjects && this.storage.files) {
848
+ const schemaCount = Object.keys(this.storage.schemaObjects).length;
849
+ consola.log(`Found ${schemaCount} schema objects in ${this.storage.files.length} files`);
850
+ consola.success(`Generated client schema files in ${resolvedPath}`);
851
+ }
852
+ }
853
+ }
854
+ async function generate$1(config) {
855
+ const mergeConfig = defu(config, {
856
+ removeExtensions: true,
857
+ _silgi: {
858
+ outputDirTS: "",
859
+ outputDirClientTS: ""
860
+ }
861
+ });
862
+ mergeConfig.rootDir = resolve(mergeConfig.rootDir || config.rootDir || ".");
863
+ const schemaManager = new SchemaManager(mergeConfig);
864
+ try {
865
+ await schemaManager.initialize();
866
+ await schemaManager.scanModules(mergeConfig);
867
+ for (const scope of mergeConfig.scopes) {
868
+ const resolvedPath = resolve(mergeConfig.rootDir, scope.path);
869
+ consola.log(`\u{1F4C1} Scanning directory: ${resolvedPath}`);
870
+ await schemaManager.scanDirectory(resolvedPath, scope);
871
+ }
872
+ await schemaManager.generateOutput();
873
+ await schemaManager.generateClientOutput();
874
+ consola.success("Schema generation completed successfully");
875
+ const eslint = new ESLint({
876
+ fix: true,
877
+ cwd: mergeConfig.rootDir,
878
+ baseConfig: await antfu({ typescript: true })
879
+ });
880
+ const results = await eslint.lintFiles([
881
+ mergeConfig._silgi.outputDirTS,
882
+ mergeConfig._silgi.outputDirClientTS
883
+ ]);
884
+ await ESLint.outputFixes(results);
885
+ const formatter = await eslint.loadFormatter("stylish");
886
+ formatter.format(results);
887
+ } catch (error) {
888
+ consola.error("Schema generation failed:", error);
889
+ process.exit(1);
890
+ }
891
+ }
892
+
893
+ const generate = defineCommand({
894
+ meta: {
895
+ name: "Silgi Generate",
896
+ description: "Generate code",
897
+ version: "0.1.0"
898
+ },
899
+ args: {},
900
+ async run() {
901
+ const result = await loadConfig({
902
+ name: "silgi",
903
+ configFile: "silgi.config",
904
+ rcFile: ".silgirc"
905
+ });
906
+ const silgiConfig = result.config;
907
+ await generate$1(silgiConfig);
908
+ }
909
+ });
910
+
911
+ export { generate as default };
@@ -0,0 +1,21 @@
1
+ import { writeFileSync } from 'node:fs';
2
+ import { defineCommand } from 'citty';
3
+
4
+ const init = defineCommand({
5
+ meta: {
6
+ name: "Silgi Init",
7
+ description: "Initialize silgi config",
8
+ version: "0.1.0"
9
+ },
10
+ args: {},
11
+ async run() {
12
+ const context = `import { defineSilgiConfig } from 'silgi/config'
13
+
14
+ export default defineSilgiConfig({
15
+
16
+ })`;
17
+ writeFileSync("silgi.config.ts", context);
18
+ }
19
+ });
20
+
21
+ export { init as default };
@@ -0,0 +1,19 @@
1
+ interface SilgiConfig {
2
+ scopes: ScopeConfig[];
3
+ removeExtensions: boolean;
4
+ output: {
5
+ server: string;
6
+ client: string;
7
+ };
8
+ modules?: string[];
9
+ moduleDir: string[];
10
+ }
11
+ interface ScopeConfig {
12
+ name: string;
13
+ path: string;
14
+ type: 'server' | 'client';
15
+ }
16
+
17
+ declare function defineSilgiConfig(config: SilgiConfig): SilgiConfig;
18
+
19
+ export { defineSilgiConfig };
@@ -0,0 +1,19 @@
1
+ interface SilgiConfig {
2
+ scopes: ScopeConfig[];
3
+ removeExtensions: boolean;
4
+ output: {
5
+ server: string;
6
+ client: string;
7
+ };
8
+ modules?: string[];
9
+ moduleDir: string[];
10
+ }
11
+ interface ScopeConfig {
12
+ name: string;
13
+ path: string;
14
+ type: 'server' | 'client';
15
+ }
16
+
17
+ declare function defineSilgiConfig(config: SilgiConfig): SilgiConfig;
18
+
19
+ export { defineSilgiConfig };
@@ -0,0 +1,5 @@
1
+ function defineSilgiConfig(config) {
2
+ return config;
3
+ }
4
+
5
+ export { defineSilgiConfig };
@@ -0,0 +1,2 @@
1
+
2
+ export { }
@@ -0,0 +1,2 @@
1
+
2
+ export { }
@@ -0,0 +1,103 @@
1
+ import { defineCommand, runMain } from 'citty';
2
+ import consola from 'consola';
3
+
4
+ const name = "silgi";
5
+ const type = "module";
6
+ const version = "0.0.1";
7
+ const exports = {
8
+ ".": {
9
+ "import": {
10
+ types: "./dist/index.d.mts",
11
+ "default": "./dist/index.mjs"
12
+ }
13
+ },
14
+ "./plugins/openapi": {
15
+ types: "./dist/plugins/openapi.d.mts",
16
+ "import": "./dist/plugins/openapi.mjs"
17
+ },
18
+ "./plugins/scalar": {
19
+ types: "./dist/plugins/scalar.d.mts",
20
+ "import": "./dist/plugins/scalar.mjs"
21
+ },
22
+ "./config": {
23
+ types: "./dist/cli/config.d.mts",
24
+ "import": "./dist/cli/config.mjs"
25
+ }
26
+ };
27
+ const types = "./dist/index.d.ts";
28
+ const bin = {
29
+ silgi: "bin/silgi.mjs"
30
+ };
31
+ const files = [
32
+ "dist"
33
+ ];
34
+ const scripts = {
35
+ build: "unbuild",
36
+ "build:stub": "unbuild --stub",
37
+ dev: "npx --yes listhen -w --open ./__tests__/h3.ts",
38
+ "dev:k6": "k6 run ./__tests__/k6.js"
39
+ };
40
+ const dependencies = {
41
+ "@anatine/zod-openapi": "^2.2.6",
42
+ "@antfu/eslint-config": "^3.12.1",
43
+ "@asteasolutions/zod-to-openapi": "^7.3.0",
44
+ "@oxc-parser/wasm": "^0.44.0",
45
+ c12: "^2.0.1",
46
+ citty: "^0.1.6",
47
+ consola: "^3.3.1",
48
+ defu: "^6.1.4",
49
+ eslint: "^9.17.0",
50
+ h3: "^1.13.0",
51
+ hookable: "^5.5.3",
52
+ jiti: "^2.4.2",
53
+ mlly: "^1.7.3",
54
+ "openapi3-ts": "^4.4.0",
55
+ pathe: "^1.1.2",
56
+ scule: "^1.3.0",
57
+ unctx: "^2.4.1",
58
+ zod: "^3.24.1"
59
+ };
60
+ const devDependencies = {
61
+ silgi: "workspace:*",
62
+ unbuild: "^3.0.1"
63
+ };
64
+ const resolutions = {
65
+ silgi: "workspace:*"
66
+ };
67
+ const packageJson = {
68
+ name: name,
69
+ type: type,
70
+ version: version,
71
+ exports: exports,
72
+ types: types,
73
+ bin: bin,
74
+ files: files,
75
+ scripts: scripts,
76
+ dependencies: dependencies,
77
+ devDependencies: devDependencies,
78
+ resolutions: resolutions
79
+ };
80
+
81
+ const main = defineCommand({
82
+ meta: {
83
+ name: "Silgi",
84
+ version: packageJson.version,
85
+ description: "Silgi CLI"
86
+ },
87
+ args: {
88
+ version: {
89
+ alias: "v",
90
+ description: "Show version"
91
+ }
92
+ },
93
+ subCommands: {
94
+ generate: () => import('../chunks/generate.mjs').then((m) => m.default),
95
+ init: () => import('../chunks/init.mjs').then((m) => m.default)
96
+ },
97
+ run({ args }) {
98
+ consola.info("Silgi CLI , --help for more info");
99
+ if (args.version)
100
+ console.warn("Silgi version:", packageJson.version);
101
+ }
102
+ });
103
+ runMain(main);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "silgi",
3
3
  "type": "module",
4
- "version": "0.0.1",
4
+ "version": "0.0.2",
5
5
  "exports": {
6
6
  ".": {
7
7
  "import": {
@@ -16,9 +16,16 @@
16
16
  "./plugins/scalar": {
17
17
  "types": "./dist/plugins/scalar.d.mts",
18
18
  "import": "./dist/plugins/scalar.mjs"
19
+ },
20
+ "./config": {
21
+ "types": "./dist/cli/config.d.mts",
22
+ "import": "./dist/cli/config.mjs"
19
23
  }
20
24
  },
21
25
  "types": "./dist/index.d.ts",
26
+ "bin": {
27
+ "silgi": "bin/silgi.mjs"
28
+ },
22
29
  "files": [
23
30
  "dist"
24
31
  ],
@@ -26,20 +33,25 @@
26
33
  "@anatine/zod-openapi": "^2.2.6",
27
34
  "@antfu/eslint-config": "^3.12.1",
28
35
  "@asteasolutions/zod-to-openapi": "^7.3.0",
36
+ "@oxc-parser/wasm": "^0.44.0",
37
+ "c12": "^2.0.1",
38
+ "citty": "^0.1.6",
29
39
  "consola": "^3.3.1",
40
+ "defu": "^6.1.4",
30
41
  "eslint": "^9.17.0",
31
42
  "h3": "^1.13.0",
32
43
  "hookable": "^5.5.3",
33
44
  "jiti": "^2.4.2",
34
45
  "mlly": "^1.7.3",
35
46
  "openapi3-ts": "^4.4.0",
47
+ "pathe": "^1.1.2",
36
48
  "scule": "^1.3.0",
37
49
  "unctx": "^2.4.1",
38
50
  "zod": "^3.24.1"
39
51
  },
40
52
  "devDependencies": {
41
53
  "unbuild": "^3.0.1",
42
- "silgi": "0.0.1"
54
+ "silgi": "0.0.2"
43
55
  },
44
56
  "resolutions": {
45
57
  "silgi": "workspace:*"