@phtngyn/cms 0.1.0-beta.2 → 0.1.0-beta.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.
Files changed (77) hide show
  1. package/README.md +52 -35
  2. package/bin/cms.mjs +2 -0
  3. package/package.json +5 -5
  4. package/src/{Configuration-CA_jvQKG.js → Configuration-CDMYfTre.js} +3 -3
  5. package/src/{Configuration-CA_jvQKG.js.map → Configuration-CDMYfTre.js.map} +1 -1
  6. package/src/Configuration.d.ts +60 -2
  7. package/src/Configuration.js.map +1 -1
  8. package/src/ContentTransfer-C3Av9sWz.js +53 -0
  9. package/src/ContentTransfer-C3Av9sWz.js.map +1 -0
  10. package/src/{DocumentEngine-CgiBQU4g.js → DocumentEngine-J7g-emdf.js} +4 -4
  11. package/src/{DocumentEngine-CgiBQU4g.js.map → DocumentEngine-J7g-emdf.js.map} +1 -1
  12. package/src/DocumentModel.d.ts +33 -2
  13. package/src/{Forms-DxbkU3_5.js → Forms-Dj6NuW66.js} +2 -2
  14. package/src/{Forms-DxbkU3_5.js.map → Forms-Dj6NuW66.js.map} +1 -1
  15. package/src/Forms.d.ts +17 -1
  16. package/src/Forms.js.map +1 -1
  17. package/src/I18n.d.ts +20 -2
  18. package/src/Module.js +2 -3
  19. package/src/Module.js.map +1 -1
  20. package/src/{S3MediaObjects-JKMAWyVB.js → S3MediaObjects-CpPSzJto.js} +219 -106
  21. package/src/S3MediaObjects-CpPSzJto.js.map +1 -0
  22. package/src/cli/content.js +258 -0
  23. package/src/cli/content.js.map +1 -0
  24. package/src/cli.js +71 -0
  25. package/src/cli.js.map +1 -0
  26. package/src/{connection-B-iz0Y9Y.js → connection-CBAy2qGy.js} +2 -2
  27. package/src/{connection-B-iz0Y9Y.js.map → connection-CBAy2qGy.js.map} +1 -1
  28. package/src/{installation-DgF2M1gL.js → installation-CNfIriVm.js} +21 -23
  29. package/src/installation-CNfIriVm.js.map +1 -0
  30. package/src/internal/authoring-catalog.js +2 -2
  31. package/src/internal/authoring-metadata.js +1 -1
  32. package/src/internal/configuration.js +6 -10
  33. package/src/internal/configuration.js.map +1 -1
  34. package/src/internal/document-model.js +7 -4
  35. package/src/internal/document-model.js.map +1 -1
  36. package/src/{location-CFKTeK1K.js → location-BTqdfIzX.js} +2 -2
  37. package/src/{location-CFKTeK1K.js.map → location-BTqdfIzX.js.map} +1 -1
  38. package/src/{models-BWm1pTAf.js → models-B4IoTmp7.js} +2 -2
  39. package/src/{models-BWm1pTAf.js.map → models-B4IoTmp7.js.map} +1 -1
  40. package/src/{models-DwYioyNV.js → models-Bah5B3bT.js} +4 -21
  41. package/src/models-Bah5B3bT.js.map +1 -0
  42. package/src/runtime/delivery/context.js +1 -1
  43. package/src/runtime/delivery/plugin.js +9 -3
  44. package/src/runtime/delivery/plugin.js.map +1 -1
  45. package/src/runtime/delivery/useCmsDocumentSeo.d.ts +3 -0
  46. package/src/runtime/delivery/useCmsDocumentSeo.js +3 -1
  47. package/src/runtime/delivery/useCmsDocumentSeo.js.map +1 -1
  48. package/src/runtime/forms/document.js +1 -1
  49. package/src/runtime/forms/useCmsForm.js +43 -2
  50. package/src/runtime/forms/useCmsForm.js.map +1 -1
  51. package/src/runtime/i18n/translator.js +31 -2
  52. package/src/runtime/i18n/translator.js.map +1 -1
  53. package/src/runtime/i18n/useCmsI18n.d.ts +3 -1
  54. package/src/runtime/i18n/useCmsI18n.js +9 -1
  55. package/src/runtime/i18n/useCmsI18n.js.map +1 -1
  56. package/src/runtime/installation.js +1 -1
  57. package/src/runtime/plugin.js +3 -3
  58. package/src/runtime/plugin.js.map +1 -1
  59. package/src/runtime/preview/connection.js +1 -1
  60. package/src/runtime/preview/controller.js +2 -2
  61. package/src/runtime/preview/plugin.client.js +3 -3
  62. package/src/runtime/preview/store.js +1 -1
  63. package/src/runtime/sitemap.js +1 -1
  64. package/src/{store-DjJs3I7k.js → store-b2CSXgMt.js} +3 -3
  65. package/src/{store-DjJs3I7k.js.map → store-b2CSXgMt.js.map} +1 -1
  66. package/studio/{CreateDocumentDialog-CTeIFlPb.js → CreateDocumentDialog-GGey1uBc.js} +1 -1
  67. package/studio/{documentPlanning-Bfv-d7OU.js → documentPlanning-DRErYmBU.js} +1490 -1450
  68. package/studio/host.js +2 -2
  69. package/src/Configuration-Br7tE9Qt.d.ts +0 -68
  70. package/src/DocumentModel-BB9igGC7.d.ts +0 -34
  71. package/src/I18n-DZK4CkJF.d.ts +0 -21
  72. package/src/S3MediaObjects-JKMAWyVB.js.map +0 -1
  73. package/src/Seed.d.ts +0 -6
  74. package/src/Seed.js +0 -34
  75. package/src/Seed.js.map +0 -1
  76. package/src/installation-DgF2M1gL.js.map +0 -1
  77. package/src/models-DwYioyNV.js.map +0 -1
