@confect/cli 8.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 +43 -1
- 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 +222 -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 +77 -73
- package/dist/utils.mjs.map +1 -1
- package/package.json +4 -3
- 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 -141
- package/src/confect/dev.ts +556 -435
- package/src/log.ts +21 -0
- package/src/templates.ts +141 -109
- package/src/utils.ts +118 -93
package/src/log.ts
CHANGED
|
@@ -5,6 +5,27 @@ import type * as FunctionPath from "./FunctionPath";
|
|
|
5
5
|
import * as GroupPath from "./GroupPath";
|
|
6
6
|
import { ProjectRoot } from "./ProjectRoot";
|
|
7
7
|
|
|
8
|
+
// --- Path styling ---
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Render a relative path as an AnsiDoc with the directory portion
|
|
12
|
+
* dimmed (`Ansi.blackBright`) and the file leaf rendered in the
|
|
13
|
+
* default terminal color. Used inline anywhere a file path appears
|
|
14
|
+
* in a CLI message.
|
|
15
|
+
*/
|
|
16
|
+
export const formatPathDoc = (relativePath: string): AnsiDoc.AnsiDoc => {
|
|
17
|
+
const lastSep = Math.max(
|
|
18
|
+
relativePath.lastIndexOf("/"),
|
|
19
|
+
relativePath.lastIndexOf("\\"),
|
|
20
|
+
);
|
|
21
|
+
const dir = lastSep < 0 ? "" : relativePath.slice(0, lastSep + 1);
|
|
22
|
+
const leaf = lastSep < 0 ? relativePath : relativePath.slice(lastSep + 1);
|
|
23
|
+
return AnsiDoc.hcat([
|
|
24
|
+
pipe(AnsiDoc.text(dir), AnsiDoc.annotate(Ansi.blackBright)),
|
|
25
|
+
AnsiDoc.text(leaf),
|
|
26
|
+
]);
|
|
27
|
+
};
|
|
28
|
+
|
|
8
29
|
// --- File operation logs ---
|
|
9
30
|
|
|
10
31
|
const logFile = (char: string, color: Ansi.Ansi) => (fullPath: string) =>
|
package/src/templates.ts
CHANGED
|
@@ -1,42 +1,36 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
|
|
1
|
+
import { Array, Effect, Option } from "effect";
|
|
2
|
+
import { CodeBlockWriter } from "./CodeBlockWriter";
|
|
3
|
+
import {
|
|
4
|
+
collectImportBindings,
|
|
5
|
+
type SpecAssemblyNode,
|
|
6
|
+
} from "./SpecAssemblyNode";
|
|
5
7
|
|
|
6
8
|
export const functions = ({
|
|
7
|
-
groupPath,
|
|
8
9
|
functionNames,
|
|
9
10
|
registeredFunctionsImportPath,
|
|
10
|
-
registeredFunctionsVariableName = "registeredFunctions",
|
|
11
|
-
registeredFunctionsLookupPath,
|
|
12
11
|
useNode = false,
|
|
13
12
|
}: {
|
|
14
|
-
groupPath: GroupPath.GroupPath;
|
|
15
13
|
functionNames: string[];
|
|
16
14
|
registeredFunctionsImportPath: string;
|
|
17
|
-
registeredFunctionsVariableName?: string;
|
|
18
|
-
registeredFunctionsLookupPath?: readonly string[];
|
|
19
15
|
useNode?: boolean;
|
|
20
16
|
}) =>
|
|
21
17
|
Effect.gen(function* () {
|
|
22
18
|
const cbw = new CodeBlockWriter({ indentNumberOfSpaces: 2 });
|
|
23
19
|
|
|
24
|
-
const lookupPath = registeredFunctionsLookupPath ?? groupPath.pathSegments;
|
|
25
|
-
|
|
26
20
|
if (useNode) {
|
|
27
21
|
yield* cbw.writeLine(`"use node";`);
|
|
28
22
|
yield* cbw.blankLine();
|
|
29
23
|
}
|
|
30
24
|
|
|
31
25
|
yield* cbw.writeLine(
|
|
32
|
-
`import
|
|
26
|
+
`import registeredFunctions from "${registeredFunctionsImportPath}";`,
|
|
33
27
|
);
|
|
34
28
|
yield* cbw.newLine();
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
`export const ${functionName} =
|
|
38
|
-
)
|
|
39
|
-
|
|
29
|
+
yield* Effect.forEach(functionNames, (functionName) =>
|
|
30
|
+
cbw.writeLine(
|
|
31
|
+
`export const ${functionName} = registeredFunctions.${functionName};`,
|
|
32
|
+
),
|
|
33
|
+
);
|
|
40
34
|
|
|
41
35
|
return yield* cbw.toString();
|
|
42
36
|
});
|
|
@@ -92,21 +86,24 @@ export const refs = ({
|
|
|
92
86
|
nodeSpecImportPath,
|
|
93
87
|
}: {
|
|
94
88
|
specImportPath: string;
|
|
95
|
-
nodeSpecImportPath
|
|
89
|
+
nodeSpecImportPath: Option.Option<string>;
|
|
96
90
|
}) =>
|
|
97
91
|
Effect.gen(function* () {
|
|
98
92
|
const cbw = new CodeBlockWriter({ indentNumberOfSpaces: 2 });
|
|
99
93
|
|
|
100
94
|
yield* cbw.writeLine(`import { Refs } from "@confect/core";`);
|
|
101
95
|
yield* cbw.writeLine(`import spec from "${specImportPath}";`);
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
96
|
+
yield* Option.match(nodeSpecImportPath, {
|
|
97
|
+
onNone: () => Effect.void,
|
|
98
|
+
onSome: (nodeSpecImportPath_) =>
|
|
99
|
+
cbw.writeLine(`import nodeSpec from "${nodeSpecImportPath_}";`),
|
|
100
|
+
});
|
|
105
101
|
yield* cbw.blankLine();
|
|
106
102
|
yield* cbw.writeLine(
|
|
107
|
-
nodeSpecImportPath
|
|
108
|
-
|
|
109
|
-
: `export default Refs.make(spec);`,
|
|
103
|
+
Option.match(nodeSpecImportPath, {
|
|
104
|
+
onSome: () => `export default Refs.make(spec, nodeSpec);`,
|
|
105
|
+
onNone: () => `export default Refs.make(spec);`,
|
|
106
|
+
}),
|
|
110
107
|
);
|
|
111
108
|
|
|
112
109
|
return yield* cbw.toString();
|
|
@@ -151,45 +148,43 @@ export const nodeApi = ({
|
|
|
151
148
|
return yield* cbw.toString();
|
|
152
149
|
});
|
|
153
150
|
|
|
154
|
-
export const
|
|
151
|
+
export const registeredFunctionsForGroup = ({
|
|
152
|
+
apiImportPath,
|
|
153
|
+
groupPathDot,
|
|
155
154
|
implImportPath,
|
|
155
|
+
layerExportName,
|
|
156
|
+
useNode = false,
|
|
156
157
|
}: {
|
|
158
|
+
apiImportPath: string;
|
|
159
|
+
groupPathDot: string;
|
|
157
160
|
implImportPath: string;
|
|
161
|
+
layerExportName: string;
|
|
162
|
+
useNode?: boolean;
|
|
158
163
|
}) =>
|
|
159
164
|
Effect.gen(function* () {
|
|
160
165
|
const cbw = new CodeBlockWriter({ indentNumberOfSpaces: 2 });
|
|
161
166
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
export const nodeRegisteredFunctions = ({
|
|
175
|
-
nodeImplImportPath,
|
|
176
|
-
}: {
|
|
177
|
-
nodeImplImportPath: string;
|
|
178
|
-
}) =>
|
|
179
|
-
Effect.gen(function* () {
|
|
180
|
-
const cbw = new CodeBlockWriter({ indentNumberOfSpaces: 2 });
|
|
167
|
+
if (useNode) {
|
|
168
|
+
yield* cbw.writeLine(
|
|
169
|
+
`import { RegisteredFunctions } from "@confect/server";`,
|
|
170
|
+
);
|
|
171
|
+
yield* cbw.writeLine(
|
|
172
|
+
`import { RegisteredNodeFunction } from "@confect/server/node";`,
|
|
173
|
+
);
|
|
174
|
+
} else {
|
|
175
|
+
yield* cbw.writeLine(
|
|
176
|
+
`import { RegisteredConvexFunction, RegisteredFunctions } from "@confect/server";`,
|
|
177
|
+
);
|
|
178
|
+
}
|
|
181
179
|
|
|
182
|
-
yield* cbw.writeLine(
|
|
183
|
-
|
|
184
|
-
);
|
|
185
|
-
yield* cbw.writeLine(
|
|
186
|
-
`import { RegisteredNodeFunction } from "@confect/server/node";`,
|
|
187
|
-
);
|
|
188
|
-
yield* cbw.blankLine();
|
|
189
|
-
yield* cbw.writeLine(`import nodeImpl from "${nodeImplImportPath}";`);
|
|
180
|
+
yield* cbw.writeLine(`import api from "${apiImportPath}";`);
|
|
181
|
+
yield* cbw.writeLine(`import ${layerExportName} from "${implImportPath}";`);
|
|
190
182
|
yield* cbw.blankLine();
|
|
183
|
+
const quotedGroupPath = `"${groupPathDot.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
191
184
|
yield* cbw.writeLine(
|
|
192
|
-
|
|
185
|
+
useNode
|
|
186
|
+
? `export default RegisteredFunctions.buildForGroup(api, ${quotedGroupPath}, ${layerExportName}, RegisteredNodeFunction.make);`
|
|
187
|
+
: `export default RegisteredFunctions.buildForGroup(api, ${quotedGroupPath}, ${layerExportName}, RegisteredConvexFunction.make);`,
|
|
193
188
|
);
|
|
194
189
|
|
|
195
190
|
return yield* cbw.toString();
|
|
@@ -382,64 +377,101 @@ export const services = ({ schemaImportPath }: { schemaImportPath: string }) =>
|
|
|
382
377
|
return yield* cbw.toString();
|
|
383
378
|
});
|
|
384
379
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
this.writer.setIndentationLevel(indentationLevel + 1);
|
|
398
|
-
yield* eff;
|
|
399
|
-
this.writer.setIndentationLevel(indentationLevel);
|
|
400
|
-
});
|
|
401
|
-
}
|
|
380
|
+
const writeChildAddGroupAt = (
|
|
381
|
+
cbw: CodeBlockWriter,
|
|
382
|
+
child: SpecAssemblyNode,
|
|
383
|
+
groupFactory: string,
|
|
384
|
+
): Effect.Effect<void> =>
|
|
385
|
+
Effect.gen(function* () {
|
|
386
|
+
yield* cbw.write(".addGroupAt(");
|
|
387
|
+
yield* cbw.quote(child.segment);
|
|
388
|
+
yield* cbw.write(", ");
|
|
389
|
+
yield* writeGroupAssembly(cbw, child, groupFactory);
|
|
390
|
+
yield* cbw.write(")");
|
|
391
|
+
});
|
|
402
392
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
393
|
+
const writeGroupFactoryCall = (
|
|
394
|
+
cbw: CodeBlockWriter,
|
|
395
|
+
node: SpecAssemblyNode,
|
|
396
|
+
groupFactory: string,
|
|
397
|
+
): Effect.Effect<void> =>
|
|
398
|
+
Effect.gen(function* () {
|
|
399
|
+
yield* cbw.write(groupFactory);
|
|
400
|
+
yield* cbw.write("(");
|
|
401
|
+
yield* cbw.quote(node.segment);
|
|
402
|
+
yield* cbw.write(")");
|
|
408
403
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
}
|
|
404
|
+
yield* Effect.forEach(node.children, (child) =>
|
|
405
|
+
writeChildAddGroupAt(cbw, child, groupFactory),
|
|
406
|
+
);
|
|
407
|
+
});
|
|
414
408
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
409
|
+
const writeGroupAssembly: (
|
|
410
|
+
cbw: CodeBlockWriter,
|
|
411
|
+
node: SpecAssemblyNode,
|
|
412
|
+
groupFactory: string,
|
|
413
|
+
) => Effect.Effect<void> = (cbw, node, groupFactory) =>
|
|
414
|
+
node.children.length === 0
|
|
415
|
+
? Option.match(node.importBinding, {
|
|
416
|
+
onNone: () => writeGroupFactoryCall(cbw, node, groupFactory),
|
|
417
|
+
onSome: (binding) => cbw.write(binding.exportName),
|
|
418
|
+
})
|
|
419
|
+
: writeGroupFactoryCall(cbw, node, groupFactory);
|
|
420
|
+
|
|
421
|
+
const writeRootAddAt = (
|
|
422
|
+
cbw: CodeBlockWriter,
|
|
423
|
+
node: SpecAssemblyNode,
|
|
424
|
+
groupFactory: string,
|
|
425
|
+
): Effect.Effect<void> =>
|
|
426
|
+
Effect.gen(function* () {
|
|
427
|
+
yield* cbw.write(".addAt(");
|
|
428
|
+
yield* cbw.quote(node.segment);
|
|
429
|
+
yield* cbw.write(", ");
|
|
429
430
|
|
|
430
|
-
|
|
431
|
-
return Effect.sync(() => {
|
|
432
|
-
this.writer.newLine();
|
|
433
|
-
});
|
|
434
|
-
}
|
|
431
|
+
yield* writeGroupAssembly(cbw, node, groupFactory);
|
|
435
432
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
433
|
+
yield* cbw.write(")");
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
export const assembledSpec = ({
|
|
437
|
+
nodes,
|
|
438
|
+
runtime,
|
|
439
|
+
}: {
|
|
440
|
+
nodes: ReadonlyArray<SpecAssemblyNode>;
|
|
441
|
+
runtime: "Convex" | "Node";
|
|
442
|
+
}) =>
|
|
443
|
+
Effect.gen(function* () {
|
|
444
|
+
const cbw = new CodeBlockWriter({ indentNumberOfSpaces: 2 });
|
|
445
|
+
|
|
446
|
+
const needsGroupSpec = Array.some(
|
|
447
|
+
nodes,
|
|
448
|
+
(node) => node.children.length > 0,
|
|
449
|
+
);
|
|
450
|
+
yield* cbw.writeLine(
|
|
451
|
+
needsGroupSpec
|
|
452
|
+
? `import { GroupSpec, Spec } from "@confect/core";`
|
|
453
|
+
: `import { Spec } from "@confect/core";`,
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
yield* Effect.forEach(collectImportBindings(nodes), (binding) =>
|
|
457
|
+
cbw.writeLine(
|
|
458
|
+
`import ${binding.exportName} from "${binding.importPath}";`,
|
|
459
|
+
),
|
|
460
|
+
);
|
|
461
|
+
|
|
462
|
+
yield* cbw.blankLine();
|
|
441
463
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
464
|
+
const specFactory =
|
|
465
|
+
runtime === "Convex" ? "Spec.make()" : "Spec.makeNode()";
|
|
466
|
+
const groupFactory =
|
|
467
|
+
runtime === "Convex" ? "GroupSpec.makeAt" : "GroupSpec.makeNodeAt";
|
|
468
|
+
|
|
469
|
+
yield* cbw.write(`export default ${specFactory}`);
|
|
470
|
+
yield* Effect.forEach(nodes, (node) =>
|
|
471
|
+
writeRootAddAt(cbw, node, groupFactory),
|
|
472
|
+
);
|
|
473
|
+
yield* cbw.write(";");
|
|
474
|
+
yield* cbw.newLine();
|
|
475
|
+
|
|
476
|
+
return yield* cbw.toString();
|
|
477
|
+
});
|
package/src/utils.ts
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
import type { FunctionSpec, Spec } from "@confect/core";
|
|
2
|
-
import { createRequire } from "node:module";
|
|
3
|
-
import { pathToFileURL } from "node:url";
|
|
4
2
|
import { FileSystem, Path } from "@effect/platform";
|
|
5
3
|
import type { PlatformError } from "@effect/platform/Error";
|
|
6
4
|
import {
|
|
7
5
|
Array,
|
|
6
|
+
Context,
|
|
8
7
|
Effect,
|
|
9
8
|
HashSet,
|
|
10
9
|
Option,
|
|
11
10
|
Order,
|
|
12
11
|
pipe,
|
|
13
12
|
Record,
|
|
13
|
+
Ref,
|
|
14
14
|
String,
|
|
15
15
|
} from "effect";
|
|
16
|
-
import * as esbuild from "esbuild";
|
|
17
16
|
import * as FunctionPaths from "./FunctionPaths";
|
|
18
17
|
import * as GroupPath from "./GroupPath";
|
|
19
18
|
import * as GroupPaths from "./GroupPaths";
|
|
@@ -22,6 +21,24 @@ import { ConfectDirectory } from "./ConfectDirectory";
|
|
|
22
21
|
import { ConvexDirectory } from "./ConvexDirectory";
|
|
23
22
|
import * as templates from "./templates";
|
|
24
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Tracks whether the current codegen run wrote anything to disk. Set to
|
|
26
|
+
* `true` by every helper that actually overwrites a file (skipping the
|
|
27
|
+
* content-unchanged case). `codegenHandler` provides a fresh `Ref` per
|
|
28
|
+
* run and reads it back to report `anyWritesHappened`; callers outside a
|
|
29
|
+
* codegen pass hit the cached default `Ref` and need not interact with
|
|
30
|
+
* the tracker.
|
|
31
|
+
*/
|
|
32
|
+
export class WriteTracker extends Context.Reference<WriteTracker>()(
|
|
33
|
+
"@confect/cli/WriteTracker",
|
|
34
|
+
{ defaultValue: () => Ref.unsafeMake(false) },
|
|
35
|
+
) {}
|
|
36
|
+
|
|
37
|
+
const markWritten = Effect.gen(function* () {
|
|
38
|
+
const tracker = yield* WriteTracker;
|
|
39
|
+
yield* Ref.set(tracker, true);
|
|
40
|
+
});
|
|
41
|
+
|
|
25
42
|
export const removePathExtension = (pathStr: string) =>
|
|
26
43
|
Effect.gen(function* () {
|
|
27
44
|
const path = yield* Path.Path;
|
|
@@ -29,65 +46,11 @@ export const removePathExtension = (pathStr: string) =>
|
|
|
29
46
|
return String.slice(0, -path.extname(pathStr).length)(pathStr);
|
|
30
47
|
});
|
|
31
48
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
"@confect/server",
|
|
35
|
-
"effect",
|
|
36
|
-
"@effect/*",
|
|
37
|
-
];
|
|
38
|
-
|
|
39
|
-
const isExternalImport = (path: string) =>
|
|
40
|
-
EXTERNAL_PACKAGES.some((p) => {
|
|
41
|
-
if (p.endsWith("/*")) {
|
|
42
|
-
return path.startsWith(p.slice(0, -1));
|
|
43
|
-
}
|
|
44
|
-
return path === p || path.startsWith(p + "/");
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
const absoluteExternalsPlugin: esbuild.Plugin = {
|
|
48
|
-
name: "absolute-externals",
|
|
49
|
-
setup(build) {
|
|
50
|
-
build.onResolve({ filter: /.*/ }, async (args) => {
|
|
51
|
-
if (args.kind !== "import-statement" && args.kind !== "dynamic-import")
|
|
52
|
-
return;
|
|
53
|
-
if (!isExternalImport(args.path)) return;
|
|
54
|
-
// `import.meta.resolve`'s second argument is silently ignored in modern
|
|
55
|
-
// Node, so resolution would always walk up from the CLI's bundled file
|
|
56
|
-
// (`packages/cli/dist/utils.mjs`) instead of from the user's project.
|
|
57
|
-
// Use `createRequire` keyed on the importing file's directory so we
|
|
58
|
-
// resolve out of *their* `node_modules`. The synthetic filename is just
|
|
59
|
-
// a CommonJS resolution anchor; the file does not need to exist.
|
|
60
|
-
const parentFile = pathToFileURL(args.resolveDir + "/_").href;
|
|
61
|
-
const require_ = createRequire(parentFile);
|
|
62
|
-
const resolvedPath = require_.resolve(args.path);
|
|
63
|
-
const resolved = pathToFileURL(resolvedPath).href;
|
|
64
|
-
return { path: resolved, external: true };
|
|
65
|
-
});
|
|
66
|
-
},
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Bundle a TypeScript entry point with esbuild and import the result via a
|
|
71
|
-
* data URL. This handles extensionless `.ts` imports regardless of whether the
|
|
72
|
-
* user's project sets `"type": "module"` in package.json.
|
|
73
|
-
*/
|
|
74
|
-
export const bundleAndImport = (entryPoint: string) =>
|
|
49
|
+
/** Ensures a relative path is a valid ESM/TS module specifier (e.g. `spec` → `./spec`). */
|
|
50
|
+
export const toModuleImportPath = (relativePath: string) =>
|
|
75
51
|
Effect.gen(function* () {
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
entryPoints: [entryPoint],
|
|
79
|
-
bundle: true,
|
|
80
|
-
write: false,
|
|
81
|
-
platform: "node",
|
|
82
|
-
format: "esm",
|
|
83
|
-
logLevel: "silent",
|
|
84
|
-
plugins: [absoluteExternalsPlugin],
|
|
85
|
-
}),
|
|
86
|
-
);
|
|
87
|
-
const code = result.outputFiles[0]!.text;
|
|
88
|
-
const dataUrl =
|
|
89
|
-
"data:text/javascript;base64," + Buffer.from(code).toString("base64");
|
|
90
|
-
return yield* Effect.promise(() => import(dataUrl));
|
|
52
|
+
const withoutExt = yield* removePathExtension(relativePath);
|
|
53
|
+
return withoutExt.startsWith(".") ? withoutExt : `./${withoutExt}`;
|
|
91
54
|
});
|
|
92
55
|
|
|
93
56
|
export const writeFileStringAndLog = (filePath: string, contents: string) =>
|
|
@@ -95,12 +58,14 @@ export const writeFileStringAndLog = (filePath: string, contents: string) =>
|
|
|
95
58
|
const fs = yield* FileSystem.FileSystem;
|
|
96
59
|
if (!(yield* fs.exists(filePath))) {
|
|
97
60
|
yield* fs.writeFileString(filePath, contents);
|
|
61
|
+
yield* markWritten;
|
|
98
62
|
yield* logFileAdded(filePath);
|
|
99
63
|
return;
|
|
100
64
|
}
|
|
101
65
|
const existing = yield* fs.readFileString(filePath);
|
|
102
66
|
if (existing !== contents) {
|
|
103
67
|
yield* fs.writeFileString(filePath, contents);
|
|
68
|
+
yield* markWritten;
|
|
104
69
|
yield* logFileModified(filePath);
|
|
105
70
|
}
|
|
106
71
|
});
|
|
@@ -136,28 +101,73 @@ export const writeFileString = (
|
|
|
136
101
|
|
|
137
102
|
if (!(yield* fs.exists(filePath))) {
|
|
138
103
|
yield* fs.writeFileString(filePath, contents);
|
|
104
|
+
yield* markWritten;
|
|
139
105
|
return "Added";
|
|
140
106
|
}
|
|
141
107
|
const existing = yield* fs.readFileString(filePath);
|
|
142
108
|
if (existing !== contents) {
|
|
143
109
|
yield* fs.writeFileString(filePath, contents);
|
|
110
|
+
yield* markWritten;
|
|
144
111
|
return "Modified";
|
|
145
112
|
}
|
|
146
113
|
return "Unchanged";
|
|
147
114
|
});
|
|
148
115
|
|
|
116
|
+
export const removePathIfExists = (
|
|
117
|
+
filePath: string,
|
|
118
|
+
): Effect.Effect<void, PlatformError, FileSystem.FileSystem> =>
|
|
119
|
+
Effect.gen(function* () {
|
|
120
|
+
const fs = yield* FileSystem.FileSystem;
|
|
121
|
+
|
|
122
|
+
if (!(yield* fs.exists(filePath))) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
yield* fs
|
|
127
|
+
.remove(filePath)
|
|
128
|
+
.pipe(
|
|
129
|
+
Effect.catchTag("SystemError", (error) =>
|
|
130
|
+
error.reason === "NotFound" ? Effect.void : Effect.fail(error),
|
|
131
|
+
),
|
|
132
|
+
);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Bump the mtime of `convex/schema.ts` so the Convex CLI's chokidar watcher
|
|
137
|
+
* emits a `change` event after every successful Confect codegen run. Without
|
|
138
|
+
* this, a codegen that doesn't change any file content (for example,
|
|
139
|
+
* recovering from a transient broken state in `confect/`) leaves Convex
|
|
140
|
+
* stuck on its previous error because nothing it observes has changed.
|
|
141
|
+
*/
|
|
142
|
+
export const touchConvexSchema = Effect.gen(function* () {
|
|
143
|
+
const fs = yield* FileSystem.FileSystem;
|
|
144
|
+
const path = yield* Path.Path;
|
|
145
|
+
const convexDirectory = yield* ConvexDirectory.get;
|
|
146
|
+
const schemaPath = path.join(convexDirectory, "schema.ts");
|
|
147
|
+
|
|
148
|
+
if (!(yield* fs.exists(schemaPath))) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const now = new Date();
|
|
153
|
+
yield* fs.utimes(schemaPath, now, now);
|
|
154
|
+
});
|
|
155
|
+
|
|
149
156
|
export const generateGroupModule = ({
|
|
150
157
|
groupPath,
|
|
151
158
|
functionNames,
|
|
159
|
+
registeredFunctionsImportPath,
|
|
160
|
+
useNode = false,
|
|
152
161
|
}: {
|
|
153
162
|
groupPath: GroupPath.GroupPath;
|
|
154
163
|
functionNames: string[];
|
|
164
|
+
registeredFunctionsImportPath: string;
|
|
165
|
+
useNode?: boolean;
|
|
155
166
|
}) =>
|
|
156
167
|
Effect.gen(function* () {
|
|
157
168
|
const fs = yield* FileSystem.FileSystem;
|
|
158
169
|
const path = yield* Path.Path;
|
|
159
170
|
const convexDirectory = yield* ConvexDirectory.get;
|
|
160
|
-
const confectDirectory = yield* ConfectDirectory.get;
|
|
161
171
|
|
|
162
172
|
const relativeModulePath = yield* GroupPath.modulePath(groupPath);
|
|
163
173
|
const modulePath = path.join(convexDirectory, relativeModulePath);
|
|
@@ -167,42 +177,21 @@ export const generateGroupModule = ({
|
|
|
167
177
|
yield* fs.makeDirectory(directoryPath, { recursive: true });
|
|
168
178
|
}
|
|
169
179
|
|
|
170
|
-
const isNodeGroup = groupPath.pathSegments[0] === "node";
|
|
171
|
-
const registeredFunctionsFileName = isNodeGroup
|
|
172
|
-
? "nodeRegisteredFunctions.ts"
|
|
173
|
-
: "registeredFunctions.ts";
|
|
174
|
-
const registeredFunctionsPath = path.join(
|
|
175
|
-
confectDirectory,
|
|
176
|
-
"_generated",
|
|
177
|
-
registeredFunctionsFileName,
|
|
178
|
-
);
|
|
179
|
-
const registeredFunctionsImportPath = yield* removePathExtension(
|
|
180
|
-
path.relative(path.dirname(modulePath), registeredFunctionsPath),
|
|
181
|
-
);
|
|
182
|
-
const registeredFunctionsVariableName = isNodeGroup
|
|
183
|
-
? "nodeRegisteredFunctions"
|
|
184
|
-
: "registeredFunctions";
|
|
185
|
-
|
|
186
180
|
const functionsContentsString = yield* templates.functions({
|
|
187
|
-
groupPath,
|
|
188
181
|
functionNames,
|
|
189
182
|
registeredFunctionsImportPath,
|
|
190
|
-
|
|
191
|
-
useNode: isNodeGroup,
|
|
192
|
-
...(isNodeGroup
|
|
193
|
-
? {
|
|
194
|
-
registeredFunctionsLookupPath: groupPath.pathSegments.slice(1),
|
|
195
|
-
}
|
|
196
|
-
: {}),
|
|
183
|
+
useNode,
|
|
197
184
|
});
|
|
198
185
|
|
|
199
186
|
if (!(yield* fs.exists(modulePath))) {
|
|
200
187
|
yield* fs.writeFileString(modulePath, functionsContentsString);
|
|
188
|
+
yield* markWritten;
|
|
201
189
|
return "Added" as const;
|
|
202
190
|
}
|
|
203
191
|
const existing = yield* fs.readFileString(modulePath);
|
|
204
192
|
if (existing !== functionsContentsString) {
|
|
205
193
|
yield* fs.writeFileString(modulePath, functionsContentsString);
|
|
194
|
+
yield* markWritten;
|
|
206
195
|
return "Modified" as const;
|
|
207
196
|
}
|
|
208
197
|
return "Unchanged" as const;
|
|
@@ -228,6 +217,7 @@ export const generateFunctions = (spec: Spec.AnyWithProps) =>
|
|
|
228
217
|
Effect.gen(function* () {
|
|
229
218
|
const path = yield* Path.Path;
|
|
230
219
|
const convexDirectory = yield* ConvexDirectory.get;
|
|
220
|
+
const confectDirectory = yield* ConfectDirectory.get;
|
|
231
221
|
|
|
232
222
|
const groupPathsFromFs = yield* getGroupPathsFromFs;
|
|
233
223
|
const functionPaths = FunctionPaths.make(spec);
|
|
@@ -250,12 +240,30 @@ export const generateFunctions = (spec: Spec.AnyWithProps) =>
|
|
|
250
240
|
),
|
|
251
241
|
Array.map((fn) => fn.name),
|
|
252
242
|
);
|
|
253
|
-
const
|
|
243
|
+
const relativeModulePath = yield* GroupPath.modulePath(groupPath);
|
|
244
|
+
const modulePath = path.join(convexDirectory, relativeModulePath);
|
|
245
|
+
const registrySegments =
|
|
246
|
+
groupPath.pathSegments[0] === "node"
|
|
247
|
+
? groupPath.pathSegments.slice(1)
|
|
248
|
+
: groupPath.pathSegments;
|
|
249
|
+
const registeredFunctionsPath =
|
|
250
|
+
path.join(
|
|
251
|
+
confectDirectory,
|
|
252
|
+
"_generated",
|
|
253
|
+
"registeredFunctions",
|
|
254
|
+
...registrySegments,
|
|
255
|
+
) + ".ts";
|
|
256
|
+
const registeredFunctionsImportPath = yield* toModuleImportPath(
|
|
257
|
+
path.relative(path.dirname(modulePath), registeredFunctionsPath),
|
|
258
|
+
);
|
|
259
|
+
const result = yield* generateGroupModule({
|
|
260
|
+
groupPath,
|
|
261
|
+
functionNames,
|
|
262
|
+
registeredFunctionsImportPath,
|
|
263
|
+
useNode: groupPath.pathSegments[0] === "node",
|
|
264
|
+
});
|
|
254
265
|
if (result === "Modified") {
|
|
255
|
-
|
|
256
|
-
yield* logFileModified(
|
|
257
|
-
path.join(convexDirectory, relativeModulePath),
|
|
258
|
-
);
|
|
266
|
+
yield* logFileModified(modulePath);
|
|
259
267
|
}
|
|
260
268
|
}),
|
|
261
269
|
);
|
|
@@ -308,7 +316,6 @@ const getGroupPathsFromFs = Effect.gen(function* () {
|
|
|
308
316
|
|
|
309
317
|
export const removeGroups = (groupPaths: GroupPaths.GroupPaths) =>
|
|
310
318
|
Effect.gen(function* () {
|
|
311
|
-
const fs = yield* FileSystem.FileSystem;
|
|
312
319
|
const path = yield* Path.Path;
|
|
313
320
|
const convexDirectory = yield* ConvexDirectory.get;
|
|
314
321
|
|
|
@@ -320,7 +327,7 @@ export const removeGroups = (groupPaths: GroupPaths.GroupPaths) =>
|
|
|
320
327
|
|
|
321
328
|
yield* Effect.logDebug(`Removing group '${relativeModulePath}'...`);
|
|
322
329
|
|
|
323
|
-
yield*
|
|
330
|
+
yield* removePathIfExists(modulePath);
|
|
324
331
|
yield* Effect.logDebug(`Group '${relativeModulePath}' removed`);
|
|
325
332
|
}),
|
|
326
333
|
),
|
|
@@ -334,6 +341,9 @@ export const writeGroups = (
|
|
|
334
341
|
) =>
|
|
335
342
|
Effect.forEach(groupPaths, (groupPath) =>
|
|
336
343
|
Effect.gen(function* () {
|
|
344
|
+
const path = yield* Path.Path;
|
|
345
|
+
const convexDirectory = yield* ConvexDirectory.get;
|
|
346
|
+
const confectDirectory = yield* ConfectDirectory.get;
|
|
337
347
|
const group = yield* GroupPath.getGroupSpec(spec, groupPath);
|
|
338
348
|
|
|
339
349
|
const functionNames = pipe(
|
|
@@ -348,10 +358,25 @@ export const writeGroups = (
|
|
|
348
358
|
Array.map((fn) => fn.name),
|
|
349
359
|
);
|
|
350
360
|
|
|
361
|
+
const relativeModulePath = yield* GroupPath.modulePath(groupPath);
|
|
362
|
+
const modulePath = path.join(convexDirectory, relativeModulePath);
|
|
363
|
+
const registeredFunctionsPath =
|
|
364
|
+
path.join(
|
|
365
|
+
confectDirectory,
|
|
366
|
+
"_generated",
|
|
367
|
+
"registeredFunctions",
|
|
368
|
+
...groupPath.pathSegments,
|
|
369
|
+
) + ".ts";
|
|
370
|
+
const registeredFunctionsImportPath = yield* toModuleImportPath(
|
|
371
|
+
path.relative(path.dirname(modulePath), registeredFunctionsPath),
|
|
372
|
+
);
|
|
373
|
+
|
|
351
374
|
yield* Effect.logDebug(`Generating group ${groupPath}...`);
|
|
352
375
|
yield* generateGroupModule({
|
|
353
376
|
groupPath,
|
|
354
377
|
functionNames,
|
|
378
|
+
registeredFunctionsImportPath,
|
|
379
|
+
useNode: groupPath.pathSegments[0] === "node",
|
|
355
380
|
});
|
|
356
381
|
yield* Effect.logDebug(`Group ${groupPath} generated`);
|
|
357
382
|
}),
|