blodemd 0.0.14 → 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 +184 -47
- 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",
|
|
@@ -2556,6 +2650,19 @@ const createUploadBatches = async function* createUploadBatchesGenerator({ files
|
|
|
2556
2650
|
};
|
|
2557
2651
|
//#endregion
|
|
2558
2652
|
//#region src/commands/push.ts
|
|
2653
|
+
const resolveAuthHeaders = async (apiKeyOption) => {
|
|
2654
|
+
const apiKey = (apiKeyOption ?? process.env["BLODEMD_API_KEY"])?.trim();
|
|
2655
|
+
if (apiKey) return {
|
|
2656
|
+
canAutoCreate: false,
|
|
2657
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
2658
|
+
};
|
|
2659
|
+
const resolved = await resolveAuthToken();
|
|
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).");
|
|
2661
|
+
return {
|
|
2662
|
+
canAutoCreate: true,
|
|
2663
|
+
headers: { Authorization: `Bearer ${resolved.token}` }
|
|
2664
|
+
};
|
|
2665
|
+
};
|
|
2559
2666
|
const resolvePushConfig = async (config, options) => {
|
|
2560
2667
|
const { project, usedLegacyNameFallback } = resolveProjectTarget({
|
|
2561
2668
|
cliProject: options.project,
|
|
@@ -2563,7 +2670,6 @@ const resolvePushConfig = async (config, options) => {
|
|
|
2563
2670
|
envProject: process.env[BLODE_PROJECT_ENV]
|
|
2564
2671
|
});
|
|
2565
2672
|
const apiUrl = options.apiUrl ?? process.env["BLODEMD_API_URL"] ?? "https://api.blode.md";
|
|
2566
|
-
const authToken = (await resolveAuthToken())?.token;
|
|
2567
2673
|
const branch = options.branch ?? process.env["BLODEMD_BRANCH"] ?? process.env.GITHUB_REF_NAME ?? readGitValue([
|
|
2568
2674
|
"rev-parse",
|
|
2569
2675
|
"--abbrev-ref",
|
|
@@ -2580,19 +2686,21 @@ const resolvePushConfig = async (config, options) => {
|
|
|
2580
2686
|
if (usedLegacyNameFallback) throw new Error(`docs.json.name is not a valid deployment slug. Add "slug" to docs.json, pass --project, or set BLODEMD_PROJECT. ${projectSlugError}`);
|
|
2581
2687
|
throw new Error(`Invalid project slug "${project}". ${projectSlugError}`);
|
|
2582
2688
|
}
|
|
2583
|
-
|
|
2689
|
+
const { headers: authHeaders, canAutoCreate } = await resolveAuthHeaders(options.apiKey);
|
|
2584
2690
|
return {
|
|
2585
2691
|
apiUrl,
|
|
2586
|
-
|
|
2692
|
+
authHeaders,
|
|
2587
2693
|
branch,
|
|
2694
|
+
canAutoCreate,
|
|
2588
2695
|
commitMessage,
|
|
2589
2696
|
project,
|
|
2590
2697
|
projectDisplayName: config.name?.trim() || project,
|
|
2591
2698
|
usedLegacyNameFallback
|
|
2592
2699
|
};
|
|
2593
2700
|
};
|
|
2594
|
-
const autoCreateProject = async (project, projectDisplayName, apiUrl, headers) => {
|
|
2595
|
-
if (!
|
|
2701
|
+
const autoCreateProject = async (project, projectDisplayName, apiUrl, headers, canAutoCreate, reporter) => {
|
|
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.`);
|
|
2596
2704
|
const shouldCreate = await confirm({ message: `Project "${project}" doesn't exist. Create it?` });
|
|
2597
2705
|
if (isCancel(shouldCreate) || !shouldCreate) return false;
|
|
2598
2706
|
const createResult = await requestJson(new URL("/projects", apiUrl).toString(), {
|
|
@@ -2603,12 +2711,18 @@ const autoCreateProject = async (project, projectDisplayName, apiUrl, headers) =
|
|
|
2603
2711
|
headers,
|
|
2604
2712
|
method: "POST"
|
|
2605
2713
|
}, "Failed to create project");
|
|
2606
|
-
|
|
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)}`);
|
|
2607
2721
|
return true;
|
|
2608
2722
|
};
|
|
2609
2723
|
const MAX_BATCH_BYTES = 4 * 1024 * 1024;
|
|
2610
|
-
const uploadFiles = async (files, root, apiPath, deploymentId, headers,
|
|
2611
|
-
|
|
2724
|
+
const uploadFiles = async (files, root, apiPath, deploymentId, headers, reporter) => {
|
|
2725
|
+
reporter.step(`Uploading ${files.length} files`);
|
|
2612
2726
|
let uploaded = 0;
|
|
2613
2727
|
for await (const batch of createUploadBatches({
|
|
2614
2728
|
files,
|
|
@@ -2621,28 +2735,28 @@ const uploadFiles = async (files, root, apiPath, deploymentId, headers, s) => {
|
|
|
2621
2735
|
method: "POST"
|
|
2622
2736
|
}, "Failed to upload files");
|
|
2623
2737
|
uploaded += batch.length;
|
|
2624
|
-
|
|
2738
|
+
reporter.step(`Uploading files (${uploaded}/${files.length})`);
|
|
2625
2739
|
}
|
|
2626
|
-
|
|
2740
|
+
reporter.success(`Uploaded ${chalk.cyan(String(files.length))} files`);
|
|
2627
2741
|
};
|
|
2628
2742
|
const registerPushCommand = (program) => {
|
|
2629
|
-
program.command("push").description("Deploy docs").argument("[dir]", "docs directory").option("--project <slug>", "project slug (env: BLODEMD_PROJECT)").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) => {
|
|
2630
|
-
|
|
2631
|
-
|
|
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"));
|
|
2632
2746
|
try {
|
|
2633
2747
|
const root = await resolveDocsRoot(dir);
|
|
2634
|
-
|
|
2748
|
+
reporter.step("Validating configuration");
|
|
2635
2749
|
const { config, warnings } = await loadValidatedSiteConfig(root);
|
|
2636
|
-
|
|
2637
|
-
for (const warning of warnings)
|
|
2638
|
-
const { project, projectDisplayName, apiUrl,
|
|
2639
|
-
if (usedLegacyNameFallback)
|
|
2640
|
-
|
|
2750
|
+
reporter.success("Configuration valid");
|
|
2751
|
+
for (const warning of warnings) reporter.warn(warning);
|
|
2752
|
+
const { project, projectDisplayName, apiUrl, authHeaders, canAutoCreate, branch, commitMessage, usedLegacyNameFallback } = await resolvePushConfig(config, options);
|
|
2753
|
+
if (usedLegacyNameFallback) reporter.warn(LEGACY_PROJECT_NAME_FALLBACK_WARNING);
|
|
2754
|
+
reporter.step("Collecting files");
|
|
2641
2755
|
const files = await collectFiles(root);
|
|
2642
2756
|
if (files.length === 0) throw new Error("No files found to deploy.");
|
|
2643
|
-
|
|
2757
|
+
reporter.success(`Found ${chalk.cyan(String(files.length))} files`);
|
|
2644
2758
|
const headers = {
|
|
2645
|
-
|
|
2759
|
+
...authHeaders,
|
|
2646
2760
|
"Content-Type": "application/json"
|
|
2647
2761
|
};
|
|
2648
2762
|
const apiPath = (suffix) => new URL(`/projects/slug/${project}/deployments${suffix}`, apiUrl).toString();
|
|
@@ -2650,7 +2764,7 @@ const registerPushCommand = (program) => {
|
|
|
2650
2764
|
branch,
|
|
2651
2765
|
commitMessage
|
|
2652
2766
|
});
|
|
2653
|
-
|
|
2767
|
+
reporter.step("Creating deployment");
|
|
2654
2768
|
let deployment;
|
|
2655
2769
|
try {
|
|
2656
2770
|
deployment = await requestJson(apiPath(""), {
|
|
@@ -2660,34 +2774,39 @@ const registerPushCommand = (program) => {
|
|
|
2660
2774
|
}, "Failed to create deployment");
|
|
2661
2775
|
} catch (error) {
|
|
2662
2776
|
if (!(error instanceof Error ? error.message : "").includes("404")) throw error;
|
|
2663
|
-
|
|
2664
|
-
if (!await autoCreateProject(project, projectDisplayName, apiUrl, headers)) {
|
|
2665
|
-
|
|
2777
|
+
reporter.stop("Project not found");
|
|
2778
|
+
if (!await autoCreateProject(project, projectDisplayName, apiUrl, headers, canAutoCreate, reporter)) {
|
|
2779
|
+
reporter.info("Cancelled");
|
|
2666
2780
|
return;
|
|
2667
2781
|
}
|
|
2668
|
-
|
|
2782
|
+
reporter.step("Creating deployment");
|
|
2669
2783
|
deployment = await requestJson(apiPath(""), {
|
|
2670
2784
|
body: createDeploymentBody,
|
|
2671
2785
|
headers,
|
|
2672
2786
|
method: "POST"
|
|
2673
2787
|
}, "Failed to create deployment");
|
|
2674
2788
|
}
|
|
2675
|
-
|
|
2676
|
-
await uploadFiles(files, root, apiPath, deployment.id, headers,
|
|
2677
|
-
|
|
2789
|
+
reporter.success(`Deployment ${chalk.cyan(deployment.id)} created`);
|
|
2790
|
+
await uploadFiles(files, root, apiPath, deployment.id, headers, reporter);
|
|
2791
|
+
reporter.step("Finalizing deployment");
|
|
2678
2792
|
const finalized = await requestJson(apiPath(`/${deployment.id}/finalize`), {
|
|
2679
2793
|
body: JSON.stringify({ promote: true }),
|
|
2680
2794
|
headers,
|
|
2681
2795
|
method: "POST"
|
|
2682
2796
|
}, "Failed to finalize deployment");
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
if (finalized.manifestUrl)
|
|
2686
|
-
if (typeof finalized.fileCount === "number")
|
|
2687
|
-
|
|
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
|
+
});
|
|
2688
2807
|
} catch (error) {
|
|
2689
|
-
|
|
2690
|
-
reportCommandError("Push failed", error);
|
|
2808
|
+
reporter.stop("Failed");
|
|
2809
|
+
reportCommandError("Push failed", error, { json: options.json });
|
|
2691
2810
|
}
|
|
2692
2811
|
});
|
|
2693
2812
|
};
|
|
@@ -2695,15 +2814,24 @@ const registerPushCommand = (program) => {
|
|
|
2695
2814
|
//#region src/commands/validate.ts
|
|
2696
2815
|
const CONFIG_FILE = "docs.json";
|
|
2697
2816
|
const registerValidateCommand = (program) => {
|
|
2698
|
-
program.command("validate").description("Validate docs.json").argument("[dir]", "docs directory").action(async (dir) => {
|
|
2699
|
-
|
|
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"));
|
|
2700
2820
|
try {
|
|
2701
2821
|
const { warnings } = await loadValidatedSiteConfig(await resolveDocsRoot(dir));
|
|
2702
|
-
for (const warning of warnings)
|
|
2703
|
-
|
|
2704
|
-
|
|
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
|
+
});
|
|
2705
2829
|
} catch (error) {
|
|
2706
|
-
|
|
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 });
|
|
2707
2835
|
}
|
|
2708
2836
|
});
|
|
2709
2837
|
};
|
|
@@ -2757,9 +2885,18 @@ registerAuthCommands(program);
|
|
|
2757
2885
|
registerNewCommand(program);
|
|
2758
2886
|
registerValidateCommand(program);
|
|
2759
2887
|
registerPushCommand(program);
|
|
2888
|
+
registerProjectsCommand(program);
|
|
2760
2889
|
registerDevCommand(program);
|
|
2761
2890
|
registerAnalyticsCommand(program);
|
|
2762
|
-
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
|
+
}
|
|
2763
2900
|
//#endregion
|
|
2764
2901
|
export {};
|
|
2765
2902
|
|