Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
110 changes: 110 additions & 0 deletions .github/workflows/sfdx-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
name: Salesforce Release

# Creates a new Salesforce managed package version, optionally promotes it to
# released, tags `salesforce-vX.Y.Z`, and cuts a GitHub release.
# Triggered locally via `pnpm release:salesforce`.
on:
workflow_dispatch:
inputs:
bump:
description: "Version bump type"
required: true
default: "patch"
type: choice
options: [patch, minor, major]

promote:
description: "Promote to released (installable in production orgs). False = beta only."
required: true
type: boolean

concurrency:
group: sfdx-release
cancel-in-progress: false

permissions:
contents: write

jobs:
release-salesforce:
runs-on: ubuntu-latest
timeout-minutes: 120
environment: production

steps:
- name: Validate branch
env:
REF_NAME: ${{ github.ref_name }}
run: |
if [[ "$REF_NAME" != "main" && ! "$REF_NAME" =~ ^hotfix/ ]]; then
echo "Error: This workflow can only run on 'main' or a 'hotfix/*' branch. Current branch: $REF_NAME"
exit 1
fi

- uses: actions/checkout@v6
with:
fetch-depth: 0
# Overridden later with a GitHub App installation token so the bump
# commit + tag push are made as the App (on the ruleset bypass list).
persist-credentials: false

- uses: actions/setup-node@v6
with:
node-version: "24"

- name: Install Salesforce CLI
run: npm install -g @salesforce/cli

- name: Decode JWT key
run: echo "${{ secrets.SFDX_JWT_KEY_BASE64 }}" | base64 --decode > server.key

- name: Authenticate to Dev Hub
run: |
sf org login jwt \
--client-id ${{ secrets.SFDX_CONSUMER_KEY }} \
--jwt-key-file server.key \
--username ${{ secrets.SFDX_DEVHUB_USERNAME }} \
--set-default-dev-hub \
--alias devhub

