blodemd 0.0.15 → 0.2.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 +217 -217
- package/dist/cli.mjs.map +1 -1
- package/package.json +3 -36
- package/dev-server/app/[[...slug]]/page.tsx +0 -140
- package/dev-server/app/blodemd-dev/invalidate/route.ts +0 -12
- package/dev-server/app/blodemd-dev/static/[...path]/route.ts +0 -32
- package/dev-server/app/blodemd-dev/version/route.ts +0 -14
- package/dev-server/app/blodemd-internal/proxy/route.ts +0 -86
- package/dev-server/app/error.tsx +0 -24
- package/dev-server/app/favicon.ico +0 -0
- package/dev-server/app/globals.css +0 -4
- package/dev-server/app/layout.tsx +0 -38
- package/dev-server/app/not-found.tsx +0 -18
- package/dev-server/app/search/route.ts +0 -17
- package/dev-server/components/dev-reload-script.tsx +0 -86
- package/dev-server/components/providers.tsx +0 -15
- package/dev-server/lib/dev-state.ts +0 -8
- package/dev-server/lib/local-content-source.ts +0 -103
- package/dev-server/lib/local-runtime.tsx +0 -555
- package/dev-server/next-env.d.ts +0 -5
- package/dev-server/next.config.js +0 -54
- package/dev-server/package.json +0 -61
- package/dev-server/postcss.config.mjs +0 -7
- package/dev-server/public/glide-variable.woff2 +0 -0
- package/dev-server/tsconfig.json +0 -50
- package/docs/app/globals.css +0 -471
- package/docs/components/animate-ui/primitives/buttons/button.tsx +0 -14
- package/docs/components/api/api-playground.tsx +0 -467
- package/docs/components/api/api-reference.tsx +0 -157
- package/docs/components/content/collection-index.tsx +0 -114
- package/docs/components/docs/contextual-menu.tsx +0 -495
- package/docs/components/docs/copy-page-menu.tsx +0 -364
- package/docs/components/docs/doc-header.tsx +0 -223
- package/docs/components/docs/doc-shell.tsx +0 -355
- package/docs/components/docs/doc-sidebar.tsx +0 -256
- package/docs/components/docs/doc-toc.tsx +0 -46
- package/docs/components/docs/mobile-nav.tsx +0 -195
- package/docs/components/docs/sidebar-active-highlight.tsx +0 -34
- package/docs/components/docs/sidebar-scroll-area.tsx +0 -63
- package/docs/components/git/repo-picker.tsx +0 -529
- package/docs/components/icons/doc-icon.tsx +0 -96
- package/docs/components/mdx/accordion.tsx +0 -83
- package/docs/components/mdx/agent-instructions.tsx +0 -17
- package/docs/components/mdx/badge.tsx +0 -79
- package/docs/components/mdx/callout.tsx +0 -88
- package/docs/components/mdx/card.tsx +0 -110
- package/docs/components/mdx/code-block.tsx +0 -80
- package/docs/components/mdx/code-group.tsx +0 -203
- package/docs/components/mdx/color.tsx +0 -87
- package/docs/components/mdx/columns.tsx +0 -25
- package/docs/components/mdx/expandable.tsx +0 -45
- package/docs/components/mdx/field-layout.tsx +0 -77
- package/docs/components/mdx/frame.tsx +0 -23
- package/docs/components/mdx/get-text-content.ts +0 -18
- package/docs/components/mdx/icon.tsx +0 -12
- package/docs/components/mdx/iframe.tsx +0 -62
- package/docs/components/mdx/index.tsx +0 -121
- package/docs/components/mdx/installer.tsx +0 -20
- package/docs/components/mdx/panel.tsx +0 -11
- package/docs/components/mdx/param-field.tsx +0 -56
- package/docs/components/mdx/preview.tsx +0 -36
- package/docs/components/mdx/prompt.tsx +0 -63
- package/docs/components/mdx/request-example.tsx +0 -27
- package/docs/components/mdx/response-field.tsx +0 -42
- package/docs/components/mdx/steps.tsx +0 -92
- package/docs/components/mdx/tabs.tsx +0 -193
- package/docs/components/mdx/tile.tsx +0 -43
- package/docs/components/mdx/tooltip.tsx +0 -71
- package/docs/components/mdx/tree.tsx +0 -120
- package/docs/components/mdx/type-table.tsx +0 -71
- package/docs/components/mdx/update.tsx +0 -44
- package/docs/components/mdx/video.tsx +0 -45
- package/docs/components/mdx/view.tsx +0 -66
- package/docs/components/posthog-provider.tsx +0 -42
- package/docs/components/providers.tsx +0 -15
- package/docs/components/tenant-analytics.tsx +0 -49
- package/docs/components/third-parties.tsx +0 -32
- package/docs/components/ui/badge.tsx +0 -61
- package/docs/components/ui/breadcrumb.tsx +0 -112
- package/docs/components/ui/button-group.tsx +0 -83
- package/docs/components/ui/button.tsx +0 -65
- package/docs/components/ui/card.tsx +0 -92
- package/docs/components/ui/command.tsx +0 -167
- package/docs/components/ui/copy-button.tsx +0 -129
- package/docs/components/ui/dialog.tsx +0 -146
- package/docs/components/ui/field.tsx +0 -237
- package/docs/components/ui/input.tsx +0 -23
- package/docs/components/ui/kbd.tsx +0 -98
- package/docs/components/ui/label.tsx +0 -22
- package/docs/components/ui/morph-icon.tsx +0 -79
- package/docs/components/ui/popover.tsx +0 -267
- package/docs/components/ui/search-dialog.tsx +0 -431
- package/docs/components/ui/search.tsx +0 -101
- package/docs/components/ui/separator.tsx +0 -26
- package/docs/components/ui/sheet.tsx +0 -133
- package/docs/components/ui/sidebar.tsx +0 -758
- package/docs/components/ui/skeleton.tsx +0 -11
- package/docs/components/ui/switch.tsx +0 -32
- package/docs/components/ui/tabs.tsx +0 -138
- package/docs/components/ui/theme-toggle.tsx +0 -62
- package/docs/components/ui/tooltip.tsx +0 -53
- package/docs/components/web-mcp.tsx +0 -115
- package/docs/lib/agent-skills.ts +0 -48
- package/docs/lib/api-client.ts +0 -72
- package/docs/lib/config.ts +0 -6
- package/docs/lib/content-root.ts +0 -33
- package/docs/lib/content-source.ts +0 -70
- package/docs/lib/contextual-options.ts +0 -222
- package/docs/lib/dashboard-session.ts +0 -167
- package/docs/lib/db.ts +0 -13
- package/docs/lib/docs-collection.ts +0 -7
- package/docs/lib/docs-runtime.tsx +0 -745
- package/docs/lib/edge-config.ts +0 -95
- package/docs/lib/env.ts +0 -23
- package/docs/lib/etag.ts +0 -22
- package/docs/lib/github-install.ts +0 -33
- package/docs/lib/llms-full.ts +0 -82
- package/docs/lib/marketing-markdown.ts +0 -79
- package/docs/lib/marketing-site.ts +0 -31
- package/docs/lib/mdx.ts +0 -98
- package/docs/lib/navigation.ts +0 -288
- package/docs/lib/openapi-proxy.ts +0 -88
- package/docs/lib/openapi.ts +0 -158
- package/docs/lib/platform-config.ts +0 -6
- package/docs/lib/project-authz.ts +0 -46
- package/docs/lib/routes.ts +0 -103
- package/docs/lib/server-cache.ts +0 -83
- package/docs/lib/shiki.ts +0 -44
- package/docs/lib/supabase.ts +0 -37
- package/docs/lib/tenancy.ts +0 -364
- package/docs/lib/tenant-headers.ts +0 -43
- package/docs/lib/tenant-static.ts +0 -705
- package/docs/lib/tenant-utility-context.ts +0 -62
- package/docs/lib/tenants.ts +0 -72
- package/docs/lib/theme.ts +0 -29
- package/docs/lib/time-ago.ts +0 -24
- package/docs/lib/toc.ts +0 -2
- package/docs/lib/use-mobile.ts +0 -19
- package/docs/lib/use-tab-observer.ts +0 -71
- package/docs/lib/utils.ts +0 -5
- package/packages/@repo/common/dist/index.d.ts +0 -16
- package/packages/@repo/common/dist/index.d.ts.map +0 -1
- package/packages/@repo/common/dist/index.js +0 -84
- package/packages/@repo/common/package.json +0 -34
- package/packages/@repo/common/src/index.ts +0 -101
- package/packages/@repo/contracts/dist/analytics.d.ts +0 -21
- package/packages/@repo/contracts/dist/analytics.d.ts.map +0 -1
- package/packages/@repo/contracts/dist/analytics.js +0 -18
- package/packages/@repo/contracts/dist/dates.d.ts +0 -4
- package/packages/@repo/contracts/dist/dates.d.ts.map +0 -1
- package/packages/@repo/contracts/dist/dates.js +0 -2
- package/packages/@repo/contracts/dist/deployment.d.ts +0 -71
- package/packages/@repo/contracts/dist/deployment.d.ts.map +0 -1
- package/packages/@repo/contracts/dist/deployment.js +0 -46
- package/packages/@repo/contracts/dist/domain.d.ts +0 -94
- package/packages/@repo/contracts/dist/domain.d.ts.map +0 -1
- package/packages/@repo/contracts/dist/domain.js +0 -36
- package/packages/@repo/contracts/dist/git.d.ts +0 -28
- package/packages/@repo/contracts/dist/git.d.ts.map +0 -1
- package/packages/@repo/contracts/dist/git.js +0 -24
- package/packages/@repo/contracts/dist/ids.d.ts +0 -14
- package/packages/@repo/contracts/dist/ids.d.ts.map +0 -1
- package/packages/@repo/contracts/dist/ids.js +0 -11
- package/packages/@repo/contracts/dist/index.d.ts +0 -11
- package/packages/@repo/contracts/dist/index.d.ts.map +0 -1
- package/packages/@repo/contracts/dist/index.js +0 -12
- package/packages/@repo/contracts/dist/pagination.d.ts +0 -23
- package/packages/@repo/contracts/dist/pagination.d.ts.map +0 -1
- package/packages/@repo/contracts/dist/pagination.js +0 -15
- package/packages/@repo/contracts/dist/project.d.ts +0 -42
- package/packages/@repo/contracts/dist/project.d.ts.map +0 -1
- package/packages/@repo/contracts/dist/project.js +0 -25
- package/packages/@repo/contracts/dist/tenant.d.ts +0 -147
- package/packages/@repo/contracts/dist/tenant.d.ts.map +0 -1
- package/packages/@repo/contracts/dist/tenant.js +0 -58
- package/packages/@repo/contracts/dist/user.d.ts +0 -9
- package/packages/@repo/contracts/dist/user.d.ts.map +0 -1
- package/packages/@repo/contracts/dist/user.js +0 -9
- package/packages/@repo/contracts/package.json +0 -37
- package/packages/@repo/contracts/src/analytics.ts +0 -33
- package/packages/@repo/contracts/src/dates.ts +0 -4
- package/packages/@repo/contracts/src/deployment.ts +0 -73
- package/packages/@repo/contracts/src/domain.ts +0 -51
- package/packages/@repo/contracts/src/git.ts +0 -31
- package/packages/@repo/contracts/src/ids.ts +0 -23
- package/packages/@repo/contracts/src/index.ts +0 -12
- package/packages/@repo/contracts/src/pagination.ts +0 -21
- package/packages/@repo/contracts/src/project.ts +0 -32
- package/packages/@repo/contracts/src/tenant.ts +0 -94
- package/packages/@repo/contracts/src/user.ts +0 -12
- package/packages/@repo/models/dist/docs-config.d.ts +0 -961
- package/packages/@repo/models/dist/docs-config.d.ts.map +0 -1
- package/packages/@repo/models/dist/docs-config.js +0 -531
- package/packages/@repo/models/dist/index.d.ts +0 -3
- package/packages/@repo/models/dist/index.d.ts.map +0 -1
- package/packages/@repo/models/dist/index.js +0 -3
- package/packages/@repo/models/dist/tenant.d.ts +0 -37
- package/packages/@repo/models/dist/tenant.d.ts.map +0 -1
- package/packages/@repo/models/dist/tenant.js +0 -1
- package/packages/@repo/models/package.json +0 -37
- package/packages/@repo/models/src/docs-config.ts +0 -629
- package/packages/@repo/models/src/index.ts +0 -3
- package/packages/@repo/models/src/tenant.ts +0 -44
- package/packages/@repo/prebuild/dist/index.d.ts +0 -2
- package/packages/@repo/prebuild/dist/index.d.ts.map +0 -1
- package/packages/@repo/prebuild/dist/index.js +0 -2
- package/packages/@repo/prebuild/dist/openapi.d.ts +0 -43
- package/packages/@repo/prebuild/dist/openapi.d.ts.map +0 -1
- package/packages/@repo/prebuild/dist/openapi.js +0 -86
- package/packages/@repo/prebuild/package.json +0 -39
- package/packages/@repo/prebuild/src/index.ts +0 -2
- package/packages/@repo/prebuild/src/openapi.ts +0 -154
- package/packages/@repo/previewing/dist/blob-source.d.ts +0 -16
- package/packages/@repo/previewing/dist/blob-source.d.ts.map +0 -1
- package/packages/@repo/previewing/dist/blob-source.js +0 -128
- package/packages/@repo/previewing/dist/config-loader.d.ts +0 -134
- package/packages/@repo/previewing/dist/config-loader.d.ts.map +0 -1
- package/packages/@repo/previewing/dist/config-loader.js +0 -231
- package/packages/@repo/previewing/dist/constants.d.ts +0 -15
- package/packages/@repo/previewing/dist/constants.d.ts.map +0 -1
- package/packages/@repo/previewing/dist/constants.js +0 -14
- package/packages/@repo/previewing/dist/content-index.d.ts +0 -7
- package/packages/@repo/previewing/dist/content-index.d.ts.map +0 -1
- package/packages/@repo/previewing/dist/content-index.js +0 -291
- package/packages/@repo/previewing/dist/content-source.d.ts +0 -12
- package/packages/@repo/previewing/dist/content-source.d.ts.map +0 -1
- package/packages/@repo/previewing/dist/content-source.js +0 -1
- package/packages/@repo/previewing/dist/fs-source.d.ts +0 -11
- package/packages/@repo/previewing/dist/fs-source.d.ts.map +0 -1
- package/packages/@repo/previewing/dist/fs-source.js +0 -71
- package/packages/@repo/previewing/dist/index.d.ts +0 -16
- package/packages/@repo/previewing/dist/index.d.ts.map +0 -1
- package/packages/@repo/previewing/dist/index.js +0 -13
- package/packages/@repo/previewing/dist/markdown/agent-markdown.d.ts +0 -3
- package/packages/@repo/previewing/dist/markdown/agent-markdown.d.ts.map +0 -1
- package/packages/@repo/previewing/dist/markdown/agent-markdown.js +0 -176
- package/packages/@repo/previewing/dist/markdown/format.d.ts +0 -8
- package/packages/@repo/previewing/dist/markdown/format.d.ts.map +0 -1
- package/packages/@repo/previewing/dist/markdown/format.js +0 -43
- package/packages/@repo/previewing/dist/markdown/links.d.ts +0 -3
- package/packages/@repo/previewing/dist/markdown/links.d.ts.map +0 -1
- package/packages/@repo/previewing/dist/markdown/links.js +0 -31
- package/packages/@repo/previewing/dist/openapi-pages.d.ts +0 -7
- package/packages/@repo/previewing/dist/openapi-pages.d.ts.map +0 -1
- package/packages/@repo/previewing/dist/openapi-pages.js +0 -200
- package/packages/@repo/previewing/dist/search-index.d.ts +0 -4
- package/packages/@repo/previewing/dist/search-index.d.ts.map +0 -1
- package/packages/@repo/previewing/dist/search-index.js +0 -23
- package/packages/@repo/previewing/dist/serialization.d.ts +0 -13
- package/packages/@repo/previewing/dist/serialization.d.ts.map +0 -1
- package/packages/@repo/previewing/dist/serialization.js +0 -108
- package/packages/@repo/previewing/dist/toc-index.d.ts +0 -5
- package/packages/@repo/previewing/dist/toc-index.d.ts.map +0 -1
- package/packages/@repo/previewing/dist/toc-index.js +0 -53
- package/packages/@repo/previewing/dist/types.d.ts +0 -92
- package/packages/@repo/previewing/dist/types.d.ts.map +0 -1
- package/packages/@repo/previewing/dist/types.js +0 -1
- package/packages/@repo/previewing/dist/utility-index.d.ts +0 -7
- package/packages/@repo/previewing/dist/utility-index.d.ts.map +0 -1
- package/packages/@repo/previewing/dist/utility-index.js +0 -201
- package/packages/@repo/previewing/package.json +0 -41
- package/packages/@repo/previewing/src/blob-source.ts +0 -185
- package/packages/@repo/previewing/src/config-loader.ts +0 -316
- package/packages/@repo/previewing/src/constants.ts +0 -16
- package/packages/@repo/previewing/src/content-index.ts +0 -384
- package/packages/@repo/previewing/src/content-source.ts +0 -12
- package/packages/@repo/previewing/src/fs-source.ts +0 -103
- package/packages/@repo/previewing/src/index.ts +0 -71
- package/packages/@repo/previewing/src/markdown/agent-markdown.ts +0 -254
- package/packages/@repo/previewing/src/markdown/format.ts +0 -63
- package/packages/@repo/previewing/src/markdown/links.ts +0 -46
- package/packages/@repo/previewing/src/openapi-pages.ts +0 -319
- package/packages/@repo/previewing/src/search-index.ts +0 -38
- package/packages/@repo/previewing/src/serialization.ts +0 -191
- package/packages/@repo/previewing/src/toc-index.ts +0 -72
- package/packages/@repo/previewing/src/types.ts +0 -105
- package/packages/@repo/previewing/src/utility-index.ts +0 -304
- package/packages/@repo/validation/dist/index.d.ts +0 -12
- package/packages/@repo/validation/dist/index.d.ts.map +0 -1
- package/packages/@repo/validation/dist/index.js +0 -30
- package/packages/@repo/validation/package.json +0 -38
- package/packages/@repo/validation/src/blodemd-docs-schema.json +0 -3575
- package/packages/@repo/validation/src/index.ts +0 -59
- package/scripts/prepare-package.mjs +0 -53
- package/scripts/repo-packages.mjs +0 -8
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
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { createRequire } from "node:module";
|
|
3
2
|
import { Command, InvalidArgumentError } from "commander";
|
|
4
3
|
import { confirm, intro, isCancel, log, select, spinner, text } from "@clack/prompts";
|
|
5
4
|
import chalk from "chalk";
|
|
@@ -20,6 +19,7 @@ import { watch } from "chokidar";
|
|
|
20
19
|
import { readFileSync } from "node:fs";
|
|
21
20
|
//#region src/constants.ts
|
|
22
21
|
const CLI_NAME = "blodemd";
|
|
22
|
+
const BLODE_API_KEY_ENV = "BLODEMD_API_KEY";
|
|
23
23
|
const BLODE_PROJECT_ENV = "BLODEMD_PROJECT";
|
|
24
24
|
const OAUTH_CLIENT_ID = "6b5f9860-fe96-4a83-b1ad-266260523c91";
|
|
25
25
|
const DEFAULT_OAUTH_CALLBACK_PORT = 8787;
|
|
@@ -55,6 +55,7 @@ const toCliError = (error) => {
|
|
|
55
55
|
if (error instanceof Error) {
|
|
56
56
|
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
57
|
if (error.name === "TimeoutError" || error.name === "AbortError") return new CliError("Request timed out.", EXIT_CODES.NETWORK, "The API may be unavailable. Try again later.");
|
|
58
|
+
if (/\b401\b/.test(error.message)) return new CliError(error.message, EXIT_CODES.AUTH_REQUIRED, "Check your API key or run \"blodemd login\".");
|
|
58
59
|
return new CliError(error.message, EXIT_CODES.ERROR);
|
|
59
60
|
}
|
|
60
61
|
return new CliError("Unknown error", EXIT_CODES.ERROR);
|
|
@@ -1220,6 +1221,67 @@ const registerAnalyticsCommand = (program) => {
|
|
|
1220
1221
|
});
|
|
1221
1222
|
};
|
|
1222
1223
|
//#endregion
|
|
1224
|
+
//#region src/output.ts
|
|
1225
|
+
const isInteractive = (json = false) => !json && Boolean(process.stdout.isTTY) && !process.env.CI;
|
|
1226
|
+
const writeLine = (message) => {
|
|
1227
|
+
process.stderr.write(`${message}\n`);
|
|
1228
|
+
};
|
|
1229
|
+
const createReporter = ({ json = false } = {}) => {
|
|
1230
|
+
const interactive = isInteractive(json);
|
|
1231
|
+
if (!interactive) return {
|
|
1232
|
+
info: writeLine,
|
|
1233
|
+
interactive,
|
|
1234
|
+
json: (payload) => {
|
|
1235
|
+
process.stdout.write(`${JSON.stringify(payload)}\n`);
|
|
1236
|
+
},
|
|
1237
|
+
step: writeLine,
|
|
1238
|
+
stop: (message) => {
|
|
1239
|
+
if (message) writeLine(message);
|
|
1240
|
+
},
|
|
1241
|
+
success: writeLine,
|
|
1242
|
+
warn: writeLine
|
|
1243
|
+
};
|
|
1244
|
+
const s = spinner();
|
|
1245
|
+
let active = false;
|
|
1246
|
+
return {
|
|
1247
|
+
info(message) {
|
|
1248
|
+
if (active) {
|
|
1249
|
+
s.stop();
|
|
1250
|
+
active = false;
|
|
1251
|
+
}
|
|
1252
|
+
log.info(message);
|
|
1253
|
+
},
|
|
1254
|
+
interactive,
|
|
1255
|
+
json() {},
|
|
1256
|
+
step(message) {
|
|
1257
|
+
if (active) s.message(message);
|
|
1258
|
+
else {
|
|
1259
|
+
s.start(message);
|
|
1260
|
+
active = true;
|
|
1261
|
+
}
|
|
1262
|
+
},
|
|
1263
|
+
stop(message) {
|
|
1264
|
+
if (active) {
|
|
1265
|
+
s.stop(message);
|
|
1266
|
+
active = false;
|
|
1267
|
+
}
|
|
1268
|
+
},
|
|
1269
|
+
success(message) {
|
|
1270
|
+
if (active) {
|
|
1271
|
+
s.stop(message);
|
|
1272
|
+
active = false;
|
|
1273
|
+
} else log.success(message);
|
|
1274
|
+
},
|
|
1275
|
+
warn(message) {
|
|
1276
|
+
if (active) {
|
|
1277
|
+
s.stop();
|
|
1278
|
+
active = false;
|
|
1279
|
+
}
|
|
1280
|
+
log.warn(message);
|
|
1281
|
+
}
|
|
1282
|
+
};
|
|
1283
|
+
};
|
|
1284
|
+
//#endregion
|
|
1223
1285
|
//#region src/scaffold.ts
|
|
1224
1286
|
const SCAFFOLD_TEMPLATES = ["minimal", "starter"];
|
|
1225
1287
|
const DEFAULT_SCAFFOLD_DIRECTORY = "docs";
|
|
@@ -1617,11 +1679,16 @@ const collectFiles = async (root, directory = root) => {
|
|
|
1617
1679
|
}
|
|
1618
1680
|
return files.toSorted((left, right) => left.localeCompare(right));
|
|
1619
1681
|
};
|
|
1620
|
-
const reportCommandError = (prefix, error) => {
|
|
1682
|
+
const reportCommandError = (prefix, error, options) => {
|
|
1621
1683
|
const cliError = toCliError(error);
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1684
|
+
if (isInteractive(options?.json)) {
|
|
1685
|
+
log.error(`${prefix}: ${cliError.message}`);
|
|
1686
|
+
if (cliError.hint) log.info(cliError.hint);
|
|
1687
|
+
log.info("Failed");
|
|
1688
|
+
} else {
|
|
1689
|
+
process.stderr.write(`${prefix}: ${cliError.message}\n`);
|
|
1690
|
+
if (cliError.hint) process.stderr.write(`${cliError.hint}\n`);
|
|
1691
|
+
}
|
|
1625
1692
|
process.exitCode = cliError.exitCode;
|
|
1626
1693
|
};
|
|
1627
1694
|
const parseScaffoldTemplate = (value) => {
|
|
@@ -1827,6 +1894,7 @@ const registerAuthCommands = (program) => {
|
|
|
1827
1894
|
const resolved = await resolveAuthToken();
|
|
1828
1895
|
if (!resolved) {
|
|
1829
1896
|
log.warn("Not logged in. Run \"blodemd login\" to authenticate.");
|
|
1897
|
+
process.exitCode = EXIT_CODES.AUTH_REQUIRED;
|
|
1830
1898
|
return;
|
|
1831
1899
|
}
|
|
1832
1900
|
const status = resolveTokenStatus(resolved);
|
|
@@ -1904,51 +1972,7 @@ const DEV_READY_TIMEOUT_MS = 45e3;
|
|
|
1904
1972
|
const DEV_PORT_SCAN_LIMIT = 10;
|
|
1905
1973
|
const DEV_SHUTDOWN_TIMEOUT_MS = 5e3;
|
|
1906
1974
|
const LOCALHOST = "127.0.0.1";
|
|
1907
|
-
const
|
|
1908
|
-
".next",
|
|
1909
|
-
".turbo",
|
|
1910
|
-
"node_modules"
|
|
1911
|
-
]);
|
|
1912
|
-
const STANDALONE_RUNTIME_MAX_AGE_MS = 1440 * 60 * 1e3;
|
|
1913
|
-
const STANDALONE_RUNTIME_PREFIX = "standalone-runtime-";
|
|
1914
|
-
const TURBOPACK_ARGS = ["dev", "--turbopack"];
|
|
1915
|
-
const fileExists = async (filePath) => {
|
|
1916
|
-
try {
|
|
1917
|
-
await fs.access(filePath);
|
|
1918
|
-
return true;
|
|
1919
|
-
} catch {
|
|
1920
|
-
return false;
|
|
1921
|
-
}
|
|
1922
|
-
};
|
|
1923
|
-
const resolveCommonAncestor = (pathsToCompare) => {
|
|
1924
|
-
const [firstPath, ...restPaths] = pathsToCompare;
|
|
1925
|
-
if (!firstPath) return path.sep;
|
|
1926
|
-
const first = path.resolve(firstPath);
|
|
1927
|
-
const { root } = path.parse(first);
|
|
1928
|
-
const firstSegments = first.slice(root.length).split(path.sep).filter(Boolean);
|
|
1929
|
-
const sharedSegments = [];
|
|
1930
|
-
for (const [index, segment] of firstSegments.entries()) {
|
|
1931
|
-
if (!restPaths.every((candidatePath) => {
|
|
1932
|
-
const candidate = path.resolve(candidatePath);
|
|
1933
|
-
if (path.parse(candidate).root !== root) return false;
|
|
1934
|
-
return candidate.slice(root.length).split(path.sep).filter(Boolean)[index] === segment;
|
|
1935
|
-
})) break;
|
|
1936
|
-
sharedSegments.push(segment);
|
|
1937
|
-
}
|
|
1938
|
-
return path.join(root, ...sharedSegments);
|
|
1939
|
-
};
|
|
1940
|
-
const cleanupStandaloneRuntimeRoots = async (configDir, maxAgeMs = STANDALONE_RUNTIME_MAX_AGE_MS) => {
|
|
1941
|
-
const cutoff = Date.now() - maxAgeMs;
|
|
1942
|
-
const entries = await fs.readdir(configDir, { withFileTypes: true });
|
|
1943
|
-
await Promise.all(entries.filter((entry) => entry.isDirectory() && entry.name.startsWith(STANDALONE_RUNTIME_PREFIX)).map(async (entry) => {
|
|
1944
|
-
const entryPath = path.join(configDir, entry.name);
|
|
1945
|
-
if ((await fs.stat(entryPath)).mtimeMs >= cutoff) return;
|
|
1946
|
-
await fs.rm(entryPath, {
|
|
1947
|
-
force: true,
|
|
1948
|
-
recursive: true
|
|
1949
|
-
});
|
|
1950
|
-
}));
|
|
1951
|
-
};
|
|
1975
|
+
const DEV_PACKAGE_NAME = "blodemd-dev";
|
|
1952
1976
|
const probePortAvailability = async (port) => {
|
|
1953
1977
|
const server = createServer$1();
|
|
1954
1978
|
const listening = (async () => {
|
|
@@ -2001,122 +2025,33 @@ const shutdownChildProcess = async (child, timeoutMs = DEV_SHUTDOWN_TIMEOUT_MS)
|
|
|
2001
2025
|
clearTimeout(timer);
|
|
2002
2026
|
});
|
|
2003
2027
|
};
|
|
2004
|
-
|
|
2005
|
-
* Derive the CLI npm package root from the running script path.
|
|
2006
|
-
* The CLI entry point is at `<pkg-root>/dist/cli.mjs`.
|
|
2007
|
-
*/
|
|
2008
|
-
const resolveCliPackageRoot = (cliFilePath) => path.dirname(path.dirname(cliFilePath));
|
|
2009
|
-
const copyStandaloneTree = async (sourceDir, targetDir) => {
|
|
2010
|
-
await fs.cp(sourceDir, targetDir, {
|
|
2011
|
-
filter: (source) => {
|
|
2012
|
-
const relative = path.relative(sourceDir, source);
|
|
2013
|
-
if (!relative) return true;
|
|
2014
|
-
const topSegment = relative.split(path.sep)[0] ?? "";
|
|
2015
|
-
return !RUNTIME_EXCLUDE_DIRS.has(topSegment);
|
|
2016
|
-
},
|
|
2017
|
-
recursive: true
|
|
2018
|
-
});
|
|
2019
|
-
};
|
|
2020
|
-
const isStandaloneCliInstall = async (cliPackageRoot) => {
|
|
2028
|
+
const fileExists = async (filePath) => {
|
|
2021
2029
|
try {
|
|
2022
|
-
|
|
2030
|
+
await fs.access(filePath);
|
|
2031
|
+
return true;
|
|
2023
2032
|
} catch {
|
|
2024
|
-
return
|
|
2033
|
+
return false;
|
|
2025
2034
|
}
|
|
2026
2035
|
};
|
|
2027
|
-
const createStandaloneRuntimeRoot = async (configDir = CONFIG_DIR) => {
|
|
2028
|
-
await fs.mkdir(configDir, { recursive: true });
|
|
2029
|
-
await cleanupStandaloneRuntimeRoots(configDir);
|
|
2030
|
-
return await fs.mkdtemp(path.join(configDir, STANDALONE_RUNTIME_PREFIX));
|
|
2031
|
-
};
|
|
2032
2036
|
/**
|
|
2033
|
-
*
|
|
2034
|
-
*
|
|
2035
|
-
* `npx` caches) hoist shared deps above the package directory, so
|
|
2036
|
-
* `<cliPackageRoot>/node_modules` may not exist or may not contain `next`.
|
|
2037
|
-
* Resolve `next/package.json` and use the directory that owns it.
|
|
2037
|
+
* Derive the CLI npm package root from the running script path.
|
|
2038
|
+
* The CLI entry point is at `<pkg-root>/dist/cli.mjs`.
|
|
2038
2039
|
*/
|
|
2039
|
-
const
|
|
2040
|
-
const localNodeModules = path.join(cliPackageRoot, "node_modules");
|
|
2041
|
-
if (await fileExists(path.join(localNodeModules, "next", "package.json"))) return localNodeModules;
|
|
2042
|
-
const nextPkgPath = createRequire(path.join(cliPackageRoot, "package.json")).resolve("next/package.json");
|
|
2043
|
-
return path.dirname(path.dirname(nextPkgPath));
|
|
2044
|
-
};
|
|
2045
|
-
const materializeStandaloneRuntime = async (cliPackageRoot) => {
|
|
2046
|
-
const runtimeRoot = await createStandaloneRuntimeRoot();
|
|
2047
|
-
try {
|
|
2048
|
-
for (const dir of [
|
|
2049
|
-
"dev-server",
|
|
2050
|
-
"docs",
|
|
2051
|
-
"packages"
|
|
2052
|
-
]) await copyStandaloneTree(path.join(cliPackageRoot, dir), path.join(runtimeRoot, dir));
|
|
2053
|
-
const runtimeNodeModules = await resolveRuntimeNodeModules(cliPackageRoot);
|
|
2054
|
-
await fs.symlink(runtimeNodeModules, path.join(runtimeRoot, "node_modules"), process.platform === "win32" ? "junction" : "dir");
|
|
2055
|
-
const linkTarget = path.join(runtimeRoot, "packages", "@repo");
|
|
2056
|
-
for (const consumer of [
|
|
2057
|
-
"dev-server",
|
|
2058
|
-
"docs",
|
|
2059
|
-
"packages"
|
|
2060
|
-
]) {
|
|
2061
|
-
await fs.mkdir(path.join(runtimeRoot, consumer, "node_modules"), { recursive: true });
|
|
2062
|
-
await fs.symlink(linkTarget, path.join(runtimeRoot, consumer, "node_modules", "@repo"), process.platform === "win32" ? "junction" : "dir");
|
|
2063
|
-
}
|
|
2064
|
-
await fs.writeFile(path.join(runtimeRoot, "dev-server", "package.json"), `${JSON.stringify({
|
|
2065
|
-
dependencies: {
|
|
2066
|
-
next: "16.2.1",
|
|
2067
|
-
react: "^19.2.0",
|
|
2068
|
-
"react-dom": "^19.2.0"
|
|
2069
|
-
},
|
|
2070
|
-
devDependencies: {
|
|
2071
|
-
"@types/node": "^24.12.2",
|
|
2072
|
-
"@types/react": "19.2.14",
|
|
2073
|
-
"@types/react-dom": "19.2.3",
|
|
2074
|
-
typescript: "6.0.2"
|
|
2075
|
-
},
|
|
2076
|
-
name: "blodemd-dev-server",
|
|
2077
|
-
private: true,
|
|
2078
|
-
type: "module"
|
|
2079
|
-
}, null, 2)}\n`);
|
|
2080
|
-
return {
|
|
2081
|
-
devServerDir: path.join(runtimeRoot, "dev-server"),
|
|
2082
|
-
runtimeRoot
|
|
2083
|
-
};
|
|
2084
|
-
} catch (error) {
|
|
2085
|
-
await fs.rm(runtimeRoot, {
|
|
2086
|
-
force: true,
|
|
2087
|
-
recursive: true
|
|
2088
|
-
});
|
|
2089
|
-
throw error;
|
|
2090
|
-
}
|
|
2091
|
-
};
|
|
2040
|
+
const resolveCliPackageRoot = (cliFilePath) => path.dirname(path.dirname(cliFilePath));
|
|
2092
2041
|
/**
|
|
2093
|
-
*
|
|
2094
|
-
*
|
|
2042
|
+
* Read the CLI's own version so the delegated `blodemd-dev` invocation is
|
|
2043
|
+
* pinned to the exact matching release (they version together via changesets).
|
|
2095
2044
|
*/
|
|
2096
|
-
const
|
|
2097
|
-
const
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
try {
|
|
2101
|
-
createRequire(path.join(cliPackageRoot, "package.json")).resolve("next/package.json");
|
|
2102
|
-
} catch {
|
|
2103
|
-
return null;
|
|
2104
|
-
}
|
|
2105
|
-
const runtime = await materializeStandaloneRuntime(cliPackageRoot);
|
|
2106
|
-
return {
|
|
2107
|
-
devServerDir: runtime.devServerDir,
|
|
2108
|
-
mode: "standalone",
|
|
2109
|
-
nextPackageRoot: cliPackageRoot,
|
|
2110
|
-
runtimeRoot: runtime.runtimeRoot
|
|
2111
|
-
};
|
|
2045
|
+
const readCliPackageVersion = async (cliFilePath) => {
|
|
2046
|
+
const packageJsonPath = path.join(resolveCliPackageRoot(cliFilePath), "package.json");
|
|
2047
|
+
const raw = await fs.readFile(packageJsonPath, "utf8");
|
|
2048
|
+
return JSON.parse(raw).version ?? "0.0.0";
|
|
2112
2049
|
};
|
|
2113
2050
|
/**
|
|
2114
|
-
*
|
|
2051
|
+
* Walk up from the running CLI looking for the blodemd monorepo checkout.
|
|
2052
|
+
* Returns the repo root when found (development mode), otherwise `null`
|
|
2053
|
+
* (installed mode, which delegates to `blodemd-dev`).
|
|
2115
2054
|
*/
|
|
2116
|
-
const resolveNextBin = (cliPackageRoot) => {
|
|
2117
|
-
const nextPkgPath = createRequire(path.join(cliPackageRoot, "package.json")).resolve("next/package.json");
|
|
2118
|
-
return path.join(path.dirname(nextPkgPath), "dist", "bin", "next");
|
|
2119
|
-
};
|
|
2120
2055
|
const findMonorepoRoot = async (start) => {
|
|
2121
2056
|
let current = start;
|
|
2122
2057
|
while (true) {
|
|
@@ -2124,35 +2059,50 @@ const findMonorepoRoot = async (start) => {
|
|
|
2124
2059
|
if (await fileExists(packageJsonPath)) {
|
|
2125
2060
|
const raw = await fs.readFile(packageJsonPath, "utf8");
|
|
2126
2061
|
const workspaces = JSON.parse(raw).workspaces ?? [];
|
|
2127
|
-
|
|
2062
|
+
const hasWorkspaces = workspaces.includes("apps/*") && workspaces.includes("packages/*");
|
|
2063
|
+
const hasDevServer = await fileExists(path.join(current, "apps", "dev-server", "next.config.js"));
|
|
2064
|
+
if (hasWorkspaces && hasDevServer) return current;
|
|
2128
2065
|
}
|
|
2129
2066
|
const parent = path.dirname(current);
|
|
2130
2067
|
if (parent === current) break;
|
|
2131
2068
|
current = parent;
|
|
2132
2069
|
}
|
|
2133
|
-
|
|
2070
|
+
return null;
|
|
2134
2071
|
};
|
|
2135
2072
|
const resolveDevServer = async (cliFilePath) => {
|
|
2136
|
-
const
|
|
2137
|
-
if (
|
|
2138
|
-
return {
|
|
2073
|
+
const repoRoot = await findMonorepoRoot(path.dirname(cliFilePath));
|
|
2074
|
+
if (repoRoot) return {
|
|
2139
2075
|
mode: "monorepo",
|
|
2140
|
-
repoRoot
|
|
2076
|
+
repoRoot
|
|
2077
|
+
};
|
|
2078
|
+
return {
|
|
2079
|
+
devPackageVersion: await readCliPackageVersion(cliFilePath),
|
|
2080
|
+
mode: "delegated"
|
|
2141
2081
|
};
|
|
2142
2082
|
};
|
|
2143
2083
|
const buildDevServerLaunch = (server, { root, port }) => {
|
|
2144
|
-
if (server.mode === "
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2084
|
+
if (server.mode === "delegated") {
|
|
2085
|
+
const npxCommand = process.platform === "win32" ? "npx.cmd" : "npx";
|
|
2086
|
+
return {
|
|
2087
|
+
args: [
|
|
2088
|
+
"-y",
|
|
2089
|
+
`${DEV_PACKAGE_NAME}@${server.devPackageVersion}`,
|
|
2090
|
+
"dev",
|
|
2091
|
+
"--port",
|
|
2092
|
+
String(port),
|
|
2093
|
+
"--dir",
|
|
2094
|
+
root,
|
|
2095
|
+
"--no-open"
|
|
2096
|
+
],
|
|
2097
|
+
command: npxCommand,
|
|
2098
|
+
cwd: process.cwd(),
|
|
2099
|
+
env: {
|
|
2100
|
+
...process.env,
|
|
2101
|
+
DOCS_ROOT: root,
|
|
2102
|
+
PORT: String(port)
|
|
2103
|
+
}
|
|
2104
|
+
};
|
|
2105
|
+
}
|
|
2156
2106
|
return {
|
|
2157
2107
|
args: [
|
|
2158
2108
|
"run",
|
|
@@ -2198,7 +2148,6 @@ const defaultDevCommandDependencies = {
|
|
|
2198
2148
|
getLog: log,
|
|
2199
2149
|
getOpen: open,
|
|
2200
2150
|
parsePortValue: parsePort,
|
|
2201
|
-
removeDirectory: fs.rm,
|
|
2202
2151
|
resolveDevPortValue: resolveDevPort,
|
|
2203
2152
|
resolveDocsRootValue: resolveDocsRoot,
|
|
2204
2153
|
resolveServer: resolveDevServer,
|
|
@@ -2233,10 +2182,6 @@ const devCommand = async ({ dir, openBrowser, port: portValue }, dependencies =
|
|
|
2233
2182
|
watcher = null;
|
|
2234
2183
|
}
|
|
2235
2184
|
await dependencies.shutdownChild(child);
|
|
2236
|
-
if (server.mode === "standalone") await dependencies.removeDirectory(server.runtimeRoot, {
|
|
2237
|
-
force: true,
|
|
2238
|
-
recursive: true
|
|
2239
|
-
});
|
|
2240
2185
|
};
|
|
2241
2186
|
process.once("SIGINT", closeAll);
|
|
2242
2187
|
process.once("SIGTERM", closeAll);
|
|
@@ -2513,6 +2458,31 @@ const registerNewCommand = (program) => {
|
|
|
2513
2458
|
});
|
|
2514
2459
|
};
|
|
2515
2460
|
//#endregion
|
|
2461
|
+
//#region src/commands/projects.ts
|
|
2462
|
+
const registerProjectsCommand = (program) => {
|
|
2463
|
+
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) => {
|
|
2464
|
+
const reporter = createReporter({ json: options.json });
|
|
2465
|
+
if (reporter.interactive) intro(chalk.bold("blodemd projects"));
|
|
2466
|
+
try {
|
|
2467
|
+
const resolved = await resolveAuthToken();
|
|
2468
|
+
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.");
|
|
2469
|
+
const apiUrl = options.apiUrl ?? process.env["BLODEMD_API_URL"] ?? "https://api.blode.md";
|
|
2470
|
+
const projects = await requestJson(new URL("/projects", apiUrl).toString(), { headers: { Authorization: `Bearer ${resolved.token}` } }, "Failed to list projects");
|
|
2471
|
+
reporter.json(projects);
|
|
2472
|
+
if (projects.length === 0) {
|
|
2473
|
+
reporter.info("No projects yet.");
|
|
2474
|
+
return;
|
|
2475
|
+
}
|
|
2476
|
+
for (const project of projects) {
|
|
2477
|
+
const label = project.name ? `${chalk.cyan(project.slug)} — ${project.name}` : chalk.cyan(project.slug);
|
|
2478
|
+
reporter.info(label);
|
|
2479
|
+
}
|
|
2480
|
+
} catch (error) {
|
|
2481
|
+
reportCommandError("Projects failed", error, { json: options.json });
|
|
2482
|
+
}
|
|
2483
|
+
});
|
|
2484
|
+
};
|
|
2485
|
+
//#endregion
|
|
2516
2486
|
//#region src/upload.ts
|
|
2517
2487
|
const TEXT_CONTENT_TYPES = {
|
|
2518
2488
|
".css": "text/css; charset=utf-8",
|
|
@@ -2560,10 +2530,10 @@ const resolveAuthHeaders = async (apiKeyOption) => {
|
|
|
2560
2530
|
const apiKey = (apiKeyOption ?? process.env["BLODEMD_API_KEY"])?.trim();
|
|
2561
2531
|
if (apiKey) return {
|
|
2562
2532
|
canAutoCreate: false,
|
|
2563
|
-
headers: {
|
|
2533
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
2564
2534
|
};
|
|
2565
2535
|
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.");
|
|
2536
|
+
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
2537
|
return {
|
|
2568
2538
|
canAutoCreate: true,
|
|
2569
2539
|
headers: { Authorization: `Bearer ${resolved.token}` }
|
|
@@ -2604,8 +2574,9 @@ const resolvePushConfig = async (config, options) => {
|
|
|
2604
2574
|
usedLegacyNameFallback
|
|
2605
2575
|
};
|
|
2606
2576
|
};
|
|
2607
|
-
const autoCreateProject = async (project, projectDisplayName, apiUrl, headers, canAutoCreate) => {
|
|
2577
|
+
const autoCreateProject = async (project, projectDisplayName, apiUrl, headers, canAutoCreate, reporter) => {
|
|
2608
2578
|
if (!canAutoCreate) throw new Error(`Project "${project}" not found. Create it at blode.md or login with "blodemd login" to auto-create.`);
|
|
2579
|
+
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
2580
|
const shouldCreate = await confirm({ message: `Project "${project}" doesn't exist. Create it?` });
|
|
2610
2581
|
if (isCancel(shouldCreate) || !shouldCreate) return false;
|
|
2611
2582
|
const createResult = await requestJson(new URL("/projects", apiUrl).toString(), {
|
|
@@ -2616,12 +2587,18 @@ const autoCreateProject = async (project, projectDisplayName, apiUrl, headers, c
|
|
|
2616
2587
|
headers,
|
|
2617
2588
|
method: "POST"
|
|
2618
2589
|
}, "Failed to create project");
|
|
2619
|
-
|
|
2590
|
+
reporter.success(`Project ${chalk.cyan(createResult.slug)} created`);
|
|
2591
|
+
const keyResult = await requestJson(new URL(`/projects/${createResult.id}/keys`, apiUrl).toString(), {
|
|
2592
|
+
body: JSON.stringify({ name: "CI deploy key" }),
|
|
2593
|
+
headers,
|
|
2594
|
+
method: "POST"
|
|
2595
|
+
}, "Failed to create deploy key");
|
|
2596
|
+
reporter.info(`Deploy key created (save this — shown once, use as ${BLODE_API_KEY_ENV} in CI): ${chalk.cyan(keyResult.key)}`);
|
|
2620
2597
|
return true;
|
|
2621
2598
|
};
|
|
2622
2599
|
const MAX_BATCH_BYTES = 4 * 1024 * 1024;
|
|
2623
|
-
const uploadFiles = async (files, root, apiPath, deploymentId, headers,
|
|
2624
|
-
|
|
2600
|
+
const uploadFiles = async (files, root, apiPath, deploymentId, headers, reporter) => {
|
|
2601
|
+
reporter.step(`Uploading ${files.length} files`);
|
|
2625
2602
|
let uploaded = 0;
|
|
2626
2603
|
for await (const batch of createUploadBatches({
|
|
2627
2604
|
files,
|
|
@@ -2634,26 +2611,26 @@ const uploadFiles = async (files, root, apiPath, deploymentId, headers, s) => {
|
|
|
2634
2611
|
method: "POST"
|
|
2635
2612
|
}, "Failed to upload files");
|
|
2636
2613
|
uploaded += batch.length;
|
|
2637
|
-
|
|
2614
|
+
reporter.step(`Uploading files (${uploaded}/${files.length})`);
|
|
2638
2615
|
}
|
|
2639
|
-
|
|
2616
|
+
reporter.success(`Uploaded ${chalk.cyan(String(files.length))} files`);
|
|
2640
2617
|
};
|
|
2641
2618
|
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
|
-
|
|
2619
|
+
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) => {
|
|
2620
|
+
const reporter = createReporter({ json: options.json });
|
|
2621
|
+
if (reporter.interactive) intro(chalk.bold("blodemd push"));
|
|
2645
2622
|
try {
|
|
2646
2623
|
const root = await resolveDocsRoot(dir);
|
|
2647
|
-
|
|
2624
|
+
reporter.step("Validating configuration");
|
|
2648
2625
|
const { config, warnings } = await loadValidatedSiteConfig(root);
|
|
2649
|
-
|
|
2650
|
-
for (const warning of warnings)
|
|
2626
|
+
reporter.success("Configuration valid");
|
|
2627
|
+
for (const warning of warnings) reporter.warn(warning);
|
|
2651
2628
|
const { project, projectDisplayName, apiUrl, authHeaders, canAutoCreate, branch, commitMessage, usedLegacyNameFallback } = await resolvePushConfig(config, options);
|
|
2652
|
-
if (usedLegacyNameFallback)
|
|
2653
|
-
|
|
2629
|
+
if (usedLegacyNameFallback) reporter.warn(LEGACY_PROJECT_NAME_FALLBACK_WARNING);
|
|
2630
|
+
reporter.step("Collecting files");
|
|
2654
2631
|
const files = await collectFiles(root);
|
|
2655
2632
|
if (files.length === 0) throw new Error("No files found to deploy.");
|
|
2656
|
-
|
|
2633
|
+
reporter.success(`Found ${chalk.cyan(String(files.length))} files`);
|
|
2657
2634
|
const headers = {
|
|
2658
2635
|
...authHeaders,
|
|
2659
2636
|
"Content-Type": "application/json"
|
|
@@ -2663,7 +2640,7 @@ const registerPushCommand = (program) => {
|
|
|
2663
2640
|
branch,
|
|
2664
2641
|
commitMessage
|
|
2665
2642
|
});
|
|
2666
|
-
|
|
2643
|
+
reporter.step("Creating deployment");
|
|
2667
2644
|
let deployment;
|
|
2668
2645
|
try {
|
|
2669
2646
|
deployment = await requestJson(apiPath(""), {
|
|
@@ -2673,34 +2650,39 @@ const registerPushCommand = (program) => {
|
|
|
2673
2650
|
}, "Failed to create deployment");
|
|
2674
2651
|
} catch (error) {
|
|
2675
2652
|
if (!(error instanceof Error ? error.message : "").includes("404")) throw error;
|
|
2676
|
-
|
|
2677
|
-
if (!await autoCreateProject(project, projectDisplayName, apiUrl, headers, canAutoCreate)) {
|
|
2678
|
-
|
|
2653
|
+
reporter.stop("Project not found");
|
|
2654
|
+
if (!await autoCreateProject(project, projectDisplayName, apiUrl, headers, canAutoCreate, reporter)) {
|
|
2655
|
+
reporter.info("Cancelled");
|
|
2679
2656
|
return;
|
|
2680
2657
|
}
|
|
2681
|
-
|
|
2658
|
+
reporter.step("Creating deployment");
|
|
2682
2659
|
deployment = await requestJson(apiPath(""), {
|
|
2683
2660
|
body: createDeploymentBody,
|
|
2684
2661
|
headers,
|
|
2685
2662
|
method: "POST"
|
|
2686
2663
|
}, "Failed to create deployment");
|
|
2687
2664
|
}
|
|
2688
|
-
|
|
2689
|
-
await uploadFiles(files, root, apiPath, deployment.id, headers,
|
|
2690
|
-
|
|
2665
|
+
reporter.success(`Deployment ${chalk.cyan(deployment.id)} created`);
|
|
2666
|
+
await uploadFiles(files, root, apiPath, deployment.id, headers, reporter);
|
|
2667
|
+
reporter.step("Finalizing deployment");
|
|
2691
2668
|
const finalized = await requestJson(apiPath(`/${deployment.id}/finalize`), {
|
|
2692
2669
|
body: JSON.stringify({ promote: true }),
|
|
2693
2670
|
headers,
|
|
2694
2671
|
method: "POST"
|
|
2695
2672
|
}, "Failed to finalize deployment");
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
if (finalized.manifestUrl)
|
|
2699
|
-
if (typeof finalized.fileCount === "number")
|
|
2700
|
-
|
|
2673
|
+
reporter.success("Deployment finalized");
|
|
2674
|
+
reporter.success(`Published ${chalk.cyan(finalized.id)}`);
|
|
2675
|
+
if (finalized.manifestUrl) reporter.info(`Manifest: ${finalized.manifestUrl}`);
|
|
2676
|
+
if (typeof finalized.fileCount === "number") reporter.info(`Files: ${finalized.fileCount}`);
|
|
2677
|
+
reporter.info("Done");
|
|
2678
|
+
reporter.json({
|
|
2679
|
+
deploymentId: finalized.id,
|
|
2680
|
+
fileCount: finalized.fileCount ?? files.length,
|
|
2681
|
+
manifestUrl: finalized.manifestUrl ?? null
|
|
2682
|
+
});
|
|
2701
2683
|
} catch (error) {
|
|
2702
|
-
|
|
2703
|
-
reportCommandError("Push failed", error);
|
|
2684
|
+
reporter.stop("Failed");
|
|
2685
|
+
reportCommandError("Push failed", error, { json: options.json });
|
|
2704
2686
|
}
|
|
2705
2687
|
});
|
|
2706
2688
|
};
|
|
@@ -2708,15 +2690,24 @@ const registerPushCommand = (program) => {
|
|
|
2708
2690
|
//#region src/commands/validate.ts
|
|
2709
2691
|
const CONFIG_FILE = "docs.json";
|
|
2710
2692
|
const registerValidateCommand = (program) => {
|
|
2711
|
-
program.command("validate").description("Validate docs.json").argument("[dir]", "docs directory").action(async (dir) => {
|
|
2712
|
-
|
|
2693
|
+
program.command("validate").description("Validate docs.json").argument("[dir]", "docs directory").option("--json", "output machine-readable JSON (implies non-interactive)").action(async (dir, options) => {
|
|
2694
|
+
const reporter = createReporter({ json: options.json });
|
|
2695
|
+
if (reporter.interactive) intro(chalk.bold("blodemd validate"));
|
|
2713
2696
|
try {
|
|
2714
2697
|
const { warnings } = await loadValidatedSiteConfig(await resolveDocsRoot(dir));
|
|
2715
|
-
for (const warning of warnings)
|
|
2716
|
-
|
|
2717
|
-
|
|
2698
|
+
for (const warning of warnings) reporter.warn(warning);
|
|
2699
|
+
reporter.success(`${chalk.cyan(CONFIG_FILE)} is valid.`);
|
|
2700
|
+
reporter.info("Done");
|
|
2701
|
+
reporter.json({
|
|
2702
|
+
valid: true,
|
|
2703
|
+
warnings
|
|
2704
|
+
});
|
|
2718
2705
|
} catch (error) {
|
|
2719
|
-
|
|
2706
|
+
if (options.json && error instanceof CliError && error.exitCode === EXIT_CODES.VALIDATION) reporter.json({
|
|
2707
|
+
errors: error.message.split("\n"),
|
|
2708
|
+
valid: false
|
|
2709
|
+
});
|
|
2710
|
+
reportCommandError("Validation failed", error, { json: options.json });
|
|
2720
2711
|
}
|
|
2721
2712
|
});
|
|
2722
2713
|
};
|
|
@@ -2770,9 +2761,18 @@ registerAuthCommands(program);
|
|
|
2770
2761
|
registerNewCommand(program);
|
|
2771
2762
|
registerValidateCommand(program);
|
|
2772
2763
|
registerPushCommand(program);
|
|
2764
|
+
registerProjectsCommand(program);
|
|
2773
2765
|
registerDevCommand(program);
|
|
2774
2766
|
registerAnalyticsCommand(program);
|
|
2775
|
-
program.
|
|
2767
|
+
program.addHelpText("after", "\nExample:\n $ blodemd push ./docs --project my-docs\n");
|
|
2768
|
+
try {
|
|
2769
|
+
await program.parseAsync();
|
|
2770
|
+
} catch (error) {
|
|
2771
|
+
const cliError = toCliError(error);
|
|
2772
|
+
console.error(cliError.message);
|
|
2773
|
+
if (cliError.hint) console.error(cliError.hint);
|
|
2774
|
+
process.exitCode = cliError.exitCode;
|
|
2775
|
+
}
|
|
2776
2776
|
//#endregion
|
|
2777
2777
|
export {};
|
|
2778
2778
|
|