From 89f2a292c83fcbc4b438645873237e8061aa5439 Mon Sep 17 00:00:00 2001 From: Gordon Farquharson Date: Tue, 7 Apr 2026 14:35:38 +0100 Subject: [PATCH 1/2] wip - plugin-integration --- .gitignore | 3 + AGENTS.md | 37 +++++ CLAUDE.md | 6 + context/CHANGELOG.md | 11 ++ context/CONTEXT_INDEX.md | 5 +- examples/README.md | 16 +- fastedge-plugin-source/check-copilot-sync.sh | 30 ++-- fastedge-plugin-source/generate-docs.sh | 7 +- fastedge-plugin-source/generate-llms-txt.sh | 77 +++++++--- fastedge-plugin-source/manifest.json | 152 ++++++++++++++++++- llms.txt | 2 +- 11 files changed, 291 insertions(+), 55 deletions(-) create mode 100644 AGENTS.md diff --git a/.gitignore b/.gitignore index 268f6d4..091e5af 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,6 @@ yarn-error.log # Using pnpm package-lock.json + +# FastEdge debugger artifacts +**/.fastedge-debug/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..bdd2692 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,37 @@ +--- +doc_type: policy +audience: bot +lang: en +tags: ['ai-agents', 'rules', 'critical', 'codex'] +last_modified: 2026-04-02T00:00:00Z +copyright: '© 2026 gcore.com' +--- + +# RULES FOR AI AGENTS + +TL;DR: Keep command output short. Do not take actions unless asked. Do not waste tokens on +experiments. Do only what is asked. + +# COMMUNICATION STYLE + +- Use English by default; if the user writes in another language, use that language +- Use an informal tone, avoid formal business language +- Question ideas and suggest alternatives — do not just agree with everything +- Think for yourself instead of agreeing to be polite + +# INVARIANTS + +- NEVER do anything beyond the assigned task +- NEVER change code that was not asked to change +- NEVER "improve" or "optimize" without a clear request +- NEVER use scripts for mass code replacements +- NEVER make architecture decisions on your own +- ALWAYS keep command output short — every extra line = wasted tokens +- ALWAYS think before acting — do not repeat checks, remember context +- ALWAYS ask an expert when the solution is not clear +- ALWAYS tell apart an observation from an action request: observation ("works oddly") → discuss, DO + NOT fix request ("fix this") → act + +# PROJECT CONTEXT + +see CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md index 61ac6ca..5204f38 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,5 +1,11 @@ # AI Agent Instructions for FastEdge JS SDK +## Governance (REQUIRED) + +Read `AGENTS.md` for company-wide agent rules. These are mandatory and override any conflicting behavior. Key rules: never go beyond the assigned task, never change code that was not asked to change, never "improve" or "optimize" without a clear request, always distinguish observations from action requests. + +--- + ## CRITICAL: Read Smart, Not Everything **DO NOT read all context files upfront.** This repository uses a **discovery-based context system** to minimize token usage while maximizing effectiveness. diff --git a/context/CHANGELOG.md b/context/CHANGELOG.md index 84aac9d..904f939 100644 --- a/context/CHANGELOG.md +++ b/context/CHANGELOG.md @@ -5,6 +5,17 @@ When this file grows large, use grep to search — don't read linearly. --- +## [2026-03-31] — Examples and Terminology Conventions + +### Overview +Documented conventions for example maintenance and user-facing terminology. + +### Decisions +- Every example must have its own `README.md` and an entry in the top-level `examples/README.md` index +- In all user-facing documentation (READMEs, docs), always use "environment variables" — never "dictionary variables". The `dictionary` package is an internal implementation detail for accessing environment variables on the platform, not a user-facing concept + +--- + ## [2026-03-26] — Examples Consolidation ### Overview diff --git a/context/CONTEXT_INDEX.md b/context/CONTEXT_INDEX.md index 6bc1a63..eed715f 100644 --- a/context/CONTEXT_INDEX.md +++ b/context/CONTEXT_INDEX.md @@ -76,10 +76,13 @@ 2. Check `github-pages/src/content/docs/reference/` for user-facing docs 3. Run `pnpm run build:types` to verify -### Adding a New Example +### Adding or Editing an Example 1. Browse `examples/` for an existing example similar to your target 2. Each example is standalone with its own `package.json` 3. Install SDK via `npm install --save-dev @gcoredev/fastedge-sdk-js` +4. **Every example MUST have its own `README.md`** explaining what it does +5. **Every example MUST have an entry in `examples/README.md`** (the top-level index) +6. **Terminology**: In READMEs, always refer to "environment variables" — never "dictionary variables". `dictionary` is the internal package name for accessing environment variables, not a user-facing concept ### Changing the Build System 1. Read `development/BUILD_SYSTEM.md` diff --git a/examples/README.md b/examples/README.md index 51fda5a..312cf75 100644 --- a/examples/README.md +++ b/examples/README.md @@ -6,14 +6,14 @@ network using ## Getting Started Examples -| Example | Description | -| ------------------------------------------------------------- | ------------------------------------------------------------------- | -| [hello-world](./hello-world/) | Simplest request handler — returns the request URL | -| [downstream-fetch](./downstream-fetch/) | Fetch from a downstream HTTP origin | -| [downstream-modify-response](./downstream-modify-response/) | Fetch downstream and transform the response | -| [headers](./headers/) | Header manipulation using environment variables | -| [kv-store-basic](./kv-store-basic/) | Simple KV Store get operation | -| [variables-and-secrets](./variables-and-secrets/) | Read environment variables and secrets | +| Example | Description | +| ----------------------------------------------------------- | -------------------------------------------------- | +| [hello-world](./hello-world/) | Simplest request handler — returns the request URL | +| [downstream-fetch](./downstream-fetch/) | Fetch from a downstream HTTP origin | +| [downstream-modify-response](./downstream-modify-response/) | Fetch downstream and transform the response | +| [headers](./headers/) | Header manipulation using environment variables | +| [kv-store-basic](./kv-store-basic/) | Simple KV Store get operation | +| [variables-and-secrets](./variables-and-secrets/) | Read environment variables and secrets | ## Full Examples diff --git a/fastedge-plugin-source/check-copilot-sync.sh b/fastedge-plugin-source/check-copilot-sync.sh index 03f439b..28d1153 100755 --- a/fastedge-plugin-source/check-copilot-sync.sh +++ b/fastedge-plugin-source/check-copilot-sync.sh @@ -23,8 +23,16 @@ fi # --- Check 1: manifest doc files appear in the mapping table --- # Extract doc/schema paths that appear in mapping table rows (lines starting with '|') -# grep returns exit code 1 when no lines match, which would abort under set -euo pipefail -mapping_table_docs=$(grep '^|' "$COPILOT" | grep -oP '`((?:docs|schemas)/[^`]+)`' | tr -d '`' | sort -u || true) +# Use POSIX-compatible awk instead of grep -P so this works on macOS/BSD grep too +mapping_table_docs=$(awk ' + /^\|/ { + line = $0 + while (match(line, /`(docs|schemas)\/[^`]+`/)) { + print substr(line, RSTART + 1, RLENGTH - 2) + line = substr(line, RSTART + RLENGTH) + } + } +' "$COPILOT" | sort -u) if [ -z "$mapping_table_docs" ]; then echo "FAIL: No doc/schema paths found in $COPILOT mapping table — expected backticked docs/ or schemas/ paths in table rows" @@ -32,18 +40,12 @@ if [ -z "$mapping_table_docs" ]; then fi if [ -f "$MANIFEST" ]; then - doc_files=$(node -e " - const m = require('./$MANIFEST'); - const files = new Set(); - for (const src of Object.values(m.sources)) { - for (const f of src.files) { - if (f.startsWith('docs/') || f.startsWith('schemas/')) { - files.add(f); - } - } - } - [...files].sort().forEach(f => console.log(f)); - ") + if ! command -v jq &>/dev/null; then + echo "Error: jq is required but not installed" + exit 1 + fi + + doc_files=$(jq -r '.sources[].files[]' "$MANIFEST" | grep -E '^(docs|schemas)/' | sort -u) missing=() for doc in $doc_files; do diff --git a/fastedge-plugin-source/generate-docs.sh b/fastedge-plugin-source/generate-docs.sh index a278eaf..d2162fd 100755 --- a/fastedge-plugin-source/generate-docs.sh +++ b/fastedge-plugin-source/generate-docs.sh @@ -54,7 +54,7 @@ cleanup() { # Clean up temp files left by killed generate_file subshells # Must happen BEFORE kill -- -$$ which kills this script too - rm -f "$DOCS_DIR"/.[A-Z]*.[a-zA-Z0-9]* 2>/dev/null || true + rm -f "$DOCS_DIR"/.*.md.[a-zA-Z0-9]* 2>/dev/null || true rm -f "$INTERRUPT_FLAG" # Belt-and-suspenders: kill entire process group (including this script) @@ -137,7 +137,7 @@ fi mkdir -p "$DOCS_DIR" # Clean up stale temp files from previous interrupted runs -rm -f "$DOCS_DIR"/.[A-Z]*.[a-zA-Z0-9]* 2>/dev/null || true +rm -f "$DOCS_DIR"/.*.md.[a-zA-Z0-9]* 2>/dev/null || true generate_file() { local target="$1" @@ -209,8 +209,7 @@ $(cat "$full_path") # Existing Content for docs/$target Use this as the baseline. Preserve all accurate content and manual additions. Only change what is incorrect, incomplete, or missing per the source code. Keep sections not covered by the instructions above. Apply table formatting rules to all tables. - - + $existing_doc " diff --git a/fastedge-plugin-source/generate-llms-txt.sh b/fastedge-plugin-source/generate-llms-txt.sh index 5ccb7e3..616dbd3 100755 --- a/fastedge-plugin-source/generate-llms-txt.sh +++ b/fastedge-plugin-source/generate-llms-txt.sh @@ -1,22 +1,20 @@ #!/usr/bin/env bash set -euo pipefail -# Generate llms.txt from docs/ contents and package.json +# Generate llms.txt from docs/ contents # # Produces an llms.txt file at the repo root that indexes all documentation # files in docs/. This follows the llms.txt proposal (llmstxt.org) to help # LLM agents discover and navigate package documentation. # # Usage: -# ./fastedge-plugin-source/generate-llms-txt.sh +# ./fastedge-plugin-source/generate-llms-txt.sh # standalone +# ./fastedge-plugin-source/generate-docs.sh # calls this automatically after a full run # -# Setup: -# 1. Copy this file to /fastedge-plugin-source/generate-llms-txt.sh -# 2. chmod +x fastedge-plugin-source/generate-llms-txt.sh -# 3. Called automatically by generate-docs.sh after a full generation run -# -# Requirements: jq, bash 4+ +# Requirements: bash 4+, jq (only if package.json is the name source) # No customization needed — package name and docs are discovered at runtime. +# Supports: package.json (Node), Cargo.toml (Rust), pyproject.toml (Python), +# or falls back to the directory name. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" @@ -25,11 +23,6 @@ OUTPUT="$REPO_ROOT/llms.txt" # --- Validate prerequisites --- -if [ ! -f "$REPO_ROOT/package.json" ]; then - echo "Error: package.json not found in $REPO_ROOT" - exit 1 -fi - if [ ! -d "$DOCS_DIR" ]; then echo "Error: docs/ directory not found" exit 1 @@ -40,14 +33,36 @@ if [ ! -f "$DOCS_DIR/INDEX.md" ]; then exit 1 fi -if ! command -v jq &>/dev/null; then - echo "Error: jq is required but not installed" - exit 1 -fi +# --- Extract package name (language-agnostic) --- +# Tries in order: package.json (Node), Cargo.toml (Rust), pyproject.toml (Python), dirname fallback + +detect_package_name() { + if [ -f "$REPO_ROOT/package.json" ]; then + if command -v jq &>/dev/null; then + jq -r '.name' "$REPO_ROOT/package.json" + return + fi + fi -# --- Extract metadata --- + if [ -f "$REPO_ROOT/Cargo.toml" ]; then + sed -n '/^\[package\]/,/^\[/{ s/^name *= *"\(.*\)"/\1/p; }' "$REPO_ROOT/Cargo.toml" | head -1 + return + fi -PACKAGE_NAME=$(jq -r '.name' "$REPO_ROOT/package.json") + if [ -f "$REPO_ROOT/pyproject.toml" ]; then + sed -n '/^\[project\]/,/^\[/{ s/^name *= *"\(.*\)"/\1/p; }' "$REPO_ROOT/pyproject.toml" | head -1 + return + fi + + basename "$REPO_ROOT" +} + +PACKAGE_NAME=$(detect_package_name) + +if [ -z "$PACKAGE_NAME" ]; then + PACKAGE_NAME=$(basename "$REPO_ROOT") + echo "Warning: could not detect package name, using directory name: $PACKAGE_NAME" +fi # Extract summary from INDEX.md line 3 (expected format: blockquote or plain text after H1 + blank line) # Strips leading "> " if present @@ -68,18 +83,30 @@ fi echo "## Documentation" echo "" - # INDEX.md first — it's the entry point - index_heading=$(head -1 "$DOCS_DIR/INDEX.md" | sed 's/^#\+ //') - echo "- [$index_heading](docs/INDEX.md)" + # Curated order: INDEX first (entry point), then quickstart, then rest alphabetically. + # This keeps the most useful docs near the top rather than relying on glob order + # (which puts lowercase filenames like quickstart.md last). + PRIORITY_FILES=("INDEX.md" "quickstart.md") + + for pfile in "${PRIORITY_FILES[@]}"; do + if [ -f "$DOCS_DIR/$pfile" ]; then + heading=$(head -1 "$DOCS_DIR/$pfile" | sed 's/^#\+ //') + [ -z "$heading" ] && heading="${pfile%.md}" + echo "- [$heading](docs/$pfile)" + fi + done - # Remaining docs alphabetically, skip INDEX.md + # Remaining docs alphabetically, skip priority files for doc in "$DOCS_DIR"/*.md; do filename=$(basename "$doc") - [ "$filename" = "INDEX.md" ] && continue + skip=false + for pfile in "${PRIORITY_FILES[@]}"; do + [ "$filename" = "$pfile" ] && skip=true && break + done + [ "$skip" = true ] && continue heading=$(head -1 "$doc" | sed 's/^#\+ //') if [ -z "$heading" ]; then - # Fallback: use filename without extension heading="${filename%.md}" fi diff --git a/fastedge-plugin-source/manifest.json b/fastedge-plugin-source/manifest.json index ef32d8a..e648495 100644 --- a/fastedge-plugin-source/manifest.json +++ b/fastedge-plugin-source/manifest.json @@ -1,7 +1,7 @@ { "$schema": "https://fastedge-plugin-source/manifest/v1", "repo_id": "FastEdge-sdk-js", - "version": "1.0.0", + "version": "1.1.0", "sources": { "sdk-api": { "files": [ @@ -38,6 +38,104 @@ ], "required": true, "description": "Installation, first build, basic usage patterns with code examples" + }, + + "hello-world-blueprint": { + "files": [ + "examples/hello-world/src/index.js", + "examples/hello-world/package.json" + ], + "required": true, + "description": "Hello World example — base skeleton blueprint extraction (HTTP JS/TS)" + }, + + "kv-store-blueprint": { + "files": [ + "examples/kv-store/src/index.ts", + "examples/kv-store/src/utils.ts", + "examples/kv-store/package.json", + "examples/kv-store/tsconfig.json" + ], + "required": true, + "description": "KV Store example — scaffold blueprint extraction" + }, + "kv-store-pattern": { + "files": [ + "examples/kv-store/src/index.ts", + "examples/kv-store/src/utils.ts", + "examples/kv-store/package.json", + "examples/kv-store/tsconfig.json" + ], + "required": true, + "description": "KV Store example — docs pattern extraction" + }, + + "ab-testing-blueprint": { + "files": [ + "examples/ab-testing/src/index.js", + "examples/ab-testing/package.json" + ], + "required": true, + "description": "A/B Testing example — scaffold blueprint extraction" + }, + "ab-testing-pattern": { + "files": [ + "examples/ab-testing/src/index.js", + "examples/ab-testing/package.json" + ], + "required": true, + "description": "A/B Testing example — docs pattern extraction" + }, + + "fetch-blueprint": { + "files": [ + "examples/downstream-fetch/src/index.js", + "examples/downstream-fetch/package.json" + ], + "required": true, + "description": "Downstream Fetch example — scaffold blueprint extraction" + }, + "fetch-pattern": { + "files": [ + "examples/downstream-fetch/src/index.js", + "examples/downstream-fetch/package.json" + ], + "required": true, + "description": "Downstream Fetch example — docs pattern extraction" + }, + + "headers-blueprint": { + "files": [ + "examples/headers/src/index.js", + "examples/headers/package.json" + ], + "required": true, + "description": "Headers manipulation example — scaffold blueprint extraction" + }, + "headers-pattern": { + "files": [ + "examples/headers/src/index.js", + "examples/headers/package.json" + ], + "required": true, + "description": "Headers manipulation example — docs pattern extraction" + }, + + "geo-redirect-blueprint": { + "files": [ + "examples/geo-redirect/src/index.js", + "examples/geo-redirect/package.json" + ], + "required": true, + "description": "Geo Redirect example — scaffold blueprint extraction" + }, + "geo-redirect-pattern": { + "files": [ + "examples/geo-redirect/src/index.js", + "examples/geo-redirect/package.json" + ], + "required": true, + "description": "Geo Redirect example — docs pattern extraction" } }, "target_mapping": { @@ -60,10 +158,60 @@ "quickstart": { "reference_file": "plugins/gcore-fastedge/skills/fastedge-docs/reference/quickstart.md", "section": null + }, + + "hello-world-blueprint": { + "reference_file": "plugins/gcore-fastedge/skills/scaffold/reference/base-http-ts.md", + "section": null + }, + + "kv-store-blueprint": { + "reference_file": "plugins/gcore-fastedge/skills/scaffold/reference/http-ts-kv-store.md", + "section": null + }, + "kv-store-pattern": { + "reference_file": "plugins/gcore-fastedge/skills/fastedge-docs/reference/examples-kv-store.md", + "section": null + }, + + "ab-testing-blueprint": { + "reference_file": "plugins/gcore-fastedge/skills/scaffold/reference/http-ts-ab-testing.md", + "section": null + }, + "ab-testing-pattern": { + "reference_file": "plugins/gcore-fastedge/skills/fastedge-docs/reference/examples-ab-testing.md", + "section": null + }, + + "fetch-blueprint": { + "reference_file": "plugins/gcore-fastedge/skills/scaffold/reference/http-ts-fetch.md", + "section": null + }, + "fetch-pattern": { + "reference_file": "plugins/gcore-fastedge/skills/fastedge-docs/reference/examples-fetch.md", + "section": null + }, + + "headers-blueprint": { + "reference_file": "plugins/gcore-fastedge/skills/scaffold/reference/http-ts-headers.md", + "section": null + }, + "headers-pattern": { + "reference_file": "plugins/gcore-fastedge/skills/fastedge-docs/reference/examples-headers.md", + "section": null + }, + + "geo-redirect-blueprint": { + "reference_file": "plugins/gcore-fastedge/skills/scaffold/reference/http-ts-geo-redirect.md", + "section": null + }, + "geo-redirect-pattern": { + "reference_file": "plugins/gcore-fastedge/skills/fastedge-docs/reference/examples-geo-redirect.md", + "section": null } }, "validation": { "mode": "advisory", - "strict_fields": ["sdk-api", "build-cli", "quickstart"] + "strict_fields": ["sdk-api", "build-cli", "quickstart", "hello-world-blueprint"] } } diff --git a/llms.txt b/llms.txt index 77a380f..7a92689 100644 --- a/llms.txt +++ b/llms.txt @@ -5,9 +5,9 @@ ## Documentation - [FastEdge JS SDK Documentation](docs/INDEX.md) +- [Quickstart](docs/quickstart.md) - [fastedge-assets CLI](docs/ASSETS_CLI.md) - [fastedge-build CLI](docs/BUILD_CLI.md) - [fastedge-init CLI](docs/INIT_CLI.md) -- [Quickstart](docs/quickstart.md) - [SDK API Reference](docs/SDK_API.md) - [Static Sites](docs/STATIC_SITES.md) From 38d31ad0501cdc0e633237b43a05b6a976634ae3 Mon Sep 17 00:00:00 2001 From: Gordon Farquharson Date: Tue, 7 Apr 2026 15:54:23 +0100 Subject: [PATCH 2/2] plugin manifest updated --- docs/ASSETS_CLI.md | 14 ++--- docs/BUILD_CLI.md | 43 +++++++------ docs/INDEX.md | 60 +++++++++--------- docs/INIT_CLI.md | 20 +++--- docs/SDK_API.md | 148 ++++++++++++++++++++++----------------------- 5 files changed, 146 insertions(+), 139 deletions(-) diff --git a/docs/ASSETS_CLI.md b/docs/ASSETS_CLI.md index d9c5af9..b8aa718 100644 --- a/docs/ASSETS_CLI.md +++ b/docs/ASSETS_CLI.md @@ -28,13 +28,13 @@ npx fastedge-assets --version ## Flags -| Flag | Alias | Type | Description | -| ----------------- | ----- | --------- | ----------------------------------------------------------------------------- | -| `--input ` | `-i` | `string` | Path to the directory of source assets (e.g. `./public`) | -| `--output ` | `-o` | `string` | Output file path for the generated manifest (e.g. `./src/asset-manifest.ts`) | -| `--config ` | `-c` | `string` | Path to an asset config file containing `AssetCacheConfig` fields | -| `--version` | `-v` | `boolean` | Print the package version | -| `--help` | `-h` | `boolean` | Print usage information | +| Flag | Alias | Type | Description | +| ----------------- | ----- | --------- | ---------------------------------------------------------------------------- | +| `--input ` | `-i` | `string` | Path to the directory of source assets (e.g. `./public`) | +| `--output ` | `-o` | `string` | Output file path for the generated manifest (e.g. `./src/asset-manifest.ts`) | +| `--config ` | `-c` | `string` | Path to an asset config file containing `AssetCacheConfig` fields | +| `--version` | `-v` | `boolean` | Print the package version | +| `--help` | `-h` | `boolean` | Print usage information | **Notes:** diff --git a/docs/BUILD_CLI.md b/docs/BUILD_CLI.md index 620f631..44ad65e 100644 --- a/docs/BUILD_CLI.md +++ b/docs/BUILD_CLI.md @@ -26,14 +26,14 @@ npx fastedge-build --version ## Options -| Flag | Alias | Type | Description | -| ------------ | ----- | ---------- | -------------------------------- | -| `--input` | `-i` | `String` | Input JavaScript/TypeScript file | -| `--output` | `-o` | `String` | Output WebAssembly file path | -| `--tsconfig` | `-t` | `String` | Path to tsconfig.json | -| `--config` | `-c` | `String[]` | Path(s) to build config files | -| `--help` | `-h` | `Boolean` | Show help | -| `--version` | `-v` | `Boolean` | Show version | +| Flag | Alias | Type | Description | +| -------------- | ----- | ---------- | -------------------------------- | +| `--input` | `-i` | `String` | Input JavaScript/TypeScript file | +| `--output` | `-o` | `String` | Output WebAssembly file path | +| `--tsconfig` | `-t` | `String` | Path to tsconfig.json | +| `--config` | `-c` | `String[]` | Path(s) to build config files | +| `--help` | `-h` | `Boolean` | Show help | +| `--version` | `-v` | `Boolean` | Show version | ## Build Modes @@ -127,14 +127,14 @@ export { config }; When `type` is `'static'`, the following fields from `AssetCacheConfig` apply: -| Field | Type | Required | Description | -| ------------------- | ------------------------------ | -------- | -------------------------------------------- | -| `publicDir` | `string` | Yes | Directory containing static files to embed | -| `assetManifestPath` | `string` | Yes | Output path for the generated asset manifest | -| `contentTypes` | `Array` | No | Custom content type mappings | -| `ignoreDotFiles` | `boolean` | No | Skip files beginning with `.` | -| `ignorePaths` | `string[]` | No | Paths to exclude from the manifest | -| `ignoreWellKnown` | `boolean` | No | Skip the `.well-known/` directory | +| Field | Type | Required | Description | +| --------------------- | ------------------------------ | -------- | -------------------------------------------- | +| `publicDir` | `string` | Yes | Directory containing static files to embed | +| `assetManifestPath` | `string` | Yes | Output path for the generated asset manifest | +| `contentTypes` | `Array` | No | Custom content type mappings | +| `ignoreDotFiles` | `boolean` | No | Skip files beginning with `.` | +| `ignorePaths` | `string[]` | No | Paths to exclude from the manifest | +| `ignoreWellKnown` | `boolean` | No | Skip the `.well-known/` directory | ### ContentTypeDefinition @@ -189,13 +189,20 @@ If `type` is absent or does not match `'http'` or `'static'`, the build exits wi A successful build writes a `.wasm` file to the path specified by `wasmOutput` (or `--output` in direct mode). The file is a WebAssembly Component Model component compatible with the FastEdge runtime. -On success the CLI prints: +In direct mode, on success the CLI prints: + +``` +Build success!! +"" -> "" +``` + +In config-driven mode, on success the CLI prints: ``` Success: Built ``` -On failure it exits with a non-zero status code and prints an error message. +On failure the CLI exits with a non-zero status code and prints an error message. ## See Also diff --git a/docs/INDEX.md b/docs/INDEX.md index 1272ff6..160fe37 100644 --- a/docs/INDEX.md +++ b/docs/INDEX.md @@ -13,22 +13,22 @@ The FastEdge JS SDK (`@gcoredev/fastedge-sdk-js`) is the JavaScript/TypeScript d ## CLI Tools -| Tool | Command | Purpose | -| ------------------- | ------------------------------------------ | ------------------------------- | -| **fastedge-build** | `npx fastedge-build ` | Compile JS/TS to WebAssembly | -| **fastedge-init** | `npx fastedge-init` | Interactive project scaffolding | -| **fastedge-assets** | `npx fastedge-assets ` | Generate static asset manifest | +| Tool | Command | Purpose | +| ----------------------- | ---------------------------------------------- | ------------------------------- | +| **fastedge-build** | `npx fastedge-build ` | Compile JS/TS to WebAssembly | +| **fastedge-init** | `npx fastedge-init` | Interactive project scaffolding | +| **fastedge-assets** | `npx fastedge-assets ` | Generate static asset manifest | ## Documentation -| Document | Description | -| ------------------------------------ | -------------------------------------------- | -| [Quickstart](quickstart.md) | Installation and first build | -| [fastedge-build CLI](BUILD_CLI.md) | Compile JavaScript to WebAssembly | -| [fastedge-init CLI](INIT_CLI.md) | Scaffold a new FastEdge project | -| [fastedge-assets CLI](ASSETS_CLI.md) | Generate static asset manifests | -| [Static Sites](STATIC_SITES.md) | Serve static websites from WASM | -| [SDK Runtime API](SDK_API.md) | Environment, KV Store, Secrets, and Web APIs | +| Document | Description | +| -------------------------------------- | -------------------------------------------- | +| [Quickstart](quickstart.md) | Installation and first build | +| [fastedge-build CLI](BUILD_CLI.md) | Compile JavaScript to WebAssembly | +| [fastedge-init CLI](INIT_CLI.md) | Scaffold a new FastEdge project | +| [fastedge-assets CLI](ASSETS_CLI.md) | Generate static asset manifests | +| [Static Sites](STATIC_SITES.md) | Serve static websites from WASM | +| [SDK Runtime API](SDK_API.md) | Environment, KV Store, Secrets, and Web APIs | ## Application Model @@ -49,10 +49,10 @@ addEventListener('fetch', (event) => { ## Build Types -| Type | Description | CLI | -| ---------- | ----------------------------------- | --------------------------------------------------- | -| **HTTP** | Standard request handler | `fastedge-build src/index.js output.wasm` | -| **Static** | Serve static files embedded in WASM | `fastedge-build --config .fastedge/build-config.js` | +| Type | Description | CLI | +| -------------- | ----------------------------------- | ------------------------------------------------------- | +| **HTTP** | Standard request handler | `fastedge-build src/index.js output.wasm` | +| **Static** | Serve static files embedded in WASM | `fastedge-build --config .fastedge/build-config.js` | ## Runtime APIs @@ -60,22 +60,22 @@ Runtime APIs are available via `fastedge::` module specifiers inside your applic ### FastEdge APIs -| Import | Export | Signature | -| ------------------- | ---------------------- | ----------------------------------------------------- | -| `fastedge::env` | `getEnv` | `(name: string): string \| null` | -| `fastedge::secret` | `getSecret` | `(name: string): string \| null` | -| `fastedge::secret` | `getSecretEffectiveAt` | `(name: string, effectiveAt: number): string \| null` | -| `fastedge::kv` | `KvStore.open` | `(name: string): KvStoreInstance` | +| Import | Export | Signature | +| ----------------------- | -------------------------- | ------------------------------------------------------- | +| `fastedge::env` | `getEnv` | `(name: string): string \| null` | +| `fastedge::secret` | `getSecret` | `(name: string): string \| null` | +| `fastedge::secret` | `getSecretEffectiveAt` | `(name: string, effectiveAt: number): string \| null` | +| `fastedge::kv` | `KvStore.open` | `(name: string): KvStoreInstance` | ### KvStoreInstance Methods -| Method | Signature | Description | -| --------------- | ----------------------------------------------------------------------- | ------------------------------------------------------ | -| `get` | `(key: string): ArrayBuffer \| null` | Retrieve a value by key | -| `scan` | `(pattern: string): Array` | Retrieve keys matching a prefix pattern (e.g. `foo*`) | -| `zrangeByScore` | `(key: string, min: number, max: number): Array<[ArrayBuffer, number]>` | Retrieve sorted set entries by score range | -| `zscan` | `(key: string, pattern: string): Array<[ArrayBuffer, number]>` | Retrieve sorted set entries matching a prefix pattern | -| `bfExists` | `(key: string, value: string): boolean` | Check if a value exists in a Bloom Filter | +| Method | Signature | Description | +| ------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------ | +| `get` | `(key: string): ArrayBuffer \| null` | Retrieve a value by key | +| `scan` | `(pattern: string): Array` | Retrieve keys matching a prefix pattern (e.g. `foo*`) | +| `zrangeByScore` | `(key: string, min: number, max: number): Array<[ArrayBuffer, number]>` | Retrieve sorted set entries by score range | +| `zscan` | `(key: string, pattern: string): Array<[ArrayBuffer, number]>` | Retrieve sorted set entries matching a prefix pattern | +| `bfExists` | `(key: string, value: string): boolean` | Check if a value exists in a Bloom Filter | ### Web APIs diff --git a/docs/INIT_CLI.md b/docs/INIT_CLI.md index 16cb6ef..bf65486 100644 --- a/docs/INIT_CLI.md +++ b/docs/INIT_CLI.md @@ -175,16 +175,16 @@ For a SPA, `spaEntrypoint` is set to the normalized path entered at the prompt ( #### Server Config (`serverConfig`) -| Field | Type | Default | Description | -| ----------------- | ----------------- | ------------------------------ | ----------------------------------------------------- | -| `type` | `string` | `"static"` | Server type identifier | -| `extendedCache` | `string[]` | `[]` | Additional paths to serve with long cache TTLs | -| `publicDirPrefix` | `string` | `""` | URL prefix stripped before resolving asset paths | -| `compression` | `string[]` | `[]` | Compression formats (reserved for future use) | -| `notFoundPage` | `string` | `"/404.html"` | Asset path served on 404 | -| `autoExt` | `string[]` | `[]` | Extensions appended when a path has no extension | -| `autoIndex` | `string[]` | `["index.html", "index.htm"]` | Index filenames tried when resolving a directory path | -| `spaEntrypoint` | `string \| null` | `null` | Fallback asset path for unmatched routes in SPA mode | +| Field | Type | Default | Description | +| ----------------- | ---------------- | ----------------------------- | ----------------------------------------------------- | +| `type` | `string` | `"static"` | Server type identifier | +| `extendedCache` | `string[]` | `[]` | Additional paths to serve with long cache TTLs | +| `publicDirPrefix` | `string` | `""` | URL prefix stripped before resolving asset paths | +| `compression` | `string[]` | `[]` | Compression formats (reserved for future use) | +| `notFoundPage` | `string` | `"/404.html"` | Asset path served on 404 | +| `autoExt` | `string[]` | `[]` | Extensions appended when a path has no extension | +| `autoIndex` | `string[]` | `["index.html", "index.htm"]` | Index filenames tried when resolving a directory path | +| `spaEntrypoint` | `string \| null` | `null` | Fallback asset path for unmatched routes in SPA mode | ### Build Command diff --git a/docs/SDK_API.md b/docs/SDK_API.md index f899396..d181423 100644 --- a/docs/SDK_API.md +++ b/docs/SDK_API.md @@ -12,9 +12,9 @@ Runtime APIs available to FastEdge applications compiled to WebAssembly. import { getEnv } from "fastedge::env"; ``` -| Function | Signature | Returns | -| ---------------- | ---------------------------------- | ---------------- | -| `getEnv(name)` | `(name: string) => string \| null` | `string \| null` | +| Function | Signature | Returns | +| -------------- | ---------------------------------- | ---------------- | +| `getEnv(name)` | `(name: string) => string \| null` | `string \| null` | Retrieves the value of a named environment variable, or `null` if not set. Environment variables are set on the application and injected at request time. @@ -45,10 +45,10 @@ addEventListener("fetch", event => event.respondWith(app(event))); import { getSecret, getSecretEffectiveAt } from "fastedge::secret"; ``` -| Function | Signature | Returns | -| -------------------------------------- | ------------------------------------------------------- | ---------------- | -| `getSecret(name)` | `(name: string) => string \| null` | `string \| null` | -| `getSecretEffectiveAt(name, effectiveAt)` | `(name: string, effectiveAt: number) => string \| null` | `string \| null` | +| Function | Signature | Returns | +| ----------------------------------------- | --------------------------------------------------------------- | ---------------- | +| `getSecret(name)` | `(name: string) => string \| null` | `string \| null` | +| `getSecretEffectiveAt(name, effectiveAt)` | `(name: string, effectiveAt: number) => string \| null` | `string \| null` | **Note:** Secrets can only be read during request processing, not during build-time initialization. @@ -138,13 +138,13 @@ addEventListener("fetch", event => event.respondWith(app(event))); #### KvStoreInstance methods -| Method | Signature | Returns | -| -------------------------------- | --------------------------------------------------------------------------- | ------------------------------ | -| `get(key)` | `(key: string) => ArrayBuffer \| null` | `ArrayBuffer \| null` | -| `scan(pattern)` | `(pattern: string) => Array` | `Array` | -| `zrangeByScore(key, min, max)` | `(key: string, min: number, max: number) => Array<[ArrayBuffer, number]>` | `Array<[ArrayBuffer, number]>` | -| `zscan(key, pattern)` | `(key: string, pattern: string) => Array<[ArrayBuffer, number]>` | `Array<[ArrayBuffer, number]>` | -| `bfExists(key, value)` | `(key: string, value: string) => boolean` | `boolean` | +| Method | Signature | Returns | +| ------------------------------ | ------------------------------------------------------------------------- | ------------------------------ | +| `get(key)` | `(key: string) => ArrayBuffer \| null` | `ArrayBuffer \| null` | +| `scan(pattern)` | `(pattern: string) => Array` | `Array` | +| `zrangeByScore(key, min, max)` | `(key: string, min: number, max: number) => Array<[ArrayBuffer, number]>` | `Array<[ArrayBuffer, number]>` | +| `zscan(key, pattern)` | `(key: string, pattern: string) => Array<[ArrayBuffer, number]>` | `Array<[ArrayBuffer, number]>` | +| `bfExists(key, value)` | `(key: string, value: string) => boolean` | `boolean` | ##### `get` @@ -289,12 +289,12 @@ const data = await response.json(); new Request(input: RequestInfo | URL, init?: RequestInit): Request ``` -| `RequestInit` field | Type | Description | -| ---------------------- | ------------------ | ------------------------------------------------------------------ | -| `method` | `string` | HTTP method. Defaults to `"GET"`. | -| `headers` | `HeadersInit` | Request headers. | -| `body` | `BodyInit \| null` | Request body. | -| `manualFramingHeaders` | `boolean` | When `true`, disables automatic framing header management. | +| `RequestInit` field | Type | Description | +| ---------------------- | ------------------ | ---------------------------------------------------------- | +| `method` | `string` | HTTP method. Defaults to `"GET"`. | +| `headers` | `HeadersInit` | Request headers. | +| `body` | `BodyInit \| null` | Request body. | +| `manualFramingHeaders` | `boolean` | When `true`, disables automatic framing header management. | | `Request` property / method | Type | Description | | --------------------------------- | ------------------------------------ | ------------------------------------------------- | @@ -349,17 +349,17 @@ new Headers(init?: HeadersInit): Headers `HeadersInit` accepts a `Headers` instance, a `string[][]` array of `[name, value]` pairs, or a `Record` object. -| Method | Signature | -| --------------------- | ---------------------------------------------------------------------------- | -| `get(name)` | `(name: string) => string \| null` | -| `has(name)` | `(name: string) => boolean` | -| `set(name, value)` | `(name: string, value: string) => void` | -| `append(name, value)` | `(name: string, value: string) => void` | -| `delete(name)` | `(name: string) => void` | -| `forEach(callback)` | `(callback: (value: string, key: string, parent: Headers) => void) => void` | -| `entries()` | `() => IterableIterator<[string, string]>` | -| `keys()` | `() => IterableIterator` | -| `values()` | `() => IterableIterator` | +| Method | Signature | +| --------------------- | --------------------------------------------------------------------------- | +| `get(name)` | `(name: string) => string \| null` | +| `has(name)` | `(name: string) => boolean` | +| `set(name, value)` | `(name: string, value: string) => void` | +| `append(name, value)` | `(name: string, value: string) => void` | +| `delete(name)` | `(name: string) => void` | +| `forEach(callback)` | `(callback: (value: string, key: string, parent: Headers) => void) => void` | +| `entries()` | `() => IterableIterator<[string, string]>` | +| `keys()` | `() => IterableIterator` | +| `values()` | `() => IterableIterator` | **Immutability note:** The `headers` object on an incoming `event.request` is read-only. Attempting to mutate it will throw a `TypeError`. To add or change headers, construct a new `Headers` object: @@ -414,19 +414,19 @@ new URLSearchParams( ): URLSearchParams ``` -| Method | Signature | -| --------------------- | -------------------------------------------------------------------------------------------- | -| `get(name)` | `(name: string) => string \| null` | -| `getAll(name)` | `(name: string) => string[]` | -| `has(name)` | `(name: string) => boolean` | -| `set(name, value)` | `(name: string, value: string) => void` | -| `append(name, value)` | `(name: string, value: string) => void` | -| `delete(name)` | `(name: string) => void` | -| `sort()` | `() => void` | -| `entries()` | `() => IterableIterator<[string, string]>` | -| `keys()` | `() => IterableIterator` | -| `values()` | `() => IterableIterator` | -| `forEach(callback)` | `(callback: (value: string, name: string, searchParams: URLSearchParams) => void) => void` | +| Method | Signature | +| --------------------- | ------------------------------------------------------------------------------------------ | +| `get(name)` | `(name: string) => string \| null` | +| `getAll(name)` | `(name: string) => string[]` | +| `has(name)` | `(name: string) => boolean` | +| `set(name, value)` | `(name: string, value: string) => void` | +| `append(name, value)` | `(name: string, value: string) => void` | +| `delete(name)` | `(name: string) => void` | +| `sort()` | `() => void` | +| `entries()` | `() => IterableIterator<[string, string]>` | +| `keys()` | `() => IterableIterator` | +| `values()` | `() => IterableIterator` | +| `forEach(callback)` | `(callback: (value: string, name: string, searchParams: URLSearchParams) => void) => void` | --- @@ -440,21 +440,21 @@ The WHATWG Streams API is available for constructing and transforming streaming new ReadableStream(underlyingSource?: UnderlyingSource, strategy?: QueuingStrategy): ReadableStream ``` -| `UnderlyingSource` field | Type | -| ------------------------ | -------------------------------------------------------------------------------- | -| `start` | `(controller: ReadableStreamDefaultController) => any` | -| `pull` | `(controller: ReadableStreamDefaultController) => void \| PromiseLike` | -| `cancel` | `(reason?: any) => void \| PromiseLike` | -| `type` | `"bytes" \| undefined` | -| `autoAllocateChunkSize` | `number` | +| `UnderlyingSource` field | Type | +| ------------------------ | ------------------------------------------------------------------------------- | +| `start` | `(controller: ReadableStreamDefaultController) => any` | +| `pull` | `(controller: ReadableStreamDefaultController) => void \| PromiseLike` | +| `cancel` | `(reason?: any) => void \| PromiseLike` | +| `type` | `"bytes" \| undefined` | +| `autoAllocateChunkSize` | `number` | -| `ReadableStream` method | Signature | -| ------------------------------------ | -------------------------------------------------------------------------------------------- | -| `getReader()` | `() => ReadableStreamDefaultReader` | -| `pipeTo(dest, options?)` | `(dest: WritableStream, options?: StreamPipeOptions) => Promise` | -| `pipeThrough(transform, options?)` | `(transform: ReadableWritablePair, options?: StreamPipeOptions) => ReadableStream` | -| `tee()` | `() => [ReadableStream, ReadableStream]` | -| `cancel(reason?)` | `(reason?: any) => Promise` | +| `ReadableStream` method | Signature | +| ---------------------------------- | ------------------------------------------------------------------------------------------- | +| `getReader()` | `() => ReadableStreamDefaultReader` | +| `pipeTo(dest, options?)` | `(dest: WritableStream, options?: StreamPipeOptions) => Promise` | +| `pipeThrough(transform, options?)` | `(transform: ReadableWritablePair, options?: StreamPipeOptions) => ReadableStream` | +| `tee()` | `() => [ReadableStream, ReadableStream]` | +| `cancel(reason?)` | `(reason?: any) => Promise` | ```javascript const stream = new ReadableStream({ @@ -474,10 +474,10 @@ return new Response(stream, { status: 200 }); new WritableStream(underlyingSink?: UnderlyingSink, strategy?: QueuingStrategy): WritableStream ``` -| `WritableStream` method | Signature | -| ----------------------- | --------------------------------------- | -| `getWriter()` | `() => WritableStreamDefaultWriter` | -| `abort(reason?)` | `(reason?: any) => Promise` | +| `WritableStream` method | Signature | +| ----------------------- | -------------------------------------- | +| `getWriter()` | `() => WritableStreamDefaultWriter` | +| `abort(reason?)` | `(reason?: any) => Promise` | #### `TransformStream` @@ -542,12 +542,12 @@ crypto.subtle: SubtleCrypto Available as `crypto.subtle`. Supported operations: -| Method | Signature | -| ----------- | -------------------------------------------------------------------------------------------------------------------- | -| `digest` | `(algorithm: AlgorithmIdentifier, data: BufferSource) => Promise` | -| `importKey` | See overloads below | -| `sign` | `(algorithm: AlgorithmIdentifier, key: CryptoKey, data: BufferSource) => Promise` | -| `verify` | `(algorithm: AlgorithmIdentifier, key: CryptoKey, signature: BufferSource, data: BufferSource) => Promise` | +| Method | Signature | +| ----------- | ------------------------------------------------------------------------------------------------------------------- | +| `digest` | `(algorithm: AlgorithmIdentifier, data: BufferSource) => Promise` | +| `importKey` | See overloads below | +| `sign` | `(algorithm: AlgorithmIdentifier, key: CryptoKey, data: BufferSource) => Promise` | +| `verify` | `(algorithm: AlgorithmIdentifier, key: CryptoKey, signature: BufferSource, data: BufferSource) => Promise` | `importKey` overloads: @@ -648,12 +648,12 @@ console.log(`elapsed: ${elapsed}ms`); ### Additional Globals -| Global | Type / Signature | Description | -| -------------------------------- | ----------------------------------------------------------- | ------------------------------------------------- | -| `self` | `typeof globalThis` | Reference to the global object. | -| `location` | `WorkerLocation` | URL of the current worker script. | -| `queueMicrotask(callback)` | `(callback: () => void) => void` | Queues a microtask. | -| `structuredClone(value, opts?)` | `(value: any, options?: StructuredSerializeOptions) => any` | Deep-clones a value. Transferable: `ArrayBuffer`. | +| Global | Type / Signature | Description | +| ------------------------------- | ----------------------------------------------------------- | ------------------------------------------------- | +| `self` | `typeof globalThis` | Reference to the global object. | +| `location` | `WorkerLocation` | URL of the current worker script. | +| `queueMicrotask(callback)` | `(callback: () => void) => void` | Queues a microtask. | +| `structuredClone(value, opts?)` | `(value: any, options?: StructuredSerializeOptions) => any` | Deep-clones a value. Transferable: `ArrayBuffer`. | ---