From 707aeb231a498cdd2c961010dba6684708ce68d0 Mon Sep 17 00:00:00 2001 From: "farming-labs-docs[bot]" <276457176+farming-labs-docs[bot]@users.noreply.github.com> Date: Fri, 29 May 2026 22:15:32 +0000 Subject: [PATCH] docs(sqlite-studio): scaffold docs app Generated 19 docs files in one commit for a faster preview startup. Generated by Docs Cloud via https://docs.farming-labs.dev --- AGENTS.md | 21 ++ apps/docs/.gitignore | 14 ++ apps/docs/app/docs/configuration/page.mdx | 69 ++++++ apps/docs/app/docs/databases/page.mdx | 32 +++ apps/docs/app/docs/features/page.mdx | 19 ++ apps/docs/app/docs/installation/page.mdx | 70 ++++++ apps/docs/app/docs/page.mdx | 21 ++ apps/docs/app/docs/quickstart/page.mdx | 50 ++++ apps/docs/app/global.css | 2 + apps/docs/app/layout.tsx | 33 +++ apps/docs/app/page.tsx | 14 ++ apps/docs/docs.config.tsx | 28 +++ apps/docs/next-env.d.ts | 6 + apps/docs/next.config.ts | 12 + apps/docs/package.json | 29 +++ apps/docs/postcss.config.mjs | 7 + apps/docs/scripts/docs-cloud-vercel.mjs | 277 ++++++++++++++++++++++ apps/docs/tsconfig.json | 42 ++++ docs.json | 21 ++ 19 files changed, 767 insertions(+) create mode 100644 AGENTS.md create mode 100644 apps/docs/.gitignore create mode 100644 apps/docs/app/docs/configuration/page.mdx create mode 100644 apps/docs/app/docs/databases/page.mdx create mode 100644 apps/docs/app/docs/features/page.mdx create mode 100644 apps/docs/app/docs/installation/page.mdx create mode 100644 apps/docs/app/docs/page.mdx create mode 100644 apps/docs/app/docs/quickstart/page.mdx create mode 100644 apps/docs/app/global.css create mode 100644 apps/docs/app/layout.tsx create mode 100644 apps/docs/app/page.tsx create mode 100644 apps/docs/docs.config.tsx create mode 100644 apps/docs/next-env.d.ts create mode 100644 apps/docs/next.config.ts create mode 100644 apps/docs/package.json create mode 100644 apps/docs/postcss.config.mjs create mode 100644 apps/docs/scripts/docs-cloud-vercel.mjs create mode 100644 apps/docs/tsconfig.json create mode 100644 docs.json diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..40cf3b3 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,21 @@ +# Docs Maintenance Guide +Use this file as the handoff checklist for future edits to this documentation PR. +## Source Layout +- The docs source lives in `apps/docs`. +- `docs.json` is the Docs Cloud configuration for publishing, previews, and content roots. +- Keep every page grounded in README content, package metadata, source exports, CLI help, environment examples, or existing docs. +## Docs Routes +- /docs - Introduction +- /docs/installation - Installation +- /docs/quickstart - Quickstart +- /docs/configuration - Configuration +- /docs/databases - Databases +- /docs/features - Features +## Editing Guidelines +- Prefer reader-facing setup, usage, and troubleshooting notes over source inventories. +- Do not add commands, flags, environment variables, routes, imports, or framework names unless they are present in the repository. +- If you add or rename a page, keep its frontmatter title and description accurate and make sure the navigation ordering still includes it. +- Avoid analyzer language such as generated from, source evidence, implementation map, source surface, or detected in files. +## Verification +- Build the docs site with `cd apps/docs && node ./scripts/docs-cloud-vercel.mjs install && node ./scripts/docs-cloud-vercel.mjs build` before handing off a docs PR. +- Open `/docs` and at least one generated leaf page to confirm the sidebar and page content match the PR. diff --git a/apps/docs/.gitignore b/apps/docs/.gitignore new file mode 100644 index 0000000..aff9a25 --- /dev/null +++ b/apps/docs/.gitignore @@ -0,0 +1,14 @@ +node_modules +.env* +!.env.example +.next +.nuxt +.output +.svelte-kit +.astro +dist +build +.vercel +app/api/docs +app/docs/layout.tsx +mdx-components.tsx diff --git a/apps/docs/app/docs/configuration/page.mdx b/apps/docs/app/docs/configuration/page.mdx new file mode 100644 index 0000000..607ded0 --- /dev/null +++ b/apps/docs/app/docs/configuration/page.mdx @@ -0,0 +1,69 @@ +--- +title: "Configuration" +description: "Global CLI options, environment variables, and deployment configuration." +order: 0 +--- + +# Configuration + +SQL Studio is configured entirely through CLI flags and environment variables — there is no configuration file. Global options must be placed **before** the subcommand name. + +```bash +sql-studio [OPTIONS] [ARGS...] +``` + +## Global Options + +| Option | Short | Description | Default | Env Var | +|--------|-------|-------------|---------|---------| +| `--address` | `-a` | Address and port to bind to | `127.0.0.1:3030` | `ADDRESS` | +| `--timeout` | `-t` | Timeout for queries from the Query page | `5secs` | `TIMEOUT` | +| `--base-path` | `-b` | Base URL path for the UI (e.g. `/sql-studio`) | _(none)_ | `BASE_PATH` | +| `--no-browser` | | Don't open a browser window on startup | `false` | `NO_BROWSER` | +| `--no-shutdown` | | Hide the shutdown button in the UI | `false` | `NO_SHUTDOWN` | + +Each option has a corresponding environment variable, so you can configure SQL Studio without passing flags every time. + +## Timeout Format + +The `--timeout` value accepts human-readable durations. Examples: `5secs`, `30secs`, `1min`, `2min 30secs`. Use longer values when querying large datasets on slow connections. + +## Environment Variables + +You can set any option via its environment variable instead of a flag: + +```bash +ADDRESS=0.0.0.0:8080 +TIMEOUT=30secs +NO_BROWSER=true +NO_SHUTDOWN=true +BASE_PATH=/sql-studio +``` + +The `RUST_LOG` variable controls server-side log verbosity. Set it to `debug` or `info` to see request logs: + +```bash +RUST_LOG=info +``` + +## Examples + +Bind to all interfaces on port 8080 without opening a browser: + +```bash +sql-studio --address 0.0.0.0:8080 --no-browser sqlite ./app.db +``` + +Serve the UI under a sub-path (useful behind a reverse proxy): + +```bash +sql-studio --base-path /sql-studio postgres postgres://localhost:5432/mydb +``` + +Raise the query timeout for heavy analytical queries: + +```bash +sql-studio --timeout 60secs duckdb ./warehouse.duckdb +``` + +> **Note:** Options like `--no-browser` and `--no-shutdown` are particularly useful when running SQL Studio in Docker or behind a reverse proxy. See [Installation](/docs/installation) for the recommended Docker invocation. diff --git a/apps/docs/app/docs/databases/page.mdx b/apps/docs/app/docs/databases/page.mdx new file mode 100644 index 0000000..e8cec0f --- /dev/null +++ b/apps/docs/app/docs/databases/page.mdx @@ -0,0 +1,32 @@ +--- +title: "Databases" +description: "Overview of all databases supported by SQL Studio." +order: 1 +--- + +# Databases + +SQL Studio supports a wide range of databases and file formats. Each has its own subcommand with specific connection arguments. Global options like `--address` and `--timeout` go before the subcommand; connection arguments go after. + +```bash +sql-studio [OPTIONS] [ARGS...] +``` + +## Supported Databases + +| Database | Subcommand | Type | +|----------|------------|------| +| SQLite | `sqlite` | Local file | +| libSQL | `libsql` | Remote server | +| Local libSQL | `local-libsql` | Local file (libSQL driver) | +| PostgreSQL | `postgres` | Remote server | +| MySQL / MariaDB | `mysql` | Remote server | +| DuckDB | `duckdb` | Local file | +| Parquet | `parquet` | Local file | +| CSV | `csv` | Local file | +| ClickHouse | `clickhouse` | Remote server | +| Microsoft SQL Server | `mssql` | Remote server | + +> **Note:** DuckDB, Parquet, and CSV are not available in the `musl` static Linux build. Use the glibc Linux build, macOS, or Windows for those subcommands. + +See [Configuration](/docs/configuration) for the global options that apply to every subcommand. diff --git a/apps/docs/app/docs/features/page.mdx b/apps/docs/app/docs/features/page.mdx new file mode 100644 index 0000000..615fdd4 --- /dev/null +++ b/apps/docs/app/docs/features/page.mdx @@ -0,0 +1,19 @@ +--- +title: "Features" +description: "Explore the SQL Studio user interface." +order: 12 +--- + +# Features + +SQL Studio provides four main UI pages, all accessible from the left sidebar once the server is running. Every page works against any supported database — the same UI surfaces whether you're browsing a SQLite file, a PostgreSQL server, or a Parquet dataset. + +## Pages + +**Overview Dashboard** is the landing page. It shows database metadata — file name, version, size, creation and modification timestamps — plus summary statistics (table count, index count, trigger count, view count) and bar charts that break row counts and column counts down per table. + +**Table Explorer** lists every table in the sidebar with its row count. Selecting a table shows metadata cards, the `CREATE TABLE` statement, and a data grid that loads rows on demand via infinite scroll. + +**Query Editor** gives you a full Monaco editor (the same engine as VS Code) with SQL syntax highlighting and IntelliSense autocomplete powered by your live database schema. Write your query and run it to see paginated results. + +**ERD Viewer** renders an interactive entity-relationship diagram with tables as nodes, columns annotated with types and constraints, and foreign-key edges connecting related tables. You can pan, zoom, and drag nodes to rearrange the layout. diff --git a/apps/docs/app/docs/installation/page.mdx b/apps/docs/app/docs/installation/page.mdx new file mode 100644 index 0000000..327020c --- /dev/null +++ b/apps/docs/app/docs/installation/page.mdx @@ -0,0 +1,70 @@ +--- +title: "Installation" +description: "Install and configure SQL Studio." +order: 10 +--- + +# Installation + +SQL Studio ships as a self-contained binary with no runtime dependencies. Pick the method that suits your platform. + +## Shell Script (macOS and Linux) + +The fastest way to get the latest release: + +```bash +curl --proto '=https' --tlsv1.2 -LsSf https://github.com/frectonz/sql-studio/releases/download/0.1.51/sql-studio-installer.sh | sh +``` + +After the script completes, `sql-studio` is on your `PATH` and ready to use. + +## PowerShell (Windows) + +Open a PowerShell terminal and run the installer from the same releases page. The installer places the binary in a location already on your `PATH`. + +## Nix + +SQL Studio is available in Nixpkgs. Add it to your environment or run it directly with `nix run`. + +## Docker + +A Docker image is published on Docker Hub as `frectonz/sql-studio`. The recommended flags when running in a container are `--no-browser` (no desktop browser inside a container), `--no-shutdown` (keep the server alive), and `--address=0.0.0.0:3030` (bind to all interfaces so the host can reach it): + +```bash +docker run -p 3030:3030 frectonz/sql-studio /bin/sql-studio \ + --no-browser \ + --no-shutdown \ + --address=0.0.0.0:3030 \ + postgres \ + postgres://localhost:5432/mydb +``` + +## Building from Source + +You need Rust (via `cargo`) and Node.js installed. The UI is compiled separately and then embedded in the Rust binary at build time. + +```bash +git clone git@github.com:frectonz/sql-studio.git +cd sql-studio +cd ui +npm install +npm run build +cd .. +cargo build --release +``` + +The compiled binary lands at `target/release/sql-studio`. + +> **Note:** DuckDB-backed formats (DuckDB, Parquet, CSV) are not available in the `musl` static Linux build due to DuckDB's compatibility requirements. Use the glibc Linux build, macOS, or Windows for those subcommands. + +## Updating + +If you installed via the shell script or the PowerShell installer, re-run the same install command to pull the latest release. Docker users can `docker pull frectonz/sql-studio` to get the newest image. + +Once installed, confirm everything works by running: + +```bash +sql-studio --help +``` + +You should see the list of subcommands and global options. From here, jump to the [Quickstart](/docs/quickstart) to launch your first session. diff --git a/apps/docs/app/docs/page.mdx b/apps/docs/app/docs/page.mdx new file mode 100644 index 0000000..b6f7590 --- /dev/null +++ b/apps/docs/app/docs/page.mdx @@ -0,0 +1,21 @@ +--- +title: "Introduction" +description: "Single binary, single command SQL database explorer for SQLite, PostgreSQL, MySQL, DuckDB, ClickHouse, MSSQL, Parquet, CSV, and more." +order: 0 +--- + +# Introduction + +SQL Studio is a single-binary, single-command web-based SQL database explorer. Point it at any supported database and you instantly get a rich browser UI for browsing schemas, exploring tables, running queries, and visualizing relationships — no configuration files, no installation wizards, no daemon to manage. + +The entire UI is statically embedded in the binary. You run one command, a browser window opens, and you're looking at your data. When you're done, click the shutdown button or press `Ctrl+C`. + +SQL Studio supports SQLite, libSQL (Turso), PostgreSQL, MySQL/MariaDB, DuckDB, ClickHouse, Microsoft SQL Server, Parquet files, and CSV files. Every database has its own subcommand: + +```bash +sql-studio sqlite my-database.db +sql-studio postgres postgres://localhost:5432/mydb +sql-studio parquet data.parquet +``` + +If you're ready to install, head to [Installation](/docs/installation). To run SQL Studio for the first time in under a minute, see the [Quickstart](/docs/quickstart). Once it's running, the [Features](/docs/features) section explains every UI page. To connect a specific database, the [Databases](/docs/databases) section has a dedicated page for each supported engine. diff --git a/apps/docs/app/docs/quickstart/page.mdx b/apps/docs/app/docs/quickstart/page.mdx new file mode 100644 index 0000000..300355f --- /dev/null +++ b/apps/docs/app/docs/quickstart/page.mdx @@ -0,0 +1,50 @@ +--- +title: "Quickstart" +description: "Run SQL Studio for the first time." +order: 20 +--- + +# Quickstart + +SQL Studio ships with a built-in sample SQLite database so you can try the full UI without any setup. Install the binary (see [Installation](/docs/installation)), then run: + +```bash +sql-studio sqlite preview +``` + +This launches SQL Studio against a built-in sample database and opens `http://127.0.0.1:3030` in your default browser. You'll land on the Overview page with database metadata, table counts, and row-count bar charts — no file required. + +## Use Your Own Database + +Point SQL Studio at a local SQLite file you already have: + +```bash +sql-studio sqlite ./my-app.db +``` + +Or connect to a running PostgreSQL server: + +```bash +sql-studio postgres postgres://localhost:5432/mydb +``` + +The subcommand determines the driver. Every other supported database works the same way — see [Databases](/docs/databases) for the full list and connection string formats. + +## What You'll See + +Once the browser opens you'll find four pages in the left sidebar: + +- **Overview** — database name, version, size, and summary statistics +- **Tables** — row counts, column definitions, creation SQL, and an infinite-scroll data grid for every table +- **Query** — a Monaco-powered SQL editor with IntelliSense autocomplete backed by your live schema +- **Schema** — an interactive ERD diagram showing tables, columns, and foreign-key relationships + +## Controlling the Server + +By default SQL Studio binds to `127.0.0.1:3030` and opens a browser window automatically. You can change this with global flags placed *before* the subcommand: + +```bash +sql-studio --address 0.0.0.0:8080 --no-browser sqlite ./my-app.db +``` + +See [Configuration](/docs/configuration) for the full list of options and their equivalent environment variables. diff --git a/apps/docs/app/global.css b/apps/docs/app/global.css new file mode 100644 index 0000000..3c489ba --- /dev/null +++ b/apps/docs/app/global.css @@ -0,0 +1,2 @@ +@import "tailwindcss"; +@import "@farming-labs/theme/colorful/css"; diff --git a/apps/docs/app/layout.tsx b/apps/docs/app/layout.tsx new file mode 100644 index 0000000..8a31ca5 --- /dev/null +++ b/apps/docs/app/layout.tsx @@ -0,0 +1,33 @@ +import type { Metadata } from "next"; +import { Geist, Geist_Mono } from "next/font/google"; +import { RootProvider } from "@farming-labs/theme"; +import docsConfig from "../docs.config"; +import "./global.css"; + +const geistSans = Geist({ + variable: "--fd-font-sans", + subsets: ["latin"], +}); + +const geistMono = Geist_Mono({ + variable: "--fd-font-mono", + subsets: ["latin"], +}); + +export const metadata: Metadata = { + title: { + default: "Docs", + template: docsConfig.metadata?.titleTemplate ?? "%s", + }, + description: docsConfig.metadata?.description, +}; + +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + + {children} + + + ); +} diff --git a/apps/docs/app/page.tsx b/apps/docs/app/page.tsx new file mode 100644 index 0000000..ea8d6ca --- /dev/null +++ b/apps/docs/app/page.tsx @@ -0,0 +1,14 @@ +import Link from "next/link"; + +const title = "SQL Studio"; +const description = "Single binary, single command SQL database explorer for SQLite, PostgreSQL, MySQL, DuckDB, ClickHouse, MSSQL, Parquet, CSV, and more."; + +export default function HomePage() { + return ( +
+

