Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 4 additions & 24 deletions .github/workflows/deploy.yaml
Original file line number Diff line number Diff line change
@@ -1,43 +1,25 @@
name: Deploy Docusaurus to GitHub Pages

on:
# Trigger the workflow on pull requests and pushes to specific branches
pull_request:
push:
branches:
- main
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:

# Concurrency configuration to manage parallel workflow runs
#
# Group composition: pages-<event_type>-<unique_identifier>
# - event_type: 'pull_request', 'push', or 'workflow_dispatch'
# - unique_identifier: PR number for PRs, branch ref for pushes/manual runs
#
# Examples of group names:
# - PR #123: "pages-pull_request-123"
# - Push to main: "pages-push-refs/heads/main"
# - Manual run on main: "pages-workflow_dispatch-refs/heads/main"
#
# Behavior:
# - PRs: New commits cancel previous runs (cancel-in-progress: true)
# - Main branch: Runs complete without cancellation (cancel-in-progress: false)
# - Manual dispatch: Runs complete without cancellation (cancel-in-progress: false)
concurrency:
group: pages-${{ github.event_name }}-${{ github.event_name == 'pull_request' && github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
group: production-deploy
cancel-in-progress: true

jobs:
build:
name: Build Docusaurus
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/checkout@v6

- uses: actions/setup-node@v4
- uses: actions/setup-node@v6
with:
node-version: '22'
cache: 'npm'
Expand Down Expand Up @@ -81,8 +63,6 @@ jobs:
needs: build
name: Deploy to GitHub Pages
runs-on: ubuntu-latest
# Only deploy on push to main or manual trigger, not on PRs
if: (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'workflow_dispatch'

permissions:
pages: write
Expand Down
203 changes: 203 additions & 0 deletions .github/workflows/pr-preview.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
name: Deploy PR Preview

on:
pull_request:
types: [opened, synchronize, reopened, closed]

# Cancel previous runs when new commits are pushed to the PR
concurrency:
group: pr-preview-${{ github.event.pull_request.number }}
cancel-in-progress: true

jobs:
deploy-preview:
name: Deploy PR Preview
runs-on: ubuntu-latest
if: github.event.action != 'closed'
permissions:
pull-requests: write
deployments: write
contents: read

steps:
- name: Checkout PR code
uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha }}

- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '22'
cache: 'npm'
cache-dependency-path: 'package-lock.json'

- name: Install dependencies
run: |
echo "Installing dependencies..."
npm ci

- name: Build Docusaurus site
env:
DOCUSAURUS_BASE_URL: /pr-${{ github.event.pull_request.number }}
run: |
echo "Building site with base URL: $DOCUSAURUS_BASE_URL"
npm run build

- name: Upload build artifact for local testing
uses: actions/upload-artifact@v4
with:
name: pr-${{ github.event.pull_request.number }}-build
path: build/
retention-days: 30

- name: Prepare deployment directory
run: |
PR_DIR="pr-${{ github.event.pull_request.number }}"
echo "Creating directory: $PR_DIR"
mkdir -p "$PR_DIR"

# Create config.yaml
cat > "$PR_DIR/config.yaml" << 'EOF'
static:
files: 'build/**'
urlPath: 'pr-${{ github.event.pull_request.number }}'
extensions: ['html']
index: true
EOF

# Copy build directory
cp -r build "$PR_DIR/"

echo "Contents of $PR_DIR:"
ls -la "$PR_DIR"

- name: Install HarperDB CLI
run: |
npm install -g harperdb

- name: Deploy to HarperDB
env:
HARPER_PREVIEW_TARGET: ${{ secrets.HARPER_PREVIEW_TARGET }}
HARPER_PREVIEW_USERNAME: ${{ secrets.HARPER_PREVIEW_USERNAME }}
HARPER_PREVIEW_PASSWORD: ${{ secrets.HARPER_PREVIEW_PASSWORD }}
run: |
cd "pr-${{ github.event.pull_request.number }}"
# Add your additional arguments here
harper deploy \
target=${HARPER_PREVIEW_TARGET}:9925 \
username=$HARPER_PREVIEW_USERNAME \
password=$HARPER_PREVIEW_PASSWORD \
project=pr-${{ github.event.pull_request.number }} \
restart=true \
replicated=true

- name: Create deployment
env:
HARPER_PREVIEW_TARGET: ${{ secrets.HARPER_PREVIEW_TARGET }}
uses: actions/github-script@v8
with:
script: |
const prNumber = context.payload.pull_request.number;
const target = process.env.HARPER_PREVIEW_TARGET;
const deploymentUrl = `${target}/pr-${prNumber}`;

// Create deployment
const deployment = await github.rest.repos.createDeployment({
owner: context.repo.owner,
repo: context.repo.repo,
ref: context.payload.pull_request.head.sha,
environment: `pr-${prNumber}`,
description: `Preview deployment for PR #${prNumber}`,
auto_merge: false,
required_contexts: []
});

// Create deployment status
await github.rest.repos.createDeploymentStatus({
owner: context.repo.owner,
repo: context.repo.repo,
deployment_id: deployment.data.id,
state: 'success',
environment_url: deploymentUrl,
description: 'Preview deployment successful'
});

// Also add a comment to the PR
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: `## 🚀 Preview Deployment\n\nYour preview deployment is ready!\n\n🔗 **Preview URL:** ${deploymentUrl}\n\nThis preview will update automatically when you push new commits.`
});

