@confect/cli 9.0.0-next.8 → 9.0.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 +187 -1
- package/dist/Bundler.mjs +1 -4
- package/dist/Bundler.mjs.map +1 -1
- package/dist/CodegenError.mjs +3 -13
- package/dist/CodegenError.mjs.map +1 -1
- package/dist/LeafModule.mjs +9 -22
- package/dist/LeafModule.mjs.map +1 -1
- package/dist/SpecAssemblyNode.mjs +1 -8
- package/dist/SpecAssemblyNode.mjs.map +1 -1
- package/dist/confect/codegen.mjs +26 -69
- package/dist/confect/codegen.mjs.map +1 -1
- package/dist/confect/dev.mjs +0 -3
- package/dist/confect/dev.mjs.map +1 -1
- package/dist/log.mjs +2 -2
- package/dist/log.mjs.map +1 -1
- package/dist/package.mjs +1 -1
- package/dist/templates.mjs +5 -14
- package/dist/templates.mjs.map +1 -1
- package/dist/utils.mjs +32 -22
- package/dist/utils.mjs.map +1 -1
- package/package.json +4 -21
- package/dist/index.d.mts +0 -1
- package/src/BuildError.ts +0 -217
- package/src/Bundler.ts +0 -145
- package/src/CodeBlockWriter.ts +0 -65
- package/src/CodegenError.ts +0 -443
- package/src/ConfectDirectory.ts +0 -45
- package/src/ConvexDirectory.ts +0 -72
- package/src/FunctionPath.ts +0 -27
- package/src/FunctionPaths.ts +0 -107
- package/src/GroupPath.ts +0 -116
- package/src/GroupPaths.ts +0 -7
- package/src/LeafModule.ts +0 -323
- package/src/ProjectRoot.ts +0 -55
- package/src/SpecAssemblyNode.ts +0 -88
- package/src/TableModule.ts +0 -157
- package/src/cliApp.ts +0 -8
- package/src/confect/codegen.ts +0 -908
- package/src/confect/dev.ts +0 -790
- package/src/confect.ts +0 -19
- package/src/index.ts +0 -23
- package/src/log.ts +0 -110
- package/src/templates.ts +0 -633
- package/src/utils.ts +0 -428
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 * as FileSystem from \"@effect/platform/FileSystem\";\nimport * as Path from \"@effect/platform/Path\";\nimport type { PlatformError } from \"@effect/platform/Error\";\nimport { pipe } from \"effect/Function\";\nimport * as Array from \"effect/Array\";\nimport * as Context from \"effect/Context\";\nimport * as Effect from \"effect/Effect\";\nimport * as HashSet from \"effect/HashSet\";\nimport * as Option from \"effect/Option\";\nimport * as Order from \"effect/Order\";\nimport * as Record from \"effect/Record\";\nimport * as Ref from \"effect/Ref\";\nimport * as String from \"effect/String\";\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":"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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 * as FileSystem from \"@effect/platform/FileSystem\";\nimport * as Path from \"@effect/platform/Path\";\nimport type { PlatformError } from \"@effect/platform/Error\";\nimport { pipe } from \"effect/Function\";\nimport * as Array from \"effect/Array\";\nimport * as Context from \"effect/Context\";\nimport * as Effect from \"effect/Effect\";\nimport * as HashSet from \"effect/HashSet\";\nimport * as Option from \"effect/Option\";\nimport * as Order from \"effect/Order\";\nimport * as Record from \"effect/Record\";\nimport * as Ref from \"effect/Ref\";\nimport * as String from \"effect/String\";\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\n/**\n * Compute the module import specifier (relative to `modulePath`) for a group's\n * registry file under `confect/_generated/registeredFunctions/`. The registry\n * path mirrors the group's path one-to-one (see `registeredFunctionsRelativePath`\n * in `LeafModule.ts`) for both Convex and Node groups. Centralizing this here\n * keeps the \"overlapping\" and \"new\" group branches of `generateFunctions` from\n * drifting apart.\n */\nconst registeredFunctionsImportPathForGroup = (\n groupPath: GroupPath.GroupPath,\n modulePath: string,\n) =>\n Effect.gen(function* () {\n const path = yield* Path.Path;\n const confectDirectory = yield* ConfectDirectory.get;\n\n const registeredFunctionsPath =\n path.join(\n confectDirectory,\n \"_generated\",\n \"registeredFunctions\",\n ...groupPath.pathSegments,\n ) + \".ts\";\n\n return yield* toModuleImportPath(\n path.relative(path.dirname(modulePath), registeredFunctionsPath),\n );\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 relativeModulePath = yield* GroupPath.modulePath(groupPath);\n const modulePath = path.join(convexDirectory, relativeModulePath);\n const registeredFunctionsImportPath =\n yield* registeredFunctionsImportPathForGroup(groupPath, modulePath);\n const result = yield* generateGroupModule({\n groupPath,\n functionNames,\n registeredFunctionsImportPath,\n useNode: group.runtime === \"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 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 registeredFunctionsImportPath =\n yield* registeredFunctionsImportPathForGroup(groupPath, modulePath);\n\n yield* Effect.logDebug(`Generating group ${groupPath}...`);\n yield* generateGroupModule({\n groupPath,\n functionNames,\n registeredFunctionsImportPath,\n useNode: group.runtime === \"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":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@confect/cli",
|
|
3
3
|
"description": "Developer tooling for codegen and sync",
|
|
4
|
-
"version": "9.0.0
|
|
4
|
+
"version": "9.0.0",
|
|
5
5
|
"author": "RJ Dellecese",
|
|
6
6
|
"bin": {
|
|
7
7
|
"confect": "./dist/index.mjs"
|
|
@@ -23,15 +23,11 @@
|
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@effect/language-service": "0.86.1",
|
|
25
25
|
"@effect/vitest": "0.29.0",
|
|
26
|
-
"@eslint/js": "10.0.1",
|
|
27
26
|
"@types/node": "25.3.3",
|
|
28
27
|
"@vitest/coverage-v8": "3.2.4",
|
|
29
28
|
"effect": "3.21.2",
|
|
30
|
-
"eslint": "10.0.2",
|
|
31
|
-
"prettier": "3.8.1",
|
|
32
29
|
"tsdown": "0.20.3",
|
|
33
30
|
"typescript": "5.9.3",
|
|
34
|
-
"typescript-eslint": "8.56.1",
|
|
35
31
|
"vite": "7.3.1",
|
|
36
32
|
"vite-tsconfig-paths": "6.1.1",
|
|
37
33
|
"vitest": "3.2.4"
|
|
@@ -40,10 +36,6 @@
|
|
|
40
36
|
"node": ">=22"
|
|
41
37
|
},
|
|
42
38
|
"exports": {
|
|
43
|
-
".": {
|
|
44
|
-
"types": "./dist/index.d.ts",
|
|
45
|
-
"default": "./dist/index.js"
|
|
46
|
-
},
|
|
47
39
|
"./package.json": "./package.json"
|
|
48
40
|
},
|
|
49
41
|
"files": [
|
|
@@ -51,8 +43,7 @@
|
|
|
51
43
|
"LICENSE",
|
|
52
44
|
"README.md",
|
|
53
45
|
"dist",
|
|
54
|
-
"package.json"
|
|
55
|
-
"src"
|
|
46
|
+
"package.json"
|
|
56
47
|
],
|
|
57
48
|
"homepage": "https://confect.dev",
|
|
58
49
|
"keywords": [
|
|
@@ -61,27 +52,19 @@
|
|
|
61
52
|
"effect"
|
|
62
53
|
],
|
|
63
54
|
"license": "ISC",
|
|
64
|
-
"main": "./dist/index.js",
|
|
65
|
-
"module": "./dist/index.js",
|
|
66
55
|
"peerDependencies": {
|
|
67
56
|
"effect": "^3.21.2",
|
|
68
|
-
"@confect/core": "^9.0.0
|
|
69
|
-
"@confect/server": "^9.0.0
|
|
57
|
+
"@confect/core": "^9.0.0",
|
|
58
|
+
"@confect/server": "^9.0.0"
|
|
70
59
|
},
|
|
71
60
|
"repository": {
|
|
72
61
|
"type": "git",
|
|
73
62
|
"url": "https://github.com/rjdellecese/confect.git"
|
|
74
63
|
},
|
|
75
|
-
"sideEffects": false,
|
|
76
64
|
"type": "module",
|
|
77
|
-
"types": "./dist/index.d.ts",
|
|
78
65
|
"scripts": {
|
|
79
|
-
"build": "tsdown --config-loader unrun",
|
|
80
66
|
"clean": "rm -rf dist coverage node_modules",
|
|
81
67
|
"dev": "tsdown --watch --config-loader unrun",
|
|
82
|
-
"fix": "prettier --write . && eslint --fix . --max-warnings=0",
|
|
83
|
-
"format": "prettier --check .",
|
|
84
|
-
"lint": "eslint . --max-warnings=0",
|
|
85
68
|
"test": "vitest run",
|
|
86
69
|
"typecheck": "tsc --noEmit --project tsconfig.json"
|
|
87
70
|
}
|
package/dist/index.d.mts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { };
|
package/src/BuildError.ts
DELETED
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
import * as Ansi from "@effect/printer-ansi/Ansi";
|
|
2
|
-
import * as AnsiDoc from "@effect/printer-ansi/AnsiDoc";
|
|
3
|
-
import { pipe } from "effect/Function";
|
|
4
|
-
import * as Array from "effect/Array";
|
|
5
|
-
import * as Effect from "effect/Effect";
|
|
6
|
-
import * as Match from "effect/Match";
|
|
7
|
-
import * as Option from "effect/Option";
|
|
8
|
-
import * as Schema from "effect/Schema";
|
|
9
|
-
import * as String from "effect/String";
|
|
10
|
-
import * as esbuild from "esbuild";
|
|
11
|
-
import { formatPathDoc } from "./log";
|
|
12
|
-
|
|
13
|
-
// --- Variants ---
|
|
14
|
-
|
|
15
|
-
export class BundleFailedError extends Schema.TaggedError<BundleFailedError>()(
|
|
16
|
-
"BundleFailedError",
|
|
17
|
-
{
|
|
18
|
-
file: Schema.String,
|
|
19
|
-
errors: Schema.Array(Schema.Unknown),
|
|
20
|
-
},
|
|
21
|
-
) {}
|
|
22
|
-
|
|
23
|
-
export class ImportFailedError extends Schema.TaggedError<ImportFailedError>()(
|
|
24
|
-
"ImportFailedError",
|
|
25
|
-
{
|
|
26
|
-
file: Schema.String,
|
|
27
|
-
cause: Schema.Unknown,
|
|
28
|
-
},
|
|
29
|
-
) {}
|
|
30
|
-
|
|
31
|
-
export const BuildError = Schema.Union(BundleFailedError, ImportFailedError);
|
|
32
|
-
export type BuildError = typeof BuildError.Type;
|
|
33
|
-
|
|
34
|
-
export const isBuildError = (error: unknown): error is BuildError =>
|
|
35
|
-
Schema.is(BuildError)(error);
|
|
36
|
-
|
|
37
|
-
// --- Bundler adapter ---
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Internal failure produced by the esbuild bundle/import pipeline. Always
|
|
41
|
-
* remapped to a {@link BuildError} (which carries enough context for the CLI
|
|
42
|
-
* to render it) before reaching a user-surface boundary.
|
|
43
|
-
*/
|
|
44
|
-
export class BundlerError extends Schema.TaggedError<BundlerError>()(
|
|
45
|
-
"BundlerError",
|
|
46
|
-
{
|
|
47
|
-
cause: Schema.Unknown,
|
|
48
|
-
},
|
|
49
|
-
) {}
|
|
50
|
-
|
|
51
|
-
const isEsbuildBuildFailure = (error: unknown): error is esbuild.BuildFailure =>
|
|
52
|
-
typeof error === "object" &&
|
|
53
|
-
error !== null &&
|
|
54
|
-
"errors" in error &&
|
|
55
|
-
globalThis.Array.isArray((error as esbuild.BuildFailure).errors);
|
|
56
|
-
|
|
57
|
-
export const fromBundlerError = (
|
|
58
|
-
file: string,
|
|
59
|
-
error: BundlerError,
|
|
60
|
-
): BuildError =>
|
|
61
|
-
isEsbuildBuildFailure(error.cause)
|
|
62
|
-
? new BundleFailedError({ file, errors: error.cause.errors })
|
|
63
|
-
: new ImportFailedError({ file, cause: error.cause });
|
|
64
|
-
|
|
65
|
-
// --- Rendering ---
|
|
66
|
-
|
|
67
|
-
const cross = pipe(AnsiDoc.char("✘"), AnsiDoc.annotate(Ansi.red));
|
|
68
|
-
|
|
69
|
-
const errorGutter = pipe(
|
|
70
|
-
AnsiDoc.char("│"),
|
|
71
|
-
AnsiDoc.annotate(Ansi.red),
|
|
72
|
-
AnsiDoc.render({ style: "pretty" }),
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
const withErrorGutterBlock = (output: string): string =>
|
|
76
|
-
pipe(
|
|
77
|
-
String.split(output, "\n"),
|
|
78
|
-
Array.map((line) =>
|
|
79
|
-
pipe(line, String.trim) === "" ? errorGutter : `${errorGutter} ${line}`,
|
|
80
|
-
),
|
|
81
|
-
Array.join("\n"),
|
|
82
|
-
(guttered) => `${errorGutter}\n${guttered}\n${errorGutter}`,
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
const formatBuildMessage = (
|
|
86
|
-
error: esbuild.Message | undefined,
|
|
87
|
-
formattedMessage: string,
|
|
88
|
-
): string => {
|
|
89
|
-
const lines = String.split(formattedMessage, "\n");
|
|
90
|
-
const redErrorText = pipe(
|
|
91
|
-
AnsiDoc.text(error?.text ?? ""),
|
|
92
|
-
AnsiDoc.annotate(Ansi.red),
|
|
93
|
-
AnsiDoc.render({ style: "pretty" }),
|
|
94
|
-
);
|
|
95
|
-
const replaced = pipe(
|
|
96
|
-
Array.findFirstIndex(lines, (l) => pipe(l, String.trim, String.isNonEmpty)),
|
|
97
|
-
Option.match({
|
|
98
|
-
onNone: () => lines,
|
|
99
|
-
onSome: (index) => Array.modify(lines, index, () => redErrorText),
|
|
100
|
-
}),
|
|
101
|
-
);
|
|
102
|
-
return pipe(replaced, Array.join("\n"));
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Render a list of esbuild messages into a styled, gutter-prefixed block.
|
|
107
|
-
* Used by both {@link renderBundleFailedError} and the dev-mode esbuild
|
|
108
|
-
* watcher's `onEnd` hook (where the messages don't flow through the
|
|
109
|
-
* tagged-error pipeline).
|
|
110
|
-
*/
|
|
111
|
-
export const formatEsbuildMessages = (
|
|
112
|
-
errors: readonly esbuild.Message[],
|
|
113
|
-
formattedMessages: readonly string[],
|
|
114
|
-
): string =>
|
|
115
|
-
pipe(
|
|
116
|
-
formattedMessages,
|
|
117
|
-
Array.map((message, i) => formatBuildMessage(errors[i], message)),
|
|
118
|
-
Array.join(""),
|
|
119
|
-
String.trimEnd,
|
|
120
|
-
withErrorGutterBlock,
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
const renderImportFailedError = (error: ImportFailedError): AnsiDoc.AnsiDoc => {
|
|
124
|
-
const causeMessage =
|
|
125
|
-
error.cause instanceof Error
|
|
126
|
-
? error.cause.message
|
|
127
|
-
: typeof error.cause === "string"
|
|
128
|
-
? error.cause
|
|
129
|
-
: globalThis.String(error.cause);
|
|
130
|
-
const oneLineCause = pipe(
|
|
131
|
-
String.split(causeMessage, "\n"),
|
|
132
|
-
Array.findFirst((line) => pipe(line, String.trim, String.isNonEmpty)),
|
|
133
|
-
Option.map(String.trim),
|
|
134
|
-
Option.getOrElse(() => "unknown error"),
|
|
135
|
-
);
|
|
136
|
-
return pipe(
|
|
137
|
-
cross,
|
|
138
|
-
AnsiDoc.catWithSpace(
|
|
139
|
-
AnsiDoc.hcat([
|
|
140
|
-
AnsiDoc.text("Failed to load bundled module "),
|
|
141
|
-
formatPathDoc(error.file),
|
|
142
|
-
AnsiDoc.text(
|
|
143
|
-
`: ${oneLineCause}; check the file's top-level imports and side effects.`,
|
|
144
|
-
),
|
|
145
|
-
]),
|
|
146
|
-
),
|
|
147
|
-
);
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Render a {@link BundleFailedError} as a multi-line, ANSI-styled string:
|
|
152
|
-
* a one-line `✘ <file>: build errors` header followed by the
|
|
153
|
-
* gutter-prefixed esbuild diagnostic block. Multi-line is appropriate
|
|
154
|
-
* here because a single `BundleFailedError` carries an array of distinct
|
|
155
|
-
* esbuild messages.
|
|
156
|
-
*/
|
|
157
|
-
const renderBundleFailedError = (error: BundleFailedError): string => {
|
|
158
|
-
const messages = error.errors as readonly esbuild.Message[];
|
|
159
|
-
const formatted = esbuild.formatMessagesSync(messages as esbuild.Message[], {
|
|
160
|
-
kind: "error",
|
|
161
|
-
color: true,
|
|
162
|
-
terminalWidth: 80,
|
|
163
|
-
});
|
|
164
|
-
const header = pipe(
|
|
165
|
-
cross,
|
|
166
|
-
AnsiDoc.catWithSpace(
|
|
167
|
-
AnsiDoc.hcat([formatPathDoc(error.file), AnsiDoc.text(": build errors")]),
|
|
168
|
-
),
|
|
169
|
-
AnsiDoc.render({ style: "pretty" }),
|
|
170
|
-
);
|
|
171
|
-
return `${header}\n${formatEsbuildMessages(messages, formatted)}`;
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Render any {@link BuildError} into a styled, ready-to-print string.
|
|
176
|
-
* `ImportFailedError` collapses to a single line; `BundleFailedError`
|
|
177
|
-
* expands to a header plus one diagnostic block per esbuild message.
|
|
178
|
-
*/
|
|
179
|
-
export const renderBuildError = (error: BuildError): string =>
|
|
180
|
-
Match.value(error).pipe(
|
|
181
|
-
Match.tag("BundleFailedError", renderBundleFailedError),
|
|
182
|
-
Match.tag("ImportFailedError", (e) =>
|
|
183
|
-
pipe(renderImportFailedError(e), AnsiDoc.render({ style: "pretty" })),
|
|
184
|
-
),
|
|
185
|
-
Match.exhaustive,
|
|
186
|
-
);
|
|
187
|
-
|
|
188
|
-
export const logBuildError = (error: BuildError) =>
|
|
189
|
-
Effect.sync(() => console.error(renderBuildError(error)));
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Render a flat list of esbuild messages as a single error block with a
|
|
193
|
-
* generic `✘ Build errors` header. Used by dev-mode when multiple
|
|
194
|
-
* watchers surface the same underlying error and we want to log the
|
|
195
|
-
* coalesced set rather than one block per watcher.
|
|
196
|
-
*/
|
|
197
|
-
const renderCoalescedBuildErrors = (
|
|
198
|
-
messages: readonly esbuild.Message[],
|
|
199
|
-
): string => {
|
|
200
|
-
const formatted = esbuild.formatMessagesSync(messages as esbuild.Message[], {
|
|
201
|
-
kind: "error",
|
|
202
|
-
color: true,
|
|
203
|
-
terminalWidth: 80,
|
|
204
|
-
});
|
|
205
|
-
const header = pipe(
|
|
206
|
-
cross,
|
|
207
|
-
AnsiDoc.catWithSpace(AnsiDoc.text("Build errors")),
|
|
208
|
-
AnsiDoc.render({ style: "pretty" }),
|
|
209
|
-
);
|
|
210
|
-
return `${header}\n${formatEsbuildMessages(messages, formatted)}`;
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
export const logCoalescedBuildErrors = (messages: readonly esbuild.Message[]) =>
|
|
214
|
-
Effect.sync(() => {
|
|
215
|
-
if (messages.length === 0) return;
|
|
216
|
-
console.error(renderCoalescedBuildErrors(messages));
|
|
217
|
-
});
|
package/src/Bundler.ts
DELETED
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
import { dirname, isAbsolute, resolve } from "node:path";
|
|
2
|
-
import * as Path from "@effect/platform/Path";
|
|
3
|
-
import { bundleRequire } from "bundle-require";
|
|
4
|
-
import { pipe } from "effect/Function";
|
|
5
|
-
import * as Array from "effect/Array";
|
|
6
|
-
import * as Effect from "effect/Effect";
|
|
7
|
-
import * as Option from "effect/Option";
|
|
8
|
-
import type * as esbuild from "esbuild";
|
|
9
|
-
import { BundlerError } from "./BuildError";
|
|
10
|
-
|
|
11
|
-
export interface Bundled {
|
|
12
|
-
readonly module: any;
|
|
13
|
-
readonly metafile: esbuild.Metafile;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* `bundle-require` sets `absWorkingDir: cwd` on the underlying esbuild build,
|
|
18
|
-
* so the metafile's input keys (and each input's `imports[].path`) are stored
|
|
19
|
-
* relative to that cwd. Callers reach for the metafile with absolute paths
|
|
20
|
-
* (e.g. {@link directlyImports}), so we normalize every key/import path to
|
|
21
|
-
* absolute up front. That way the lookup logic stays oblivious to whatever
|
|
22
|
-
* cwd was used during bundling.
|
|
23
|
-
*/
|
|
24
|
-
const absolutizeMetafile = (
|
|
25
|
-
metafile: esbuild.Metafile,
|
|
26
|
-
cwd: string,
|
|
27
|
-
): esbuild.Metafile => {
|
|
28
|
-
const absolutize = (p: string) => (isAbsolute(p) ? p : resolve(cwd, p));
|
|
29
|
-
const inputs: esbuild.Metafile["inputs"] = {};
|
|
30
|
-
for (const [key, value] of Object.entries(metafile.inputs)) {
|
|
31
|
-
inputs[absolutize(key)] = {
|
|
32
|
-
...value,
|
|
33
|
-
imports: value.imports.map((i) => ({ ...i, path: absolutize(i.path) })),
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
const outputs: esbuild.Metafile["outputs"] = {};
|
|
37
|
-
for (const [key, value] of Object.entries(metafile.outputs)) {
|
|
38
|
-
outputs[absolutize(key)] = value;
|
|
39
|
-
}
|
|
40
|
-
return { inputs, outputs };
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Bundle a TypeScript entry point with esbuild via {@link bundleRequire} and
|
|
45
|
-
* import the result. `bundle-require` writes a temp `.mjs` next to the source,
|
|
46
|
-
* `import()`s it, and deletes it — so bare-specifier externals (third-party
|
|
47
|
-
* packages, workspace deps) resolve through the user's normal `node_modules`
|
|
48
|
-
* walk, and tsconfig `paths` aliases stay inside the bundle.
|
|
49
|
-
*
|
|
50
|
-
* `cwd` is set to the entry's directory so `bundle-require`'s `tsconfig.json`
|
|
51
|
-
* discovery (which walks upward from `cwd`) lands on the project's tsconfig
|
|
52
|
-
* regardless of where `confect codegen` was invoked from, and so esbuild
|
|
53
|
-
* resolves relative imports against the entry's location.
|
|
54
|
-
*
|
|
55
|
-
* The returned pair carries both the imported module and the esbuild metafile
|
|
56
|
-
* so callers can inspect the import graph (see {@link directlyImports}); the
|
|
57
|
-
* metafile is captured via a small `onEnd` plugin because `bundle-require`
|
|
58
|
-
* itself only exposes a flat `dependencies: string[]`.
|
|
59
|
-
*/
|
|
60
|
-
export const bundle = (
|
|
61
|
-
entryPoint: string,
|
|
62
|
-
): Effect.Effect<Bundled, BundlerError> =>
|
|
63
|
-
Effect.gen(function* () {
|
|
64
|
-
let metafile: esbuild.Metafile | undefined;
|
|
65
|
-
const captureMetafile: esbuild.Plugin = {
|
|
66
|
-
name: "confect:capture-metafile",
|
|
67
|
-
setup(build) {
|
|
68
|
-
build.onEnd((result) => {
|
|
69
|
-
metafile = result.metafile;
|
|
70
|
-
});
|
|
71
|
-
},
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
const cwd = dirname(entryPoint);
|
|
75
|
-
const result = yield* Effect.tryPromise({
|
|
76
|
-
try: () =>
|
|
77
|
-
bundleRequire({
|
|
78
|
-
filepath: entryPoint,
|
|
79
|
-
cwd,
|
|
80
|
-
format: "esm",
|
|
81
|
-
esbuildOptions: {
|
|
82
|
-
plugins: [captureMetafile],
|
|
83
|
-
logLevel: "silent",
|
|
84
|
-
},
|
|
85
|
-
}),
|
|
86
|
-
catch: (cause) => new BundlerError({ cause }),
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
if (!metafile) {
|
|
90
|
-
return yield* Effect.dieMessage("esbuild metafile missing");
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return { module: result.mod, metafile: absolutizeMetafile(metafile, cwd) };
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
const findMetafileInputKey = (
|
|
97
|
-
metafile: esbuild.Metafile,
|
|
98
|
-
absolutePath: string,
|
|
99
|
-
) =>
|
|
100
|
-
Effect.gen(function* () {
|
|
101
|
-
const path = yield* Path.Path;
|
|
102
|
-
const resolved = path.resolve(absolutePath);
|
|
103
|
-
return Array.findFirst(
|
|
104
|
-
Object.keys(metafile.inputs),
|
|
105
|
-
(key) => path.resolve(key) === resolved,
|
|
106
|
-
);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Returns `true` when the module bundled from `sourceAbsolutePath` declares a
|
|
111
|
-
* direct import of `targetAbsolutePath` (according to the bundle's esbuild
|
|
112
|
-
* metafile). Returns `false` if either path is missing from the metafile.
|
|
113
|
-
*/
|
|
114
|
-
export const directlyImports = (
|
|
115
|
-
bundled: Bundled,
|
|
116
|
-
sourceAbsolutePath: string,
|
|
117
|
-
targetAbsolutePath: string,
|
|
118
|
-
) =>
|
|
119
|
-
Effect.gen(function* () {
|
|
120
|
-
const path = yield* Path.Path;
|
|
121
|
-
const sourceKey = yield* findMetafileInputKey(
|
|
122
|
-
bundled.metafile,
|
|
123
|
-
sourceAbsolutePath,
|
|
124
|
-
);
|
|
125
|
-
const targetKey = yield* findMetafileInputKey(
|
|
126
|
-
bundled.metafile,
|
|
127
|
-
targetAbsolutePath,
|
|
128
|
-
);
|
|
129
|
-
|
|
130
|
-
return pipe(
|
|
131
|
-
Option.all([sourceKey, targetKey]),
|
|
132
|
-
Option.flatMap(([sourceKey_, targetKey_]) =>
|
|
133
|
-
Option.fromNullable(bundled.metafile.inputs[sourceKey_]).pipe(
|
|
134
|
-
Option.map((sourceInput) => {
|
|
135
|
-
const targetResolved = path.resolve(targetKey_);
|
|
136
|
-
return sourceInput.imports.some(
|
|
137
|
-
(importedFile) =>
|
|
138
|
-
path.resolve(importedFile.path) === targetResolved,
|
|
139
|
-
);
|
|
140
|
-
}),
|
|
141
|
-
),
|
|
142
|
-
),
|
|
143
|
-
Option.getOrElse(() => false),
|
|
144
|
-
);
|
|
145
|
-
});
|
package/src/CodeBlockWriter.ts
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import type { Options as CodeBlockWriterOptions } from "code-block-writer";
|
|
2
|
-
import CodeBlockWriter_ from "code-block-writer";
|
|
3
|
-
import * as Effect from "effect/Effect";
|
|
4
|
-
|
|
5
|
-
export class CodeBlockWriter {
|
|
6
|
-
private readonly writer: CodeBlockWriter_;
|
|
7
|
-
|
|
8
|
-
constructor(opts?: Partial<CodeBlockWriterOptions>) {
|
|
9
|
-
this.writer = new CodeBlockWriter_(opts);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
indent<E = never, R = never>(
|
|
13
|
-
effect: Effect.Effect<void, E, R>,
|
|
14
|
-
): Effect.Effect<void, E, R> {
|
|
15
|
-
return Effect.gen(this, function* () {
|
|
16
|
-
const indentationLevel = this.writer.getIndentationLevel();
|
|
17
|
-
this.writer.setIndentationLevel(indentationLevel + 1);
|
|
18
|
-
yield* effect;
|
|
19
|
-
this.writer.setIndentationLevel(indentationLevel);
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
writeLine<E = never, R = never>(line: string): Effect.Effect<void, E, R> {
|
|
24
|
-
return Effect.sync(() => {
|
|
25
|
-
this.writer.writeLine(line);
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
write<E = never, R = never>(text: string): Effect.Effect<void, E, R> {
|
|
30
|
-
return Effect.sync(() => {
|
|
31
|
-
this.writer.write(text);
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
quote<E = never, R = never>(text: string): Effect.Effect<void, E, R> {
|
|
36
|
-
return Effect.sync(() => {
|
|
37
|
-
this.writer.quote(text);
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
conditionalWriteLine<E = never, R = never>(
|
|
42
|
-
condition: boolean,
|
|
43
|
-
text: string,
|
|
44
|
-
): Effect.Effect<void, E, R> {
|
|
45
|
-
return Effect.sync(() => {
|
|
46
|
-
this.writer.conditionalWriteLine(condition, text);
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
newLine<E = never, R = never>(): Effect.Effect<void, E, R> {
|
|
51
|
-
return Effect.sync(() => {
|
|
52
|
-
this.writer.newLine();
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
blankLine<E = never, R = never>(): Effect.Effect<void, E, R> {
|
|
57
|
-
return Effect.sync(() => {
|
|
58
|
-
this.writer.blankLine();
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
toString<E = never, R = never>(): Effect.Effect<string, E, R> {
|
|
63
|
-
return Effect.sync(() => this.writer.toString());
|
|
64
|
-
}
|
|
65
|
-
}
|