@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 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;
@@ -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 };
@@ -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
+ }