cleanup-preview:
name: Cleanup PR Preview
runs-on: ubuntu-latest
if: github.event.action == 'closed'
permissions:
pull-requests: write
deployments: write
contents: read

steps:
- name: Checkout code
uses: actions/checkout@v6

- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '22'
cache: 'npm'
cache-dependency-path: 'package-lock.json'

- name: Install HarperDB CLI
run: |
npm install -g harperdb

- name: Remove preview deployment
env:
HARPER_PREVIEW_TARGET: ${{ secrets.HARPER_PREVIEW_TARGET }}
HARPER_PREVIEW_USERNAME: ${{ secrets.HARPER_PREVIEW_USERNAME }}
HARPER_PREVIEW_PASSWORD: ${{ secrets.HARPER_PREVIEW_PASSWORD }}
run: |
# Add your cleanup command here (e.g., harper undeploy or similar)
harper drop_component \
target=${HARPER_PREVIEW_TARGET}:9925 \
username=$HARPER_PREVIEW_USERNAME \
password=$HARPER_PREVIEW_PASSWORD \
project=pr-${{ github.event.pull_request.number }} \
replicated=true \
restart=true
echo "Cleaning up preview for PR #${{ github.event.pull_request.number }}"

- name: Update deployment status
uses: actions/github-script@v7
with:
script: |
const prNumber = context.payload.pull_request.number;

// Mark deployment as inactive
const deployments = await github.rest.repos.listDeployments({
owner: context.repo.owner,
repo: context.repo.repo,
environment: `pr-${prNumber}`
});

for (const deployment of deployments.data) {
await github.rest.repos.createDeploymentStatus({
owner: context.repo.owner,
repo: context.repo.repo,
deployment_id: deployment.id,
state: 'inactive',
description: 'Preview deployment removed'
});
}

// Add cleanup comment to PR
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: `## 🧹 Preview Cleanup\n\nThe preview deployment for this PR has been removed.`
});
6 changes: 3 additions & 3 deletions docs/developers/applications/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ This guide is going to walk you through building a basic Harper application usin

## Custom Functionality with JavaScript

[The getting started guide](../../learn/) covers how to build an application entirely through schema configuration. However, if your application requires more custom functionality, you will probably want to employ your own JavaScript modules to implement more specific features and interactions. This gives you tremendous flexibility and control over how data is accessed and modified in Harper. Let's take a look at how we can use JavaScript to extend and define "resources" for custom functionality. In Harper, data is accessed through our [Resource API](../../reference/resources/), a standard interface to access data sources, tables, and make them available to endpoints. Database tables are `Resource` classes, and so extending the function of a table is as simple as extending their class.
[The getting started guide](/learn/) covers how to build an application entirely through schema configuration. However, if your application requires more custom functionality, you will probably want to employ your own JavaScript modules to implement more specific features and interactions. This gives you tremendous flexibility and control over how data is accessed and modified in Harper. Let's take a look at how we can use JavaScript to extend and define "resources" for custom functionality. In Harper, data is accessed through our [Resource API](../../reference/resources/), a standard interface to access data sources, tables, and make them available to endpoints. Database tables are `Resource` classes, and so extending the function of a table is as simple as extending their class.

