From 6d7d2ea89f6ca54346fb7e4fb54148ba36c4bc7b Mon Sep 17 00:00:00 2001 From: Nathan Richards Date: Tue, 19 May 2026 12:37:24 +0200 Subject: [PATCH 1/3] docs: add in instructions on how to link to scratch-editor for local development --- README.md | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/README.md b/README.md index 5d20b0ed8..3c9ae31a5 100644 --- a/README.md +++ b/README.md @@ -227,6 +227,93 @@ Python code snippets are styled and syntax-highlighted using the `language-pytho print('hello world') ``` +### Linking a local scratch-editor (Scratch GUI) + +When you are working on the [Raspberry Pi Foundation scratch-editor](https://github.com/RaspberryPiFoundation/scratch-editor) fork (for example changes to `scratch-gui`), you may want editor-ui to load that build instead of the `@scratch/scratch-gui` version from the npm registry. + +editor-ui does not bundle Scratch GUI into the main webpack app. It copies a prebuilt `dist/scratch-gui.js` (and static assets) from `node_modules` into the dev server output. Pointing the dependency at your local clone is enough to test GUI changes in the Scratch iframe. + +**Do not commit these linking changes.** They are temporary for local development only. These changes to `package.json`, `yarn.lock`, and `docker-compose.yml` are for local development only. + +#### Repository layout + +Clone both repositories as siblings: + +```text +Development/ + editor-ui/ + scratch-editor/ ← github.com/RaspberryPiFoundation/scratch-editor +``` + +#### Temporary changes in editor-ui + +**1. `package.json`** — replace the registry dependency with a file link: + +```json +"@scratch/scratch-gui": "file:../scratch-editor/packages/scratch-gui" +``` + +**2. `docker-compose.yml`** — mount the scratch-editor repo into the container (read-only): + +```yaml +volumes: + - .:/app + - ../scratch-editor:/scratch-editor:ro + - node_modules:/app/node_modules +``` + +From `/app` in the container, `file:../scratch-editor/packages/scratch-gui` resolves to `/scratch-editor/packages/scratch-gui` on the mounted volume. + +**3. `yarn.lock`** — run `yarn install` inside Docker (see below) and commit nothing; the lockfile will change while the link is active. Revert it when you restore the registry version in `package.json`. + +The fork’s package name is `@RaspberryPiFoundation/scratch-gui`; editor-ui still imports `@scratch/scratch-gui`. The file link installs under `@scratch/scratch-gui` in `node_modules` — no rename required in scratch-editor for local linking. + +#### Build scratch-gui + +Build on your machine (the `scratch-editor` mount is read-only inside the editor-ui container): + +```bash +cd ../scratch-editor +npm ci +npm run build -w @RaspberryPiFoundation/scratch-gui +``` + +After every scratch-gui code change, run the build again, then restart the editor-ui container so webpack copies the new `dist/scratch-gui.js`. + +#### Run with Docker + +**editor-api** (Scratch projects and assets API): + +```bash +cd ../editor-api +docker compose up +``` + +**editor-ui**: + +```bash +cd ../editor-ui +docker compose up +``` + +The container runs `yarn install` then `yarn start` on each start. The first start after switching to the file link may take longer while dependencies are linked. + +#### Verify in the browser + +Scratch runs in an iframe served from editor-ui (port **3011**), not from the main web-component bundle alone. + +- Web component test page: open a **Scratch** sample, e.g. + `http://localhost:3011/web-component.html` + then choose **cool-scratch**. +- **editor-standalone** (port **3012**): also loads the web component from `http://localhost:3011`; open a Scratch project under `/en-US/projects/…` with editor-ui and editor-api running. + +#### Revert when finished + +1. Restore `"@scratch/scratch-gui": "^13.0.0"` (or the version your branch pins) in `package.json`. +2. Remove the `../scratch-editor:/scratch-editor:ro` volume from `docker-compose.yml`. +3. Revert `yarn.lock` (e.g. `git checkout -- yarn.lock`) or run `yarn install` again after restoring `package.json`. +4. Restart `docker compose up` in editor-ui. + ## Deployment Deployment is managed through Github actions. The UI is deployed to staging and production environments through an S3 bucket, managed via Cloudflare. This requires the following environment variables to be set From d47c852a8c136bc4fc6abf9245feca1c002e02b4 Mon Sep 17 00:00:00 2001 From: Nathan Richards Date: Tue, 19 May 2026 18:01:38 +0200 Subject: [PATCH 2/3] feat: set the host url for assets --- src/components/ScratchEditor/ScratchEditor.jsx | 2 ++ src/components/ScratchEditor/ScratchEditor.test.jsx | 8 ++++++++ src/scratch.html | 4 ++-- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/components/ScratchEditor/ScratchEditor.jsx b/src/components/ScratchEditor/ScratchEditor.jsx index dd29f75c3..64110f9bd 100644 --- a/src/components/ScratchEditor/ScratchEditor.jsx +++ b/src/components/ScratchEditor/ScratchEditor.jsx @@ -79,6 +79,8 @@ const ScratchEditor = ({ menuBarHidden={true} projectHost={`${apiUrl}/api/scratch/projects`} assetHost={`${apiUrl}/api/scratch/assets`} + libraryAssetHost={`${apiUrl}/api/scratch/assets`} + libraryAssetsFetchWithHeaders basePath={`${process.env.ASSETS_URL}/scratch-gui/`} onStorageInit={(storage) => { scratchFetchApiRef.current = storage.scratchFetch; diff --git a/src/components/ScratchEditor/ScratchEditor.test.jsx b/src/components/ScratchEditor/ScratchEditor.test.jsx index 30d4fd320..42956521c 100644 --- a/src/components/ScratchEditor/ScratchEditor.test.jsx +++ b/src/components/ScratchEditor/ScratchEditor.test.jsx @@ -32,6 +32,14 @@ describe("ScratchEditor", () => { ); expect(getByTestId("wrapped-scratch-gui")).toBeTruthy(); + expect(mockWrappedScratchGui).toHaveBeenCalledWith( + expect.objectContaining({ + assetHost: "https://api.example.com/api/scratch/assets", + libraryAssetHost: "https://api.example.com/api/scratch/assets", + libraryAssetsFetchWithHeaders: true, + projectHost: "https://api.example.com/api/scratch/projects", + }), + ); }); test("routes project saves through scratchFetch metadata after storage init", async () => { diff --git a/src/scratch.html b/src/scratch.html index 609a8734d..ffbb657dd 100644 --- a/src/scratch.html +++ b/src/scratch.html @@ -14,8 +14,8 @@ worker-src 'self' blob:; child-src 'self' blob:; connect-src 'self' <%= cspApiMultipleOrigins || cspApiOrigin %> <%= cspAssetOrigin %> <%= isDev ? "ws: wss:" : "" %>; - img-src 'self' data: blob: <%= cspAssetOrigin %>; - media-src 'self' blob: <%= cspAssetOrigin %>; + img-src 'self' data: blob: <%= cspApiMultipleOrigins || cspApiOrigin %> <%= cspAssetOrigin %>; + media-src 'self' blob: <%= cspApiMultipleOrigins || cspApiOrigin %> <%= cspAssetOrigin %>; font-src 'self' data: <%= cspAssetOrigin %>; form-action 'self'; upgrade-insecure-requests; From 534c7c24f962f46568d1624cacfff72bbaafd715 Mon Sep 17 00:00:00 2001 From: Nathan Richards Date: Wed, 20 May 2026 19:31:27 +0200 Subject: [PATCH 3/3] chore: initial draft for using @RaspberryPiFoundation/scratch-gui --- .github/workflows/ci-cd.yml | 4 ++ .github/workflows/deploy.yml | 4 ++ .npmrc | 2 + .yarnrc.yml | 3 +- README.md | 41 +++++++++++++++---- docker-compose.yml | 2 + package.json | 2 +- .../ScratchEditor/ScratchIntegrationHOC.jsx | 2 +- .../ScratchIntegrationHOC.test.jsx | 2 +- .../ScratchEditor/WrappedScratchGui.jsx | 2 +- webpack.config.js | 11 ++--- 11 files changed, 58 insertions(+), 17 deletions(-) create mode 100644 .npmrc diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 1eabf0edd..750965eef 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -1,5 +1,9 @@ name: CI-CD +permissions: + contents: read + packages: read + on: push: branches: diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index b9e916af8..1f4931ced 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,5 +1,9 @@ name: Deploy +permissions: + contents: read + packages: read + on: workflow_call: inputs: diff --git a/.npmrc b/.npmrc new file mode 100644 index 000000000..5cac20b35 --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +@RaspberryPiFoundation:registry=https://npm.pkg.github.com +//npm.pkg.github.com/:_authToken=${NPM_AUTH_TOKEN} diff --git a/.yarnrc.yml b/.yarnrc.yml index 7b135dac3..e69932c71 100644 --- a/.yarnrc.yml +++ b/.yarnrc.yml @@ -12,4 +12,5 @@ npmMinimalAgeGate: 7d npmPreapprovedPackages: - "@raspberrypifoundation/design-system-react" - - "@raspberrypifoundation/design-system-core" \ No newline at end of file + - "@raspberrypifoundation/design-system-core" + - "@raspberrypifoundation/scratch-gui" \ No newline at end of file diff --git a/README.md b/README.md index 3c9ae31a5..138aba606 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,42 @@ This project provides a web component containing the Raspberry Pi Code Editor for use on other sites. Although originally bootstrapped with [Create React App](https://github.com/facebook/create-react-app), the application has been ejected so all the build scripts etc. are now in the repo. +## GitHub Packages authentication + +Some dependencies are published to [GitHub Packages](https://github.com/orgs/RaspberryPiFoundation/packages), including: + +- [`@RaspberryPiFoundation/scratch-gui`](https://github.com/RaspberryPiFoundation/scratch-editor/pkgs/npm/scratch-gui) (Scratch editor UI) +- [`@raspberrypifoundation/design-system-react`](https://github.com/RaspberryPiFoundation/design-system-react) + +1. Create a GitHub personal access token with the `read:packages` scope. +2. Export it for Yarn (the committed `.npmrc` reads this variable): + + ```bash + export NPM_AUTH_TOKEN= + ``` + +3. Install dependencies with Yarn 4: + + ```bash + yarn install + ``` + +CI uses `GITHUB_TOKEN` automatically; you only need `NPM_AUTH_TOKEN` for local development (and Docker — see below). + +**Scratch GUI version:** `package.json` pins `@RaspberryPiFoundation/scratch-gui` to a GitHub Packages release (e.g. `13.7.3-experience-cs.`). Until that package is published: + +- Do **not** run `yarn install` expecting the pin to resolve — `yarn.lock` is unchanged and CI `yarn install --immutable` will fail until you replace `PLACEHOLDER` with the real version from [scratch-gui packages](https://github.com/RaspberryPiFoundation/scratch-editor/pkgs/npm/scratch-gui), run `yarn install`, and commit `yarn.lock`. +- To work on scratch-gui **before** publish, use the [local scratch-editor file link](#linking-a-local-scratch-editor-scratch-gui) below instead of the registry pin. + ## Install dependencies -This repository uses Yarn (see `package.json` → `packageManager`). Please install dependencies with Yarn: +This repository uses Yarn 4 (see `package.json` → `packageManager` and `.yarnrc.yml`). Install dependencies with: ``` yarn install ``` -Using `npm install` can fail due to strict peer-dependency resolution in npm for some legacy packages in this project. +Do not use `npm install` — it can fail due to strict peer-dependency resolution for some legacy packages in this project. ## Environment variables @@ -229,7 +256,7 @@ Python code snippets are styled and syntax-highlighted using the `language-pytho ### Linking a local scratch-editor (Scratch GUI) -When you are working on the [Raspberry Pi Foundation scratch-editor](https://github.com/RaspberryPiFoundation/scratch-editor) fork (for example changes to `scratch-gui`), you may want editor-ui to load that build instead of the `@scratch/scratch-gui` version from the npm registry. +When you are working on the [Raspberry Pi Foundation scratch-editor](https://github.com/RaspberryPiFoundation/scratch-editor) fork (for example changes to `scratch-gui`), you may want editor-ui to load that build instead of the published `@RaspberryPiFoundation/scratch-gui` package from GitHub Packages. editor-ui does not bundle Scratch GUI into the main webpack app. It copies a prebuilt `dist/scratch-gui.js` (and static assets) from `node_modules` into the dev server output. Pointing the dependency at your local clone is enough to test GUI changes in the Scratch iframe. @@ -250,7 +277,7 @@ Development/ **1. `package.json`** — replace the registry dependency with a file link: ```json -"@scratch/scratch-gui": "file:../scratch-editor/packages/scratch-gui" +"@RaspberryPiFoundation/scratch-gui": "file:../scratch-editor/packages/scratch-gui" ``` **2. `docker-compose.yml`** — mount the scratch-editor repo into the container (read-only): @@ -266,7 +293,7 @@ From `/app` in the container, `file:../scratch-editor/packages/scratch-gui` reso **3. `yarn.lock`** — run `yarn install` inside Docker (see below) and commit nothing; the lockfile will change while the link is active. Revert it when you restore the registry version in `package.json`. -The fork’s package name is `@RaspberryPiFoundation/scratch-gui`; editor-ui still imports `@scratch/scratch-gui`. The file link installs under `@scratch/scratch-gui` in `node_modules` — no rename required in scratch-editor for local linking. +The file link uses the same package name as the published dependency (`@RaspberryPiFoundation/scratch-gui`). GitHub Packages authentication is not required for scratch-gui while the file link is active (you may still need `NPM_AUTH_TOKEN` for other dependencies). #### Build scratch-gui @@ -296,7 +323,7 @@ cd ../editor-ui docker compose up ``` -The container runs `yarn install` then `yarn start` on each start. The first start after switching to the file link may take longer while dependencies are linked. +The container runs `yarn install` then `yarn start` on each start. Pass `NPM_AUTH_TOKEN` on the host (see [GitHub Packages authentication](#github-packages-authentication)) so the container can install dependencies from GitHub Packages. The first start after switching to the file link may take longer while dependencies are linked. #### Verify in the browser @@ -309,7 +336,7 @@ Scratch runs in an iframe served from editor-ui (port **3011**), not from the ma #### Revert when finished -1. Restore `"@scratch/scratch-gui": "^13.0.0"` (or the version your branch pins) in `package.json`. +1. Restore the published `@RaspberryPiFoundation/scratch-gui` version pin in `package.json` (see [GitHub Packages authentication](#github-packages-authentication)). 2. Remove the `../scratch-editor:/scratch-editor:ro` volume from `docker-compose.yml`. 3. Revert `yarn.lock` (e.g. `git checkout -- yarn.lock`) or run `yarn install` again after restoring `package.json`. 4. Restart `docker compose up` in editor-ui. diff --git a/docker-compose.yml b/docker-compose.yml index d1ecbd1ff..ce87e03d4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,8 @@ x-app: &x-app build: context: . + environment: + NPM_AUTH_TOKEN: ${NPM_AUTH_TOKEN} volumes: - .:/app - node_modules:/app/node_modules diff --git a/package.json b/package.json index f576d96bd..543a41f09 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "@react-three/fiber": "^8.0.13", "@reduxjs/toolkit": "^1.6.2", "@replit/codemirror-indentation-markers": "^6.1.0", - "@scratch/scratch-gui": "^13.0.0", + "@RaspberryPiFoundation/scratch-gui": "13.7.3-experience-cs.PLACEHOLDER", "@sentry/browser": "^7.17.3", "@sentry/react": "7.16.0", "@sentry/tracing": "7.16.0", diff --git a/src/components/ScratchEditor/ScratchIntegrationHOC.jsx b/src/components/ScratchEditor/ScratchIntegrationHOC.jsx index 34b257565..a6cb74c51 100644 --- a/src/components/ScratchEditor/ScratchIntegrationHOC.jsx +++ b/src/components/ScratchEditor/ScratchIntegrationHOC.jsx @@ -6,7 +6,7 @@ import { remixProject, manualUpdateProject, setStageSize, -} from "@scratch/scratch-gui"; +} from "@RaspberryPiFoundation/scratch-gui"; import { allowedIframeHost } from "../../utils/iframeUtils"; import { postScratchGuiEvent } from "./events.js"; diff --git a/src/components/ScratchEditor/ScratchIntegrationHOC.test.jsx b/src/components/ScratchEditor/ScratchIntegrationHOC.test.jsx index 516cca6fd..d1f5072ff 100644 --- a/src/components/ScratchEditor/ScratchIntegrationHOC.test.jsx +++ b/src/components/ScratchEditor/ScratchIntegrationHOC.test.jsx @@ -5,7 +5,7 @@ const configureStore = require("redux-mock-store").default; jest.mock("file-saver", () => ({ saveAs: jest.fn() })); jest.mock("./events.js", () => ({ postScratchGuiEvent: jest.fn() })); -jest.mock("@scratch/scratch-gui", () => ({ +jest.mock("@RaspberryPiFoundation/scratch-gui", () => ({ remixProject: () => ({ type: "remix" }), manualUpdateProject: () => ({ type: "manualUpdate" }), setStageSize: () => ({ type: "setStageSize" }), diff --git a/src/components/ScratchEditor/WrappedScratchGui.jsx b/src/components/ScratchEditor/WrappedScratchGui.jsx index 5f30e29cd..f8e647766 100644 --- a/src/components/ScratchEditor/WrappedScratchGui.jsx +++ b/src/components/ScratchEditor/WrappedScratchGui.jsx @@ -1,4 +1,4 @@ -import GUI, { AppStateHOC } from "@scratch/scratch-gui"; +import GUI, { AppStateHOC } from "@RaspberryPiFoundation/scratch-gui"; import ScratchIntegrationHOC from "./ScratchIntegrationHOC.jsx"; import { compose } from "redux"; diff --git a/webpack.config.js b/webpack.config.js index 2a95ff47b..8080920f3 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -50,12 +50,12 @@ const cspApiMultipleOrigins = String(process.env.CSP_API_MULTIPLE_ORIGINS || "") const scratchStaticDir = path.resolve( __dirname, - "node_modules/@scratch/scratch-gui/dist/static", + "node_modules/@RaspberryPiFoundation/scratch-gui/dist/static", ); const scratchChunkDir = path.resolve( __dirname, - "node_modules/@scratch/scratch-gui/dist/chunks", + "node_modules/@RaspberryPiFoundation/scratch-gui/dist/chunks", ); const moduleRules = [ @@ -255,7 +255,8 @@ const scratchConfig = { }, externals: [ function ({ request }, callback) { - if (request === "@scratch/scratch-gui") return callback(null, "GUI"); + if (request === "@RaspberryPiFoundation/scratch-gui") + return callback(null, "GUI"); if (request === "react") return callback(null, "React"); if (request === "react-dom" || request.startsWith("react-dom/")) return callback(null, "ReactDOM"); @@ -304,11 +305,11 @@ const scratchConfig = { to: "vendor/react-redux.min.js", }, { - from: "node_modules/@scratch/scratch-gui/dist/scratch-gui.js", + from: "node_modules/@RaspberryPiFoundation/scratch-gui/dist/scratch-gui.js", to: "vendor/scratch-gui.js", }, { - from: "node_modules/@scratch/scratch-gui/dist/scratch-gui.js.LICENSE.txt", + from: "node_modules/@RaspberryPiFoundation/scratch-gui/dist/scratch-gui.js.LICENSE.txt", to: "vendor/scratch-gui.js.LICENSE.txt", }, ],