@confect/cli 7.0.0 → 9.0.0-next.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/CHANGELOG.md +60 -0
- package/dist/BuildError.mjs +101 -0
- package/dist/BuildError.mjs.map +1 -0
- package/dist/Bundler.mjs +91 -0
- package/dist/Bundler.mjs.map +1 -0
- package/dist/CodeBlockWriter.mjs +55 -0
- package/dist/CodeBlockWriter.mjs.map +1 -0
- package/dist/CodegenError.mjs +94 -0
- package/dist/CodegenError.mjs.map +1 -0
- package/dist/FunctionPaths.mjs +1 -1
- package/dist/LeafModule.mjs +167 -0
- package/dist/LeafModule.mjs.map +1 -0
- package/dist/SpecAssemblyNode.mjs +33 -0
- package/dist/SpecAssemblyNode.mjs.map +1 -0
- package/dist/confect/codegen.mjs +221 -72
- package/dist/confect/codegen.mjs.map +1 -1
- package/dist/confect/dev.mjs +234 -180
- package/dist/confect/dev.mjs.map +1 -1
- package/dist/log.mjs +13 -1
- package/dist/log.mjs.map +1 -1
- package/dist/package.mjs +1 -1
- package/dist/templates.mjs +65 -72
- package/dist/templates.mjs.map +1 -1
- package/dist/utils.mjs +76 -69
- package/dist/utils.mjs.map +1 -1
- package/package.json +14 -15
- package/src/BuildError.ts +210 -0
- package/src/Bundler.ts +144 -0
- package/src/CodeBlockWriter.ts +65 -0
- package/src/CodegenError.ts +344 -0
- package/src/LeafModule.ts +313 -0
- package/src/SpecAssemblyNode.ts +82 -0
- package/src/confect/codegen.ts +390 -142
- package/src/confect/dev.ts +556 -435
- package/src/log.ts +21 -0
- package/src/templates.ts +141 -109
- package/src/utils.ts +118 -85
package/dist/utils.mjs
CHANGED
|
@@ -2,68 +2,46 @@ import { fromGroupModulePath, getGroupSpec, modulePath } from "./GroupPath.mjs";
|
|
|
2
2
|
import { logFileAdded, logFileModified, logFileRemoved } from "./log.mjs";
|
|
3
3
|
import { ConvexDirectory } from "./ConvexDirectory.mjs";
|
|
4
4
|
import { ConfectDirectory } from "./ConfectDirectory.mjs";
|
|
5
|
-
import { authConfig, crons, functions, http } from "./templates.mjs";
|
|
6
5
|
import { GroupPaths } from "./GroupPaths.mjs";
|
|
7
6
|
import { groupPaths, make } from "./FunctionPaths.mjs";
|
|
8
|
-
import {
|
|
7
|
+
import { authConfig, crons, functions, http } from "./templates.mjs";
|
|
8
|
+
import { Array, Context, Effect, HashSet, Option, Order, Record, Ref, String, pipe } from "effect";
|
|
9
9
|
import { FileSystem, Path } from "@effect/platform";
|
|
10
|
-
import * as esbuild from "esbuild";
|
|
11
10
|
|
|
12
11
|
//#region src/utils.ts
|
|
12
|
+
/**
|
|
13
|
+
* Tracks whether the current codegen run wrote anything to disk. Set to
|
|
14
|
+
* `true` by every helper that actually overwrites a file (skipping the
|
|
15
|
+
* content-unchanged case). `codegenHandler` provides a fresh `Ref` per
|
|
16
|
+
* run and reads it back to report `anyWritesHappened`; callers outside a
|
|
17
|
+
* codegen pass hit the cached default `Ref` and need not interact with
|
|
18
|
+
* the tracker.
|
|
19
|
+
*/
|
|
20
|
+
var WriteTracker = class extends Context.Reference()("@confect/cli/WriteTracker", { defaultValue: () => Ref.unsafeMake(false) }) {};
|
|
21
|
+
const markWritten = Effect.gen(function* () {
|
|
22
|
+
const tracker = yield* WriteTracker;
|
|
23
|
+
yield* Ref.set(tracker, true);
|
|
24
|
+
});
|
|
13
25
|
const removePathExtension = (pathStr) => Effect.gen(function* () {
|
|
14
26
|
const path = yield* Path.Path;
|
|
15
27
|
return String.slice(0, -path.extname(pathStr).length)(pathStr);
|
|
16
28
|
});
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"
|
|
21
|
-
"@effect/*"
|
|
22
|
-
];
|
|
23
|
-
const isExternalImport = (path) => EXTERNAL_PACKAGES.some((p) => {
|
|
24
|
-
if (p.endsWith("/*")) return path.startsWith(p.slice(0, -1));
|
|
25
|
-
return path === p || path.startsWith(p + "/");
|
|
26
|
-
});
|
|
27
|
-
const absoluteExternalsPlugin = {
|
|
28
|
-
name: "absolute-externals",
|
|
29
|
-
setup(build) {
|
|
30
|
-
build.onResolve({ filter: /.*/ }, async (args) => {
|
|
31
|
-
if (args.kind !== "import-statement" && args.kind !== "dynamic-import") return;
|
|
32
|
-
if (!isExternalImport(args.path)) return;
|
|
33
|
-
return {
|
|
34
|
-
path: import.meta.resolve(args.path, "file://" + args.resolveDir + "/"),
|
|
35
|
-
external: true
|
|
36
|
-
};
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
/**
|
|
41
|
-
* Bundle a TypeScript entry point with esbuild and import the result via a
|
|
42
|
-
* data URL. This handles extensionless `.ts` imports regardless of whether the
|
|
43
|
-
* user's project sets `"type": "module"` in package.json.
|
|
44
|
-
*/
|
|
45
|
-
const bundleAndImport = (entryPoint) => Effect.gen(function* () {
|
|
46
|
-
const code = (yield* Effect.promise(() => esbuild.build({
|
|
47
|
-
entryPoints: [entryPoint],
|
|
48
|
-
bundle: true,
|
|
49
|
-
write: false,
|
|
50
|
-
platform: "node",
|
|
51
|
-
format: "esm",
|
|
52
|
-
logLevel: "silent",
|
|
53
|
-
plugins: [absoluteExternalsPlugin]
|
|
54
|
-
}))).outputFiles[0].text;
|
|
55
|
-
const dataUrl = "data:text/javascript;base64," + Buffer.from(code).toString("base64");
|
|
56
|
-
return yield* Effect.promise(() => import(dataUrl));
|
|
29
|
+
/** Ensures a relative path is a valid ESM/TS module specifier (e.g. `spec` → `./spec`). */
|
|
30
|
+
const toModuleImportPath = (relativePath) => Effect.gen(function* () {
|
|
31
|
+
const withoutExt = yield* removePathExtension(relativePath);
|
|
32
|
+
return withoutExt.startsWith(".") ? withoutExt : `./${withoutExt}`;
|
|
57
33
|
});
|
|
58
34
|
const writeFileStringAndLog = (filePath, contents) => Effect.gen(function* () {
|
|
59
35
|
const fs = yield* FileSystem.FileSystem;
|
|
60
36
|
if (!(yield* fs.exists(filePath))) {
|
|
61
37
|
yield* fs.writeFileString(filePath, contents);
|
|
38
|
+
yield* markWritten;
|
|
62
39
|
yield* logFileAdded(filePath);
|
|
63
40
|
return;
|
|
64
41
|
}
|
|
65
42
|
if ((yield* fs.readFileString(filePath)) !== contents) {
|
|
66
43
|
yield* fs.writeFileString(filePath, contents);
|
|
44
|
+
yield* markWritten;
|
|
67
45
|
yield* logFileModified(filePath);
|
|
68
46
|
}
|
|
69
47
|
});
|
|
@@ -80,42 +58,58 @@ const writeFileString = (filePath, contents) => Effect.gen(function* () {
|
|
|
80
58
|
const fs = yield* FileSystem.FileSystem;
|
|
81
59
|
if (!(yield* fs.exists(filePath))) {
|
|
82
60
|
yield* fs.writeFileString(filePath, contents);
|
|
61
|
+
yield* markWritten;
|
|
83
62
|
return "Added";
|
|
84
63
|
}
|
|
85
64
|
if ((yield* fs.readFileString(filePath)) !== contents) {
|
|
86
65
|
yield* fs.writeFileString(filePath, contents);
|
|
66
|
+
yield* markWritten;
|
|
87
67
|
return "Modified";
|
|
88
68
|
}
|
|
89
69
|
return "Unchanged";
|
|
90
70
|
});
|
|
91
|
-
const
|
|
71
|
+
const removePathIfExists = (filePath) => Effect.gen(function* () {
|
|
72
|
+
const fs = yield* FileSystem.FileSystem;
|
|
73
|
+
if (!(yield* fs.exists(filePath))) return;
|
|
74
|
+
yield* fs.remove(filePath).pipe(Effect.catchTag("SystemError", (error) => error.reason === "NotFound" ? Effect.void : Effect.fail(error)));
|
|
75
|
+
});
|
|
76
|
+
/**
|
|
77
|
+
* Bump the mtime of `convex/schema.ts` so the Convex CLI's chokidar watcher
|
|
78
|
+
* emits a `change` event after every successful Confect codegen run. Without
|
|
79
|
+
* this, a codegen that doesn't change any file content (for example,
|
|
80
|
+
* recovering from a transient broken state in `confect/`) leaves Convex
|
|
81
|
+
* stuck on its previous error because nothing it observes has changed.
|
|
82
|
+
*/
|
|
83
|
+
const touchConvexSchema = Effect.gen(function* () {
|
|
84
|
+
const fs = yield* FileSystem.FileSystem;
|
|
85
|
+
const path = yield* Path.Path;
|
|
86
|
+
const convexDirectory = yield* ConvexDirectory.get;
|
|
87
|
+
const schemaPath = path.join(convexDirectory, "schema.ts");
|
|
88
|
+
if (!(yield* fs.exists(schemaPath))) return;
|
|
89
|
+
const now = /* @__PURE__ */ new Date();
|
|
90
|
+
yield* fs.utimes(schemaPath, now, now);
|
|
91
|
+
});
|
|
92
|
+
const generateGroupModule = ({ groupPath, functionNames, registeredFunctionsImportPath, useNode = false }) => Effect.gen(function* () {
|
|
92
93
|
const fs = yield* FileSystem.FileSystem;
|
|
93
94
|
const path = yield* Path.Path;
|
|
94
95
|
const convexDirectory = yield* ConvexDirectory.get;
|
|
95
|
-
const confectDirectory = yield* ConfectDirectory.get;
|
|
96
96
|
const relativeModulePath = yield* modulePath(groupPath);
|
|
97
|
-
const modulePath$
|
|
98
|
-
const directoryPath = path.dirname(modulePath$
|
|
97
|
+
const modulePath$4 = path.join(convexDirectory, relativeModulePath);
|
|
98
|
+
const directoryPath = path.dirname(modulePath$4);
|
|
99
99
|
if (!(yield* fs.exists(directoryPath))) yield* fs.makeDirectory(directoryPath, { recursive: true });
|
|
100
|
-
const isNodeGroup = groupPath.pathSegments[0] === "node";
|
|
101
|
-
const registeredFunctionsFileName = isNodeGroup ? "nodeRegisteredFunctions.ts" : "registeredFunctions.ts";
|
|
102
|
-
const registeredFunctionsPath = path.join(confectDirectory, "_generated", registeredFunctionsFileName);
|
|
103
|
-
const registeredFunctionsImportPath = yield* removePathExtension(path.relative(path.dirname(modulePath$2), registeredFunctionsPath));
|
|
104
|
-
const registeredFunctionsVariableName = isNodeGroup ? "nodeRegisteredFunctions" : "registeredFunctions";
|
|
105
100
|
const functionsContentsString = yield* functions({
|
|
106
|
-
groupPath,
|
|
107
101
|
functionNames,
|
|
108
102
|
registeredFunctionsImportPath,
|
|
109
|
-
|
|
110
|
-
useNode: isNodeGroup,
|
|
111
|
-
...isNodeGroup ? { registeredFunctionsLookupPath: groupPath.pathSegments.slice(1) } : {}
|
|
103
|
+
useNode
|
|
112
104
|
});
|
|
113
|
-
if (!(yield* fs.exists(modulePath$
|
|
114
|
-
yield* fs.writeFileString(modulePath$
|
|
105
|
+
if (!(yield* fs.exists(modulePath$4))) {
|
|
106
|
+
yield* fs.writeFileString(modulePath$4, functionsContentsString);
|
|
107
|
+
yield* markWritten;
|
|
115
108
|
return "Added";
|
|
116
109
|
}
|
|
117
|
-
if ((yield* fs.readFileString(modulePath$
|
|
118
|
-
yield* fs.writeFileString(modulePath$
|
|
110
|
+
if ((yield* fs.readFileString(modulePath$4)) !== functionsContentsString) {
|
|
111
|
+
yield* fs.writeFileString(modulePath$4, functionsContentsString);
|
|
112
|
+
yield* markWritten;
|
|
119
113
|
return "Modified";
|
|
120
114
|
}
|
|
121
115
|
return "Unchanged";
|
|
@@ -131,18 +125,23 @@ const logGroupPaths = (groupPaths, logFn) => Effect.gen(function* () {
|
|
|
131
125
|
const generateFunctions = (spec) => Effect.gen(function* () {
|
|
132
126
|
const path = yield* Path.Path;
|
|
133
127
|
const convexDirectory = yield* ConvexDirectory.get;
|
|
128
|
+
const confectDirectory = yield* ConfectDirectory.get;
|
|
134
129
|
const groupPathsFromFs = yield* getGroupPathsFromFs;
|
|
135
130
|
const functionPaths = make(spec);
|
|
136
131
|
const groupPathsFromSpec = groupPaths(functionPaths);
|
|
137
132
|
const overlappingGroupPaths = GroupPaths.make(HashSet.intersection(groupPathsFromFs, groupPathsFromSpec));
|
|
138
133
|
yield* Effect.forEach(overlappingGroupPaths, (groupPath) => Effect.gen(function* () {
|
|
134
|
+
const functionNames = pipe((yield* getGroupSpec(spec, groupPath)).functions, Record.values, Array.sortBy(Order.mapInput(Order.string, (fn) => fn.name)), Array.map((fn) => fn.name));
|
|
135
|
+
const relativeModulePath = yield* modulePath(groupPath);
|
|
136
|
+
const modulePath$3 = path.join(convexDirectory, relativeModulePath);
|
|
137
|
+
const registrySegments = groupPath.pathSegments[0] === "node" ? groupPath.pathSegments.slice(1) : groupPath.pathSegments;
|
|
138
|
+
const registeredFunctionsPath = path.join(confectDirectory, "_generated", "registeredFunctions", ...registrySegments) + ".ts";
|
|
139
139
|
if ((yield* generateGroupModule({
|
|
140
140
|
groupPath,
|
|
141
|
-
functionNames
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
}
|
|
141
|
+
functionNames,
|
|
142
|
+
registeredFunctionsImportPath: yield* toModuleImportPath(path.relative(path.dirname(modulePath$3), registeredFunctionsPath)),
|
|
143
|
+
useNode: groupPath.pathSegments[0] === "node"
|
|
144
|
+
})) === "Modified") yield* logFileModified(modulePath$3);
|
|
146
145
|
}));
|
|
147
146
|
const extinctGroupPaths = GroupPaths.make(HashSet.difference(groupPathsFromFs, groupPathsFromSpec));
|
|
148
147
|
yield* removeGroups(extinctGroupPaths);
|
|
@@ -166,23 +165,31 @@ const getGroupPathsFromFs = Effect.gen(function* () {
|
|
|
166
165
|
return pipe(yield* pipe(yield* fs.readDirectory(convexDirectory, { recursive: true }), Array.filter((convexPath) => path.extname(convexPath) === ".ts" && !RESERVED_CONVEX_TS_FILE_NAMES.has(path.basename(convexPath)) && path.basename(path.dirname(convexPath)) !== "_generated"), Effect.forEach((groupModulePath) => fromGroupModulePath(groupModulePath))), HashSet.fromIterable, GroupPaths.make);
|
|
167
166
|
});
|
|
168
167
|
const removeGroups = (groupPaths) => Effect.gen(function* () {
|
|
169
|
-
const fs = yield* FileSystem.FileSystem;
|
|
170
168
|
const path = yield* Path.Path;
|
|
171
169
|
const convexDirectory = yield* ConvexDirectory.get;
|
|
172
170
|
yield* Effect.all(HashSet.map(groupPaths, (groupPath) => Effect.gen(function* () {
|
|
173
171
|
const relativeModulePath = yield* modulePath(groupPath);
|
|
174
172
|
const modulePath$1 = path.join(convexDirectory, relativeModulePath);
|
|
175
173
|
yield* Effect.logDebug(`Removing group '${relativeModulePath}'...`);
|
|
176
|
-
yield*
|
|
174
|
+
yield* removePathIfExists(modulePath$1);
|
|
177
175
|
yield* Effect.logDebug(`Group '${relativeModulePath}' removed`);
|
|
178
176
|
})), { concurrency: "unbounded" });
|
|
179
177
|
});
|
|
180
178
|
const writeGroups = (spec, groupPaths) => Effect.forEach(groupPaths, (groupPath) => Effect.gen(function* () {
|
|
179
|
+
const path = yield* Path.Path;
|
|
180
|
+
const convexDirectory = yield* ConvexDirectory.get;
|
|
181
|
+
const confectDirectory = yield* ConfectDirectory.get;
|
|
181
182
|
const functionNames = pipe((yield* getGroupSpec(spec, groupPath)).functions, Record.values, Array.sortBy(Order.mapInput(Order.string, (fn) => fn.name)), Array.map((fn) => fn.name));
|
|
183
|
+
const relativeModulePath = yield* modulePath(groupPath);
|
|
184
|
+
const modulePath$2 = path.join(convexDirectory, relativeModulePath);
|
|
185
|
+
const registeredFunctionsPath = path.join(confectDirectory, "_generated", "registeredFunctions", ...groupPath.pathSegments) + ".ts";
|
|
186
|
+
const registeredFunctionsImportPath = yield* toModuleImportPath(path.relative(path.dirname(modulePath$2), registeredFunctionsPath));
|
|
182
187
|
yield* Effect.logDebug(`Generating group ${groupPath}...`);
|
|
183
188
|
yield* generateGroupModule({
|
|
184
189
|
groupPath,
|
|
185
|
-
functionNames
|
|
190
|
+
functionNames,
|
|
191
|
+
registeredFunctionsImportPath,
|
|
192
|
+
useNode: groupPath.pathSegments[0] === "node"
|
|
186
193
|
});
|
|
187
194
|
yield* Effect.logDebug(`Group ${groupPath} generated`);
|
|
188
195
|
}));
|
|
@@ -205,5 +212,5 @@ const generateCrons = generateOptionalFile("crons.ts", "crons.ts", (importPath)
|
|
|
205
212
|
const generateAuthConfig = generateOptionalFile("auth.ts", "auth.config.ts", (importPath) => authConfig({ authImportPath: importPath }));
|
|
206
213
|
|
|
207
214
|
//#endregion
|
|
208
|
-
export {
|
|
215
|
+
export { WriteTracker, generateAuthConfig, generateCrons, generateFunctions, generateHttp, removePathExtension, removePathIfExists, toModuleImportPath, touchConvexSchema, writeFileStringAndLog };
|
|
209
216
|
//# sourceMappingURL=utils.mjs.map
|
package/dist/utils.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.mjs","names":["GroupPath.modulePath","modulePath","templates.functions","FunctionPaths.make","FunctionPaths.groupPaths","GroupPath.getGroupSpec","GroupPath.fromGroupModulePath","templates.http","templates.crons","templates.authConfig"],"sources":["../src/utils.ts"],"sourcesContent":["import type { FunctionSpec, Spec } from \"@confect/core\";\nimport { FileSystem, Path } from \"@effect/platform\";\nimport type { PlatformError } from \"@effect/platform/Error\";\nimport {\n Array,\n Effect,\n HashSet,\n Option,\n Order,\n pipe,\n Record,\n String,\n} from \"effect\";\nimport * as esbuild from \"esbuild\";\nimport * as FunctionPaths from \"./FunctionPaths\";\nimport * as GroupPath from \"./GroupPath\";\nimport * as GroupPaths from \"./GroupPaths\";\nimport { logFileAdded, logFileModified, logFileRemoved } from \"./log\";\nimport { ConfectDirectory } from \"./ConfectDirectory\";\nimport { ConvexDirectory } from \"./ConvexDirectory\";\nimport * as templates from \"./templates\";\n\nexport const removePathExtension = (pathStr: string) =>\n Effect.gen(function* () {\n const path = yield* Path.Path;\n\n return String.slice(0, -path.extname(pathStr).length)(pathStr);\n });\n\nexport const EXTERNAL_PACKAGES = [\n \"@confect/core\",\n \"@confect/server\",\n \"effect\",\n \"@effect/*\",\n];\n\nconst isExternalImport = (path: string) =>\n EXTERNAL_PACKAGES.some((p) => {\n if (p.endsWith(\"/*\")) {\n return path.startsWith(p.slice(0, -1));\n }\n return path === p || path.startsWith(p + \"/\");\n });\n\nconst absoluteExternalsPlugin: esbuild.Plugin = {\n name: \"absolute-externals\",\n setup(build) {\n build.onResolve({ filter: /.*/ }, async (args) => {\n if (args.kind !== \"import-statement\" && args.kind !== \"dynamic-import\")\n return;\n if (!isExternalImport(args.path)) return;\n const resolved = import.meta.resolve(\n args.path,\n \"file://\" + args.resolveDir + \"/\",\n );\n return { path: resolved, external: true };\n });\n },\n};\n\n/**\n * Bundle a TypeScript entry point with esbuild and import the result via a\n * data URL. This handles extensionless `.ts` imports regardless of whether the\n * user's project sets `\"type\": \"module\"` in package.json.\n */\nexport const bundleAndImport = (entryPoint: string) =>\n Effect.gen(function* () {\n const result = yield* Effect.promise(() =>\n esbuild.build({\n entryPoints: [entryPoint],\n bundle: true,\n write: false,\n platform: \"node\",\n format: \"esm\",\n logLevel: \"silent\",\n plugins: [absoluteExternalsPlugin],\n }),\n );\n const code = result.outputFiles[0]!.text;\n const dataUrl =\n \"data:text/javascript;base64,\" + Buffer.from(code).toString(\"base64\");\n return yield* Effect.promise(() => import(dataUrl));\n });\n\nexport const writeFileStringAndLog = (filePath: string, contents: string) =>\n Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n if (!(yield* fs.exists(filePath))) {\n yield* fs.writeFileString(filePath, contents);\n yield* logFileAdded(filePath);\n return;\n }\n const existing = yield* fs.readFileString(filePath);\n if (existing !== contents) {\n yield* fs.writeFileString(filePath, contents);\n yield* logFileModified(filePath);\n }\n });\n\nexport const findProjectRoot = Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n const path = yield* Path.Path;\n\n const startDir = path.resolve(\".\");\n const root = path.parse(startDir).root;\n\n const directories = Array.unfold(startDir, (dir) =>\n dir === root\n ? Option.none()\n : Option.some([dir, path.dirname(dir)] as const),\n );\n\n const projectRoot = yield* Effect.findFirst(directories, (dir) =>\n fs.exists(path.join(dir, \"package.json\")),\n );\n\n return Option.getOrElse(projectRoot, () => startDir);\n});\n\nexport type WriteChange = \"Added\" | \"Modified\" | \"Unchanged\";\n\nexport const writeFileString = (\n filePath: string,\n contents: string,\n): Effect.Effect<WriteChange, PlatformError, FileSystem.FileSystem> =>\n Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n\n if (!(yield* fs.exists(filePath))) {\n yield* fs.writeFileString(filePath, contents);\n return \"Added\";\n }\n const existing = yield* fs.readFileString(filePath);\n if (existing !== contents) {\n yield* fs.writeFileString(filePath, contents);\n return \"Modified\";\n }\n return \"Unchanged\";\n });\n\nexport const generateGroupModule = ({\n groupPath,\n functionNames,\n}: {\n groupPath: GroupPath.GroupPath;\n functionNames: string[];\n}) =>\n Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n const path = yield* Path.Path;\n const convexDirectory = yield* ConvexDirectory.get;\n const confectDirectory = yield* ConfectDirectory.get;\n\n const relativeModulePath = yield* GroupPath.modulePath(groupPath);\n const modulePath = path.join(convexDirectory, relativeModulePath);\n\n const directoryPath = path.dirname(modulePath);\n if (!(yield* fs.exists(directoryPath))) {\n yield* fs.makeDirectory(directoryPath, { recursive: true });\n }\n\n const isNodeGroup = groupPath.pathSegments[0] === \"node\";\n const registeredFunctionsFileName = isNodeGroup\n ? \"nodeRegisteredFunctions.ts\"\n : \"registeredFunctions.ts\";\n const registeredFunctionsPath = path.join(\n confectDirectory,\n \"_generated\",\n registeredFunctionsFileName,\n );\n const registeredFunctionsImportPath = yield* removePathExtension(\n path.relative(path.dirname(modulePath), registeredFunctionsPath),\n );\n const registeredFunctionsVariableName = isNodeGroup\n ? \"nodeRegisteredFunctions\"\n : \"registeredFunctions\";\n\n const functionsContentsString = yield* templates.functions({\n groupPath,\n functionNames,\n registeredFunctionsImportPath,\n registeredFunctionsVariableName,\n useNode: isNodeGroup,\n ...(isNodeGroup\n ? {\n registeredFunctionsLookupPath: groupPath.pathSegments.slice(1),\n }\n : {}),\n });\n\n if (!(yield* fs.exists(modulePath))) {\n yield* fs.writeFileString(modulePath, functionsContentsString);\n return \"Added\" as const;\n }\n const existing = yield* fs.readFileString(modulePath);\n if (existing !== functionsContentsString) {\n yield* fs.writeFileString(modulePath, functionsContentsString);\n return \"Modified\" as const;\n }\n return \"Unchanged\" as const;\n });\n\nconst logGroupPaths = <R>(\n groupPaths: GroupPaths.GroupPaths,\n logFn: (fullPath: string) => Effect.Effect<void, never, R>,\n) =>\n Effect.gen(function* () {\n const path = yield* Path.Path;\n const convexDirectory = yield* ConvexDirectory.get;\n\n yield* Effect.forEach(groupPaths, (gp) =>\n Effect.gen(function* () {\n const relativeModulePath = yield* GroupPath.modulePath(gp);\n yield* logFn(path.join(convexDirectory, relativeModulePath));\n }),\n );\n });\n\nexport const generateFunctions = (spec: Spec.AnyWithProps) =>\n Effect.gen(function* () {\n const path = yield* Path.Path;\n const convexDirectory = yield* ConvexDirectory.get;\n\n const groupPathsFromFs = yield* getGroupPathsFromFs;\n const functionPaths = FunctionPaths.make(spec);\n const groupPathsFromSpec = FunctionPaths.groupPaths(functionPaths);\n\n const overlappingGroupPaths = GroupPaths.GroupPaths.make(\n HashSet.intersection(groupPathsFromFs, groupPathsFromSpec),\n );\n yield* Effect.forEach(overlappingGroupPaths, (groupPath) =>\n Effect.gen(function* () {\n const group = yield* GroupPath.getGroupSpec(spec, groupPath);\n const functionNames = pipe(\n group.functions,\n Record.values,\n Array.sortBy(\n Order.mapInput(\n Order.string,\n (fn: FunctionSpec.AnyWithProps) => fn.name,\n ),\n ),\n Array.map((fn) => fn.name),\n );\n const result = yield* generateGroupModule({ groupPath, functionNames });\n if (result === \"Modified\") {\n const relativeModulePath = yield* GroupPath.modulePath(groupPath);\n yield* logFileModified(\n path.join(convexDirectory, relativeModulePath),\n );\n }\n }),\n );\n\n const extinctGroupPaths = GroupPaths.GroupPaths.make(\n HashSet.difference(groupPathsFromFs, groupPathsFromSpec),\n );\n yield* removeGroups(extinctGroupPaths);\n yield* logGroupPaths(extinctGroupPaths, logFileRemoved);\n\n const newGroupPaths = GroupPaths.GroupPaths.make(\n HashSet.difference(groupPathsFromSpec, groupPathsFromFs),\n );\n yield* writeGroups(spec, newGroupPaths);\n yield* logGroupPaths(newGroupPaths, logFileAdded);\n\n return functionPaths;\n });\n\nconst getGroupPathsFromFs = Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n const path = yield* Path.Path;\n const convexDirectory = yield* ConvexDirectory.get;\n\n const RESERVED_CONVEX_TS_FILE_NAMES = new Set([\n \"schema.ts\",\n \"http.ts\",\n \"crons.ts\",\n \"auth.config.ts\",\n \"convex.config.ts\",\n ]);\n\n const allConvexPaths = yield* fs.readDirectory(convexDirectory, {\n recursive: true,\n });\n const groupPathArray = yield* pipe(\n allConvexPaths,\n Array.filter(\n (convexPath) =>\n path.extname(convexPath) === \".ts\" &&\n !RESERVED_CONVEX_TS_FILE_NAMES.has(path.basename(convexPath)) &&\n path.basename(path.dirname(convexPath)) !== \"_generated\",\n ),\n Effect.forEach((groupModulePath) =>\n GroupPath.fromGroupModulePath(groupModulePath),\n ),\n );\n return pipe(groupPathArray, HashSet.fromIterable, GroupPaths.GroupPaths.make);\n});\n\nexport const removeGroups = (groupPaths: GroupPaths.GroupPaths) =>\n Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n const path = yield* Path.Path;\n const convexDirectory = yield* ConvexDirectory.get;\n\n yield* Effect.all(\n HashSet.map(groupPaths, (groupPath) =>\n Effect.gen(function* () {\n const relativeModulePath = yield* GroupPath.modulePath(groupPath);\n const modulePath = path.join(convexDirectory, relativeModulePath);\n\n yield* Effect.logDebug(`Removing group '${relativeModulePath}'...`);\n\n yield* fs.remove(modulePath);\n yield* Effect.logDebug(`Group '${relativeModulePath}' removed`);\n }),\n ),\n { concurrency: \"unbounded\" },\n );\n });\n\nexport const writeGroups = (\n spec: Spec.AnyWithProps,\n groupPaths: GroupPaths.GroupPaths,\n) =>\n Effect.forEach(groupPaths, (groupPath) =>\n Effect.gen(function* () {\n const group = yield* GroupPath.getGroupSpec(spec, groupPath);\n\n const functionNames = pipe(\n group.functions,\n Record.values,\n Array.sortBy(\n Order.mapInput(\n Order.string,\n (fn: FunctionSpec.AnyWithProps) => fn.name,\n ),\n ),\n Array.map((fn) => fn.name),\n );\n\n yield* Effect.logDebug(`Generating group ${groupPath}...`);\n yield* generateGroupModule({\n groupPath,\n functionNames,\n });\n yield* Effect.logDebug(`Group ${groupPath} generated`);\n }),\n );\n\nconst generateOptionalFile = (\n confectFile: string,\n convexFile: string,\n generateContents: (importPath: string) => Effect.Effect<string>,\n) =>\n Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n const path = yield* Path.Path;\n const confectDirectory = yield* ConfectDirectory.get;\n const convexDirectory = yield* ConvexDirectory.get;\n\n const confectFilePath = path.join(confectDirectory, confectFile);\n\n if (!(yield* fs.exists(confectFilePath))) {\n return Option.none();\n }\n\n const convexFilePath = path.join(convexDirectory, convexFile);\n const relativeImportPath = path.relative(\n path.dirname(convexFilePath),\n confectFilePath,\n );\n const importPathWithoutExt = yield* removePathExtension(relativeImportPath);\n const contents = yield* generateContents(importPathWithoutExt);\n const change = yield* writeFileString(convexFilePath, contents);\n return Option.some({ change, convexFilePath });\n });\n\nexport const generateHttp = generateOptionalFile(\n \"http.ts\",\n \"http.ts\",\n (importPath) => templates.http({ httpImportPath: importPath }),\n);\n\nexport const generateCrons = generateOptionalFile(\n \"crons.ts\",\n \"crons.ts\",\n (importPath) => templates.crons({ cronsImportPath: importPath }),\n);\n\nexport const generateAuthConfig = generateOptionalFile(\n \"auth.ts\",\n \"auth.config.ts\",\n (importPath) => templates.authConfig({ authImportPath: importPath }),\n);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"utils.mjs","names":["GroupPath.modulePath","modulePath","templates.functions","FunctionPaths.make","FunctionPaths.groupPaths","GroupPath.getGroupSpec","GroupPath.fromGroupModulePath","templates.http","templates.crons","templates.authConfig"],"sources":["../src/utils.ts"],"sourcesContent":["import type { FunctionSpec, Spec } from \"@confect/core\";\nimport { FileSystem, Path } from \"@effect/platform\";\nimport type { PlatformError } from \"@effect/platform/Error\";\nimport {\n Array,\n Context,\n Effect,\n HashSet,\n Option,\n Order,\n pipe,\n Record,\n Ref,\n String,\n} from \"effect\";\nimport * as FunctionPaths from \"./FunctionPaths\";\nimport * as GroupPath from \"./GroupPath\";\nimport * as GroupPaths from \"./GroupPaths\";\nimport { logFileAdded, logFileModified, logFileRemoved } from \"./log\";\nimport { ConfectDirectory } from \"./ConfectDirectory\";\nimport { ConvexDirectory } from \"./ConvexDirectory\";\nimport * as templates from \"./templates\";\n\n/**\n * Tracks whether the current codegen run wrote anything to disk. Set to\n * `true` by every helper that actually overwrites a file (skipping the\n * content-unchanged case). `codegenHandler` provides a fresh `Ref` per\n * run and reads it back to report `anyWritesHappened`; callers outside a\n * codegen pass hit the cached default `Ref` and need not interact with\n * the tracker.\n */\nexport class WriteTracker extends Context.Reference<WriteTracker>()(\n \"@confect/cli/WriteTracker\",\n { defaultValue: () => Ref.unsafeMake(false) },\n) {}\n\nconst markWritten = Effect.gen(function* () {\n const tracker = yield* WriteTracker;\n yield* Ref.set(tracker, true);\n});\n\nexport const removePathExtension = (pathStr: string) =>\n Effect.gen(function* () {\n const path = yield* Path.Path;\n\n return String.slice(0, -path.extname(pathStr).length)(pathStr);\n });\n\n/** Ensures a relative path is a valid ESM/TS module specifier (e.g. `spec` → `./spec`). */\nexport const toModuleImportPath = (relativePath: string) =>\n Effect.gen(function* () {\n const withoutExt = yield* removePathExtension(relativePath);\n return withoutExt.startsWith(\".\") ? withoutExt : `./${withoutExt}`;\n });\n\nexport const writeFileStringAndLog = (filePath: string, contents: string) =>\n Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n if (!(yield* fs.exists(filePath))) {\n yield* fs.writeFileString(filePath, contents);\n yield* markWritten;\n yield* logFileAdded(filePath);\n return;\n }\n const existing = yield* fs.readFileString(filePath);\n if (existing !== contents) {\n yield* fs.writeFileString(filePath, contents);\n yield* markWritten;\n yield* logFileModified(filePath);\n }\n });\n\nexport const findProjectRoot = Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n const path = yield* Path.Path;\n\n const startDir = path.resolve(\".\");\n const root = path.parse(startDir).root;\n\n const directories = Array.unfold(startDir, (dir) =>\n dir === root\n ? Option.none()\n : Option.some([dir, path.dirname(dir)] as const),\n );\n\n const projectRoot = yield* Effect.findFirst(directories, (dir) =>\n fs.exists(path.join(dir, \"package.json\")),\n );\n\n return Option.getOrElse(projectRoot, () => startDir);\n});\n\nexport type WriteChange = \"Added\" | \"Modified\" | \"Unchanged\";\n\nexport const writeFileString = (\n filePath: string,\n contents: string,\n): Effect.Effect<WriteChange, PlatformError, FileSystem.FileSystem> =>\n Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n\n if (!(yield* fs.exists(filePath))) {\n yield* fs.writeFileString(filePath, contents);\n yield* markWritten;\n return \"Added\";\n }\n const existing = yield* fs.readFileString(filePath);\n if (existing !== contents) {\n yield* fs.writeFileString(filePath, contents);\n yield* markWritten;\n return \"Modified\";\n }\n return \"Unchanged\";\n });\n\nexport const removePathIfExists = (\n filePath: string,\n): Effect.Effect<void, PlatformError, FileSystem.FileSystem> =>\n Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n\n if (!(yield* fs.exists(filePath))) {\n return;\n }\n\n yield* fs\n .remove(filePath)\n .pipe(\n Effect.catchTag(\"SystemError\", (error) =>\n error.reason === \"NotFound\" ? Effect.void : Effect.fail(error),\n ),\n );\n });\n\n/**\n * Bump the mtime of `convex/schema.ts` so the Convex CLI's chokidar watcher\n * emits a `change` event after every successful Confect codegen run. Without\n * this, a codegen that doesn't change any file content (for example,\n * recovering from a transient broken state in `confect/`) leaves Convex\n * stuck on its previous error because nothing it observes has changed.\n */\nexport const touchConvexSchema = Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n const path = yield* Path.Path;\n const convexDirectory = yield* ConvexDirectory.get;\n const schemaPath = path.join(convexDirectory, \"schema.ts\");\n\n if (!(yield* fs.exists(schemaPath))) {\n return;\n }\n\n const now = new Date();\n yield* fs.utimes(schemaPath, now, now);\n});\n\nexport const generateGroupModule = ({\n groupPath,\n functionNames,\n registeredFunctionsImportPath,\n useNode = false,\n}: {\n groupPath: GroupPath.GroupPath;\n functionNames: string[];\n registeredFunctionsImportPath: string;\n useNode?: boolean;\n}) =>\n Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n const path = yield* Path.Path;\n const convexDirectory = yield* ConvexDirectory.get;\n\n const relativeModulePath = yield* GroupPath.modulePath(groupPath);\n const modulePath = path.join(convexDirectory, relativeModulePath);\n\n const directoryPath = path.dirname(modulePath);\n if (!(yield* fs.exists(directoryPath))) {\n yield* fs.makeDirectory(directoryPath, { recursive: true });\n }\n\n const functionsContentsString = yield* templates.functions({\n functionNames,\n registeredFunctionsImportPath,\n useNode,\n });\n\n if (!(yield* fs.exists(modulePath))) {\n yield* fs.writeFileString(modulePath, functionsContentsString);\n yield* markWritten;\n return \"Added\" as const;\n }\n const existing = yield* fs.readFileString(modulePath);\n if (existing !== functionsContentsString) {\n yield* fs.writeFileString(modulePath, functionsContentsString);\n yield* markWritten;\n return \"Modified\" as const;\n }\n return \"Unchanged\" as const;\n });\n\nconst logGroupPaths = <R>(\n groupPaths: GroupPaths.GroupPaths,\n logFn: (fullPath: string) => Effect.Effect<void, never, R>,\n) =>\n Effect.gen(function* () {\n const path = yield* Path.Path;\n const convexDirectory = yield* ConvexDirectory.get;\n\n yield* Effect.forEach(groupPaths, (gp) =>\n Effect.gen(function* () {\n const relativeModulePath = yield* GroupPath.modulePath(gp);\n yield* logFn(path.join(convexDirectory, relativeModulePath));\n }),\n );\n });\n\nexport const generateFunctions = (spec: Spec.AnyWithProps) =>\n Effect.gen(function* () {\n const path = yield* Path.Path;\n const convexDirectory = yield* ConvexDirectory.get;\n const confectDirectory = yield* ConfectDirectory.get;\n\n const groupPathsFromFs = yield* getGroupPathsFromFs;\n const functionPaths = FunctionPaths.make(spec);\n const groupPathsFromSpec = FunctionPaths.groupPaths(functionPaths);\n\n const overlappingGroupPaths = GroupPaths.GroupPaths.make(\n HashSet.intersection(groupPathsFromFs, groupPathsFromSpec),\n );\n yield* Effect.forEach(overlappingGroupPaths, (groupPath) =>\n Effect.gen(function* () {\n const group = yield* GroupPath.getGroupSpec(spec, groupPath);\n const functionNames = pipe(\n group.functions,\n Record.values,\n Array.sortBy(\n Order.mapInput(\n Order.string,\n (fn: FunctionSpec.AnyWithProps) => fn.name,\n ),\n ),\n Array.map((fn) => fn.name),\n );\n const relativeModulePath = yield* GroupPath.modulePath(groupPath);\n const modulePath = path.join(convexDirectory, relativeModulePath);\n const registrySegments =\n groupPath.pathSegments[0] === \"node\"\n ? groupPath.pathSegments.slice(1)\n : groupPath.pathSegments;\n const registeredFunctionsPath =\n path.join(\n confectDirectory,\n \"_generated\",\n \"registeredFunctions\",\n ...registrySegments,\n ) + \".ts\";\n const registeredFunctionsImportPath = yield* toModuleImportPath(\n path.relative(path.dirname(modulePath), registeredFunctionsPath),\n );\n const result = yield* generateGroupModule({\n groupPath,\n functionNames,\n registeredFunctionsImportPath,\n useNode: groupPath.pathSegments[0] === \"node\",\n });\n if (result === \"Modified\") {\n yield* logFileModified(modulePath);\n }\n }),\n );\n\n const extinctGroupPaths = GroupPaths.GroupPaths.make(\n HashSet.difference(groupPathsFromFs, groupPathsFromSpec),\n );\n yield* removeGroups(extinctGroupPaths);\n yield* logGroupPaths(extinctGroupPaths, logFileRemoved);\n\n const newGroupPaths = GroupPaths.GroupPaths.make(\n HashSet.difference(groupPathsFromSpec, groupPathsFromFs),\n );\n yield* writeGroups(spec, newGroupPaths);\n yield* logGroupPaths(newGroupPaths, logFileAdded);\n\n return functionPaths;\n });\n\nconst getGroupPathsFromFs = Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n const path = yield* Path.Path;\n const convexDirectory = yield* ConvexDirectory.get;\n\n const RESERVED_CONVEX_TS_FILE_NAMES = new Set([\n \"schema.ts\",\n \"http.ts\",\n \"crons.ts\",\n \"auth.config.ts\",\n \"convex.config.ts\",\n ]);\n\n const allConvexPaths = yield* fs.readDirectory(convexDirectory, {\n recursive: true,\n });\n const groupPathArray = yield* pipe(\n allConvexPaths,\n Array.filter(\n (convexPath) =>\n path.extname(convexPath) === \".ts\" &&\n !RESERVED_CONVEX_TS_FILE_NAMES.has(path.basename(convexPath)) &&\n path.basename(path.dirname(convexPath)) !== \"_generated\",\n ),\n Effect.forEach((groupModulePath) =>\n GroupPath.fromGroupModulePath(groupModulePath),\n ),\n );\n return pipe(groupPathArray, HashSet.fromIterable, GroupPaths.GroupPaths.make);\n});\n\nexport const removeGroups = (groupPaths: GroupPaths.GroupPaths) =>\n Effect.gen(function* () {\n const path = yield* Path.Path;\n const convexDirectory = yield* ConvexDirectory.get;\n\n yield* Effect.all(\n HashSet.map(groupPaths, (groupPath) =>\n Effect.gen(function* () {\n const relativeModulePath = yield* GroupPath.modulePath(groupPath);\n const modulePath = path.join(convexDirectory, relativeModulePath);\n\n yield* Effect.logDebug(`Removing group '${relativeModulePath}'...`);\n\n yield* removePathIfExists(modulePath);\n yield* Effect.logDebug(`Group '${relativeModulePath}' removed`);\n }),\n ),\n { concurrency: \"unbounded\" },\n );\n });\n\nexport const writeGroups = (\n spec: Spec.AnyWithProps,\n groupPaths: GroupPaths.GroupPaths,\n) =>\n Effect.forEach(groupPaths, (groupPath) =>\n Effect.gen(function* () {\n const path = yield* Path.Path;\n const convexDirectory = yield* ConvexDirectory.get;\n const confectDirectory = yield* ConfectDirectory.get;\n const group = yield* GroupPath.getGroupSpec(spec, groupPath);\n\n const functionNames = pipe(\n group.functions,\n Record.values,\n Array.sortBy(\n Order.mapInput(\n Order.string,\n (fn: FunctionSpec.AnyWithProps) => fn.name,\n ),\n ),\n Array.map((fn) => fn.name),\n );\n\n const relativeModulePath = yield* GroupPath.modulePath(groupPath);\n const modulePath = path.join(convexDirectory, relativeModulePath);\n const registeredFunctionsPath =\n path.join(\n confectDirectory,\n \"_generated\",\n \"registeredFunctions\",\n ...groupPath.pathSegments,\n ) + \".ts\";\n const registeredFunctionsImportPath = yield* toModuleImportPath(\n path.relative(path.dirname(modulePath), registeredFunctionsPath),\n );\n\n yield* Effect.logDebug(`Generating group ${groupPath}...`);\n yield* generateGroupModule({\n groupPath,\n functionNames,\n registeredFunctionsImportPath,\n useNode: groupPath.pathSegments[0] === \"node\",\n });\n yield* Effect.logDebug(`Group ${groupPath} generated`);\n }),\n );\n\nconst generateOptionalFile = (\n confectFile: string,\n convexFile: string,\n generateContents: (importPath: string) => Effect.Effect<string>,\n) =>\n Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n const path = yield* Path.Path;\n const confectDirectory = yield* ConfectDirectory.get;\n const convexDirectory = yield* ConvexDirectory.get;\n\n const confectFilePath = path.join(confectDirectory, confectFile);\n\n if (!(yield* fs.exists(confectFilePath))) {\n return Option.none();\n }\n\n const convexFilePath = path.join(convexDirectory, convexFile);\n const relativeImportPath = path.relative(\n path.dirname(convexFilePath),\n confectFilePath,\n );\n const importPathWithoutExt = yield* removePathExtension(relativeImportPath);\n const contents = yield* generateContents(importPathWithoutExt);\n const change = yield* writeFileString(convexFilePath, contents);\n return Option.some({ change, convexFilePath });\n });\n\nexport const generateHttp = generateOptionalFile(\n \"http.ts\",\n \"http.ts\",\n (importPath) => templates.http({ httpImportPath: importPath }),\n);\n\nexport const generateCrons = generateOptionalFile(\n \"crons.ts\",\n \"crons.ts\",\n (importPath) => templates.crons({ cronsImportPath: importPath }),\n);\n\nexport const generateAuthConfig = generateOptionalFile(\n \"auth.ts\",\n \"auth.config.ts\",\n (importPath) => templates.authConfig({ authImportPath: importPath }),\n);\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA+BA,IAAa,eAAb,cAAkC,QAAQ,WAAyB,CACjE,6BACA,EAAE,oBAAoB,IAAI,WAAW,MAAM,EAAE,CAC9C,CAAC;AAEF,MAAM,cAAc,OAAO,IAAI,aAAa;CAC1C,MAAM,UAAU,OAAO;AACvB,QAAO,IAAI,IAAI,SAAS,KAAK;EAC7B;AAEF,MAAa,uBAAuB,YAClC,OAAO,IAAI,aAAa;CACtB,MAAM,OAAO,OAAO,KAAK;AAEzB,QAAO,OAAO,MAAM,GAAG,CAAC,KAAK,QAAQ,QAAQ,CAAC,OAAO,CAAC,QAAQ;EAC9D;;AAGJ,MAAa,sBAAsB,iBACjC,OAAO,IAAI,aAAa;CACtB,MAAM,aAAa,OAAO,oBAAoB,aAAa;AAC3D,QAAO,WAAW,WAAW,IAAI,GAAG,aAAa,KAAK;EACtD;AAEJ,MAAa,yBAAyB,UAAkB,aACtD,OAAO,IAAI,aAAa;CACtB,MAAM,KAAK,OAAO,WAAW;AAC7B,KAAI,EAAE,OAAO,GAAG,OAAO,SAAS,GAAG;AACjC,SAAO,GAAG,gBAAgB,UAAU,SAAS;AAC7C,SAAO;AACP,SAAO,aAAa,SAAS;AAC7B;;AAGF,MADiB,OAAO,GAAG,eAAe,SAAS,MAClC,UAAU;AACzB,SAAO,GAAG,gBAAgB,UAAU,SAAS;AAC7C,SAAO;AACP,SAAO,gBAAgB,SAAS;;EAElC;AAEJ,MAAa,kBAAkB,OAAO,IAAI,aAAa;CACrD,MAAM,KAAK,OAAO,WAAW;CAC7B,MAAM,OAAO,OAAO,KAAK;CAEzB,MAAM,WAAW,KAAK,QAAQ,IAAI;CAClC,MAAM,OAAO,KAAK,MAAM,SAAS,CAAC;CAElC,MAAM,cAAc,MAAM,OAAO,WAAW,QAC1C,QAAQ,OACJ,OAAO,MAAM,GACb,OAAO,KAAK,CAAC,KAAK,KAAK,QAAQ,IAAI,CAAC,CAAU,CACnD;CAED,MAAM,cAAc,OAAO,OAAO,UAAU,cAAc,QACxD,GAAG,OAAO,KAAK,KAAK,KAAK,eAAe,CAAC,CAC1C;AAED,QAAO,OAAO,UAAU,mBAAmB,SAAS;EACpD;AAIF,MAAa,mBACX,UACA,aAEA,OAAO,IAAI,aAAa;CACtB,MAAM,KAAK,OAAO,WAAW;AAE7B,KAAI,EAAE,OAAO,GAAG,OAAO,SAAS,GAAG;AACjC,SAAO,GAAG,gBAAgB,UAAU,SAAS;AAC7C,SAAO;AACP,SAAO;;AAGT,MADiB,OAAO,GAAG,eAAe,SAAS,MAClC,UAAU;AACzB,SAAO,GAAG,gBAAgB,UAAU,SAAS;AAC7C,SAAO;AACP,SAAO;;AAET,QAAO;EACP;AAEJ,MAAa,sBACX,aAEA,OAAO,IAAI,aAAa;CACtB,MAAM,KAAK,OAAO,WAAW;AAE7B,KAAI,EAAE,OAAO,GAAG,OAAO,SAAS,EAC9B;AAGF,QAAO,GACJ,OAAO,SAAS,CAChB,KACC,OAAO,SAAS,gBAAgB,UAC9B,MAAM,WAAW,aAAa,OAAO,OAAO,OAAO,KAAK,MAAM,CAC/D,CACF;EACH;;;;;;;;AASJ,MAAa,oBAAoB,OAAO,IAAI,aAAa;CACvD,MAAM,KAAK,OAAO,WAAW;CAC7B,MAAM,OAAO,OAAO,KAAK;CACzB,MAAM,kBAAkB,OAAO,gBAAgB;CAC/C,MAAM,aAAa,KAAK,KAAK,iBAAiB,YAAY;AAE1D,KAAI,EAAE,OAAO,GAAG,OAAO,WAAW,EAChC;CAGF,MAAM,sBAAM,IAAI,MAAM;AACtB,QAAO,GAAG,OAAO,YAAY,KAAK,IAAI;EACtC;AAEF,MAAa,uBAAuB,EAClC,WACA,eACA,+BACA,UAAU,YAOV,OAAO,IAAI,aAAa;CACtB,MAAM,KAAK,OAAO,WAAW;CAC7B,MAAM,OAAO,OAAO,KAAK;CACzB,MAAM,kBAAkB,OAAO,gBAAgB;CAE/C,MAAM,qBAAqB,OAAOA,WAAqB,UAAU;CACjE,MAAMC,eAAa,KAAK,KAAK,iBAAiB,mBAAmB;CAEjE,MAAM,gBAAgB,KAAK,QAAQA,aAAW;AAC9C,KAAI,EAAE,OAAO,GAAG,OAAO,cAAc,EACnC,QAAO,GAAG,cAAc,eAAe,EAAE,WAAW,MAAM,CAAC;CAG7D,MAAM,0BAA0B,OAAOC,UAAoB;EACzD;EACA;EACA;EACD,CAAC;AAEF,KAAI,EAAE,OAAO,GAAG,OAAOD,aAAW,GAAG;AACnC,SAAO,GAAG,gBAAgBA,cAAY,wBAAwB;AAC9D,SAAO;AACP,SAAO;;AAGT,MADiB,OAAO,GAAG,eAAeA,aAAW,MACpC,yBAAyB;AACxC,SAAO,GAAG,gBAAgBA,cAAY,wBAAwB;AAC9D,SAAO;AACP,SAAO;;AAET,QAAO;EACP;AAEJ,MAAM,iBACJ,YACA,UAEA,OAAO,IAAI,aAAa;CACtB,MAAM,OAAO,OAAO,KAAK;CACzB,MAAM,kBAAkB,OAAO,gBAAgB;AAE/C,QAAO,OAAO,QAAQ,aAAa,OACjC,OAAO,IAAI,aAAa;EACtB,MAAM,qBAAqB,OAAOD,WAAqB,GAAG;AAC1D,SAAO,MAAM,KAAK,KAAK,iBAAiB,mBAAmB,CAAC;GAC5D,CACH;EACD;AAEJ,MAAa,qBAAqB,SAChC,OAAO,IAAI,aAAa;CACtB,MAAM,OAAO,OAAO,KAAK;CACzB,MAAM,kBAAkB,OAAO,gBAAgB;CAC/C,MAAM,mBAAmB,OAAO,iBAAiB;CAEjD,MAAM,mBAAmB,OAAO;CAChC,MAAM,gBAAgBG,KAAmB,KAAK;CAC9C,MAAM,qBAAqBC,WAAyB,cAAc;CAElE,MAAM,mCAA8C,KAClD,QAAQ,aAAa,kBAAkB,mBAAmB,CAC3D;AACD,QAAO,OAAO,QAAQ,wBAAwB,cAC5C,OAAO,IAAI,aAAa;EAEtB,MAAM,gBAAgB,MADR,OAAOC,aAAuB,MAAM,UAAU,EAEpD,WACN,OAAO,QACP,MAAM,OACJ,MAAM,SACJ,MAAM,SACL,OAAkC,GAAG,KACvC,CACF,EACD,MAAM,KAAK,OAAO,GAAG,KAAK,CAC3B;EACD,MAAM,qBAAqB,OAAOL,WAAqB,UAAU;EACjE,MAAMC,eAAa,KAAK,KAAK,iBAAiB,mBAAmB;EACjE,MAAM,mBACJ,UAAU,aAAa,OAAO,SAC1B,UAAU,aAAa,MAAM,EAAE,GAC/B,UAAU;EAChB,MAAM,0BACJ,KAAK,KACH,kBACA,cACA,uBACA,GAAG,iBACJ,GAAG;AAUN,OANe,OAAO,oBAAoB;GACxC;GACA;GACA,+BANoC,OAAO,mBAC3C,KAAK,SAAS,KAAK,QAAQA,aAAW,EAAE,wBAAwB,CACjE;GAKC,SAAS,UAAU,aAAa,OAAO;GACxC,CAAC,MACa,WACb,QAAO,gBAAgBA,aAAW;GAEpC,CACH;CAED,MAAM,+BAA0C,KAC9C,QAAQ,WAAW,kBAAkB,mBAAmB,CACzD;AACD,QAAO,aAAa,kBAAkB;AACtC,QAAO,cAAc,mBAAmB,eAAe;CAEvD,MAAM,2BAAsC,KAC1C,QAAQ,WAAW,oBAAoB,iBAAiB,CACzD;AACD,QAAO,YAAY,MAAM,cAAc;AACvC,QAAO,cAAc,eAAe,aAAa;AAEjD,QAAO;EACP;AAEJ,MAAM,sBAAsB,OAAO,IAAI,aAAa;CAClD,MAAM,KAAK,OAAO,WAAW;CAC7B,MAAM,OAAO,OAAO,KAAK;CACzB,MAAM,kBAAkB,OAAO,gBAAgB;CAE/C,MAAM,gCAAgC,IAAI,IAAI;EAC5C;EACA;EACA;EACA;EACA;EACD,CAAC;AAiBF,QAAO,KAZgB,OAAO,KAHP,OAAO,GAAG,cAAc,iBAAiB,EAC9D,WAAW,MACZ,CAAC,EAGA,MAAM,QACH,eACC,KAAK,QAAQ,WAAW,KAAK,SAC7B,CAAC,8BAA8B,IAAI,KAAK,SAAS,WAAW,CAAC,IAC7D,KAAK,SAAS,KAAK,QAAQ,WAAW,CAAC,KAAK,aAC/C,EACD,OAAO,SAAS,oBACdK,oBAA8B,gBAAgB,CAC/C,CACF,EAC2B,QAAQ,yBAAoC,KAAK;EAC7E;AAEF,MAAa,gBAAgB,eAC3B,OAAO,IAAI,aAAa;CACtB,MAAM,OAAO,OAAO,KAAK;CACzB,MAAM,kBAAkB,OAAO,gBAAgB;AAE/C,QAAO,OAAO,IACZ,QAAQ,IAAI,aAAa,cACvB,OAAO,IAAI,aAAa;EACtB,MAAM,qBAAqB,OAAON,WAAqB,UAAU;EACjE,MAAMC,eAAa,KAAK,KAAK,iBAAiB,mBAAmB;AAEjE,SAAO,OAAO,SAAS,mBAAmB,mBAAmB,MAAM;AAEnE,SAAO,mBAAmBA,aAAW;AACrC,SAAO,OAAO,SAAS,UAAU,mBAAmB,WAAW;GAC/D,CACH,EACD,EAAE,aAAa,aAAa,CAC7B;EACD;AAEJ,MAAa,eACX,MACA,eAEA,OAAO,QAAQ,aAAa,cAC1B,OAAO,IAAI,aAAa;CACtB,MAAM,OAAO,OAAO,KAAK;CACzB,MAAM,kBAAkB,OAAO,gBAAgB;CAC/C,MAAM,mBAAmB,OAAO,iBAAiB;CAGjD,MAAM,gBAAgB,MAFR,OAAOI,aAAuB,MAAM,UAAU,EAGpD,WACN,OAAO,QACP,MAAM,OACJ,MAAM,SACJ,MAAM,SACL,OAAkC,GAAG,KACvC,CACF,EACD,MAAM,KAAK,OAAO,GAAG,KAAK,CAC3B;CAED,MAAM,qBAAqB,OAAOL,WAAqB,UAAU;CACjE,MAAMC,eAAa,KAAK,KAAK,iBAAiB,mBAAmB;CACjE,MAAM,0BACJ,KAAK,KACH,kBACA,cACA,uBACA,GAAG,UAAU,aACd,GAAG;CACN,MAAM,gCAAgC,OAAO,mBAC3C,KAAK,SAAS,KAAK,QAAQA,aAAW,EAAE,wBAAwB,CACjE;AAED,QAAO,OAAO,SAAS,oBAAoB,UAAU,KAAK;AAC1D,QAAO,oBAAoB;EACzB;EACA;EACA;EACA,SAAS,UAAU,aAAa,OAAO;EACxC,CAAC;AACF,QAAO,OAAO,SAAS,SAAS,UAAU,YAAY;EACtD,CACH;AAEH,MAAM,wBACJ,aACA,YACA,qBAEA,OAAO,IAAI,aAAa;CACtB,MAAM,KAAK,OAAO,WAAW;CAC7B,MAAM,OAAO,OAAO,KAAK;CACzB,MAAM,mBAAmB,OAAO,iBAAiB;CACjD,MAAM,kBAAkB,OAAO,gBAAgB;CAE/C,MAAM,kBAAkB,KAAK,KAAK,kBAAkB,YAAY;AAEhE,KAAI,EAAE,OAAO,GAAG,OAAO,gBAAgB,EACrC,QAAO,OAAO,MAAM;CAGtB,MAAM,iBAAiB,KAAK,KAAK,iBAAiB,WAAW;CAO7D,MAAM,SAAS,OAAO,gBAAgB,gBADrB,OAAO,iBADK,OAAO,oBAJT,KAAK,SAC9B,KAAK,QAAQ,eAAe,EAC5B,gBACD,CAC0E,CACb,CACC;AAC/D,QAAO,OAAO,KAAK;EAAE;EAAQ;EAAgB,CAAC;EAC9C;AAEJ,MAAa,eAAe,qBAC1B,WACA,YACC,eAAeM,KAAe,EAAE,gBAAgB,YAAY,CAAC,CAC/D;AAED,MAAa,gBAAgB,qBAC3B,YACA,aACC,eAAeC,MAAgB,EAAE,iBAAiB,YAAY,CAAC,CACjE;AAED,MAAa,qBAAqB,qBAChC,WACA,mBACC,eAAeC,WAAqB,EAAE,gBAAgB,YAAY,CAAC,CACrE"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@confect/cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "9.0.0-next.0",
|
|
4
4
|
"description": "Developer tooling for codegen and sync",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -38,22 +38,22 @@
|
|
|
38
38
|
"author": "RJ Dellecese",
|
|
39
39
|
"license": "ISC",
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@effect/cli": "^0.
|
|
42
|
-
"@effect/platform
|
|
43
|
-
"@effect/platform-node
|
|
44
|
-
"@effect/
|
|
45
|
-
"@effect/printer
|
|
41
|
+
"@effect/cli": "^0.75.1",
|
|
42
|
+
"@effect/platform": "0.96.1",
|
|
43
|
+
"@effect/platform-node": "0.106.0",
|
|
44
|
+
"@effect/platform-node-shared": "0.59.0",
|
|
45
|
+
"@effect/printer": "^0.49.0",
|
|
46
|
+
"@effect/printer-ansi": "^0.49.0",
|
|
46
47
|
"code-block-writer": "^13.0.3",
|
|
47
48
|
"esbuild": "^0.27.3"
|
|
48
49
|
},
|
|
49
50
|
"devDependencies": {
|
|
50
|
-
"@effect/language-service": "0.
|
|
51
|
-
"@effect/
|
|
52
|
-
"@effect/vitest": "0.27.0",
|
|
51
|
+
"@effect/language-service": "0.86.1",
|
|
52
|
+
"@effect/vitest": "0.29.0",
|
|
53
53
|
"@eslint/js": "10.0.1",
|
|
54
54
|
"@types/node": "25.3.3",
|
|
55
55
|
"@vitest/coverage-v8": "3.2.4",
|
|
56
|
-
"effect": "3.
|
|
56
|
+
"effect": "3.21.2",
|
|
57
57
|
"eslint": "10.0.2",
|
|
58
58
|
"prettier": "3.8.1",
|
|
59
59
|
"tsdown": "0.20.3",
|
|
@@ -64,10 +64,9 @@
|
|
|
64
64
|
"vitest": "3.2.4"
|
|
65
65
|
},
|
|
66
66
|
"peerDependencies": {
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
"@confect/
|
|
70
|
-
"@confect/server": "7.0.0"
|
|
67
|
+
"effect": "^3.21.2",
|
|
68
|
+
"@confect/core": "^9.0.0-next.0",
|
|
69
|
+
"@confect/server": "^9.0.0-next.0"
|
|
71
70
|
},
|
|
72
71
|
"engines": {
|
|
73
72
|
"node": ">=22",
|
|
@@ -78,7 +77,7 @@
|
|
|
78
77
|
"types": "./dist/index.d.ts",
|
|
79
78
|
"scripts": {
|
|
80
79
|
"build": "tsdown --config-loader unrun",
|
|
81
|
-
"dev": "tsdown --watch",
|
|
80
|
+
"dev": "tsdown --watch --config-loader unrun",
|
|
82
81
|
"test": "vitest run",
|
|
83
82
|
"typecheck": "tsc --noEmit --project tsconfig.json",
|
|
84
83
|
"fix": "prettier --write . && eslint --fix . --max-warnings=0",
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { Ansi, AnsiDoc } from "@effect/printer-ansi";
|
|
2
|
+
import { Array, Effect, Match, Option, pipe, Schema, String } from "effect";
|
|
3
|
+
import * as esbuild from "esbuild";
|
|
4
|
+
import { formatPathDoc } from "./log";
|
|
5
|
+
|
|
6
|
+
// --- Variants ---
|
|
7
|
+
|
|
8
|
+
export class BundleFailedError extends Schema.TaggedError<BundleFailedError>()(
|
|
9
|
+
"BundleFailedError",
|
|
10
|
+
{
|
|
11
|
+
file: Schema.String,
|
|
12
|
+
errors: Schema.Array(Schema.Unknown),
|
|
13
|
+
},
|
|
14
|
+
) {}
|
|
15
|
+
|
|
16
|
+
export class ImportFailedError extends Schema.TaggedError<ImportFailedError>()(
|
|
17
|
+
"ImportFailedError",
|
|
18
|
+
{
|
|
19
|
+
file: Schema.String,
|
|
20
|
+
cause: Schema.Unknown,
|
|
21
|
+
},
|
|
22
|
+
) {}
|
|
23
|
+
|
|
24
|
+
export const BuildError = Schema.Union(BundleFailedError, ImportFailedError);
|
|
25
|
+
export type BuildError = typeof BuildError.Type;
|
|
26
|
+
|
|
27
|
+
export const isBuildError = (error: unknown): error is BuildError =>
|
|
28
|
+
Schema.is(BuildError)(error);
|
|
29
|
+
|
|
30
|
+
// --- Bundler adapter ---
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Internal failure produced by the esbuild bundle/import pipeline. Always
|
|
34
|
+
* remapped to a {@link BuildError} (which carries enough context for the CLI
|
|
35
|
+
* to render it) before reaching a user-surface boundary.
|
|
36
|
+
*/
|
|
37
|
+
export class BundlerError extends Schema.TaggedError<BundlerError>()(
|
|
38
|
+
"BundlerError",
|
|
39
|
+
{
|
|
40
|
+
cause: Schema.Unknown,
|
|
41
|
+
},
|
|
42
|
+
) {}
|
|
43
|
+
|
|
44
|
+
const isEsbuildBuildFailure = (error: unknown): error is esbuild.BuildFailure =>
|
|
45
|
+
typeof error === "object" &&
|
|
46
|
+
error !== null &&
|
|
47
|
+
"errors" in error &&
|
|
48
|
+
globalThis.Array.isArray((error as esbuild.BuildFailure).errors);
|
|
49
|
+
|
|
50
|
+
export const fromBundlerError = (
|
|
51
|
+
file: string,
|
|
52
|
+
error: BundlerError,
|
|
53
|
+
): BuildError =>
|
|
54
|
+
isEsbuildBuildFailure(error.cause)
|
|
55
|
+
? new BundleFailedError({ file, errors: error.cause.errors })
|
|
56
|
+
: new ImportFailedError({ file, cause: error.cause });
|
|
57
|
+
|
|
58
|
+
// --- Rendering ---
|
|
59
|
+
|
|
60
|
+
const cross = pipe(AnsiDoc.char("✘"), AnsiDoc.annotate(Ansi.red));
|
|
61
|
+
|
|
62
|
+
const errorGutter = pipe(
|
|
63
|
+
AnsiDoc.char("│"),
|
|
64
|
+
AnsiDoc.annotate(Ansi.red),
|
|
65
|
+
AnsiDoc.render({ style: "pretty" }),
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const withErrorGutterBlock = (output: string): string =>
|
|
69
|
+
pipe(
|
|
70
|
+
String.split(output, "\n"),
|
|
71
|
+
Array.map((line) =>
|
|
72
|
+
pipe(line, String.trim) === "" ? errorGutter : `${errorGutter} ${line}`,
|
|
73
|
+
),
|
|
74
|
+
Array.join("\n"),
|
|
75
|
+
(guttered) => `${errorGutter}\n${guttered}\n${errorGutter}`,
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const formatBuildMessage = (
|
|
79
|
+
error: esbuild.Message | undefined,
|
|
80
|
+
formattedMessage: string,
|
|
81
|
+
): string => {
|
|
82
|
+
const lines = String.split(formattedMessage, "\n");
|
|
83
|
+
const redErrorText = pipe(
|
|
84
|
+
AnsiDoc.text(error?.text ?? ""),
|
|
85
|
+
AnsiDoc.annotate(Ansi.red),
|
|
86
|
+
AnsiDoc.render({ style: "pretty" }),
|
|
87
|
+
);
|
|
88
|
+
const replaced = pipe(
|
|
89
|
+
Array.findFirstIndex(lines, (l) => pipe(l, String.trim, String.isNonEmpty)),
|
|
90
|
+
Option.match({
|
|
91
|
+
onNone: () => lines,
|
|
92
|
+
onSome: (index) => Array.modify(lines, index, () => redErrorText),
|
|
93
|
+
}),
|
|
94
|
+
);
|
|
95
|
+
return pipe(replaced, Array.join("\n"));
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Render a list of esbuild messages into a styled, gutter-prefixed block.
|
|
100
|
+
* Used by both {@link renderBundleFailedError} and the dev-mode esbuild
|
|
101
|
+
* watcher's `onEnd` hook (where the messages don't flow through the
|
|
102
|
+
* tagged-error pipeline).
|
|
103
|
+
*/
|
|
104
|
+
export const formatEsbuildMessages = (
|
|
105
|
+
errors: readonly esbuild.Message[],
|
|
106
|
+
formattedMessages: readonly string[],
|
|
107
|
+
): string =>
|
|
108
|
+
pipe(
|
|
109
|
+
formattedMessages,
|
|
110
|
+
Array.map((message, i) => formatBuildMessage(errors[i], message)),
|
|
111
|
+
Array.join(""),
|
|
112
|
+
String.trimEnd,
|
|
113
|
+
withErrorGutterBlock,
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
const renderImportFailedError = (error: ImportFailedError): AnsiDoc.AnsiDoc => {
|
|
117
|
+
const causeMessage =
|
|
118
|
+
error.cause instanceof Error
|
|
119
|
+
? error.cause.message
|
|
120
|
+
: typeof error.cause === "string"
|
|
121
|
+
? error.cause
|
|
122
|
+
: globalThis.String(error.cause);
|
|
123
|
+
const oneLineCause = pipe(
|
|
124
|
+
String.split(causeMessage, "\n"),
|
|
125
|
+
Array.findFirst((line) => pipe(line, String.trim, String.isNonEmpty)),
|
|
126
|
+
Option.map(String.trim),
|
|
127
|
+
Option.getOrElse(() => "unknown error"),
|
|
128
|
+
);
|
|
129
|
+
return pipe(
|
|
130
|
+
cross,
|
|
131
|
+
AnsiDoc.catWithSpace(
|
|
132
|
+
AnsiDoc.hcat([
|
|
133
|
+
AnsiDoc.text("Failed to load bundled module "),
|
|
134
|
+
formatPathDoc(error.file),
|
|
135
|
+
AnsiDoc.text(
|
|
136
|
+
`: ${oneLineCause}; check the file's top-level imports and side effects.`,
|
|
137
|
+
),
|
|
138
|
+
]),
|
|
139
|
+
),
|
|
140
|
+
);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Render a {@link BundleFailedError} as a multi-line, ANSI-styled string:
|
|
145
|
+
* a one-line `✘ <file>: build errors` header followed by the
|
|
146
|
+
* gutter-prefixed esbuild diagnostic block. Multi-line is appropriate
|
|
147
|
+
* here because a single `BundleFailedError` carries an array of distinct
|
|
148
|
+
* esbuild messages.
|
|
149
|
+
*/
|
|
150
|
+
const renderBundleFailedError = (error: BundleFailedError): string => {
|
|
151
|
+
const messages = error.errors as readonly esbuild.Message[];
|
|
152
|
+
const formatted = esbuild.formatMessagesSync(messages as esbuild.Message[], {
|
|
153
|
+
kind: "error",
|
|
154
|
+
color: true,
|
|
155
|
+
terminalWidth: 80,
|
|
156
|
+
});
|
|
157
|
+
const header = pipe(
|
|
158
|
+
cross,
|
|
159
|
+
AnsiDoc.catWithSpace(
|
|
160
|
+
AnsiDoc.hcat([formatPathDoc(error.file), AnsiDoc.text(": build errors")]),
|
|
161
|
+
),
|
|
162
|
+
AnsiDoc.render({ style: "pretty" }),
|
|
163
|
+
);
|
|
164
|
+
return `${header}\n${formatEsbuildMessages(messages, formatted)}`;
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Render any {@link BuildError} into a styled, ready-to-print string.
|
|
169
|
+
* `ImportFailedError` collapses to a single line; `BundleFailedError`
|
|
170
|
+
* expands to a header plus one diagnostic block per esbuild message.
|
|
171
|
+
*/
|
|
172
|
+
export const renderBuildError = (error: BuildError): string =>
|
|
173
|
+
Match.value(error).pipe(
|
|
174
|
+
Match.tag("BundleFailedError", renderBundleFailedError),
|
|
175
|
+
Match.tag("ImportFailedError", (e) =>
|
|
176
|
+
pipe(renderImportFailedError(e), AnsiDoc.render({ style: "pretty" })),
|
|
177
|
+
),
|
|
178
|
+
Match.exhaustive,
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
export const logBuildError = (error: BuildError) =>
|
|
182
|
+
Effect.sync(() => console.error(renderBuildError(error)));
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Render a flat list of esbuild messages as a single error block with a
|
|
186
|
+
* generic `✘ Build errors` header. Used by dev-mode when multiple
|
|
187
|
+
* watchers surface the same underlying error and we want to log the
|
|
188
|
+
* coalesced set rather than one block per watcher.
|
|
189
|
+
*/
|
|
190
|
+
const renderCoalescedBuildErrors = (
|
|
191
|
+
messages: readonly esbuild.Message[],
|
|
192
|
+
): string => {
|
|
193
|
+
const formatted = esbuild.formatMessagesSync(messages as esbuild.Message[], {
|
|
194
|
+
kind: "error",
|
|
195
|
+
color: true,
|
|
196
|
+
terminalWidth: 80,
|
|
197
|
+
});
|
|
198
|
+
const header = pipe(
|
|
199
|
+
cross,
|
|
200
|
+
AnsiDoc.catWithSpace(AnsiDoc.text("Build errors")),
|
|
201
|
+
AnsiDoc.render({ style: "pretty" }),
|
|
202
|
+
);
|
|
203
|
+
return `${header}\n${formatEsbuildMessages(messages, formatted)}`;
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
export const logCoalescedBuildErrors = (messages: readonly esbuild.Message[]) =>
|
|
207
|
+
Effect.sync(() => {
|
|
208
|
+
if (messages.length === 0) return;
|
|
209
|
+
console.error(renderCoalescedBuildErrors(messages));
|
|
210
|
+
});
|