@confect/cli 7.0.0 → 9.0.0-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +60 -0
- package/dist/BuildError.mjs +101 -0
- package/dist/BuildError.mjs.map +1 -0
- package/dist/Bundler.mjs +91 -0
- package/dist/Bundler.mjs.map +1 -0
- package/dist/CodeBlockWriter.mjs +55 -0
- package/dist/CodeBlockWriter.mjs.map +1 -0
- package/dist/CodegenError.mjs +94 -0
- package/dist/CodegenError.mjs.map +1 -0
- package/dist/FunctionPaths.mjs +1 -1
- package/dist/LeafModule.mjs +167 -0
- package/dist/LeafModule.mjs.map +1 -0
- package/dist/SpecAssemblyNode.mjs +33 -0
- package/dist/SpecAssemblyNode.mjs.map +1 -0
- package/dist/confect/codegen.mjs +221 -72
- package/dist/confect/codegen.mjs.map +1 -1
- package/dist/confect/dev.mjs +234 -180
- package/dist/confect/dev.mjs.map +1 -1
- package/dist/log.mjs +13 -1
- package/dist/log.mjs.map +1 -1
- package/dist/package.mjs +1 -1
- package/dist/templates.mjs +65 -72
- package/dist/templates.mjs.map +1 -1
- package/dist/utils.mjs +76 -69
- package/dist/utils.mjs.map +1 -1
- package/package.json +14 -15
- package/src/BuildError.ts +210 -0
- package/src/Bundler.ts +144 -0
- package/src/CodeBlockWriter.ts +65 -0
- package/src/CodegenError.ts +344 -0
- package/src/LeafModule.ts +313 -0
- package/src/SpecAssemblyNode.ts +82 -0
- package/src/confect/codegen.ts +390 -142
- package/src/confect/dev.ts +556 -435
- package/src/log.ts +21 -0
- package/src/templates.ts +141 -109
- package/src/utils.ts +118 -85
package/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
|
@@ -3,15 +3,16 @@ import { FileSystem, Path } from "@effect/platform";
|
|
|
3
3
|
import type { PlatformError } from "@effect/platform/Error";
|
|
4
4
|
import {
|
|
5
5
|
Array,
|
|
6
|
+
Context,
|
|
6
7
|
Effect,
|
|
7
8
|
HashSet,
|
|
8
9
|
Option,
|
|
9
10
|
Order,
|
|
10
11
|
pipe,
|
|
11
12
|
Record,
|
|
13
|
+
Ref,
|
|
12
14
|
String,
|
|
13
15
|
} from "effect";
|
|
14
|
-
import * as esbuild from "esbuild";
|
|
15
16
|
import * as FunctionPaths from "./FunctionPaths";
|
|
16
17
|
import * as GroupPath from "./GroupPath";
|
|
17
18
|
import * as GroupPaths from "./GroupPaths";
|
|
@@ -20,6 +21,24 @@ import { ConfectDirectory } from "./ConfectDirectory";
|
|
|
20
21
|
import { ConvexDirectory } from "./ConvexDirectory";
|
|
21
22
|
import * as templates from "./templates";
|
|
22
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
|
+
|
|
23
42
|
export const removePathExtension = (pathStr: string) =>
|
|
24
43
|
Effect.gen(function* () {
|
|
25
44
|
const path = yield* Path.Path;
|
|
@@ -27,59 +46,11 @@ export const removePathExtension = (pathStr: string) =>
|
|
|
27
46
|
return String.slice(0, -path.extname(pathStr).length)(pathStr);
|
|
28
47
|
});
|
|
29
48
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
"@confect/server",
|
|
33
|
-
"effect",
|
|
34
|
-
"@effect/*",
|
|
35
|
-
];
|
|
36
|
-
|
|
37
|
-
const isExternalImport = (path: string) =>
|
|
38
|
-
EXTERNAL_PACKAGES.some((p) => {
|
|
39
|
-
if (p.endsWith("/*")) {
|
|
40
|
-
return path.startsWith(p.slice(0, -1));
|
|
41
|
-
}
|
|
42
|
-
return path === p || path.startsWith(p + "/");
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
const absoluteExternalsPlugin: esbuild.Plugin = {
|
|
46
|
-
name: "absolute-externals",
|
|
47
|
-
setup(build) {
|
|
48
|
-
build.onResolve({ filter: /.*/ }, async (args) => {
|
|
49
|
-
if (args.kind !== "import-statement" && args.kind !== "dynamic-import")
|
|
50
|
-
return;
|
|
51
|
-
if (!isExternalImport(args.path)) return;
|
|
52
|
-
const resolved = import.meta.resolve(
|
|
53
|
-
args.path,
|
|
54
|
-
"file://" + args.resolveDir + "/",
|
|
55
|
-
);
|
|
56
|
-
return { path: resolved, external: true };
|
|
57
|
-
});
|
|
58
|
-
},
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Bundle a TypeScript entry point with esbuild and import the result via a
|
|
63
|
-
* data URL. This handles extensionless `.ts` imports regardless of whether the
|
|
64
|
-
* user's project sets `"type": "module"` in package.json.
|
|
65
|
-
*/
|
|
66
|
-
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) =>
|
|
67
51
|
Effect.gen(function* () {
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
entryPoints: [entryPoint],
|
|
71
|
-
bundle: true,
|
|
72
|
-
write: false,
|
|
73
|
-
platform: "node",
|
|
74
|
-
format: "esm",
|
|
75
|
-
logLevel: "silent",
|
|
76
|
-
plugins: [absoluteExternalsPlugin],
|
|
77
|
-
}),
|
|
78
|
-
);
|
|
79
|
-
const code = result.outputFiles[0]!.text;
|
|
80
|
-
const dataUrl =
|
|
81
|
-
"data:text/javascript;base64," + Buffer.from(code).toString("base64");
|
|
82
|
-
return yield* Effect.promise(() => import(dataUrl));
|
|
52
|
+
const withoutExt = yield* removePathExtension(relativePath);
|
|
53
|
+
return withoutExt.startsWith(".") ? withoutExt : `./${withoutExt}`;
|
|
83
54
|
});
|
|
84
55
|
|
|
85
56
|
export const writeFileStringAndLog = (filePath: string, contents: string) =>
|
|
@@ -87,12 +58,14 @@ export const writeFileStringAndLog = (filePath: string, contents: string) =>
|
|
|
87
58
|
const fs = yield* FileSystem.FileSystem;
|
|
88
59
|
if (!(yield* fs.exists(filePath))) {
|
|
89
60
|
yield* fs.writeFileString(filePath, contents);
|
|
61
|
+
yield* markWritten;
|
|
90
62
|
yield* logFileAdded(filePath);
|
|
91
63
|
return;
|
|
92
64
|
}
|
|
93
65
|
const existing = yield* fs.readFileString(filePath);
|
|
94
66
|
if (existing !== contents) {
|
|
95
67
|
yield* fs.writeFileString(filePath, contents);
|
|
68
|
+
yield* markWritten;
|
|
96
69
|
yield* logFileModified(filePath);
|
|
97
70
|
}
|
|
98
71
|
});
|
|
@@ -128,28 +101,73 @@ export const writeFileString = (
|
|
|
128
101
|
|
|
129
102
|
if (!(yield* fs.exists(filePath))) {
|
|
130
103
|
yield* fs.writeFileString(filePath, contents);
|
|
104
|
+
yield* markWritten;
|
|
131
105
|
return "Added";
|
|
132
106
|
}
|
|
133
107
|
const existing = yield* fs.readFileString(filePath);
|
|
134
108
|
if (existing !== contents) {
|
|
135
109
|
yield* fs.writeFileString(filePath, contents);
|
|
110
|
+
yield* markWritten;
|
|
136
111
|
return "Modified";
|
|
137
112
|
}
|
|
138
113
|
return "Unchanged";
|
|
139
114
|
});
|
|
140
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
|
+
|
|
141
156
|
export const generateGroupModule = ({
|
|
142
157
|
groupPath,
|
|
143
158
|
functionNames,
|
|
159
|
+
registeredFunctionsImportPath,
|
|
160
|
+
useNode = false,
|
|
144
161
|
}: {
|
|
145
162
|
groupPath: GroupPath.GroupPath;
|
|
146
163
|
functionNames: string[];
|
|
164
|
+
registeredFunctionsImportPath: string;
|
|
165
|
+
useNode?: boolean;
|
|
147
166
|
}) =>
|
|
148
167
|
Effect.gen(function* () {
|
|
149
168
|
const fs = yield* FileSystem.FileSystem;
|
|
150
169
|
const path = yield* Path.Path;
|
|
151
170
|
const convexDirectory = yield* ConvexDirectory.get;
|
|
152
|
-
const confectDirectory = yield* ConfectDirectory.get;
|
|
153
171
|
|
|
154
172
|
const relativeModulePath = yield* GroupPath.modulePath(groupPath);
|
|
155
173
|
const modulePath = path.join(convexDirectory, relativeModulePath);
|
|
@@ -159,42 +177,21 @@ export const generateGroupModule = ({
|
|
|
159
177
|
yield* fs.makeDirectory(directoryPath, { recursive: true });
|
|
160
178
|
}
|
|
161
179
|
|
|
162
|
-
const isNodeGroup = groupPath.pathSegments[0] === "node";
|
|
163
|
-
const registeredFunctionsFileName = isNodeGroup
|
|
164
|
-
? "nodeRegisteredFunctions.ts"
|
|
165
|
-
: "registeredFunctions.ts";
|
|
166
|
-
const registeredFunctionsPath = path.join(
|
|
167
|
-
confectDirectory,
|
|
168
|
-
"_generated",
|
|
169
|
-
registeredFunctionsFileName,
|
|
170
|
-
);
|
|
171
|
-
const registeredFunctionsImportPath = yield* removePathExtension(
|
|
172
|
-
path.relative(path.dirname(modulePath), registeredFunctionsPath),
|
|
173
|
-
);
|
|
174
|
-
const registeredFunctionsVariableName = isNodeGroup
|
|
175
|
-
? "nodeRegisteredFunctions"
|
|
176
|
-
: "registeredFunctions";
|
|
177
|
-
|
|
178
180
|
const functionsContentsString = yield* templates.functions({
|
|
179
|
-
groupPath,
|
|
180
181
|
functionNames,
|
|
181
182
|
registeredFunctionsImportPath,
|
|
182
|
-
|
|
183
|
-
useNode: isNodeGroup,
|
|
184
|
-
...(isNodeGroup
|
|
185
|
-
? {
|
|
186
|
-
registeredFunctionsLookupPath: groupPath.pathSegments.slice(1),
|
|
187
|
-
}
|
|
188
|
-
: {}),
|
|
183
|
+
useNode,
|
|
189
184
|
});
|
|
190
185
|
|
|
191
186
|
if (!(yield* fs.exists(modulePath))) {
|
|
192
187
|
yield* fs.writeFileString(modulePath, functionsContentsString);
|
|
188
|
+
yield* markWritten;
|
|
193
189
|
return "Added" as const;
|
|
194
190
|
}
|
|
195
191
|
const existing = yield* fs.readFileString(modulePath);
|
|
196
192
|
if (existing !== functionsContentsString) {
|
|
197
193
|
yield* fs.writeFileString(modulePath, functionsContentsString);
|
|
194
|
+
yield* markWritten;
|
|
198
195
|
return "Modified" as const;
|
|
199
196
|
}
|
|
200
197
|
return "Unchanged" as const;
|
|
@@ -220,6 +217,7 @@ export const generateFunctions = (spec: Spec.AnyWithProps) =>
|
|
|
220
217
|
Effect.gen(function* () {
|
|
221
218
|
const path = yield* Path.Path;
|
|
222
219
|
const convexDirectory = yield* ConvexDirectory.get;
|
|
220
|
+
const confectDirectory = yield* ConfectDirectory.get;
|
|
223
221
|
|
|
224
222
|
const groupPathsFromFs = yield* getGroupPathsFromFs;
|
|
225
223
|
const functionPaths = FunctionPaths.make(spec);
|
|
@@ -242,12 +240,30 @@ export const generateFunctions = (spec: Spec.AnyWithProps) =>
|
|
|
242
240
|
),
|
|
243
241
|
Array.map((fn) => fn.name),
|
|
244
242
|
);
|
|
245
|
-
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
|
+
});
|
|
246
265
|
if (result === "Modified") {
|
|
247
|
-
|
|
248
|
-
yield* logFileModified(
|
|
249
|
-
path.join(convexDirectory, relativeModulePath),
|
|
250
|
-
);
|
|
266
|
+
yield* logFileModified(modulePath);
|
|
251
267
|
}
|
|
252
268
|
}),
|
|
253
269
|
);
|
|
@@ -300,7 +316,6 @@ const getGroupPathsFromFs = Effect.gen(function* () {
|
|
|
300
316
|
|
|
301
317
|
export const removeGroups = (groupPaths: GroupPaths.GroupPaths) =>
|
|
302
318
|
Effect.gen(function* () {
|
|
303
|
-
const fs = yield* FileSystem.FileSystem;
|
|
304
319
|
const path = yield* Path.Path;
|
|
305
320
|
const convexDirectory = yield* ConvexDirectory.get;
|
|
306
321
|
|
|
@@ -312,7 +327,7 @@ export const removeGroups = (groupPaths: GroupPaths.GroupPaths) =>
|
|
|
312
327
|
|
|
313
328
|
yield* Effect.logDebug(`Removing group '${relativeModulePath}'...`);
|
|
314
329
|
|
|
315
|
-
yield*
|
|
330
|
+
yield* removePathIfExists(modulePath);
|
|
316
331
|
yield* Effect.logDebug(`Group '${relativeModulePath}' removed`);
|
|
317
332
|
}),
|
|
318
333
|
),
|
|
@@ -326,6 +341,9 @@ export const writeGroups = (
|
|
|
326
341
|
) =>
|
|
327
342
|
Effect.forEach(groupPaths, (groupPath) =>
|
|
328
343
|
Effect.gen(function* () {
|
|
344
|
+
const path = yield* Path.Path;
|
|
345
|
+
const convexDirectory = yield* ConvexDirectory.get;
|
|
346
|
+
const confectDirectory = yield* ConfectDirectory.get;
|
|
329
347
|
const group = yield* GroupPath.getGroupSpec(spec, groupPath);
|
|
330
348
|
|
|
331
349
|
const functionNames = pipe(
|
|
@@ -340,10 +358,25 @@ export const writeGroups = (
|
|
|
340
358
|
Array.map((fn) => fn.name),
|
|
341
359
|
);
|
|
342
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
|
+
|
|
343
374
|
yield* Effect.logDebug(`Generating group ${groupPath}...`);
|
|
344
375
|
yield* generateGroupModule({
|
|
345
376
|
groupPath,
|
|
346
377
|
functionNames,
|
|
378
|
+
registeredFunctionsImportPath,
|
|
379
|
+
useNode: groupPath.pathSegments[0] === "node",
|
|
347
380
|
});
|
|
348
381
|
yield* Effect.logDebug(`Group ${groupPath} generated`);
|
|
349
382
|
}),
|