@confect/cli 9.0.0-next.9 → 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 +158 -1
- package/dist/Bundler.mjs +1 -4
- package/dist/Bundler.mjs.map +1 -1
- package/dist/confect/codegen.mjs.map +1 -1
- package/dist/log.mjs +2 -2
- package/dist/log.mjs.map +1 -1
- package/dist/package.mjs +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 -407
- 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 -305
- package/src/ProjectRoot.ts +0 -55
- package/src/SpecAssemblyNode.ts +0 -75
- package/src/TableModule.ts +0 -157
- package/src/cliApp.ts +0 -8
- package/src/confect/codegen.ts +0 -847
- package/src/confect/dev.ts +0 -785
- package/src/confect.ts +0 -19
- package/src/index.ts +0 -23
- package/src/log.ts +0 -110
- package/src/templates.ts +0 -614
- package/src/utils.ts +0 -435
package/src/GroupPath.ts
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import { type GroupSpec, type Spec } from "@confect/core";
|
|
2
|
-
import * as Path from "@effect/platform/Path";
|
|
3
|
-
import { pipe } from "effect/Function";
|
|
4
|
-
import * as Array from "effect/Array";
|
|
5
|
-
import * as Data from "effect/Data";
|
|
6
|
-
import * as Effect from "effect/Effect";
|
|
7
|
-
import * as Option from "effect/Option";
|
|
8
|
-
import * as Record from "effect/Record";
|
|
9
|
-
import * as Schema from "effect/Schema";
|
|
10
|
-
import * as String from "effect/String";
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* The path to a group in the Confect API.
|
|
14
|
-
*/
|
|
15
|
-
export class GroupPath extends Schema.Class<GroupPath>("GroupPath")({
|
|
16
|
-
pathSegments: Schema.Data(Schema.NonEmptyArray(Schema.NonEmptyString)),
|
|
17
|
-
}) {}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Create a GroupPath from path segments.
|
|
21
|
-
*/
|
|
22
|
-
export const make = (pathSegments: readonly [string, ...string[]]): GroupPath =>
|
|
23
|
-
GroupPath.make({ pathSegments: Data.array(pathSegments) });
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Append a group name to a GroupPath to create a new GroupPath.
|
|
27
|
-
*/
|
|
28
|
-
export const append = (groupPath: GroupPath, groupName: string): GroupPath =>
|
|
29
|
-
make([...groupPath.pathSegments, groupName]);
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Expects a path string of the form `./group1/group2.ts`, relative to the Convex functions directory.
|
|
33
|
-
*/
|
|
34
|
-
export const fromGroupModulePath = (groupModulePath: string) =>
|
|
35
|
-
Effect.gen(function* () {
|
|
36
|
-
const path = yield* Path.Path;
|
|
37
|
-
|
|
38
|
-
const { dir, name, ext } = path.parse(groupModulePath);
|
|
39
|
-
|
|
40
|
-
if (ext === ".ts") {
|
|
41
|
-
const dirSegments = Array.filter(
|
|
42
|
-
String.split(dir, path.sep),
|
|
43
|
-
String.isNonEmpty,
|
|
44
|
-
);
|
|
45
|
-
yield* Effect.logDebug(Array.append(dirSegments, name));
|
|
46
|
-
return make(Array.append(dirSegments, name));
|
|
47
|
-
} else {
|
|
48
|
-
return yield* new GroupModulePathIsNotATypeScriptFileError({
|
|
49
|
-
path: groupModulePath,
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Get the module path for a group, relative to the Convex functions directory.
|
|
56
|
-
*/
|
|
57
|
-
export const modulePath = (groupPath: GroupPath) =>
|
|
58
|
-
Effect.gen(function* () {
|
|
59
|
-
const path = yield* Path.Path;
|
|
60
|
-
|
|
61
|
-
return path.join(...groupPath.pathSegments) + ".ts";
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
export const getGroupSpec = (
|
|
65
|
-
spec: Spec.AnyWithProps,
|
|
66
|
-
groupPath: GroupPath,
|
|
67
|
-
): Option.Option<GroupSpec.AnyWithProps> =>
|
|
68
|
-
pipe(
|
|
69
|
-
groupPath.pathSegments,
|
|
70
|
-
Array.matchLeft({
|
|
71
|
-
onEmpty: () => Option.none(),
|
|
72
|
-
onNonEmpty: (head, tail) =>
|
|
73
|
-
pipe(
|
|
74
|
-
Record.get(spec.groups, head),
|
|
75
|
-
Option.flatMap((group) =>
|
|
76
|
-
Array.isNonEmptyArray(tail)
|
|
77
|
-
? getGroupSpecHelper(group, tail)
|
|
78
|
-
: Option.some(group),
|
|
79
|
-
),
|
|
80
|
-
),
|
|
81
|
-
}),
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
const getGroupSpecHelper = (
|
|
85
|
-
group: GroupSpec.AnyWithProps,
|
|
86
|
-
remainingPath: ReadonlyArray<string>,
|
|
87
|
-
): Option.Option<GroupSpec.AnyWithProps> =>
|
|
88
|
-
pipe(
|
|
89
|
-
remainingPath,
|
|
90
|
-
Array.matchLeft({
|
|
91
|
-
onEmpty: () => Option.some(group),
|
|
92
|
-
onNonEmpty: (head, tail) =>
|
|
93
|
-
pipe(
|
|
94
|
-
Record.get(group.groups, head),
|
|
95
|
-
Option.flatMap((subGroup) =>
|
|
96
|
-
Array.isNonEmptyArray(tail)
|
|
97
|
-
? getGroupSpecHelper(subGroup, tail)
|
|
98
|
-
: Option.some(subGroup),
|
|
99
|
-
),
|
|
100
|
-
),
|
|
101
|
-
}),
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
export const toString = (groupPath: GroupPath) =>
|
|
105
|
-
Array.join(groupPath.pathSegments, ".");
|
|
106
|
-
|
|
107
|
-
export class GroupModulePathIsNotATypeScriptFileError extends Schema.TaggedError<GroupModulePathIsNotATypeScriptFileError>()(
|
|
108
|
-
"GroupModulePathIsNotATypeScriptFileError",
|
|
109
|
-
{
|
|
110
|
-
path: Schema.NonEmptyString,
|
|
111
|
-
},
|
|
112
|
-
) {
|
|
113
|
-
override get message(): string {
|
|
114
|
-
return `Expected group module path to end with .ts, got ${this.path}`;
|
|
115
|
-
}
|
|
116
|
-
}
|
package/src/GroupPaths.ts
DELETED
package/src/LeafModule.ts
DELETED
|
@@ -1,305 +0,0 @@
|
|
|
1
|
-
import { GroupSpec, Registry } from "@confect/core";
|
|
2
|
-
import * as GroupImpl from "@confect/server/GroupImpl";
|
|
3
|
-
import * as FileSystem from "@effect/platform/FileSystem";
|
|
4
|
-
import * as Path from "@effect/platform/Path";
|
|
5
|
-
import type { Context } from "effect";
|
|
6
|
-
import * as Array from "effect/Array";
|
|
7
|
-
import * as Effect from "effect/Effect";
|
|
8
|
-
import * as Layer from "effect/Layer";
|
|
9
|
-
import * as Option from "effect/Option";
|
|
10
|
-
import * as Ref from "effect/Ref";
|
|
11
|
-
import * as String from "effect/String";
|
|
12
|
-
import { fromBundlerError } from "./BuildError";
|
|
13
|
-
import * as Bundler from "./Bundler";
|
|
14
|
-
import {
|
|
15
|
-
ImplMissingDefaultLayerError,
|
|
16
|
-
ImplMissingFunctionsError,
|
|
17
|
-
ImplMissingSpecImportError,
|
|
18
|
-
ImplNotFinalizedError,
|
|
19
|
-
SpecMissingDefaultGroupSpecError,
|
|
20
|
-
} from "./CodegenError";
|
|
21
|
-
import { ConfectDirectory } from "./ConfectDirectory";
|
|
22
|
-
import { removePathExtension } from "./utils";
|
|
23
|
-
|
|
24
|
-
export interface LeafModule {
|
|
25
|
-
readonly relativePath: string;
|
|
26
|
-
readonly pathSegments: readonly [string, ...string[]];
|
|
27
|
-
readonly groupPathDot: string;
|
|
28
|
-
readonly exportName: string;
|
|
29
|
-
/**
|
|
30
|
-
* The runtime declared by the group's spec — `"Node"` for
|
|
31
|
-
* `GroupSpec.makeNode()`, `"Convex"` for `GroupSpec.make()`. `None` while the
|
|
32
|
-
* runtime is unknown: discovery (`toLeafModule`) works from the file path alone,
|
|
33
|
-
* which does not determine the runtime, so this is filled in once the spec has
|
|
34
|
-
* been bundled and validated (see `validateSpec`).
|
|
35
|
-
*/
|
|
36
|
-
readonly runtime: Option.Option<"Convex" | "Node">;
|
|
37
|
-
readonly specImportPath: string;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export const SPEC_SUFFIX = ".spec.ts";
|
|
41
|
-
export const IMPL_SUFFIX = ".impl.ts";
|
|
42
|
-
|
|
43
|
-
const swapModuleSuffix = (
|
|
44
|
-
relativePath: string,
|
|
45
|
-
fromSuffix: string,
|
|
46
|
-
toSuffix: string,
|
|
47
|
-
) =>
|
|
48
|
-
Effect.gen(function* () {
|
|
49
|
-
const path = yield* Path.Path;
|
|
50
|
-
const { dir, name, ext } = path.parse(relativePath);
|
|
51
|
-
if (ext !== ".ts" || !name.endsWith(fromSuffix.slice(0, -".ts".length))) {
|
|
52
|
-
return relativePath;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const stem = name.slice(0, -fromSuffix.slice(0, -".ts".length).length);
|
|
56
|
-
const nextName = `${stem}${toSuffix.slice(0, -".ts".length)}`;
|
|
57
|
-
return dir.length > 0
|
|
58
|
-
? path.join(dir, `${nextName}${ext}`)
|
|
59
|
-
: `${nextName}${ext}`;
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
export const isLeafSpecPath = (relativePath: string) =>
|
|
63
|
-
relativePath.endsWith(SPEC_SUFFIX);
|
|
64
|
-
|
|
65
|
-
export const isLeafImplPath = (relativePath: string) =>
|
|
66
|
-
relativePath.endsWith(IMPL_SUFFIX);
|
|
67
|
-
|
|
68
|
-
export const exportNameFromModulePath = (relativePath: string) =>
|
|
69
|
-
Effect.gen(function* () {
|
|
70
|
-
const path = yield* Path.Path;
|
|
71
|
-
const { name, ext } = path.parse(relativePath);
|
|
72
|
-
if (ext !== ".ts") {
|
|
73
|
-
return name;
|
|
74
|
-
}
|
|
75
|
-
return name.endsWith(".spec") ? name.slice(0, -".spec".length) : name;
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
export const groupPathFromRelativeModulePath = (relativePath: string) =>
|
|
79
|
-
Effect.gen(function* () {
|
|
80
|
-
const path = yield* Path.Path;
|
|
81
|
-
const { dir, name, ext } = path.parse(relativePath);
|
|
82
|
-
const stem =
|
|
83
|
-
ext === ".ts" && name.endsWith(".spec")
|
|
84
|
-
? name.slice(0, -".spec".length)
|
|
85
|
-
: name;
|
|
86
|
-
const dirSegments = Array.filter(
|
|
87
|
-
String.split(dir, path.sep),
|
|
88
|
-
String.isNonEmpty,
|
|
89
|
-
);
|
|
90
|
-
const pathSegments = Array.append(dirSegments, stem) as [
|
|
91
|
-
string,
|
|
92
|
-
...string[],
|
|
93
|
-
];
|
|
94
|
-
return {
|
|
95
|
-
pathSegments,
|
|
96
|
-
groupPathDot: Array.join(pathSegments, "."),
|
|
97
|
-
};
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
export const specImportPathFromGenerated = (specRelativePath: string) =>
|
|
101
|
-
Effect.gen(function* () {
|
|
102
|
-
const withoutExt = yield* removePathExtension(specRelativePath);
|
|
103
|
-
return `../${withoutExt}`;
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
export const specPathForImpl = (implRelativePath: string) =>
|
|
107
|
-
swapModuleSuffix(implRelativePath, IMPL_SUFFIX, SPEC_SUFFIX);
|
|
108
|
-
|
|
109
|
-
export const implPathForSpec = (specRelativePath: string) =>
|
|
110
|
-
swapModuleSuffix(specRelativePath, SPEC_SUFFIX, IMPL_SUFFIX);
|
|
111
|
-
|
|
112
|
-
export const registeredFunctionsRelativePath = (leaf: LeafModule) =>
|
|
113
|
-
Effect.gen(function* () {
|
|
114
|
-
const path = yield* Path.Path;
|
|
115
|
-
return path.join("registeredFunctions", ...leaf.pathSegments) + ".ts";
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
export const discoverLeafSpecFiles = Effect.gen(function* () {
|
|
119
|
-
const fs = yield* FileSystem.FileSystem;
|
|
120
|
-
const path = yield* Path.Path;
|
|
121
|
-
const confectDirectory = yield* ConfectDirectory.get;
|
|
122
|
-
|
|
123
|
-
const excludedDirs = new Set(["_generated", "tables"]);
|
|
124
|
-
const excludedFiles = new Set(["nodeSpec.ts", "spec.ts"]);
|
|
125
|
-
|
|
126
|
-
const allPaths = yield* fs.readDirectory(confectDirectory, {
|
|
127
|
-
recursive: true,
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
return Array.filter(allPaths, (relativePath) => {
|
|
131
|
-
if (!isLeafSpecPath(relativePath)) {
|
|
132
|
-
return false;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
if (excludedFiles.has(relativePath)) {
|
|
136
|
-
return false;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const segments = String.split(relativePath, path.sep);
|
|
140
|
-
return !Array.some(segments, (segment) => excludedDirs.has(segment));
|
|
141
|
-
});
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
export const discoverLeafImplFiles = Effect.gen(function* () {
|
|
145
|
-
const fs = yield* FileSystem.FileSystem;
|
|
146
|
-
const path = yield* Path.Path;
|
|
147
|
-
const confectDirectory = yield* ConfectDirectory.get;
|
|
148
|
-
|
|
149
|
-
const excludedDirs = new Set(["_generated", "tables"]);
|
|
150
|
-
|
|
151
|
-
const allPaths = yield* fs.readDirectory(confectDirectory, {
|
|
152
|
-
recursive: true,
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
return Array.filter(allPaths, (relativePath) => {
|
|
156
|
-
if (!isLeafImplPath(relativePath)) {
|
|
157
|
-
return false;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const segments = String.split(relativePath, path.sep);
|
|
161
|
-
return !Array.some(segments, (segment) => excludedDirs.has(segment));
|
|
162
|
-
});
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
export const toLeafModule = (specRelativePath: string) =>
|
|
166
|
-
Effect.gen(function* () {
|
|
167
|
-
const exportName = yield* exportNameFromModulePath(specRelativePath);
|
|
168
|
-
const { pathSegments, groupPathDot } =
|
|
169
|
-
yield* groupPathFromRelativeModulePath(specRelativePath);
|
|
170
|
-
const specImportPath = yield* specImportPathFromGenerated(specRelativePath);
|
|
171
|
-
|
|
172
|
-
return {
|
|
173
|
-
relativePath: specRelativePath,
|
|
174
|
-
pathSegments,
|
|
175
|
-
groupPathDot,
|
|
176
|
-
exportName,
|
|
177
|
-
// Unknown until the spec is bundled; see `LeafModule.runtime`.
|
|
178
|
-
runtime: Option.none(),
|
|
179
|
-
specImportPath,
|
|
180
|
-
} satisfies LeafModule;
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
const absoluteModulePath = (relativePath: string) =>
|
|
184
|
-
Effect.gen(function* () {
|
|
185
|
-
const confectDirectory = yield* ConfectDirectory.get;
|
|
186
|
-
const path = yield* Path.Path;
|
|
187
|
-
return path.resolve(confectDirectory, relativePath);
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Validate that the leaf's spec file default-exports a `GroupSpec`. Returns the
|
|
192
|
-
* validated `GroupSpec` so callers can read its runtime and avoid re-bundling for
|
|
193
|
-
* later inspection (e.g. stamping `leaf.runtime` and parent/child name-collision
|
|
194
|
-
* checks at codegen time). The group's runtime (`Convex` vs `Node`) is whatever
|
|
195
|
-
* the spec declares — it is not constrained by the file's location.
|
|
196
|
-
*/
|
|
197
|
-
export const validateSpec = (leaf: LeafModule) =>
|
|
198
|
-
Effect.gen(function* () {
|
|
199
|
-
const absolutePath = yield* absoluteModulePath(leaf.relativePath);
|
|
200
|
-
const { module } = yield* Bundler.bundle(absolutePath).pipe(
|
|
201
|
-
Effect.mapError((error) => fromBundlerError(leaf.relativePath, error)),
|
|
202
|
-
);
|
|
203
|
-
|
|
204
|
-
const groupSpec = module.default;
|
|
205
|
-
|
|
206
|
-
if (!GroupSpec.isGroupSpec(groupSpec)) {
|
|
207
|
-
return yield* new SpecMissingDefaultGroupSpecError({
|
|
208
|
-
specPath: leaf.relativePath,
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
return groupSpec;
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* Walk the built `Context` for a `Finalized` `GroupImpl` service value. The
|
|
217
|
-
* lookup is value-shaped (via `GroupImpl.isFinalizedGroupImpl`) so we don't
|
|
218
|
-
* need to know the group's path up front to construct a typed tag for it.
|
|
219
|
-
*/
|
|
220
|
-
const findFinalizedGroupImpl = <S>(
|
|
221
|
-
context: Context.Context<S>,
|
|
222
|
-
): Option.Option<GroupImpl.AnyFinalized> =>
|
|
223
|
-
Array.findFirst(context.unsafeMap.values(), GroupImpl.isFinalizedGroupImpl);
|
|
224
|
-
|
|
225
|
-
/**
|
|
226
|
-
* Build the impl layer with a fresh `Registry` so each validation is
|
|
227
|
-
* isolated from prior validations' `FunctionImpl.make` writes. The CLI no
|
|
228
|
-
* longer reads the registry directly — `GroupImpl.finalize` snapshots the
|
|
229
|
-
* registered function names onto the produced `Finalized` `GroupImpl`
|
|
230
|
-
* service value — but a fresh `Ref` is still required because the default
|
|
231
|
-
* `Context.Reference` is cached globally and would otherwise accumulate
|
|
232
|
-
* items across impls.
|
|
233
|
-
*/
|
|
234
|
-
const buildImplLayer = (implLayer: Layer.Layer<unknown>) =>
|
|
235
|
-
Effect.gen(function* () {
|
|
236
|
-
const registry = Ref.unsafeMake<Registry.RegistryItems>({});
|
|
237
|
-
return yield* Layer.build(
|
|
238
|
-
implLayer as Layer.Layer<unknown, never, never>,
|
|
239
|
-
).pipe(Effect.provideService(Registry.Registry, registry));
|
|
240
|
-
}).pipe(Effect.scoped);
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* Validate that the leaf's sibling impl file imports the spec, default-exports
|
|
244
|
-
* a finalized `GroupImpl` layer, and provides a `FunctionImpl` for every
|
|
245
|
-
* function declared by the spec.
|
|
246
|
-
*/
|
|
247
|
-
export const validateImpl = (leaf: LeafModule) =>
|
|
248
|
-
Effect.gen(function* () {
|
|
249
|
-
const implRelativePath = yield* implPathForSpec(leaf.relativePath);
|
|
250
|
-
const implAbsolutePath = yield* absoluteModulePath(implRelativePath);
|
|
251
|
-
const specAbsolutePath = yield* absoluteModulePath(leaf.relativePath);
|
|
252
|
-
|
|
253
|
-
const bundled = yield* Bundler.bundle(implAbsolutePath).pipe(
|
|
254
|
-
Effect.mapError((error) => fromBundlerError(implRelativePath, error)),
|
|
255
|
-
);
|
|
256
|
-
|
|
257
|
-
if (
|
|
258
|
-
!(yield* Bundler.directlyImports(
|
|
259
|
-
bundled,
|
|
260
|
-
implAbsolutePath,
|
|
261
|
-
specAbsolutePath,
|
|
262
|
-
))
|
|
263
|
-
) {
|
|
264
|
-
return yield* new ImplMissingSpecImportError({
|
|
265
|
-
implPath: implRelativePath,
|
|
266
|
-
expectedSpecPath: leaf.relativePath,
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
if (!Layer.isLayer(bundled.module.default)) {
|
|
271
|
-
return yield* new ImplMissingDefaultLayerError({
|
|
272
|
-
implPath: implRelativePath,
|
|
273
|
-
});
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
const { module: specModule } = yield* Bundler.bundle(specAbsolutePath).pipe(
|
|
277
|
-
Effect.mapError((error) => fromBundlerError(leaf.relativePath, error)),
|
|
278
|
-
);
|
|
279
|
-
const groupSpec = specModule.default as GroupSpec.AnyWithProps;
|
|
280
|
-
const expectedFunctionNames = Object.keys(groupSpec.functions);
|
|
281
|
-
|
|
282
|
-
const context = yield* buildImplLayer(
|
|
283
|
-
bundled.module.default as Layer.Layer<unknown>,
|
|
284
|
-
);
|
|
285
|
-
const finalizedGroupImpl = yield* Option.match(
|
|
286
|
-
findFinalizedGroupImpl(context),
|
|
287
|
-
{
|
|
288
|
-
onNone: () => new ImplNotFinalizedError({ implPath: implRelativePath }),
|
|
289
|
-
onSome: Effect.succeed,
|
|
290
|
-
},
|
|
291
|
-
);
|
|
292
|
-
|
|
293
|
-
const registeredSet = new Set(finalizedGroupImpl.registeredFunctionNames);
|
|
294
|
-
const missing = expectedFunctionNames.filter(
|
|
295
|
-
(name) => !registeredSet.has(name),
|
|
296
|
-
);
|
|
297
|
-
|
|
298
|
-
if (missing.length > 0) {
|
|
299
|
-
return yield* new ImplMissingFunctionsError({
|
|
300
|
-
implPath: implRelativePath,
|
|
301
|
-
groupPath: leaf.groupPathDot,
|
|
302
|
-
missingFunctionNames: missing,
|
|
303
|
-
});
|
|
304
|
-
}
|
|
305
|
-
});
|
package/src/ProjectRoot.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import * as FileSystem from "@effect/platform/FileSystem";
|
|
2
|
-
import * as Path from "@effect/platform/Path";
|
|
3
|
-
import * as NodeFileSystem from "@effect/platform-node/NodeFileSystem";
|
|
4
|
-
import * as Array from "effect/Array";
|
|
5
|
-
import * as Effect from "effect/Effect";
|
|
6
|
-
import * as Option from "effect/Option";
|
|
7
|
-
import * as Ref from "effect/Ref";
|
|
8
|
-
import * as Schema from "effect/Schema";
|
|
9
|
-
|
|
10
|
-
export class ProjectRoot extends Effect.Service<ProjectRoot>()(
|
|
11
|
-
"@confect/cli/ProjectRoot",
|
|
12
|
-
{
|
|
13
|
-
effect: Effect.gen(function* () {
|
|
14
|
-
const projectRoot = yield* findProjectRoot;
|
|
15
|
-
|
|
16
|
-
const ref = yield* Ref.make<string>(projectRoot);
|
|
17
|
-
|
|
18
|
-
return { get: Ref.get(ref) } as const;
|
|
19
|
-
}),
|
|
20
|
-
dependencies: [NodeFileSystem.layer],
|
|
21
|
-
accessors: true,
|
|
22
|
-
},
|
|
23
|
-
) {}
|
|
24
|
-
|
|
25
|
-
export const findProjectRoot = Effect.gen(function* () {
|
|
26
|
-
const fs = yield* FileSystem.FileSystem;
|
|
27
|
-
const path = yield* Path.Path;
|
|
28
|
-
|
|
29
|
-
const startDir = path.resolve(".");
|
|
30
|
-
const root = path.parse(startDir).root;
|
|
31
|
-
|
|
32
|
-
const directories = Array.unfold(startDir, (dir) =>
|
|
33
|
-
dir === root
|
|
34
|
-
? Option.none()
|
|
35
|
-
: Option.some([dir, path.dirname(dir)] as const),
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
const projectRoot = yield* Effect.findFirst(directories, (dir) =>
|
|
39
|
-
fs.exists(path.join(dir, "package.json")),
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
return yield* Option.match(projectRoot, {
|
|
43
|
-
onNone: () => Effect.fail(new ProjectRootNotFoundError()),
|
|
44
|
-
onSome: Effect.succeed,
|
|
45
|
-
});
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
export class ProjectRootNotFoundError extends Schema.TaggedError<ProjectRootNotFoundError>()(
|
|
49
|
-
"ProjectRootNotFoundError",
|
|
50
|
-
{},
|
|
51
|
-
) {
|
|
52
|
-
override get message(): string {
|
|
53
|
-
return "Could not find project root (no 'package.json' found)";
|
|
54
|
-
}
|
|
55
|
-
}
|
package/src/SpecAssemblyNode.ts
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import type { LeafModule } from "./LeafModule";
|
|
2
|
-
import { pipe } from "effect/Function";
|
|
3
|
-
import * as Array from "effect/Array";
|
|
4
|
-
import * as Option from "effect/Option";
|
|
5
|
-
import * as Order from "effect/Order";
|
|
6
|
-
import * as Record from "effect/Record";
|
|
7
|
-
|
|
8
|
-
export interface SpecImportBinding {
|
|
9
|
-
readonly importPath: string;
|
|
10
|
-
readonly exportName: string;
|
|
11
|
-
readonly localName: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface SpecAssemblyNode {
|
|
15
|
-
readonly segment: string;
|
|
16
|
-
readonly importBinding: Option.Option<SpecImportBinding>;
|
|
17
|
-
readonly children: ReadonlyArray<SpecAssemblyNode>;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const importBindingFromLeaf = (leaf: LeafModule): SpecImportBinding => ({
|
|
21
|
-
importPath: leaf.specImportPath,
|
|
22
|
-
exportName: leaf.exportName,
|
|
23
|
-
localName: leaf.pathSegments.join("_"),
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
const assemblyNodesAtDepth = (
|
|
27
|
-
leaves: ReadonlyArray<LeafModule>,
|
|
28
|
-
depth: number,
|
|
29
|
-
): ReadonlyArray<SpecAssemblyNode> =>
|
|
30
|
-
pipe(
|
|
31
|
-
Array.groupBy(leaves, (leaf) => leaf.pathSegments[depth]!),
|
|
32
|
-
Record.toEntries,
|
|
33
|
-
Array.sortBy(Order.mapInput(Order.string, ([segment]) => segment)),
|
|
34
|
-
Array.map(([segment, groupLeaves]) => {
|
|
35
|
-
const terminal = Array.findFirst(
|
|
36
|
-
groupLeaves,
|
|
37
|
-
(leaf) => leaf.pathSegments.length === depth + 1,
|
|
38
|
-
);
|
|
39
|
-
const descendants = Array.filter(
|
|
40
|
-
groupLeaves,
|
|
41
|
-
(leaf) => leaf.pathSegments.length > depth + 1,
|
|
42
|
-
);
|
|
43
|
-
return {
|
|
44
|
-
segment,
|
|
45
|
-
importBinding: Option.map(terminal, importBindingFromLeaf),
|
|
46
|
-
children: assemblyNodesAtDepth(descendants, depth + 1),
|
|
47
|
-
};
|
|
48
|
-
}),
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
export const assemblyNodesFromLeaves = (
|
|
52
|
-
leaves: ReadonlyArray<LeafModule>,
|
|
53
|
-
): ReadonlyArray<SpecAssemblyNode> => assemblyNodesAtDepth(leaves, 0);
|
|
54
|
-
|
|
55
|
-
const importBindingsForNode = (
|
|
56
|
-
node: SpecAssemblyNode,
|
|
57
|
-
): ReadonlyArray<SpecImportBinding> =>
|
|
58
|
-
pipe(node.children, Array.flatMap(importBindingsForNode), (childBindings) =>
|
|
59
|
-
Option.match(node.importBinding, {
|
|
60
|
-
onNone: () => childBindings,
|
|
61
|
-
onSome: (binding) => Array.prepend(childBindings, binding),
|
|
62
|
-
}),
|
|
63
|
-
);
|
|
64
|
-
|
|
65
|
-
export const collectImportBindings = (
|
|
66
|
-
nodes: ReadonlyArray<SpecAssemblyNode>,
|
|
67
|
-
): ReadonlyArray<SpecImportBinding> =>
|
|
68
|
-
pipe(
|
|
69
|
-
Array.flatMap(nodes, importBindingsForNode),
|
|
70
|
-
(bindings) =>
|
|
71
|
-
Record.fromIterableBy(bindings, (binding) => binding.importPath),
|
|
72
|
-
Record.toEntries,
|
|
73
|
-
Array.map(([, binding]) => binding),
|
|
74
|
-
Array.sortBy(Order.mapInput(Order.string, (binding) => binding.localName)),
|
|
75
|
-
);
|