@@ -0,0 +1,258 @@
1
+ import { t as CMS_AUTHENTICATION_PATH, u as STUDIO_API_PATH } from "../HttpPaths-c9nLZ9H2.js";
2
+ import { a as ContentSource, i as ContentSnapshot, n as ContentApplyResult, r as ContentPlan } from "../ContentTransfer-C3Av9sWz.js";
3
+ import { Config, Console, Crypto, Effect, FileSystem, Option, Path, Redacted, Result, Schema } from "effect";
4
+ import { Cookies, HttpClient, HttpClientRequest } from "effect/unstable/http";
5
+ import { createTwoFilesPatch } from "diff";
6
+ import { CliError, Prompt } from "effect/unstable/cli";
7
+
8
+ //#region src/cli/content.ts
9
+ const IndexEntry = Schema.Struct({
10
+ sourcePath: ContentSource.fields.sourcePath,
11
+ published: Schema.Boolean,
12
+ sha256: Schema.String.check(Schema.isPattern(/^[a-f0-9]{64}$/u))
13
+ });
14
+ const IndexMetadata = Schema.Struct({
15
+ version: Schema.Literal(1),
16
+ sources: Schema.Array(IndexEntry)
17
+ });
18
+ const ContentIndex = Schema.Struct({
19
+ ...IndexMetadata.fields,
20
+ exportSha256: IndexEntry.fields.sha256
21
+ });
22
+ function userError(message) {
23
+ return new CliError.UserError({
24
+ cause: new Error(message),
25
+ userMessage: message
26
+ });
27
+ }
28
+ const hash = Effect.fn("CmsCli.content.hash")(function* (markdown) {
29
+ const digest = yield* (yield* Crypto.Crypto).digest("SHA-256", new TextEncoder().encode(markdown));
30
+ return Array.from(digest, (byte) => byte.toString(16).padStart(2, "0")).join("");
31
+ });
32
+ const metadataHash = Effect.fn("CmsCli.content.metadataHash")(function* (metadata) {
33
+ return yield* hash(yield* Schema.encodeEffect(Schema.fromJsonString(IndexMetadata))(metadata));
34
+ });
35
+ const resolveConnection = Effect.fn("CmsCli.resolveConnection")(function* (options) {
36
+ const configuredUrl = options.url || (yield* Config.String("CMS_URL").pipe(Config.withDefault("")));
37
+ const configuredEmail = options.email || (yield* Config.String("CMS_EMAIL").pipe(Config.withDefault("")));
38
+ const rawUrl = configuredUrl || (yield* Prompt.String({ message: "CMS URL" }));
39
+ const email = configuredEmail || (yield* Prompt.String({ message: "Administrator email" }));
40
+ if (!rawUrl || !email) return yield* userError("CMS URL and administrator email are required.");
41
+ const url = yield* Effect.try({
42
+ try: () => new URL(rawUrl),
43
+ catch: () => userError("CMS URL is invalid.")
44
+ });
45
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && [
46
+ "localhost",
47
+ "127.0.0.1",
48
+ "[::1]"
49
+ ].includes(url.hostname))) throw userError("Use HTTPS for CMS credentials; HTTP is allowed only on localhost.");
50
+ if (url.username || url.password || url.search || url.hash) throw userError("CMS URL must be an origin without credentials, query, or fragment.");
51
+ return {
52
+ url: new URL(url.origin),
53
+ email
54
+ };
55
+ });
56
+ const SignIn = Schema.Struct({
57
+ email: Schema.String,
58
+ password: Schema.String
59
+ });
60
+ const ApiError = Schema.Struct({ message: Schema.optionalKey(Schema.String) });
61
+ const connect = Effect.fn("CmsCli.connect")(function* (options) {
62
+ const { url, email } = yield* resolveConnection(options);
63
+ const configured = yield* Config.Redacted("CMS_PASSWORD").pipe(Config.option);
64
+ const password = Option.isSome(configured) ? configured.value : yield* Prompt.Password({ message: `Password for ${email} at ${url.origin}` });
65
+ const client = yield* HttpClient.HttpClient;
66
+ const signInRequest = yield* HttpClientRequest.post(new URL(`${CMS_AUTHENTICATION_PATH}/sign-in/email`, url)).pipe(HttpClientRequest.setHeader("accept", "application/json"), HttpClientRequest.setHeader("origin", url.origin), HttpClientRequest.schemaBodyJson(SignIn)({
67
+ email,
68
+ password: Redacted.value(password)
69
+ }));
70
+ const response = yield* client.execute(signInRequest);
71
+ if (response.status < 200 || response.status >= 300) return yield* userError(`CMS sign-in failed (${response.status}).`);
72
+ const cookie = Cookies.toCookieHeader(response.cookies);
73
+ if (!cookie) return yield* userError("CMS sign-in did not return a session cookie.");
74
+ return {
75
+ url,
76
+ cookie
77
+ };
78
+ });
79
+ const request = Effect.fn("CmsCli.request")(function* (session, path, body) {
80
+ const client = yield* HttpClient.HttpClient;
81
+ const url = new URL(`${STUDIO_API_PATH}/content/${path}`, session.url);
82
+ const base = (body === void 0 ? HttpClientRequest.get(url) : HttpClientRequest.post(url)).pipe(HttpClientRequest.setHeader("accept", "application/json"), HttpClientRequest.setHeader("cookie", session.cookie));
83
+ const prepared = body === void 0 ? base : yield* HttpClientRequest.bodyJson(base, body);
84
+ const response = yield* client.execute(prepared);
85
+ const payload = yield* response.json;
86
+ if (response.status < 200 || response.status >= 300) {
87
+ const decoded = Schema.decodeUnknownResult(ApiError)(payload);
88
+ return yield* userError(`CMS content ${path} failed: ${Result.isSuccess(decoded) ? decoded.success.message ?? `HTTP ${response.status}` : `HTTP ${response.status}`}`);
89
+ }
90
+ return payload;
91
+ });
92
+ function contentPath(directory, sourcePath, path) {
93
+ const resolved = path.resolve(directory, sourcePath);
94
+ if (!resolved.startsWith(`${directory}${path.sep}`)) throw userError(`Unsafe content path: ${sourcePath}`);
95
+ return resolved;
96
+ }
97
+ const readIndex = Effect.fn("CmsCli.readIndex")(function* (directory) {
98
+ const fileSystem = yield* FileSystem.FileSystem;
99
+ const indexPath = (yield* Path.Path).join(directory, ".cms-content.json");
100
+ if (!(yield* fileSystem.exists(indexPath))) return void 0;
101
+ return yield* Schema.decodeEffect(Schema.fromJsonString(ContentIndex))(yield* fileSystem.readFileString(indexPath));
102
+ });
103
+ const readSources = Effect.fn("CmsCli.readSources")(function* (directory) {
104
+ const fileSystem = yield* FileSystem.FileSystem;
105
+ const path = yield* Path.Path;
106
+ const root = path.resolve(directory);
107
+ const index = yield* readIndex(root);
108
+ if (!index) {
109
+ const paths = yield* markdownFiles(root);
110
+ if (paths.length === 0) return yield* userError(`No Markdown files found in ${root}.`);
111
+ return yield* Effect.forEach(paths, (file) => Effect.gen(function* () {
112
+ const sourcePath = path.relative(root, file).replaceAll("\\", "/");
113
+ const markdown = yield* fileSystem.readFileString(file);
114
+ return yield* Schema.decodeEffect(ContentSource)({
115
+ sourcePath,
116
+ markdown,
117
+ published: true
118
+ });
119
+ }));
120
+ }
121
+ const seen = /* @__PURE__ */ new Set();
122
+ const sources = [];
123
+ for (const entry of index.sources) {
124
+ if (seen.has(entry.sourcePath)) return yield* userError(`Duplicate source path in index: ${entry.sourcePath}`);
125
+ seen.add(entry.sourcePath);
126
+ const markdown = yield* fileSystem.readFileString(contentPath(root, entry.sourcePath, path));
127
+ sources.push(ContentSource.make({
128
+ sourcePath: entry.sourcePath,
129
+ markdown,
130
+ published: entry.published
131
+ }));
132
+ }
133
+ return sources;
134
+ });
135
+ function markdownFiles(directory) {
136
+ return Effect.gen(function* () {
137
+ const fileSystem = yield* FileSystem.FileSystem;
138
+ const path = yield* Path.Path;
139
+ const names = yield* fileSystem.readDirectory(directory);
140
+ return (yield* Effect.forEach(names, (name) => Effect.gen(function* () {
141
+ const file = path.join(directory, name);
142
+ const info = yield* fileSystem.stat(file);
143
+ if (info.type === "Directory") return yield* markdownFiles(file);
144
+ return info.type === "File" && [".md", ".mdc"].includes(path.extname(file)) ? [file] : [];
145
+ }))).flat().sort((left, right) => left.localeCompare(right));
146
+ });
147
+ }
148
+ const exportContent = Effect.fn("CmsCli.content.export")(function* (connection, directory) {
149
+ const fileSystem = yield* FileSystem.FileSystem;
150
+ const path = yield* Path.Path;
151
+ const root = path.resolve(directory);
152
+ const session = yield* connect(connection);
153
+ const snapshot = yield* Schema.decodeUnknownEffect(ContentSnapshot)(yield* request(session, "snapshot"));
154
+ const previous = yield* readIndex(root);
155
+ const oldEntries = new Map(previous?.sources.map((entry) => [entry.sourcePath, entry]));
156
+ if (previous && (yield* metadataHash({
157
+ version: previous.version,
158
+ sources: previous.sources
159
+ })) !== previous.exportSha256) return yield* userError(`Local edits in ${path.join(root, ".cms-content.json")}; review them before exporting again.`);
160
+ for (const source of snapshot.sources) {
161
+ const file = contentPath(root, source.sourcePath, path);
162
+ if (!(yield* fileSystem.exists(file))) continue;
163
+ const local = yield* fileSystem.readFileString(file);
164
+ const old = oldEntries.get(source.sourcePath);
165
+ if (old ? (yield* hash(local)) !== old.sha256 : local !== source.markdown) return yield* userError(`Local edits in ${file}; review them before exporting again.`);
166
+ }
167
+ yield* fileSystem.makeDirectory(root, { recursive: true });
168
+ for (const source of snapshot.sources) {
169
+ const file = contentPath(root, source.sourcePath, path);
170
+ yield* fileSystem.makeDirectory(path.dirname(file), { recursive: true });
171
+ yield* fileSystem.writeFileString(file, source.markdown);
172
+ }
173
+ const metadata = {
174
+ version: 1,
175
+ sources: yield* Effect.forEach(snapshot.sources, (source) => hash(source.markdown).pipe(Effect.map((sha256) => ({
176
+ sourcePath: source.sourcePath,
177
+ published: source.published,
178
+ sha256
179
+ }))))
180
+ };
181
+ const index = ContentIndex.make({
182
+ ...metadata,
183
+ exportSha256: yield* metadataHash(metadata)
184
+ });
185
+ yield* fileSystem.writeFileString(path.join(root, ".cms-content.json"), `${yield* Schema.encodeEffect(Schema.fromJsonString(ContentIndex))(index)}\n`);
186
+ yield* Console.log(`Exported ${index.sources.length} documents to ${root}`);
187
+ });
188
+ const plan = Effect.fn("CmsCli.content.plan")(function* (session, sources, options) {
189
+ return yield* Schema.decodeUnknownEffect(ContentPlan)(yield* request(session, "plan", {
190
+ sources,
191
+ ...options
192
+ }));
193
+ });
194
+ function printPlan(value) {
195
+ return Effect.gen(function* () {
196
+ for (const entry of value.entries) yield* Console.log(`${entry.action.padEnd(9)} ${entry.sourcePath}`);
197
+ for (const issue of value.issues) yield* Console.error(`${issue.severity}: ${issue.sourcePath ?? ""} ${issue.message}`);
198
+ yield* Console.log(value.valid ? `Ready at generation ${value.generation}` : "Plan has conflicts or validation errors.");
199
+ });
200
+ }
201
+ const preparePlan = Effect.fn("CmsCli.content.preparePlan")(function* (connection, directory, options) {
202
+ const sources = yield* readSources(directory);
203
+ const session = yield* connect(connection);
204
+ const value = yield* plan(session, sources, options);
205
+ yield* printPlan(value);
206
+ if (!value.valid) return yield* userError("Content plan is not ready to apply.");
207
+ return {
208
+ session,
209
+ sources,
210
+ value
211
+ };
212
+ });
213
+ const planContent = Effect.fn("CmsCli.content.planCommand")(function* (connection, directory, options) {
214
+ yield* preparePlan(connection, directory, options);
215
+ });
216
+ const diffContent = Effect.fn("CmsCli.content.diff")(function* (connection, directory) {
217
+ const local = yield* readSources(directory);
218
+ const session = yield* connect(connection);
219
+ const comparison = describeContentDiff(local, (yield* Schema.decodeUnknownEffect(ContentSnapshot)(yield* request(session, "snapshot"))).sources);
220
+ for (const line of comparison.lines) yield* Console.log(line);
221
+ yield* Console.log(`${comparison.changed} local document${comparison.changed === 1 ? " differs" : "s differ"} from the CMS.`);
222
+ });
223
+ function describeContentDiff(local, remote) {
224
+ const remoteByPath = new Map(remote.map((source) => [source.sourcePath, source]));
225
+ const localPaths = new Set(local.map((source) => source.sourcePath));
226
+ const lines = [];
227
+ let changed = 0;
228
+ for (const source of local) {
229
+ const current = remoteByPath.get(source.sourcePath);
230
+ const markdownChanged = current?.markdown !== source.markdown;
231
+ const publicationChanged = current !== void 0 && current.published !== source.published;
232
+ if (!markdownChanged && !publicationChanged) continue;
233
+ if (markdownChanged) {
234
+ const patch = createTwoFilesPatch(`remote/${source.sourcePath}`, `local/${source.sourcePath}`, current?.markdown ?? "", source.markdown);
235
+ lines.push(patch.trimEnd());
236
+ }
237
+ if (publicationChanged) lines.push(`Publication ${source.sourcePath}: CMS ${current.published ? "published" : "draft"}, local ${source.published ? "published (use --publish to apply)" : "draft (import keeps existing publication)"}`);
238
+ changed++;
239
+ }
240
+ for (const source of remote) if (!localPaths.has(source.sourcePath)) lines.push(`Remote only: ${source.sourcePath}`);
241
+ return {
242
+ lines,
243
+ changed
244
+ };
245
+ }
246
+ const applyContent = Effect.fn("CmsCli.content.apply")(function* (connection, directory, options) {
247
+ const { session, sources, value } = yield* preparePlan(connection, directory, options);
248
+ const result = yield* Schema.decodeUnknownEffect(ContentApplyResult)(yield* request(session, "apply", {
249
+ sources,
250
+ ...options,
251
+ expectedGeneration: value.generation
252
+ }));
253
+ yield* Console.log(`Applied: ${result.created} created, ${result.updated} updated, ${result.published} published, ${result.unchanged} unchanged.`);
254
+ });
255
+
256
+ //#endregion
257
+ export { applyContent, connect, describeContentDiff, diffContent, exportContent, planContent, readSources };
258
+ //# sourceMappingURL=content.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content.js","names":["ContentSourceSchema"],"sources":["../../../../src/cli/content.ts"],"sourcesContent":["import type { ContentSource } from '@internal/protocol/ContentTransfer'\nimport {\n ContentApplyResult,\n ContentPlan,\n ContentSnapshot,\n ContentSource as ContentSourceSchema,\n} from '@internal/protocol/ContentTransfer'\nimport type { ContentApplyInput, ContentTransferInput } from '@internal/protocol/ContentTransfer'\nimport { CMS_AUTHENTICATION_PATH, STUDIO_API_PATH } from '@internal/protocol/HttpPaths'\nimport { Config, Console, Crypto, Effect, FileSystem, Option, Path, Redacted, Result, Schema } from 'effect'\nimport type { PlatformError } from 'effect'\nimport { CliError, Prompt } from 'effect/unstable/cli'\nimport { Cookies, HttpClient, HttpClientRequest } from 'effect/unstable/http'\nimport { createTwoFilesPatch } from 'diff'\n\nconst IndexEntry = Schema.Struct({\n sourcePath: ContentSourceSchema.fields.sourcePath,\n published: Schema.Boolean,\n sha256: Schema.String.check(Schema.isPattern(/^[a-f0-9]{64}$/u)),\n})\n\nconst IndexMetadata = Schema.Struct({ version: Schema.Literal(1), sources: Schema.Array(IndexEntry) })\n\nconst ContentIndex = Schema.Struct({ ...IndexMetadata.fields, exportSha256: IndexEntry.fields.sha256 })\n\ntype ContentIndex = typeof ContentIndex.Type\n\nexport interface ConnectionOptions {\n readonly url: string\n readonly email: string\n}\n\nexport interface ImportOptions {\n readonly overwrite: boolean\n readonly publish: boolean\n}\n\nfunction userError(message: string) {\n return new CliError.UserError({ cause: new Error(message), userMessage: message })\n}\n\nconst hash = Effect.fn('CmsCli.content.hash')(function* (markdown: string) {\n const crypto = yield* Crypto.Crypto\n const digest = yield* crypto.digest('SHA-256', new TextEncoder().encode(markdown))\n\n return Array.from(digest, (byte) => byte.toString(16).padStart(2, '0')).join('')\n})\n\nconst metadataHash = Effect.fn('CmsCli.content.metadataHash')(function* (metadata: typeof IndexMetadata.Type) {\n return yield* hash(yield* Schema.encodeEffect(Schema.fromJsonString(IndexMetadata))(metadata))\n})\n\nconst resolveConnection = Effect.fn('CmsCli.resolveConnection')(function* (options: ConnectionOptions) {\n const configuredUrl = options.url || (yield* Config.String('CMS_URL').pipe(Config.withDefault('')))\n const configuredEmail = options.email || (yield* Config.String('CMS_EMAIL').pipe(Config.withDefault('')))\n const rawUrl = configuredUrl || (yield* Prompt.String({ message: 'CMS URL' }))\n const email = configuredEmail || (yield* Prompt.String({ message: 'Administrator email' }))\n\n if (!rawUrl || !email) return yield* userError('CMS URL and administrator email are required.')\n\n const url = yield* Effect.try({ try: () => new URL(rawUrl), catch: () => userError('CMS URL is invalid.') })\n\n if (\n url.protocol !== 'https:' &&\n !(url.protocol === 'http:' && ['localhost', '127.0.0.1', '[::1]'].includes(url.hostname))\n )\n throw userError('Use HTTPS for CMS credentials; HTTP is allowed only on localhost.')\n\n if (url.username || url.password || url.search || url.hash)\n throw userError('CMS URL must be an origin without credentials, query, or fragment.')\n\n return { url: new URL(url.origin), email }\n})\n\ninterface Session {\n readonly url: URL\n readonly cookie: string\n}\n\nconst SignIn = Schema.Struct({ email: Schema.String, password: Schema.String })\n\nconst ApiError = Schema.Struct({ message: Schema.optionalKey(Schema.String) })\n\nexport const connect = Effect.fn('CmsCli.connect')(function* (options: ConnectionOptions) {\n const { url, email } = yield* resolveConnection(options)\n\n const configured = yield* Config.Redacted('CMS_PASSWORD').pipe(Config.option)\n\n const password = Option.isSome(configured)\n ? configured.value\n : yield* Prompt.Password({ message: `Password for ${email} at ${url.origin}` })\n\n const client = yield* HttpClient.HttpClient\n\n const signInRequest = yield* HttpClientRequest.post(new URL(`${CMS_AUTHENTICATION_PATH}/sign-in/email`, url)).pipe(\n HttpClientRequest.setHeader('accept', 'application/json'),\n HttpClientRequest.setHeader('origin', url.origin),\n HttpClientRequest.schemaBodyJson(SignIn)({ email, password: Redacted.value(password) }),\n )\n\n const response = yield* client.execute(signInRequest)\n\n if (response.status < 200 || response.status >= 300)\n return yield* userError(`CMS sign-in failed (${response.status}).`)\n\n const cookie = Cookies.toCookieHeader(response.cookies)\n\n if (!cookie) return yield* userError('CMS sign-in did not return a session cookie.')\n\n return { url, cookie } satisfies Session\n})\n\nconst request = Effect.fn('CmsCli.request')(function* (\n session: Session,\n path: string,\n body?: ContentTransferInput | ContentApplyInput,\n) {\n const client = yield* HttpClient.HttpClient\n const url = new URL(`${STUDIO_API_PATH}/content/${path}`, session.url)\n\n const base = (body === undefined ? HttpClientRequest.get(url) : HttpClientRequest.post(url)).pipe(\n HttpClientRequest.setHeader('accept', 'application/json'),\n HttpClientRequest.setHeader('cookie', session.cookie),\n )\n\n const prepared = body === undefined ? base : yield* HttpClientRequest.bodyJson(base, body)\n const response = yield* client.execute(prepared)\n\n const payload = yield* response.json\n\n if (response.status < 200 || response.status >= 300) {\n const decoded = Schema.decodeUnknownResult(ApiError)(payload)\n\n const detail = Result.isSuccess(decoded)\n ? (decoded.success.message ?? `HTTP ${response.status}`)\n : `HTTP ${response.status}`\n\n return yield* userError(`CMS content ${path} failed: ${detail}`)\n }\n\n return payload\n})\n\nfunction contentPath(directory: string, sourcePath: string, path: Path.Path): string {\n const resolved = path.resolve(directory, sourcePath)\n\n if (!resolved.startsWith(`${directory}${path.sep}`)) throw userError(`Unsafe content path: ${sourcePath}`)\n\n return resolved\n}\n\nconst readIndex = Effect.fn('CmsCli.readIndex')(function* (directory: string) {\n const fileSystem = yield* FileSystem.FileSystem\n const path = yield* Path.Path\n const indexPath = path.join(directory, '.cms-content.json')\n\n if (!(yield* fileSystem.exists(indexPath))) return undefined\n\n return yield* Schema.decodeEffect(Schema.fromJsonString(ContentIndex))(yield* fileSystem.readFileString(indexPath))\n})\n\nexport const readSources = Effect.fn('CmsCli.readSources')(function* (directory: string) {\n const fileSystem = yield* FileSystem.FileSystem\n const path = yield* Path.Path\n const root = path.resolve(directory)\n const index = yield* readIndex(root)\n\n if (!index) {\n const paths = yield* markdownFiles(root)\n\n if (paths.length === 0) return yield* userError(`No Markdown files found in ${root}.`)\n\n return yield* Effect.forEach(paths, (file) =>\n Effect.gen(function* () {\n const sourcePath = path.relative(root, file).replaceAll('\\\\', '/')\n const markdown = yield* fileSystem.readFileString(file)\n\n return yield* Schema.decodeEffect(ContentSourceSchema)({ sourcePath, markdown, published: true })\n }),\n )\n }\n\n const seen = new Set<string>()\n const sources: ContentSource[] = []\n\n for (const entry of index.sources) {\n if (seen.has(entry.sourcePath)) return yield* userError(`Duplicate source path in index: ${entry.sourcePath}`)\n seen.add(entry.sourcePath)\n const markdown = yield* fileSystem.readFileString(contentPath(root, entry.sourcePath, path))\n sources.push(ContentSourceSchema.make({ sourcePath: entry.sourcePath, markdown, published: entry.published }))\n }\n\n return sources\n})\n\nfunction markdownFiles(\n directory: string,\n): Effect.Effect<ReadonlyArray<string>, PlatformError.PlatformError, FileSystem.FileSystem | Path.Path> {\n return Effect.gen(function* () {\n const fileSystem = yield* FileSystem.FileSystem\n const path = yield* Path.Path\n const names = yield* fileSystem.readDirectory(directory)\n\n const nested = yield* Effect.forEach(names, (name) =>\n Effect.gen(function* () {\n const file = path.join(directory, name)\n const info = yield* fileSystem.stat(file)\n\n if (info.type === 'Directory') return yield* markdownFiles(file)\n\n return info.type === 'File' && ['.md', '.mdc'].includes(path.extname(file)) ? [file] : []\n }),\n )\n\n return nested.flat().sort((left, right) => left.localeCompare(right))\n })\n}\n\nexport const exportContent = Effect.fn('CmsCli.content.export')(function* (\n connection: ConnectionOptions,\n directory: string,\n) {\n const fileSystem = yield* FileSystem.FileSystem\n const path = yield* Path.Path\n const root = path.resolve(directory)\n const session = yield* connect(connection)\n const snapshot = yield* Schema.decodeUnknownEffect(ContentSnapshot)(yield* request(session, 'snapshot'))\n const previous = yield* readIndex(root)\n const oldEntries = new Map(previous?.sources.map((entry) => [entry.sourcePath, entry]))\n\n if (\n previous &&\n (yield* metadataHash({ version: previous.version, sources: previous.sources })) !== previous.exportSha256\n )\n return yield* userError(\n `Local edits in ${path.join(root, '.cms-content.json')}; review them before exporting again.`,\n )\n\n // Preflight every file before replacing any of them.\n for (const source of snapshot.sources) {\n const file = contentPath(root, source.sourcePath, path)\n\n if (!(yield* fileSystem.exists(file))) continue\n\n const local = yield* fileSystem.readFileString(file)\n const old = oldEntries.get(source.sourcePath)\n\n if (old ? (yield* hash(local)) !== old.sha256 : local !== source.markdown)\n return yield* userError(`Local edits in ${file}; review them before exporting again.`)\n }\n\n yield* fileSystem.makeDirectory(root, { recursive: true })\n\n for (const source of snapshot.sources) {\n const file = contentPath(root, source.sourcePath, path)\n yield* fileSystem.makeDirectory(path.dirname(file), { recursive: true })\n yield* fileSystem.writeFileString(file, source.markdown)\n }\n\n const metadata: typeof IndexMetadata.Type = {\n version: 1,\n sources: yield* Effect.forEach(snapshot.sources, (source) =>\n hash(source.markdown).pipe(\n Effect.map((sha256) => ({\n sourcePath: source.sourcePath,\n published: source.published,\n sha256,\n })),\n ),\n ),\n }\n\n const index = ContentIndex.make({ ...metadata, exportSha256: yield* metadataHash(metadata) })\n\n yield* fileSystem.writeFileString(\n path.join(root, '.cms-content.json'),\n `${yield* Schema.encodeEffect(Schema.fromJsonString(ContentIndex))(index)}\\n`,\n )\n yield* Console.log(`Exported ${index.sources.length} documents to ${root}`)\n})\n\nconst plan = Effect.fn('CmsCli.content.plan')(function* (\n session: Session,\n sources: ReadonlyArray<ContentSource>,\n options: ImportOptions,\n) {\n return yield* Schema.decodeUnknownEffect(ContentPlan)(yield* request(session, 'plan', { sources, ...options }))\n})\n\nfunction printPlan(value: ContentPlan) {\n return Effect.gen(function* () {\n for (const entry of value.entries) yield* Console.log(`${entry.action.padEnd(9)} ${entry.sourcePath}`)\n\n for (const issue of value.issues)\n yield* Console.error(`${issue.severity}: ${issue.sourcePath ?? ''} ${issue.message}`)\n\n yield* Console.log(\n value.valid ? `Ready at generation ${value.generation}` : 'Plan has conflicts or validation errors.',\n )\n })\n}\n\nconst preparePlan = Effect.fn('CmsCli.content.preparePlan')(function* (\n connection: ConnectionOptions,\n directory: string,\n options: ImportOptions,\n) {\n const sources = yield* readSources(directory)\n const session = yield* connect(connection)\n const value = yield* plan(session, sources, options)\n yield* printPlan(value)\n\n if (!value.valid) return yield* userError('Content plan is not ready to apply.')\n\n return { session, sources, value }\n})\n\nexport const planContent = Effect.fn('CmsCli.content.planCommand')(function* (\n connection: ConnectionOptions,\n directory: string,\n options: ImportOptions,\n) {\n yield* preparePlan(connection, directory, options)\n})\n\nexport const diffContent = Effect.fn('CmsCli.content.diff')(function* (\n connection: ConnectionOptions,\n directory: string,\n) {\n const local = yield* readSources(directory)\n const session = yield* connect(connection)\n const remote = yield* Schema.decodeUnknownEffect(ContentSnapshot)(yield* request(session, 'snapshot'))\n const comparison = describeContentDiff(local, remote.sources)\n\n for (const line of comparison.lines) yield* Console.log(line)\n\n yield* Console.log(\n `${comparison.changed} local document${comparison.changed === 1 ? ' differs' : 's differ'} from the CMS.`,\n )\n})\n\nexport function describeContentDiff(local: ReadonlyArray<ContentSource>, remote: ReadonlyArray<ContentSource>) {\n const remoteByPath = new Map(remote.map((source) => [source.sourcePath, source]))\n const localPaths = new Set(local.map((source) => source.sourcePath))\n const lines: string[] = []\n let changed = 0\n\n for (const source of local) {\n const current = remoteByPath.get(source.sourcePath)\n const markdownChanged = current?.markdown !== source.markdown\n const publicationChanged = current !== undefined && current.published !== source.published\n\n if (!markdownChanged && !publicationChanged) continue\n\n if (markdownChanged) {\n const patch = createTwoFilesPatch(\n `remote/${source.sourcePath}`,\n `local/${source.sourcePath}`,\n current?.markdown ?? '',\n source.markdown,\n )\n\n lines.push(patch.trimEnd())\n }\n\n if (publicationChanged)\n lines.push(\n `Publication ${source.sourcePath}: CMS ${current.published ? 'published' : 'draft'}, local ${source.published ? 'published (use --publish to apply)' : 'draft (import keeps existing publication)'}`,\n )\n\n changed++\n }\n\n for (const source of remote) if (!localPaths.has(source.sourcePath)) lines.push(`Remote only: ${source.sourcePath}`)\n\n return { lines, changed }\n}\n\nexport const applyContent = Effect.fn('CmsCli.content.apply')(function* (\n connection: ConnectionOptions,\n directory: string,\n options: ImportOptions,\n) {\n const { session, sources, value } = yield* preparePlan(connection, directory, options)\n\n const result = yield* Schema.decodeUnknownEffect(ContentApplyResult)(\n yield* request(session, 'apply', { sources, ...options, expectedGeneration: value.generation }),\n )\n\n yield* Console.log(\n `Applied: ${result.created} created, ${result.updated} updated, ${result.published} published, ${result.unchanged} unchanged.`,\n )\n})\n"],"mappings":"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"}
package/src/cli.js ADDED
@@ -0,0 +1,71 @@
1
+ import { applyContent, diffContent, exportContent, planContent } from "./cli/content.js";
2
+ import { Effect, Layer } from "effect";
3
+ import { NodeChildProcessSpawner, NodeCrypto, NodeFileSystem, NodePath, NodeRuntime, NodeStdio, NodeTerminal } from "@effect/platform-node-shared";
4
+ import { FetchHttpClient } from "effect/unstable/http";
5
+ import { Argument, Command, Flag } from "effect/unstable/cli";
6
+
7
+ //#region package.json
8
+ var version = "0.1.0-beta.4";
9
+
10
+ //#endregion
11
+ //#region src/cli.ts
12
+ const root = Command.make("cms").pipe(Command.withSharedFlags({
13
+ url: Flag.String("url").pipe(Flag.withDefault("")),
14
+ email: Flag.String("email").pipe(Flag.withDefault(""))
15
+ }));
16
+ const directory = Argument.String("directory");
17
+ const overwrite = Flag.Boolean("overwrite").pipe(Flag.withDefault(false));
18
+ const publish = Flag.Boolean("publish").pipe(Flag.withDefault(false));
19
+ const exportCommand = Command.make("export", { directory }, ({ directory }) => Effect.gen(function* () {
20
+ const { url, email } = yield* root;
21
+ return yield* exportContent({
22
+ url,
23
+ email
24
+ }, directory);
25
+ })).pipe(Command.withDescription("Export authored Markdown from a CMS installation"));
26
+ const diffCommand = Command.make("diff", { directory }, ({ directory }) => Effect.gen(function* () {
27
+ const { url, email } = yield* root;
28
+ return yield* diffContent({
29
+ url,
30
+ email
31
+ }, directory);
32
+ })).pipe(Command.withDescription("Compare local Markdown with the current CMS content"));
33
+ const planCommand = Command.make("plan", {
34
+ directory,
35
+ overwrite,
36
+ publish
37
+ }, ({ directory, overwrite, publish }) => Effect.gen(function* () {
38
+ const { url, email } = yield* root;
39
+ return yield* planContent({
40
+ url,
41
+ email
42
+ }, directory, {
43
+ overwrite,
44
+ publish
45
+ });
46
+ })).pipe(Command.withDescription("Preview content changes without writing to the CMS"));
47
+ const applyCommand = Command.make("apply", {
48
+ directory,
49
+ overwrite,
50
+ publish
51
+ }, ({ directory, overwrite, publish }) => Effect.gen(function* () {
52
+ const { url, email } = yield* root;
53
+ return yield* applyContent({
54
+ url,
55
+ email
56
+ }, directory, {
57
+ overwrite,
58
+ publish
59
+ });
60
+ })).pipe(Command.withDescription("Apply a validated content plan to the CMS"));
61
+ const content = Command.make("content").pipe(Command.withSubcommands([
62
+ exportCommand,
63
+ diffCommand,
64
+ planCommand,
65
+ applyCommand
66
+ ]));
67
+ root.pipe(Command.withSubcommands([content]), Command.run({ version }), Effect.provide(Layer.mergeAll(NodeFileSystem.layer, NodePath.layer, NodeCrypto.layer, NodeTerminal.layer, NodeStdio.layer, NodeChildProcessSpawner.layer.pipe(Layer.provide(Layer.merge(NodeFileSystem.layer, NodePath.layer))), FetchHttpClient.layer)), NodeRuntime.runMain);
68
+
69
+ //#endregion
70
+ export { };
71
+ //# sourceMappingURL=cli.js.map
package/src/cli.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","names":["packageManifest.version"],"sources":["../../../package.json","../../../src/cli.ts"],"sourcesContent":["","import {\n NodeChildProcessSpawner,\n NodeCrypto,\n NodeFileSystem,\n NodePath,\n NodeRuntime,\n NodeStdio,\n NodeTerminal,\n} from '@effect/platform-node-shared'\nimport { Effect, Layer } from 'effect'\nimport { Argument, Command, Flag } from 'effect/unstable/cli'\nimport { FetchHttpClient } from 'effect/unstable/http'\nimport packageManifest from '../package.json' with { type: 'json' }\nimport { applyContent, diffContent, exportContent, planContent } from './cli/content'\n\nconst root = Command.make('cms').pipe(\n Command.withSharedFlags({\n url: Flag.String('url').pipe(Flag.withDefault('')),\n email: Flag.String('email').pipe(Flag.withDefault('')),\n }),\n)\n\nconst directory = Argument.String('directory')\n\nconst overwrite = Flag.Boolean('overwrite').pipe(Flag.withDefault(false))\n\nconst publish = Flag.Boolean('publish').pipe(Flag.withDefault(false))\n\nconst exportCommand = Command.make('export', { directory }, ({ directory }) =>\n Effect.gen(function* () {\n const { url, email } = yield* root\n\n return yield* exportContent({ url, email }, directory)\n }),\n).pipe(Command.withDescription('Export authored Markdown from a CMS installation'))\n\nconst diffCommand = Command.make('diff', { directory }, ({ directory }) =>\n Effect.gen(function* () {\n const { url, email } = yield* root\n\n return yield* diffContent({ url, email }, directory)\n }),\n).pipe(Command.withDescription('Compare local Markdown with the current CMS content'))\n\nconst planCommand = Command.make('plan', { directory, overwrite, publish }, ({ directory, overwrite, publish }) =>\n Effect.gen(function* () {\n const { url, email } = yield* root\n\n return yield* planContent({ url, email }, directory, { overwrite, publish })\n }),\n).pipe(Command.withDescription('Preview content changes without writing to the CMS'))\n\nconst applyCommand = Command.make('apply', { directory, overwrite, publish }, ({ directory, overwrite, publish }) =>\n Effect.gen(function* () {\n const { url, email } = yield* root\n\n return yield* applyContent({ url, email }, directory, { overwrite, publish })\n }),\n).pipe(Command.withDescription('Apply a validated content plan to the CMS'))\n\nconst content = Command.make('content').pipe(\n Command.withSubcommands([exportCommand, diffCommand, planCommand, applyCommand]),\n)\n\nroot.pipe(\n Command.withSubcommands([content]),\n Command.run({ version: packageManifest.version }),\n Effect.provide(\n Layer.mergeAll(\n NodeFileSystem.layer,\n NodePath.layer,\n NodeCrypto.layer,\n NodeTerminal.layer,\n NodeStdio.layer,\n NodeChildProcessSpawner.layer.pipe(Layer.provide(Layer.merge(NodeFileSystem.layer, NodePath.layer))),\n FetchHttpClient.layer,\n ),\n ),\n NodeRuntime.runMain,\n)\n"],"mappings":";;;;;;;;;;;ACeA,MAAM,OAAO,QAAQ,KAAK,KAAK,CAAC,CAAC,KAC/B,QAAQ,gBAAgB;CACtB,KAAK,KAAK,OAAO,KAAK,CAAC,CAAC,KAAK,KAAK,YAAY,EAAE,CAAC;CACjD,OAAO,KAAK,OAAO,OAAO,CAAC,CAAC,KAAK,KAAK,YAAY,EAAE,CAAC;AACvD,CAAC,CACH;AAEA,MAAM,YAAY,SAAS,OAAO,WAAW;AAE7C,MAAM,YAAY,KAAK,QAAQ,WAAW,CAAC,CAAC,KAAK,KAAK,YAAY,KAAK,CAAC;AAExE,MAAM,UAAU,KAAK,QAAQ,SAAS,CAAC,CAAC,KAAK,KAAK,YAAY,KAAK,CAAC;AAEpE,MAAM,gBAAgB,QAAQ,KAAK,UAAU,EAAE,UAAU,IAAI,EAAE,gBAC7D,OAAO,IAAI,aAAa;CACtB,MAAM,EAAE,KAAK,UAAU,OAAO;CAE9B,OAAO,OAAO,cAAc;EAAE;EAAK;CAAM,GAAG,SAAS;AACvD,CAAC,CACH,CAAC,CAAC,KAAK,QAAQ,gBAAgB,kDAAkD,CAAC;AAElF,MAAM,cAAc,QAAQ,KAAK,QAAQ,EAAE,UAAU,IAAI,EAAE,gBACzD,OAAO,IAAI,aAAa;CACtB,MAAM,EAAE,KAAK,UAAU,OAAO;CAE9B,OAAO,OAAO,YAAY;EAAE;EAAK;CAAM,GAAG,SAAS;AACrD,CAAC,CACH,CAAC,CAAC,KAAK,QAAQ,gBAAgB,qDAAqD,CAAC;AAErF,MAAM,cAAc,QAAQ,KAAK,QAAQ;CAAE;CAAW;CAAW;AAAQ,IAAI,EAAE,WAAW,WAAW,cACnG,OAAO,IAAI,aAAa;CACtB,MAAM,EAAE,KAAK,UAAU,OAAO;CAE9B,OAAO,OAAO,YAAY;EAAE;EAAK;CAAM,GAAG,WAAW;EAAE;EAAW;CAAQ,CAAC;AAC7E,CAAC,CACH,CAAC,CAAC,KAAK,QAAQ,gBAAgB,oDAAoD,CAAC;AAEpF,MAAM,eAAe,QAAQ,KAAK,SAAS;CAAE;CAAW;CAAW;AAAQ,IAAI,EAAE,WAAW,WAAW,cACrG,OAAO,IAAI,aAAa;CACtB,MAAM,EAAE,KAAK,UAAU,OAAO;CAE9B,OAAO,OAAO,aAAa;EAAE;EAAK;CAAM,GAAG,WAAW;EAAE;EAAW;CAAQ,CAAC;AAC9E,CAAC,CACH,CAAC,CAAC,KAAK,QAAQ,gBAAgB,2CAA2C,CAAC;AAE3E,MAAM,UAAU,QAAQ,KAAK,SAAS,CAAC,CAAC,KACtC,QAAQ,gBAAgB;CAAC;CAAe;CAAa;CAAa;AAAY,CAAC,CACjF;AAEA,KAAK,KACH,QAAQ,gBAAgB,CAAC,OAAO,CAAC,GACjC,QAAQ,IAAI,EAAWA,QAAwB,CAAC,GAChD,OAAO,QACL,MAAM,SACJ,eAAe,OACf,SAAS,OACT,WAAW,OACX,aAAa,OACb,UAAU,OACV,wBAAwB,MAAM,KAAK,MAAM,QAAQ,MAAM,MAAM,eAAe,OAAO,SAAS,KAAK,CAAC,CAAC,GACnG,gBAAgB,KAClB,CACF,GACA,YAAY,OACd"}
@@ -1,4 +1,4 @@
1
- import { n as DocumentPreviewMessage, o as isDocumentPreviewMessage, t as DOCUMENT_PREVIEW_CHANNEL } from "./models-BWm1pTAf.js";
1
+ import { n as DocumentPreviewMessage, o as isDocumentPreviewMessage, t as DOCUMENT_PREVIEW_CHANNEL } from "./models-B4IoTmp7.js";
2
2
  import { Effect, Exit, Result, Schedule, Schema, Scope } from "effect";
