@mearie/codegen 0.0.0-next-20260228035926
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/LICENSE +20 -0
- package/README.md +10 -0
- package/dist/index.cjs +473 -0
- package/dist/index.d.cts +77 -0
- package/dist/index.d.mts +77 -0
- package/dist/index.mjs +438 -0
- package/package.json +79 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright 2025 Bae Junehyeon
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
|
7
|
+
the Software without restriction, including without limitation the rights to
|
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
|
10
|
+
subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# @mearie/codegen
|
|
2
|
+
|
|
3
|
+
Code generator for Mearie GraphQL client.
|
|
4
|
+
|
|
5
|
+
This package generates TypeScript types from GraphQL schema and operations. It
|
|
6
|
+
is used internally by build plugins and is not intended for direct use.
|
|
7
|
+
|
|
8
|
+
## Documentation
|
|
9
|
+
|
|
10
|
+
Full documentation is available at <https://mearie.dev/config/codegen>.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
12
|
+
key = keys[i];
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except) {
|
|
14
|
+
__defProp(to, key, {
|
|
15
|
+
get: ((k) => from[k]).bind(null, key),
|
|
16
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
24
|
+
value: mod,
|
|
25
|
+
enumerable: true
|
|
26
|
+
}) : target, mod));
|
|
27
|
+
|
|
28
|
+
//#endregion
|
|
29
|
+
let tinyglobby = require("tinyglobby");
|
|
30
|
+
let picomatch = require("picomatch");
|
|
31
|
+
picomatch = __toESM(picomatch);
|
|
32
|
+
let node_fs_promises = require("node:fs/promises");
|
|
33
|
+
let _mearie_native = require("@mearie/native");
|
|
34
|
+
let node_path = require("node:path");
|
|
35
|
+
node_path = __toESM(node_path);
|
|
36
|
+
let _logtape_logtape = require("@logtape/logtape");
|
|
37
|
+
let picocolors = require("picocolors");
|
|
38
|
+
picocolors = __toESM(picocolors);
|
|
39
|
+
|
|
40
|
+
//#region src/glob.ts
|
|
41
|
+
/**
|
|
42
|
+
* Finds files matching the given patterns.
|
|
43
|
+
* @param cwd - Current working directory.
|
|
44
|
+
* @param patterns - File patterns to match.
|
|
45
|
+
* @returns Array of absolute file paths.
|
|
46
|
+
*/
|
|
47
|
+
const findFiles = async (cwd, patterns) => {
|
|
48
|
+
const includePatterns = Array.isArray(patterns.include) ? patterns.include : [patterns.include];
|
|
49
|
+
const negatedExcludes = (patterns.exclude ? Array.isArray(patterns.exclude) ? patterns.exclude : [patterns.exclude] : []).map((pattern) => `!${pattern}`);
|
|
50
|
+
return await (0, tinyglobby.glob)([...includePatterns, ...negatedExcludes], {
|
|
51
|
+
cwd,
|
|
52
|
+
absolute: true
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Creates a matcher function.
|
|
57
|
+
* @internal
|
|
58
|
+
* @param patterns - File patterns to match.
|
|
59
|
+
* @returns A function that checks if a file path matches the patterns.
|
|
60
|
+
*/
|
|
61
|
+
const createMatcher = (patterns) => {
|
|
62
|
+
const includePatterns = Array.isArray(patterns.include) ? patterns.include : [patterns.include];
|
|
63
|
+
const excludePatterns = patterns.exclude ? Array.isArray(patterns.exclude) ? patterns.exclude : [patterns.exclude] : [];
|
|
64
|
+
const isMatch = (0, picomatch.default)(includePatterns);
|
|
65
|
+
const isExcluded = excludePatterns.length > 0 ? (0, picomatch.default)(excludePatterns) : () => false;
|
|
66
|
+
return (filePath) => {
|
|
67
|
+
return isMatch(filePath) && !isExcluded(filePath);
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
//#endregion
|
|
72
|
+
//#region src/errors.ts
|
|
73
|
+
/**
|
|
74
|
+
* Custom error class for Mearie-specific errors.
|
|
75
|
+
*/
|
|
76
|
+
var MearieError = class MearieError extends Error {
|
|
77
|
+
filePath;
|
|
78
|
+
line;
|
|
79
|
+
column;
|
|
80
|
+
constructor(message, filePath, line, column) {
|
|
81
|
+
super(message);
|
|
82
|
+
this.name = "MearieError";
|
|
83
|
+
this.filePath = filePath;
|
|
84
|
+
this.line = line;
|
|
85
|
+
this.column = column;
|
|
86
|
+
}
|
|
87
|
+
static fromNative(data) {
|
|
88
|
+
if (!data || typeof data !== "object") throw new TypeError("Invalid native error data");
|
|
89
|
+
const error = data;
|
|
90
|
+
const filePath = error.location?.file_path;
|
|
91
|
+
const line = error.location?.line;
|
|
92
|
+
const column = error.location?.column;
|
|
93
|
+
return new MearieError(error.message, filePath, line, column);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Aggregate error for multiple Mearie errors.
|
|
98
|
+
*/
|
|
99
|
+
var MearieAggregateError = class extends Error {
|
|
100
|
+
errors;
|
|
101
|
+
constructor(errors, message) {
|
|
102
|
+
super(message ?? `${errors.length} error${errors.length > 1 ? "s" : ""} occurred`);
|
|
103
|
+
this.name = "MearieAggregateError";
|
|
104
|
+
this.errors = errors;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* Error thrown when `@vue/compiler-sfc` is not installed.
|
|
109
|
+
*/
|
|
110
|
+
var MissingVueCompilerError = class extends Error {
|
|
111
|
+
constructor() {
|
|
112
|
+
super(`
|
|
113
|
+
GraphQL operations cannot be extracted from Vue files without @vue/compiler-sfc.
|
|
114
|
+
|
|
115
|
+
Install it with:
|
|
116
|
+
npm install @vue/compiler-sfc
|
|
117
|
+
# or
|
|
118
|
+
pnpm add @vue/compiler-sfc
|
|
119
|
+
# or
|
|
120
|
+
yarn add @vue/compiler-sfc
|
|
121
|
+
`);
|
|
122
|
+
this.name = "MissingVueCompilerError";
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* Error thrown when svelte compiler is not installed.
|
|
127
|
+
*/
|
|
128
|
+
var MissingSvelteCompilerError = class extends Error {
|
|
129
|
+
constructor() {
|
|
130
|
+
super(`
|
|
131
|
+
GraphQL operations cannot be extracted from Svelte files without svelte.
|
|
132
|
+
|
|
133
|
+
Install it with:
|
|
134
|
+
npm install svelte
|
|
135
|
+
# or
|
|
136
|
+
pnpm add svelte
|
|
137
|
+
# or
|
|
138
|
+
yarn add svelte
|
|
139
|
+
`);
|
|
140
|
+
this.name = "MissingSvelteCompilerError";
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
/**
|
|
144
|
+
* Error thrown when TypeScript is not installed.
|
|
145
|
+
*/
|
|
146
|
+
var MissingTypeScriptError = class extends Error {
|
|
147
|
+
constructor() {
|
|
148
|
+
super(`
|
|
149
|
+
GraphQL operations cannot be extracted from Vue files without typescript.
|
|
150
|
+
|
|
151
|
+
Install it with:
|
|
152
|
+
npm install typescript
|
|
153
|
+
# or
|
|
154
|
+
pnpm add typescript
|
|
155
|
+
# or
|
|
156
|
+
yarn add typescript
|
|
157
|
+
`);
|
|
158
|
+
this.name = "MissingTypeScriptError";
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
//#endregion
|
|
163
|
+
//#region src/loaders.ts
|
|
164
|
+
/**
|
|
165
|
+
* Loads the Vue compiler dynamically.
|
|
166
|
+
* @returns The Vue compiler module.
|
|
167
|
+
* @throws {MissingVueCompilerError} If `@vue/compiler-sfc` is not installed.
|
|
168
|
+
*/
|
|
169
|
+
const loadVueCompiler = async () => {
|
|
170
|
+
try {
|
|
171
|
+
return await import("@vue/compiler-sfc");
|
|
172
|
+
} catch {
|
|
173
|
+
throw new MissingVueCompilerError();
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
/**
|
|
177
|
+
* Loads TypeScript dynamically.
|
|
178
|
+
* @returns The TypeScript module.
|
|
179
|
+
* @throws {MissingTypeScriptError} If typescript is not installed.
|
|
180
|
+
*/
|
|
181
|
+
const loadTypeScript = async () => {
|
|
182
|
+
try {
|
|
183
|
+
return await import("typescript");
|
|
184
|
+
} catch {
|
|
185
|
+
throw new MissingTypeScriptError();
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
/**
|
|
189
|
+
* Loads the Svelte compiler dynamically.
|
|
190
|
+
* @returns The Svelte compiler module.
|
|
191
|
+
* @throws {MissingSvelteCompilerError} If svelte is not installed.
|
|
192
|
+
*/
|
|
193
|
+
const loadSvelteCompiler = async () => {
|
|
194
|
+
try {
|
|
195
|
+
return await import("svelte/compiler");
|
|
196
|
+
} catch {
|
|
197
|
+
throw new MissingSvelteCompilerError();
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
//#endregion
|
|
202
|
+
//#region src/parsers.ts
|
|
203
|
+
const extractVueScript = async (source) => {
|
|
204
|
+
const [vueCompiler, typescript] = await Promise.all([loadVueCompiler(), loadTypeScript()]);
|
|
205
|
+
vueCompiler.registerTS(() => typescript);
|
|
206
|
+
const { descriptor } = vueCompiler.parse(source.code, { filename: source.filePath });
|
|
207
|
+
const blocks = [];
|
|
208
|
+
if (descriptor.script) blocks.push({
|
|
209
|
+
code: descriptor.script.content,
|
|
210
|
+
filePath: source.filePath,
|
|
211
|
+
startLine: source.startLine + descriptor.script.loc.start.line - 1
|
|
212
|
+
});
|
|
213
|
+
if (descriptor.scriptSetup) blocks.push({
|
|
214
|
+
code: descriptor.scriptSetup.content,
|
|
215
|
+
filePath: source.filePath,
|
|
216
|
+
startLine: source.startLine + descriptor.scriptSetup.loc.start.line - 1
|
|
217
|
+
});
|
|
218
|
+
return blocks;
|
|
219
|
+
};
|
|
220
|
+
const extractSvelteScript = async (source) => {
|
|
221
|
+
const ast = (await loadSvelteCompiler()).parse(source.code);
|
|
222
|
+
const blocks = [];
|
|
223
|
+
if (ast.instance?.content) {
|
|
224
|
+
const code = source.code.slice(ast.instance.content.start, ast.instance.content.end);
|
|
225
|
+
const lineOffset = source.code.slice(0, ast.instance.content.start).split("\n").length - 1;
|
|
226
|
+
blocks.push({
|
|
227
|
+
code,
|
|
228
|
+
filePath: `${source.filePath}.instance.ts`,
|
|
229
|
+
startLine: source.startLine + lineOffset
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
if (ast.module?.content) {
|
|
233
|
+
const code = source.code.slice(ast.module.content.start, ast.module.content.end);
|
|
234
|
+
const lineOffset = source.code.slice(0, ast.module.content.start).split("\n").length - 1;
|
|
235
|
+
blocks.push({
|
|
236
|
+
code,
|
|
237
|
+
filePath: `${source.filePath}.module.ts`,
|
|
238
|
+
startLine: source.startLine + lineOffset
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
return blocks;
|
|
242
|
+
};
|
|
243
|
+
const extractAstroScripts = (source) => {
|
|
244
|
+
const blocks = [];
|
|
245
|
+
const frontmatterMatch = /^---\s*\n([\s\S]*?)\n---/.exec(source.code);
|
|
246
|
+
if (frontmatterMatch) {
|
|
247
|
+
const lineOffset = source.code.slice(0, frontmatterMatch.index).split("\n").length;
|
|
248
|
+
blocks.push({
|
|
249
|
+
code: frontmatterMatch[1],
|
|
250
|
+
filePath: `${source.filePath}.frontmatter.ts`,
|
|
251
|
+
startLine: source.startLine + lineOffset
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
const scriptMatches = source.code.matchAll(/<script\b[^>]*>([\s\S]*?)<\/script>/gi);
|
|
255
|
+
let index = 0;
|
|
256
|
+
for (const match of scriptMatches) {
|
|
257
|
+
const lineOffset = source.code.slice(0, match.index).split("\n").length;
|
|
258
|
+
blocks.push({
|
|
259
|
+
code: match[1],
|
|
260
|
+
filePath: `${source.filePath}.${index}.ts`,
|
|
261
|
+
startLine: source.startLine + lineOffset
|
|
262
|
+
});
|
|
263
|
+
index++;
|
|
264
|
+
}
|
|
265
|
+
return blocks;
|
|
266
|
+
};
|
|
267
|
+
const extractMarkdownCodeBlocks = (source) => {
|
|
268
|
+
const codeBlocks = [];
|
|
269
|
+
const codeBlockRegex = /```(tsx|ts)[^\n]*mearie[^\n]*\n([\s\S]*?)```/g;
|
|
270
|
+
let match;
|
|
271
|
+
let index = 0;
|
|
272
|
+
while ((match = codeBlockRegex.exec(source.code)) !== null) {
|
|
273
|
+
const lineOffset = source.code.slice(0, match.index).split("\n").length;
|
|
274
|
+
codeBlocks.push({
|
|
275
|
+
code: match[2],
|
|
276
|
+
filePath: `${source.filePath}.${index}.${match[1]}`,
|
|
277
|
+
startLine: source.startLine + lineOffset
|
|
278
|
+
});
|
|
279
|
+
index++;
|
|
280
|
+
}
|
|
281
|
+
return codeBlocks;
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
//#endregion
|
|
285
|
+
//#region src/extractor.ts
|
|
286
|
+
const extractGraphQLSources = async (source) => {
|
|
287
|
+
const ext = source.filePath.split(".").pop()?.toLowerCase();
|
|
288
|
+
const sources = [];
|
|
289
|
+
const errors = [];
|
|
290
|
+
const blocks = [];
|
|
291
|
+
try {
|
|
292
|
+
switch (ext) {
|
|
293
|
+
case "vue":
|
|
294
|
+
blocks.push(...await extractVueScript(source));
|
|
295
|
+
break;
|
|
296
|
+
case "svelte":
|
|
297
|
+
blocks.push(...await extractSvelteScript(source));
|
|
298
|
+
break;
|
|
299
|
+
case "astro":
|
|
300
|
+
blocks.push(...extractAstroScripts(source));
|
|
301
|
+
break;
|
|
302
|
+
case "md":
|
|
303
|
+
blocks.push(...extractMarkdownCodeBlocks(source));
|
|
304
|
+
break;
|
|
305
|
+
case "js":
|
|
306
|
+
case "jsx":
|
|
307
|
+
case "ts":
|
|
308
|
+
case "tsx":
|
|
309
|
+
blocks.push(source);
|
|
310
|
+
break;
|
|
311
|
+
default: return {
|
|
312
|
+
sources,
|
|
313
|
+
errors
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
} catch (error) {
|
|
317
|
+
throw new MearieError(error instanceof Error ? error.message : String(error), source.filePath);
|
|
318
|
+
}
|
|
319
|
+
for (const block of blocks) {
|
|
320
|
+
const result = (0, _mearie_native.extractGraphQLSources)(block);
|
|
321
|
+
sources.push(...result.sources);
|
|
322
|
+
errors.push(...result.errors.map((error) => MearieError.fromNative(error)));
|
|
323
|
+
}
|
|
324
|
+
return {
|
|
325
|
+
sources,
|
|
326
|
+
errors
|
|
327
|
+
};
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
//#endregion
|
|
331
|
+
//#region src/generator.ts
|
|
332
|
+
/**
|
|
333
|
+
* Generates code from GraphQL documents and schemas.
|
|
334
|
+
* @param options - Generation options.
|
|
335
|
+
* @returns Generated code.
|
|
336
|
+
* @throws {Error} If code generation fails.
|
|
337
|
+
*/
|
|
338
|
+
const generate = (options) => {
|
|
339
|
+
const { schemas, documents, config } = options;
|
|
340
|
+
const { sources, errors } = (0, _mearie_native.generateCode)(schemas, documents, config);
|
|
341
|
+
return {
|
|
342
|
+
sources,
|
|
343
|
+
errors: errors.map((error) => MearieError.fromNative(error))
|
|
344
|
+
};
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
//#endregion
|
|
348
|
+
//#region src/writer.ts
|
|
349
|
+
const writeFiles = async (cwd, sources) => {
|
|
350
|
+
const mearieDir = node_path.default.resolve(cwd, ".mearie");
|
|
351
|
+
await (0, node_fs_promises.mkdir)(mearieDir, { recursive: true });
|
|
352
|
+
await Promise.all(sources.map((source) => (0, node_fs_promises.writeFile)(node_path.default.join(mearieDir, source.filePath), source.code, "utf8")));
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
//#endregion
|
|
356
|
+
//#region src/context.ts
|
|
357
|
+
/**
|
|
358
|
+
* Stateful context for incremental code generation.
|
|
359
|
+
* Reads and caches file contents when files are added/updated.
|
|
360
|
+
*/
|
|
361
|
+
var CodegenContext = class {
|
|
362
|
+
schemas = /* @__PURE__ */ new Map();
|
|
363
|
+
documents = /* @__PURE__ */ new Map();
|
|
364
|
+
cwd;
|
|
365
|
+
config;
|
|
366
|
+
constructor(cwd = process.cwd()) {
|
|
367
|
+
this.cwd = cwd;
|
|
368
|
+
}
|
|
369
|
+
setConfig(config) {
|
|
370
|
+
this.config = config;
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Adds a schema file by reading it.
|
|
374
|
+
* @param filePath - Schema file path.
|
|
375
|
+
*/
|
|
376
|
+
async addSchema(filePath) {
|
|
377
|
+
const code = await (0, node_fs_promises.readFile)(filePath, "utf8");
|
|
378
|
+
this.schemas.set(filePath, {
|
|
379
|
+
code,
|
|
380
|
+
filePath,
|
|
381
|
+
startLine: 1
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Removes a schema file.
|
|
386
|
+
* @param filePath - Schema file path.
|
|
387
|
+
*/
|
|
388
|
+
removeSchema(filePath) {
|
|
389
|
+
this.schemas.delete(filePath);
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Adds a document file by reading and extracting operations.
|
|
393
|
+
* @param filePath - Document file path.
|
|
394
|
+
*/
|
|
395
|
+
async addDocument(filePath) {
|
|
396
|
+
const { sources, errors } = await extractGraphQLSources({
|
|
397
|
+
code: await (0, node_fs_promises.readFile)(filePath, "utf8"),
|
|
398
|
+
filePath,
|
|
399
|
+
startLine: 1
|
|
400
|
+
});
|
|
401
|
+
this.documents.set(filePath, sources);
|
|
402
|
+
if (errors.length > 0) throw new MearieAggregateError(errors);
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Removes a document file.
|
|
406
|
+
* @param filePath - Document file path.
|
|
407
|
+
*/
|
|
408
|
+
removeDocument(filePath) {
|
|
409
|
+
this.documents.delete(filePath);
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Generates code from cached schemas and documents and writes to files.
|
|
413
|
+
* @returns Written file paths.
|
|
414
|
+
* @throws {Error} If generation or writing fails.
|
|
415
|
+
*/
|
|
416
|
+
async generate() {
|
|
417
|
+
const { sources, errors } = generate({
|
|
418
|
+
schemas: [...this.schemas.values()],
|
|
419
|
+
documents: [...this.documents.values()].flat(),
|
|
420
|
+
config: this.config
|
|
421
|
+
});
|
|
422
|
+
await writeFiles(this.cwd, sources);
|
|
423
|
+
if (errors.length > 0) throw new MearieAggregateError(errors);
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
//#endregion
|
|
428
|
+
//#region src/logger.ts
|
|
429
|
+
(0, _logtape_logtape.configureSync)({
|
|
430
|
+
reset: true,
|
|
431
|
+
sinks: { console: (0, _logtape_logtape.getConsoleSink)({ formatter: (0, _logtape_logtape.getAnsiColorFormatter)({
|
|
432
|
+
level: "FULL",
|
|
433
|
+
timestamp: "time",
|
|
434
|
+
category: (category) => `💬 ${category.join("·")}`
|
|
435
|
+
}) }) },
|
|
436
|
+
loggers: [{
|
|
437
|
+
category: "mearie",
|
|
438
|
+
lowestLevel: "info",
|
|
439
|
+
sinks: ["console"]
|
|
440
|
+
}, {
|
|
441
|
+
category: ["logtape", "meta"],
|
|
442
|
+
lowestLevel: "warning",
|
|
443
|
+
sinks: ["console"]
|
|
444
|
+
}]
|
|
445
|
+
});
|
|
446
|
+
const logger = (0, _logtape_logtape.getLogger)(["mearie"]);
|
|
447
|
+
const formatMearieError = (error) => {
|
|
448
|
+
const parts = [
|
|
449
|
+
error.filePath,
|
|
450
|
+
error.line,
|
|
451
|
+
error.column
|
|
452
|
+
].filter((part) => part !== void 0 && part !== null).map(String);
|
|
453
|
+
const location = parts.length > 0 ? parts.join(":") : "";
|
|
454
|
+
if (location) return `${picocolors.default.bold(error.message)} ${picocolors.default.cyan(picocolors.default.underline(location))}`;
|
|
455
|
+
return picocolors.default.bold(error.message);
|
|
456
|
+
};
|
|
457
|
+
/**
|
|
458
|
+
* Reports an error using the provided logger.
|
|
459
|
+
* @param logger - The logger to use.
|
|
460
|
+
* @param error - The error to report.
|
|
461
|
+
*/
|
|
462
|
+
const report = (logger, error) => {
|
|
463
|
+
if (error instanceof MearieAggregateError) for (const err of error.errors) logger.error(formatMearieError(err));
|
|
464
|
+
else if (error instanceof MearieError) logger.error(formatMearieError(error));
|
|
465
|
+
else logger.error("{error}", { error });
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
//#endregion
|
|
469
|
+
exports.CodegenContext = CodegenContext;
|
|
470
|
+
exports.createMatcher = createMatcher;
|
|
471
|
+
exports.findFiles = findFiles;
|
|
472
|
+
exports.logger = logger;
|
|
473
|
+
exports.report = report;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Logger } from "@logtape/logtape";
|
|
2
|
+
|
|
3
|
+
//#region src/glob.d.ts
|
|
4
|
+
type GlobPatterns = {
|
|
5
|
+
include: string | string[];
|
|
6
|
+
exclude?: string | string[];
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Finds files matching the given patterns.
|
|
10
|
+
* @param cwd - Current working directory.
|
|
11
|
+
* @param patterns - File patterns to match.
|
|
12
|
+
* @returns Array of absolute file paths.
|
|
13
|
+
*/
|
|
14
|
+
declare const findFiles: (cwd: string, patterns: GlobPatterns) => Promise<string[]>;
|
|
15
|
+
/**
|
|
16
|
+
* Creates a matcher function.
|
|
17
|
+
* @internal
|
|
18
|
+
* @param patterns - File patterns to match.
|
|
19
|
+
* @returns A function that checks if a file path matches the patterns.
|
|
20
|
+
*/
|
|
21
|
+
declare const createMatcher: (patterns: GlobPatterns) => ((filePath: string) => boolean);
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region src/generator.d.ts
|
|
24
|
+
type GenerateConfig = {
|
|
25
|
+
scalars?: Record<string, string>;
|
|
26
|
+
};
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region src/context.d.ts
|
|
29
|
+
/**
|
|
30
|
+
* Stateful context for incremental code generation.
|
|
31
|
+
* Reads and caches file contents when files are added/updated.
|
|
32
|
+
*/
|
|
33
|
+
declare class CodegenContext {
|
|
34
|
+
private schemas;
|
|
35
|
+
private documents;
|
|
36
|
+
private cwd;
|
|
37
|
+
private config?;
|
|
38
|
+
constructor(cwd?: string);
|
|
39
|
+
setConfig(config: GenerateConfig): void;
|
|
40
|
+
/**
|
|
41
|
+
* Adds a schema file by reading it.
|
|
42
|
+
* @param filePath - Schema file path.
|
|
43
|
+
*/
|
|
44
|
+
addSchema(filePath: string): Promise<void>;
|
|
45
|
+
/**
|
|
46
|
+
* Removes a schema file.
|
|
47
|
+
* @param filePath - Schema file path.
|
|
48
|
+
*/
|
|
49
|
+
removeSchema(filePath: string): void;
|
|
50
|
+
/**
|
|
51
|
+
* Adds a document file by reading and extracting operations.
|
|
52
|
+
* @param filePath - Document file path.
|
|
53
|
+
*/
|
|
54
|
+
addDocument(filePath: string): Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* Removes a document file.
|
|
57
|
+
* @param filePath - Document file path.
|
|
58
|
+
*/
|
|
59
|
+
removeDocument(filePath: string): void;
|
|
60
|
+
/**
|
|
61
|
+
* Generates code from cached schemas and documents and writes to files.
|
|
62
|
+
* @returns Written file paths.
|
|
63
|
+
* @throws {Error} If generation or writing fails.
|
|
64
|
+
*/
|
|
65
|
+
generate(): Promise<void>;
|
|
66
|
+
}
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region src/logger.d.ts
|
|
69
|
+
declare const logger: Logger;
|
|
70
|
+
/**
|
|
71
|
+
* Reports an error using the provided logger.
|
|
72
|
+
* @param logger - The logger to use.
|
|
73
|
+
* @param error - The error to report.
|
|
74
|
+
*/
|
|
75
|
+
declare const report: (logger: Logger, error: unknown) => void;
|
|
76
|
+
//#endregion
|
|
77
|
+
export { CodegenContext, createMatcher, findFiles, logger, report };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Logger } from "@logtape/logtape";
|
|
2
|
+
|
|
3
|
+
//#region src/glob.d.ts
|
|
4
|
+
type GlobPatterns = {
|
|
5
|
+
include: string | string[];
|
|
6
|
+
exclude?: string | string[];
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Finds files matching the given patterns.
|
|
10
|
+
* @param cwd - Current working directory.
|
|
11
|
+
* @param patterns - File patterns to match.
|
|
12
|
+
* @returns Array of absolute file paths.
|
|
13
|
+
*/
|
|
14
|
+
declare const findFiles: (cwd: string, patterns: GlobPatterns) => Promise<string[]>;
|
|
15
|
+
/**
|
|
16
|
+
* Creates a matcher function.
|
|
17
|
+
* @internal
|
|
18
|
+
* @param patterns - File patterns to match.
|
|
19
|
+
* @returns A function that checks if a file path matches the patterns.
|
|
20
|
+
*/
|
|
21
|
+
declare const createMatcher: (patterns: GlobPatterns) => ((filePath: string) => boolean);
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region src/generator.d.ts
|
|
24
|
+
type GenerateConfig = {
|
|
25
|
+
scalars?: Record<string, string>;
|
|
26
|
+
};
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region src/context.d.ts
|
|
29
|
+
/**
|
|
30
|
+
* Stateful context for incremental code generation.
|
|
31
|
+
* Reads and caches file contents when files are added/updated.
|
|
32
|
+
*/
|
|
33
|
+
declare class CodegenContext {
|
|
34
|
+
private schemas;
|
|
35
|
+
private documents;
|
|
36
|
+
private cwd;
|
|
37
|
+
private config?;
|
|
38
|
+
constructor(cwd?: string);
|
|
39
|
+
setConfig(config: GenerateConfig): void;
|
|
40
|
+
/**
|
|
41
|
+
* Adds a schema file by reading it.
|
|
42
|
+
* @param filePath - Schema file path.
|
|
43
|
+
*/
|
|
44
|
+
addSchema(filePath: string): Promise<void>;
|
|
45
|
+
/**
|
|
46
|
+
* Removes a schema file.
|
|
47
|
+
* @param filePath - Schema file path.
|
|
48
|
+
*/
|
|
49
|
+
removeSchema(filePath: string): void;
|
|
50
|
+
/**
|
|
51
|
+
* Adds a document file by reading and extracting operations.
|
|
52
|
+
* @param filePath - Document file path.
|
|
53
|
+
*/
|
|
54
|
+
addDocument(filePath: string): Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* Removes a document file.
|
|
57
|
+
* @param filePath - Document file path.
|
|
58
|
+
*/
|
|
59
|
+
removeDocument(filePath: string): void;
|
|
60
|
+
/**
|
|
61
|
+
* Generates code from cached schemas and documents and writes to files.
|
|
62
|
+
* @returns Written file paths.
|
|
63
|
+
* @throws {Error} If generation or writing fails.
|
|
64
|
+
*/
|
|
65
|
+
generate(): Promise<void>;
|
|
66
|
+
}
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region src/logger.d.ts
|
|
69
|
+
declare const logger: Logger;
|
|
70
|
+
/**
|
|
71
|
+
* Reports an error using the provided logger.
|
|
72
|
+
* @param logger - The logger to use.
|
|
73
|
+
* @param error - The error to report.
|
|
74
|
+
*/
|
|
75
|
+
declare const report: (logger: Logger, error: unknown) => void;
|
|
76
|
+
//#endregion
|
|
77
|
+
export { CodegenContext, createMatcher, findFiles, logger, report };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
import { glob } from "tinyglobby";
|
|
2
|
+
import picomatch from "picomatch";
|
|
3
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
4
|
+
import { extractGraphQLSources, generateCode } from "@mearie/native";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { configureSync, getAnsiColorFormatter, getConsoleSink, getLogger } from "@logtape/logtape";
|
|
7
|
+
import pc from "picocolors";
|
|
8
|
+
|
|
9
|
+
//#region src/glob.ts
|
|
10
|
+
/**
|
|
11
|
+
* Finds files matching the given patterns.
|
|
12
|
+
* @param cwd - Current working directory.
|
|
13
|
+
* @param patterns - File patterns to match.
|
|
14
|
+
* @returns Array of absolute file paths.
|
|
15
|
+
*/
|
|
16
|
+
const findFiles = async (cwd, patterns) => {
|
|
17
|
+
const includePatterns = Array.isArray(patterns.include) ? patterns.include : [patterns.include];
|
|
18
|
+
const negatedExcludes = (patterns.exclude ? Array.isArray(patterns.exclude) ? patterns.exclude : [patterns.exclude] : []).map((pattern) => `!${pattern}`);
|
|
19
|
+
return await glob([...includePatterns, ...negatedExcludes], {
|
|
20
|
+
cwd,
|
|
21
|
+
absolute: true
|
|
22
|
+
});
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Creates a matcher function.
|
|
26
|
+
* @internal
|
|
27
|
+
* @param patterns - File patterns to match.
|
|
28
|
+
* @returns A function that checks if a file path matches the patterns.
|
|
29
|
+
*/
|
|
30
|
+
const createMatcher = (patterns) => {
|
|
31
|
+
const includePatterns = Array.isArray(patterns.include) ? patterns.include : [patterns.include];
|
|
32
|
+
const excludePatterns = patterns.exclude ? Array.isArray(patterns.exclude) ? patterns.exclude : [patterns.exclude] : [];
|
|
33
|
+
const isMatch = picomatch(includePatterns);
|
|
34
|
+
const isExcluded = excludePatterns.length > 0 ? picomatch(excludePatterns) : () => false;
|
|
35
|
+
return (filePath) => {
|
|
36
|
+
return isMatch(filePath) && !isExcluded(filePath);
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/errors.ts
|
|
42
|
+
/**
|
|
43
|
+
* Custom error class for Mearie-specific errors.
|
|
44
|
+
*/
|
|
45
|
+
var MearieError = class MearieError extends Error {
|
|
46
|
+
filePath;
|
|
47
|
+
line;
|
|
48
|
+
column;
|
|
49
|
+
constructor(message, filePath, line, column) {
|
|
50
|
+
super(message);
|
|
51
|
+
this.name = "MearieError";
|
|
52
|
+
this.filePath = filePath;
|
|
53
|
+
this.line = line;
|
|
54
|
+
this.column = column;
|
|
55
|
+
}
|
|
56
|
+
static fromNative(data) {
|
|
57
|
+
if (!data || typeof data !== "object") throw new TypeError("Invalid native error data");
|
|
58
|
+
const error = data;
|
|
59
|
+
const filePath = error.location?.file_path;
|
|
60
|
+
const line = error.location?.line;
|
|
61
|
+
const column = error.location?.column;
|
|
62
|
+
return new MearieError(error.message, filePath, line, column);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Aggregate error for multiple Mearie errors.
|
|
67
|
+
*/
|
|
68
|
+
var MearieAggregateError = class extends Error {
|
|
69
|
+
errors;
|
|
70
|
+
constructor(errors, message) {
|
|
71
|
+
super(message ?? `${errors.length} error${errors.length > 1 ? "s" : ""} occurred`);
|
|
72
|
+
this.name = "MearieAggregateError";
|
|
73
|
+
this.errors = errors;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Error thrown when `@vue/compiler-sfc` is not installed.
|
|
78
|
+
*/
|
|
79
|
+
var MissingVueCompilerError = class extends Error {
|
|
80
|
+
constructor() {
|
|
81
|
+
super(`
|
|
82
|
+
GraphQL operations cannot be extracted from Vue files without @vue/compiler-sfc.
|
|
83
|
+
|
|
84
|
+
Install it with:
|
|
85
|
+
npm install @vue/compiler-sfc
|
|
86
|
+
# or
|
|
87
|
+
pnpm add @vue/compiler-sfc
|
|
88
|
+
# or
|
|
89
|
+
yarn add @vue/compiler-sfc
|
|
90
|
+
`);
|
|
91
|
+
this.name = "MissingVueCompilerError";
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* Error thrown when svelte compiler is not installed.
|
|
96
|
+
*/
|
|
97
|
+
var MissingSvelteCompilerError = class extends Error {
|
|
98
|
+
constructor() {
|
|
99
|
+
super(`
|
|
100
|
+
GraphQL operations cannot be extracted from Svelte files without svelte.
|
|
101
|
+
|
|
102
|
+
Install it with:
|
|
103
|
+
npm install svelte
|
|
104
|
+
# or
|
|
105
|
+
pnpm add svelte
|
|
106
|
+
# or
|
|
107
|
+
yarn add svelte
|
|
108
|
+
`);
|
|
109
|
+
this.name = "MissingSvelteCompilerError";
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* Error thrown when TypeScript is not installed.
|
|
114
|
+
*/
|
|
115
|
+
var MissingTypeScriptError = class extends Error {
|
|
116
|
+
constructor() {
|
|
117
|
+
super(`
|
|
118
|
+
GraphQL operations cannot be extracted from Vue files without typescript.
|
|
119
|
+
|
|
120
|
+
Install it with:
|
|
121
|
+
npm install typescript
|
|
122
|
+
# or
|
|
123
|
+
pnpm add typescript
|
|
124
|
+
# or
|
|
125
|
+
yarn add typescript
|
|
126
|
+
`);
|
|
127
|
+
this.name = "MissingTypeScriptError";
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
//#endregion
|
|
132
|
+
//#region src/loaders.ts
|
|
133
|
+
/**
|
|
134
|
+
* Loads the Vue compiler dynamically.
|
|
135
|
+
* @returns The Vue compiler module.
|
|
136
|
+
* @throws {MissingVueCompilerError} If `@vue/compiler-sfc` is not installed.
|
|
137
|
+
*/
|
|
138
|
+
const loadVueCompiler = async () => {
|
|
139
|
+
try {
|
|
140
|
+
return await import("@vue/compiler-sfc");
|
|
141
|
+
} catch {
|
|
142
|
+
throw new MissingVueCompilerError();
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
/**
|
|
146
|
+
* Loads TypeScript dynamically.
|
|
147
|
+
* @returns The TypeScript module.
|
|
148
|
+
* @throws {MissingTypeScriptError} If typescript is not installed.
|
|
149
|
+
*/
|
|
150
|
+
const loadTypeScript = async () => {
|
|
151
|
+
try {
|
|
152
|
+
return await import("typescript");
|
|
153
|
+
} catch {
|
|
154
|
+
throw new MissingTypeScriptError();
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
/**
|
|
158
|
+
* Loads the Svelte compiler dynamically.
|
|
159
|
+
* @returns The Svelte compiler module.
|
|
160
|
+
* @throws {MissingSvelteCompilerError} If svelte is not installed.
|
|
161
|
+
*/
|
|
162
|
+
const loadSvelteCompiler = async () => {
|
|
163
|
+
try {
|
|
164
|
+
return await import("svelte/compiler");
|
|
165
|
+
} catch {
|
|
166
|
+
throw new MissingSvelteCompilerError();
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
//#endregion
|
|
171
|
+
//#region src/parsers.ts
|
|
172
|
+
const extractVueScript = async (source) => {
|
|
173
|
+
const [vueCompiler, typescript] = await Promise.all([loadVueCompiler(), loadTypeScript()]);
|
|
174
|
+
vueCompiler.registerTS(() => typescript);
|
|
175
|
+
const { descriptor } = vueCompiler.parse(source.code, { filename: source.filePath });
|
|
176
|
+
const blocks = [];
|
|
177
|
+
if (descriptor.script) blocks.push({
|
|
178
|
+
code: descriptor.script.content,
|
|
179
|
+
filePath: source.filePath,
|
|
180
|
+
startLine: source.startLine + descriptor.script.loc.start.line - 1
|
|
181
|
+
});
|
|
182
|
+
if (descriptor.scriptSetup) blocks.push({
|
|
183
|
+
code: descriptor.scriptSetup.content,
|
|
184
|
+
filePath: source.filePath,
|
|
185
|
+
startLine: source.startLine + descriptor.scriptSetup.loc.start.line - 1
|
|
186
|
+
});
|
|
187
|
+
return blocks;
|
|
188
|
+
};
|
|
189
|
+
const extractSvelteScript = async (source) => {
|
|
190
|
+
const ast = (await loadSvelteCompiler()).parse(source.code);
|
|
191
|
+
const blocks = [];
|
|
192
|
+
if (ast.instance?.content) {
|
|
193
|
+
const code = source.code.slice(ast.instance.content.start, ast.instance.content.end);
|
|
194
|
+
const lineOffset = source.code.slice(0, ast.instance.content.start).split("\n").length - 1;
|
|
195
|
+
blocks.push({
|
|
196
|
+
code,
|
|
197
|
+
filePath: `${source.filePath}.instance.ts`,
|
|
198
|
+
startLine: source.startLine + lineOffset
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
if (ast.module?.content) {
|
|
202
|
+
const code = source.code.slice(ast.module.content.start, ast.module.content.end);
|
|
203
|
+
const lineOffset = source.code.slice(0, ast.module.content.start).split("\n").length - 1;
|
|
204
|
+
blocks.push({
|
|
205
|
+
code,
|
|
206
|
+
filePath: `${source.filePath}.module.ts`,
|
|
207
|
+
startLine: source.startLine + lineOffset
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
return blocks;
|
|
211
|
+
};
|
|
212
|
+
const extractAstroScripts = (source) => {
|
|
213
|
+
const blocks = [];
|
|
214
|
+
const frontmatterMatch = /^---\s*\n([\s\S]*?)\n---/.exec(source.code);
|
|
215
|
+
if (frontmatterMatch) {
|
|
216
|
+
const lineOffset = source.code.slice(0, frontmatterMatch.index).split("\n").length;
|
|
217
|
+
blocks.push({
|
|
218
|
+
code: frontmatterMatch[1],
|
|
219
|
+
filePath: `${source.filePath}.frontmatter.ts`,
|
|
220
|
+
startLine: source.startLine + lineOffset
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
const scriptMatches = source.code.matchAll(/<script\b[^>]*>([\s\S]*?)<\/script>/gi);
|
|
224
|
+
let index = 0;
|
|
225
|
+
for (const match of scriptMatches) {
|
|
226
|
+
const lineOffset = source.code.slice(0, match.index).split("\n").length;
|
|
227
|
+
blocks.push({
|
|
228
|
+
code: match[1],
|
|
229
|
+
filePath: `${source.filePath}.${index}.ts`,
|
|
230
|
+
startLine: source.startLine + lineOffset
|
|
231
|
+
});
|
|
232
|
+
index++;
|
|
233
|
+
}
|
|
234
|
+
return blocks;
|
|
235
|
+
};
|
|
236
|
+
const extractMarkdownCodeBlocks = (source) => {
|
|
237
|
+
const codeBlocks = [];
|
|
238
|
+
const codeBlockRegex = /```(tsx|ts)[^\n]*mearie[^\n]*\n([\s\S]*?)```/g;
|
|
239
|
+
let match;
|
|
240
|
+
let index = 0;
|
|
241
|
+
while ((match = codeBlockRegex.exec(source.code)) !== null) {
|
|
242
|
+
const lineOffset = source.code.slice(0, match.index).split("\n").length;
|
|
243
|
+
codeBlocks.push({
|
|
244
|
+
code: match[2],
|
|
245
|
+
filePath: `${source.filePath}.${index}.${match[1]}`,
|
|
246
|
+
startLine: source.startLine + lineOffset
|
|
247
|
+
});
|
|
248
|
+
index++;
|
|
249
|
+
}
|
|
250
|
+
return codeBlocks;
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
//#endregion
|
|
254
|
+
//#region src/extractor.ts
|
|
255
|
+
const extractGraphQLSources$1 = async (source) => {
|
|
256
|
+
const ext = source.filePath.split(".").pop()?.toLowerCase();
|
|
257
|
+
const sources = [];
|
|
258
|
+
const errors = [];
|
|
259
|
+
const blocks = [];
|
|
260
|
+
try {
|
|
261
|
+
switch (ext) {
|
|
262
|
+
case "vue":
|
|
263
|
+
blocks.push(...await extractVueScript(source));
|
|
264
|
+
break;
|
|
265
|
+
case "svelte":
|
|
266
|
+
blocks.push(...await extractSvelteScript(source));
|
|
267
|
+
break;
|
|
268
|
+
case "astro":
|
|
269
|
+
blocks.push(...extractAstroScripts(source));
|
|
270
|
+
break;
|
|
271
|
+
case "md":
|
|
272
|
+
blocks.push(...extractMarkdownCodeBlocks(source));
|
|
273
|
+
break;
|
|
274
|
+
case "js":
|
|
275
|
+
case "jsx":
|
|
276
|
+
case "ts":
|
|
277
|
+
case "tsx":
|
|
278
|
+
blocks.push(source);
|
|
279
|
+
break;
|
|
280
|
+
default: return {
|
|
281
|
+
sources,
|
|
282
|
+
errors
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
} catch (error) {
|
|
286
|
+
throw new MearieError(error instanceof Error ? error.message : String(error), source.filePath);
|
|
287
|
+
}
|
|
288
|
+
for (const block of blocks) {
|
|
289
|
+
const result = extractGraphQLSources(block);
|
|
290
|
+
sources.push(...result.sources);
|
|
291
|
+
errors.push(...result.errors.map((error) => MearieError.fromNative(error)));
|
|
292
|
+
}
|
|
293
|
+
return {
|
|
294
|
+
sources,
|
|
295
|
+
errors
|
|
296
|
+
};
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
//#endregion
|
|
300
|
+
//#region src/generator.ts
|
|
301
|
+
/**
|
|
302
|
+
* Generates code from GraphQL documents and schemas.
|
|
303
|
+
* @param options - Generation options.
|
|
304
|
+
* @returns Generated code.
|
|
305
|
+
* @throws {Error} If code generation fails.
|
|
306
|
+
*/
|
|
307
|
+
const generate = (options) => {
|
|
308
|
+
const { schemas, documents, config } = options;
|
|
309
|
+
const { sources, errors } = generateCode(schemas, documents, config);
|
|
310
|
+
return {
|
|
311
|
+
sources,
|
|
312
|
+
errors: errors.map((error) => MearieError.fromNative(error))
|
|
313
|
+
};
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
//#endregion
|
|
317
|
+
//#region src/writer.ts
|
|
318
|
+
const writeFiles = async (cwd, sources) => {
|
|
319
|
+
const mearieDir = path.resolve(cwd, ".mearie");
|
|
320
|
+
await mkdir(mearieDir, { recursive: true });
|
|
321
|
+
await Promise.all(sources.map((source) => writeFile(path.join(mearieDir, source.filePath), source.code, "utf8")));
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
//#endregion
|
|
325
|
+
//#region src/context.ts
|
|
326
|
+
/**
|
|
327
|
+
* Stateful context for incremental code generation.
|
|
328
|
+
* Reads and caches file contents when files are added/updated.
|
|
329
|
+
*/
|
|
330
|
+
var CodegenContext = class {
|
|
331
|
+
schemas = /* @__PURE__ */ new Map();
|
|
332
|
+
documents = /* @__PURE__ */ new Map();
|
|
333
|
+
cwd;
|
|
334
|
+
config;
|
|
335
|
+
constructor(cwd = process.cwd()) {
|
|
336
|
+
this.cwd = cwd;
|
|
337
|
+
}
|
|
338
|
+
setConfig(config) {
|
|
339
|
+
this.config = config;
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Adds a schema file by reading it.
|
|
343
|
+
* @param filePath - Schema file path.
|
|
344
|
+
*/
|
|
345
|
+
async addSchema(filePath) {
|
|
346
|
+
const code = await readFile(filePath, "utf8");
|
|
347
|
+
this.schemas.set(filePath, {
|
|
348
|
+
code,
|
|
349
|
+
filePath,
|
|
350
|
+
startLine: 1
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Removes a schema file.
|
|
355
|
+
* @param filePath - Schema file path.
|
|
356
|
+
*/
|
|
357
|
+
removeSchema(filePath) {
|
|
358
|
+
this.schemas.delete(filePath);
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Adds a document file by reading and extracting operations.
|
|
362
|
+
* @param filePath - Document file path.
|
|
363
|
+
*/
|
|
364
|
+
async addDocument(filePath) {
|
|
365
|
+
const { sources, errors } = await extractGraphQLSources$1({
|
|
366
|
+
code: await readFile(filePath, "utf8"),
|
|
367
|
+
filePath,
|
|
368
|
+
startLine: 1
|
|
369
|
+
});
|
|
370
|
+
this.documents.set(filePath, sources);
|
|
371
|
+
if (errors.length > 0) throw new MearieAggregateError(errors);
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Removes a document file.
|
|
375
|
+
* @param filePath - Document file path.
|
|
376
|
+
*/
|
|
377
|
+
removeDocument(filePath) {
|
|
378
|
+
this.documents.delete(filePath);
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Generates code from cached schemas and documents and writes to files.
|
|
382
|
+
* @returns Written file paths.
|
|
383
|
+
* @throws {Error} If generation or writing fails.
|
|
384
|
+
*/
|
|
385
|
+
async generate() {
|
|
386
|
+
const { sources, errors } = generate({
|
|
387
|
+
schemas: [...this.schemas.values()],
|
|
388
|
+
documents: [...this.documents.values()].flat(),
|
|
389
|
+
config: this.config
|
|
390
|
+
});
|
|
391
|
+
await writeFiles(this.cwd, sources);
|
|
392
|
+
if (errors.length > 0) throw new MearieAggregateError(errors);
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
//#endregion
|
|
397
|
+
//#region src/logger.ts
|
|
398
|
+
configureSync({
|
|
399
|
+
reset: true,
|
|
400
|
+
sinks: { console: getConsoleSink({ formatter: getAnsiColorFormatter({
|
|
401
|
+
level: "FULL",
|
|
402
|
+
timestamp: "time",
|
|
403
|
+
category: (category) => `💬 ${category.join("·")}`
|
|
404
|
+
}) }) },
|
|
405
|
+
loggers: [{
|
|
406
|
+
category: "mearie",
|
|
407
|
+
lowestLevel: "info",
|
|
408
|
+
sinks: ["console"]
|
|
409
|
+
}, {
|
|
410
|
+
category: ["logtape", "meta"],
|
|
411
|
+
lowestLevel: "warning",
|
|
412
|
+
sinks: ["console"]
|
|
413
|
+
}]
|
|
414
|
+
});
|
|
415
|
+
const logger = getLogger(["mearie"]);
|
|
416
|
+
const formatMearieError = (error) => {
|
|
417
|
+
const parts = [
|
|
418
|
+
error.filePath,
|
|
419
|
+
error.line,
|
|
420
|
+
error.column
|
|
421
|
+
].filter((part) => part !== void 0 && part !== null).map(String);
|
|
422
|
+
const location = parts.length > 0 ? parts.join(":") : "";
|
|
423
|
+
if (location) return `${pc.bold(error.message)} ${pc.cyan(pc.underline(location))}`;
|
|
424
|
+
return pc.bold(error.message);
|
|
425
|
+
};
|
|
426
|
+
/**
|
|
427
|
+
* Reports an error using the provided logger.
|
|
428
|
+
* @param logger - The logger to use.
|
|
429
|
+
* @param error - The error to report.
|
|
430
|
+
*/
|
|
431
|
+
const report = (logger, error) => {
|
|
432
|
+
if (error instanceof MearieAggregateError) for (const err of error.errors) logger.error(formatMearieError(err));
|
|
433
|
+
else if (error instanceof MearieError) logger.error(formatMearieError(error));
|
|
434
|
+
else logger.error("{error}", { error });
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
//#endregion
|
|
438
|
+
export { CodegenContext, createMatcher, findFiles, logger, report };
|
package/package.json
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mearie/codegen",
|
|
3
|
+
"version": "0.0.0-next-20260228035926",
|
|
4
|
+
"description": "Type-safe, zero-overhead GraphQL client",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"graphql",
|
|
7
|
+
"graphql-client",
|
|
8
|
+
"typescript",
|
|
9
|
+
"codegen",
|
|
10
|
+
"cache"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://github.com/devunt/mearie#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/devunt/mearie/issues"
|
|
15
|
+
},
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/devunt/mearie.git",
|
|
19
|
+
"directory": "packages/codegen"
|
|
20
|
+
},
|
|
21
|
+
"funding": {
|
|
22
|
+
"type": "github",
|
|
23
|
+
"url": "https://github.com/sponsors/devunt"
|
|
24
|
+
},
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"author": "Bae Junehyeon <finn@penxle.io>",
|
|
27
|
+
"sideEffects": false,
|
|
28
|
+
"type": "module",
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"import": {
|
|
32
|
+
"types": "./dist/index.d.mts",
|
|
33
|
+
"default": "./dist/index.mjs"
|
|
34
|
+
},
|
|
35
|
+
"require": {
|
|
36
|
+
"types": "./dist/index.d.cts",
|
|
37
|
+
"default": "./dist/index.cjs"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"./package.json": "./package.json"
|
|
41
|
+
},
|
|
42
|
+
"files": [
|
|
43
|
+
"dist",
|
|
44
|
+
"package.json",
|
|
45
|
+
"README.md"
|
|
46
|
+
],
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@logtape/logtape": "^2.0.2",
|
|
49
|
+
"picocolors": "^1.1.1",
|
|
50
|
+
"picomatch": "^4.0.3",
|
|
51
|
+
"tinyglobby": "^0.2.15",
|
|
52
|
+
"@mearie/config": "0.0.0-next-20260228035926",
|
|
53
|
+
"@mearie/native": "0.0.0-next-20260228035926",
|
|
54
|
+
"@mearie/shared": "0.0.0-next-20260228035926"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@types/node": "^25.3.0",
|
|
58
|
+
"@types/picomatch": "^4.0.2",
|
|
59
|
+
"@vue/compiler-sfc": "^3.5.29",
|
|
60
|
+
"svelte": "^5.53.3",
|
|
61
|
+
"tsdown": "^0.20.3",
|
|
62
|
+
"typescript": "^5.9.3"
|
|
63
|
+
},
|
|
64
|
+
"engines": {
|
|
65
|
+
"bun": ">=1.2.0",
|
|
66
|
+
"deno": ">=2.2.0",
|
|
67
|
+
"node": ">=20.0.0"
|
|
68
|
+
},
|
|
69
|
+
"publishConfig": {
|
|
70
|
+
"access": "public"
|
|
71
|
+
},
|
|
72
|
+
"scripts": {
|
|
73
|
+
"build": "tsdown",
|
|
74
|
+
"typecheck": "tsc"
|
|
75
|
+
},
|
|
76
|
+
"main": "./dist/index.cjs",
|
|
77
|
+
"module": "./dist/index.mjs",
|
|
78
|
+
"types": "./dist/index.d.mts"
|
|
79
|
+
}
|