@typeonce/effect-machine-devtools 0.26.2 → 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.
@@ -5,8 +5,8 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <meta name="description" content="Interactive statechart visualization for Effect Machine" />
7
7
  <title>Effect Machine · Statechart</title>
8
- <script type="module" crossorigin src="/assets/index-H0UhNc0S.js"></script>
9
- <link rel="stylesheet" crossorigin href="/assets/index-BsorPSDS.css">
8
+ <script type="module" crossorigin src="./assets/index-BFxKU97K.js"></script>
9
+ <link rel="stylesheet" crossorigin href="./assets/index-BsorPSDS.css">
10
10
  </head>
11
11
  <body>
12
12
  <div id="app"></div>
@@ -0,0 +1,25 @@
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 * as ProjectInspector from "../ProjectInspector.js";
6
+ export interface Options {
7
+ readonly root: string;
8
+ readonly include?: string | undefined;
9
+ readonly outputDirectory: string;
10
+ }
11
+ export interface BuildResult {
12
+ readonly outputDirectory: string;
13
+ readonly machineIds: ReadonlyArray<string>;
14
+ }
15
+ declare const StaticSiteError_base: Schema.Class<StaticSiteError, Schema.Struct<{
16
+ readonly _tag: Schema.tag<"StaticSiteError">;
17
+ readonly message: Schema.String;
18
+ readonly cause: Schema.optional<Schema.Defect>;
19
+ }>, import("effect/Cause").YieldableError>;
20
+ export declare class StaticSiteError extends StaticSiteError_base {
21
+ }
22
+ export declare const staticIndex: (index: string) => Effect.Effect<string, StaticSiteError>;
23
+ export declare const build: (options: Options) => Effect.Effect<BuildResult, StaticSiteError, FileSystem.FileSystem | Path.Path | ProjectInspector.ProjectInspector>;
24
+ export {};
25
+ //# sourceMappingURL=staticSite.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"staticSite.d.ts","sourceRoot":"","sources":["../../src/internal/staticSite.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,UAAU,MAAM,mBAAmB,CAAA;AAC/C,OAAO,KAAK,IAAI,MAAM,aAAa,CAAA;AACnC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAMvC,OAAO,KAAK,gBAAgB,MAAM,wBAAwB,CAAA;AAE1D,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACrC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAA;CACjC;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAA;IAChC,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;CAC3C;;;;;;AAED,qBAAa,eAAgB,SAAQ,oBAMnC;CAAG;AAQL,eAAO,MAAM,WAAW,GAAI,OAAO,MAAM,KAAG,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,eAAe,CAShF,CAAA;AAyED,eAAO,MAAM,KAAK,GAAI,SAAS,OAAO,KAAG,MAAM,CAAC,MAAM,CACpD,WAAW,EACX,eAAe,EACf,UAAU,CAAC,UAAU,GAAG,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC,gBAAgB,CAuFpE,CAAA"}
@@ -0,0 +1,147 @@
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
+ export class StaticSiteError extends Schema.Error("@typeonce/effect-machine-devtools/internal/StaticSiteError")({
12
+ _tag: Schema.tag("StaticSiteError"),
13
+ message: Schema.String,
14
+ cause: Schema.optional(Schema.Defect())
15
+ }) {
16
+ }
17
+ const packageRoot = fileURLToPath(new URL("../..", import.meta.url));
18
+ const generatedMarker = ".effect-machine-site";
19
+ const staticDataMeta = "<meta name=\"effect-machine-static-data\" content=\"./machines.json\" />";
20
+ const prettyJson = (value) => `${JSON.stringify(value, null, 2)}\n`;
21
+ export const staticIndex = (index) => {
22
+ if (!index.includes("</head>")) {
23
+ return Effect.fail(new StaticSiteError({
24
+ message: "The visualizer index does not contain a closing head element"
25
+ }));
26
+ }
27
+ return Effect.succeed(index.replace("</head>", ` ${staticDataMeta}\n </head>`));
28
+ };
29
+ const formatFailures = (failures) => failures.map((failure) => {
30
+ const messages = failure.diagnostics.map((diagnostic) => diagnostic.message).join("; ");
31
+ return `- ${failure.key}: ${messages}`;
32
+ }).join("\n");
33
+ const ensureReplaceable = Effect.fnUntraced(function* (fs, path, outputDirectory) {
34
+ if (!(yield* fs.exists(outputDirectory)))
35
+ return;
36
+ if (yield* fs.exists(path.join(outputDirectory, generatedMarker)))
37
+ return;
38
+ const entries = yield* fs.readDirectory(outputDirectory);
39
+ if (entries.length === 0)
40
+ return;
41
+ return yield* new StaticSiteError({
42
+ message: `Refusing to replace non-generated directory: ${outputDirectory}`
43
+ });
44
+ }, (effect, _fs, _path, outputDirectory) => effect.pipe(Effect.mapError((cause) => cause instanceof StaticSiteError
45
+ ? cause
46
+ : new StaticSiteError({
47
+ message: `Could not inspect output directory: ${outputDirectory}`,
48
+ cause
49
+ }))));
50
+ const inspect = Effect.fnUntraced(function* (options) {
51
+ const inspector = yield* ProjectInspector.ProjectInspector;
52
+ const results = yield* inspector.inspect({
53
+ root: options.root,
54
+ include: options.include,
55
+ revision: 1
56
+ });
57
+ const failures = results.filter((result) => result._tag === "Failed");
58
+ if (failures.length > 0) {
59
+ return yield* new StaticSiteError({
60
+ message: `Static site generation failed for ${failures.length} machine candidate${failures.length === 1 ? "" : "s"}:\n${formatFailures(failures)}`
61
+ });
62
+ }
63
+ const ready = results
64
+ .filter((result) => result._tag === "Ready")
65
+ .sort((left, right) => left.key.localeCompare(right.key));
66
+ if (ready.length === 0) {
67
+ return yield* new StaticSiteError({
68
+ message: `No Effect Machine definitions were found under ${options.root}`
69
+ });
70
+ }
71
+ const snapshot = yield* Schema.decodeUnknownEffect(DevToolsProtocol.RegistrySnapshot)({
72
+ protocolVersion: DevToolsProtocol.protocolVersion,
73
+ revision: 1,
74
+ results: ready
75
+ }).pipe(Effect.mapError((cause) => new StaticSiteError({
76
+ message: "The generated machine registry is invalid",
77
+ cause
78
+ })));
79
+ return { ready, snapshot };
80
+ });
81
+ export const build = (options) => Effect.gen(function* () {
82
+ const fs = yield* FileSystem.FileSystem;
83
+ const path = yield* Path.Path;
84
+ const root = path.resolve(options.root);
85
+ const outputDirectory = path.resolve(options.outputDirectory);
86
+ if (outputDirectory === root || path.dirname(outputDirectory) === outputDirectory) {
87
+ return yield* new StaticSiteError({
88
+ message: `Refusing to replace project or filesystem root: ${outputDirectory}`
89
+ });
90
+ }
91
+ yield* ensureReplaceable(fs, path, outputDirectory);
92
+ const { ready, snapshot } = yield* inspect({ ...options, root });
93
+ const parent = path.dirname(outputDirectory);
94
+ yield* fs.makeDirectory(parent, { recursive: true });
95
+ return yield* Effect.acquireUseRelease(fs.makeTempDirectory({ directory: parent, prefix: ".effect-machine-site-" }), (stagingDirectory) => Effect.gen(function* () {
96
+ yield* Effect.tryPromise({
97
+ try: () => buildVite({
98
+ root: packageRoot,
99
+ base: "./",
100
+ configFile: false,
101
+ logLevel: "error",
102
+ build: {
103
+ outDir: stagingDirectory,
104
+ emptyOutDir: true
105
+ }
106
+ }),
107
+ catch: (cause) => new StaticSiteError({
108
+ message: "Could not bundle the static visualizer",
109
+ cause
110
+ })
111
+ });
112
+ const indexPath = path.join(stagingDirectory, "index.html");
113
+ const index = yield* fs.readFileString(indexPath);
114
+ yield* fs.writeFileString(indexPath, yield* staticIndex(index));
115
+ yield* fs.writeFileString(path.join(stagingDirectory, "machines.json"), prettyJson(snapshot));
116
+ yield* fs.writeFileString(path.join(stagingDirectory, "manifest.json"), prettyJson({
117
+ formatVersion: 1,
118
+ generator: {
119
+ name: PackageJson.name,
120
+ version: PackageJson.version
121
+ },
122
+ protocolVersion: DevToolsProtocol.protocolVersion,
123
+ machineDocumentSchemaVersion: MachineDocument.schemaVersion,
124
+ machines: ready.map((result) => ({
125
+ key: result.key,
126
+ machineId: result.document.machineId,
127
+ source: result.document.source
128
+ }))
129
+ }));
130
+ yield* fs.writeFileString(path.join(stagingDirectory, generatedMarker), `${PackageJson.name}@${PackageJson.version}\n`);
131
+ yield* ensureReplaceable(fs, path, outputDirectory);
132
+ if (yield* fs.exists(outputDirectory)) {
133
+ yield* fs.remove(outputDirectory, { recursive: true });
134
+ }
135
+ yield* fs.rename(stagingDirectory, outputDirectory);
136
+ return {
137
+ outputDirectory,
138
+ machineIds: ready.map((result) => result.document.machineId)
139
+ };
140
+ }), (stagingDirectory) => fs.remove(stagingDirectory, { recursive: true }).pipe(Effect.ignore));
141
+ }).pipe(Effect.mapError((cause) => cause instanceof StaticSiteError
142
+ ? cause
143
+ : new StaticSiteError({
144
+ message: "Could not build the Effect Machine static site",
145
+ cause
146
+ })));
147
+ //# sourceMappingURL=staticSite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"staticSite.js","sourceRoot":"","sources":["../../src/internal/staticSite.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typeonce/effect-machine-devtools",
3
- "version": "0.26.2",
3
+ "version": "0.27.0",
4
4
  "description": "Local development tools for Effect Machine",
