@tryghost/ghst 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/dist/index.js +235 -63
- package/dist/index.js.map +1 -1
- package/package.json +11 -11
package/README.md
CHANGED
|
@@ -109,10 +109,15 @@ Site/profile management:
|
|
|
109
109
|
ghst auth list
|
|
110
110
|
ghst auth switch <site-alias>
|
|
111
111
|
ghst auth link --site <site-alias>
|
|
112
|
+
ghst auth link --site <site-alias> --yes
|
|
113
|
+
ghst auth logout --yes
|
|
112
114
|
ghst auth token
|
|
113
115
|
```
|
|
114
116
|
|
|
115
117
|
`ghst auth token` prints a short-lived staff JWT. Treat the output as sensitive.
|
|
118
|
+
`ghst auth logout` requires confirmation when removing all configured sites; use `--yes` in non-interactive scripts.
|
|
119
|
+
`ghst auth link` requires confirmation before replacing an existing project link; use `--yes` in non-interactive scripts.
|
|
120
|
+
Interactive destructive confirmations also emit `GHST_AGENT_NOTICE:` lines on stderr instructing cooperative agents to ask the user for approval before continuing.
|
|
116
121
|
|
|
117
122
|
## Command Reference
|
|
118
123
|
|
|
@@ -229,6 +234,7 @@ ghst socialweb status
|
|
|
229
234
|
ghst socialweb profile
|
|
230
235
|
ghst socialweb notes --json
|
|
231
236
|
ghst socialweb follow @alice@example.com
|
|
237
|
+
ghst socialweb delete https://example.com/.ghost/activitypub/note/1 --yes
|
|
232
238
|
ghst socialweb note --content "Hello fediverse"
|
|
233
239
|
ghst socialweb reply https://example.com/users/alice/statuses/1 --content "Replying from ghst"
|
|
234
240
|
```
|
|
@@ -236,6 +242,7 @@ ghst socialweb reply https://example.com/users/alice/statuses/1 --content "Reply
|
|
|
236
242
|
Social web auth note:
|
|
237
243
|
- `ghst socialweb` bootstraps a short-lived identity JWT from `/ghost/api/admin/identities/`.
|
|
238
244
|
- That bridge requires an Owner or Administrator staff access token.
|
|
245
|
+
- `ghst socialweb delete` requires confirmation; use `--yes` in non-interactive scripts.
|
|
239
246
|
- Public Ghost post publishing still lives under `ghst post`; `ghst socialweb` is for notes, interactions, profile, feed, and moderation flows.
|
|
240
247
|
|
|
241
248
|
Ghost analytics filter semantics:
|
|
@@ -246,6 +253,9 @@ Ghost range semantics:
|
|
|
246
253
|
- `stats growth` clips member, MRR, and subscription histories client-side when Ghost only exposes broader source data.
|
|
247
254
|
- `stats post ... growth` clips Ghost's lifetime post-growth history to the selected window.
|
|
248
255
|
|
|
256
|
+
File output safety:
|
|
257
|
+
- `ghst member export --output`, `ghst stats ... --csv --output`, and `ghst migrate export --output` refuse to overwrite an existing file.
|
|
258
|
+
|
|
249
259
|
`endpointPath` must stay within the selected Ghost API root. Use resource paths such as `/posts/`
|
|
250
260
|
or canonical Ghost API paths such as `/ghost/api/admin/posts/`.
|
|
251
261
|
|
package/dist/index.js
CHANGED
|
@@ -1089,6 +1089,13 @@ async function findProjectConfigPath(cwd = process.cwd()) {
|
|
|
1089
1089
|
dir = path.dirname(dir);
|
|
1090
1090
|
}
|
|
1091
1091
|
}
|
|
1092
|
+
async function resolveProjectConfigCwd(cwd = process.cwd()) {
|
|
1093
|
+
const configPath = await findProjectConfigPath(cwd);
|
|
1094
|
+
if (!configPath) {
|
|
1095
|
+
return cwd;
|
|
1096
|
+
}
|
|
1097
|
+
return path.dirname(path.dirname(configPath));
|
|
1098
|
+
}
|
|
1092
1099
|
async function readUserConfig(env = process.env) {
|
|
1093
1100
|
const configPath = getUserConfigPath(env);
|
|
1094
1101
|
try {
|
|
@@ -2439,13 +2446,44 @@ function registerApiCommands(program) {
|
|
|
2439
2446
|
|
|
2440
2447
|
// src/commands/auth.ts
|
|
2441
2448
|
import { spawn as spawn2 } from "child_process";
|
|
2442
|
-
import
|
|
2449
|
+
import readline2 from "readline/promises";
|
|
2443
2450
|
import chalk2 from "chalk";
|
|
2451
|
+
|
|
2452
|
+
// src/lib/prompts.ts
|
|
2453
|
+
import readline from "readline/promises";
|
|
2454
|
+
async function defaultPrompt(question) {
|
|
2455
|
+
const rl = readline.createInterface({
|
|
2456
|
+
input: process.stdin,
|
|
2457
|
+
output: process.stdout
|
|
2458
|
+
});
|
|
2459
|
+
try {
|
|
2460
|
+
const value = await rl.question(question);
|
|
2461
|
+
return value.trim();
|
|
2462
|
+
} finally {
|
|
2463
|
+
rl.close();
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
2466
|
+
var promptHandler = defaultPrompt;
|
|
2467
|
+
async function ask(question) {
|
|
2468
|
+
return promptHandler(question);
|
|
2469
|
+
}
|
|
2470
|
+
async function confirm(question) {
|
|
2471
|
+
const value = (await ask(question)).trim().toLowerCase();
|
|
2472
|
+
return value === "y" || value === "yes";
|
|
2473
|
+
}
|
|
2474
|
+
async function confirmDestructiveAction(question, notice) {
|
|
2475
|
+
console.error("GHST_AGENT_NOTICE: destructive_action");
|
|
2476
|
+
console.error("GHST_AGENT_NOTICE: Agents must ask the user for approval before continuing.");
|
|
2477
|
+
console.error(`GHST_AGENT_NOTICE: ${JSON.stringify(notice)}`);
|
|
2478
|
+
return confirm(question);
|
|
2479
|
+
}
|
|
2480
|
+
|
|
2481
|
+
// src/commands/auth.ts
|
|
2444
2482
|
var LOGIN_GUIDANCE_BORDER = "------------------------------------------------------------";
|
|
2445
2483
|
var LOGIN_GUIDANCE_TITLE = "Continue In Ghost Admin";
|
|
2446
2484
|
var LOGIN_GUIDANCE_LINE = "Copy the staff access token from your profile, then return here to continue.";
|
|
2447
2485
|
async function prompt(question) {
|
|
2448
|
-
const rl =
|
|
2486
|
+
const rl = readline2.createInterface({
|
|
2449
2487
|
input: process.stdin,
|
|
2450
2488
|
output: process.stdout
|
|
2451
2489
|
});
|
|
@@ -2913,7 +2951,7 @@ function registerAuthCommands(program) {
|
|
|
2913
2951
|
`Active site set to '${formatSwitchSiteOption(resolvedTargetSite, config.sites)}'.`
|
|
2914
2952
|
);
|
|
2915
2953
|
});
|
|
2916
|
-
auth.command("logout").description("Remove credentials for one site or all sites").option("--site <alias>", "Specific site to remove").action(async (options, command) => {
|
|
2954
|
+
auth.command("logout").description("Remove credentials for one site or all sites").option("--site <alias>", "Specific site to remove").option("--yes", "Skip confirmation when removing all configured sites").action(async (options, command) => {
|
|
2917
2955
|
const global = getGlobalOptions(command);
|
|
2918
2956
|
const config = await readUserConfig();
|
|
2919
2957
|
const targetSite = options.site ?? global.site;
|
|
@@ -2937,6 +2975,29 @@ function registerAuthCommands(program) {
|
|
|
2937
2975
|
console.log(`Removed site '${targetSite}'.`);
|
|
2938
2976
|
return;
|
|
2939
2977
|
}
|
|
2978
|
+
if (!options.yes) {
|
|
2979
|
+
if (isNonInteractive()) {
|
|
2980
|
+
throw new GhstError("Removing all sites in non-interactive mode requires --yes.", {
|
|
2981
|
+
exitCode: 2 /* USAGE_ERROR */,
|
|
2982
|
+
code: "USAGE_ERROR"
|
|
2983
|
+
});
|
|
2984
|
+
}
|
|
2985
|
+
const ok = await confirmDestructiveAction(
|
|
2986
|
+
"Remove all configured sites and credentials? [y/N]: ",
|
|
2987
|
+
{
|
|
2988
|
+
action: "logout_all_sites",
|
|
2989
|
+
target: "all_configured_sites",
|
|
2990
|
+
reversible: false,
|
|
2991
|
+
sideEffects: ["remove_credentials", "remove_site_links"]
|
|
2992
|
+
}
|
|
2993
|
+
);
|
|
2994
|
+
if (!ok) {
|
|
2995
|
+
throw new GhstError("Operation cancelled.", {
|
|
2996
|
+
exitCode: 4 /* OPERATION_CANCELLED */,
|
|
2997
|
+
code: "OPERATION_CANCELLED"
|
|
2998
|
+
});
|
|
2999
|
+
}
|
|
3000
|
+
}
|
|
2940
3001
|
for (const site of Object.values(config.sites)) {
|
|
2941
3002
|
if (site.credentialRef) {
|
|
2942
3003
|
await store.delete(site.credentialRef).catch(() => void 0);
|
|
@@ -2947,7 +3008,7 @@ function registerAuthCommands(program) {
|
|
|
2947
3008
|
await writeUserConfig(config);
|
|
2948
3009
|
console.log("Removed all configured sites.");
|
|
2949
3010
|
});
|
|
2950
|
-
auth.command("link").description("Link current project directory to a configured site alias").option("--site <alias>", "Site alias to link").action(async (options, command) => {
|
|
3011
|
+
auth.command("link").description("Link current project directory to a configured site alias").option("--site <alias>", "Site alias to link").option("--yes", "Skip confirmation when replacing an existing project link").action(async (options, command) => {
|
|
2951
3012
|
const global = getGlobalOptions(command);
|
|
2952
3013
|
const config = await readUserConfig();
|
|
2953
3014
|
const siteAlias = options.site ?? global.site ?? config.active;
|
|
@@ -2957,9 +3018,44 @@ function registerAuthCommands(program) {
|
|
|
2957
3018
|
code: "SITE_REQUIRED"
|
|
2958
3019
|
});
|
|
2959
3020
|
}
|
|
2960
|
-
await
|
|
2961
|
-
|
|
2962
|
-
|
|
3021
|
+
const projectConfig = await readProjectConfig();
|
|
3022
|
+
const projectConfigCwd = await resolveProjectConfigCwd();
|
|
3023
|
+
if (projectConfig && projectConfig.site !== siteAlias) {
|
|
3024
|
+
if (!options.yes) {
|
|
3025
|
+
if (isNonInteractive()) {
|
|
3026
|
+
throw new GhstError(
|
|
3027
|
+
"Overwriting an existing project link in non-interactive mode requires --yes.",
|
|
3028
|
+
{
|
|
3029
|
+
exitCode: 2 /* USAGE_ERROR */,
|
|
3030
|
+
code: "USAGE_ERROR"
|
|
3031
|
+
}
|
|
3032
|
+
);
|
|
3033
|
+
}
|
|
3034
|
+
const ok = await confirmDestructiveAction(
|
|
3035
|
+
`Relink current directory from '${projectConfig.site}' to '${siteAlias}'? [y/N]: `,
|
|
3036
|
+
{
|
|
3037
|
+
action: "relink_project",
|
|
3038
|
+
target: `${projectConfig.site}->${siteAlias}`,
|
|
3039
|
+
reversible: true,
|
|
3040
|
+
site: siteAlias,
|
|
3041
|
+
sideEffects: ["update_project_link"]
|
|
3042
|
+
}
|
|
3043
|
+
);
|
|
3044
|
+
if (!ok) {
|
|
3045
|
+
throw new GhstError("Operation cancelled.", {
|
|
3046
|
+
exitCode: 4 /* OPERATION_CANCELLED */,
|
|
3047
|
+
code: "OPERATION_CANCELLED"
|
|
3048
|
+
});
|
|
3049
|
+
}
|
|
3050
|
+
}
|
|
3051
|
+
}
|
|
3052
|
+
await writeProjectConfig(
|
|
3053
|
+
{
|
|
3054
|
+
...projectConfig?.defaults ? { defaults: projectConfig.defaults } : {},
|
|
3055
|
+
site: siteAlias
|
|
3056
|
+
},
|
|
3057
|
+
projectConfigCwd
|
|
3058
|
+
);
|
|
2963
3059
|
console.log(`Linked current directory to '${siteAlias}'.`);
|
|
2964
3060
|
});
|
|
2965
3061
|
auth.command("token").description("Print a short-lived staff JWT for the active connection (sensitive output)").action(async (_, command) => {
|
|
@@ -3099,29 +3195,6 @@ async function setCommentStatus(global, id, status) {
|
|
|
3099
3195
|
return client.comments.setStatus(id, status);
|
|
3100
3196
|
}
|
|
3101
3197
|
|
|
3102
|
-
// src/lib/prompts.ts
|
|
3103
|
-
import readline2 from "readline/promises";
|
|
3104
|
-
async function defaultPrompt(question) {
|
|
3105
|
-
const rl = readline2.createInterface({
|
|
3106
|
-
input: process.stdin,
|
|
3107
|
-
output: process.stdout
|
|
3108
|
-
});
|
|
3109
|
-
try {
|
|
3110
|
-
const value = await rl.question(question);
|
|
3111
|
-
return value.trim();
|
|
3112
|
-
} finally {
|
|
3113
|
-
rl.close();
|
|
3114
|
-
}
|
|
3115
|
-
}
|
|
3116
|
-
var promptHandler = defaultPrompt;
|
|
3117
|
-
async function ask(question) {
|
|
3118
|
-
return promptHandler(question);
|
|
3119
|
-
}
|
|
3120
|
-
async function confirm(question) {
|
|
3121
|
-
const value = (await ask(question)).trim().toLowerCase();
|
|
3122
|
-
return value === "y" || value === "yes";
|
|
3123
|
-
}
|
|
3124
|
-
|
|
3125
3198
|
// src/schemas/comment.ts
|
|
3126
3199
|
import { z as z3 } from "zod";
|
|
3127
3200
|
var CommentListLimitSchema = z3.union([z3.number().int().positive().max(100), z3.literal("all")]);
|
|
@@ -3365,7 +3438,12 @@ function registerCommentCommands(program) {
|
|
|
3365
3438
|
exitCode: 2 /* USAGE_ERROR */
|
|
3366
3439
|
});
|
|
3367
3440
|
}
|
|
3368
|
-
const ok = await
|
|
3441
|
+
const ok = await confirmDestructiveAction(`Delete comment '${parsed.data.id}'? [y/N]: `, {
|
|
3442
|
+
action: "delete_comment",
|
|
3443
|
+
target: parsed.data.id,
|
|
3444
|
+
reversible: false,
|
|
3445
|
+
site: global.site ?? null
|
|
3446
|
+
});
|
|
3369
3447
|
if (!ok) {
|
|
3370
3448
|
throw new GhstError("Operation cancelled.", {
|
|
3371
3449
|
code: "OPERATION_CANCELLED",
|
|
@@ -3991,7 +4069,12 @@ function registerLabelCommands(program) {
|
|
|
3991
4069
|
exitCode: 2 /* USAGE_ERROR */
|
|
3992
4070
|
});
|
|
3993
4071
|
}
|
|
3994
|
-
const ok = await
|
|
4072
|
+
const ok = await confirmDestructiveAction(`Delete label '${parsed.data.id}'? [y/N]: `, {
|
|
4073
|
+
action: "delete_label",
|
|
4074
|
+
target: parsed.data.id,
|
|
4075
|
+
reversible: false,
|
|
4076
|
+
site: global.site ?? null
|
|
4077
|
+
});
|
|
3995
4078
|
if (!ok) {
|
|
3996
4079
|
throw new GhstError("Operation cancelled.", {
|
|
3997
4080
|
code: "OPERATION_CANCELLED",
|
|
@@ -8720,7 +8803,24 @@ function registerMcpCommands(program) {
|
|
|
8720
8803
|
}
|
|
8721
8804
|
|
|
8722
8805
|
// src/commands/member.ts
|
|
8806
|
+
import fs8 from "fs/promises";
|
|
8807
|
+
|
|
8808
|
+
// src/lib/file-guards.ts
|
|
8723
8809
|
import fs7 from "fs/promises";
|
|
8810
|
+
async function assertFileDoesNotExist(filePath) {
|
|
8811
|
+
try {
|
|
8812
|
+
await fs7.access(filePath);
|
|
8813
|
+
} catch (error) {
|
|
8814
|
+
if (error.code === "ENOENT") {
|
|
8815
|
+
return;
|
|
8816
|
+
}
|
|
8817
|
+
throw error;
|
|
8818
|
+
}
|
|
8819
|
+
throw new GhstError(`Refusing to overwrite existing file: ${filePath}`, {
|
|
8820
|
+
code: "USAGE_ERROR",
|
|
8821
|
+
exitCode: 2 /* USAGE_ERROR */
|
|
8822
|
+
});
|
|
8823
|
+
}
|
|
8724
8824
|
|
|
8725
8825
|
// src/schemas/member.ts
|
|
8726
8826
|
import { z as z7 } from "zod";
|
|
@@ -9040,7 +9140,13 @@ function registerMemberCommands(program) {
|
|
|
9040
9140
|
exitCode: 2 /* USAGE_ERROR */
|
|
9041
9141
|
});
|
|
9042
9142
|
}
|
|
9043
|
-
const ok = await
|
|
9143
|
+
const ok = await confirmDestructiveAction(`Delete member '${parsed.data.id}'? [y/N]: `, {
|
|
9144
|
+
action: "delete_member",
|
|
9145
|
+
target: parsed.data.id,
|
|
9146
|
+
reversible: false,
|
|
9147
|
+
site: global.site ?? null,
|
|
9148
|
+
sideEffects: parsed.data.cancel ? ["cancel_subscriptions"] : void 0
|
|
9149
|
+
});
|
|
9044
9150
|
if (!ok) {
|
|
9045
9151
|
throw new GhstError("Operation cancelled.", {
|
|
9046
9152
|
code: "OPERATION_CANCELLED",
|
|
@@ -9094,7 +9200,8 @@ function registerMemberCommands(program) {
|
|
|
9094
9200
|
search: parsed.data.search
|
|
9095
9201
|
});
|
|
9096
9202
|
if (parsed.data.output) {
|
|
9097
|
-
await
|
|
9203
|
+
await assertFileDoesNotExist(parsed.data.output);
|
|
9204
|
+
await fs8.writeFile(parsed.data.output, csv, "utf8");
|
|
9098
9205
|
if (global.json) {
|
|
9099
9206
|
printJson({ ok: true, output: parsed.data.output });
|
|
9100
9207
|
return;
|
|
@@ -9145,7 +9252,7 @@ function registerMemberCommands(program) {
|
|
|
9145
9252
|
}
|
|
9146
9253
|
|
|
9147
9254
|
// src/lib/migrate.ts
|
|
9148
|
-
import
|
|
9255
|
+
import fs9 from "fs/promises";
|
|
9149
9256
|
import os2 from "os";
|
|
9150
9257
|
import path6 from "path";
|
|
9151
9258
|
import { slugify } from "@tryghost/string";
|
|
@@ -9352,7 +9459,7 @@ function parseCsvRows(raw) {
|
|
|
9352
9459
|
}
|
|
9353
9460
|
async function uploadDbImportFile(global, filePath) {
|
|
9354
9461
|
const client = await getClient17(global);
|
|
9355
|
-
const bytes = await
|
|
9462
|
+
const bytes = await fs9.readFile(filePath);
|
|
9356
9463
|
const formData = new FormData();
|
|
9357
9464
|
formData.append("importfile", new Blob([bytes]), path6.basename(filePath));
|
|
9358
9465
|
return client.db.import(formData);
|
|
@@ -9368,9 +9475,9 @@ async function buildGhostDbImportFile(input) {
|
|
|
9368
9475
|
}
|
|
9369
9476
|
const ghostJson = await toGhostJSON(input, {});
|
|
9370
9477
|
const payload = { db: [ghostJson] };
|
|
9371
|
-
const tempDir = await
|
|
9478
|
+
const tempDir = await fs9.mkdtemp(path6.join(os2.tmpdir(), "ghst-import-json-"));
|
|
9372
9479
|
const outputPath = path6.join(tempDir, "import.json");
|
|
9373
|
-
await
|
|
9480
|
+
await fs9.writeFile(outputPath, JSON.stringify(payload), "utf8");
|
|
9374
9481
|
return outputPath;
|
|
9375
9482
|
}
|
|
9376
9483
|
async function migrateImportJson(global, filePath) {
|
|
@@ -9485,7 +9592,7 @@ async function migrateImportSubstack(global, filePath, url) {
|
|
|
9485
9592
|
return uploadDbImportFile(global, importPath);
|
|
9486
9593
|
}
|
|
9487
9594
|
async function migrateImportCsv(global, filePath) {
|
|
9488
|
-
const raw = await
|
|
9595
|
+
const raw = await fs9.readFile(filePath, "utf8");
|
|
9489
9596
|
const rows = parseCsvRows(raw);
|
|
9490
9597
|
const posts = rows.map((row, index) => {
|
|
9491
9598
|
const html = row.html ?? markdownRenderer.render(row.markdown ?? "");
|
|
@@ -9523,7 +9630,8 @@ async function migrateImportCsv(global, filePath) {
|
|
|
9523
9630
|
async function migrateExport(global, outputPath) {
|
|
9524
9631
|
const client = await getClient17(global);
|
|
9525
9632
|
const data = await client.db.export();
|
|
9526
|
-
await
|
|
9633
|
+
await assertFileDoesNotExist(outputPath);
|
|
9634
|
+
await fs9.writeFile(outputPath, data);
|
|
9527
9635
|
return outputPath;
|
|
9528
9636
|
}
|
|
9529
9637
|
|
|
@@ -10133,7 +10241,7 @@ function registerOfferCommands(program) {
|
|
|
10133
10241
|
}
|
|
10134
10242
|
|
|
10135
10243
|
// src/commands/page.ts
|
|
10136
|
-
import
|
|
10244
|
+
import fs10 from "fs/promises";
|
|
10137
10245
|
|
|
10138
10246
|
// src/schemas/page.ts
|
|
10139
10247
|
import { z as z11 } from "zod";
|
|
@@ -10248,7 +10356,7 @@ async function readOptionalFile(filePath) {
|
|
|
10248
10356
|
if (!filePath) {
|
|
10249
10357
|
return void 0;
|
|
10250
10358
|
}
|
|
10251
|
-
return
|
|
10359
|
+
return fs10.readFile(filePath, "utf8");
|
|
10252
10360
|
}
|
|
10253
10361
|
function registerPageCommands(program) {
|
|
10254
10362
|
const page = program.command("page").description("Page management");
|
|
@@ -10415,7 +10523,12 @@ function registerPageCommands(program) {
|
|
|
10415
10523
|
exitCode: 2 /* USAGE_ERROR */
|
|
10416
10524
|
});
|
|
10417
10525
|
}
|
|
10418
|
-
const ok = await
|
|
10526
|
+
const ok = await confirmDestructiveAction(`Delete page '${parsed.data.id}'? [y/N]: `, {
|
|
10527
|
+
action: "delete_page",
|
|
10528
|
+
target: parsed.data.id,
|
|
10529
|
+
reversible: false,
|
|
10530
|
+
site: global.site ?? null
|
|
10531
|
+
});
|
|
10419
10532
|
if (!ok) {
|
|
10420
10533
|
throw new GhstError("Operation cancelled.", {
|
|
10421
10534
|
code: "OPERATION_CANCELLED",
|
|
@@ -10471,7 +10584,7 @@ function registerPageCommands(program) {
|
|
|
10471
10584
|
}
|
|
10472
10585
|
|
|
10473
10586
|
// src/commands/post.ts
|
|
10474
|
-
import
|
|
10587
|
+
import fs11 from "fs/promises";
|
|
10475
10588
|
import MarkdownIt2 from "markdown-it";
|
|
10476
10589
|
|
|
10477
10590
|
// src/schemas/post.ts
|
|
@@ -10685,7 +10798,7 @@ async function readOptionalFile2(filePath) {
|
|
|
10685
10798
|
if (!filePath) {
|
|
10686
10799
|
return void 0;
|
|
10687
10800
|
}
|
|
10688
|
-
return
|
|
10801
|
+
return fs11.readFile(filePath, "utf8");
|
|
10689
10802
|
}
|
|
10690
10803
|
async function readOptionalStdin(enabled) {
|
|
10691
10804
|
if (!enabled) {
|
|
@@ -10723,7 +10836,7 @@ async function readOptionalPostJson(filePath) {
|
|
|
10723
10836
|
if (!filePath) {
|
|
10724
10837
|
return {};
|
|
10725
10838
|
}
|
|
10726
|
-
const payload = JSON.parse(await
|
|
10839
|
+
const payload = JSON.parse(await fs11.readFile(filePath, "utf8"));
|
|
10727
10840
|
return asPostPayload(payload);
|
|
10728
10841
|
}
|
|
10729
10842
|
function assignDefined(target, values) {
|
|
@@ -10979,7 +11092,13 @@ function registerPostCommands(program) {
|
|
|
10979
11092
|
});
|
|
10980
11093
|
}
|
|
10981
11094
|
const label = parsed.data.filter ? `Delete posts matching '${parsed.data.filter}'` : `Delete post '${parsed.data.id}'`;
|
|
10982
|
-
const ok = await
|
|
11095
|
+
const ok = await confirmDestructiveAction(`${label}? [y/N]: `, {
|
|
11096
|
+
action: "delete_post",
|
|
11097
|
+
target: parsed.data.filter ?? parsed.data.id ?? "(unknown)",
|
|
11098
|
+
count: parsed.data.filter ? void 0 : 1,
|
|
11099
|
+
reversible: false,
|
|
11100
|
+
site: global.site ?? null
|
|
11101
|
+
});
|
|
10983
11102
|
if (!ok) {
|
|
10984
11103
|
throw new GhstError("Operation cancelled.", {
|
|
10985
11104
|
code: "OPERATION_CANCELLED",
|
|
@@ -11226,7 +11345,7 @@ function registerSiteCommands(program) {
|
|
|
11226
11345
|
}
|
|
11227
11346
|
|
|
11228
11347
|
// src/commands/socialweb.ts
|
|
11229
|
-
import
|
|
11348
|
+
import fs12 from "fs/promises";
|
|
11230
11349
|
|
|
11231
11350
|
// src/schemas/socialweb.ts
|
|
11232
11351
|
import { z as z14 } from "zod";
|
|
@@ -11292,6 +11411,10 @@ var SocialWebHandleActionInputSchema = z14.object({
|
|
|
11292
11411
|
var SocialWebIdInputSchema = z14.object({
|
|
11293
11412
|
id: UrlSchema2
|
|
11294
11413
|
});
|
|
11414
|
+
var SocialWebDeleteInputSchema = z14.object({
|
|
11415
|
+
id: UrlSchema2,
|
|
11416
|
+
yes: z14.boolean().optional()
|
|
11417
|
+
});
|
|
11295
11418
|
var SocialWebBlockDomainInputSchema = z14.object({
|
|
11296
11419
|
url: UrlSchema2
|
|
11297
11420
|
});
|
|
@@ -11580,10 +11703,9 @@ function registerSocialWebCommands(program) {
|
|
|
11580
11703
|
["like", likePost, "Like a post"],
|
|
11581
11704
|
["unlike", unlikePost, "Unlike a post"],
|
|
11582
11705
|
["repost", repostPost, "Repost a post"],
|
|
11583
|
-
["derepost", derepostPost, "Undo repost on a post"]
|
|
11584
|
-
["delete", deleteSocialWebPost, "Delete a post"]
|
|
11706
|
+
["derepost", derepostPost, "Undo repost on a post"]
|
|
11585
11707
|
]) {
|
|
11586
|
-
socialweb.command(`${entry[0]} <id>`).description(entry[2]).action(async (id,
|
|
11708
|
+
socialweb.command(`${entry[0]} <id>`).description(entry[2]).action(async (id, _options, command) => {
|
|
11587
11709
|
const global = getGlobalOptions(command);
|
|
11588
11710
|
const parsed = SocialWebIdInputSchema.safeParse({ id });
|
|
11589
11711
|
if (!parsed.success) {
|
|
@@ -11594,9 +11716,48 @@ function registerSocialWebCommands(program) {
|
|
|
11594
11716
|
printJson(payload, global.jq);
|
|
11595
11717
|
return;
|
|
11596
11718
|
}
|
|
11597
|
-
console.log(
|
|
11719
|
+
console.log("OK");
|
|
11598
11720
|
});
|
|
11599
11721
|
}
|
|
11722
|
+
socialweb.command("delete <id>").description("Delete a post").option("--yes", "Skip confirmation").action(async (id, options, command) => {
|
|
11723
|
+
const global = getGlobalOptions(command);
|
|
11724
|
+
const parsed = SocialWebDeleteInputSchema.safeParse({
|
|
11725
|
+
id,
|
|
11726
|
+
yes: parseBooleanFlag(options.yes)
|
|
11727
|
+
});
|
|
11728
|
+
if (!parsed.success) {
|
|
11729
|
+
throwValidationError11(parsed.error);
|
|
11730
|
+
}
|
|
11731
|
+
if (!parsed.data.yes) {
|
|
11732
|
+
if (isNonInteractive()) {
|
|
11733
|
+
throw new GhstError("Deleting in non-interactive mode requires --yes.", {
|
|
11734
|
+
code: "USAGE_ERROR",
|
|
11735
|
+
exitCode: 2 /* USAGE_ERROR */
|
|
11736
|
+
});
|
|
11737
|
+
}
|
|
11738
|
+
const ok = await confirmDestructiveAction(
|
|
11739
|
+
`Delete social web post '${parsed.data.id}'? [y/N]: `,
|
|
11740
|
+
{
|
|
11741
|
+
action: "delete_socialweb_post",
|
|
11742
|
+
target: parsed.data.id,
|
|
11743
|
+
reversible: false,
|
|
11744
|
+
site: global.site ?? null
|
|
11745
|
+
}
|
|
11746
|
+
);
|
|
11747
|
+
if (!ok) {
|
|
11748
|
+
throw new GhstError("Operation cancelled.", {
|
|
11749
|
+
code: "OPERATION_CANCELLED",
|
|
11750
|
+
exitCode: 4 /* OPERATION_CANCELLED */
|
|
11751
|
+
});
|
|
11752
|
+
}
|
|
11753
|
+
}
|
|
11754
|
+
const payload = await deleteSocialWebPost(global, parsed.data.id);
|
|
11755
|
+
if (global.json) {
|
|
11756
|
+
printJson(payload, global.jq);
|
|
11757
|
+
return;
|
|
11758
|
+
}
|
|
11759
|
+
console.log("Deleted post");
|
|
11760
|
+
});
|
|
11600
11761
|
socialweb.command("note").description("Create a social web note").option("--content <text>", "Note content").option("--stdin", "Read note content from stdin").option("--image-file <path>", "Attach an image from a local file").option("--image-url <url>", "Attach an image by URL").option("--image-alt <text>", "Image alt text").action(async (options, command) => {
|
|
11601
11762
|
const global = getGlobalOptions(command);
|
|
11602
11763
|
const parsed = SocialWebContentInputSchema.safeParse({
|
|
@@ -11733,7 +11894,7 @@ function registerSocialWebCommands(program) {
|
|
|
11733
11894
|
throwValidationError11(parsed.error);
|
|
11734
11895
|
}
|
|
11735
11896
|
try {
|
|
11736
|
-
await
|
|
11897
|
+
await fs12.access(parsed.data.filePath);
|
|
11737
11898
|
} catch {
|
|
11738
11899
|
throw new GhstError(`File not found: ${parsed.data.filePath}`, {
|
|
11739
11900
|
code: "VALIDATION_ERROR",
|
|
@@ -11750,7 +11911,7 @@ function registerSocialWebCommands(program) {
|
|
|
11750
11911
|
}
|
|
11751
11912
|
|
|
11752
11913
|
// src/commands/stats.ts
|
|
11753
|
-
import
|
|
11914
|
+
import fs13 from "fs/promises";
|
|
11754
11915
|
|
|
11755
11916
|
// src/schemas/stats.ts
|
|
11756
11917
|
import { z as z15 } from "zod";
|
|
@@ -12079,7 +12240,8 @@ function postReferrersCsv(payload) {
|
|
|
12079
12240
|
}
|
|
12080
12241
|
async function emitCsv(csv, output) {
|
|
12081
12242
|
if (output) {
|
|
12082
|
-
await
|
|
12243
|
+
await assertFileDoesNotExist(output);
|
|
12244
|
+
await fs13.writeFile(output, `${csv}
|
|
12083
12245
|
`, "utf8");
|
|
12084
12246
|
return;
|
|
12085
12247
|
}
|
|
@@ -12598,7 +12760,12 @@ function registerTagCommands(program) {
|
|
|
12598
12760
|
exitCode: 2 /* USAGE_ERROR */
|
|
12599
12761
|
});
|
|
12600
12762
|
}
|
|
12601
|
-
const ok = await
|
|
12763
|
+
const ok = await confirmDestructiveAction(`Delete tag '${parsed.data.id}'? [y/N]: `, {
|
|
12764
|
+
action: "delete_tag",
|
|
12765
|
+
target: parsed.data.id,
|
|
12766
|
+
reversible: false,
|
|
12767
|
+
site: global.site ?? null
|
|
12768
|
+
});
|
|
12602
12769
|
if (!ok) {
|
|
12603
12770
|
throw new GhstError("Operation cancelled.", {
|
|
12604
12771
|
code: "OPERATION_CANCELLED",
|
|
@@ -12642,13 +12809,13 @@ function registerTagCommands(program) {
|
|
|
12642
12809
|
|
|
12643
12810
|
// src/commands/theme.ts
|
|
12644
12811
|
import { spawn as spawn4 } from "child_process";
|
|
12645
|
-
import
|
|
12812
|
+
import fs15 from "fs/promises";
|
|
12646
12813
|
import os4 from "os";
|
|
12647
12814
|
import path8 from "path";
|
|
12648
12815
|
|
|
12649
12816
|
// src/lib/theme-dev.ts
|
|
12650
12817
|
import { spawn as spawn3 } from "child_process";
|
|
12651
|
-
import
|
|
12818
|
+
import fs14 from "fs";
|
|
12652
12819
|
import fsPromises from "fs/promises";
|
|
12653
12820
|
import os3 from "os";
|
|
12654
12821
|
import path7 from "path";
|
|
@@ -12705,7 +12872,7 @@ async function runThemeDev(global, options) {
|
|
|
12705
12872
|
if (!options.watch) {
|
|
12706
12873
|
return initial.payload;
|
|
12707
12874
|
}
|
|
12708
|
-
const watcher =
|
|
12875
|
+
const watcher = fs14.watch(options.path, { recursive: true });
|
|
12709
12876
|
let timer = null;
|
|
12710
12877
|
let running = false;
|
|
12711
12878
|
let queued = false;
|
|
@@ -12817,7 +12984,7 @@ function throwValidationError14(error) {
|
|
|
12817
12984
|
);
|
|
12818
12985
|
}
|
|
12819
12986
|
async function zipDirectory2(directoryPath) {
|
|
12820
|
-
const tempDir = await
|
|
12987
|
+
const tempDir = await fs15.mkdtemp(path8.join(os4.tmpdir(), "ghst-theme-"));
|
|
12821
12988
|
const zipPath = path8.join(tempDir, `${path8.basename(directoryPath)}.zip`);
|
|
12822
12989
|
await new Promise((resolve, reject) => {
|
|
12823
12990
|
const child = spawn4("zip", ["-r", zipPath, "."], {
|
|
@@ -12838,7 +13005,7 @@ async function zipDirectory2(directoryPath) {
|
|
|
12838
13005
|
async function validateTheme(targetPath) {
|
|
12839
13006
|
const gscanModule = await import("gscan");
|
|
12840
13007
|
const gscan = gscanModule.default ?? gscanModule;
|
|
12841
|
-
const stat = await
|
|
13008
|
+
const stat = await fs15.stat(targetPath);
|
|
12842
13009
|
if (stat.isDirectory()) {
|
|
12843
13010
|
const result2 = await gscan.check?.(targetPath, {
|
|
12844
13011
|
checkVersion: "v6"
|
|
@@ -12894,7 +13061,7 @@ function registerThemeCommands(program) {
|
|
|
12894
13061
|
if (!parsed.success) {
|
|
12895
13062
|
throwValidationError14(parsed.error);
|
|
12896
13063
|
}
|
|
12897
|
-
const stat = await
|
|
13064
|
+
const stat = await fs15.stat(parsed.data.path);
|
|
12898
13065
|
let uploadPath = parsed.data.path;
|
|
12899
13066
|
if (stat.isDirectory()) {
|
|
12900
13067
|
if (!parsed.data.zip) {
|
|
@@ -13795,7 +13962,12 @@ function registerWebhookCommands(program) {
|
|
|
13795
13962
|
exitCode: 2 /* USAGE_ERROR */
|
|
13796
13963
|
});
|
|
13797
13964
|
}
|
|
13798
|
-
const ok = await
|
|
13965
|
+
const ok = await confirmDestructiveAction(`Delete webhook '${parsed.data.id}'? [y/N]: `, {
|
|
13966
|
+
action: "delete_webhook",
|
|
13967
|
+
target: parsed.data.id,
|
|
13968
|
+
reversible: false,
|
|
13969
|
+
site: global.site ?? null
|
|
13970
|
+
});
|
|
13799
13971
|
if (!ok) {
|
|
13800
13972
|
throw new GhstError("Operation cancelled.", {
|
|
13801
13973
|
code: "OPERATION_CANCELLED",
|