@phtngyn/cms 0.1.0-beta.5 → 0.2.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/README.md +114 -41
- package/package.json +13 -1
- package/src/ApplicationTypes.d.ts +84 -0
- package/src/ApplicationTypes.js +1 -0
- package/src/{Configuration-CDMYfTre.js → Configuration-BU-UyGoH.js} +4 -4
- package/src/{Configuration-CDMYfTre.js.map → Configuration-BU-UyGoH.js.map} +1 -1
- package/src/Configuration.d.ts +43 -21
- package/src/Configuration.js.map +1 -1
- package/src/Content.d.ts +67 -0
- package/src/Content.js +291 -0
- package/src/Content.js.map +1 -0
- package/src/{ContentTransfer-C3Av9sWz.js → ContentTransfer-BhF9G56s.js} +8 -4
- package/src/ContentTransfer-BhF9G56s.js.map +1 -0
- package/src/ContentTransfer-CRQet3QJ.d.ts +48 -0
- package/src/{DocumentEngine-J7g-emdf.js → DocumentEngine-ClhFXW9M.js} +4 -4
- package/src/{DocumentEngine-J7g-emdf.js.map → DocumentEngine-ClhFXW9M.js.map} +1 -1
- package/src/DocumentModel.d.ts +14 -2
- package/src/DocumentModel.js +32 -4
- package/src/DocumentModel.js.map +1 -1
- package/src/{Forms-CfcI3tPU.js → Forms-BbkE7QfC.js} +47 -16
- package/src/Forms-BbkE7QfC.js.map +1 -0
- package/src/{Forms-Dj6NuW66.js → Forms-DoB56hfe.js} +24 -7
- package/src/Forms-DoB56hfe.js.map +1 -0
- package/src/{Forms-BsJMev5_.d.ts → Forms-R0HoTR-o.d.ts} +31 -13
- package/src/Forms.d.ts +23 -11
- package/src/Forms.js.map +1 -1
- package/src/I18n-zbqpWxki.d.ts +29 -0
- package/src/I18n.d.ts +2 -20
- package/src/Module.js +18 -7
- package/src/Module.js.map +1 -1
- package/src/{S3MediaObjects-CpPSzJto.js → S3MediaObjects-D5Ns4FM6.js} +61 -40
- package/src/S3MediaObjects-D5Ns4FM6.js.map +1 -0
- package/src/cli/content.js +63 -221
- package/src/cli/content.js.map +1 -1
- package/src/cli.js +120 -5
- package/src/cli.js.map +1 -1
- package/src/{connection-CBAy2qGy.js → connection-D5S9Iph-.js} +2 -2
- package/src/{connection-CBAy2qGy.js.map → connection-D5S9Iph-.js.map} +1 -1
- package/src/dev-lifecycle-CZ7iCzG9.js +515 -0
- package/src/dev-lifecycle-CZ7iCzG9.js.map +1 -0
- package/src/{installation-CNfIriVm.js → installation-BNuoRDOz.js} +601 -468
- package/src/installation-BNuoRDOz.js.map +1 -0
- package/src/internal/application-types.js +43 -0
- package/src/internal/application-types.js.map +1 -0
- package/src/internal/authoring-catalog.js +9 -4
- package/src/internal/authoring-catalog.js.map +1 -1
- package/src/internal/authoring-metadata.js +1 -1
- package/src/internal/configuration.js +149 -46
- package/src/internal/configuration.js.map +1 -1
- package/src/internal/dev-lifecycle.js +3 -0
- package/src/internal/diagnostics.js +47 -0
- package/src/internal/diagnostics.js.map +1 -0
- package/src/internal/document-model.js +2 -2
- package/src/internal/document-model.js.map +1 -1
- package/src/internal/form-capabilities.js +1 -1
- package/src/internal/i18n.js +27 -1
- package/src/internal/i18n.js.map +1 -1
- package/src/{location-BTqdfIzX.js → location-CPX7oj0o.js} +2 -2
- package/src/{location-BTqdfIzX.js.map → location-CPX7oj0o.js.map} +1 -1
- package/src/{models-B4IoTmp7.js → models-C9kF9JlQ.js} +2 -2
- package/src/{models-B4IoTmp7.js.map → models-C9kF9JlQ.js.map} +1 -1
- package/src/{models-Bah5B3bT.js → models-ChRxBChy.js} +9 -2
- package/src/models-ChRxBChy.js.map +1 -0
- package/src/runtime/delivery/context.js +1 -1
- package/src/runtime/delivery/plugin.js +28 -5
- package/src/runtime/delivery/plugin.js.map +1 -1
- package/src/runtime/delivery/preview.d.ts +1 -1
- package/src/runtime/delivery/types.d.ts +2 -85
- package/src/runtime/delivery/useCmsDocumentSeo.d.ts +8 -1
- package/src/runtime/delivery/useCmsDocumentSeo.js +19 -7
- package/src/runtime/delivery/useCmsDocumentSeo.js.map +1 -1
- package/src/runtime/delivery/useDocument.d.ts +2 -2
- package/src/runtime/delivery/useDocument.js.map +1 -1
- package/src/runtime/delivery/useDocumentQuery.d.ts +3 -3
- package/src/runtime/delivery/useDocumentQuery.js.map +1 -1
- package/src/runtime/forms/document.js +1 -1
- package/src/runtime/forms/useCmsForm.js +89 -45
- package/src/runtime/forms/useCmsForm.js.map +1 -1
- package/src/runtime/i18n/useCmsI18n.d.ts +9 -7
- package/src/runtime/i18n/useCmsI18n.js +5 -4
- package/src/runtime/i18n/useCmsI18n.js.map +1 -1
- package/src/runtime/installation.js +2 -2
- package/src/runtime/plugin.js +12 -4
- package/src/runtime/plugin.js.map +1 -1
- package/src/runtime/preview/connection.js +1 -1
- package/src/runtime/preview/controller.js +2 -2
- package/src/runtime/preview/plugin.client.js +3 -3
- package/src/runtime/preview/store.js +1 -1
- package/src/runtime/sitemap.js +1 -1
- package/src/scalars-CA6kyipS.d.ts +7 -0
- package/src/{store-b2CSXgMt.js → store-B49J21NA.js} +3 -3
- package/src/{store-b2CSXgMt.js.map → store-B49J21NA.js.map} +1 -1
- package/src/types-DutFRtaG.d.ts +90 -0
- package/studio/{CreateDocumentDialog-GGey1uBc.js → CreateDocumentDialog-DJ8c0WZb.js} +3 -3
- package/studio/{documentPlanning-DRErYmBU.js → documentPlanning-CDb31mnl.js} +5935 -5869
- package/studio/host.css +1 -1
- package/studio/host.js +7608 -7250
- package/src/ContentTransfer-C3Av9sWz.js.map +0 -1
- package/src/Forms-CfcI3tPU.js.map +0 -1
- package/src/Forms-Dj6NuW66.js.map +0 -1
- package/src/S3MediaObjects-CpPSzJto.js.map +0 -1
- package/src/installation-CNfIriVm.js.map +0 -1
- package/src/models-Bah5B3bT.js.map +0 -1
package/src/cli/content.js
CHANGED
|
@@ -1,258 +1,100 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { Config, Console, Crypto, Effect, FileSystem, Option, Path, Redacted, Result, Schema } from "effect";
|
|
4
|
-
import { Cookies, HttpClient, HttpClientRequest } from "effect/unstable/http";
|
|
1
|
+
import { CmsContentError, createCmsContentClient, diffCmsContentSources, readCmsContentDirectory } from "../Content.js";
|
|
2
|
+
import { Config, Console, Effect, Option, Redacted } from "effect";
|
|
5
3
|
import { createTwoFilesPatch } from "diff";
|
|
6
4
|
import { CliError, Prompt } from "effect/unstable/cli";
|
|
7
5
|
|
|
8
6
|
//#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
7
|
function userError(message) {
|
|
23
8
|
return new CliError.UserError({
|
|
24
9
|
cause: new Error(message),
|
|
25
10
|
userMessage: message
|
|
26
11
|
});
|
|
27
12
|
}
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
|
|
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);
|
|
13
|
+
const client = Effect.fn("CmsCli.content.client")(function* (options) {
|
|
14
|
+
const baseUrl = options.url || (yield* Config.String("CMS_URL").pipe(Config.withDefault(""))) || (yield* Prompt.String({ message: "CMS URL" }));
|
|
15
|
+
const email = options.email || (yield* Config.String("CMS_EMAIL").pipe(Config.withDefault(""))) || (yield* Prompt.String({ message: "Administrator email" }));
|
|
16
|
+
if (!baseUrl || !email) return yield* userError("CMS URL and administrator email are required.");
|
|
63
17
|
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 ${
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
18
|
+
const password = Option.isSome(configured) ? Redacted.value(configured.value) : Redacted.value(yield* Prompt.Password({ message: `Password for ${email} at ${baseUrl}` }));
|
|
19
|
+
return yield* attempt(() => createCmsContentClient({
|
|
20
|
+
baseUrl,
|
|
21
|
+
credentials: {
|
|
22
|
+
email,
|
|
23
|
+
password
|
|
24
|
+
}
|
|
69
25
|
}));
|
|
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
26
|
});
|
|
79
|
-
const
|
|
80
|
-
|
|
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));
|
|
27
|
+
const readSources = Effect.fn("CmsCli.content.readSources")(function* (directory) {
|
|
28
|
+
return (yield* attempt(() => readCmsContentDirectory(directory))).sources;
|
|
102
29
|
});
|
|
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
30
|
const exportContent = Effect.fn("CmsCli.content.export")(function* (connection, directory) {
|
|
149
|
-
const
|
|
150
|
-
const
|
|
151
|
-
|
|
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}`);
|
|
31
|
+
const content = yield* client(connection);
|
|
32
|
+
const source = yield* attempt(() => content.export({ directory }));
|
|
33
|
+
yield* Console.log(`Exported ${source.sources.length} documents to ${directory}`);
|
|
187
34
|
});
|
|
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
35
|
const preparePlan = Effect.fn("CmsCli.content.preparePlan")(function* (connection, directory, options) {
|
|
202
|
-
const
|
|
203
|
-
const
|
|
204
|
-
const
|
|
205
|
-
yield* printPlan(
|
|
206
|
-
if (!
|
|
36
|
+
const source = yield* attempt(() => readCmsContentDirectory(directory));
|
|
37
|
+
const content = yield* client(connection);
|
|
38
|
+
const plan = yield* attempt(() => content.plan(source, options));
|
|
39
|
+
yield* printPlan(plan);
|
|
40
|
+
if (!plan.valid) return yield* userError("Content plan is not ready to apply.");
|
|
207
41
|
return {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
value
|
|
42
|
+
content,
|
|
43
|
+
plan
|
|
211
44
|
};
|
|
212
45
|
});
|
|
213
46
|
const planContent = Effect.fn("CmsCli.content.planCommand")(function* (connection, directory, options) {
|
|
214
47
|
yield* preparePlan(connection, directory, options);
|
|
215
48
|
});
|
|
49
|
+
const applyContent = Effect.fn("CmsCli.content.apply")(function* (connection, directory, options) {
|
|
50
|
+
const { content, plan } = yield* preparePlan(connection, directory, options);
|
|
51
|
+
const result = yield* attempt(() => content.apply(plan));
|
|
52
|
+
yield* Console.log(`Applied: ${result.created} created, ${result.updated} updated, ${result.published} published, ${result.unchanged} unchanged.`);
|
|
53
|
+
});
|
|
216
54
|
const diffContent = Effect.fn("CmsCli.content.diff")(function* (connection, directory) {
|
|
217
|
-
const
|
|
218
|
-
const
|
|
219
|
-
const comparison =
|
|
55
|
+
const source = yield* attempt(() => readCmsContentDirectory(directory));
|
|
56
|
+
const content = yield* client(connection);
|
|
57
|
+
const comparison = describeContentDiffResult(yield* attempt(() => content.diff(source)));
|
|
220
58
|
for (const line of comparison.lines) yield* Console.log(line);
|
|
221
59
|
yield* Console.log(`${comparison.changed} local document${comparison.changed === 1 ? " differs" : "s differ"} from the CMS.`);
|
|
222
60
|
});
|
|
223
61
|
function describeContentDiff(local, remote) {
|
|
224
|
-
|
|
225
|
-
|
|
62
|
+
return describeContentDiffResult(diffCmsContentSources(local, remote));
|
|
63
|
+
}
|
|
64
|
+
function describeContentDiffResult(comparison) {
|
|
226
65
|
const lines = [];
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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++;
|
|
66
|
+
for (const change of comparison.changes) {
|
|
67
|
+
if (change.markdownChanged && change.local) lines.push(createTwoFilesPatch(`remote/${change.sourcePath}`, `local/${change.sourcePath}`, change.target?.markdown ?? "", change.local.markdown).trimEnd());
|
|
68
|
+
if (change.publicationChanged && change.local && change.target) lines.push(`Publication ${change.sourcePath}: CMS ${change.target.published ? "published" : "draft"}, local ${change.local.published ? "published (use --publish to apply)" : "draft (import keeps existing publication)"}`);
|
|
239
69
|
}
|
|
240
|
-
for (const source of
|
|
70
|
+
for (const source of comparison.targetOnly) lines.push(`Remote only: ${source.sourcePath}`);
|
|
241
71
|
return {
|
|
242
72
|
lines,
|
|
243
|
-
changed
|
|
73
|
+
changed: comparison.changes.length
|
|
244
74
|
};
|
|
245
75
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
});
|
|
76
|
+
function printPlan(value) {
|
|
77
|
+
return Effect.gen(function* () {
|
|
78
|
+
for (const [action, entries] of [
|
|
79
|
+
["create", value.creates],
|
|
80
|
+
["update", value.updates],
|
|
81
|
+
["publish", value.publications],
|
|
82
|
+
["unchanged", value.unchanged],
|
|
83
|
+
["conflict", value.conflicts]
|
|
84
|
+
]) for (const entry of entries) yield* Console.log(`${action.padEnd(9)} ${entry.sourcePath}`);
|
|
85
|
+
for (const issue of value.issues) yield* Console.error(`${issue.severity}: ${issue.sourcePath ?? ""} ${issue.message}`);
|
|
86
|
+
yield* Console.log(value.valid ? `Ready at generation ${value.generation}` : "Plan has conflicts or validation errors.");
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
function attempt(operation) {
|
|
90
|
+
return Effect.tryPromise({
|
|
91
|
+
try: () => Promise.resolve(operation()),
|
|
92
|
+
catch: (cause) => {
|
|
93
|
+
return userError(cause instanceof CmsContentError ? `${cause.code}: ${cause.message}` : String(cause));
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
255
97
|
|
|
256
98
|
//#endregion
|
|
257
|
-
export { applyContent,
|
|
99
|
+
export { applyContent, describeContentDiff, diffContent, exportContent, planContent, readSources };
|
|
258
100
|
//# sourceMappingURL=content.js.map
|
package/src/cli/content.js.map
CHANGED
|
@@ -1 +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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"}
|
|
1
|
+
{"version":3,"file":"content.js","names":[],"sources":["../../../../src/cli/content.ts"],"sourcesContent":["import type { ContentSource } from '@internal/protocol/ContentTransfer'\nimport { CmsContentError, createCmsContentClient, diffCmsContentSources, readCmsContentDirectory } from '../Content'\nimport type { CmsContentDiff, CmsContentPlan } from '../Content'\nimport { Config, Console, Effect, Option, Redacted } from 'effect'\nimport { CliError, Prompt } from 'effect/unstable/cli'\nimport { createTwoFilesPatch } from 'diff'\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 client = Effect.fn('CmsCli.content.client')(function* (options: ConnectionOptions) {\n const baseUrl =\n options.url ||\n (yield* Config.String('CMS_URL').pipe(Config.withDefault(''))) ||\n (yield* Prompt.String({ message: 'CMS URL' }))\n\n const email =\n options.email ||\n (yield* Config.String('CMS_EMAIL').pipe(Config.withDefault(''))) ||\n (yield* Prompt.String({ message: 'Administrator email' }))\n\n if (!baseUrl || !email) return yield* userError('CMS URL and administrator email are required.')\n const configured = yield* Config.Redacted('CMS_PASSWORD').pipe(Config.option)\n\n const password = Option.isSome(configured)\n ? Redacted.value(configured.value)\n : Redacted.value(yield* Prompt.Password({ message: `Password for ${email} at ${baseUrl}` }))\n\n return yield* attempt(() => createCmsContentClient({ baseUrl, credentials: { email, password } }))\n})\n\nexport const readSources = Effect.fn('CmsCli.content.readSources')(function* (directory: string) {\n return (yield* attempt(() => readCmsContentDirectory(directory))).sources\n})\n\nexport const exportContent = Effect.fn('CmsCli.content.export')(function* (\n connection: ConnectionOptions,\n directory: string,\n) {\n const content = yield* client(connection)\n const source = yield* attempt(() => content.export({ directory }))\n yield* Console.log(`Exported ${source.sources.length} documents to ${directory}`)\n})\n\nconst preparePlan = Effect.fn('CmsCli.content.preparePlan')(function* (\n connection: ConnectionOptions,\n directory: string,\n options: ImportOptions,\n) {\n const source = yield* attempt(() => readCmsContentDirectory(directory))\n const content = yield* client(connection)\n const plan = yield* attempt(() => content.plan(source, options))\n yield* printPlan(plan)\n\n if (!plan.valid) return yield* userError('Content plan is not ready to apply.')\n\n return { content, plan }\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 applyContent = Effect.fn('CmsCli.content.apply')(function* (\n connection: ConnectionOptions,\n directory: string,\n options: ImportOptions,\n) {\n const { content, plan } = yield* preparePlan(connection, directory, options)\n const result = yield* attempt(() => content.apply(plan))\n yield* Console.log(\n `Applied: ${result.created} created, ${result.updated} updated, ${result.published} published, ${result.unchanged} unchanged.`,\n )\n})\n\nexport const diffContent = Effect.fn('CmsCli.content.diff')(function* (\n connection: ConnectionOptions,\n directory: string,\n) {\n const source = yield* attempt(() => readCmsContentDirectory(directory))\n const content = yield* client(connection)\n const comparison = describeContentDiffResult(yield* attempt(() => content.diff(source)))\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 return describeContentDiffResult(diffCmsContentSources(local, remote))\n}\n\nfunction describeContentDiffResult(comparison: CmsContentDiff) {\n const lines: string[] = []\n\n for (const change of comparison.changes) {\n if (change.markdownChanged && change.local) {\n lines.push(\n createTwoFilesPatch(\n `remote/${change.sourcePath}`,\n `local/${change.sourcePath}`,\n change.target?.markdown ?? '',\n change.local.markdown,\n ).trimEnd(),\n )\n }\n\n if (change.publicationChanged && change.local && change.target)\n lines.push(\n `Publication ${change.sourcePath}: CMS ${change.target.published ? 'published' : 'draft'}, local ${change.local.published ? 'published (use --publish to apply)' : 'draft (import keeps existing publication)'}`,\n )\n }\n\n for (const source of comparison.targetOnly) lines.push(`Remote only: ${source.sourcePath}`)\n\n return { lines, changed: comparison.changes.length }\n}\n\nfunction printPlan(value: CmsContentPlan) {\n return Effect.gen(function* () {\n for (const [action, entries] of [\n ['create', value.creates],\n ['update', value.updates],\n ['publish', value.publications],\n ['unchanged', value.unchanged],\n ['conflict', value.conflicts],\n ] as const)\n for (const entry of entries) yield* Console.log(`${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\nfunction attempt<Value>(operation: () => Value | PromiseLike<Value>) {\n return Effect.tryPromise({\n try: () => Promise.resolve(operation()),\n catch: (cause) => {\n const message = cause instanceof CmsContentError ? `${cause.code}: ${cause.message}` : String(cause)\n\n return userError(message)\n },\n })\n}\n"],"mappings":"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"}
|
package/src/cli.js
CHANGED
|
@@ -1,17 +1,27 @@
|
|
|
1
1
|
import { applyContent, diffContent, exportContent, planContent } from "./cli/content.js";
|
|
2
|
-
import {
|
|
2
|
+
import { i as resetCmsDevelopment, n as doctorCmsDevelopment, r as loadCmsProjectEnvironment, t as bootstrapCmsDevelopment } from "./dev-lifecycle-CZ7iCzG9.js";
|
|
3
|
+
import { diagnosticFromCause, renderDiagnostic } from "./internal/diagnostics.js";
|
|
4
|
+
import { Config, Console, Effect, Layer, Match, Option, Redacted, Runtime, Schema } from "effect";
|
|
3
5
|
import { NodeChildProcessSpawner, NodeCrypto, NodeFileSystem, NodePath, NodeRuntime, NodeStdio, NodeTerminal } from "@effect/platform-node-shared";
|
|
4
6
|
import { FetchHttpClient } from "effect/unstable/http";
|
|
5
|
-
import { Argument, Command, Flag } from "effect/unstable/cli";
|
|
7
|
+
import { Argument, CliError, Command, Flag, Prompt } from "effect/unstable/cli";
|
|
6
8
|
|
|
7
9
|
//#region package.json
|
|
8
|
-
var version = "0.
|
|
10
|
+
var version = "0.2.0-beta.2";
|
|
9
11
|
|
|
10
12
|
//#endregion
|
|
11
13
|
//#region src/cli.ts
|
|
14
|
+
const rootDirectory = Flag.String("root").pipe(Flag.withDefault("."));
|
|
15
|
+
const envFile = Flag.String("env-file").pipe(Flag.withDefault(""));
|
|
16
|
+
const verbose = Flag.Boolean("verbose").pipe(Flag.withDefault(false));
|
|
17
|
+
const json = Flag.Boolean("json").pipe(Flag.withDefault(false));
|
|
12
18
|
const root = Command.make("cms").pipe(Command.withSharedFlags({
|
|
13
19
|
url: Flag.String("url").pipe(Flag.withDefault("")),
|
|
14
|
-
email: Flag.String("email").pipe(Flag.withDefault(""))
|
|
20
|
+
email: Flag.String("email").pipe(Flag.withDefault("")),
|
|
21
|
+
rootDirectory,
|
|
22
|
+
envFile,
|
|
23
|
+
verbose,
|
|
24
|
+
json
|
|
15
25
|
}));
|
|
16
26
|
const directory = Argument.String("directory");
|
|
17
27
|
const overwrite = Flag.Boolean("overwrite").pipe(Flag.withDefault(false));
|
|
@@ -64,7 +74,112 @@ const content = Command.make("content").pipe(Command.withSubcommands([
|
|
|
64
74
|
planCommand,
|
|
65
75
|
applyCommand
|
|
66
76
|
]));
|
|
67
|
-
|
|
77
|
+
const full = Flag.Boolean("full").pipe(Flag.withDefault(false));
|
|
78
|
+
const contentDirectory = Flag.String("content").pipe(Flag.withDefault(""));
|
|
79
|
+
const administratorName = Flag.String("name").pipe(Flag.withDefault(""));
|
|
80
|
+
const database = Flag.Boolean("database").pipe(Flag.withDefault(false));
|
|
81
|
+
const media = Flag.Boolean("media").pipe(Flag.withDefault(false));
|
|
82
|
+
const bootstrap = Flag.Boolean("bootstrap").pipe(Flag.withDefault(false));
|
|
83
|
+
const yes = Flag.Boolean("yes").pipe(Flag.withDefault(false));
|
|
84
|
+
const doctorCommand = Command.make("doctor", { full }, ({ full }) => Effect.gen(function* () {
|
|
85
|
+
const options = yield* root;
|
|
86
|
+
const report = yield* attempt("doctor.configuration", options, () => doctorCmsDevelopment(options.rootDirectory, full, options.envFile || void 0));
|
|
87
|
+
if (options.json) yield* Console.log(JSON.stringify(report));
|
|
88
|
+
else for (const check of report.checks) {
|
|
89
|
+
const icon = Match.value(check.status).pipe(Match.when("pass", () => "✓"), Match.when("fail", () => "✗"), Match.when("not-verified", () => "−"), Match.exhaustive);
|
|
90
|
+
yield* Console.log(`${icon} ${check.label}${check.detail ? `\n\n ${check.detail}` : ""}`);
|
|
91
|
+
}
|
|
92
|
+
if (!report.ok) return yield* options.json ? new JsonCommandFailed({}) : cliError("One or more CMS development checks failed.");
|
|
93
|
+
})).pipe(Command.withDescription("Check local CMS configuration and infrastructure"));
|
|
94
|
+
const bootstrapCommand = Command.make("bootstrap", {
|
|
95
|
+
contentDirectory,
|
|
96
|
+
administratorName,
|
|
97
|
+
publish
|
|
98
|
+
}, ({ contentDirectory, administratorName, publish }) => Effect.gen(function* () {
|
|
99
|
+
const options = yield* root;
|
|
100
|
+
yield* attempt("bootstrap.environment", options, () => loadCmsProjectEnvironment(options.rootDirectory, options.envFile || void 0));
|
|
101
|
+
const administrator = yield* administratorOptions(administratorName, contentDirectory, publish, options, "bootstrap.credentials");
|
|
102
|
+
const result = yield* attempt("bootstrap.installation", options, () => bootstrapCmsDevelopment({
|
|
103
|
+
rootDir: options.rootDirectory,
|
|
104
|
+
envFile: options.envFile || void 0,
|
|
105
|
+
verbose: options.verbose,
|
|
106
|
+
...administrator
|
|
107
|
+
}));
|
|
108
|
+
if (options.json) yield* Console.log(JSON.stringify(result));
|
|
109
|
+
else yield* Console.log(`Bootstrap complete: administrator ${result.administrator}${result.contentApplied ? ", content applied" : ""}.`);
|
|
110
|
+
})).pipe(Command.withDescription("Provision and initialize a local CMS installation"));
|
|
111
|
+
const resetCommand = Command.make("reset", {
|
|
112
|
+
bootstrap,
|
|
113
|
+
contentDirectory,
|
|
114
|
+
database,
|
|
115
|
+
media,
|
|
116
|
+
publish,
|
|
117
|
+
yes
|
|
118
|
+
}, ({ bootstrap, contentDirectory, database, media, publish, yes }) => Effect.gen(function* () {
|
|
119
|
+
const options = yield* root;
|
|
120
|
+
if (!database && !media) return yield* commandFailure("reset.arguments", options, /* @__PURE__ */ new Error("Choose --database, --media, or both."));
|
|
121
|
+
if (bootstrap && yes) yield* attempt("reset.environment", options, () => loadCmsProjectEnvironment(options.rootDirectory, options.envFile || void 0));
|
|
122
|
+
const administrator = bootstrap && yes ? yield* administratorOptions("", contentDirectory, publish, options, "reset.credentials") : void 0;
|
|
123
|
+
const result = yield* attempt("reset.installation", options, () => resetCmsDevelopment({
|
|
124
|
+
rootDir: options.rootDirectory,
|
|
125
|
+
envFile: options.envFile || void 0,
|
|
126
|
+
database,
|
|
127
|
+
media,
|
|
128
|
+
yes,
|
|
129
|
+
verbose: options.verbose,
|
|
130
|
+
bootstrap: administrator
|
|
131
|
+
}));
|
|
132
|
+
if (options.json) return yield* Console.log(JSON.stringify(result));
|
|
133
|
+
for (const target of result.targets) yield* Console.log(`${result.executed ? "✓ Reset" : "Would reset"} ${target}`);
|
|
134
|
+
if (!result.executed) yield* Console.log("Dry run only. Add --yes to execute these exact targets.");
|
|
135
|
+
})).pipe(Command.withDescription("Reset CMS-owned local data with a dry-run default"));
|
|
136
|
+
const dev = Command.make("dev").pipe(Command.withSubcommands([
|
|
137
|
+
doctorCommand,
|
|
138
|
+
bootstrapCommand,
|
|
139
|
+
resetCommand
|
|
140
|
+
]));
|
|
141
|
+
const administratorOptions = Effect.fn("CmsCli.dev.administratorOptions")(function* (name, content, publishContent, options, phase) {
|
|
142
|
+
const email = yield* Config.String("CMS_EMAIL").pipe(Config.withDefault(""));
|
|
143
|
+
const configuredPassword = yield* Config.Redacted("CMS_PASSWORD").pipe(Config.option);
|
|
144
|
+
if (!email) return yield* commandFailure(phase, options, /* @__PURE__ */ new Error("CMS_EMAIL is required."));
|
|
145
|
+
if (Option.isNone(configuredPassword) && options.json) return yield* commandFailure(phase, options, /* @__PURE__ */ new Error("CMS_PASSWORD is required."));
|
|
146
|
+
const password = Option.isSome(configuredPassword) ? Redacted.value(configuredPassword.value) : Redacted.value(yield* Prompt.Password({ message: `Password for ${email}` }));
|
|
147
|
+
if (!password) return yield* commandFailure(phase, options, /* @__PURE__ */ new Error("CMS_PASSWORD is required."));
|
|
148
|
+
return {
|
|
149
|
+
content: content || void 0,
|
|
150
|
+
email,
|
|
151
|
+
name: name || (yield* Config.String("CMS_NAME").pipe(Config.withDefault("Administrator"))),
|
|
152
|
+
password,
|
|
153
|
+
publish: publishContent
|
|
154
|
+
};
|
|
155
|
+
});
|
|
156
|
+
function cliError(message) {
|
|
157
|
+
return new CliError.UserError({
|
|
158
|
+
cause: new Error(message),
|
|
159
|
+
userMessage: message
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
var CliOperationFailed = class extends Schema.TaggedError()("CliOperationFailed", { cause: Schema.Defect() }) {};
|
|
163
|
+
var JsonCommandFailed = class extends Schema.TaggedError()("JsonCommandFailed", {}) {
|
|
164
|
+
[Runtime.errorExitCode] = 1;
|
|
165
|
+
[Runtime.errorReported] = false;
|
|
166
|
+
};
|
|
167
|
+
function attempt(phase, options, operation) {
|
|
168
|
+
return Effect.tryPromise({
|
|
169
|
+
try: () => Promise.resolve(operation()),
|
|
170
|
+
catch: (cause) => new CliOperationFailed({ cause })
|
|
171
|
+
}).pipe(Effect.catch((failure) => commandFailure(phase, options, failure.cause)));
|
|
172
|
+
}
|
|
173
|
+
function commandFailure(phase, options, cause) {
|
|
174
|
+
return Effect.gen(function* () {
|
|
175
|
+
const diagnostic = diagnosticFromCause(phase, cause);
|
|
176
|
+
if (!options.json) return yield* cliError(renderDiagnostic(diagnostic, options.verbose));
|
|
177
|
+
const { details: _details, ...output } = diagnostic;
|
|
178
|
+
yield* Console.log(JSON.stringify(output));
|
|
179
|
+
return yield* new JsonCommandFailed({});
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
root.pipe(Command.withSubcommands([content, dev]), 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
183
|
|
|
69
184
|
//#endregion
|
|
70
185
|
export { };
|
package/src/cli.js.map
CHANGED
|
@@ -1 +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
|
+
{"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 { Config, Console, Effect, Layer, Match, Option, Redacted, Runtime, Schema } from 'effect'\nimport { Argument, CliError, Command, Flag, Prompt } 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'\nimport {\n bootstrapCmsDevelopment,\n doctorCmsDevelopment,\n loadCmsProjectEnvironment,\n resetCmsDevelopment,\n} from './internal/dev-lifecycle'\nimport { diagnosticFromCause, renderDiagnostic } from './internal/diagnostics'\n\nconst rootDirectory = Flag.String('root').pipe(Flag.withDefault('.'))\n\nconst envFile = Flag.String('env-file').pipe(Flag.withDefault(''))\n\nconst verbose = Flag.Boolean('verbose').pipe(Flag.withDefault(false))\n\nconst json = Flag.Boolean('json').pipe(Flag.withDefault(false))\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 rootDirectory,\n envFile,\n verbose,\n json,\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\nconst full = Flag.Boolean('full').pipe(Flag.withDefault(false))\n\nconst contentDirectory = Flag.String('content').pipe(Flag.withDefault(''))\n\nconst administratorName = Flag.String('name').pipe(Flag.withDefault(''))\n\nconst database = Flag.Boolean('database').pipe(Flag.withDefault(false))\n\nconst media = Flag.Boolean('media').pipe(Flag.withDefault(false))\n\nconst bootstrap = Flag.Boolean('bootstrap').pipe(Flag.withDefault(false))\n\nconst yes = Flag.Boolean('yes').pipe(Flag.withDefault(false))\n\nconst doctorCommand = Command.make('doctor', { full }, ({ full }) =>\n Effect.gen(function* () {\n const options = yield* root\n\n const report = yield* attempt('doctor.configuration', options, () =>\n doctorCmsDevelopment(options.rootDirectory, full, options.envFile || undefined),\n )\n\n if (options.json) {\n // oxlint-disable-next-line effecttsgo/prefer-schema-over-json -- This flag intentionally emits the report's exact JSON representation.\n yield* Console.log(JSON.stringify(report))\n } else\n for (const check of report.checks) {\n const icon = Match.value(check.status).pipe(\n Match.when('pass', () => '✓'),\n Match.when('fail', () => '✗'),\n Match.when('not-verified', () => '−'),\n Match.exhaustive,\n )\n\n yield* Console.log(`${icon} ${check.label}${check.detail ? `\\n\\n ${check.detail}` : ''}`)\n }\n\n if (!report.ok)\n return yield* options.json ? new JsonCommandFailed({}) : cliError('One or more CMS development checks failed.')\n }),\n).pipe(Command.withDescription('Check local CMS configuration and infrastructure'))\n\nconst bootstrapCommand = Command.make(\n 'bootstrap',\n { contentDirectory, administratorName, publish },\n ({ contentDirectory, administratorName, publish }) =>\n Effect.gen(function* () {\n const options = yield* root\n yield* attempt('bootstrap.environment', options, () =>\n loadCmsProjectEnvironment(options.rootDirectory, options.envFile || undefined),\n )\n\n const administrator = yield* administratorOptions(\n administratorName,\n contentDirectory,\n publish,\n options,\n 'bootstrap.credentials',\n )\n\n const result = yield* attempt('bootstrap.installation', options, () =>\n bootstrapCmsDevelopment({\n rootDir: options.rootDirectory,\n envFile: options.envFile || undefined,\n verbose: options.verbose,\n ...administrator,\n }),\n )\n\n if (options.json) {\n // oxlint-disable-next-line effecttsgo/prefer-schema-over-json -- This flag intentionally emits the report's exact JSON representation.\n yield* Console.log(JSON.stringify(result))\n } else\n yield* Console.log(\n `Bootstrap complete: administrator ${result.administrator}${result.contentApplied ? ', content applied' : ''}.`,\n )\n }),\n).pipe(Command.withDescription('Provision and initialize a local CMS installation'))\n\nconst resetCommand = Command.make(\n 'reset',\n { bootstrap, contentDirectory, database, media, publish, yes },\n ({ bootstrap, contentDirectory, database, media, publish, yes }) =>\n Effect.gen(function* () {\n const options = yield* root\n\n if (!database && !media)\n return yield* commandFailure('reset.arguments', options, new Error('Choose --database, --media, or both.'))\n\n if (bootstrap && yes)\n yield* attempt('reset.environment', options, () =>\n loadCmsProjectEnvironment(options.rootDirectory, options.envFile || undefined),\n )\n\n const administrator =\n bootstrap && yes\n ? yield* administratorOptions('', contentDirectory, publish, options, 'reset.credentials')\n : undefined\n\n const result = yield* attempt('reset.installation', options, () =>\n resetCmsDevelopment({\n rootDir: options.rootDirectory,\n envFile: options.envFile || undefined,\n database,\n media,\n yes,\n verbose: options.verbose,\n bootstrap: administrator,\n }),\n )\n\n if (options.json) {\n // oxlint-disable-next-line effecttsgo/prefer-schema-over-json -- This flag intentionally emits the report's exact JSON representation.\n return yield* Console.log(JSON.stringify(result))\n }\n\n for (const target of result.targets)\n yield* Console.log(`${result.executed ? '✓ Reset' : 'Would reset'} ${target}`)\n\n if (!result.executed) yield* Console.log('Dry run only. Add --yes to execute these exact targets.')\n }),\n).pipe(Command.withDescription('Reset CMS-owned local data with a dry-run default'))\n\nconst dev = Command.make('dev').pipe(Command.withSubcommands([doctorCommand, bootstrapCommand, resetCommand]))\n\nconst administratorOptions = Effect.fn('CmsCli.dev.administratorOptions')(function* (\n name: string,\n content: string,\n publishContent: boolean,\n options: { readonly json: boolean; readonly verbose: boolean },\n phase: string,\n) {\n const email = yield* Config.String('CMS_EMAIL').pipe(Config.withDefault(''))\n const configuredPassword = yield* Config.Redacted('CMS_PASSWORD').pipe(Config.option)\n\n if (!email) return yield* commandFailure(phase, options, new Error('CMS_EMAIL is required.'))\n\n if (Option.isNone(configuredPassword) && options.json)\n return yield* commandFailure(phase, options, new Error('CMS_PASSWORD is required.'))\n\n const password = Option.isSome(configuredPassword)\n ? Redacted.value(configuredPassword.value)\n : Redacted.value(yield* Prompt.Password({ message: `Password for ${email}` }))\n\n if (!password) return yield* commandFailure(phase, options, new Error('CMS_PASSWORD is required.'))\n\n return {\n content: content || undefined,\n email,\n name: name || (yield* Config.String('CMS_NAME').pipe(Config.withDefault('Administrator'))),\n password,\n publish: publishContent,\n }\n})\n\nfunction cliError(message: string) {\n return new CliError.UserError({ cause: new Error(message), userMessage: message })\n}\n\nclass CliOperationFailed extends Schema.TaggedError<CliOperationFailed>()('CliOperationFailed', {\n cause: Schema.Defect(),\n}) {}\n\nclass JsonCommandFailed extends Schema.TaggedError<JsonCommandFailed>()('JsonCommandFailed', {}) {\n override readonly [Runtime.errorExitCode] = 1\n override [Runtime.errorReported] = false\n}\n\nfunction attempt<Value>(\n phase: string,\n options: { readonly json: boolean; readonly verbose: boolean },\n operation: () => Value | PromiseLike<Value>,\n) {\n return Effect.tryPromise({\n try: () => Promise.resolve(operation()),\n catch: (cause) => new CliOperationFailed({ cause }),\n }).pipe(Effect.catch((failure) => commandFailure(phase, options, failure.cause)))\n}\n\nfunction commandFailure(phase: string, options: { readonly json: boolean; readonly verbose: boolean }, cause: unknown) {\n return Effect.gen(function* () {\n const diagnostic = diagnosticFromCause(phase, cause)\n\n if (!options.json) return yield* cliError(renderDiagnostic(diagnostic, options.verbose))\n\n const { details: _details, ...output } = diagnostic\n\n // oxlint-disable-next-line effecttsgo/prefer-schema-over-json -- CLI JSON is an intentionally stable plain object.\n yield* Console.log(JSON.stringify(output))\n\n return yield* new JsonCommandFailed({})\n })\n}\n\nroot.pipe(\n Command.withSubcommands([content, dev]),\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":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as DocumentPreviewMessage, o as isDocumentPreviewMessage, t as DOCUMENT_PREVIEW_CHANNEL } from "./models-
|
|
1
|
+
import { n as DocumentPreviewMessage, o as isDocumentPreviewMessage, t as DOCUMENT_PREVIEW_CHANNEL } from "./models-C9kF9JlQ.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-
|
|
92
|
+
//# sourceMappingURL=connection-D5S9Iph-.js.map
|