5
5
  "author": "Sandro Maglione",
6
6
  "repository": {
@@ -33,7 +33,7 @@
33
33
  "elkjs": "0.12.0",
34
34
  "typescript": "6.0.3",
35
35
  "vite": "8.1.5",
36
- "@typeonce/effect-machine": "^0.26.2"
36
+ "@typeonce/effect-machine": "^0.27.0"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "effect": "4.0.0-rc.112"
package/src/bin.ts CHANGED
@@ -1,12 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import * as NodeRuntime from "@effect/platform-node/NodeRuntime"
3
3
  import * as NodeServices from "@effect/platform-node/NodeServices"
4
+ import * as Console from "effect/Console"
4
5
  import * as Effect from "effect/Effect"
5
6
  import * as Layer from "effect/Layer"
6
7
  import * as Command from "effect/unstable/cli/Command"
7
8
  import * as Flag from "effect/unstable/cli/Flag"
8
9
  import PackageJson from "../package.json" with { type: "json" }
9
10
  import * as DevServer from "./DevServer.js"
11
+ import * as StaticSite from "./internal/staticSite.js"
10
12
  import * as MachineRegistry from "./MachineRegistry.js"
11
13
  import * as ProjectInspector from "./ProjectInspector.js"
12
14
 
@@ -40,7 +42,18 @@ const watchPolling = Flag.boolean("watch-polling").pipe(
40
42
  Flag.withDefault(false)
41
43
  )
42
44
 
43
- const cli = Command.make("effect-machine", { root, include, host, port, open, watchPolling }).pipe(
45
+ const outputDirectory = Flag.directory("out-dir").pipe(
46
+ Flag.withAlias("o"),
47
+ Flag.withDescription("Directory to write the static website"),
48
+ Flag.withDefault(".effect-machine/site")
49
+ )
50
+
51
+ const base = Command.make("effect-machine", { host, port, open, watchPolling }).pipe(
52
+ Command.withSharedFlags({ root, include }),
53
+ Command.withDescription("Inspect and publish Effect Machine visualizations")
54
+ )
55
+
56
+ const dev = base.pipe(
44
57
  Command.withDescription("Inspect Effect Machine definitions in a live local visualizer"),
45
58
  Command.withHandler(({ host, include, open, port, root, watchPolling }) => {
46
59
  const RegistryLayer = MachineRegistry.layer({ root, include }).pipe(
@@ -50,6 +63,23 @@ const cli = Command.make("effect-machine", { root, include, host, port, open, wa
50
63
  })
51
64
  )
52
65
 
66
+ const build = Command.make("build", { outputDirectory }).pipe(
67
+ Command.withDescription("Generate a static website from the project's machines"),
68
+ Command.withHandler(Effect.fnUntraced(function*({ outputDirectory }) {
69
+ const { include, root } = yield* base
70
+ const result = yield* StaticSite.build({ root, include, outputDirectory }).pipe(
71
+ Effect.provide(ProjectInspector.layer)
72
+ )
73
+ yield* Console.log(
74
+ `Effect Machine static site: ${result.outputDirectory}\n${result.machineIds.length} machine${
75
+ result.machineIds.length === 1 ? "" : "s"
76
+ }: ${result.machineIds.join(", ")}`
77
+ )
78
+ }))
79
+ )
80
+
81
+ const cli = dev.pipe(Command.withSubcommands([build]))
82
+
53
83
  Command.run(cli, { version: PackageJson.version }).pipe(
54
84
  Effect.provide(NodeServices.layer),
55
85
  NodeRuntime.runMain
@@ -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
- Effect.scoped(connect).pipe(BrowserRuntime.runMain)
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
+ )