3
3
 
4
4
  //#region ../protocol/src/internal/preview/bridge.ts
@@ -89,4 +89,4 @@ async function openSitePreviewConnection(options) {
89
89
 
90
90
  //#endregion
91
91
  export { openSitePreviewConnection as t };
92
- //# sourceMappingURL=connection-B-iz0Y9Y.js.map
92
+ //# sourceMappingURL=connection-CBAy2qGy.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"connection-B-iz0Y9Y.js","names":["DocumentPreviewMessageSchema"],"sources":["../../../../protocol/src/internal/preview/bridge.ts","../../../src/runtime/preview/connection.ts"],"sourcesContent":["import type {\n DocumentPreviewMessage,\n PreviewOrigin,\n PreviewSessionId,\n PreviewSitePayload,\n StudioPreviewPayload,\n} from './models'\nimport type { Scope } from 'effect'\nimport { Effect, Result, Schedule, Schema } from 'effect'\nimport {\n DOCUMENT_PREVIEW_CHANNEL,\n DocumentPreviewMessage as DocumentPreviewMessageSchema,\n isDocumentPreviewMessage,\n} from './models'\n\nexport interface PreviewMessageEvent {\n readonly data: unknown\n readonly origin: string\n readonly source: object | null\n}\n\nexport interface PreviewMessageSource {\n readonly addEventListener: (type: 'message', listener: (event: PreviewMessageEvent) => void) => void\n readonly removeEventListener: (type: 'message', listener: (event: PreviewMessageEvent) => void) => void\n}\n\nexport interface PreviewMessageTarget {\n readonly postMessage: (message: DocumentPreviewMessage, targetOrigin: string) => void\n}\n\nexport type PreviewBridgeState = 'closed' | 'connecting' | 'ready'\n\nexport interface PreviewHostBridge {\n readonly reconnect: () => void\n readonly send: (payload: Exclude<StudioPreviewPayload, { readonly type: 'studio:connect' }>) => boolean\n readonly state: () => PreviewBridgeState\n readonly subscribe: (listener: (message: PreviewSitePayload) => void) => () => void\n}\n\nexport interface PreviewSiteBridge {\n readonly send: (payload: Exclude<PreviewSitePayload, { readonly type: 'preview:ready' }>) => boolean\n readonly subscribe: (listener: (message: StudioPreviewPayload) => void) => () => void\n}\n\nexport interface PreviewHostBridgeOptions {\n readonly handshakeIntervalMs?: number\n readonly sessionId: PreviewSessionId\n readonly target: () => PreviewMessageTarget | undefined\n readonly targetOrigin: PreviewOrigin\n readonly view: PreviewMessageSource\n}\n\nexport interface PreviewSiteBridgeOptions {\n readonly parent: PreviewMessageTarget\n readonly parentOrigin: PreviewOrigin\n readonly route: PreviewSitePayload & { readonly type: 'preview:ready' }\n readonly sessionId: PreviewSessionId\n readonly view: PreviewMessageSource\n}\n\nexport function makePreviewHostBridge(\n options: PreviewHostBridgeOptions,\n): Effect.Effect<PreviewHostBridge, never, Scope.Scope> {\n return Effect.acquireRelease(\n Effect.context<never>().pipe(\n Effect.map((context) => createPreviewHostBridge(options, Effect.runForkWith(context))),\n ),\n (bridge) => Effect.sync(() => bridge.close()),\n ).pipe(Effect.map(({ close: _close, ...bridge }) => bridge))\n}\n\nexport function makePreviewSiteBridge(\n options: PreviewSiteBridgeOptions,\n): Effect.Effect<PreviewSiteBridge, never, Scope.Scope> {\n return Effect.acquireRelease(\n Effect.sync(() => createPreviewSiteBridge(options)),\n (bridge) => Effect.sync(() => bridge.close()),\n ).pipe(Effect.map(({ close: _close, ...bridge }) => bridge))\n}\n\nfunction createPreviewHostBridge(options: PreviewHostBridgeOptions, runFork: typeof Effect.runFork) {\n const listeners = new Set<(message: PreviewSitePayload) => void>()\n let retry: ReturnType<typeof Effect.runFork> | undefined\n let state: PreviewBridgeState = 'connecting'\n\n const stopRetry = (): void => {\n if (retry === undefined) return\n retry.interruptUnsafe()\n retry = undefined\n }\n\n const post = (payload: StudioPreviewPayload): boolean => {\n const target = options.target()\n\n if (!target) return false\n target.postMessage(envelope(options.sessionId, payload), options.targetOrigin)\n\n return true\n }\n\n const connect = (): void => {\n post({ type: 'studio:connect' })\n }\n\n const reconnect = (): void => {\n if (state === 'closed') return\n state = 'connecting'\n stopRetry()\n const interval = options.handshakeIntervalMs ?? 250\n retry = runFork(Effect.sync(connect).pipe(Effect.repeat(Schedule.spaced(interval)), Effect.delay(interval)))\n connect()\n }\n\n const receive = (event: PreviewMessageEvent): void => {\n const target = options.target()\n\n if (!target) return\n const message = receivePreviewMessage(event, target, options.targetOrigin, options.sessionId)\n\n if (!message || !isPreviewSiteMessage(message)) return\n\n if (message.type === 'preview:ready') {\n state = 'ready'\n stopRetry()\n }\n\n for (const listener of listeners) listener(message)\n }\n\n const close = (): void => {\n if (state === 'closed') return\n state = 'closed'\n stopRetry()\n options.view.removeEventListener('message', receive)\n listeners.clear()\n }\n\n options.view.addEventListener('message', receive)\n reconnect()\n\n return {\n close,\n reconnect,\n send: (payload: Exclude<StudioPreviewPayload, { readonly type: 'studio:connect' }>) =>\n state === 'ready' && post(payload),\n state: () => state,\n subscribe: (listener: (message: PreviewSitePayload) => void) => {\n listeners.add(listener)\n\n return () => listeners.delete(listener)\n },\n }\n}\n\nfunction createPreviewSiteBridge(options: PreviewSiteBridgeOptions) {\n const listeners = new Set<(message: StudioPreviewPayload) => void>()\n let active = false\n let closed = false\n\n const post = (payload: PreviewSitePayload): boolean => {\n if (!active || closed) return false\n options.parent.postMessage(envelope(options.sessionId, payload), options.parentOrigin)\n\n return true\n }\n\n const receive = (event: PreviewMessageEvent): void => {\n if (closed) return\n const message = receivePreviewMessage(event, options.parent, options.parentOrigin, options.sessionId)\n\n if (!message || !isStudioPreviewMessage(message)) return\n\n if (message.type === 'studio:connect') {\n active = true\n post(options.route)\n }\n\n for (const listener of listeners) listener(message)\n }\n\n const close = (): void => {\n if (closed) return\n closed = true\n active = false\n options.view.removeEventListener('message', receive)\n listeners.clear()\n }\n\n options.view.addEventListener('message', receive)\n\n return {\n close,\n send: post,\n subscribe: (listener: (message: StudioPreviewPayload) => void) => {\n listeners.add(listener)\n\n return () => listeners.delete(listener)\n },\n }\n}\n\nfunction envelope(\n sessionId: PreviewSessionId,\n payload: PreviewSitePayload | StudioPreviewPayload,\n): DocumentPreviewMessage {\n return Result.getOrThrow(\n Schema.decodeResult(Schema.toType(DocumentPreviewMessageSchema))({\n channel: DOCUMENT_PREVIEW_CHANNEL,\n sessionId,\n ...payload,\n }),\n )\n}\n\nfunction receivePreviewMessage(\n event: PreviewMessageEvent,\n source: PreviewMessageTarget,\n origin: PreviewOrigin,\n sessionId: PreviewSessionId,\n): DocumentPreviewMessage | undefined {\n if (event.origin !== origin || event.source !== source || !isDocumentPreviewMessage(event.data)) return undefined\n\n return event.data.sessionId === sessionId ? event.data : undefined\n}\n\nfunction isPreviewSiteMessage(\n message: DocumentPreviewMessage,\n): message is Extract<DocumentPreviewMessage, { readonly type: `preview:${string}` }> {\n return message.type.startsWith('preview:')\n}\n\nfunction isStudioPreviewMessage(\n message: DocumentPreviewMessage,\n): message is Extract<DocumentPreviewMessage, { readonly type: `studio:${string}` }> {\n return message.type.startsWith('studio:')\n}\n","import type { PublicPath } from '@internal/protocol/Documents'\nimport type { PreviewOrigin, PreviewSessionId, PreviewSiteBridge } from '@internal/protocol/Preview'\nimport { makePreviewSiteBridge } from '@internal/protocol/Preview'\nimport { Effect, Exit, Scope } from 'effect'\n\nexport interface SitePreviewConnection extends PreviewSiteBridge {\n readonly close: () => Promise<void>\n}\n\nexport async function openSitePreviewConnection(options: {\n readonly editorOrigin: PreviewOrigin\n readonly route: PublicPath\n readonly sessionId: PreviewSessionId\n}): Promise<SitePreviewConnection> {\n const scope = await Effect.runPromise(Scope.make())\n\n try {\n const bridge = await Effect.runPromise(\n makePreviewSiteBridge({\n parent: window.parent,\n parentOrigin: options.editorOrigin,\n route: { path: options.route, type: 'preview:ready' },\n sessionId: options.sessionId,\n view: window,\n }).pipe(Effect.provideService(Scope.Scope, scope)),\n )\n\n let closed = false\n\n return {\n async close() {\n if (closed) return\n closed = true\n await Effect.runPromise(Scope.close(scope, Exit.void))\n },\n send: bridge.send,\n subscribe: bridge.subscribe,\n }\n } catch (cause) {\n await Effect.runPromise(Scope.close(scope, Exit.fail(cause)))\n throw cause\n }\n}\n"],"mappings":";;;;AAuEA,SAAgB,sBACd,SACsD;CACtD,OAAO,OAAO,eACZ,OAAO,WAAW,wBAAwB,OAAO,CAAC,IACjD,WAAW,OAAO,WAAW,OAAO,MAAM,CAAC,CAC9C,CAAC,CAAC,KAAK,OAAO,KAAK,EAAE,OAAO,QAAQ,GAAG,aAAa,MAAM,CAAC;AAC7D;AA4EA,SAAS,wBAAwB,SAAmC;CAClE,MAAM,4BAAY,IAAI,IAA6C;CACnE,IAAI,SAAS;CACb,IAAI,SAAS;CAEb,MAAM,QAAQ,YAAyC;EACrD,IAAI,CAAC,UAAU,QAAQ,OAAO;EAC9B,QAAQ,OAAO,YAAY,SAAS,QAAQ,WAAW,OAAO,GAAG,QAAQ,YAAY;EAErF,OAAO;CACT;CAEA,MAAM,WAAW,UAAqC;EACpD,IAAI,QAAQ;EACZ,MAAM,UAAU,sBAAsB,OAAO,QAAQ,QAAQ,QAAQ,cAAc,QAAQ,SAAS;EAEpG,IAAI,CAAC,WAAW,CAAC,uBAAuB,OAAO,GAAG;EAElD,IAAI,QAAQ,SAAS,kBAAkB;GACrC,SAAS;GACT,KAAK,QAAQ,KAAK;EACpB;EAEA,KAAK,MAAM,YAAY,WAAW,SAAS,OAAO;CACpD;CAEA,MAAM,cAAoB;EACxB,IAAI,QAAQ;EACZ,SAAS;EACT,SAAS;EACT,QAAQ,KAAK,oBAAoB,WAAW,OAAO;EACnD,UAAU,MAAM;CAClB;CAEA,QAAQ,KAAK,iBAAiB,WAAW,OAAO;CAEhD,OAAO;EACL;EACA,MAAM;EACN,YAAY,aAAsD;GAChE,UAAU,IAAI,QAAQ;GAEtB,aAAa,UAAU,OAAO,QAAQ;EACxC;CACF;AACF;AAEA,SAAS,SACP,WACA,SACwB;CACxB,OAAO,OAAO,WACZ,OAAO,aAAa,OAAO,OAAOA,sBAA4B,CAAC,CAAC,CAAC;EAC/D,SAAS;EACT;EACA,GAAG;CACL,CAAC,CACH;AACF;AAEA,SAAS,sBACP,OACA,QACA,QACA,WACoC;CACpC,IAAI,MAAM,WAAW,UAAU,MAAM,WAAW,UAAU,CAAC,yBAAyB,MAAM,IAAI,GAAG,OAAO;CAExG,OAAO,MAAM,KAAK,cAAc,YAAY,MAAM,OAAO;AAC3D;AAQA,SAAS,uBACP,SACmF;CACnF,OAAO,QAAQ,KAAK,WAAW,SAAS;AAC1C;;;;AClOA,eAAsB,0BAA0B,SAIb;CACjC,MAAM,QAAQ,MAAM,OAAO,WAAW,MAAM,KAAK,CAAC;CAElD,IAAI;EACF,MAAM,SAAS,MAAM,OAAO,WAC1B,sBAAsB;GACpB,QAAQ,OAAO;GACf,cAAc,QAAQ;GACtB,OAAO;IAAE,MAAM,QAAQ;IAAO,MAAM;GAAgB;GACpD,WAAW,QAAQ;GACnB,MAAM;EACR,CAAC,CAAC,CAAC,KAAK,OAAO,eAAe,MAAM,OAAO,KAAK,CAAC,CACnD;EAEA,IAAI,SAAS;EAEb,OAAO;GACL,MAAM,QAAQ;IACZ,IAAI,QAAQ;IACZ,SAAS;IACT,MAAM,OAAO,WAAW,MAAM,MAAM,OAAO,KAAK,IAAI,CAAC;GACvD;GACA,MAAM,OAAO;GACb,WAAW,OAAO;EACpB;CACF,SAAS,OAAO;EACd,MAAM,OAAO,WAAW,MAAM,MAAM,OAAO,KAAK,KAAK,KAAK,CAAC,CAAC;EAC5D,MAAM;CACR;AACF"}
1
+ {"version":3,"file":"connection-CBAy2qGy.js","names":["DocumentPreviewMessageSchema"],"sources":["../../../../protocol/src/internal/preview/bridge.ts","../../../src/runtime/preview/connection.ts"],"sourcesContent":["import type {\n DocumentPreviewMessage,\n PreviewOrigin,\n PreviewSessionId,\n PreviewSitePayload,\n StudioPreviewPayload,\n} from './models'\nimport type { Scope } from 'effect'\nimport { Effect, Result, Schedule, Schema } from 'effect'\nimport {\n DOCUMENT_PREVIEW_CHANNEL,\n DocumentPreviewMessage as DocumentPreviewMessageSchema,\n isDocumentPreviewMessage,\n} from './models'\n\nexport interface PreviewMessageEvent {\n readonly data: unknown\n readonly origin: string\n readonly source: object | null\n}\n\nexport interface PreviewMessageSource {\n readonly addEventListener: (type: 'message', listener: (event: PreviewMessageEvent) => void) => void\n readonly removeEventListener: (type: 'message', listener: (event: PreviewMessageEvent) => void) => void\n}\n\nexport interface PreviewMessageTarget {\n readonly postMessage: (message: DocumentPreviewMessage, targetOrigin: string) => void\n}\n\nexport type PreviewBridgeState = 'closed' | 'connecting' | 'ready'\n\nexport interface PreviewHostBridge {\n readonly reconnect: () => void\n readonly send: (payload: Exclude<StudioPreviewPayload, { readonly type: 'studio:connect' }>) => boolean\n readonly state: () => PreviewBridgeState\n readonly subscribe: (listener: (message: PreviewSitePayload) => void) => () => void\n}\n\nexport interface PreviewSiteBridge {\n readonly send: (payload: Exclude<PreviewSitePayload, { readonly type: 'preview:ready' }>) => boolean\n readonly subscribe: (listener: (message: StudioPreviewPayload) => void) => () => void\n}\n\nexport interface PreviewHostBridgeOptions {\n readonly handshakeIntervalMs?: number\n readonly sessionId: PreviewSessionId\n readonly target: () => PreviewMessageTarget | undefined\n readonly targetOrigin: PreviewOrigin\n readonly view: PreviewMessageSource\n}\n\nexport interface PreviewSiteBridgeOptions {\n readonly parent: PreviewMessageTarget\n readonly parentOrigin: PreviewOrigin\n readonly route: PreviewSitePayload & { readonly type: 'preview:ready' }\n readonly sessionId: PreviewSessionId\n readonly view: PreviewMessageSource\n}\n\nexport function makePreviewHostBridge(\n options: PreviewHostBridgeOptions,\n): Effect.Effect<PreviewHostBridge, never, Scope.Scope> {\n return Effect.acquireRelease(\n Effect.context<never>().pipe(\n Effect.map((context) => createPreviewHostBridge(options, Effect.runForkWith(context))),\n ),\n (bridge) => Effect.sync(() => bridge.close()),\n ).pipe(Effect.map(({ close: _close, ...bridge }) => bridge))\n}\n\nexport function makePreviewSiteBridge(\n options: PreviewSiteBridgeOptions,\n): Effect.Effect<PreviewSiteBridge, never, Scope.Scope> {\n return Effect.acquireRelease(\n Effect.sync(() => createPreviewSiteBridge(options)),\n (bridge) => Effect.sync(() => bridge.close()),\n ).pipe(Effect.map(({ close: _close, ...bridge }) => bridge))\n}\n\nfunction createPreviewHostBridge(options: PreviewHostBridgeOptions, runFork: typeof Effect.runFork) {\n const listeners = new Set<(message: PreviewSitePayload) => void>()\n let retry: ReturnType<typeof Effect.runFork> | undefined\n let state: PreviewBridgeState = 'connecting'\n\n const stopRetry = (): void => {\n if (retry === undefined) return\n retry.interruptUnsafe()\n retry = undefined\n }\n\n const post = (payload: StudioPreviewPayload): boolean => {\n const target = options.target()\n\n if (!target) return false\n target.postMessage(envelope(options.sessionId, payload), options.targetOrigin)\n\n return true\n }\n\n const connect = (): void => {\n post({ type: 'studio:connect' })\n }\n\n const reconnect = (): void => {\n if (state === 'closed') return\n state = 'connecting'\n stopRetry()\n const interval = options.handshakeIntervalMs ?? 250\n retry = runFork(Effect.sync(connect).pipe(Effect.repeat(Schedule.spaced(interval)), Effect.delay(interval)))\n connect()\n }\n\n const receive = (event: PreviewMessageEvent): void => {\n const target = options.target()\n\n if (!target) return\n const message = receivePreviewMessage(event, target, options.targetOrigin, options.sessionId)\n\n if (!message || !isPreviewSiteMessage(message)) return\n\n if (message.type === 'preview:ready') {\n state = 'ready'\n stopRetry()\n }\n\n for (const listener of listeners) listener(message)\n }\n\n const close = (): void => {\n if (state === 'closed') return\n state = 'closed'\n stopRetry()\n options.view.removeEventListener('message', receive)\n listeners.clear()\n }\n\n options.view.addEventListener('message', receive)\n reconnect()\n\n return {\n close,\n reconnect,\n send: (payload: Exclude<StudioPreviewPayload, { readonly type: 'studio:connect' }>) =>\n state === 'ready' && post(payload),\n state: () => state,\n subscribe: (listener: (message: PreviewSitePayload) => void) => {\n listeners.add(listener)\n\n return () => listeners.delete(listener)\n },\n }\n}\n\nfunction createPreviewSiteBridge(options: PreviewSiteBridgeOptions) {\n const listeners = new Set<(message: StudioPreviewPayload) => void>()\n let active = false\n let closed = false\n\n const post = (payload: PreviewSitePayload): boolean => {\n if (!active || closed) return false\n options.parent.postMessage(envelope(options.sessionId, payload), options.parentOrigin)\n\n return true\n }\n\n const receive = (event: PreviewMessageEvent): void => {\n if (closed) return\n const message = receivePreviewMessage(event, options.parent, options.parentOrigin, options.sessionId)\n\n if (!message || !isStudioPreviewMessage(message)) return\n\n if (message.type === 'studio:connect') {\n active = true\n post(options.route)\n }\n\n for (const listener of listeners) listener(message)\n }\n\n const close = (): void => {\n if (closed) return\n closed = true\n active = false\n options.view.removeEventListener('message', receive)\n listeners.clear()\n }\n\n options.view.addEventListener('message', receive)\n\n return {\n close,\n send: post,\n subscribe: (listener: (message: StudioPreviewPayload) => void) => {\n listeners.add(listener)\n\n return () => listeners.delete(listener)\n },\n }\n}\n\nfunction envelope(\n sessionId: PreviewSessionId,\n payload: PreviewSitePayload | StudioPreviewPayload,\n): DocumentPreviewMessage {\n return Result.getOrThrow(\n Schema.decodeResult(Schema.toType(DocumentPreviewMessageSchema))({\n channel: DOCUMENT_PREVIEW_CHANNEL,\n sessionId,\n ...payload,\n }),\n )\n}\n\nfunction receivePreviewMessage(\n event: PreviewMessageEvent,\n source: PreviewMessageTarget,\n origin: PreviewOrigin,\n sessionId: PreviewSessionId,\n): DocumentPreviewMessage | undefined {\n if (event.origin !== origin || event.source !== source || !isDocumentPreviewMessage(event.data)) return undefined\n\n return event.data.sessionId === sessionId ? event.data : undefined\n}\n\nfunction isPreviewSiteMessage(\n message: DocumentPreviewMessage,\n): message is Extract<DocumentPreviewMessage, { readonly type: `preview:${string}` }> {\n return message.type.startsWith('preview:')\n}\n\nfunction isStudioPreviewMessage(\n message: DocumentPreviewMessage,\n): message is Extract<DocumentPreviewMessage, { readonly type: `studio:${string}` }> {\n return message.type.startsWith('studio:')\n}\n","import type { PublicPath } from '@internal/protocol/Documents'\nimport type { PreviewOrigin, PreviewSessionId, PreviewSiteBridge } from '@internal/protocol/Preview'\nimport { makePreviewSiteBridge } from '@internal/protocol/Preview'\nimport { Effect, Exit, Scope } from 'effect'\n\nexport interface SitePreviewConnection extends PreviewSiteBridge {\n readonly close: () => Promise<void>\n}\n\nexport async function openSitePreviewConnection(options: {\n readonly editorOrigin: PreviewOrigin\n readonly route: PublicPath\n readonly sessionId: PreviewSessionId\n}): Promise<SitePreviewConnection> {\n const scope = await Effect.runPromise(Scope.make())\n\n try {\n const bridge = await Effect.runPromise(\n makePreviewSiteBridge({\n parent: window.parent,\n parentOrigin: options.editorOrigin,\n route: { path: options.route, type: 'preview:ready' },\n sessionId: options.sessionId,\n view: window,\n }).pipe(Effect.provideService(Scope.Scope, scope)),\n )\n\n let closed = false\n\n return {\n async close() {\n if (closed) return\n closed = true\n await Effect.runPromise(Scope.close(scope, Exit.void))\n },\n send: bridge.send,\n subscribe: bridge.subscribe,\n }\n } catch (cause) {\n await Effect.runPromise(Scope.close(scope, Exit.fail(cause)))\n throw cause\n }\n}\n"],"mappings":";;;;AAuEA,SAAgB,sBACd,SACsD;CACtD,OAAO,OAAO,eACZ,OAAO,WAAW,wBAAwB,OAAO,CAAC,IACjD,WAAW,OAAO,WAAW,OAAO,MAAM,CAAC,CAC9C,CAAC,CAAC,KAAK,OAAO,KAAK,EAAE,OAAO,QAAQ,GAAG,aAAa,MAAM,CAAC;AAC7D;AA4EA,SAAS,wBAAwB,SAAmC;CAClE,MAAM,4BAAY,IAAI,IAA6C;CACnE,IAAI,SAAS;CACb,IAAI,SAAS;CAEb,MAAM,QAAQ,YAAyC;EACrD,IAAI,CAAC,UAAU,QAAQ,OAAO;EAC9B,QAAQ,OAAO,YAAY,SAAS,QAAQ,WAAW,OAAO,GAAG,QAAQ,YAAY;EAErF,OAAO;CACT;CAEA,MAAM,WAAW,UAAqC;EACpD,IAAI,QAAQ;EACZ,MAAM,UAAU,sBAAsB,OAAO,QAAQ,QAAQ,QAAQ,cAAc,QAAQ,SAAS;EAEpG,IAAI,CAAC,WAAW,CAAC,uBAAuB,OAAO,GAAG;EAElD,IAAI,QAAQ,SAAS,kBAAkB;GACrC,SAAS;GACT,KAAK,QAAQ,KAAK;EACpB;EAEA,KAAK,MAAM,YAAY,WAAW,SAAS,OAAO;CACpD;CAEA,MAAM,cAAoB;EACxB,IAAI,QAAQ;EACZ,SAAS;EACT,SAAS;EACT,QAAQ,KAAK,oBAAoB,WAAW,OAAO;EACnD,UAAU,MAAM;CAClB;CAEA,QAAQ,KAAK,iBAAiB,WAAW,OAAO;CAEhD,OAAO;EACL;EACA,MAAM;EACN,YAAY,aAAsD;GAChE,UAAU,IAAI,QAAQ;GAEtB,aAAa,UAAU,OAAO,QAAQ;EACxC;CACF;AACF;AAEA,SAAS,SACP,WACA,SACwB;CACxB,OAAO,OAAO,WACZ,OAAO,aAAa,OAAO,OAAOA,sBAA4B,CAAC,CAAC,CAAC;EAC/D,SAAS;EACT;EACA,GAAG;CACL,CAAC,CACH;AACF;AAEA,SAAS,sBACP,OACA,QACA,QACA,WACoC;CACpC,IAAI,MAAM,WAAW,UAAU,MAAM,WAAW,UAAU,CAAC,yBAAyB,MAAM,IAAI,GAAG,OAAO;CAExG,OAAO,MAAM,KAAK,cAAc,YAAY,MAAM,OAAO;AAC3D;AAQA,SAAS,uBACP,SACmF;CACnF,OAAO,QAAQ,KAAK,WAAW,SAAS;AAC1C;;;;AClOA,eAAsB,0BAA0B,SAIb;CACjC,MAAM,QAAQ,MAAM,OAAO,WAAW,MAAM,KAAK,CAAC;CAElD,IAAI;EACF,MAAM,SAAS,MAAM,OAAO,WAC1B,sBAAsB;GACpB,QAAQ,OAAO;GACf,cAAc,QAAQ;GACtB,OAAO;IAAE,MAAM,QAAQ;IAAO,MAAM;GAAgB;GACpD,WAAW,QAAQ;GACnB,MAAM;EACR,CAAC,CAAC,CAAC,KAAK,OAAO,eAAe,MAAM,OAAO,KAAK,CAAC,CACnD;EAEA,IAAI,SAAS;EAEb,OAAO;GACL,MAAM,QAAQ;IACZ,IAAI,QAAQ;IACZ,SAAS;IACT,MAAM,OAAO,WAAW,MAAM,MAAM,OAAO,KAAK,IAAI,CAAC;GACvD;GACA,MAAM,OAAO;GACb,WAAW,OAAO;EACpB;CACF,SAAS,OAAO;EACd,MAAM,OAAO,WAAW,MAAM,MAAM,OAAO,KAAK,KAAK,KAAK,CAAC,CAAC;EAC5D,MAAM;CACR;AACF"}
@@ -1,11 +1,12 @@
1
1
  import { a as EditorNodeId, c as RevisionId, d as SourcePath, i as DocumentVersion, r as DocumentId, t as DocumentGeneration } from "./scalars-B2lgGUnj.js";
2
2
  import { i as FormResult, n as FORM_SUBMISSION_MAX_BYTES } from "./Forms-CfcI3tPU.js";
3
- import { a as encodeCmsEditorComponentAttributes, d as createCmsComarkRenderData, i as validateFormValues, t as documentForms } from "./Forms-DxbkU3_5.js";
4
- import { A as RemoveDocumentInput, C as MarkdownNode, Ct as MediaTooLarge, G as SeoRevision, H as SeoConfirmationRequired, M as UndeleteDocumentInput, N as UnpublishDocumentInput, P as UpdateDocumentInput, Q as AuthenticationRequired, S as DuplicateDocumentInput, St as MediaStatus, T as PublishDocumentInput, U as SeoMediaUnavailable, Y as SeoVersionConflict, Z as AccessDenied, _ as DocumentSource, _t as MediaObjectKey, a as DOCUMENT_REVISION_ACTIONS, at as ImageMimeType, bt as MediaReference, c as DocumentImportInput, ct as MediaCleanupReport, dt as MediaFolder, f as DocumentMutationResult, ft as MediaId, g as DocumentRevisionAction, gt as MediaNotFound, ht as MediaName, it as StudioRequestContext, j as RestoreDocumentInput, kt as RequestId, lt as MediaConflict, mt as MediaInvalid, n as CmsMarkdownDocument, nt as StudioActor, o as DeletedDocument, ot as MediaAsset, pt as MediaIntegrityReport, rt as StudioEntryDenied, st as MediaByteSize, tt as Role, ut as MediaCursor, vt as MediaPage, w as MoveDocumentInput, wt as MediaUpload, xt as MediaReferenced } from "./models-DwYioyNV.js";
5
- import { $ as DocumentRevision, A as StudioBadRequest, B as User, C as DocumentStore, D as CurrentStudioRequest, E as defaultAccessControlLayer, F as studioInternalError, G as UserSelfRemovalDenied, H as UserAlreadyExists, I as InitializationAlreadyCompleted, J as CollaborationServerMessage, K as UserSelfRoleChangeDenied, L as InitializationProofInvalid, M as StudioRequestConflict, N as StudioSchemaError, O as StudioApi, P as StudioInternalError, Q as DocumentHistoryInvalidCursor, R as InitializationState, S as DocumentPersistenceError, T as AccessControl, U as UserNotFound, V as UserAdministrationDenied, W as UserPage, X as StateVector, Y as CollaborationTicket, Z as DocumentHistoryComparisonTooLarge, _ as Documents, a as mediaObjectPrefix, at as ActivityEntry, b as cursorCodec, c as Seo, ct as ActivityPage, d as seoDeliveryLayer, dt as DocumentMediaUnavailable, et as ACTIVITY_ACTIONS, f as seoLayer, ft as DocumentNotFound, g as DocumentPreviews, gt as PublishedDocumentNotFound, h as DocumentMaintenance, ht as DocumentVersionConflict, i as MediaObjectStore, it as ActivityCursorInvalid, k as StudioAuthentication, l as SeoDelivery, lt as ActivityTarget, m as SeoStore, mt as DocumentValidationFailed, n as s3MediaObjectStoreLayer, nt as ActivityContext, o as PreviewSeo, ot as ActivityEntryDraft, p as SeoPersistenceError, pt as DocumentPathConflict, q as CollaborationClientMessage, r as MediaPersistenceError, rt as ActivityCursor, s as PublishedSeo, st as ActivityId, tt as ActivityAction, u as makeDefaultSeoOptions, ut as ActivityTargetKind, v as PublishedDocuments, w as renderEditorMarkdown, x as DocumentGenerationConflict, y as documentsLayer, z as CreateUserInput } from "./S3MediaObjects-JKMAWyVB.js";
3
+ import { a as encodeCmsEditorComponentAttributes, d as createCmsComarkRenderData, i as validateFormValues, t as documentForms } from "./Forms-Dj6NuW66.js";
4
+ import { $ as StudioActor, A as UnpublishDocumentInput, B as SeoMediaUnavailable, D as RemoveDocumentInput, Et as RequestId, H as SeoRevision, J as AccessDenied, K as SeoVersionConflict, O as RestoreDocumentInput, Q as Role, S as PublishDocumentInput, Y as AuthenticationRequired, _t as MediaReference, a as DOCUMENT_REVISION_ACTIONS, at as MediaCleanupReport, b as MarkdownNode, bt as MediaTooLarge, ct as MediaFolder, dt as MediaInvalid, et as StudioEntryDenied, ft as MediaName, ht as MediaPage, it as MediaByteSize, j as UpdateDocumentInput, k as UndeleteDocumentInput, l as DocumentMutationResult, lt as MediaId, m as DocumentSource, mt as MediaObjectKey, n as CmsMarkdownDocument, nt as ImageMimeType, o as DeletedDocument, ot as MediaConflict, p as DocumentRevisionAction, pt as MediaNotFound, rt as MediaAsset, st as MediaCursor, tt as StudioRequestContext, ut as MediaIntegrityReport, vt as MediaReferenced, x as MoveDocumentInput, xt as MediaUpload, y as DuplicateDocumentInput, yt as MediaStatus, z as SeoConfirmationRequired } from "./models-Bah5B3bT.js";
5
+ import { $ as DocumentRevision, A as StudioBadRequest, B as User, C as DocumentStore, D as CurrentStudioRequest, E as defaultAccessControlLayer, F as studioInternalError, G as UserSelfRemovalDenied, H as UserAlreadyExists, I as InitializationAlreadyCompleted, J as CollaborationServerMessage, K as UserSelfRoleChangeDenied, L as InitializationProofInvalid, M as StudioRequestConflict, N as StudioSchemaError, O as StudioApi, P as StudioInternalError, Q as DocumentHistoryInvalidCursor, R as InitializationState, S as DocumentPersistenceError, T as AccessControl, U as UserNotFound, V as UserAdministrationDenied, W as UserPage, X as StateVector, Y as CollaborationTicket, Z as DocumentHistoryComparisonTooLarge, _ as Documents, a as mediaObjectPrefix, at as ActivityEntry, b as cursorCodec, c as Seo, ct as ActivityPage, d as seoDeliveryLayer, dt as DocumentMediaUnavailable, et as ACTIVITY_ACTIONS, f as seoLayer, ft as DocumentNotFound, g as DocumentPreviews, gt as PublishedDocumentNotFound, h as DocumentMaintenance, ht as DocumentVersionConflict, i as MediaObjectStore, it as ActivityCursorInvalid, k as StudioAuthentication, l as SeoDelivery, lt as ActivityTarget, m as SeoStore, mt as DocumentValidationFailed, n as s3MediaObjectStoreLayer, nt as ActivityContext, o as PreviewSeo, ot as ActivityEntryDraft, p as SeoPersistenceError, pt as DocumentPathConflict, q as CollaborationClientMessage, r as MediaPersistenceError, rt as ActivityCursor, s as PublishedSeo, st as ActivityId, tt as ActivityAction, u as makeDefaultSeoOptions, ut as ActivityTargetKind, v as PublishedDocuments, w as renderEditorMarkdown, x as DocumentGenerationConflict, y as documentsLayer, z as CreateUserInput } from "./S3MediaObjects-CpPSzJto.js";
6
6
  import { a as CMS_FORM_SUBMIT_PATH, l as CMS_READINESS_PATH, o as CMS_LIVENESS_PATH, p as STUDIO_COLLABORATION_PATH, t as CMS_AUTHENTICATION_PATH, u as STUDIO_API_PATH } from "./HttpPaths-c9nLZ9H2.js";
7
7
  import { a as createEditorNodeId, o as editorNodeId, r as identifiedAttributes, t as assertCanonicalEditorContent } from "./metadata-BEZPWh-3.js";
8
- import { C as finiteNumberAttribute, S as compactJsonAttributes, _ as comarkTagForEditorMark, b as encodeEditorImageAttributes, g as EDITOR_MARK_TYPES, h as comarkToEditor, u as EditorDocumentInvalid, v as isEditorMark, w as nonEmptyStringAttribute, x as normalizeEditorImageLayout, y as EDITOR_IMAGE_LAYOUT_ATTRIBUTE } from "./Configuration-CA_jvQKG.js";
8
+ import { C as finiteNumberAttribute, S as compactJsonAttributes, _ as comarkTagForEditorMark, b as encodeEditorImageAttributes, g as EDITOR_MARK_TYPES, h as comarkToEditor, u as EditorDocumentInvalid, v as isEditorMark, w as nonEmptyStringAttribute, x as normalizeEditorImageLayout, y as EDITOR_IMAGE_LAYOUT_ATTRIBUTE } from "./Configuration-CDMYfTre.js";
9
+ import { o as ContentTransferConflict } from "./ContentTransfer-C3Av9sWz.js";
9
10
  import { Cause, Clock, Context, Crypto, Data, DateTime, Deferred, Effect, Encoding, Exit, Fiber, Layer, Option, Predicate, PubSub, Pull, Queue, Redacted, Ref, Result, Schedule, Schema, Scope, Semaphore, Stream, flow } from "effect";
10
11
  import { Extension, Node, getSchema, mergeAttributes } from "@tiptap/core";
11
12
  import { prosemirrorJSONToYDoc, ySyncPluginKey, yXmlFragmentToProseMirrorRootNode } from "@tiptap/y-tiptap";
@@ -4651,7 +4652,6 @@ var Initialization = class extends Context.Service()("@internal/server/Initializ
4651
4652
  function initializationLayer(options) {
4652
4653
  return Layer.effect(Initialization, Effect.gen(function* () {
4653
4654
  const journal = yield* ChangeJournal;
4654
- const documentOperations = yield* DocumentMaintenance;
4655
4655
  const crypto = yield* Crypto.Crypto;
4656
4656
  const identity = yield* InitialIdentity;
4657
4657
  const recovery = yield* InitialIdentityRecovery;
@@ -4690,15 +4690,6 @@ function initializationLayer(options) {
4690
4690
  intent: prepared.intent
4691
4691
  });
4692
4692
  if (!(yield* store.claim(durableClaim))) return yield* new InitializationAlreadyCompleted();
4693
- const imported = yield* documentOperations.importSources(DocumentImportInput.make({
4694
- mode: "preserve",
4695
- reason: "CMS initialization",
4696
- sources: options.initialSources ?? []
4697
- }), context).pipe(Effect.result);
4698
- if (Result.isFailure(imported)) {
4699
- yield* store.reset(changeId);
4700
- return yield* imported.failure;
4701
- }
4702
4693
  const begin = yield* journal.begin(initialAdministratorDraft({
4703
4694
  context,
4704
4695
  id: changeId
@@ -5086,6 +5077,7 @@ const isAccessDenied$1 = Schema.is(AccessDenied);
5086
5077
  const isDocumentNotFound$1 = Schema.is(DocumentNotFound);
5087
5078
  const isDocumentVersionConflict = Schema.is(DocumentVersionConflict);
5088
5079
  const isDocumentGenerationConflict = Schema.is(DocumentGenerationConflict);
5080
+ const isContentTransferConflict = Schema.is(ContentTransferConflict);
5089
5081
  const isPublicMutationError = Schema.is(Schema.Union([
5090
5082
  AccessDenied,
5091
5083
  DocumentMediaUnavailable,
@@ -5125,6 +5117,10 @@ const documentsApiLayer = HttpApiBuilder.group(StudioApi, "documents", (handlers
5125
5117
  ...payload
5126
5118
  }))).pipe(mapMutationError));
5127
5119
  }));
5120
+ const contentApiLayer = HttpApiBuilder.group(StudioApi, "content", (handlers) => Effect.gen(function* () {
5121
+ const documents = yield* Documents;
5122
+ return handlers.handle("snapshot", () => withStudioRequest((context) => documents.contentSnapshot(context)).pipe(preservePublicErrors(isAccessDenied$1))).handle("plan", ({ payload }) => withStudioRequest((context) => documents.contentPlan(context, payload)).pipe(preservePublicErrors((error) => isAccessDenied$1(error) || isContentTransferConflict(error)))).handle("apply", ({ payload }) => withStudioRequest((context) => documents.contentApply(context, payload)).pipe(preservePublicErrors((error) => isAccessDenied$1(error) || isContentTransferConflict(error) || Schema.is(DocumentValidationFailed)(error))));
5123
+ }));
5128
5124
  function mapMutationError(effect) {
5129
5125
  return effect.pipe(Effect.mapError((error) => {
5130
5126
  if (isPublicMutationError(error)) return error;
@@ -5301,7 +5297,7 @@ const userAdministrationHttpLayer = HttpApiBuilder.group(StudioApi, "users", (ha
5301
5297
  //#region ../platform-node/src/internal/http/api.ts
5302
5298
  function studioApiLayer(bootstrap, authoringRevision, components = [], formHandlers = {}) {
5303
5299
  const configuration = HttpApiBuilder.group(StudioApi, "configuration", (handlers) => handlers.handle("bootstrap", () => Effect.succeed(bootstrap)));
5304
- return authenticatedRouterHandlers(Layer.mergeAll(configuration, formsApiLayer(components, formHandlers), documentsApiLayer, mediaApiLayer(bootstrap.media.enabled, bootstrap.media.maxUploadBytes), collaborationApiLayer(authoringRevision), deliveryApiLayer, previewApiLayer, administrationApiLayer, userAdministrationHttpLayer).pipe(Layer.provideMerge(studioSchemaErrorLayer)));
5300
+ return authenticatedRouterHandlers(Layer.mergeAll(configuration, formsApiLayer(components, formHandlers), documentsApiLayer, contentApiLayer, mediaApiLayer(bootstrap.media.enabled, bootstrap.media.maxUploadBytes), collaborationApiLayer(authoringRevision), deliveryApiLayer, previewApiLayer, administrationApiLayer, userAdministrationHttpLayer).pipe(Layer.provideMerge(studioSchemaErrorLayer)));
5305
5301
  }
5306
5302
  function authenticatedRouterHandlers(handlers) {
5307
5303
  return handlers;
@@ -5337,10 +5333,7 @@ function makeInstallationLayer(validated) {
5337
5333
  }).pipe(Layer.provide(Layer.merge(core, media.objects)));
5338
5334
  const productModules = validated.media.enabled ? enabledProductModules(validated.media.configuration) : productModulesLayer(productOptions).pipe(Layer.provide(core));
5339
5335
  const authentication = studioAuthenticationLayer.pipe(Layer.provide(core));
5340
- const initialization = initializationLayer({
5341
- initialSources: options.initialSources,
5342
- initializationToken: options.initializationToken
5343
- }).pipe(Layer.provide(Layer.merge(productModules, core)));
5336
+ const initialization = initializationLayer({ initializationToken: options.initializationToken }).pipe(Layer.provide(core));
5344
5337
  const users = userAdministrationLayer.pipe(Layer.provide(core));
5345
5338
  const tickets = studioRealtimeTicketsLayer(options.studioSecret).pipe(Layer.provide(core));
5346
5339
  const roomHost = studioRealtimeRoomHostLayer().pipe(Layer.provide(productModules));
@@ -5401,7 +5394,6 @@ function createCmsInstallationLayer(options) {
5401
5394
  composition: options.composition,
5402
5395
  studioSecret: options.studioSecret,
5403
5396
  documentModel: options.documentModel,
5404
- initialSources: options.initialSources,
5405
5397
  initializationToken: options.initializationToken
5406
5398
  });
5407
5399
  }
@@ -6138,13 +6130,15 @@ const applyDocumentCommitPlan = Effect.fn("DocumentStore.Drizzle.applyCommitPlan
6138
6130
  const revisionId = yield* appendRevision(transaction, plan, activity);
6139
6131
  yield* transaction.insert(documentHeads).values({
6140
6132
  authoredRevisionId: revisionId,
6141
- documentId: plan.documentId
6133
+ documentId: plan.documentId,
6134
+ publishedRevisionId: plan.publish ? revisionId : null
6142
6135
  });
6143
6136
  return;
6144
6137
  }
6145
6138
  case "document.update": {
6146
6139
  const revisionId = yield* appendRevision(transaction, plan, activity);
6147
6140
  yield* updateAuthoredHead(transaction, plan.documentId, revisionId);
6141
+ if (plan.publish) yield* transaction.update(documentHeads).set({ publishedRevisionId: revisionId }).where(eq(documentHeads.documentId, plan.documentId));
6148
6142
  yield* touchDocument(transaction, plan.documentId);
6149
6143
  return;
6150
6144
  }
@@ -6670,6 +6664,11 @@ const recordDocumentActivity = Effect.fn("DocumentStore.Drizzle.recordActivity")
6670
6664
  };
6671
6665
  if (plan.action !== "document.publish" && plan.action !== "document.unpublish") values.id = source.revisionId;
6672
6666
  yield* database.insert(activityEntries).values(values);
6667
+ if ((plan.action === "document.create" || plan.action === "document.update") && plan.publish) yield* database.insert(activityEntries).values({
6668
+ ...values,
6669
+ action: "document.published",
6670
+ id: void 0
6671
+ });
6673
6672
  });
6674
6673
  const readSnapshot$1 = Effect.fn("DocumentStore.Drizzle.readSnapshot")(function* (database, generation) {
6675
6674
  const rows = yield* database.select({
@@ -8116,7 +8115,6 @@ const installCms = Effect.fn("NuxtCmsInstallation.install")(function* (options)
8116
8115
  } : void 0
8117
8116
  },
8118
8117
  documentModel: options.documentModel,
8119
- initialSources: options.initialSources,
8120
8118
  initializationToken: Redacted.make(options.initializationToken),
8121
8119
  studioSecret: Redacted.make(`cms-collaboration:${options.secret}`)
8122
8120
  });
@@ -8140,4 +8138,4 @@ function toS3MediaObjectStoreOptions(options) {
8140
8138
 
8141
8139
  //#endregion
8142
8140
  export { toS3MediaObjectStoreOptions as n, installCms as t };
8143
- //# sourceMappingURL=installation-DgF2M1gL.js.map
8141
+ //# sourceMappingURL=installation-CNfIriVm.js.map