From 28114de04e870c125ebd7db783ad0194eea95b78 Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Mon, 12 Oct 2026 12:19:21 +0100 Subject: [PATCH 1/4] update docs on .openfn folder --- docs/build-for-developers/cli-sync.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/build-for-developers/cli-sync.md b/docs/build-for-developers/cli-sync.md index 432be5126c8..0171b77e8ea 100644 --- a/docs/build-for-developers/cli-sync.md +++ b/docs/build-for-developers/cli-sync.md @@ -83,7 +83,7 @@ Briefly, these files are: - The `.projects` folder contains a complete YAML representation of each Project. This is rarely used by humans - The `.openfn` folder contains metadata used by the CLI. It is not used by - humans and should not be checked-in to git. + humans. It should be checked into git for GitHub Sync to work properly. ::: note From b010c99dfc053bd4685c9a154600c4c61402e1eb Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Fri, 23 Oct 2026 12:02:35 +0100 Subject: [PATCH 2/4] update sync docs --- docs/manage-projects/link-to-gh.md | 168 +++++++++-------------------- static/img/github-options.webp | Bin 30340 -> 0 bytes 2 files changed, 50 insertions(+), 118 deletions(-) delete mode 100644 static/img/github-options.webp diff --git a/docs/manage-projects/link-to-gh.md b/docs/manage-projects/link-to-gh.md index 4ae70e41e6c..1ab7a0c12ca 100644 --- a/docs/manage-projects/link-to-gh.md +++ b/docs/manage-projects/link-to-gh.md @@ -52,40 +52,13 @@ To configure your project to use GitHub sync, follow these steps: 4. Choose your preferred repository and branch you'd like to connect your project to. -![Configure](/img/github-options.webp) +5. Choose the **direction** of the very _first_ sync action. You can either send + a copy of your OpenFn project to GitHub (this will not affect your project on + OpenFn), or you can import a project already on GitHub into OpenFn (which + will remove any existing workflows in the app and replace them with the + project on GitHub). -5. **_Optionally_**, if you _first want to sync from GitHub to OpenFn and - already have a config file_, add a filepath to an existing project - `config.json` file. - - :::caution Most users leave "Path to config" blank. - - This advanced feature allows you to connect to a GitHub repo that _already_ - has an OpenFn `project.yaml` and `config.json`. (Most people can skip this - step.) It's useful when you want the first sync to pull data from GitHub into - OpenFn. Most users opt to have the first sync come _from_ OpenFn and let the - app set up the required `config.json` and `project.yaml` files for them. - - ::: - -6. Choose the **direction** of the very _first_ sync action. I.e., when this - connection is established, do you want the integration to _first_ send a copy - of your OpenFn project to GitHub, or _first_ overwrite your existing OpenFn - project with an existing `project.yaml` from GitHub? - - :::warning Choosing to _first_ deploy from "GitHub to OpenFn" is destructive - - By default, we take what you've got in your current OpenFn project and send - it to GitHub to start the version control process. If you choose to instead - take an existing `project.yaml` file from GitHub and overwrite your current - OpenFn project, you won't be able to recover your existing workflows on - OpenFn. This is a feature that covers certain advanced use-cases, and unless - you know what you're doing you should start by syncing from "OpenFn to - GitHub". - - ::: - -7. Click **"Connect Branch & Initiate First Sync"** to finish. When you've done +6. Click **"Connect Branch & Initiate First Sync"** to finish. When you've done this, you can head over to GitHub (via the link provided) to view (and start working with) your OpenFn project as code. @@ -124,12 +97,15 @@ project. This sync pushes changes from your OpenFn project to GitHub. This sync operation will trigger an `openfn pull` action workflow on your connected GitHub repository, which will pull the latest configuration from the OpenFn app and -save it as code in the `project.yaml` file on your repository. +save it as code in your repository. See +[Project Structure](http://localhost:3000/documentation/sync#project-structure) +to understand what will be created in GitHub. -:::info +:::info The Portability Spec -Your OpenFn project can be represented as code and packaged as project.yaml -which is called the project spec. See the +Your OpenFn project can be represented as code. This representation of your +project excludes any details which are unique to your app, like credentials or +UUIDs. It is sometimes called the project spec. See the [portability documentation](/deploy/portability.md) to learn more. ::: @@ -171,103 +147,59 @@ To configure your project to sync to GitHub via the project settings: ### Sync from GitHub to OpenFn Use this sync method when you want to pull a version of your project from GitHub -into OpenFn. When this sync is triggered, `openfn-deploy` action is executed on -GitHub and your project spec _(file ending with `.yaml`)_ will be auto-deployed -to OpenFn. +into OpenFn. When this sync is triggered, the `openfn deploy` action is executed +on GitHub and the workflows defined there will be uploaded to OpenFn. -:::tip Considerations for syncing GitHub changes to OpenFn +### Advanced Sync Options -From v2.7.19, OpenFn deploy and pull actions now support the use of relative -paths in project spec. Consequently, projects with directory structure that uses -relative paths for job code in project spec, automatically get packaged and -deployed without the user having to copy changes into the project spec. This new -approach gives developers more flexibility to better manage their job code in -individual files rather than having all the code in the `projectSpec.yaml` file. +#### Use Legacy Formats -Learn more about relative paths and directory structure in -[portability documentation](/deploy/portability-v3.md#directory-structure). +Over time, we have used different file formats to represent an OpenFn project as +code. -::: - -### Using Sync v2 - -By default, GitHub Sync will use the legacy folder structure to represent your -project on GitHub. That folder structure is explained below - it'll create -`config.json`, `state.json` and `project.yaml` files to represent your Project -in your git repo. - -You can instead choose to use the v2 sync format, as described in the -[Sync](/build-for-developers/cli-sync.md) pages. This will automatically -"expand" your workflows and steps into files to be easily read and written, and -provides a much better developer experience. - -This v2 style will be made the default means of syncing Projects soon. +By default, since Lightning version v2.20.0, GitHub Sync expands your project +across the file system (see +[the docs](http://localhost:3000/documentation/sync#project-structure) for more +details), writing each workflow to a folder with a yaml workflow representation, +plus all job code in a `.js` file. This format is driven by an `openfn.yaml` +file at the root of the file system. -You can also create an empty `openfn.yaml` file in an already connected repo and -the next sync will generate the v2 file structures. +Prior to this, GitHub Sync wrote a project to three files: `config.json`, +`state.json` and `project.yaml`. -:::warning +This way of representing a project still works perfectly well and is fully +supported. Users who are reconnecting an existing Github Sync using this format +should enable legacy formats to reduce the size of the diff on GitHub created by +the next commit. -In Sync v1, you can have multiple 2-way syncs existing on the same branch in a -single repo. That's because each project creates its own set of sync artifacts -(config.json, project.yaml and state.json). Usually you'd want to do this to -sync your production and staging projects, or multiple sandboxes, to the same -GitHub repo. +#### Workspace folder -This doesn't work with the new sync protocol, because the new sync shares a -`workflows` folder. So each time GitHub pulls from your project, it'll overwrite -`workflows` and wipe out state from your other projects. +GitHub Sync works by writing a project representation to a single folder on the +file system (mostly in the `workflows` folder). -To do this in v2 Sync, you can: +By default, GitHub sync will read and write from the root of your repository. -- Maintain one two-way sync per branch. Each Sandbox maintains its own GitHub - Sync to a different branch in your repo. This works great because you can - compare differences between your sandbox and main project by comparing the - branches on git. -- Connect many Projects to a branch so long as they only sync one-way. This - works in a production environment where one Project is replicated over several - deployments, so committing to GitHub will trigger an update to all connected - Projects. This works so long as you can promise that no user will Save & Sync - back from the production projects. +For repos which are structured more like a monorepo, and contain several +projects, you can specify the path to a subfolder to use to sync this specific +project. The folder should point to where the `openfn.yaml` and `workflows` +folder lives for the correct project. -::: +Note that it is safe to connect a Sandbox to a branch and overrwite the +`workflows`folder for that branch. Sandboxes are designed to work without +setting a different workspace folder per sandbox. -## What is in your GitHub Repository? +#### Local Alias -:::info +When a project is written to GitHub, it is written with an alias. This is the +`main` in `main@app.openfn.org.yaml` file created in your `.projects` folder. -These docs describe the legacy format of GitHub Sync. The latest format is -described in the [CLI Sync](/build-for-developers/cli-sync.md) pages, and will -be used by default soon. +The alias is a shorthand used by CLI user to easily reference a project without +having to use its UUID. -::: +By default, a project will be assigned the alias `main`, and a sandbox will use +the sandbox name as an alias. -When you initiate a connection between OpenFn and your GitHub repository, a -config.json file containing reference to your project spec and project state -files, and the endpoint of your OpenFn deployment is automatically created in -your specified branch. By default, OpenFn will name all your files with your -project UUID on OpenFn, so you'll see files that look like this: - -```json -{ - "endpoint": "https://app.openfn.org", - "specPath": "openfn-fdfdf286-aa8e-4c9e-a1d2-89c1e6928a2a-spec.yaml", - "statePath": "openfn-fdfdf286-aa8e-4c9e-a1d2-89c1e6928a2a-state.json" -} -``` - -Users have the flexibility to edit the config.json file to suit their folder -structure so long as it is pointing to the right project spec, state, and OpenFn -endpoint. See example config.json file below with a custom name for the project -spec and project state. - -```json -{ - "endpoint": "https://app.openfn.org", - "statePath": "./custom-name-for-project-state.json", - "specPath": "./custom-name-for-project-spec.yaml" -} -``` +You can specify your own project alias by setting this value. ## Troubleshooting diff --git a/static/img/github-options.webp b/static/img/github-options.webp deleted file mode 100644 index b3d4e5511a59dec4eefbb8433ad6df2936c26754..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30340 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 From 9862c0ddaadaadb11a9a84e065d2a7866864a13c Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Fri, 23 Oct 2026 12:10:52 +0100 Subject: [PATCH 3/4] Tidy GitHub Sync page Fix typos, broken localhost/anchor links and heading level, and de-emphasise the legacy format in favour of the v2 default. Co-Authored-By: Claude Sonnet 5.5 --- docs/manage-projects/link-to-gh.md | 75 ++++++++++++++---------------- 1 file changed, 35 insertions(+), 40 deletions(-) diff --git a/docs/manage-projects/link-to-gh.md b/docs/manage-projects/link-to-gh.md index 1ab7a0c12ca..2a89de6033b 100644 --- a/docs/manage-projects/link-to-gh.md +++ b/docs/manage-projects/link-to-gh.md @@ -8,7 +8,7 @@ GitHub Sync enables two-way sync between an OpenFn project and your GitHub repository. This means changes made to a Project in OpenFn can commit back to your GitHub -repo; and commits pushed to GitHub can update the Project in your OpenFn App. +repo, and commits pushed to GitHub can update the Project in your OpenFn App. :::info For Cloud Hosted OpenFn Users @@ -17,13 +17,13 @@ Scale or Custom plans. ::: -### Configuring your Project to use GitHub Sync +## Configuring your Project to use GitHub Sync Users can configure their projects to access one or more repositories on GitHub. -Users must have admin access to the GitHub Repo to ensure the OpenFn application +Users must have admin access to the GitHub repo to ensure the OpenFn application is installed. -To configure your project to use GitHub sync, follow these steps: +To configure your project to use GitHub Sync, follow these steps: 1. Navigate to `Project Settings > Sync to GitHub`. @@ -40,7 +40,7 @@ To configure your project to use GitHub sync, follow these steps: If you don't see any installations, or those installations don't have access to the repositories you want, click the **"Create/update GitHub installations or modify permissions"** link to manage the OpenFn Installation on GitHub. - This would require you to grant permissions for OpenFn App to access your + This requires you to grant permissions for the OpenFn App to access your GitHub account and repository. See [Managing GitHub permissions](#managing-github-permissions) for help. @@ -87,10 +87,10 @@ interface. After clicking that link, you can follow the steps below: ## Using Version Control & Managing Changes -The `Sync to GitHub` feature makes use of GitHub actions to automatically deploy -(after a commit on GitHub) or pull (when **"Initiate Sync to Branch"** button is -clicked on OpenFn) project changes to keep a repository in sync with your OpenFn -project. +The `Sync to GitHub` feature makes use of GitHub Actions to automatically deploy +(after a commit on GitHub) or pull (when the **"Initiate Sync to Branch"** +button is clicked on OpenFn) project changes to keep a repository in sync with +your OpenFn project. ### Sync from OpenFn to GitHub @@ -98,8 +98,8 @@ This sync pushes changes from your OpenFn project to GitHub. This sync operation will trigger an `openfn pull` action workflow on your connected GitHub repository, which will pull the latest configuration from the OpenFn app and save it as code in your repository. See -[Project Structure](http://localhost:3000/documentation/sync#project-structure) -to understand what will be created in GitHub. +[Project Structure](/build-for-developers/cli-sync.md#project-structure) to +understand what will be created in GitHub. :::info The Portability Spec @@ -111,9 +111,9 @@ UUIDs. It is sometimes called the project spec. See the ::: After successfully setting up your project connection to GitHub as detailed -[above](#managing-github-permissions), you can initiate subsequent syncs either -via the Canvas, the Inspector or the version control page in the project -settings. +[above](#configuring-your-project-to-use-github-sync), you can initiate +subsequent syncs either via the Canvas, the Inspector or the version control +page in the project settings. To initiate a sync via the Canvas or Inspector, press `Ctrl+Shift+s`. (Or `⌘+Shift+s` on Mac; see [keyboard shortcuts](/keyboard-shortcuts.md).) You can @@ -127,13 +127,13 @@ you to customize the commit message. When you trigger `Save & Sync` in a workflow, your new changes and _previous_ uncommitted changes (if any) to your project's resources (including other -workflows) will be committed to GitHub. I.e., if there are other, uncommitted -changes by either you or someone else to other workflows in the project, they’ll -also show up in that sync. +workflows) will be committed to GitHub. That is, if there are other, uncommitted +changes by either you or someone else to other workflows in the project, they +will also show up in that sync. ::: -To configure your project to sync to GitHub via the project settings: +To sync to GitHub via the project settings: 1. Go to the Project where you made edits to your Workflow(s), and then navigate to the `Project Settings` page @@ -147,35 +147,30 @@ To configure your project to sync to GitHub via the project settings: ### Sync from GitHub to OpenFn Use this sync method when you want to pull a version of your project from GitHub -into OpenFn. When this sync is triggered, the `openfn deploy` action is executed -on GitHub and the workflows defined there will be uploaded to OpenFn. +into OpenFn. When this sync is triggered (by a commit to the connected branch), +the `openfn deploy` action is executed on GitHub and the workflows defined there +will be uploaded to OpenFn. -### Advanced Sync Options +### Advanced Settings #### Use Legacy Formats -Over time, we have used different file formats to represent an OpenFn project as -code. +GitHub Sync writes your project across the file system, with each workflow in +its own folder as a yaml file, plus all job code in `.js` files. This format is +driven by an `openfn.yaml` file at the root of the workspace. See +[Project Structure](/build-for-developers/cli-sync.md#project-structure) for +details. -By default, since Lightning version v2.20.0, GitHub Sync expands your project -across the file system (see -[the docs](http://localhost:3000/documentation/sync#project-structure) for more -details), writing each workflow to a folder with a yaml workflow representation, -plus all job code in a `.js` file. This format is driven by an `openfn.yaml` -file at the root of the file system. - -Prior to this, GitHub Sync wrote a project to three files: `config.json`, -`state.json` and `project.yaml`. - -This way of representing a project still works perfectly well and is fully -supported. Users who are reconnecting an existing Github Sync using this format -should enable legacy formats to reduce the size of the diff on GitHub created by -the next commit. +Earlier versions of GitHub Sync wrote a project to `config.json`, `state.json` +and `project.yaml` files instead. This format is still supported, and can be +selected with the **"Use legacy formats"** option. If you are reconnecting an +existing repo that uses it, enable this option to reduce the size of the diff +created by the next commit. #### Workspace folder GitHub Sync works by writing a project representation to a single folder on the -file system (mostly in the `workflows` folder). +file system (mostly in the `workflows` folder), known as the workspace. By default, GitHub sync will read and write from the root of your repository. @@ -185,7 +180,7 @@ project. The folder should point to where the `openfn.yaml` and `workflows` folder lives for the correct project. Note that it is safe to connect a Sandbox to a branch and overrwite the -`workflows`folder for that branch. Sandboxes are designed to work without +`workflows` folder for that branch. Sandboxes are designed to work without setting a different workspace folder per sandbox. #### Local Alias @@ -193,7 +188,7 @@ setting a different workspace folder per sandbox. When a project is written to GitHub, it is written with an alias. This is the `main` in `main@app.openfn.org.yaml` file created in your `.projects` folder. -The alias is a shorthand used by CLI user to easily reference a project without +The alias is a shorthand used by CLI users to easily reference a project without having to use its UUID. By default, a project will be assigned the alias `main`, and a sandbox will use From 45261ee895b3c1b48c819988b04916467c97434e Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Fri, 23 Oct 2026 12:19:30 +0100 Subject: [PATCH 4/4] Update tab name --- docs/manage-projects/link-to-gh.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/manage-projects/link-to-gh.md b/docs/manage-projects/link-to-gh.md index 2a89de6033b..16ccc6cc74b 100644 --- a/docs/manage-projects/link-to-gh.md +++ b/docs/manage-projects/link-to-gh.md @@ -25,7 +25,7 @@ is installed. To configure your project to use GitHub Sync, follow these steps: -1. Navigate to `Project Settings > Sync to GitHub`. +1. Navigate to `Project Settings > Sync`. 2. If you have not already connected your OpenFn user account to GitHub, do so by clicking the **"Connect your OpenFn account to GitHub"** button.