{title}

+

{description}

+ Open docs +
+ ); +} diff --git a/apps/docs/docs.config.tsx b/apps/docs/docs.config.tsx new file mode 100644 index 0000000..81b510d --- /dev/null +++ b/apps/docs/docs.config.tsx @@ -0,0 +1,28 @@ +import { defineDocs } from "@farming-labs/docs"; +import { colorful } from "@farming-labs/theme/colorful"; + +export default defineDocs({ + entry: "docs", + theme: colorful(), + ordering: [ + { + "slug": "quickstart" + }, + { + "slug": "installation" + }, + { + "slug": "configuration" + }, + { + "slug": "databases" + }, + { + "slug": "features" + } + ], + metadata: { + titleTemplate: "%s – Docs", + description: "Generated by @farming-labs/docs Cloud", + }, +}); diff --git a/apps/docs/next-env.d.ts b/apps/docs/next-env.d.ts new file mode 100644 index 0000000..9edff1c --- /dev/null +++ b/apps/docs/next-env.d.ts @@ -0,0 +1,6 @@ +/// +/// +import "./.next/types/routes.d.ts"; + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/apps/docs/next.config.ts b/apps/docs/next.config.ts new file mode 100644 index 0000000..948bed4 --- /dev/null +++ b/apps/docs/next.config.ts @@ -0,0 +1,12 @@ +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { withDocs } from "@farming-labs/next/config"; + +const appDir = dirname(fileURLToPath(import.meta.url)); +const root = join(appDir, "../.."); + +export default withDocs({ + turbopack: { + root, + }, +}); diff --git a/apps/docs/package.json b/apps/docs/package.json new file mode 100644 index 0000000..a98fc7f --- /dev/null +++ b/apps/docs/package.json @@ -0,0 +1,29 @@ +{ + "name": "@docs-cloud/generated-docs", + "private": true, + "packageManager": "npm@11.6.2", + "scripts": { + "dev": "next dev --turbopack", + "build": "next build --turbopack", + "start": "next start" + }, + "dependencies": { + "@farming-labs/docs": "latest", + "@farming-labs/next": "latest", + "@farming-labs/theme": "latest", + "next": "16.2.3", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "zod": "^4.1.0" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4.1.18", + "@types/mdx": "^2.0.13", + "@types/node": "^22.10.0", + "@types/react": "^19.2.0", + "@types/react-dom": "^19.2.0", + "postcss": "^8.5.6", + "tailwindcss": "^4.1.18", + "typescript": "^5.9.3" + } +} diff --git a/apps/docs/postcss.config.mjs b/apps/docs/postcss.config.mjs new file mode 100644 index 0000000..61e3684 --- /dev/null +++ b/apps/docs/postcss.config.mjs @@ -0,0 +1,7 @@ +const config = { + plugins: { + "@tailwindcss/postcss": {}, + }, +}; + +export default config; diff --git a/apps/docs/scripts/docs-cloud-vercel.mjs b/apps/docs/scripts/docs-cloud-vercel.mjs new file mode 100644 index 0000000..95ba44e --- /dev/null +++ b/apps/docs/scripts/docs-cloud-vercel.mjs @@ -0,0 +1,277 @@ +#!/usr/bin/env node +import { existsSync, readFileSync } from "node:fs"; +import { spawnSync } from "node:child_process"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +const appRoot = resolve(fileURLToPath(new URL("..", import.meta.url))); +const repoRoot = findRepoRoot(appRoot); +const action = process.argv[2] || "build"; + +function fileExists(path) { + return existsSync(path); +} + +function readJson(path) { + try { + return JSON.parse(readFileSync(path, "utf8")); + } catch { + return null; + } +} + +function readPackageJson(directory) { + const packageJson = readJson(join(directory, "package.json")); + return packageJson && typeof packageJson === "object" ? packageJson : null; +} + +function findRepoRoot(startDirectory) { + let current = startDirectory; + + while (true) { + if (fileExists(join(current, "docs.json")) || fileExists(join(current, ".git"))) { + return current; + } + + const parent = dirname(current); + if (parent === current) { + return startDirectory; + } + + current = parent; + } +} + +function packageManagerFromPackageJson(directory) { + const packageJson = readPackageJson(directory); + const packageManager = typeof packageJson?.packageManager === "string" + ? packageJson.packageManager + : ""; + + if (packageManager.startsWith("bun@")) return "bun"; + if (packageManager.startsWith("pnpm@")) return "pnpm"; + if (packageManager.startsWith("yarn@")) return "yarn"; + if (packageManager.startsWith("npm@")) return "npm"; + return null; +} + +function detectPackageManager(directory) { + const declared = packageManagerFromPackageJson(directory); + if (declared) return declared; + + if (fileExists(join(directory, "pnpm-lock.yaml"))) return "pnpm"; + if (fileExists(join(directory, "bun.lock")) || fileExists(join(directory, "bun.lockb"))) return "bun"; + if (fileExists(join(directory, "package-lock.json")) || fileExists(join(directory, "npm-shrinkwrap.json"))) return "npm"; + if (fileExists(join(directory, "yarn.lock"))) return "yarn"; + return "pnpm"; +} + +function hasPackageJson(directory) { + return fileExists(join(directory, "package.json")); +} + +function hasLockfile(directory, packageManager) { + if (packageManager === "pnpm") return fileExists(join(directory, "pnpm-lock.yaml")); + if (packageManager === "bun") return fileExists(join(directory, "bun.lock")) || fileExists(join(directory, "bun.lockb")); + if (packageManager === "npm") return fileExists(join(directory, "package-lock.json")) || fileExists(join(directory, "npm-shrinkwrap.json")); + if (packageManager === "yarn") return fileExists(join(directory, "yarn.lock")); + return false; +} + +function commandExists(command) { + const result = spawnSync(command, ["--version"], { stdio: "ignore" }); + return result.status === 0; +} + +function enableCorepack() { + if (commandExists("corepack")) { + spawnSync("corepack", ["enable"], { cwd: repoRoot, stdio: "ignore" }); + } +} + +function packageManagerSpec(directory, packageManager) { + const packageJson = readPackageJson(directory); + const declared = typeof packageJson?.packageManager === "string" + ? packageJson.packageManager + : ""; + + if (declared.startsWith(packageManager + "@")) { + return declared; + } + + if (packageManager === "pnpm") return "pnpm@10.9.0"; + if (packageManager === "yarn") return "yarn@1.22.22"; + return packageManager; +} + +function resolvePackageManagerCommand(directory, packageManager) { + if (commandExists(packageManager)) { + return { + command: packageManager, + argsPrefix: [], + }; + } + + if (packageManager === "pnpm" || packageManager === "yarn") { + enableCorepack(); + + if (commandExists(packageManager)) { + return { + command: packageManager, + argsPrefix: [], + }; + } + + const spec = packageManagerSpec(directory, packageManager); + if (commandExists("corepack")) { + const corepackProbe = spawnSync("corepack", [spec, "--version"], { + cwd: directory, + stdio: "ignore", + }); + + if (corepackProbe.status === 0) { + return { + command: "corepack", + argsPrefix: [spec], + }; + } + } + + if (packageManager === "pnpm" && commandExists("npx")) { + return { + command: "npx", + argsPrefix: ["--yes", spec], + }; + } + } + + throw new Error( + "Docs Cloud detected " + packageManager + " but could not run it directly, through Corepack, or through npx.", + ); +} + +function runCommand(command, args, cwd, options = {}) { + console.log("[docs-cloud] " + command + " " + args.join(" ") + " (" + cwd + ")"); + const result = spawnSync(command, args, { + cwd, + stdio: "inherit", + env: { + ...process.env, + CI: process.env.CI || "1", + }, + }); + + if (result.error) { + throw result.error; + } + + if (result.status !== 0) { + if (options.exitOnFailure === false) { + return result.status || 1; + } + + process.exit(result.status || 1); + } + + return 0; +} + +function runPackageManager(directory, packageManager, args, options = {}) { + const resolved = resolvePackageManagerCommand(directory, packageManager); + return runCommand(resolved.command, [...resolved.argsPrefix, ...args], directory, options); +} + +function installArgs(directory, packageManager) { + const frozen = hasLockfile(directory, packageManager); + + if (packageManager === "pnpm") { + return ["install", frozen ? "--frozen-lockfile" : "--no-frozen-lockfile"]; + } + + if (packageManager === "bun") { + return frozen ? ["install", "--frozen-lockfile"] : ["install"]; + } + + if (packageManager === "npm") { + return frozen ? ["ci"] : ["install"]; + } + + if (packageManager === "yarn") { + const packageJson = readPackageJson(directory); + const packageManagerValue = typeof packageJson?.packageManager === "string" + ? packageJson.packageManager + : ""; + const isModernYarn = /^yarn@[3-9]/.test(packageManagerValue) || fileExists(join(directory, ".yarnrc.yml")); + return frozen + ? ["install", isModernYarn ? "--immutable" : "--frozen-lockfile"] + : ["install"]; + } + + return ["install"]; +} + +function relaxedInstallArgs(packageManager) { + if (packageManager === "pnpm") return ["install", "--no-frozen-lockfile"]; + if (packageManager === "bun") return ["install"]; + if (packageManager === "npm") return ["install"]; + if (packageManager === "yarn") return ["install"]; + return ["install"]; +} + +function scriptArgs(packageManager, scriptName) { + if (packageManager === "npm") return ["run", scriptName]; + if (packageManager === "yarn") return [scriptName]; + return ["run", scriptName]; +} + +function installProject(directory, label) { + if (!hasPackageJson(directory)) { + console.log("[docs-cloud] skipping " + label + " install; no package.json found"); + return; + } + + const packageManager = detectPackageManager(directory); + const args = installArgs(directory, packageManager); + const status = runPackageManager(directory, packageManager, args, { exitOnFailure: false }); + if (status === 0) { + return; + } + + const relaxedArgs = relaxedInstallArgs(packageManager); + if (args.join(" ") !== relaxedArgs.join(" ")) { + console.log("[docs-cloud] frozen install failed; retrying with " + relaxedArgs.join(" ")); + runPackageManager(directory, packageManager, relaxedArgs); + return; + } + + process.exit(status); +} + +function runProjectScript(directory, scriptName) { + const packageJson = readPackageJson(directory); + if (!packageJson?.scripts || typeof packageJson.scripts[scriptName] !== "string") { + throw new Error("The docs app package.json does not define a " + scriptName + " script."); + } + + const packageManager = detectPackageManager(directory); + runPackageManager(directory, packageManager, scriptArgs(packageManager, scriptName)); +} + +function install() { + console.log("[docs-cloud] repo root: " + repoRoot); + console.log("[docs-cloud] docs root: " + appRoot); + + installProject(repoRoot, "repository"); + + if (repoRoot !== appRoot) { + installProject(appRoot, "docs app"); + } +} + +if (action === "install") { + install(); +} else if (action === "build" || action === "dev") { + runProjectScript(appRoot, action); +} else { + throw new Error("Unknown Docs Cloud Vercel action: " + action); +} diff --git a/apps/docs/tsconfig.json b/apps/docs/tsconfig.json new file mode 100644 index 0000000..247f602 --- /dev/null +++ b/apps/docs/tsconfig.json @@ -0,0 +1,42 @@ +{ + "compilerOptions": { + "target": "ES2017", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "react-jsx", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "baseUrl": ".", + "paths": { + "@/*": [ + "./*" + ] + } + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + ".next/dev/types/**/*.ts" + ], + "exclude": [ + "node_modules" + ] +} diff --git a/docs.json b/docs.json new file mode 100644 index 0000000..1ae4b44 --- /dev/null +++ b/docs.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://docs.farming-labs.dev/schema/docs.json", + "version": 1, + "docs": { + "mode": "framework", + "runtime": "nextjs", + "root": "apps/docs" + }, + "cloud": { + "apiKey": { + "env": "DOCS_CLOUD_API_KEY" + }, + "preview": { + "enabled": true + }, + "publish": { + "mode": "draft-pr", + "baseBranch": "main" + } + } +}