# Generate a GitHub App installation token for git pushes.
# The App's bot account is on the main-branch ruleset bypass list, which a PAT is not.
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@v3
with:
client-id: ${{ secrets.CLIENT_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}

- name: Get GitHub App user ID
id: app-user
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
APP_SLUG: ${{ steps.app-token.outputs.app-slug }}
run: |
USER_ID=$(gh api "/users/${APP_SLUG}[bot]" --jq .id)
echo "user-id=${USER_ID}" >> "$GITHUB_OUTPUT"

- name: Configure git for release
env:
APP_SLUG: ${{ steps.app-token.outputs.app-slug }}
USER_ID: ${{ steps.app-user.outputs.user-id }}
APP_TOKEN: ${{ steps.app-token.outputs.token }}
REPO: ${{ github.repository }}
run: |
git config user.name "${APP_SLUG}[bot]"
git config user.email "${USER_ID}+${APP_SLUG}[bot]@users.noreply.github.com"
git remote set-url origin "https://x-access-token:${APP_TOKEN}@github.com/${REPO}.git"

- name: Create package version and release
env:
BUMP: ${{ inputs.bump }}
PROMOTE: ${{ inputs.promote }}
SFDX_INSTALLATION_PASSWORD: ${{ secrets.SFDX_INSTALLATION_PASSWORD }}
GH_TOKEN: ${{ steps.app-token.outputs.token }}
REPO: ${{ github.repository }}
run: node apps-sfdx/scripts/sfdx/release.mjs

- name: Remove key file
if: always()
run: rm -f server.key
89 changes: 87 additions & 2 deletions apps-sfdx/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,90 @@
# Jetstream for Salesforce

Navigating to the full screen version of Jetstream:
A managed package that embeds [Jetstream](https://getjetstream.app) directly inside Salesforce as a
[canvas app](https://developer.salesforce.com/docs/platform/canvas-developer-guide/overview), giving users a powerful workspace to
query, load, update, and manage their data and metadata without leaving their org.

`/jetstream/JetstreamApp.app`
- **Package name:** `Jetstream for Salesforce`
- **Namespace:** `jetstream`
- **Type:** Managed (2GP)

## What's included

| Component | API Name | Purpose |
| ------------------- | ------------------------- | ------------------------------------------------------------------------------------------ |
| External Client App | `Jetstream_Canvas` | The canvas app definition, OAuth settings, and policies that embed Jetstream |
| Custom Application | `Jetstream` | Lightning app that hosts the Jetstream tab |
| Custom Tab | `JetstreamAppTab` | Tab that renders the Visualforce wrapper page |
| Visualforce Page | `JetstreamPage` | Full-screen wrapper that hosts the canvas iframe |
| Apex Controller | `JetstreamPageController` | Supplies canvas parameters (e.g. full-screen detection) to the page |
| Custom Setting | `UserPreferences__c` | Hierarchy custom setting storing per-user preferences (SOQL formatting, record sync, etc.) |
| Permission Set | `Jetstream` | Grants access to the app, tab, page, and user-preference fields |

## Accessing the app

After the package is installed and the **Jetstream** permission set is assigned (see [Permissions](#permissions)):

- Open the **App Launcher** and search for **Jetstream**, or
- Navigate directly to the Lightning app: `/lightning/app/jetstream__Jetstream`

## Requirements

For local development and packaging you'll need:

- [Salesforce CLI](https://developer.salesforce.com/tools/salesforcecli) (`sf`)
- A Dev Hub org with the `jetstream` namespace linked
- [Node.js](https://nodejs.org) (for the helper scripts)

All commands below are run from the `apps-sfdx/` directory.

## Local development

Spin up a fully configured scratch org (creates the org, deploys source, assigns the `Jetstream` permission set, sets a password, and
opens the org):

```bash
npm run org:scratch:create
```

### Environment variables

The canvas URL in the package points at production (`https://getjetstream.app/canvas/app`). To point a scratch org at a local or
staging Jetstream instance, create a `.env.local` file and the `replacements` block in `sfdx-project.json` will swap the URL on deploy:

```bash
# apps-sfdx/.env.local
SFDX_ENV=DEVELOPMENT
JETSTREAM_CANVAS_BASE_URL=http://localhost:3333
```

The override only applies when `SFDX_ENV=DEVELOPMENT`; production package builds always use the `getjetstream.app` URL.

## Project structure

```
apps-sfdx/
├── canvas-app/
│ ├── jetstream/ # Packaged source (everything that ships in the managed package)
│ │ └── main/default/
│ │ ├── applications/ # Jetstream Lightning app
│ │ ├── classes/ # Apex controller + tests
│ │ ├── contentassets/ # App logo
│ │ ├── externalClientApps/ # Canvas app (ECA) + OAuth/canvas settings & policies
│ │ ├── objects/ # UserPreferences__c custom setting + fields
│ │ ├── pages/ # JetstreamPage Visualforce wrapper
│ │ ├── permissionsets/ # Jetstream permission set
│ │ └── tabs/ # JetstreamAppTab
│ └── unpackaged/ # Source deployed to dev orgs but NOT included in the package
├── config/ # Scratch org definition files
└── scripts/ # Helper scripts (scratch org creation, etc.)
```

## Permissions

Access to the canvas app is gated by the **Jetstream** permission set.

```bash
sf org assign permset --name Jetstream --target-org <org-alias>
```

Subscribers must assign this permission set to any user who needs the app.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8" ?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>66.0</apiVersion>
<apiVersion>67.0</apiVersion>
<status>Active</status>
</ApexClass>
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8" ?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>66.0</apiVersion>
<apiVersion>67.0</apiVersion>
<status>Active</status>
</ApexClass>
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8" ?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>66.0</apiVersion>
<apiVersion>67.0</apiVersion>
<status>Active</status>
</ApexClass>
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<distributionState>Packaged</distributionState>
<iconUrl>https://getjetstream.app/assets/images/jetstream-icon-pro-128.png</iconUrl>
<isProtected>false</isProtected>
<label>Jetstream Canvas</label>
<label>Jetstream for Salesforce</label>
<logoUrl>https://getjetstream.app/assets/images/jetstream-icon-pro-128.png</logoUrl>
<orgScopedExternalApp>00D4S000000pHDFUA2:Jetstream_Canvas</orgScopedExternalApp>
<orgScopedExternalApp>00Dam00001cYDdx:Jetstream_Canvas</orgScopedExternalApp>
</ExternalClientApplication>
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
<externalClientApplication>Jetstream_Canvas</externalClientApplication>
<isFirstPartyAppEnabled>false</isFirstPartyAppEnabled>
<label>Jetstream_Canvas_oauth</label>

<oauthLink>00Dam00001cYDdx:888am000001yNkL</oauthLink>
Comment thread
paustint marked this conversation as resolved.
</ExtlClntAppOauthSettings>
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8" ?>
<CustomObject xmlns="http://soap.sforce.com/2006/04/metadata">
<customSettingsType>Hierarchy</customSettingsType>
<description>Stores per-user preferences for the Jetstream Canvas App.</description>
<description>Stores per-user preferences for the Jetstream for Salesforce App.</description>
<enableFeeds>false</enableFeeds>
<label>Jetstream User Preferences</label>
<visibility>Public</visibility>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>ColorScheme__c</fullName>
<description>The user's preferred color scheme: light, dark, or system.</description>
<externalId>false</externalId>
<label>Color Scheme</label>
<length>10</length>
<required>false</required>
<type>Text</type>
<unique>false</unique>
</CustomField>
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8" ?>
<ApexPage xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>66.0</apiVersion>
<apiVersion>67.0</apiVersion>
<availableInTouch>false</availableInTouch>
<confirmationTokenRequired>false</confirmationTokenRequired>
<label>Jetstream</label>
Expand Down
Loading
Loading