@kurotako/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,1238 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ BANNER: () => BANNER,
34
+ DependencyCycleError: () => DependencyCycleError,
35
+ DriverError: () => DriverError,
36
+ DuplicateNamespaceError: () => DuplicateNamespaceError,
37
+ GITATTRIBUTES: () => GITATTRIBUTES,
38
+ HookError: () => HookError,
39
+ InvalidDependencyError: () => InvalidDependencyError,
40
+ InvalidOutputPathError: () => InvalidOutputPathError,
41
+ IrValidationError: () => IrValidationError,
42
+ MissingPackageWorkspaceFilesError: () => MissingPackageWorkspaceFilesError,
43
+ NamespaceMismatchError: () => NamespaceMismatchError,
44
+ OutputCollisionError: () => OutputCollisionError,
45
+ OutputNotGeneratedError: () => OutputNotGeneratedError,
46
+ OutputPeerConflictError: () => OutputPeerConflictError,
47
+ PackageBuildError: () => PackageBuildError,
48
+ PackageInstallError: () => PackageInstallError,
49
+ TakoError: () => TakoError,
50
+ UnknownDependencyError: () => UnknownDependencyError,
51
+ UnsupportedOutputModeError: () => UnsupportedOutputModeError,
52
+ applyBanner: () => applyBanner,
53
+ childLogger: () => childLogger,
54
+ collectPeerDependencies: () => collectPeerDependencies,
55
+ directoryWriter: () => directoryWriter,
56
+ noopLogger: () => noopLogger,
57
+ packageWriter: () => packageWriter,
58
+ resolvePackageManager: () => resolvePackageManager,
59
+ run: () => run,
60
+ runInstall: () => runInstall,
61
+ selectWriter: () => selectWriter,
62
+ synthesizeRootBarrels: () => synthesizeRootBarrels
63
+ });
64
+ module.exports = __toCommonJS(index_exports);
65
+
66
+ // src/errors.ts
67
+ var TakoError = class extends Error {
68
+ code;
69
+ constructor(code, message, options) {
70
+ super(message, options);
71
+ this.name = new.target.name;
72
+ this.code = code;
73
+ }
74
+ };
75
+ var NamespaceMismatchError = class extends TakoError {
76
+ namespace;
77
+ returned;
78
+ constructor(namespace, returned) {
79
+ super(
80
+ "namespace_mismatch",
81
+ `parser for namespace '${namespace}' returned a SourceIR with namespace '${returned}'`
82
+ );
83
+ this.namespace = namespace;
84
+ this.returned = returned;
85
+ }
86
+ };
87
+ var IrValidationError = class extends TakoError {
88
+ issues;
89
+ namespace;
90
+ constructor(issues, namespace) {
91
+ const detail = issues.map((i) => `${i.path === "" ? "<root>" : i.path}: ${i.message}`).join("; ");
92
+ const where = namespace ? ` in namespace '${namespace}'` : "";
93
+ super("ir_invalid", `invalid IR${where}: ${detail}`);
94
+ this.issues = issues;
95
+ this.namespace = namespace;
96
+ }
97
+ };
98
+ var DuplicateNamespaceError = class extends TakoError {
99
+ namespace;
100
+ constructor(namespace) {
101
+ super(
102
+ "duplicate_namespace",
103
+ `two sources claim the namespace '${namespace}'`
104
+ );
105
+ this.namespace = namespace;
106
+ }
107
+ };
108
+ var UnknownDependencyError = class extends TakoError {
109
+ generator;
110
+ missing;
111
+ constructor(generator, missing) {
112
+ super(
113
+ "unknown_dependency",
114
+ `generator '${generator}' declares a hard dependency on '${missing}', which is not in the config`
115
+ );
116
+ this.generator = generator;
117
+ this.missing = missing;
118
+ }
119
+ };
120
+ var InvalidDependencyError = class extends TakoError {
121
+ generator;
122
+ dependency;
123
+ constructor(generator, dependency) {
124
+ super(
125
+ "invalid_dependency",
126
+ `generator '${generator}' lists '${dependency}' in both dependsOn and optionalDependsOn`
127
+ );
128
+ this.generator = generator;
129
+ this.dependency = dependency;
130
+ }
131
+ };
132
+ var DependencyCycleError = class extends TakoError {
133
+ cycle;
134
+ constructor(cycle) {
135
+ super(
136
+ "dependency_cycle",
137
+ `the generator dependency graph has a cycle: ${cycle.join(" -> ")}`
138
+ );
139
+ this.cycle = cycle;
140
+ }
141
+ };
142
+ var OutputCollisionError = class extends TakoError {
143
+ path;
144
+ generators;
145
+ constructor(path5, generators, hint) {
146
+ super(
147
+ "output_collision",
148
+ `generators '${generators[0]}' and '${generators[1]}' both emit '${path5}'${hint ? `. ${hint}` : ""}`
149
+ );
150
+ this.path = path5;
151
+ this.generators = generators;
152
+ }
153
+ };
154
+ var InvalidOutputPathError = class extends TakoError {
155
+ path;
156
+ generator;
157
+ constructor(path5, generator) {
158
+ super(
159
+ "invalid_output_path",
160
+ `generator '${generator}' emits '${path5}', which escapes the output root`
161
+ );
162
+ this.path = path5;
163
+ this.generator = generator;
164
+ }
165
+ };
166
+ var UnsupportedOutputModeError = class extends TakoError {
167
+ mode;
168
+ constructor(mode) {
169
+ super(
170
+ "unsupported_output_mode",
171
+ `unsupported output mode '${mode}' (expected 'dir' or 'package')`
172
+ );
173
+ this.mode = mode;
174
+ }
175
+ };
176
+ var OutputPeerConflictError = class extends TakoError {
177
+ namespace;
178
+ package;
179
+ ranges;
180
+ generators;
181
+ constructor(namespace, pkg, ranges, generators) {
182
+ super(
183
+ "output_peer_conflict",
184
+ `namespace '${namespace}': generators [${generators.join(", ")}] declare peer '${pkg}' with conflicting ranges [${ranges.join(", ")}]`
185
+ );
186
+ this.namespace = namespace;
187
+ this.package = pkg;
188
+ this.ranges = ranges;
189
+ this.generators = generators;
190
+ }
191
+ };
192
+ var PackageBuildError = class extends TakoError {
193
+ namespace;
194
+ constructor(namespace, options) {
195
+ super(
196
+ "package_build_error",
197
+ `the build of the generated package for namespace '${namespace}' failed`,
198
+ options
199
+ );
200
+ this.namespace = namespace;
201
+ }
202
+ };
203
+ var MISSING_PACKAGE_WORKSPACE_FILE_GUIDANCE = {
204
+ "tsconfig.base.json": `Create '<workspaceRoot>/tsconfig.base.json':
205
+ {
206
+ "compilerOptions": {
207
+ "target": "ES2022",
208
+ "module": "ESNext",
209
+ "moduleResolution": "bundler",
210
+ "strict": true,
211
+ "skipLibCheck": true
212
+ }
213
+ }
214
+
215
+ Use these values as-is, no adjustment needed: in particular, keep
216
+ "moduleResolution": "bundler" \u2014 'node16'/'nodenext' would fail to compile
217
+ the extensionless \`export * from './zod';\` that tako's generated root
218
+ barrel always emits.`,
219
+ "tsup.config.base.{ts,js,mjs,cjs}": `Create '<workspaceRoot>/tsup.config.base.ts':
220
+ import type { Options } from 'tsup';
221
+
222
+ export const basePreset: Options = {
223
+ entry: ['src/index.ts'],
224
+ format: ['esm', 'cjs'],
225
+ dts: { compilerOptions: { composite: false, incremental: false } },
226
+ sourcemap: true,
227
+ clean: true,
228
+ target: 'node22',
229
+ outDir: 'dist',
230
+ };`,
231
+ "'typescript' (devDependency, needed for the .d.ts build)": `Run, from '<workspaceRoot>':
232
+ <your package manager> add -D typescript`
233
+ };
234
+ var MissingPackageWorkspaceFilesError = class extends TakoError {
235
+ workspaceRoot;
236
+ missing;
237
+ constructor(workspaceRoot, missing) {
238
+ const guidance = missing.map((item) => {
239
+ const template = MISSING_PACKAGE_WORKSPACE_FILE_GUIDANCE[item];
240
+ return template ? template.replaceAll("<workspaceRoot>", workspaceRoot) : item;
241
+ }).join("\n\n");
242
+ super(
243
+ "missing_package_workspace_files",
244
+ `mode 'package' requires 'tsconfig.base.json' and 'tsup.config.base.{ts,js,mjs,cjs}' in '${workspaceRoot}' (one directory above 'packagesDir'); missing: ${missing.join(", ")}
245
+
246
+ ${guidance}`
247
+ );
248
+ this.workspaceRoot = workspaceRoot;
249
+ this.missing = missing;
250
+ }
251
+ };
252
+ var OutputNotGeneratedError = class extends TakoError {
253
+ path;
254
+ constructor(path5) {
255
+ super(
256
+ "output_not_generated",
257
+ `refusing to wipe '${path5}': it is non-empty and its package.json lacks the '"//": "Generated by tako\u2026"' marker`
258
+ );
259
+ this.path = path5;
260
+ }
261
+ };
262
+ var PackageInstallError = class extends TakoError {
263
+ pm;
264
+ constructor(pm, options) {
265
+ super(
266
+ "package_install_error",
267
+ `the '${pm} install' step for the generated packages exited non-zero`,
268
+ options
269
+ );
270
+ this.pm = pm;
271
+ }
272
+ };
273
+ var DriverError = class extends TakoError {
274
+ role;
275
+ driverName;
276
+ namespace;
277
+ constructor(role, driverName, options) {
278
+ const where = options?.namespace ? ` (namespace '${options.namespace}')` : "";
279
+ super(
280
+ "driver_error",
281
+ `${role} '${driverName}'${where} threw during ${role === "parser" ? "parse" : "generate"}`,
282
+ options
283
+ );
284
+ this.role = role;
285
+ this.driverName = driverName;
286
+ this.namespace = options?.namespace;
287
+ }
288
+ };
289
+ var HookError = class extends TakoError {
290
+ hook;
291
+ constructor(hook, options) {
292
+ super("hook_error", `the '${hook}' hook threw`, options);
293
+ this.hook = hook;
294
+ }
295
+ };
296
+
297
+ // src/logger.ts
298
+ var noopLogger = {
299
+ debug() {
300
+ },
301
+ info() {
302
+ },
303
+ warn() {
304
+ },
305
+ error() {
306
+ }
307
+ };
308
+ function mergeMeta(prefixMeta, meta) {
309
+ if (meta === void 0) {
310
+ return { ...prefixMeta };
311
+ }
312
+ if (meta !== null && typeof meta === "object" && !Array.isArray(meta)) {
313
+ return { ...prefixMeta, ...meta };
314
+ }
315
+ return { ...prefixMeta, value: meta };
316
+ }
317
+ function childLogger(base, prefixMeta) {
318
+ return {
319
+ debug: (msg, meta) => base.debug(msg, mergeMeta(prefixMeta, meta)),
320
+ info: (msg, meta) => base.info(msg, mergeMeta(prefixMeta, meta)),
321
+ warn: (msg, meta) => base.warn(msg, mergeMeta(prefixMeta, meta)),
322
+ error: (msg, meta) => base.error(msg, mergeMeta(prefixMeta, meta))
323
+ };
324
+ }
325
+
326
+ // src/collect.ts
327
+ var import_node_path = __toESM(require("path"), 1);
328
+ function normalizePath(rawPath, generator) {
329
+ const posix = rawPath.replace(/\\/g, "/");
330
+ if (posix.startsWith("/")) {
331
+ throw new InvalidOutputPathError(rawPath, generator);
332
+ }
333
+ const normalized = import_node_path.default.posix.normalize(posix);
334
+ if (normalized === ".." || normalized.startsWith("../") || import_node_path.default.posix.isAbsolute(normalized)) {
335
+ throw new InvalidOutputPathError(rawPath, generator);
336
+ }
337
+ return normalized;
338
+ }
339
+ function mergeTrees(perGenerator, opts) {
340
+ const byPath = /* @__PURE__ */ new Map();
341
+ for (const { generator, files } of perGenerator) {
342
+ for (const file of files) {
343
+ const normalized = normalizePath(file.path, generator);
344
+ const existing = byPath.get(normalized);
345
+ if (existing) {
346
+ throw new OutputCollisionError(
347
+ normalized,
348
+ [existing.generator, generator],
349
+ opts?.collisionHint
350
+ );
351
+ }
352
+ byPath.set(normalized, {
353
+ generator,
354
+ file: { path: normalized, content: file.content }
355
+ });
356
+ }
357
+ }
358
+ return [...byPath.values()].map((entry) => entry.file).sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
359
+ }
360
+
361
+ // src/filter.ts
362
+ function filterIR(ir, namespaces) {
363
+ const clone = structuredClone(ir);
364
+ if (namespaces === void 0) {
365
+ return clone;
366
+ }
367
+ const keep = new Set(namespaces);
368
+ const sources = {};
369
+ for (const [key, source] of Object.entries(clone.sources)) {
370
+ if (keep.has(key)) {
371
+ sources[key] = source;
372
+ }
373
+ }
374
+ clone.sources = sources;
375
+ return clone;
376
+ }
377
+
378
+ // src/graph.ts
379
+ function generatorOrder(generators) {
380
+ const names = Object.keys(generators);
381
+ const index = new Map(names.map((name, i) => [name, i]));
382
+ const dependents = new Map(names.map((n) => [n, []]));
383
+ const indegree = new Map(names.map((n) => [n, 0]));
384
+ for (const name of names) {
385
+ const cfg = generators[name];
386
+ const hard = cfg?.generator.dependsOn ?? [];
387
+ const optional = cfg?.generator.optionalDependsOn ?? [];
388
+ const seen = /* @__PURE__ */ new Set();
389
+ const addEdge = (dep) => {
390
+ if (seen.has(dep)) {
391
+ return;
392
+ }
393
+ seen.add(dep);
394
+ dependents.get(dep)?.push(name);
395
+ indegree.set(name, (indegree.get(name) ?? 0) + 1);
396
+ };
397
+ for (const dep of hard) {
398
+ if (optional.includes(dep)) {
399
+ throw new InvalidDependencyError(name, dep);
400
+ }
401
+ if (!index.has(dep)) {
402
+ throw new UnknownDependencyError(name, dep);
403
+ }
404
+ addEdge(dep);
405
+ }
406
+ for (const dep of optional) {
407
+ if (!index.has(dep)) {
408
+ continue;
409
+ }
410
+ addEdge(dep);
411
+ }
412
+ }
413
+ const byConfigOrder = (a, b) => (index.get(a) ?? 0) - (index.get(b) ?? 0);
414
+ const ready = names.filter((n) => (indegree.get(n) ?? 0) === 0).sort(byConfigOrder);
415
+ const order = [];
416
+ while (ready.length > 0) {
417
+ const node = ready.shift();
418
+ order.push(node);
419
+ for (const dependent of dependents.get(node) ?? []) {
420
+ const next = (indegree.get(dependent) ?? 0) - 1;
421
+ indegree.set(dependent, next);
422
+ if (next === 0) {
423
+ ready.push(dependent);
424
+ ready.sort(byConfigOrder);
425
+ }
426
+ }
427
+ }
428
+ if (order.length < names.length) {
429
+ const remaining = new Set(names.filter((n) => !order.includes(n)));
430
+ throw new DependencyCycleError(findCycle(remaining, dependents));
431
+ }
432
+ return order;
433
+ }
434
+ function findCycle(nodes, dependents) {
435
+ const stack = [];
436
+ const onStack = /* @__PURE__ */ new Set();
437
+ const visited = /* @__PURE__ */ new Set();
438
+ const walk = (node) => {
439
+ stack.push(node);
440
+ onStack.add(node);
441
+ for (const next of dependents.get(node) ?? []) {
442
+ if (!nodes.has(next)) {
443
+ continue;
444
+ }
445
+ if (onStack.has(next)) {
446
+ return [...stack.slice(stack.indexOf(next)), next];
447
+ }
448
+ if (!visited.has(next)) {
449
+ const found = walk(next);
450
+ if (found) {
451
+ return found;
452
+ }
453
+ }
454
+ }
455
+ stack.pop();
456
+ onStack.delete(node);
457
+ visited.add(node);
458
+ return void 0;
459
+ };
460
+ for (const node of nodes) {
461
+ if (!visited.has(node)) {
462
+ const found = walk(node);
463
+ if (found) {
464
+ return found;
465
+ }
466
+ }
467
+ }
468
+ return [...nodes];
469
+ }
470
+
471
+ // src/merge.ts
472
+ var import_ir = require("@kurotako/ir");
473
+ function mergeSources(entries) {
474
+ const sources = {};
475
+ for (const { namespace, sourceIR } of entries) {
476
+ if (sourceIR.namespace !== namespace) {
477
+ throw new NamespaceMismatchError(namespace, sourceIR.namespace);
478
+ }
479
+ const validation = (0, import_ir.validateSourceIR)(sourceIR);
480
+ if (!validation.ok) {
481
+ throw new IrValidationError(validation.issues, namespace);
482
+ }
483
+ if (namespace in sources) {
484
+ throw new DuplicateNamespaceError(namespace);
485
+ }
486
+ sources[namespace] = validation.value;
487
+ }
488
+ const ir = { irVersion: import_ir.IR_VERSION, sources };
489
+ try {
490
+ (0, import_ir.assertIR)(ir);
491
+ } catch (error) {
492
+ if (error instanceof import_ir.IrValidationError) {
493
+ throw new IrValidationError(error.issues);
494
+ }
495
+ throw error;
496
+ }
497
+ return ir;
498
+ }
499
+
500
+ // src/writer/banner.ts
501
+ var BANNER = "// Generated by tako. Do not edit.\n";
502
+ var GITATTRIBUTES = "* linguist-generated=true\n";
503
+ var COMMENTABLE_BASENAMES = /* @__PURE__ */ new Set(["tsconfig.json"]);
504
+ function takesBanner(path5) {
505
+ if (path5.endsWith(".ts") || path5.endsWith(".tsx")) {
506
+ return true;
507
+ }
508
+ const basename = path5.slice(path5.lastIndexOf("/") + 1);
509
+ return COMMENTABLE_BASENAMES.has(basename);
510
+ }
511
+ function applyBanner(files) {
512
+ return files.map((file) => {
513
+ if (!takesBanner(file.path) || file.content.startsWith(BANNER)) {
514
+ return file;
515
+ }
516
+ return { path: file.path, content: BANNER + file.content };
517
+ });
518
+ }
519
+
520
+ // src/writer/tree.ts
521
+ function contributingGenerators(files) {
522
+ const acc = /* @__PURE__ */ new Map();
523
+ for (const file of files) {
524
+ const parts = file.path.split("/");
525
+ if (parts.length < 3) {
526
+ continue;
527
+ }
528
+ const [namespace, generator] = parts;
529
+ if (!namespace || !generator) {
530
+ continue;
531
+ }
532
+ let set = acc.get(namespace);
533
+ if (!set) {
534
+ set = /* @__PURE__ */ new Set();
535
+ acc.set(namespace, set);
536
+ }
537
+ set.add(generator);
538
+ }
539
+ const out = /* @__PURE__ */ new Map();
540
+ for (const [namespace, set] of acc) {
541
+ out.set(namespace, [...set].sort());
542
+ }
543
+ return out;
544
+ }
545
+
546
+ // src/writer/barrel.ts
547
+ function synthesizeRootBarrels(files, artifactsByGenerator, logger) {
548
+ const contributors = contributingGenerators(files);
549
+ const barrels = [];
550
+ for (const namespace of [...contributors.keys()].sort()) {
551
+ const generators = contributors.get(namespace) ?? [];
552
+ if (artifactsByGenerator && logger) {
553
+ warnAmbiguousReExports(
554
+ namespace,
555
+ generators,
556
+ artifactsByGenerator,
557
+ logger
558
+ );
559
+ }
560
+ const content = generators.map((name) => `export * from './${name}';
561
+ `).join("");
562
+ barrels.push({ path: `${namespace}/index.ts`, content });
563
+ }
564
+ return barrels;
565
+ }
566
+ function warnAmbiguousReExports(namespace, generators, artifactsByGenerator, logger) {
567
+ const owners = /* @__PURE__ */ new Map();
568
+ for (const name of generators) {
569
+ const artifact = artifactsByGenerator[name];
570
+ if (!artifact) {
571
+ continue;
572
+ }
573
+ const identifiers = /* @__PURE__ */ new Set();
574
+ for (const [key, entity] of Object.entries(artifact.entities)) {
575
+ const dot = key.indexOf(".");
576
+ const entityNamespace = dot === -1 ? key : key.slice(0, dot);
577
+ if (entityNamespace !== namespace) {
578
+ continue;
579
+ }
580
+ for (const identifier of Object.values(entity.symbols)) {
581
+ identifiers.add(identifier);
582
+ }
583
+ }
584
+ for (const identifier of identifiers) {
585
+ const list = owners.get(identifier) ?? [];
586
+ list.push(name);
587
+ owners.set(identifier, list);
588
+ }
589
+ }
590
+ for (const [identifier, list] of owners) {
591
+ if (list.length > 1) {
592
+ const sorted = [...list].sort();
593
+ logger.warn(
594
+ `namespace '${namespace}': identifier '${identifier}' is re-exported by generators [${sorted.join(
595
+ ", "
596
+ )}]; the ambiguous star re-export from '${namespace}/index.ts' will be dropped. Import it from a generator subpath instead.`,
597
+ { namespace, identifier, generators: sorted }
598
+ );
599
+ }
600
+ }
601
+ }
602
+
603
+ // src/writer/directory.ts
604
+ var import_promises = __toESM(require("fs/promises"), 1);
605
+ var import_node_path2 = __toESM(require("path"), 1);
606
+ var GITATTRIBUTES2 = "* linguist-generated=true\n";
607
+ function sortByPath(entries) {
608
+ return [...entries].sort(
609
+ (a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0
610
+ );
611
+ }
612
+ var directoryWriter = {
613
+ async plan({ files, output }) {
614
+ if (!output.dir) {
615
+ throw new TakoError(
616
+ "invalid_output_config",
617
+ "mode 'dir' requires 'output.dir'"
618
+ );
619
+ }
620
+ const dir = import_node_path2.default.resolve(output.dir);
621
+ const planned = files.map((file) => ({
622
+ path: import_node_path2.default.join(dir, file.path),
623
+ content: file.content
624
+ }));
625
+ planned.push({
626
+ path: import_node_path2.default.join(dir, ".gitattributes"),
627
+ content: GITATTRIBUTES2
628
+ });
629
+ return sortByPath(planned);
630
+ },
631
+ async write(input) {
632
+ const planned = await this.plan(input);
633
+ const dir = import_node_path2.default.resolve(input.output.dir);
634
+ await import_promises.default.rm(dir, { recursive: true, force: true });
635
+ await import_promises.default.mkdir(dir, { recursive: true });
636
+ for (const file of planned) {
637
+ await import_promises.default.mkdir(import_node_path2.default.dirname(file.path), { recursive: true });
638
+ await import_promises.default.writeFile(file.path, file.content, "utf8");
639
+ }
640
+ return planned.map((file) => file.path);
641
+ }
642
+ };
643
+
644
+ // src/writer/package.ts
645
+ var import_node_fs2 = require("fs");
646
+ var import_promises2 = __toESM(require("fs/promises"), 1);
647
+ var import_node_module = require("module");
648
+ var import_node_path4 = __toESM(require("path"), 1);
649
+
650
+ // src/writer/peers.ts
651
+ function collectPeerDependencies(artifactsByGenerator, files) {
652
+ const contributors = contributingGenerators(files);
653
+ const result = {};
654
+ for (const namespace of [...contributors.keys()].sort()) {
655
+ const generators = contributors.get(namespace) ?? [];
656
+ const merged = /* @__PURE__ */ new Map();
657
+ for (const generator of generators) {
658
+ const peers = artifactsByGenerator[generator]?.peerDependencies;
659
+ if (!peers) {
660
+ continue;
661
+ }
662
+ for (const [pkg, range] of Object.entries(peers)) {
663
+ const existing = merged.get(pkg);
664
+ if (existing && existing.range !== range) {
665
+ throw new OutputPeerConflictError(
666
+ namespace,
667
+ pkg,
668
+ [existing.range, range],
669
+ [existing.generator, generator]
670
+ );
671
+ }
672
+ if (!existing) {
673
+ merged.set(pkg, { range, generator });
674
+ }
675
+ }
676
+ }
677
+ const sorted = {};
678
+ for (const pkg of [...merged.keys()].sort()) {
679
+ const entry = merged.get(pkg);
680
+ if (entry) {
681
+ sorted[pkg] = entry.range;
682
+ }
683
+ }
684
+ result[namespace] = sorted;
685
+ }
686
+ return result;
687
+ }
688
+
689
+ // src/writer/pm.ts
690
+ var import_node_child_process = require("child_process");
691
+ var import_node_fs = require("fs");
692
+ var import_node_path3 = __toESM(require("path"), 1);
693
+ var import_node_util = require("util");
694
+ var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
695
+ var PACKAGE_MANAGERS = [
696
+ "bun",
697
+ "pnpm",
698
+ "yarn",
699
+ "npm"
700
+ ];
701
+ var LOCKFILES = [
702
+ ["bun.lock", "bun"],
703
+ ["bun.lockb", "bun"],
704
+ ["pnpm-lock.yaml", "pnpm"],
705
+ ["yarn.lock", "yarn"],
706
+ ["package-lock.json", "npm"]
707
+ ];
708
+ function isPackageManager(value) {
709
+ return PACKAGE_MANAGERS.includes(value);
710
+ }
711
+ function* ancestors(startDir) {
712
+ let dir = import_node_path3.default.resolve(startDir);
713
+ while (true) {
714
+ yield dir;
715
+ const parent = import_node_path3.default.dirname(dir);
716
+ if (parent === dir) {
717
+ return;
718
+ }
719
+ dir = parent;
720
+ }
721
+ }
722
+ function resolvePackageManager(opts) {
723
+ if (opts.configured) {
724
+ return opts.configured;
725
+ }
726
+ for (const dir of ancestors(opts.startDir)) {
727
+ for (const [file, pm] of LOCKFILES) {
728
+ if ((0, import_node_fs.existsSync)(import_node_path3.default.join(dir, file))) {
729
+ return pm;
730
+ }
731
+ }
732
+ if ((0, import_node_fs.existsSync)(import_node_path3.default.join(dir, ".git"))) {
733
+ break;
734
+ }
735
+ }
736
+ for (const dir of ancestors(opts.startDir)) {
737
+ const pkgPath = import_node_path3.default.join(dir, "package.json");
738
+ if ((0, import_node_fs.existsSync)(pkgPath)) {
739
+ try {
740
+ const pkg = JSON.parse((0, import_node_fs.readFileSync)(pkgPath, "utf8"));
741
+ if (typeof pkg.packageManager === "string") {
742
+ const name = pkg.packageManager.split("@")[0] ?? "";
743
+ if (isPackageManager(name)) {
744
+ return name;
745
+ }
746
+ }
747
+ } catch {
748
+ }
749
+ }
750
+ if ((0, import_node_fs.existsSync)(import_node_path3.default.join(dir, ".git"))) {
751
+ break;
752
+ }
753
+ }
754
+ return null;
755
+ }
756
+ async function runInstall(pm, cwd) {
757
+ try {
758
+ await execFileAsync(pm, ["install"], { cwd });
759
+ } catch (cause) {
760
+ throw new PackageInstallError(pm, { cause });
761
+ }
762
+ }
763
+
764
+ // src/writer/package.ts
765
+ var MARKER = "Generated by tako. Do not edit.";
766
+ function computePackageLayout({
767
+ files,
768
+ output,
769
+ artifacts
770
+ }) {
771
+ if (!output.packagesDir) {
772
+ throw new TakoError(
773
+ "invalid_output_config",
774
+ "mode 'package' requires 'output.packagesDir'"
775
+ );
776
+ }
777
+ if (!output.scope) {
778
+ throw new TakoError(
779
+ "invalid_output_config",
780
+ "mode 'package' requires 'output.scope'"
781
+ );
782
+ }
783
+ const packagesDir = import_node_path4.default.resolve(output.packagesDir);
784
+ const scope = output.scope;
785
+ const scopeSlug = scope.replace(/^@/, "");
786
+ const peersByNamespace = collectPeerDependencies(artifacts ?? {}, files);
787
+ const byNamespace = /* @__PURE__ */ new Map();
788
+ for (const file of files) {
789
+ const slash = file.path.indexOf("/");
790
+ if (slash === -1) {
791
+ continue;
792
+ }
793
+ const namespace = file.path.slice(0, slash);
794
+ const rest = file.path.slice(slash + 1);
795
+ const list = byNamespace.get(namespace) ?? [];
796
+ list.push({ path: rest, content: file.content });
797
+ byNamespace.set(namespace, list);
798
+ }
799
+ const namespaces = [...byNamespace.keys()].sort();
800
+ const entriesByNamespace = /* @__PURE__ */ new Map();
801
+ const planned = [];
802
+ for (const namespace of namespaces) {
803
+ const sources = [...byNamespace.get(namespace) ?? []].sort(
804
+ (a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0
805
+ );
806
+ const pkgDir = import_node_path4.default.join(packagesDir, `${scopeSlug}-${namespace}`);
807
+ const entries = [];
808
+ for (const src of sources) {
809
+ planned.push({
810
+ path: import_node_path4.default.join(pkgDir, "src", src.path),
811
+ content: src.content
812
+ });
813
+ entries.push(`src/${src.path}`);
814
+ }
815
+ planned.push({
816
+ path: import_node_path4.default.join(pkgDir, "package.json"),
817
+ content: buildPackageJson(
818
+ scope,
819
+ namespace,
820
+ peersByNamespace[namespace] ?? {}
821
+ )
822
+ });
823
+ planned.push({
824
+ path: import_node_path4.default.join(pkgDir, "tsconfig.json"),
825
+ content: buildTsconfig(namespace)
826
+ });
827
+ const sortedEntries = entries.sort();
828
+ entriesByNamespace.set(namespace, sortedEntries);
829
+ planned.push({
830
+ path: import_node_path4.default.join(pkgDir, "tsup.config.ts"),
831
+ content: buildTsupConfig(sortedEntries)
832
+ });
833
+ planned.push({
834
+ path: import_node_path4.default.join(pkgDir, ".gitattributes"),
835
+ content: GITATTRIBUTES
836
+ });
837
+ }
838
+ planned.push({
839
+ path: import_node_path4.default.join(packagesDir, ".gitattributes"),
840
+ content: `${scopeSlug}-*/** linguist-generated=true
841
+ `
842
+ });
843
+ planned.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
844
+ return { packagesDir, scopeSlug, namespaces, entriesByNamespace, planned };
845
+ }
846
+ var packageWriter = {
847
+ async plan(input) {
848
+ return computePackageLayout(input).planned;
849
+ },
850
+ async write({ files, output, artifacts, logger = noopLogger }) {
851
+ const { packagesDir, scopeSlug, namespaces, entriesByNamespace, planned } = computePackageLayout({ files, output, artifacts, logger });
852
+ assertWorkspaceBaseFiles(packagesDir);
853
+ await import_promises2.default.mkdir(packagesDir, { recursive: true });
854
+ for (const namespace of namespaces) {
855
+ await guardAndReset(import_node_path4.default.join(packagesDir, `${scopeSlug}-${namespace}`));
856
+ }
857
+ const written = [];
858
+ for (const file of planned) {
859
+ await import_promises2.default.mkdir(import_node_path4.default.dirname(file.path), { recursive: true });
860
+ await import_promises2.default.writeFile(file.path, file.content, "utf8");
861
+ written.push(file.path);
862
+ }
863
+ await buildPackages(packagesDir, scopeSlug, namespaces, entriesByNamespace);
864
+ const pm = resolvePackageManager({
865
+ configured: output.packageManager,
866
+ startDir: packagesDir
867
+ });
868
+ if (pm) {
869
+ await runInstall(pm, findWorkspaceRoot(packagesDir) ?? packagesDir);
870
+ } else {
871
+ logger.warn(
872
+ "could not resolve a package manager; run '<your package manager> install' in the workspace root to link the generated packages",
873
+ { packagesDir }
874
+ );
875
+ }
876
+ return written.sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
877
+ }
878
+ };
879
+ var TSUP_CONFIG_BASE_EXTENSIONS = [".ts", ".js", ".mjs", ".cjs"];
880
+ function assertWorkspaceBaseFiles(packagesDir) {
881
+ const workspaceRoot = import_node_path4.default.dirname(packagesDir);
882
+ const missing = [];
883
+ if (!(0, import_node_fs2.existsSync)(import_node_path4.default.join(workspaceRoot, "tsconfig.base.json"))) {
884
+ missing.push("tsconfig.base.json");
885
+ }
886
+ const hasTsupBase = TSUP_CONFIG_BASE_EXTENSIONS.some(
887
+ (ext) => (0, import_node_fs2.existsSync)(import_node_path4.default.join(workspaceRoot, `tsup.config.base${ext}`))
888
+ );
889
+ if (!hasTsupBase) {
890
+ missing.push("tsup.config.base.{ts,js,mjs,cjs}");
891
+ }
892
+ if (!isResolvableFrom("typescript", workspaceRoot)) {
893
+ missing.push("'typescript' (devDependency, needed for the .d.ts build)");
894
+ }
895
+ if (missing.length > 0) {
896
+ throw new MissingPackageWorkspaceFilesError(workspaceRoot, missing);
897
+ }
898
+ }
899
+ function isResolvableFrom(specifier, from) {
900
+ try {
901
+ (0, import_node_module.createRequire)(import_node_path4.default.join(from, "noop.js")).resolve(specifier);
902
+ return true;
903
+ } catch {
904
+ return false;
905
+ }
906
+ }
907
+ async function guardAndReset(pkgDir) {
908
+ let entries;
909
+ try {
910
+ entries = await import_promises2.default.readdir(pkgDir);
911
+ } catch {
912
+ entries = [];
913
+ }
914
+ if (entries.length > 0 && !hasGeneratedMarker(pkgDir)) {
915
+ throw new OutputNotGeneratedError(pkgDir);
916
+ }
917
+ await import_promises2.default.rm(pkgDir, { recursive: true, force: true });
918
+ await import_promises2.default.mkdir(pkgDir, { recursive: true });
919
+ }
920
+ function hasGeneratedMarker(pkgDir) {
921
+ try {
922
+ const pkg = JSON.parse(
923
+ (0, import_node_fs2.readFileSync)(import_node_path4.default.join(pkgDir, "package.json"), "utf8")
924
+ );
925
+ return typeof pkg["//"] === "string" && pkg["//"].startsWith("Generated by tako");
926
+ } catch {
927
+ return false;
928
+ }
929
+ }
930
+ async function buildPackages(packagesDir, scopeSlug, namespaces, entriesByNamespace) {
931
+ const { build } = await import("tsup");
932
+ const originalCwd = process.cwd();
933
+ try {
934
+ for (const namespace of namespaces) {
935
+ const pkgDir = import_node_path4.default.join(packagesDir, `${scopeSlug}-${namespace}`);
936
+ process.chdir(pkgDir);
937
+ try {
938
+ await build({
939
+ // Config auto-discovery (cosmiconfig) is disabled: the pkgDir's own
940
+ // tsup.config.ts exists for humans building the package standalone
941
+ // later, but resolving it here — a TS file re-exporting
942
+ // `../../tsup.config.base` — through tsup's config loader instead of
943
+ // this programmatic call caused spurious "Could not resolve" entry
944
+ // errors.
945
+ config: false,
946
+ // A relative `entry` array goes through tinyglobby, whose matches
947
+ // esbuild then resolves against the cwd esbuild's service captured
948
+ // at first use — *not* the cwd at this call, so it silently breaks
949
+ // after the first `process.chdir()` in this loop. An absolute-path
950
+ // entry *map* skips glob resolution entirely (tsup only runs
951
+ // `fs.existsSync` on it) and keeps `dist/` mirroring `src/`.
952
+ entry: Object.fromEntries(
953
+ (entriesByNamespace.get(namespace) ?? []).map((entry) => [
954
+ entry.replace(/^src\//, "").replace(/\.ts$/, ""),
955
+ import_node_path4.default.join(pkgDir, entry)
956
+ ])
957
+ ),
958
+ tsconfig: import_node_path4.default.join(pkgDir, "tsconfig.json"),
959
+ format: ["esm", "cjs"],
960
+ dts: { compilerOptions: { composite: false, incremental: false } },
961
+ outDir: import_node_path4.default.join(pkgDir, "dist"),
962
+ silent: true
963
+ });
964
+ } catch (cause) {
965
+ throw new PackageBuildError(namespace, { cause });
966
+ }
967
+ }
968
+ } finally {
969
+ process.chdir(originalCwd);
970
+ }
971
+ }
972
+ function findWorkspaceRoot(startDir) {
973
+ for (const dir of ancestors(startDir)) {
974
+ if ((0, import_node_fs2.existsSync)(import_node_path4.default.join(dir, "pnpm-workspace.yaml"))) {
975
+ return dir;
976
+ }
977
+ const pkgPath = import_node_path4.default.join(dir, "package.json");
978
+ if ((0, import_node_fs2.existsSync)(pkgPath)) {
979
+ try {
980
+ const pkg = JSON.parse((0, import_node_fs2.readFileSync)(pkgPath, "utf8"));
981
+ if (pkg.workspaces !== void 0) {
982
+ return dir;
983
+ }
984
+ } catch {
985
+ }
986
+ }
987
+ }
988
+ return null;
989
+ }
990
+ function buildPackageJson(scope, namespace, peerDependencies) {
991
+ const pkg = {
992
+ name: `${scope}/${namespace}`,
993
+ version: "0.0.0",
994
+ type: "module",
995
+ main: "./dist/index.cjs",
996
+ module: "./dist/index.js",
997
+ types: "./dist/index.d.ts",
998
+ exports: {
999
+ ".": {
1000
+ types: "./dist/index.d.ts",
1001
+ import: "./dist/index.js",
1002
+ require: "./dist/index.cjs"
1003
+ },
1004
+ "./*": {
1005
+ types: "./dist/*.d.ts",
1006
+ import: "./dist/*.js",
1007
+ require: "./dist/*.cjs"
1008
+ }
1009
+ },
1010
+ files: ["dist", "src"],
1011
+ peerDependencies,
1012
+ sideEffects: false,
1013
+ scripts: { build: "tsup" },
1014
+ "//": MARKER
1015
+ };
1016
+ return `${JSON.stringify(pkg, null, 2)}
1017
+ `;
1018
+ }
1019
+ function buildTsconfig(namespace) {
1020
+ const body = JSON.stringify(
1021
+ {
1022
+ extends: "../../tsconfig.base.json",
1023
+ compilerOptions: {
1024
+ outDir: "dist",
1025
+ paths: { [`${namespace}/*`]: ["./src/*"] }
1026
+ },
1027
+ include: ["src"]
1028
+ },
1029
+ null,
1030
+ 2
1031
+ );
1032
+ return `${BANNER}${body}
1033
+ `;
1034
+ }
1035
+ function buildTsupConfig(entries) {
1036
+ const list = entries.map((entry) => ` '${entry}',`).join("\n");
1037
+ return `${BANNER}import { basePreset } from '../../tsup.config.base';
1038
+
1039
+ export default {
1040
+ ...basePreset,
1041
+ entry: [
1042
+ ${list}
1043
+ ],
1044
+ };
1045
+ `;
1046
+ }
1047
+
1048
+ // src/writer/index.ts
1049
+ function selectWriter(output) {
1050
+ const mode = output.mode ?? "dir";
1051
+ if (mode === "dir") {
1052
+ return directoryWriter;
1053
+ }
1054
+ if (mode === "package") {
1055
+ return packageWriter;
1056
+ }
1057
+ throw new UnsupportedOutputModeError(mode);
1058
+ }
1059
+
1060
+ // src/run.ts
1061
+ async function run(config, opts) {
1062
+ const logger = opts?.logger ?? noopLogger;
1063
+ const checkSignal = () => opts?.signal?.throwIfAborted();
1064
+ checkSignal();
1065
+ const entries = [];
1066
+ for (const namespace of Object.keys(config.sources).sort()) {
1067
+ checkSignal();
1068
+ const source = config.sources[namespace];
1069
+ if (!source) {
1070
+ continue;
1071
+ }
1072
+ const { parser } = source;
1073
+ const anchorDir = await parser.anchor?.(config.rootDir) ?? config.rootDir;
1074
+ const ctx = {
1075
+ namespace,
1076
+ cwd: config.rootDir,
1077
+ anchorDir,
1078
+ logger: childLogger(logger, { namespace })
1079
+ };
1080
+ try {
1081
+ const sourceIR = await parser.parse(ctx);
1082
+ entries.push({ namespace, sourceIR });
1083
+ } catch (error) {
1084
+ if (error instanceof DriverError) {
1085
+ throw error;
1086
+ }
1087
+ throw new DriverError("parser", parser.name, { cause: error, namespace });
1088
+ }
1089
+ }
1090
+ checkSignal();
1091
+ const ir = mergeSources(entries);
1092
+ checkSignal();
1093
+ const order = generatorOrder(config.generators);
1094
+ const artifacts = {};
1095
+ const perGenerator = [];
1096
+ for (const name of order) {
1097
+ checkSignal();
1098
+ const cfg = config.generators[name];
1099
+ if (!cfg) {
1100
+ continue;
1101
+ }
1102
+ const { generator } = cfg;
1103
+ const view = filterIR(ir, cfg.namespaces);
1104
+ const declared = [
1105
+ ...generator.dependsOn ?? [],
1106
+ ...generator.optionalDependsOn ?? []
1107
+ ];
1108
+ const dependencies = {};
1109
+ for (const dep of declared) {
1110
+ const artifact = artifacts[dep];
1111
+ if (artifact) {
1112
+ dependencies[dep] = artifact;
1113
+ }
1114
+ }
1115
+ try {
1116
+ const out = await generator.generate({
1117
+ ir: view,
1118
+ dependencies,
1119
+ logger: childLogger(logger, { generator: name })
1120
+ });
1121
+ artifacts[name] = out.artifact;
1122
+ perGenerator.push({ generator: name, files: out.files });
1123
+ } catch (error) {
1124
+ if (error instanceof DriverError) {
1125
+ throw error;
1126
+ }
1127
+ throw new DriverError("generator", generator.name, { cause: error });
1128
+ }
1129
+ }
1130
+ checkSignal();
1131
+ const collected = mergeTrees(perGenerator);
1132
+ checkSignal();
1133
+ const barrels = synthesizeRootBarrels(collected, artifacts, logger);
1134
+ const merged = mergeTrees(
1135
+ [
1136
+ ...perGenerator,
1137
+ { generator: "<synthesized root barrel>", files: barrels }
1138
+ ],
1139
+ {
1140
+ collisionHint: "each generator must emit under its own '<namespace>/<generatorName>/' sub-tree; '<namespace>/index.ts' is synthesized by tako"
1141
+ }
1142
+ );
1143
+ const files = applyBanner(merged);
1144
+ const outputTree = (output) => {
1145
+ const names = new Set(output.generators ?? order);
1146
+ const filteredFiles = collected.filter(
1147
+ (file) => names.has(file.path.split("/")[1] ?? "")
1148
+ );
1149
+ const outputBarrels = synthesizeRootBarrels(
1150
+ filteredFiles,
1151
+ artifacts,
1152
+ logger
1153
+ );
1154
+ return applyBanner(
1155
+ mergeTrees([
1156
+ { generator: "<filtered>", files: filteredFiles },
1157
+ { generator: "<synthesized root barrel>", files: outputBarrels }
1158
+ ])
1159
+ );
1160
+ };
1161
+ if (opts?.plan === true) {
1162
+ const planned = [];
1163
+ for (const output of config.outputs) {
1164
+ checkSignal();
1165
+ const writer = selectWriter(output);
1166
+ planned.push(
1167
+ ...await writer.plan({
1168
+ files: outputTree(output),
1169
+ output,
1170
+ artifacts,
1171
+ logger
1172
+ })
1173
+ );
1174
+ }
1175
+ planned.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
1176
+ return { ir, order, files, artifacts, written: [], plan: planned };
1177
+ }
1178
+ const written = [];
1179
+ if (opts?.write !== false) {
1180
+ for (const output of config.outputs) {
1181
+ checkSignal();
1182
+ const writer = selectWriter(output);
1183
+ const writtenPaths = await writer.write({
1184
+ files: outputTree(output),
1185
+ output,
1186
+ artifacts,
1187
+ logger
1188
+ });
1189
+ written.push({ output, files: writtenPaths });
1190
+ checkSignal();
1191
+ const outputDir = (output.mode === "package" ? output.packagesDir : output.dir) ?? config.rootDir;
1192
+ try {
1193
+ await config.hooks?.afterEmit?.({
1194
+ outputDir,
1195
+ files: writtenPaths,
1196
+ logger
1197
+ });
1198
+ } catch (error) {
1199
+ throw new HookError("afterEmit", { cause: error });
1200
+ }
1201
+ }
1202
+ }
1203
+ return { ir, order, files, artifacts, written };
1204
+ }
1205
+ // Annotate the CommonJS export names for ESM import in node:
1206
+ 0 && (module.exports = {
1207
+ BANNER,
1208
+ DependencyCycleError,
1209
+ DriverError,
1210
+ DuplicateNamespaceError,
1211
+ GITATTRIBUTES,
1212
+ HookError,
1213
+ InvalidDependencyError,
1214
+ InvalidOutputPathError,
1215
+ IrValidationError,
1216
+ MissingPackageWorkspaceFilesError,
1217
+ NamespaceMismatchError,
1218
+ OutputCollisionError,
1219
+ OutputNotGeneratedError,
1220
+ OutputPeerConflictError,
1221
+ PackageBuildError,
1222
+ PackageInstallError,
1223
+ TakoError,
1224
+ UnknownDependencyError,
1225
+ UnsupportedOutputModeError,
1226
+ applyBanner,
1227
+ childLogger,
1228
+ collectPeerDependencies,
1229
+ directoryWriter,
1230
+ noopLogger,
1231
+ packageWriter,
1232
+ resolvePackageManager,
1233
+ run,
1234
+ runInstall,
1235
+ selectWriter,
1236
+ synthesizeRootBarrels
1237
+ });
1238
+ //# sourceMappingURL=index.cjs.map