@typeonce/effect-machine-devtools 0.26.1 → 0.27.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/README.md +21 -0
- package/dist/bin.js +11 -1
- package/dist/bin.js.map +1 -1
- package/dist/client/assets/index-BFxKU97K.js +37 -0
- package/dist/client/index.html +2 -2
- package/dist/internal/staticSite.d.ts +25 -0
- package/dist/internal/staticSite.d.ts.map +1 -0
- package/dist/internal/staticSite.js +147 -0
- package/dist/internal/staticSite.js.map +1 -0
- package/package.json +3 -3
- package/src/bin.ts +31 -1
- package/src/internal/browser/chart-layout.ts +319 -87
- package/src/internal/browser/layout-resilience-example.ts +145 -0
- package/src/internal/browser/main.ts +24 -1
- package/src/internal/staticSite.ts +209 -0
- package/dist/client/assets/index-eiptehRd.js +0 -37
|
@@ -8,6 +8,8 @@ import { mountMachineIndex } from "./machine-index.js"
|
|
|
8
8
|
const root = document.querySelector<HTMLDivElement>("#app")
|
|
9
9
|
if (root === null) throw new Error("Visualizer root element was not found")
|
|
10
10
|
|
|
11
|
+
const staticData = document.querySelector<HTMLMetaElement>("meta[name=\"effect-machine-static-data\"]")?.content
|
|
12
|
+
|
|
11
13
|
const showConnectionFailure = (message: string): void => {
|
|
12
14
|
const failure = document.createElement("div")
|
|
13
15
|
failure.className = "connection-failure"
|
|
@@ -39,4 +41,25 @@ const connect = Effect.acquireRelease(
|
|
|
39
41
|
)
|
|
40
42
|
)
|
|
41
43
|
|
|
42
|
-
|
|
44
|
+
const loadStatic = (location: string) =>
|
|
45
|
+
Effect.tryPromise({
|
|
46
|
+
try: async () => {
|
|
47
|
+
const response = await fetch(location)
|
|
48
|
+
if (!response.ok) throw new Error(`Could not load ${location}: ${response.status} ${response.statusText}`)
|
|
49
|
+
return response.json() as Promise<unknown>
|
|
50
|
+
},
|
|
51
|
+
catch: (cause) => cause instanceof Error ? cause : new Error(String(cause))
|
|
52
|
+
}).pipe(
|
|
53
|
+
Effect.flatMap(Schema.decodeUnknownEffect(DevToolsProtocol.RegistrySnapshot)),
|
|
54
|
+
Effect.tap((snapshot) => Effect.sync(() => mountMachineIndex(root, snapshot))),
|
|
55
|
+
Effect.asVoid
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
const run = staticData === undefined ? Effect.scoped(connect) : loadStatic(staticData)
|
|
59
|
+
|
|
60
|
+
run.pipe(
|
|
61
|
+
Effect.catch((cause) =>
|
|
62
|
+
Effect.sync(() => showConnectionFailure(cause instanceof Error ? cause.message : String(cause)))
|
|
63
|
+
),
|
|
64
|
+
BrowserRuntime.runMain
|
|
65
|
+
)
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect"
|
|
2
|
+
import * as FileSystem from "effect/FileSystem"
|
|
3
|
+
import * as Path from "effect/Path"
|
|
4
|
+
import * as Schema from "effect/Schema"
|
|
5
|
+
import { fileURLToPath } from "node:url"
|
|
6
|
+
import { build as buildVite } from "vite"
|
|
7
|
+
import PackageJson from "../../package.json" with { type: "json" }
|
|
8
|
+
import * as DevToolsProtocol from "../DevToolsProtocol.js"
|
|
9
|
+
import * as MachineDocument from "../MachineDocument.js"
|
|
10
|
+
import * as ProjectInspector from "../ProjectInspector.js"
|
|
11
|
+
|
|
12
|
+
export interface Options {
|
|
13
|
+
readonly root: string
|
|
14
|
+
readonly include?: string | undefined
|
|
15
|
+
readonly outputDirectory: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface BuildResult {
|
|
19
|
+
readonly outputDirectory: string
|
|
20
|
+
readonly machineIds: ReadonlyArray<string>
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class StaticSiteError extends Schema.Error<StaticSiteError>(
|
|
24
|
+
"@typeonce/effect-machine-devtools/internal/StaticSiteError"
|
|
25
|
+
)({
|
|
26
|
+
_tag: Schema.tag("StaticSiteError"),
|
|
27
|
+
message: Schema.String,
|
|
28
|
+
cause: Schema.optional(Schema.Defect())
|
|
29
|
+
}) {}
|
|
30
|
+
|
|
31
|
+
const packageRoot = fileURLToPath(new URL("../..", import.meta.url))
|
|
32
|
+
const generatedMarker = ".effect-machine-site"
|
|
33
|
+
const staticDataMeta = "<meta name=\"effect-machine-static-data\" content=\"./machines.json\" />"
|
|
34
|
+
|
|
35
|
+
const prettyJson = (value: unknown): string => `${JSON.stringify(value, null, 2)}\n`
|
|
36
|
+
|
|
37
|
+
export const staticIndex = (index: string): Effect.Effect<string, StaticSiteError> => {
|
|
38
|
+
if (!index.includes("</head>")) {
|
|
39
|
+
return Effect.fail(
|
|
40
|
+
new StaticSiteError({
|
|
41
|
+
message: "The visualizer index does not contain a closing head element"
|
|
42
|
+
})
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
return Effect.succeed(index.replace("</head>", ` ${staticDataMeta}\n </head>`))
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const formatFailures = (failures: ReadonlyArray<DevToolsProtocol.Failed>): string =>
|
|
49
|
+
failures.map((failure) => {
|
|
50
|
+
const messages = failure.diagnostics.map((diagnostic) => diagnostic.message).join("; ")
|
|
51
|
+
return `- ${failure.key}: ${messages}`
|
|
52
|
+
}).join("\n")
|
|
53
|
+
|
|
54
|
+
const ensureReplaceable = Effect.fnUntraced(
|
|
55
|
+
function*(
|
|
56
|
+
fs: FileSystem.FileSystem,
|
|
57
|
+
path: Path.Path,
|
|
58
|
+
outputDirectory: string
|
|
59
|
+
) {
|
|
60
|
+
if (!(yield* fs.exists(outputDirectory))) return
|
|
61
|
+
if (yield* fs.exists(path.join(outputDirectory, generatedMarker))) return
|
|
62
|
+
const entries = yield* fs.readDirectory(outputDirectory)
|
|
63
|
+
if (entries.length === 0) return
|
|
64
|
+
return yield* new StaticSiteError({
|
|
65
|
+
message: `Refusing to replace non-generated directory: ${outputDirectory}`
|
|
66
|
+
})
|
|
67
|
+
},
|
|
68
|
+
(effect, _fs, _path, outputDirectory) =>
|
|
69
|
+
effect.pipe(
|
|
70
|
+
Effect.mapError((cause) =>
|
|
71
|
+
cause instanceof StaticSiteError
|
|
72
|
+
? cause
|
|
73
|
+
: new StaticSiteError({
|
|
74
|
+
message: `Could not inspect output directory: ${outputDirectory}`,
|
|
75
|
+
cause
|
|
76
|
+
})
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
const inspect = Effect.fnUntraced(function*(options: Options) {
|
|
82
|
+
const inspector = yield* ProjectInspector.ProjectInspector
|
|
83
|
+
const results = yield* inspector.inspect({
|
|
84
|
+
root: options.root,
|
|
85
|
+
include: options.include,
|
|
86
|
+
revision: 1
|
|
87
|
+
})
|
|
88
|
+
const failures = results.filter((result): result is DevToolsProtocol.Failed => result._tag === "Failed")
|
|
89
|
+
if (failures.length > 0) {
|
|
90
|
+
return yield* new StaticSiteError({
|
|
91
|
+
message: `Static site generation failed for ${failures.length} machine candidate${
|
|
92
|
+
failures.length === 1 ? "" : "s"
|
|
93
|
+
}:\n${formatFailures(failures)}`
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
const ready = results
|
|
97
|
+
.filter((result): result is DevToolsProtocol.Ready => result._tag === "Ready")
|
|
98
|
+
.sort((left, right) => left.key.localeCompare(right.key))
|
|
99
|
+
if (ready.length === 0) {
|
|
100
|
+
return yield* new StaticSiteError({
|
|
101
|
+
message: `No Effect Machine definitions were found under ${options.root}`
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
const snapshot = yield* Schema.decodeUnknownEffect(DevToolsProtocol.RegistrySnapshot)({
|
|
105
|
+
protocolVersion: DevToolsProtocol.protocolVersion,
|
|
106
|
+
revision: 1,
|
|
107
|
+
results: ready
|
|
108
|
+
}).pipe(
|
|
109
|
+
Effect.mapError((cause) =>
|
|
110
|
+
new StaticSiteError({
|
|
111
|
+
message: "The generated machine registry is invalid",
|
|
112
|
+
cause
|
|
113
|
+
})
|
|
114
|
+
)
|
|
115
|
+
)
|
|
116
|
+
return { ready, snapshot }
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
export const build = (options: Options): Effect.Effect<
|
|
120
|
+
BuildResult,
|
|
121
|
+
StaticSiteError,
|
|
122
|
+
FileSystem.FileSystem | Path.Path | ProjectInspector.ProjectInspector
|
|
123
|
+
> =>
|
|
124
|
+
Effect.gen(function*() {
|
|
125
|
+
const fs = yield* FileSystem.FileSystem
|
|
126
|
+
const path = yield* Path.Path
|
|
127
|
+
const root = path.resolve(options.root)
|
|
128
|
+
const outputDirectory = path.resolve(options.outputDirectory)
|
|
129
|
+
if (outputDirectory === root || path.dirname(outputDirectory) === outputDirectory) {
|
|
130
|
+
return yield* new StaticSiteError({
|
|
131
|
+
message: `Refusing to replace project or filesystem root: ${outputDirectory}`
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
yield* ensureReplaceable(fs, path, outputDirectory)
|
|
135
|
+
const { ready, snapshot } = yield* inspect({ ...options, root })
|
|
136
|
+
const parent = path.dirname(outputDirectory)
|
|
137
|
+
yield* fs.makeDirectory(parent, { recursive: true })
|
|
138
|
+
|
|
139
|
+
return yield* Effect.acquireUseRelease(
|
|
140
|
+
fs.makeTempDirectory({ directory: parent, prefix: ".effect-machine-site-" }),
|
|
141
|
+
(stagingDirectory) =>
|
|
142
|
+
Effect.gen(function*() {
|
|
143
|
+
yield* Effect.tryPromise({
|
|
144
|
+
try: () =>
|
|
145
|
+
buildVite({
|
|
146
|
+
root: packageRoot,
|
|
147
|
+
base: "./",
|
|
148
|
+
configFile: false,
|
|
149
|
+
logLevel: "error",
|
|
150
|
+
build: {
|
|
151
|
+
outDir: stagingDirectory,
|
|
152
|
+
emptyOutDir: true
|
|
153
|
+
}
|
|
154
|
+
}),
|
|
155
|
+
catch: (cause) =>
|
|
156
|
+
new StaticSiteError({
|
|
157
|
+
message: "Could not bundle the static visualizer",
|
|
158
|
+
cause
|
|
159
|
+
})
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
const indexPath = path.join(stagingDirectory, "index.html")
|
|
163
|
+
const index = yield* fs.readFileString(indexPath)
|
|
164
|
+
yield* fs.writeFileString(indexPath, yield* staticIndex(index))
|
|
165
|
+
yield* fs.writeFileString(path.join(stagingDirectory, "machines.json"), prettyJson(snapshot))
|
|
166
|
+
yield* fs.writeFileString(
|
|
167
|
+
path.join(stagingDirectory, "manifest.json"),
|
|
168
|
+
prettyJson({
|
|
169
|
+
formatVersion: 1,
|
|
170
|
+
generator: {
|
|
171
|
+
name: PackageJson.name,
|
|
172
|
+
version: PackageJson.version
|
|
173
|
+
},
|
|
174
|
+
protocolVersion: DevToolsProtocol.protocolVersion,
|
|
175
|
+
machineDocumentSchemaVersion: MachineDocument.schemaVersion,
|
|
176
|
+
machines: ready.map((result) => ({
|
|
177
|
+
key: result.key,
|
|
178
|
+
machineId: result.document.machineId,
|
|
179
|
+
source: result.document.source
|
|
180
|
+
}))
|
|
181
|
+
})
|
|
182
|
+
)
|
|
183
|
+
yield* fs.writeFileString(
|
|
184
|
+
path.join(stagingDirectory, generatedMarker),
|
|
185
|
+
`${PackageJson.name}@${PackageJson.version}\n`
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
yield* ensureReplaceable(fs, path, outputDirectory)
|
|
189
|
+
if (yield* fs.exists(outputDirectory)) {
|
|
190
|
+
yield* fs.remove(outputDirectory, { recursive: true })
|
|
191
|
+
}
|
|
192
|
+
yield* fs.rename(stagingDirectory, outputDirectory)
|
|
193
|
+
return {
|
|
194
|
+
outputDirectory,
|
|
195
|
+
machineIds: ready.map((result) => result.document.machineId)
|
|
196
|
+
}
|
|
197
|
+
}),
|
|
198
|
+
(stagingDirectory) => fs.remove(stagingDirectory, { recursive: true }).pipe(Effect.ignore)
|
|
199
|
+
)
|
|
200
|
+
}).pipe(
|
|
201
|
+
Effect.mapError((cause) =>
|
|
202
|
+
cause instanceof StaticSiteError
|
|
203
|
+
? cause
|
|
204
|
+
: new StaticSiteError({
|
|
205
|
+
message: "Could not build the Effect Machine static site",
|
|
206
|
+
cause
|
|
207
|
+
})
|
|
208
|
+
)
|
|
209
|
+
)
|