@confect/cli 1.0.0-next.3 → 1.0.0-next.4
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 +9 -0
- package/dist/confect/codegen.mjs +83 -11
- package/dist/confect/codegen.mjs.map +1 -1
- package/dist/confect/dev.mjs +156 -131
- package/dist/confect/dev.mjs.map +1 -1
- package/dist/log.mjs +5 -4
- package/dist/log.mjs.map +1 -1
- package/dist/package.mjs +1 -1
- package/dist/templates.mjs +34 -8
- package/dist/templates.mjs.map +1 -1
- package/dist/utils.mjs +17 -11
- package/dist/utils.mjs.map +1 -1
- package/package.json +3 -3
- package/src/confect/codegen.ts +146 -21
- package/src/confect/dev.ts +310 -283
- package/src/log.ts +14 -37
- package/src/templates.ts +78 -6
- package/src/utils.ts +15 -1
package/src/confect/dev.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { Ansi, AnsiDoc } from "@effect/printer-ansi";
|
|
|
5
5
|
import {
|
|
6
6
|
Array,
|
|
7
7
|
Console,
|
|
8
|
-
|
|
8
|
+
Deferred,
|
|
9
9
|
Duration,
|
|
10
10
|
Effect,
|
|
11
11
|
Equal,
|
|
@@ -25,7 +25,7 @@ import * as tsx from "tsx/esm/api";
|
|
|
25
25
|
import type * as FunctionPath from "../FunctionPath";
|
|
26
26
|
import * as FunctionPaths from "../FunctionPaths";
|
|
27
27
|
import * as GroupPath from "../GroupPath";
|
|
28
|
-
import {
|
|
28
|
+
import { logFailure, logPending, logSuccess } from "../log";
|
|
29
29
|
import { ConfectDirectory } from "../services/ConfectDirectory";
|
|
30
30
|
import { ConvexDirectory } from "../services/ConvexDirectory";
|
|
31
31
|
import { ProjectRoot } from "../services/ProjectRoot";
|
|
@@ -37,10 +37,15 @@ import {
|
|
|
37
37
|
removeGroups,
|
|
38
38
|
writeGroups,
|
|
39
39
|
} from "../utils";
|
|
40
|
-
import {
|
|
40
|
+
import {
|
|
41
|
+
codegenHandler,
|
|
42
|
+
generateNodeApi,
|
|
43
|
+
generateNodeRegisteredFunctions,
|
|
44
|
+
} from "./codegen";
|
|
41
45
|
|
|
42
46
|
type Pending = {
|
|
43
47
|
readonly specDirty: boolean;
|
|
48
|
+
readonly nodeImplDirty: boolean;
|
|
44
49
|
readonly httpDirty: boolean;
|
|
45
50
|
readonly appDirty: boolean;
|
|
46
51
|
readonly cronsDirty: boolean;
|
|
@@ -49,56 +54,13 @@ type Pending = {
|
|
|
49
54
|
|
|
50
55
|
const pendingInit: Pending = {
|
|
51
56
|
specDirty: false,
|
|
57
|
+
nodeImplDirty: false,
|
|
52
58
|
httpDirty: false,
|
|
53
59
|
appDirty: false,
|
|
54
60
|
cronsDirty: false,
|
|
55
61
|
authDirty: false,
|
|
56
62
|
};
|
|
57
63
|
|
|
58
|
-
type FileChange = Data.TaggedEnum<{
|
|
59
|
-
OptionalFile: {
|
|
60
|
-
readonly change: "Added" | "Removed" | "Modified";
|
|
61
|
-
readonly filePath: string;
|
|
62
|
-
};
|
|
63
|
-
GroupModule: {
|
|
64
|
-
readonly change: "Added" | "Removed" | "Modified";
|
|
65
|
-
readonly filePath: string;
|
|
66
|
-
readonly functionsAdded: ReadonlyArray<FunctionPath.FunctionPath>;
|
|
67
|
-
readonly functionsRemoved: ReadonlyArray<FunctionPath.FunctionPath>;
|
|
68
|
-
};
|
|
69
|
-
}>;
|
|
70
|
-
|
|
71
|
-
const FileChange = Data.taggedEnum<FileChange>();
|
|
72
|
-
|
|
73
|
-
const logChangeReport = (changes: ReadonlyArray<FileChange>) =>
|
|
74
|
-
Effect.gen(function* () {
|
|
75
|
-
yield* logCompleted("Generated files are up-to-date");
|
|
76
|
-
|
|
77
|
-
yield* Effect.when(
|
|
78
|
-
Effect.forEach(changes, (change) =>
|
|
79
|
-
FileChange.$match(change, {
|
|
80
|
-
OptionalFile: ({ change: c, filePath }) =>
|
|
81
|
-
logFileChangeIndented(c, filePath),
|
|
82
|
-
GroupModule: ({
|
|
83
|
-
change: c,
|
|
84
|
-
filePath,
|
|
85
|
-
functionsAdded,
|
|
86
|
-
functionsRemoved,
|
|
87
|
-
}) =>
|
|
88
|
-
Effect.gen(function* () {
|
|
89
|
-
yield* logFileChangeIndented(c, filePath);
|
|
90
|
-
yield* Effect.forEach(functionsAdded, logFunctionAddedIndented);
|
|
91
|
-
yield* Effect.forEach(
|
|
92
|
-
functionsRemoved,
|
|
93
|
-
logFunctionRemovedIndented,
|
|
94
|
-
);
|
|
95
|
-
}),
|
|
96
|
-
}),
|
|
97
|
-
),
|
|
98
|
-
() => Array.isNonEmptyReadonlyArray(changes),
|
|
99
|
-
);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
64
|
const changeChar = (change: "Added" | "Removed" | "Modified") =>
|
|
103
65
|
Match.value(change).pipe(
|
|
104
66
|
Match.when("Added", () => ({ char: "+", color: Ansi.green })),
|
|
@@ -124,8 +86,8 @@ const logFileChangeIndented = (
|
|
|
124
86
|
|
|
125
87
|
yield* Console.log(
|
|
126
88
|
pipe(
|
|
127
|
-
AnsiDoc.
|
|
128
|
-
AnsiDoc.
|
|
89
|
+
AnsiDoc.char(char),
|
|
90
|
+
AnsiDoc.annotate(color),
|
|
129
91
|
AnsiDoc.catWithSpace(
|
|
130
92
|
AnsiDoc.hcat([
|
|
131
93
|
pipe(AnsiDoc.text(prefix), AnsiDoc.annotate(Ansi.blackBright)),
|
|
@@ -140,7 +102,7 @@ const logFileChangeIndented = (
|
|
|
140
102
|
const logFunctionAddedIndented = (functionPath: FunctionPath.FunctionPath) =>
|
|
141
103
|
Console.log(
|
|
142
104
|
pipe(
|
|
143
|
-
AnsiDoc.text("
|
|
105
|
+
AnsiDoc.text(" "),
|
|
144
106
|
AnsiDoc.cat(pipe(AnsiDoc.char("+"), AnsiDoc.annotate(Ansi.green))),
|
|
145
107
|
AnsiDoc.catWithSpace(
|
|
146
108
|
AnsiDoc.hcat([
|
|
@@ -158,7 +120,7 @@ const logFunctionAddedIndented = (functionPath: FunctionPath.FunctionPath) =>
|
|
|
158
120
|
const logFunctionRemovedIndented = (functionPath: FunctionPath.FunctionPath) =>
|
|
159
121
|
Console.log(
|
|
160
122
|
pipe(
|
|
161
|
-
AnsiDoc.text("
|
|
123
|
+
AnsiDoc.text(" "),
|
|
162
124
|
AnsiDoc.cat(pipe(AnsiDoc.char("-"), AnsiDoc.annotate(Ansi.red))),
|
|
163
125
|
AnsiDoc.catWithSpace(
|
|
164
126
|
AnsiDoc.hcat([
|
|
@@ -175,15 +137,17 @@ const logFunctionRemovedIndented = (functionPath: FunctionPath.FunctionPath) =>
|
|
|
175
137
|
|
|
176
138
|
export const dev = Command.make("dev", {}, () =>
|
|
177
139
|
Effect.gen(function* () {
|
|
140
|
+
yield* logPending("Performing initial sync…");
|
|
178
141
|
const initialFunctionPaths = yield* codegenHandler;
|
|
179
142
|
|
|
180
143
|
const pendingRef = yield* Ref.make<Pending>(pendingInit);
|
|
181
144
|
const signal = yield* Queue.sliding<void>(1);
|
|
145
|
+
const specWatcherRestartQueue = yield* Queue.sliding<void>(1);
|
|
182
146
|
|
|
183
147
|
yield* Effect.all(
|
|
184
148
|
[
|
|
185
|
-
specFileWatcher(signal, pendingRef),
|
|
186
|
-
confectDirectoryWatcher(signal, pendingRef),
|
|
149
|
+
specFileWatcher(signal, pendingRef, specWatcherRestartQueue),
|
|
150
|
+
confectDirectoryWatcher(signal, pendingRef, specWatcherRestartQueue),
|
|
187
151
|
syncLoop(signal, pendingRef, initialFunctionPaths),
|
|
188
152
|
],
|
|
189
153
|
{ concurrency: "unbounded" },
|
|
@@ -198,17 +162,30 @@ const syncLoop = (
|
|
|
198
162
|
) =>
|
|
199
163
|
Effect.gen(function* () {
|
|
200
164
|
const functionPathsRef = yield* Ref.make(initialFunctionPaths);
|
|
201
|
-
const
|
|
165
|
+
const initialSyncDone = yield* Deferred.make<void>();
|
|
202
166
|
|
|
203
167
|
return yield* Effect.forever(
|
|
204
168
|
Effect.gen(function* () {
|
|
205
169
|
yield* Effect.logDebug("Running sync loop...");
|
|
206
170
|
yield* Queue.take(signal);
|
|
207
171
|
|
|
172
|
+
const isDone = yield* Deferred.isDone(initialSyncDone);
|
|
173
|
+
yield* Effect.when(
|
|
174
|
+
logPending("Dependencies changed, reloading…"),
|
|
175
|
+
() => isDone,
|
|
176
|
+
);
|
|
177
|
+
yield* Deferred.succeed(initialSyncDone, undefined);
|
|
178
|
+
|
|
208
179
|
const pending = yield* Ref.getAndSet(pendingRef, pendingInit);
|
|
209
180
|
|
|
210
|
-
|
|
211
|
-
yield*
|
|
181
|
+
if (pending.specDirty || pending.nodeImplDirty) {
|
|
182
|
+
yield* generateNodeApi;
|
|
183
|
+
yield* generateNodeRegisteredFunctions;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const specResult: Option.Option<void> = yield* Effect.if(
|
|
187
|
+
pending.specDirty,
|
|
188
|
+
{
|
|
212
189
|
onTrue: () =>
|
|
213
190
|
loadSpec.pipe(
|
|
214
191
|
Effect.andThen(
|
|
@@ -231,104 +208,102 @@ const syncLoop = (
|
|
|
231
208
|
|
|
232
209
|
// Removed groups
|
|
233
210
|
yield* removeGroups(groupsRemoved);
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
)
|
|
246
|
-
|
|
247
|
-
functionsRemoved: Array.fromIterable(
|
|
248
|
-
HashSet.filter(functionsRemoved, (fp) =>
|
|
249
|
-
Equal.equals(fp.groupPath, gp),
|
|
250
|
-
),
|
|
211
|
+
yield* Effect.forEach(groupsRemoved, (gp) =>
|
|
212
|
+
Effect.gen(function* () {
|
|
213
|
+
const relativeModulePath =
|
|
214
|
+
yield* GroupPath.modulePath(gp);
|
|
215
|
+
const filePath = path.join(
|
|
216
|
+
convexDirectory,
|
|
217
|
+
relativeModulePath,
|
|
218
|
+
);
|
|
219
|
+
yield* logFileChangeIndented("Removed", filePath);
|
|
220
|
+
yield* Effect.forEach(
|
|
221
|
+
Array.fromIterable(
|
|
222
|
+
HashSet.filter(functionsRemoved, (fp) =>
|
|
223
|
+
Equal.equals(fp.groupPath, gp),
|
|
251
224
|
),
|
|
252
|
-
|
|
253
|
-
|
|
225
|
+
),
|
|
226
|
+
logFunctionRemovedIndented,
|
|
227
|
+
);
|
|
228
|
+
}),
|
|
254
229
|
);
|
|
255
230
|
|
|
256
231
|
// Added groups
|
|
257
232
|
yield* writeGroups(spec, groupsAdded);
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
)
|
|
270
|
-
|
|
271
|
-
HashSet.filter(functionsAdded, (fp) =>
|
|
272
|
-
Equal.equals(fp.groupPath, gp),
|
|
273
|
-
),
|
|
233
|
+
yield* Effect.forEach(groupsAdded, (gp) =>
|
|
234
|
+
Effect.gen(function* () {
|
|
235
|
+
const relativeModulePath =
|
|
236
|
+
yield* GroupPath.modulePath(gp);
|
|
237
|
+
const filePath = path.join(
|
|
238
|
+
convexDirectory,
|
|
239
|
+
relativeModulePath,
|
|
240
|
+
);
|
|
241
|
+
yield* logFileChangeIndented("Added", filePath);
|
|
242
|
+
yield* Effect.forEach(
|
|
243
|
+
Array.fromIterable(
|
|
244
|
+
HashSet.filter(functionsAdded, (fp) =>
|
|
245
|
+
Equal.equals(fp.groupPath, gp),
|
|
274
246
|
),
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
247
|
+
),
|
|
248
|
+
logFunctionAddedIndented,
|
|
249
|
+
);
|
|
250
|
+
}),
|
|
278
251
|
);
|
|
279
252
|
|
|
280
253
|
// Changed groups
|
|
281
254
|
yield* writeGroups(spec, groupsChanged);
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
)
|
|
294
|
-
|
|
295
|
-
HashSet.filter(functionsAdded, (fp) =>
|
|
296
|
-
Equal.equals(fp.groupPath, gp),
|
|
297
|
-
),
|
|
255
|
+
yield* Effect.forEach(groupsChanged, (gp) =>
|
|
256
|
+
Effect.gen(function* () {
|
|
257
|
+
const relativeModulePath =
|
|
258
|
+
yield* GroupPath.modulePath(gp);
|
|
259
|
+
const filePath = path.join(
|
|
260
|
+
convexDirectory,
|
|
261
|
+
relativeModulePath,
|
|
262
|
+
);
|
|
263
|
+
yield* logFileChangeIndented("Modified", filePath);
|
|
264
|
+
yield* Effect.forEach(
|
|
265
|
+
Array.fromIterable(
|
|
266
|
+
HashSet.filter(functionsAdded, (fp) =>
|
|
267
|
+
Equal.equals(fp.groupPath, gp),
|
|
298
268
|
),
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
269
|
+
),
|
|
270
|
+
logFunctionAddedIndented,
|
|
271
|
+
);
|
|
272
|
+
yield* Effect.forEach(
|
|
273
|
+
Array.fromIterable(
|
|
274
|
+
HashSet.filter(functionsRemoved, (fp) =>
|
|
275
|
+
Equal.equals(fp.groupPath, gp),
|
|
303
276
|
),
|
|
304
|
-
|
|
305
|
-
|
|
277
|
+
),
|
|
278
|
+
logFunctionRemovedIndented,
|
|
279
|
+
);
|
|
280
|
+
}),
|
|
306
281
|
);
|
|
307
282
|
|
|
308
283
|
yield* Ref.set(functionPathsRef, current);
|
|
309
284
|
|
|
310
|
-
return Option.some(
|
|
311
|
-
...removedChanges,
|
|
312
|
-
...addedChanges,
|
|
313
|
-
...changedChanges,
|
|
314
|
-
]);
|
|
285
|
+
return Option.some(undefined);
|
|
315
286
|
}),
|
|
316
287
|
),
|
|
317
288
|
Effect.catchTag("SpecImportFailedError", () =>
|
|
318
|
-
|
|
289
|
+
logFailure("Spec import failed").pipe(
|
|
319
290
|
Effect.as(Option.none()),
|
|
320
291
|
),
|
|
321
292
|
),
|
|
322
293
|
Effect.catchTag("SpecFileDoesNotExportSpecError", () =>
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
),
|
|
294
|
+
logFailure(
|
|
295
|
+
"Spec file does not default export a Convex spec",
|
|
296
|
+
).pipe(Effect.as(Option.none())),
|
|
297
|
+
),
|
|
298
|
+
Effect.catchTag("NodeSpecFileDoesNotExportSpecError", () =>
|
|
299
|
+
logFailure(
|
|
300
|
+
"Node spec file does not default export a Node spec",
|
|
301
|
+
).pipe(Effect.as(Option.none())),
|
|
326
302
|
),
|
|
327
303
|
),
|
|
328
|
-
onFalse: () => Effect.succeed(Option.some(
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
const specChanges = Option.getOrElse(specResult, () => []);
|
|
304
|
+
onFalse: () => Effect.succeed(Option.some(undefined)),
|
|
305
|
+
},
|
|
306
|
+
);
|
|
332
307
|
|
|
333
308
|
const dirtyOptionalFiles = [
|
|
334
309
|
...(pending.httpDirty
|
|
@@ -345,42 +320,22 @@ const syncLoop = (
|
|
|
345
320
|
: []),
|
|
346
321
|
];
|
|
347
322
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
Effect.all(dirtyOptionalFiles, {
|
|
352
|
-
concurrency: "unbounded",
|
|
353
|
-
}),
|
|
354
|
-
Effect.map(Array.getSomes),
|
|
355
|
-
)
|
|
356
|
-
: [];
|
|
357
|
-
|
|
358
|
-
yield* Ref.update(changesRef, (prev) => [
|
|
359
|
-
...prev,
|
|
360
|
-
...specChanges,
|
|
361
|
-
...optionalChanges,
|
|
362
|
-
]);
|
|
323
|
+
yield* Array.isNonEmptyReadonlyArray(dirtyOptionalFiles)
|
|
324
|
+
? Effect.all(dirtyOptionalFiles, { concurrency: "unbounded" })
|
|
325
|
+
: Effect.void;
|
|
363
326
|
|
|
364
327
|
yield* Option.match(specResult, {
|
|
365
|
-
onSome: () =>
|
|
366
|
-
|
|
367
|
-
const pendingSize = yield* Queue.size(signal);
|
|
368
|
-
yield* Effect.when(
|
|
369
|
-
Effect.gen(function* () {
|
|
370
|
-
const allChanges = yield* Ref.getAndSet(changesRef, []);
|
|
371
|
-
yield* logChangeReport(allChanges);
|
|
372
|
-
}),
|
|
373
|
-
() => pendingSize === 0,
|
|
374
|
-
);
|
|
375
|
-
}),
|
|
376
|
-
onNone: () => Ref.set(changesRef, []),
|
|
328
|
+
onSome: () => logSuccess("Generated files are up-to-date"),
|
|
329
|
+
onNone: () => Effect.void,
|
|
377
330
|
});
|
|
378
331
|
}),
|
|
379
332
|
);
|
|
380
333
|
});
|
|
381
334
|
|
|
382
335
|
const loadSpec = Effect.gen(function* () {
|
|
336
|
+
const fs = yield* FileSystem.FileSystem;
|
|
383
337
|
const path = yield* Path.Path;
|
|
338
|
+
const confectDirectory = yield* ConfectDirectory.get;
|
|
384
339
|
const specPathUrl = yield* path.toFileUrl(yield* getSpecPath);
|
|
385
340
|
const specModule = yield* Effect.tryPromise({
|
|
386
341
|
try: () => tsx.tsImport(specPathUrl.href, import.meta.url),
|
|
@@ -388,11 +343,19 @@ const loadSpec = Effect.gen(function* () {
|
|
|
388
343
|
});
|
|
389
344
|
const spec = specModule.default;
|
|
390
345
|
|
|
391
|
-
if (Spec.
|
|
392
|
-
return
|
|
393
|
-
} else {
|
|
394
|
-
return yield* Effect.fail(new SpecFileDoesNotExportSpecError());
|
|
346
|
+
if (!Spec.isConvexSpec(spec)) {
|
|
347
|
+
return yield* new SpecFileDoesNotExportSpecError();
|
|
395
348
|
}
|
|
349
|
+
|
|
350
|
+
const nodeImplPath = path.join(confectDirectory, "nodeImpl.ts");
|
|
351
|
+
const nodeImplExists = yield* fs.exists(nodeImplPath);
|
|
352
|
+
const nodeSpecOption = yield* loadNodeSpec;
|
|
353
|
+
const mergedSpec = Option.match(nodeSpecOption, {
|
|
354
|
+
onNone: () => spec,
|
|
355
|
+
onSome: (nodeSpec) => (nodeImplExists ? Spec.merge(spec, nodeSpec) : spec),
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
return mergedSpec;
|
|
396
359
|
});
|
|
397
360
|
|
|
398
361
|
const getSpecPath = Effect.gen(function* () {
|
|
@@ -402,87 +365,152 @@ const getSpecPath = Effect.gen(function* () {
|
|
|
402
365
|
return path.join(confectDirectory, "spec.ts");
|
|
403
366
|
});
|
|
404
367
|
|
|
368
|
+
const getNodeSpecPath = Effect.gen(function* () {
|
|
369
|
+
const path = yield* Path.Path;
|
|
370
|
+
const confectDirectory = yield* ConfectDirectory.get;
|
|
371
|
+
|
|
372
|
+
return path.join(confectDirectory, "nodeSpec.ts");
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
const loadNodeSpec = Effect.gen(function* () {
|
|
376
|
+
const fs = yield* FileSystem.FileSystem;
|
|
377
|
+
const path = yield* Path.Path;
|
|
378
|
+
const nodeSpecPath = yield* getNodeSpecPath;
|
|
379
|
+
|
|
380
|
+
if (!(yield* fs.exists(nodeSpecPath))) {
|
|
381
|
+
return Option.none();
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const nodeSpecPathUrl = yield* path.toFileUrl(nodeSpecPath);
|
|
385
|
+
const nodeSpecModule = yield* Effect.tryPromise({
|
|
386
|
+
try: () => tsx.tsImport(nodeSpecPathUrl.href, import.meta.url),
|
|
387
|
+
catch: (error) => new SpecImportFailedError({ error }),
|
|
388
|
+
});
|
|
389
|
+
const nodeSpec = nodeSpecModule.default;
|
|
390
|
+
|
|
391
|
+
if (!Spec.isNodeSpec(nodeSpec)) {
|
|
392
|
+
return yield* new NodeSpecFileDoesNotExportSpecError();
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return Option.some(nodeSpec);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
const esbuildOptions = (entryPoint: string) => ({
|
|
399
|
+
entryPoints: [entryPoint],
|
|
400
|
+
bundle: true,
|
|
401
|
+
write: false,
|
|
402
|
+
metafile: true,
|
|
403
|
+
platform: "node" as const,
|
|
404
|
+
format: "esm" as const,
|
|
405
|
+
logLevel: "silent" as const,
|
|
406
|
+
external: ["@confect/core", "@confect/server", "effect", "@effect/*"],
|
|
407
|
+
plugins: [
|
|
408
|
+
{
|
|
409
|
+
name: "notify-rebuild",
|
|
410
|
+
setup(build: esbuild.PluginBuild) {
|
|
411
|
+
build.onEnd((result) => {
|
|
412
|
+
if (result.errors.length === 0) {
|
|
413
|
+
(build as { _emit?: (v: void) => void })._emit?.();
|
|
414
|
+
} else {
|
|
415
|
+
Effect.runPromise(
|
|
416
|
+
Effect.gen(function* () {
|
|
417
|
+
const formattedMessages = yield* Effect.promise(() =>
|
|
418
|
+
esbuild.formatMessages(result.errors, {
|
|
419
|
+
kind: "error",
|
|
420
|
+
color: true,
|
|
421
|
+
terminalWidth: 80,
|
|
422
|
+
}),
|
|
423
|
+
);
|
|
424
|
+
const output = formatBuildErrors(
|
|
425
|
+
result.errors,
|
|
426
|
+
formattedMessages,
|
|
427
|
+
);
|
|
428
|
+
yield* Console.error("\n" + output + "\n");
|
|
429
|
+
yield* logFailure("Build errors found");
|
|
430
|
+
}),
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
},
|
|
435
|
+
},
|
|
436
|
+
],
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
const createSpecWatcher = (entryPoint: string) =>
|
|
440
|
+
Stream.asyncPush<void>(
|
|
441
|
+
(emit) =>
|
|
442
|
+
Effect.acquireRelease(
|
|
443
|
+
Effect.promise(async () => {
|
|
444
|
+
const opts = esbuildOptions(entryPoint);
|
|
445
|
+
const plugin = opts.plugins[0];
|
|
446
|
+
const originalSetup = plugin!.setup!;
|
|
447
|
+
(plugin as { setup: (build: esbuild.PluginBuild) => void }).setup = (
|
|
448
|
+
build,
|
|
449
|
+
) => {
|
|
450
|
+
(build as { _emit?: (v: void) => void })._emit = () =>
|
|
451
|
+
emit.single();
|
|
452
|
+
return originalSetup(build);
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
const ctx = await esbuild.context({
|
|
456
|
+
...opts,
|
|
457
|
+
plugins: [plugin],
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
await ctx.watch();
|
|
461
|
+
return ctx;
|
|
462
|
+
}),
|
|
463
|
+
(ctx) =>
|
|
464
|
+
Effect.promise(() => ctx.dispose()).pipe(
|
|
465
|
+
Effect.tap(() => Effect.logDebug("esbuild watcher disposed")),
|
|
466
|
+
),
|
|
467
|
+
),
|
|
468
|
+
{ bufferSize: 1, strategy: "sliding" },
|
|
469
|
+
);
|
|
470
|
+
|
|
471
|
+
type SpecWatcherEvent = "change" | "restart";
|
|
472
|
+
|
|
405
473
|
const specFileWatcher = (
|
|
406
474
|
signal: Queue.Queue<void>,
|
|
407
475
|
pendingRef: Ref.Ref<Pending>,
|
|
476
|
+
specWatcherRestartQueue: Queue.Queue<void>,
|
|
408
477
|
) =>
|
|
409
|
-
Effect.
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
const formattedMessages = yield* Effect.promise(
|
|
442
|
-
() =>
|
|
443
|
-
esbuild.formatMessages(result.errors, {
|
|
444
|
-
kind: "error",
|
|
445
|
-
color: true,
|
|
446
|
-
terminalWidth: 80,
|
|
447
|
-
}),
|
|
448
|
-
);
|
|
449
|
-
const output = formatBuildErrors(
|
|
450
|
-
result.errors,
|
|
451
|
-
formattedMessages,
|
|
452
|
-
);
|
|
453
|
-
yield* Console.error("\n" + output + "\n");
|
|
454
|
-
}),
|
|
455
|
-
);
|
|
456
|
-
}
|
|
457
|
-
});
|
|
458
|
-
},
|
|
459
|
-
},
|
|
460
|
-
],
|
|
461
|
-
});
|
|
462
|
-
|
|
463
|
-
await ctx.watch();
|
|
464
|
-
|
|
465
|
-
return ctx;
|
|
466
|
-
}),
|
|
467
|
-
(ctx) =>
|
|
468
|
-
Effect.promise(() => ctx.dispose()).pipe(
|
|
469
|
-
Effect.tap(() => Effect.logDebug("esbuild watcher disposed")),
|
|
470
|
-
),
|
|
478
|
+
Effect.forever(
|
|
479
|
+
Effect.gen(function* () {
|
|
480
|
+
const fs = yield* FileSystem.FileSystem;
|
|
481
|
+
const specPath = yield* getSpecPath;
|
|
482
|
+
const nodeSpecPath = yield* getNodeSpecPath;
|
|
483
|
+
const nodeSpecExists = yield* fs.exists(nodeSpecPath);
|
|
484
|
+
|
|
485
|
+
const specWatcher = createSpecWatcher(specPath);
|
|
486
|
+
const nodeSpecWatcher = nodeSpecExists
|
|
487
|
+
? createSpecWatcher(nodeSpecPath)
|
|
488
|
+
: Stream.empty;
|
|
489
|
+
|
|
490
|
+
const specChanges = pipe(
|
|
491
|
+
Stream.merge(specWatcher, nodeSpecWatcher),
|
|
492
|
+
Stream.map((): SpecWatcherEvent => "change"),
|
|
493
|
+
);
|
|
494
|
+
const restartStream = pipe(
|
|
495
|
+
Stream.fromQueue(specWatcherRestartQueue),
|
|
496
|
+
Stream.map((): SpecWatcherEvent => "restart"),
|
|
497
|
+
);
|
|
498
|
+
|
|
499
|
+
yield* pipe(
|
|
500
|
+
Stream.merge(specChanges, restartStream),
|
|
501
|
+
Stream.debounce(Duration.millis(200)),
|
|
502
|
+
Stream.takeUntil((event): event is "restart" => event === "restart"),
|
|
503
|
+
Stream.runForEach((event) =>
|
|
504
|
+
event === "change"
|
|
505
|
+
? Ref.update(pendingRef, (pending) => ({
|
|
506
|
+
...pending,
|
|
507
|
+
specDirty: true,
|
|
508
|
+
})).pipe(Effect.andThen(Queue.offer(signal, undefined)))
|
|
509
|
+
: Effect.void,
|
|
471
510
|
),
|
|
472
|
-
|
|
473
|
-
)
|
|
474
|
-
|
|
475
|
-
yield* pipe(
|
|
476
|
-
specChanges,
|
|
477
|
-
Stream.debounce(Duration.millis(200)),
|
|
478
|
-
Stream.runForEach(() =>
|
|
479
|
-
Ref.update(pendingRef, (pending) => ({
|
|
480
|
-
...pending,
|
|
481
|
-
specDirty: true,
|
|
482
|
-
})).pipe(Effect.andThen(Queue.offer(signal, undefined))),
|
|
483
|
-
),
|
|
484
|
-
);
|
|
485
|
-
});
|
|
511
|
+
);
|
|
512
|
+
}),
|
|
513
|
+
);
|
|
486
514
|
|
|
487
515
|
const formatBuildError = (
|
|
488
516
|
error: esbuild.Message | undefined,
|
|
@@ -498,14 +526,10 @@ const formatBuildError = (
|
|
|
498
526
|
Array.findFirstIndex(lines, (l) => pipe(l, String.trim, String.isNonEmpty)),
|
|
499
527
|
Option.match({
|
|
500
528
|
onNone: () => lines,
|
|
501
|
-
onSome: (
|
|
529
|
+
onSome: (index) => Array.modify(lines, index, () => redErrorText),
|
|
502
530
|
}),
|
|
503
531
|
);
|
|
504
|
-
return pipe(
|
|
505
|
-
replaced,
|
|
506
|
-
Array.map((l) => (pipe(l, String.trim, String.isNonEmpty) ? ` ${l}` : l)),
|
|
507
|
-
Array.join("\n"),
|
|
508
|
-
);
|
|
532
|
+
return pipe(replaced, Array.join("\n"));
|
|
509
533
|
};
|
|
510
534
|
|
|
511
535
|
const formatBuildErrors = (
|
|
@@ -519,15 +543,22 @@ const formatBuildErrors = (
|
|
|
519
543
|
String.trimEnd,
|
|
520
544
|
);
|
|
521
545
|
|
|
522
|
-
export class SpecFileDoesNotExportSpecError extends Schema.TaggedError<SpecFileDoesNotExportSpecError>(
|
|
546
|
+
export class SpecFileDoesNotExportSpecError extends Schema.TaggedError<SpecFileDoesNotExportSpecError>()(
|
|
523
547
|
"SpecFileDoesNotExportSpecError",
|
|
524
|
-
|
|
548
|
+
{},
|
|
549
|
+
) {}
|
|
525
550
|
|
|
526
|
-
export class
|
|
551
|
+
export class NodeSpecFileDoesNotExportSpecError extends Schema.TaggedError<NodeSpecFileDoesNotExportSpecError>()(
|
|
552
|
+
"NodeSpecFileDoesNotExportSpecError",
|
|
553
|
+
{},
|
|
554
|
+
) {}
|
|
555
|
+
|
|
556
|
+
export class SpecImportFailedError extends Schema.TaggedError<SpecImportFailedError>()(
|
|
527
557
|
"SpecImportFailedError",
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
}
|
|
558
|
+
{
|
|
559
|
+
error: Schema.Unknown,
|
|
560
|
+
},
|
|
561
|
+
) {}
|
|
531
562
|
|
|
532
563
|
const syncOptionalFile = (generate: typeof generateHttp, convexFile: string) =>
|
|
533
564
|
pipe(
|
|
@@ -536,16 +567,9 @@ const syncOptionalFile = (generate: typeof generateHttp, convexFile: string) =>
|
|
|
536
567
|
Option.match({
|
|
537
568
|
onSome: ({ change, convexFilePath }) =>
|
|
538
569
|
Match.value(change).pipe(
|
|
539
|
-
Match.when("Unchanged", () => Effect.
|
|
570
|
+
Match.when("Unchanged", () => Effect.void),
|
|
540
571
|
Match.whenOr("Added", "Modified", (addedOrModified) =>
|
|
541
|
-
|
|
542
|
-
Option.some(
|
|
543
|
-
FileChange.OptionalFile({
|
|
544
|
-
change: addedOrModified,
|
|
545
|
-
filePath: convexFilePath,
|
|
546
|
-
}),
|
|
547
|
-
),
|
|
548
|
-
),
|
|
572
|
+
logFileChangeIndented(addedOrModified, convexFilePath),
|
|
549
573
|
),
|
|
550
574
|
Match.exhaustive,
|
|
551
575
|
),
|
|
@@ -558,15 +582,7 @@ const syncOptionalFile = (generate: typeof generateHttp, convexFile: string) =>
|
|
|
558
582
|
|
|
559
583
|
if (yield* fs.exists(convexFilePath)) {
|
|
560
584
|
yield* fs.remove(convexFilePath);
|
|
561
|
-
|
|
562
|
-
return Option.some(
|
|
563
|
-
FileChange.OptionalFile({
|
|
564
|
-
change: "Removed",
|
|
565
|
-
filePath: convexFilePath,
|
|
566
|
-
}),
|
|
567
|
-
);
|
|
568
|
-
} else {
|
|
569
|
-
return Option.none();
|
|
585
|
+
yield* logFileChangeIndented("Removed", convexFilePath);
|
|
570
586
|
}
|
|
571
587
|
}),
|
|
572
588
|
}),
|
|
@@ -578,34 +594,45 @@ const optionalConfectFiles: ReadonlyRecord<string, keyof Pending> = {
|
|
|
578
594
|
"app.ts": "appDirty",
|
|
579
595
|
"crons.ts": "cronsDirty",
|
|
580
596
|
"auth.ts": "authDirty",
|
|
597
|
+
"nodeSpec.ts": "specDirty",
|
|
598
|
+
"nodeImpl.ts": "nodeImplDirty",
|
|
581
599
|
};
|
|
582
600
|
|
|
583
601
|
const confectDirectoryWatcher = (
|
|
584
602
|
signal: Queue.Queue<void>,
|
|
585
603
|
pendingRef: Ref.Ref<Pending>,
|
|
604
|
+
specWatcherRestartQueue: Queue.Queue<void>,
|
|
586
605
|
) =>
|
|
587
606
|
Effect.gen(function* () {
|
|
588
607
|
const fs = yield* FileSystem.FileSystem;
|
|
608
|
+
const path = yield* Path.Path;
|
|
589
609
|
const confectDirectory = yield* ConfectDirectory.get;
|
|
590
610
|
|
|
591
611
|
yield* pipe(
|
|
592
612
|
fs.watch(confectDirectory),
|
|
593
|
-
Stream.runForEach((event) =>
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
613
|
+
Stream.runForEach((event) => {
|
|
614
|
+
const basename = path.basename(event.path);
|
|
615
|
+
const pendingKey = optionalConfectFiles[basename];
|
|
616
|
+
|
|
617
|
+
if (pendingKey !== undefined) {
|
|
618
|
+
return pipe(
|
|
619
|
+
pendingRef,
|
|
620
|
+
Ref.update((pending) => {
|
|
621
|
+
const next = { ...pending, [pendingKey]: true };
|
|
622
|
+
if (basename === "nodeImpl.ts") {
|
|
623
|
+
return { ...next, specDirty: true };
|
|
624
|
+
}
|
|
625
|
+
return next;
|
|
626
|
+
}),
|
|
627
|
+
Effect.andThen(Queue.offer(signal, undefined)),
|
|
628
|
+
Effect.andThen(
|
|
629
|
+
basename === "nodeSpec.ts"
|
|
630
|
+
? Queue.offer(specWatcherRestartQueue, undefined)
|
|
631
|
+
: Effect.void,
|
|
632
|
+
),
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
return Effect.void;
|
|
636
|
+
}),
|
|
610
637
|
);
|
|
611
638
|
});
|