To define custom (JavaScript) resources as endpoints, we need to create a `resources.js` module (this goes in the root of your application folder). And then endpoints can be defined with Resource classes that `export`ed. This can be done in addition to, or in lieu of the `@export`ed types in the schema.graphql. If you are exporting and extending a table you defined in the schema make sure you remove the `@export` from the schema so that don't export the original table or resource to the same endpoint/path you are exporting with a class. Resource classes have methods that correspond to standard HTTP/REST methods, like `get`, `post`, `patch`, and `put` to implement specific handling for any of these methods (for tables they all have default implementations). Let's add a property to the dog records when they are returned, that includes their age in human years. To do this, we get the `Dog` class from the defined tables, extend it (with our custom logic), and export it:

Expand Down Expand Up @@ -117,8 +117,8 @@ type Breed @table {
We use the new table's (static) `get()` method to retrieve a breed by id. Harper will maintain the current context, ensuring that we are accessing the data atomically, in a consistent snapshot across tables. This provides:

1. Automatic tracking of most recently updated timestamps across resources for caching purposes
1. Sharing of contextual metadata (like user who requested the data)
1. Transactional atomicity for any writes (not needed in this get operation, but important for other operations)
2. Sharing of contextual metadata (like user who requested the data)
3. Transactional atomicity for any writes (not needed in this get operation, but important for other operations)

The resource methods are automatically wrapped with a transaction and will automatically commit the changes when the method finishes. This allows us to fully utilize multiple resources in our current transaction. With our own snapshot of the database for the Dog and Breed table we can then access data like this:

Expand Down
2 changes: 1 addition & 1 deletion docs/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Here, you'll find all things Harper, and everything you need to get started, tro

## Getting Started

The best way to get started using Harper is to head over to the [Learn](../learn/) section and work through the Getting Started and Developer guides.
The best way to get started using Harper is to head over to the [Learn](/learn/) section and work through the Getting Started and Developer guides.

## Building with Harper

Expand Down
2 changes: 1 addition & 1 deletion fabric/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Fabric Studio is the web-based GUI for Harper. Studio enables you to administer,

[Sign up for free!](https://fabric.harper.fast/#/sign-up)

Harper includes a simplified local Studio that is packaged with all Harper installations and served directly from the cluster. It can be enabled in the [configuration file](../docs/deployments/configuration#localstudio). This section is dedicated to the hosted Studio accessed at [studio.harperdb.io](https://fabric.harper.fast/).
Harper includes a simplified local Studio that is packaged with all Harper installations and served directly from the cluster. It can be enabled in the [configuration file](/docs/deployments/configuration#localstudio). This section is dedicated to the hosted Studio accessed at [studio.harperdb.io](https://fabric.harper.fast/).

---

Expand Down
33 changes: 14 additions & 19 deletions scripts/preview-pr.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ async function main() {
// Get the workflow run for this PR (using sanitized branch name)
const runs = JSON.parse(
execSync(
`gh api repos/HarperDB/documentation/actions/runs --paginate -X GET -f branch=${sanitizedBranch} --jq '.workflow_runs | map(select(.conclusion == "success" and .name == "Deploy Docusaurus to GitHub Pages")) | sort_by(.created_at) | reverse | .[0]'`,
`gh api repos/HarperFast/documentation/actions/runs --paginate -X GET -f branch=${sanitizedBranch} --jq '.workflow_runs | map(select(.conclusion == "success" and .name == "Deploy PR Preview")) | sort_by(.created_at) | reverse | .[0]'`,
{ encoding: 'utf-8' }
)
);
Expand All @@ -121,15 +121,16 @@ async function main() {

// Get the artifacts for this run
const artifacts = JSON.parse(
execSync(`gh api repos/HarperDB/documentation/actions/runs/${runs.id}/artifacts --jq '.artifacts'`, {
execSync(`gh api repos/HarperFast/documentation/actions/runs/${runs.id}/artifacts --jq '.artifacts'`, {
encoding: 'utf-8',
})
);

const artifact = artifacts.find((a) => a.name === 'github-pages');
const artifactName = `pr-${PR_NUMBER}-build`;
const artifact = artifacts.find((a) => a.name === artifactName);

if (!artifact) {
console.error(`❌ No 'github-pages' artifact found for this PR`);
console.error(`❌ No '${artifactName}' artifact found for this PR`);
process.exit(1);
}

Expand Down Expand Up @@ -161,7 +162,7 @@ async function main() {
// Download the artifact
console.log('⬇️ Downloading artifact...');
const artifactZip = join(PR_DIR, 'artifact.zip');
execSync(`gh api repos/HarperDB/documentation/actions/artifacts/${artifact.id}/zip > "${artifactZip}"`, {
execSync(`gh api repos/HarperFast/documentation/actions/artifacts/${artifact.id}/zip > "${artifactZip}"`, {
stdio: 'inherit',
});

Expand All @@ -170,28 +171,19 @@ async function main() {
throw new Error('Downloaded artifact file not found');
}

// Extract the artifact (it's a tar.gz inside a zip)
// Extract the artifact (it's a direct zip of the build directory)
console.log('📂 Extracting artifact...');
execSync(`unzip -q "${artifactZip}" -d "${PR_DIR}"`, { stdio: 'inherit' });

// The github-pages artifact contains a tar.gz file
const tarFile = join(PR_DIR, 'artifact.tar');
if (existsSync(tarFile)) {
mkdirSync(BUILD_DIR, { recursive: true });
execSync(`tar -xzf "${tarFile}" -C "${BUILD_DIR}"`, { stdio: 'inherit' });
} else {
throw new Error('Expected artifact.tar not found in artifact');
}
mkdirSync(BUILD_DIR, { recursive: true });
execSync(`unzip -q "${artifactZip}" -d "${BUILD_DIR}"`, { stdio: 'inherit' });

// Verify extracted files are within expected directory
const resolvedBuildDir = join(BUILD_DIR);
if (!resolvedBuildDir.startsWith(PREVIEW_DIR)) {
throw new Error('Security violation: extracted files outside preview directory');
}

// Clean up compressed files
// Clean up zip file
rmSync(artifactZip, { force: true });
rmSync(tarFile, { force: true });

console.log('\n✅ Preview ready!\n');
console.log(`📁 Build location: ${BUILD_DIR}`);
Expand All @@ -208,7 +200,10 @@ async function main() {
console.log(`\n🚀 Starting preview server...\n`);

// Start the server with quoted path to prevent injection
execSync(`npm run serve -- --dir "${BUILD_DIR}"`, { stdio: 'inherit' });
execSync(`npm run serve -- --dir "${BUILD_DIR}"`, {
stdio: 'inherit',
env: { ...process.env, DOCUSAURUS_BASE_URL: `pr-${PR_NUMBER}` },
});
} catch (error) {
console.error('\n❌ Error:', error.message);
process.exit(1);
Expand Down
2 changes: 1 addition & 1 deletion versioned_docs/version-4.5/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Welcome to the Harper Documentation! Here, you'll find all things Harper, and ev

## Getting Started

The best way to get started using Harper is to head over to the [Learn](../../learn/) section and work through the Getting Started and Developer guides.
The best way to get started using Harper is to head over to the [Learn](/learn/) section and work through the Getting Started and Developer guides.

## Building with Harper

Expand Down
2 changes: 1 addition & 1 deletion versioned_docs/version-4.6/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Here, you'll find all things Harper, and everything you need to get started, tro

## Getting Started

The best way to get started using Harper is to head over to the [Learn](../../learn/) section and work through the Getting Started and Developer guides.
The best way to get started using Harper is to head over to the [Learn](/learn/) section and work through the Getting Started and Developer guides.

## Building with Harper

Expand Down
Loading