@sjcx/vorma 0.0.0-pre.7 → 0.0.0-pre.8
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/dist/build/cli.js +67 -87
- package/dist/build/dev/dev-protocol.d.ts +1 -1
- package/dist/build/generation/compile-application.d.ts +1 -1
- package/dist/build/output/dist-dir.d.ts +1 -1
- package/dist/build/patterns/nested-route-chains.d.ts +2 -2
- package/dist/build/patterns/pattern-language.d.ts +1 -1
- package/dist/build/patterns/route-patterns.d.ts +1 -1
- package/package.json +2 -2
package/dist/build/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { i as readConfigFilePath, r as checkVormaPlugin } from "../plugin-api-Btb0teGk.js";
|
|
3
3
|
import { t as VormaConfigSchema } from "../config-FbabCxBO.js";
|
|
4
4
|
import { NodeChildProcessSpawner, NodeCrypto, NodeFileSystem, NodePath, NodeRuntime, NodeSocketServer, NodeStdio, NodeTerminal } from "@effect/platform-node-shared";
|
|
5
|
-
import { Cause, Console, Deferred, Effect, Equal, Exit, Fiber, FileSystem, Layer,
|
|
5
|
+
import { Cause, Console, Deferred, Effect, Equal, Exit, Fiber, FileSystem, Layer, Order, Path, Predicate, Queue, Record, Result, Schedule, Schema, Scope, ScopedRef, Stream } from "effect";
|
|
6
6
|
import { Argument, Command } from "effect/cli";
|
|
7
7
|
import { stripVTControlCharacters } from "node:util";
|
|
8
8
|
import * as FileSystem$1 from "effect/FileSystem";
|
|
@@ -24,7 +24,7 @@ import { Writable } from "node:stream";
|
|
|
24
24
|
import { ChildProcess as ChildProcess$1 } from "effect/process";
|
|
25
25
|
import * as NativeHttpMethod from "effect/http/HttpMethod";
|
|
26
26
|
import { DEV_CONTROLS_PROTOCOL, DOCUMENT_PROTOCOL, GENERATED_TYPES_PROTOCOL, HTTP_PROTOCOL, PATTERN_PROTOCOL, encodeRoutePattern, formatPathWireError } from "@sjcx/vorma-client/unstable";
|
|
27
|
-
import { compileNestedPattern, formatPatternCompileError } from "
|
|
27
|
+
import { compileNestedPattern, formatPatternCompileError } from "primitives-ts/matcher";
|
|
28
28
|
import { createServer as createServer$1, request } from "node:http";
|
|
29
29
|
import { createConnection } from "node:net";
|
|
30
30
|
import { watch } from "chokidar";
|
|
@@ -242,7 +242,7 @@ const ARTIFACT_PROTOCOL = {
|
|
|
242
242
|
/** Inside the build directory: the published static public assets. */
|
|
243
243
|
publicDirName: "public",
|
|
244
244
|
/** Inside the build directory: the lock that a running build or development session holds. */
|
|
245
|
-
|
|
245
|
+
lockDirName: "build.lock",
|
|
246
246
|
/** Inside the build directory: the directory in which a running build stages its output. */
|
|
247
247
|
stagingDirName: "staging",
|
|
248
248
|
publicOutputFileNamePrefix: PUBLIC_OUTPUT_FILE_NAME_PREFIX,
|
|
@@ -515,14 +515,14 @@ const Owner = Schema.fromJsonString(Schema.Struct({
|
|
|
515
515
|
dir: Schema.String,
|
|
516
516
|
/**
|
|
517
517
|
* Identifies one acquisition, so that a process releases only the lock
|
|
518
|
-
* that it took. A lock
|
|
519
|
-
*
|
|
518
|
+
* that it took. A flat lock file is readable for diagnostics but cannot
|
|
519
|
+
* be removed atomically by owner.
|
|
520
520
|
*/
|
|
521
521
|
token: Schema.optionalKey(Schema.NonEmptyString)
|
|
522
522
|
}));
|
|
523
523
|
function getRemovalCmd(lockPath) {
|
|
524
|
-
if (platform === "win32") return `PowerShell: Remove-Item -LiteralPath '${lockPath.replaceAll("'", "''")}'`;
|
|
525
|
-
return `rm -- '${lockPath.replaceAll("'", "'\"'\"'")}'`;
|
|
524
|
+
if (platform === "win32") return `PowerShell: Remove-Item -Recurse -LiteralPath '${lockPath.replaceAll("'", "''")}'`;
|
|
525
|
+
return `rm -r -- '${lockPath.replaceAll("'", "'\"'\"'")}'`;
|
|
526
526
|
}
|
|
527
527
|
/** Whether a process with this id exists; a process of another user exists but refuses the signal. */
|
|
528
528
|
function testProcessExists(processId) {
|
|
@@ -533,61 +533,35 @@ function testProcessExists(processId) {
|
|
|
533
533
|
return error.code === "EPERM";
|
|
534
534
|
}
|
|
535
535
|
}
|
|
536
|
-
/**
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
* lock that records no owner, and a lock whose owner `testOwnerRemovable`
|
|
540
|
-
* rejects stay as they are. The lock moves to `claimedPath` first, so that of
|
|
541
|
-
* two contenders only one removes it, and the moved file is read again: a lock
|
|
542
|
-
* that another process created in the meantime is moved back and kept.
|
|
543
|
-
*/
|
|
544
|
-
function removeLockIfRemovable(input) {
|
|
545
|
-
const { fileSystem, lockPath, claimedPath, testOwnerRemovable } = input;
|
|
546
|
-
function readOwner(path) {
|
|
547
|
-
return fileSystem.readFileString(path).pipe(Effect.flatMap(Schema.decodeUnknownEffect(Owner)), Effect.option);
|
|
548
|
-
}
|
|
536
|
+
/** Reads complete directory records and flat lock files used for diagnostics. */
|
|
537
|
+
function readLockRecord(input) {
|
|
538
|
+
const { fileSystem, path, lockPath } = input;
|
|
549
539
|
return Effect.gen(function* () {
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
}));
|
|
540
|
+
if ((yield* fileSystem.stat(lockPath)).type !== "Directory") return {
|
|
541
|
+
owner: yield* fileSystem.readFileString(lockPath).pipe(Effect.flatMap(Schema.decodeUnknownEffect(Owner))),
|
|
542
|
+
recordPath: void 0
|
|
543
|
+
};
|
|
544
|
+
const entries = yield* fileSystem.readDirectory(lockPath);
|
|
545
|
+
if (entries.length !== 1) return;
|
|
546
|
+
const recordPath = path.join(lockPath, entries[0]);
|
|
547
|
+
const owner = yield* fileSystem.readFileString(recordPath).pipe(Effect.flatMap(Schema.decodeUnknownEffect(Owner)));
|
|
548
|
+
return owner.token !== void 0 && entries[0] === getOwnerFileName(owner.token) ? {
|
|
549
|
+
owner,
|
|
550
|
+
recordPath
|
|
551
|
+
} : void 0;
|
|
552
|
+
}).pipe(Effect.catch(() => Effect.succeed(void 0)));
|
|
563
553
|
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
* that the lock's process ran on this machine, as a build directory is local to
|
|
567
|
-
* the machine that builds it.
|
|
568
|
-
*/
|
|
569
|
-
function removeStaleLock(input) {
|
|
570
|
-
return removeLockIfRemovable({
|
|
571
|
-
fileSystem: input.fileSystem,
|
|
572
|
-
lockPath: input.lockPath,
|
|
573
|
-
claimedPath: `${input.lockPath}.stale-${pid}`,
|
|
574
|
-
testOwnerRemovable: (owner) => owner.pid === input.pid
|
|
575
|
-
}).pipe(Effect.catch(() => Effect.succeed(false)));
|
|
554
|
+
function getOwnerFileName(token) {
|
|
555
|
+
return `owner-${token}.json`;
|
|
576
556
|
}
|
|
577
557
|
/**
|
|
578
|
-
*
|
|
579
|
-
*
|
|
580
|
-
*
|
|
558
|
+
* Only the process that removes the unique owner record may remove its directory.
|
|
559
|
+
* A delayed cleanup cannot remove a later owner's record, so it cannot expose
|
|
560
|
+
* the later build to another writer. The directory stays locked until removal
|
|
561
|
+
* completes. A flat lock file has no such conditional-removal operation.
|
|
581
562
|
*/
|
|
582
|
-
function
|
|
583
|
-
return
|
|
584
|
-
fileSystem: input.fileSystem,
|
|
585
|
-
lockPath: input.lockPath,
|
|
586
|
-
claimedPath: `${input.lockPath}.release-${input.owner.token}`,
|
|
587
|
-
testOwnerRemovable: (recorded) => recorded.token === input.owner.token
|
|
588
|
-
}).pipe(Effect.catch((cause) => {
|
|
589
|
-
return Effect.logError(`Could not remove Vorma lock ${input.lockPath}. If the owner has stopped, run ${getRemovalCmd(input.lockPath)}`, cause);
|
|
590
|
-
}));
|
|
563
|
+
function removeLockRecord(input) {
|
|
564
|
+
return input.fileSystem.remove(input.recordPath).pipe(Effect.andThen(input.fileSystem.remove(input.lockPath, { recursive: true })), Effect.as(true), Effect.catchReason("PlatformError", "NotFound", () => Effect.succeed(false)));
|
|
591
565
|
}
|
|
592
566
|
/** Resolves aliases even when the output directory has not been created. */
|
|
593
567
|
function canonicalizeOutputPath(filePath) {
|
|
@@ -629,10 +603,10 @@ function checkOutputPath(input) {
|
|
|
629
603
|
});
|
|
630
604
|
}
|
|
631
605
|
/** The entries that Vorma writes in a build directory. */
|
|
632
|
-
const
|
|
606
|
+
const VORMA_BUILD_DIR_ENTRY_NAMES = /* @__PURE__ */ new Set([
|
|
633
607
|
ARTIFACT_PROTOCOL.buildMetadataFileName,
|
|
634
608
|
ARTIFACT_PROTOCOL.publicDirName,
|
|
635
|
-
ARTIFACT_PROTOCOL.
|
|
609
|
+
ARTIFACT_PROTOCOL.lockDirName,
|
|
636
610
|
ARTIFACT_PROTOCOL.stagingDirName
|
|
637
611
|
]);
|
|
638
612
|
/**
|
|
@@ -646,7 +620,7 @@ function checkBuildDirContents(input) {
|
|
|
646
620
|
const entries = yield* input.fileSystem.readDirectory(input.dir).pipe(Effect.catchReason("PlatformError", "NotFound", () => {
|
|
647
621
|
return Effect.succeed([]);
|
|
648
622
|
}));
|
|
649
|
-
if (entries.includes(ARTIFACT_PROTOCOL.buildMetadataFileName) || entries.every((entry) =>
|
|
623
|
+
if (entries.includes(ARTIFACT_PROTOCOL.buildMetadataFileName) || entries.every((entry) => VORMA_BUILD_DIR_ENTRY_NAMES.has(entry))) return;
|
|
650
624
|
return yield* Effect.fail({
|
|
651
625
|
_tag: "OutputOwnershipIssue",
|
|
652
626
|
dir: input.dir,
|
|
@@ -782,7 +756,7 @@ function createStagedBuildDir(input) {
|
|
|
782
756
|
discard: true
|
|
783
757
|
});
|
|
784
758
|
yield* Effect.forEach(yield* fileSystem.readDirectory(buildDir), (entry) => {
|
|
785
|
-
return
|
|
759
|
+
return VORMA_BUILD_DIR_ENTRY_NAMES.has(entry) ? Effect.void : fileSystem.remove(path.join(buildDir, entry), {
|
|
786
760
|
recursive: true,
|
|
787
761
|
force: true
|
|
788
762
|
});
|
|
@@ -816,31 +790,33 @@ function acquireOutputOwnership(input) {
|
|
|
816
790
|
dir
|
|
817
791
|
});
|
|
818
792
|
yield* fileSystem.makeDirectory(dir, { recursive: true });
|
|
819
|
-
const lockPath = path.join(dir, ARTIFACT_PROTOCOL.
|
|
793
|
+
const lockPath = path.join(dir, ARTIFACT_PROTOCOL.lockDirName);
|
|
820
794
|
const owner = {
|
|
821
795
|
pid,
|
|
822
796
|
kind: input.kind,
|
|
823
797
|
dir,
|
|
824
798
|
token: randomUUID()
|
|
825
799
|
};
|
|
826
|
-
const createLock =
|
|
827
|
-
flag: "wx",
|
|
828
|
-
mode: 384
|
|
829
|
-
})).pipe(Effect.asVoid, Effect.catch((cause) => {
|
|
800
|
+
const createLock = fileSystem.makeDirectory(lockPath, { mode: 448 }).pipe(Effect.catch((cause) => {
|
|
830
801
|
return Effect.gen(function* () {
|
|
831
802
|
if (cause.reason._tag !== "AlreadyExists") return yield* Effect.fail(cause);
|
|
832
|
-
const
|
|
833
|
-
|
|
803
|
+
const record = yield* readLockRecord({
|
|
804
|
+
fileSystem,
|
|
805
|
+
path,
|
|
806
|
+
lockPath
|
|
807
|
+
});
|
|
808
|
+
const recorded = record?.owner;
|
|
809
|
+
if (record?.recordPath !== void 0 && !testProcessExists(record.owner.pid) && (yield* removeLockRecord({
|
|
834
810
|
fileSystem,
|
|
835
811
|
lockPath,
|
|
836
|
-
|
|
837
|
-
}))) return yield* createLock;
|
|
812
|
+
recordPath: record.recordPath
|
|
813
|
+
}).pipe(Effect.catch(() => Effect.succeed(false))))) return yield* createLock;
|
|
838
814
|
return yield* Effect.fail({
|
|
839
815
|
_tag: "OutputOwnershipIssue",
|
|
840
816
|
dir,
|
|
841
817
|
msg: [
|
|
842
818
|
`Cannot start ${input.kind}: the build directory is locked.`,
|
|
843
|
-
`
|
|
819
|
+
`Lock: ${lockPath}`,
|
|
844
820
|
`Recorded owner PID: ${recorded?.pid ?? "unavailable"}`,
|
|
845
821
|
`Recorded operation: ${recorded?.kind ?? "unavailable"}`,
|
|
846
822
|
"Wait for the owning process to finish. If it has stopped, remove the leftover lock and retry:",
|
|
@@ -849,14 +825,18 @@ function acquireOutputOwnership(input) {
|
|
|
849
825
|
});
|
|
850
826
|
});
|
|
851
827
|
}));
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
lockPath,
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
828
|
+
const recordPath = path.join(lockPath, getOwnerFileName(owner.token));
|
|
829
|
+
yield* Effect.acquireRelease(createLock.pipe(Effect.andThen(fileSystem.writeFileString(recordPath, JSON.stringify(owner)).pipe(Effect.tapError(() => {
|
|
830
|
+
return fileSystem.remove(lockPath, { recursive: true }).pipe(Effect.ignore);
|
|
831
|
+
})))), () => {
|
|
832
|
+
return removeLockRecord({
|
|
833
|
+
fileSystem,
|
|
834
|
+
lockPath,
|
|
835
|
+
recordPath
|
|
836
|
+
}).pipe(Effect.catch((cause) => {
|
|
837
|
+
return Effect.logError(`Could not remove Vorma lock ${lockPath}. If the owner has stopped, run ${getRemovalCmd(lockPath)}`, cause);
|
|
838
|
+
}));
|
|
839
|
+
});
|
|
860
840
|
}).pipe(Effect.mapError((cause) => {
|
|
861
841
|
return cause._tag === "OutputOwnershipIssue" ? cause : {
|
|
862
842
|
_tag: "OutputOwnershipIssue",
|
|
@@ -2784,10 +2764,9 @@ function writeOutputFile(input) {
|
|
|
2784
2764
|
}
|
|
2785
2765
|
//#endregion
|
|
2786
2766
|
//#region src/build/patterns/nested-route-chains.ts
|
|
2787
|
-
/**
|
|
2767
|
+
/** Builds each unique target's outer-to-inner chain by looking up its compiled path prefixes. */
|
|
2788
2768
|
function createNestedRouteChains(input) {
|
|
2789
2769
|
const branches = /* @__PURE__ */ new Map();
|
|
2790
|
-
const exact = new Map(input.nodes.map((node) => [node.compiledPattern.pattern, node]));
|
|
2791
2770
|
for (const node of input.nodes) {
|
|
2792
2771
|
const terminal = node.compiledPattern.segments.at(-1)?.kind;
|
|
2793
2772
|
if (terminal !== "index" && terminal !== "splat") branches.set(getNestedRoutePathKey(node.compiledPattern.segments), node);
|
|
@@ -2800,10 +2779,7 @@ function createNestedRouteChains(input) {
|
|
|
2800
2779
|
if (branch !== void 0) chain.push(branch);
|
|
2801
2780
|
}
|
|
2802
2781
|
const terminal = target.compiledPattern.segments.at(-1)?.kind;
|
|
2803
|
-
if (terminal === "index" || terminal === "splat")
|
|
2804
|
-
const leaf = exact.get(target.compiledPattern.pattern);
|
|
2805
|
-
if (leaf !== void 0) chain.push(leaf);
|
|
2806
|
-
}
|
|
2782
|
+
if (terminal === "index" || terminal === "splat") chain.push(target);
|
|
2807
2783
|
return {
|
|
2808
2784
|
target,
|
|
2809
2785
|
chain
|
|
@@ -3062,7 +3038,11 @@ function compileApplicationModel(inspection) {
|
|
|
3062
3038
|
const childPatterns = /* @__PURE__ */ new Map();
|
|
3063
3039
|
for (const { target, chain } of chains) {
|
|
3064
3040
|
const parent = chain.at(-1) === target ? chain.at(-2) : void 0;
|
|
3065
|
-
if (parent !== void 0)
|
|
3041
|
+
if (parent !== void 0) {
|
|
3042
|
+
const siblings = childPatterns.get(parent.pattern);
|
|
3043
|
+
if (siblings === void 0) childPatterns.set(parent.pattern, [target.pattern]);
|
|
3044
|
+
else siblings.push(target.pattern);
|
|
3045
|
+
}
|
|
3066
3046
|
}
|
|
3067
3047
|
const linkedViews = chains.map(({ target, chain }) => {
|
|
3068
3048
|
return {
|
|
@@ -3376,7 +3356,7 @@ const PROCESS_ENV_PROTOCOL = {
|
|
|
3376
3356
|
* reads its build from the build directory of `vorma dev` in it.
|
|
3377
3357
|
* `vorma dev` sets the variable for the server that it starts. A server in
|
|
3378
3358
|
* any other process ignores it and reads the application's
|
|
3379
|
-
* `
|
|
3359
|
+
* `dist_dir`.
|
|
3380
3360
|
*/
|
|
3381
3361
|
distDirEnvVar: "VORMA_DIST_DIR" };
|
|
3382
3362
|
/** Private development coordination between the launcher and the application process. */
|
|
@@ -5,7 +5,7 @@ export declare const PROCESS_ENV_PROTOCOL: {
|
|
|
5
5
|
* reads its build from the build directory of `vorma dev` in it.
|
|
6
6
|
* `vorma dev` sets the variable for the server that it starts. A server in
|
|
7
7
|
* any other process ignores it and reads the application's
|
|
8
|
-
* `
|
|
8
|
+
* `dist_dir`.
|
|
9
9
|
*/
|
|
10
10
|
readonly distDirEnvVar: "VORMA_DIST_DIR";
|
|
11
11
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { CompiledNestedPattern } from "@sjcx/vorma-primitives/matcher";
|
|
2
1
|
import { Result } from "effect";
|
|
2
|
+
import type { CompiledNestedPattern } from "primitives-ts/matcher";
|
|
3
3
|
import type { InspectedClientView, InspectedEndpoint, InspectedProjects, InspectedViewLoader } from "../inspection/inspect-projects.ts";
|
|
4
4
|
export type ApplicationView = {
|
|
5
5
|
/** The authored pattern, as the loader or the view wrote it. */
|
|
@@ -10,7 +10,7 @@ export declare const ARTIFACT_PROTOCOL: {
|
|
|
10
10
|
/** Inside the build directory: the published static public assets. */
|
|
11
11
|
readonly publicDirName: "public";
|
|
12
12
|
/** Inside the build directory: the lock that a running build or development session holds. */
|
|
13
|
-
readonly
|
|
13
|
+
readonly lockDirName: "build.lock";
|
|
14
14
|
/** Inside the build directory: the directory in which a running build stages its output. */
|
|
15
15
|
readonly stagingDirName: "staging";
|
|
16
16
|
readonly publicOutputFileNamePrefix: "vorma_out_";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type { CompiledNestedPattern } from "
|
|
1
|
+
import type { CompiledNestedPattern } from "primitives-ts/matcher";
|
|
2
2
|
type NestedRouteNode = {
|
|
3
3
|
readonly compiledPattern: CompiledNestedPattern;
|
|
4
4
|
};
|
|
5
|
-
/**
|
|
5
|
+
/** Builds each unique target's outer-to-inner chain by looking up its compiled path prefixes. */
|
|
6
6
|
export declare function createNestedRouteChains<Node extends NestedRouteNode>(input: {
|
|
7
7
|
readonly nodes: ReadonlyArray<Node>;
|
|
8
8
|
}): ReadonlyArray<{
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type * as Matcher from "
|
|
1
|
+
import type * as Matcher from "primitives-ts/matcher";
|
|
2
2
|
/** The view pattern language, with the markers that the client and the server use. */
|
|
3
3
|
export declare const NESTED_PATTERN_LANGUAGE: Matcher.NestedPatternLanguage;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type PathWireError } from "@sjcx/vorma-client/unstable";
|
|
2
|
-
import { type CompiledNestedPattern, type PatternCompileError } from "@sjcx/vorma-primitives/matcher";
|
|
3
2
|
import { Result } from "effect";
|
|
3
|
+
import { type CompiledNestedPattern, type PatternCompileError } from "primitives-ts/matcher";
|
|
4
4
|
export type RoutePatternError = PathWireError | PatternCompileError;
|
|
5
5
|
/**
|
|
6
6
|
* Compiles an authored view route pattern: wire encoding first, then the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sjcx/vorma",
|
|
3
|
-
"version": "0.0.0-pre.
|
|
3
|
+
"version": "0.0.0-pre.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"license": "(MIT OR Apache-2.0)",
|
|
@@ -22,10 +22,10 @@
|
|
|
22
22
|
"@effect/platform-node-shared": "^4.0.0",
|
|
23
23
|
"@noble/hashes": "^2.4.0",
|
|
24
24
|
"@scure/base": "^2.4.0",
|
|
25
|
-
"@sjcx/vorma-primitives": "0.0.0-pre.7",
|
|
26
25
|
"chokidar": "^5.0.0",
|
|
27
26
|
"effect": "^4.0.0",
|
|
28
27
|
"lightningcss": "^1.33.0",
|
|
28
|
+
"primitives-ts": "0.0.1-pre.0",
|
|
29
29
|
"rolldown": "^1.2.6",
|
|
30
30
|
"typescript": "7.0.2"
|
|
31
31
|
},
|