pagecast 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/cli.js ADDED
@@ -0,0 +1,333 @@
1
+ #!/usr/bin/env node
2
+ import path from "node:path";
3
+ import process from "node:process";
4
+ import { spawn } from "node:child_process";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ import {
8
+ deployCloudflarePagesSite,
9
+ getCloudflarePagesStatus,
10
+ listCloudflarePagesProjects,
11
+ publishReportSnapshot,
12
+ setupCloudflarePages,
13
+ startServers
14
+ } from "./server.js";
15
+
16
+ const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
17
+ // When invoked via npx, the package lives in the npm cache, so reports and config
18
+ // must live in the user's working directory, not next to the installed code.
19
+ const dataDir = path.join(process.cwd(), ".pagecast");
20
+ const staticDir = path.join(packageRoot, "public");
21
+
22
+ function openBrowser(url) {
23
+ const platform = process.platform;
24
+ const command =
25
+ platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
26
+ const args = platform === "win32" ? ["/c", "start", "", url] : [url];
27
+ try {
28
+ const child = spawn(command, args, { stdio: "ignore", detached: true });
29
+ child.on("error", () => {});
30
+ child.unref();
31
+ } catch {
32
+ // Headless or no browser available — the printed URL is the fallback.
33
+ }
34
+ }
35
+
36
+ const VALUE_FLAGS = new Set([
37
+ "account",
38
+ "account-id",
39
+ "branch",
40
+ "label",
41
+ "mode",
42
+ "output",
43
+ "project",
44
+ "project-name"
45
+ ]);
46
+
47
+ function parseFlags(args) {
48
+ const flags = new Set();
49
+ const options = {};
50
+ const positionals = [];
51
+ for (let i = 0; i < args.length; i += 1) {
52
+ const arg = args[i];
53
+ if (arg.startsWith("--")) {
54
+ const withoutPrefix = arg.slice(2);
55
+ const equalsIndex = withoutPrefix.indexOf("=");
56
+ const key = equalsIndex >= 0 ? withoutPrefix.slice(0, equalsIndex) : withoutPrefix;
57
+ if (equalsIndex >= 0) {
58
+ options[key] = withoutPrefix.slice(equalsIndex + 1);
59
+ } else if (VALUE_FLAGS.has(key)) {
60
+ const next = args[i + 1];
61
+ if (typeof next === "string" && !next.startsWith("--")) {
62
+ options[key] = next;
63
+ i += 1;
64
+ } else {
65
+ options[key] = "";
66
+ }
67
+ } else {
68
+ flags.add(key);
69
+ }
70
+ } else {
71
+ positionals.push(arg);
72
+ }
73
+ }
74
+ return { flags, options, positionals };
75
+ }
76
+
77
+ function optionValue(parsed, ...names) {
78
+ for (const name of names) {
79
+ if (typeof parsed.options[name] === "string" && parsed.options[name].trim()) {
80
+ return parsed.options[name];
81
+ }
82
+ }
83
+ return "";
84
+ }
85
+
86
+ function wantsJson(parsed) {
87
+ return parsed.flags.has("json") || optionValue(parsed, "output") === "json";
88
+ }
89
+
90
+ function errorCode(statusCode) {
91
+ if (statusCode === 400) {
92
+ return "usage_error";
93
+ }
94
+ if (statusCode === 401) {
95
+ return "auth_required";
96
+ }
97
+ if (statusCode === 404) {
98
+ return "not_found";
99
+ }
100
+ if (statusCode === 409) {
101
+ return "conflict";
102
+ }
103
+ if (statusCode >= 500) {
104
+ return "provider_error";
105
+ }
106
+ return "error";
107
+ }
108
+
109
+ function printError(error, json) {
110
+ const statusCode = error.statusCode || 500;
111
+ const payload = {
112
+ ok: false,
113
+ code: errorCode(statusCode),
114
+ error: error.message,
115
+ statusCode
116
+ };
117
+ if (json) {
118
+ console.log(JSON.stringify(payload));
119
+ } else {
120
+ console.error(error.message);
121
+ }
122
+ process.exit(statusCode === 400 ? 2 : 1);
123
+ }
124
+
125
+ function pagesOptions(parsed) {
126
+ return {
127
+ projectName: optionValue(parsed, "project", "project-name"),
128
+ accountId: optionValue(parsed, "account", "account-id"),
129
+ branch: optionValue(parsed, "branch") || "main"
130
+ };
131
+ }
132
+
133
+ function printDeployResult(result, json) {
134
+ if (json) {
135
+ console.log(JSON.stringify({ ok: true, ...result }));
136
+ return;
137
+ }
138
+ console.log(`Deployed: ${result.url}`);
139
+ if (result.deploymentUrl && result.deploymentUrl !== result.url) {
140
+ console.log(`Deployment URL: ${result.deploymentUrl}`);
141
+ }
142
+ }
143
+
144
+ function printSetupResult(result, json) {
145
+ if (json) {
146
+ console.log(JSON.stringify({ ok: true, ...result }));
147
+ return;
148
+ }
149
+ const projectName = result.config?.pages?.projectName || result.cloudflare?.selectedProject?.name || "pagecast";
150
+ const accountName = result.cloudflare?.account?.name || result.config?.pages?.accountName || "Cloudflare account";
151
+ console.log(`Cloudflare Pages ready: ${projectName}`);
152
+ console.log(`Account: ${accountName}`);
153
+ }
154
+
155
+ function printStatusResult(result, json) {
156
+ if (json) {
157
+ console.log(JSON.stringify({ ok: true, ...result }));
158
+ return;
159
+ }
160
+ const status = result.cloudflare.loggedIn ? "connected" : "not connected";
161
+ console.log(`Cloudflare: ${status}`);
162
+ console.log(`Project: ${result.cloudflare.projectName}`);
163
+ if (result.cloudflare.accountName) {
164
+ console.log(`Account: ${result.cloudflare.accountName}`);
165
+ }
166
+ console.log(`URL: ${result.cloudflare.baseUrl}`);
167
+ }
168
+
169
+ function printProjectsResult(result, json) {
170
+ if (json) {
171
+ console.log(JSON.stringify({ ok: true, ...result }));
172
+ return;
173
+ }
174
+ if (result.projects.length === 0) {
175
+ console.log("No Cloudflare Pages projects found.");
176
+ return;
177
+ }
178
+ for (const project of result.projects) {
179
+ const branch = project.productionBranch ? ` (${project.productionBranch})` : "";
180
+ console.log(`${project.name}${branch}`);
181
+ }
182
+ }
183
+
184
+ async function serve() {
185
+ const runtime = await startServers({ dataDir, staticDir });
186
+ console.log(`Pagecast admin: ${runtime.adminUrl}`);
187
+ console.log(`Local published-page server: ${runtime.publicUrl}`);
188
+ console.log("Opening the admin UI in your browser. Press Ctrl-C to stop.");
189
+ openBrowser(runtime.adminUrl);
190
+
191
+ const shutdown = async () => {
192
+ await runtime.close();
193
+ process.exit(0);
194
+ };
195
+ process.once("SIGINT", shutdown);
196
+ process.once("SIGTERM", shutdown);
197
+ }
198
+
199
+ async function publish(args) {
200
+ const parsed = parseFlags(args);
201
+ const json = wantsJson(parsed);
202
+ if (parsed.positionals[0] === "site") {
203
+ await deploySite([], { ...parsed, positionals: parsed.positionals.slice(1) });
204
+ return;
205
+ }
206
+ const label = optionValue(parsed, "label");
207
+ const reportPath = parsed.positionals[0];
208
+
209
+ try {
210
+ const result = await publishReportSnapshot({ path: reportPath, label, dataDir });
211
+ if (json) {
212
+ console.log(JSON.stringify({ ok: true, ...result }));
213
+ } else {
214
+ console.log(`Published: ${result.url}`);
215
+ }
216
+ } catch (error) {
217
+ printError(error, json);
218
+ }
219
+ }
220
+
221
+ async function deploySite(args, parsed = parseFlags(args)) {
222
+ const json = wantsJson(parsed);
223
+ const sourceDir = parsed.positionals[0];
224
+ const { projectName, accountId, branch } = pagesOptions(parsed);
225
+
226
+ try {
227
+ const result = await deployCloudflarePagesSite({
228
+ sourceDir,
229
+ projectName,
230
+ accountId,
231
+ branch,
232
+ dataDir
233
+ });
234
+ printDeployResult(result, json);
235
+ } catch (error) {
236
+ printError(error, json);
237
+ }
238
+ }
239
+
240
+ async function pages(args) {
241
+ const [subcommand, ...rest] = args;
242
+ const parsed = parseFlags(rest);
243
+ const json = wantsJson(parsed);
244
+ const { projectName, accountId, branch } = pagesOptions(parsed);
245
+
246
+ try {
247
+ if (subcommand === "setup") {
248
+ const result = await setupCloudflarePages({
249
+ projectName,
250
+ accountId,
251
+ branch,
252
+ dataDir
253
+ });
254
+ printSetupResult(result, json);
255
+ return;
256
+ }
257
+
258
+ if (subcommand === "status") {
259
+ const result = await getCloudflarePagesStatus({ dataDir });
260
+ printStatusResult(result, json);
261
+ return;
262
+ }
263
+
264
+ if (subcommand === "projects" && parsed.positionals[0] === "list") {
265
+ const result = await listCloudflarePagesProjects({
266
+ accountId,
267
+ dataDir
268
+ });
269
+ printProjectsResult(result, json);
270
+ return;
271
+ }
272
+
273
+ if (subcommand === "deploy") {
274
+ await deploySite(rest);
275
+ return;
276
+ }
277
+
278
+ console.error(`Unknown pages command: ${[subcommand, ...parsed.positionals].filter(Boolean).join(" ")}\n`);
279
+ usage();
280
+ process.exit(1);
281
+ } catch (error) {
282
+ printError(error, json);
283
+ }
284
+ }
285
+
286
+ function usage() {
287
+ console.log(
288
+ [
289
+ "Usage:",
290
+ " pagecast [serve] Start the local app and open the admin UI",
291
+ " pagecast publish <path> [--json] Publish an HTML/Markdown snapshot",
292
+ " pagecast publish site <dir> --project <name> [--json] Deploy a static folder to Pages",
293
+ " pagecast pages setup [--project <name>] [--json] Connect and prepare Cloudflare Pages",
294
+ " pagecast pages status [--json] Show Cloudflare Pages configuration",
295
+ " pagecast pages projects list [--json] List Cloudflare Pages projects",
296
+ " pagecast pages deploy <dir> --project <name> [--json] Deploy a static folder to Pages",
297
+ " pagecast --help Show this help"
298
+ ].join("\n")
299
+ );
300
+ }
301
+
302
+ async function run() {
303
+ const [command, ...rest] = process.argv.slice(2);
304
+
305
+ if (command === "--help" || command === "-h" || command === "help") {
306
+ usage();
307
+ return;
308
+ }
309
+
310
+ if (command === "publish") {
311
+ await publish(rest);
312
+ return;
313
+ }
314
+
315
+ if (command === "pages") {
316
+ await pages(rest);
317
+ return;
318
+ }
319
+
320
+ if (!command || command === "serve") {
321
+ await serve();
322
+ return;
323
+ }
324
+
325
+ console.error(`Unknown command: ${command}\n`);
326
+ usage();
327
+ process.exit(1);
328
+ }
329
+
330
+ run().catch((error) => {
331
+ console.error(error.message || error);
332
+ process.exit(1);
333
+ });
Binary file