blodemd 0.0.15 → 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/README.md +26 -1
- package/dist/cli.mjs +167 -43
- package/dist/cli.mjs.map +1 -1
- package/package.json +1 -1
- package/packages/@repo/contracts/dist/api-key.d.ts +27 -0
- package/packages/@repo/contracts/dist/api-key.d.ts.map +1 -0
- package/packages/@repo/contracts/dist/api-key.js +20 -0
- package/packages/@repo/contracts/dist/index.d.ts +1 -0
- package/packages/@repo/contracts/dist/index.d.ts.map +1 -1
- package/packages/@repo/contracts/dist/index.js +1 -0
- package/packages/@repo/contracts/src/api-key.ts +27 -0
- package/packages/@repo/contracts/src/index.ts +1 -0
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
- **One-command deploy:** Push your entire docs folder to Blode.md with `blodemd push`.
|
|
10
10
|
- **Scaffold in seconds:** Generate a ready-to-edit docs folder with `blodemd new`.
|
|
11
11
|
- **Config validation:** Catch `docs.json` errors before deploying.
|
|
12
|
-
- **Zero keys:** Sign in once with GitHub in your browser —
|
|
12
|
+
- **Zero keys locally:** Sign in once with GitHub in your browser — no keys for local deploys. CI uses a project deploy key.
|
|
13
13
|
|
|
14
14
|
## Install
|
|
15
15
|
|
|
@@ -41,6 +41,30 @@ blodemd dev
|
|
|
41
41
|
blodemd push
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
+
## Deploy from CI
|
|
45
|
+
|
|
46
|
+
The [GitHub App](#auto-deploy-without-the-cli) is the recommended zero-config path. To run the deploy yourself from GitHub Actions, use a project deploy key stored in the `BLODEMD_API_KEY` secret:
|
|
47
|
+
|
|
48
|
+
```yaml
|
|
49
|
+
name: Deploy docs
|
|
50
|
+
on:
|
|
51
|
+
push:
|
|
52
|
+
branches: [main]
|
|
53
|
+
jobs:
|
|
54
|
+
deploy:
|
|
55
|
+
runs-on: ubuntu-latest
|
|
56
|
+
steps:
|
|
57
|
+
- uses: actions/checkout@v5
|
|
58
|
+
- uses: actions/setup-node@v5
|
|
59
|
+
with:
|
|
60
|
+
node-version: 24
|
|
61
|
+
- run: npx blodemd@latest push --project your-project-slug
|
|
62
|
+
env:
|
|
63
|
+
BLODEMD_API_KEY: ${{ secrets.BLODEMD_API_KEY }}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Create a deploy key in the dashboard under **Settings → Deploy keys**, or copy the `bmd_...` key that `blodemd push` prints the first time it auto-creates a project. Deploy keys are project-scoped and deploy-only — store them as CI secrets, never commit them.
|
|
67
|
+
|
|
44
68
|
## Commands
|
|
45
69
|
|
|
46
70
|
```bash
|
|
@@ -57,6 +81,7 @@ blodemd dev [dir] Start the local docs preview server
|
|
|
57
81
|
|
|
58
82
|
```
|
|
59
83
|
--project <slug> Project slug (env: BLODEMD_PROJECT)
|
|
84
|
+
--api-key <token> API key (env: BLODEMD_API_KEY)
|
|
60
85
|
--api-url <url> API URL (env: BLODEMD_API_URL)
|
|
61
86
|
--branch <name> Git branch (env: BLODEMD_BRANCH)
|
|
62
87
|
--message <msg> Deploy message (env: BLODEMD_COMMIT_MESSAGE)
|
package/dist/cli.mjs
CHANGED
|
@@ -20,6 +20,7 @@ import { watch } from "chokidar";
|
|
|
20
20
|
import { readFileSync } from "node:fs";
|
|
21
21
|
//#region src/constants.ts
|
|
22
22
|
const CLI_NAME = "blodemd";
|
|
23
|
+
const BLODE_API_KEY_ENV = "BLODEMD_API_KEY";
|
|
23
24
|
const BLODE_PROJECT_ENV = "BLODEMD_PROJECT";
|
|
24
25
|
const OAUTH_CLIENT_ID = "6b5f9860-fe96-4a83-b1ad-266260523c91";
|
|
25
26
|
const DEFAULT_OAUTH_CALLBACK_PORT = 8787;
|
|
@@ -55,6 +56,7 @@ const toCliError = (error) => {
|
|
|
55
56
|
if (error instanceof Error) {
|
|
56
57
|
if (error instanceof TypeError && error.message.includes("fetch")) return new CliError("Cannot connect to Blode.md API.", EXIT_CODES.NETWORK, "Check your internet connection and API URL configuration.");
|
|
57
58
|
if (error.name === "TimeoutError" || error.name === "AbortError") return new CliError("Request timed out.", EXIT_CODES.NETWORK, "The API may be unavailable. Try again later.");
|
|
59
|
+
if (/\b401\b/.test(error.message)) return new CliError(error.message, EXIT_CODES.AUTH_REQUIRED, "Check your API key or run \"blodemd login\".");
|
|
58
60
|
return new CliError(error.message, EXIT_CODES.ERROR);
|
|
59
61
|
}
|
|
60
62
|
return new CliError("Unknown error", EXIT_CODES.ERROR);
|
|
@@ -1220,6 +1222,67 @@ const registerAnalyticsCommand = (program) => {
|
|
|
1220
1222
|
});
|
|
1221
1223
|
};
|
|
1222
1224
|
//#endregion
|
|
1225
|
+
//#region src/output.ts
|
|
1226
|
+
const isInteractive = (json = false) => !json && Boolean(process.stdout.isTTY) && !process.env.CI;
|
|
1227
|
+
const writeLine = (message) => {
|
|
1228
|
+
process.stderr.write(`${message}\n`);
|
|
1229
|
+
};
|
|
1230
|
+
const createReporter = ({ json = false } = {}) => {
|
|
1231
|
+
const interactive = isInteractive(json);
|
|
1232
|
+
if (!interactive) return {
|
|
1233
|
+
info: writeLine,
|
|
1234
|
+
interactive,
|
|
1235
|
+
json: (payload) => {
|
|
1236
|
+
process.stdout.write(`${JSON.stringify(payload)}\n`);
|
|
1237
|
+
},
|
|
1238
|
+
step: writeLine,
|
|
1239
|
+
stop: (message) => {
|
|
1240
|
+
if (message) writeLine(message);
|
|
1241
|
+
},
|
|
1242
|
+
success: writeLine,
|
|
1243
|
+
warn: writeLine
|
|
1244
|
+
};
|
|
1245
|
+
const s = spinner();
|
|
1246
|
+
let active = false;
|
|
1247
|
+
return {
|
|
1248
|
+
info(message) {
|
|
1249
|
+
if (active) {
|
|
1250
|
+
s.stop();
|
|
1251
|
+
active = false;
|
|
1252
|
+
}
|
|
1253
|
+
log.info(message);
|
|
1254
|
+
},
|
|
1255
|
+
interactive,
|
|
1256
|
+
json() {},
|
|
1257
|
+
step(message) {
|
|
1258
|
+
if (active) s.message(message);
|
|
1259
|
+
else {
|
|
1260
|
+
s.start(message);
|
|
1261
|
+
active = true;
|
|
1262
|
+
}
|
|
1263
|
+
},
|
|
1264
|
+
stop(message) {
|
|
1265
|
+
if (active) {
|
|
1266
|
+
s.stop(message);
|
|
1267
|
+
active = false;
|
|
1268
|
+
}
|
|
1269
|
+
},
|
|
1270
|
+
success(message) {
|
|
1271
|
+
if (active) {
|
|
1272
|
+
s.stop(message);
|
|
1273
|
+
active = false;
|
|
1274
|
+
} else log.success(message);
|
|
1275
|
+
},
|
|
1276
|
+
warn(message) {
|
|
1277
|
+
if (active) {
|
|
1278
|
+
s.stop();
|
|
1279
|
+
active = false;
|
|
1280
|
+
}
|
|
1281
|
+
log.warn(message);
|
|
1282
|
+
}
|
|
1283
|
+
};
|
|
1284
|
+
};
|
|
1285
|
+
//#endregion
|
|
1223
1286
|
//#region src/scaffold.ts
|
|
1224
1287
|
const SCAFFOLD_TEMPLATES = ["minimal", "starter"];
|
|
1225
1288
|
const DEFAULT_SCAFFOLD_DIRECTORY = "docs";
|
|
@@ -1617,11 +1680,16 @@ const collectFiles = async (root, directory = root) => {
|
|
|
1617
1680
|
}
|
|
1618
1681
|
return files.toSorted((left, right) => left.localeCompare(right));
|
|
1619
1682
|
};
|
|
1620
|
-
const reportCommandError = (prefix, error) => {
|
|
1683
|
+
const reportCommandError = (prefix, error, options) => {
|
|
1621
1684
|
const cliError = toCliError(error);
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1685
|
+
if (isInteractive(options?.json)) {
|
|
1686
|
+
log.error(`${prefix}: ${cliError.message}`);
|
|
1687
|
+
if (cliError.hint) log.info(cliError.hint);
|
|
1688
|
+
log.info("Failed");
|
|
1689
|
+
} else {
|
|
1690
|
+
process.stderr.write(`${prefix}: ${cliError.message}\n`);
|
|
1691
|
+
if (cliError.hint) process.stderr.write(`${cliError.hint}\n`);
|
|
1692
|
+
}
|
|
1625
1693
|
process.exitCode = cliError.exitCode;
|
|
1626
1694
|
};
|
|
1627
1695
|
const parseScaffoldTemplate = (value) => {
|
|
@@ -1827,6 +1895,7 @@ const registerAuthCommands = (program) => {
|
|
|
1827
1895
|
const resolved = await resolveAuthToken();
|
|
1828
1896
|
if (!resolved) {
|
|
1829
1897
|
log.warn("Not logged in. Run \"blodemd login\" to authenticate.");
|
|
1898
|
+
process.exitCode = EXIT_CODES.AUTH_REQUIRED;
|
|
1830
1899
|
return;
|
|
1831
1900
|
}
|
|
1832
1901
|
const status = resolveTokenStatus(resolved);
|
|
@@ -2513,6 +2582,31 @@ const registerNewCommand = (program) => {
|
|
|
2513
2582
|
});
|
|
2514
2583
|
};
|
|
2515
2584
|
//#endregion
|
|
2585
|
+
//#region src/commands/projects.ts
|
|
2586
|
+
const registerProjectsCommand = (program) => {
|
|
2587
|
+
program.command("projects").description("List your projects").option("--api-url <url>", "API URL (env: BLODEMD_API_URL)").option("--json", "output machine-readable JSON (implies non-interactive)").action(async (options) => {
|
|
2588
|
+
const reporter = createReporter({ json: options.json });
|
|
2589
|
+
if (reporter.interactive) intro(chalk.bold("blodemd projects"));
|
|
2590
|
+
try {
|
|
2591
|
+
const resolved = await resolveAuthToken();
|
|
2592
|
+
if (!resolved?.token) throw new CliError("Run \"blodemd login\" to list your projects. API keys cannot list projects.", EXIT_CODES.AUTH_REQUIRED, "Run \"blodemd login\" to authenticate.");
|
|
2593
|
+
const apiUrl = options.apiUrl ?? process.env["BLODEMD_API_URL"] ?? "https://api.blode.md";
|
|
2594
|
+
const projects = await requestJson(new URL("/projects", apiUrl).toString(), { headers: { Authorization: `Bearer ${resolved.token}` } }, "Failed to list projects");
|
|
2595
|
+
reporter.json(projects);
|
|
2596
|
+
if (projects.length === 0) {
|
|
2597
|
+
reporter.info("No projects yet.");
|
|
2598
|
+
return;
|
|
2599
|
+
}
|
|
2600
|
+
for (const project of projects) {
|
|
2601
|
+
const label = project.name ? `${chalk.cyan(project.slug)} — ${project.name}` : chalk.cyan(project.slug);
|
|
2602
|
+
reporter.info(label);
|
|
2603
|
+
}
|
|
2604
|
+
} catch (error) {
|
|
2605
|
+
reportCommandError("Projects failed", error, { json: options.json });
|
|
2606
|
+
}
|
|
2607
|
+
});
|
|
2608
|
+
};
|
|
2609
|
+
//#endregion
|
|
2516
2610
|
//#region src/upload.ts
|
|
2517
2611
|
const TEXT_CONTENT_TYPES = {
|
|
2518
2612
|
".css": "text/css; charset=utf-8",
|
|
@@ -2560,10 +2654,10 @@ const resolveAuthHeaders = async (apiKeyOption) => {
|
|
|
2560
2654
|
const apiKey = (apiKeyOption ?? process.env["BLODEMD_API_KEY"])?.trim();
|
|
2561
2655
|
if (apiKey) return {
|
|
2562
2656
|
canAutoCreate: false,
|
|
2563
|
-
headers: {
|
|
2657
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
2564
2658
|
};
|
|
2565
2659
|
const resolved = await resolveAuthToken();
|
|
2566
|
-
if (!resolved?.token) throw new Error("Not logged in. Run \"blodemd login\" to authenticate, pass --api-key, or set BLODEMD_API_KEY.");
|
|
2660
|
+
if (!resolved?.token) throw new Error("Not logged in. Run \"blodemd login\" to authenticate, pass --api-key, or set BLODEMD_API_KEY (see https://blode.md/docs/deployment/ci).");
|
|
2567
2661
|
return {
|
|
2568
2662
|
canAutoCreate: true,
|
|
2569
2663
|
headers: { Authorization: `Bearer ${resolved.token}` }
|
|
@@ -2604,8 +2698,9 @@ const resolvePushConfig = async (config, options) => {
|
|
|
2604
2698
|
usedLegacyNameFallback
|
|
2605
2699
|
};
|
|
2606
2700
|
};
|
|
2607
|
-
const autoCreateProject = async (project, projectDisplayName, apiUrl, headers, canAutoCreate) => {
|
|
2701
|
+
const autoCreateProject = async (project, projectDisplayName, apiUrl, headers, canAutoCreate, reporter) => {
|
|
2608
2702
|
if (!canAutoCreate) throw new Error(`Project "${project}" not found. Create it at blode.md or login with "blodemd login" to auto-create.`);
|
|
2703
|
+
if (!reporter.interactive) throw new Error(`Project "${project}" not found. Create it in the dashboard or run \`blodemd push\` in an interactive terminal to auto-create it.`);
|
|
2609
2704
|
const shouldCreate = await confirm({ message: `Project "${project}" doesn't exist. Create it?` });
|
|
2610
2705
|
if (isCancel(shouldCreate) || !shouldCreate) return false;
|
|
2611
2706
|
const createResult = await requestJson(new URL("/projects", apiUrl).toString(), {
|
|
@@ -2616,12 +2711,18 @@ const autoCreateProject = async (project, projectDisplayName, apiUrl, headers, c
|
|
|
2616
2711
|
headers,
|
|
2617
2712
|
method: "POST"
|
|
2618
2713
|
}, "Failed to create project");
|
|
2619
|
-
|
|
2714
|
+
reporter.success(`Project ${chalk.cyan(createResult.slug)} created`);
|
|
2715
|
+
const keyResult = await requestJson(new URL(`/projects/${createResult.id}/keys`, apiUrl).toString(), {
|
|
2716
|
+
body: JSON.stringify({ name: "CI deploy key" }),
|
|
2717
|
+
headers,
|
|
2718
|
+
method: "POST"
|
|
2719
|
+
}, "Failed to create deploy key");
|
|
2720
|
+
reporter.info(`Deploy key created (save this — shown once, use as ${BLODE_API_KEY_ENV} in CI): ${chalk.cyan(keyResult.key)}`);
|
|
2620
2721
|
return true;
|
|
2621
2722
|
};
|
|
2622
2723
|
const MAX_BATCH_BYTES = 4 * 1024 * 1024;
|
|
2623
|
-
const uploadFiles = async (files, root, apiPath, deploymentId, headers,
|
|
2624
|
-
|
|
2724
|
+
const uploadFiles = async (files, root, apiPath, deploymentId, headers, reporter) => {
|
|
2725
|
+
reporter.step(`Uploading ${files.length} files`);
|
|
2625
2726
|
let uploaded = 0;
|
|
2626
2727
|
for await (const batch of createUploadBatches({
|
|
2627
2728
|
files,
|
|
@@ -2634,26 +2735,26 @@ const uploadFiles = async (files, root, apiPath, deploymentId, headers, s) => {
|
|
|
2634
2735
|
method: "POST"
|
|
2635
2736
|
}, "Failed to upload files");
|
|
2636
2737
|
uploaded += batch.length;
|
|
2637
|
-
|
|
2738
|
+
reporter.step(`Uploading files (${uploaded}/${files.length})`);
|
|
2638
2739
|
}
|
|
2639
|
-
|
|
2740
|
+
reporter.success(`Uploaded ${chalk.cyan(String(files.length))} files`);
|
|
2640
2741
|
};
|
|
2641
2742
|
const registerPushCommand = (program) => {
|
|
2642
|
-
program.command("push").description("Deploy docs").argument("[dir]", "docs directory").option("--project <slug>", "project slug (env: BLODEMD_PROJECT)").option("--api-key <token>", "API key (env: BLODEMD_API_KEY)").option("--api-url <url>", "API URL (env: BLODEMD_API_URL)").option("--branch <name>", "git branch (env: BLODEMD_BRANCH)").option("--message <msg>", "deploy message (env: BLODEMD_COMMIT_MESSAGE)").action(async (dir, options) => {
|
|
2643
|
-
|
|
2644
|
-
|
|
2743
|
+
program.command("push").description("Deploy docs").argument("[dir]", "docs directory").option("--project <slug>", "project slug (env: BLODEMD_PROJECT)").option("--api-key <token>", "API key (env: BLODEMD_API_KEY)").option("--api-url <url>", "API URL (env: BLODEMD_API_URL)").option("--branch <name>", "git branch (env: BLODEMD_BRANCH)").option("--message <msg>", "deploy message (env: BLODEMD_COMMIT_MESSAGE)").option("--json", "output machine-readable JSON (implies non-interactive)").action(async (dir, options) => {
|
|
2744
|
+
const reporter = createReporter({ json: options.json });
|
|
2745
|
+
if (reporter.interactive) intro(chalk.bold("blodemd push"));
|
|
2645
2746
|
try {
|
|
2646
2747
|
const root = await resolveDocsRoot(dir);
|
|
2647
|
-
|
|
2748
|
+
reporter.step("Validating configuration");
|
|
2648
2749
|
const { config, warnings } = await loadValidatedSiteConfig(root);
|
|
2649
|
-
|
|
2650
|
-
for (const warning of warnings)
|
|
2750
|
+
reporter.success("Configuration valid");
|
|
2751
|
+
for (const warning of warnings) reporter.warn(warning);
|
|
2651
2752
|
const { project, projectDisplayName, apiUrl, authHeaders, canAutoCreate, branch, commitMessage, usedLegacyNameFallback } = await resolvePushConfig(config, options);
|
|
2652
|
-
if (usedLegacyNameFallback)
|
|
2653
|
-
|
|
2753
|
+
if (usedLegacyNameFallback) reporter.warn(LEGACY_PROJECT_NAME_FALLBACK_WARNING);
|
|
2754
|
+
reporter.step("Collecting files");
|
|
2654
2755
|
const files = await collectFiles(root);
|
|
2655
2756
|
if (files.length === 0) throw new Error("No files found to deploy.");
|
|
2656
|
-
|
|
2757
|
+
reporter.success(`Found ${chalk.cyan(String(files.length))} files`);
|
|
2657
2758
|
const headers = {
|
|
2658
2759
|
...authHeaders,
|
|
2659
2760
|
"Content-Type": "application/json"
|
|
@@ -2663,7 +2764,7 @@ const registerPushCommand = (program) => {
|
|
|
2663
2764
|
branch,
|
|
2664
2765
|
commitMessage
|
|
2665
2766
|
});
|
|
2666
|
-
|
|
2767
|
+
reporter.step("Creating deployment");
|
|
2667
2768
|
let deployment;
|
|
2668
2769
|
try {
|
|
2669
2770
|
deployment = await requestJson(apiPath(""), {
|
|
@@ -2673,34 +2774,39 @@ const registerPushCommand = (program) => {
|
|
|
2673
2774
|
}, "Failed to create deployment");
|
|
2674
2775
|
} catch (error) {
|
|
2675
2776
|
if (!(error instanceof Error ? error.message : "").includes("404")) throw error;
|
|
2676
|
-
|
|
2677
|
-
if (!await autoCreateProject(project, projectDisplayName, apiUrl, headers, canAutoCreate)) {
|
|
2678
|
-
|
|
2777
|
+
reporter.stop("Project not found");
|
|
2778
|
+
if (!await autoCreateProject(project, projectDisplayName, apiUrl, headers, canAutoCreate, reporter)) {
|
|
2779
|
+
reporter.info("Cancelled");
|
|
2679
2780
|
return;
|
|
2680
2781
|
}
|
|
2681
|
-
|
|
2782
|
+
reporter.step("Creating deployment");
|
|
2682
2783
|
deployment = await requestJson(apiPath(""), {
|
|
2683
2784
|
body: createDeploymentBody,
|
|
2684
2785
|
headers,
|
|
2685
2786
|
method: "POST"
|
|
2686
2787
|
}, "Failed to create deployment");
|
|
2687
2788
|
}
|
|
2688
|
-
|
|
2689
|
-
await uploadFiles(files, root, apiPath, deployment.id, headers,
|
|
2690
|
-
|
|
2789
|
+
reporter.success(`Deployment ${chalk.cyan(deployment.id)} created`);
|
|
2790
|
+
await uploadFiles(files, root, apiPath, deployment.id, headers, reporter);
|
|
2791
|
+
reporter.step("Finalizing deployment");
|
|
2691
2792
|
const finalized = await requestJson(apiPath(`/${deployment.id}/finalize`), {
|
|
2692
2793
|
body: JSON.stringify({ promote: true }),
|
|
2693
2794
|
headers,
|
|
2694
2795
|
method: "POST"
|
|
2695
2796
|
}, "Failed to finalize deployment");
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
if (finalized.manifestUrl)
|
|
2699
|
-
if (typeof finalized.fileCount === "number")
|
|
2700
|
-
|
|
2797
|
+
reporter.success("Deployment finalized");
|
|
2798
|
+
reporter.success(`Published ${chalk.cyan(finalized.id)}`);
|
|
2799
|
+
if (finalized.manifestUrl) reporter.info(`Manifest: ${finalized.manifestUrl}`);
|
|
2800
|
+
if (typeof finalized.fileCount === "number") reporter.info(`Files: ${finalized.fileCount}`);
|
|
2801
|
+
reporter.info("Done");
|
|
2802
|
+
reporter.json({
|
|
2803
|
+
deploymentId: finalized.id,
|
|
2804
|
+
fileCount: finalized.fileCount ?? files.length,
|
|
2805
|
+
manifestUrl: finalized.manifestUrl ?? null
|
|
2806
|
+
});
|
|
2701
2807
|
} catch (error) {
|
|
2702
|
-
|
|
2703
|
-
reportCommandError("Push failed", error);
|
|
2808
|
+
reporter.stop("Failed");
|
|
2809
|
+
reportCommandError("Push failed", error, { json: options.json });
|
|
2704
2810
|
}
|
|
2705
2811
|
});
|
|
2706
2812
|
};
|
|
@@ -2708,15 +2814,24 @@ const registerPushCommand = (program) => {
|
|
|
2708
2814
|
//#region src/commands/validate.ts
|
|
2709
2815
|
const CONFIG_FILE = "docs.json";
|
|
2710
2816
|
const registerValidateCommand = (program) => {
|
|
2711
|
-
program.command("validate").description("Validate docs.json").argument("[dir]", "docs directory").action(async (dir) => {
|
|
2712
|
-
|
|
2817
|
+
program.command("validate").description("Validate docs.json").argument("[dir]", "docs directory").option("--json", "output machine-readable JSON (implies non-interactive)").action(async (dir, options) => {
|
|
2818
|
+
const reporter = createReporter({ json: options.json });
|
|
2819
|
+
if (reporter.interactive) intro(chalk.bold("blodemd validate"));
|
|
2713
2820
|
try {
|
|
2714
2821
|
const { warnings } = await loadValidatedSiteConfig(await resolveDocsRoot(dir));
|
|
2715
|
-
for (const warning of warnings)
|
|
2716
|
-
|
|
2717
|
-
|
|
2822
|
+
for (const warning of warnings) reporter.warn(warning);
|
|
2823
|
+
reporter.success(`${chalk.cyan(CONFIG_FILE)} is valid.`);
|
|
2824
|
+
reporter.info("Done");
|
|
2825
|
+
reporter.json({
|
|
2826
|
+
valid: true,
|
|
2827
|
+
warnings
|
|
2828
|
+
});
|
|
2718
2829
|
} catch (error) {
|
|
2719
|
-
|
|
2830
|
+
if (options.json && error instanceof CliError && error.exitCode === EXIT_CODES.VALIDATION) reporter.json({
|
|
2831
|
+
errors: error.message.split("\n"),
|
|
2832
|
+
valid: false
|
|
2833
|
+
});
|
|
2834
|
+
reportCommandError("Validation failed", error, { json: options.json });
|
|
2720
2835
|
}
|
|
2721
2836
|
});
|
|
2722
2837
|
};
|
|
@@ -2770,9 +2885,18 @@ registerAuthCommands(program);
|
|
|
2770
2885
|
registerNewCommand(program);
|
|
2771
2886
|
registerValidateCommand(program);
|
|
2772
2887
|
registerPushCommand(program);
|
|
2888
|
+
registerProjectsCommand(program);
|
|
2773
2889
|
registerDevCommand(program);
|
|
2774
2890
|
registerAnalyticsCommand(program);
|
|
2775
|
-
program.
|
|
2891
|
+
program.addHelpText("after", "\nExample:\n $ blodemd push ./docs --project my-docs\n");
|
|
2892
|
+
try {
|
|
2893
|
+
await program.parseAsync();
|
|
2894
|
+
} catch (error) {
|
|
2895
|
+
const cliError = toCliError(error);
|
|
2896
|
+
console.error(cliError.message);
|
|
2897
|
+
if (cliError.hint) console.error(cliError.hint);
|
|
2898
|
+
process.exitCode = cliError.exitCode;
|
|
2899
|
+
}
|
|
2776
2900
|
//#endregion
|
|
2777
2901
|
export {};
|
|
2778
2902
|
|