Skip to content
Draft
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
4 changes: 4 additions & 0 deletions .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
name: CI-CD

permissions:
contents: read
packages: read

on:
push:
branches:
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
name: Deploy

permissions:
contents: read
packages: read

on:
workflow_call:
inputs:
Expand Down
2 changes: 2 additions & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@RaspberryPiFoundation:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${NPM_AUTH_TOKEN}
3 changes: 2 additions & 1 deletion .yarnrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ npmMinimalAgeGate: 7d

npmPreapprovedPackages:
- "@raspberrypifoundation/design-system-react"
- "@raspberrypifoundation/design-system-core"
- "@raspberrypifoundation/design-system-core"
- "@raspberrypifoundation/scratch-gui"
118 changes: 116 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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=<your-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.<timestamp>`). 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

Expand Down Expand Up @@ -227,6 +254,93 @@ Python code snippets are styled and syntax-highlighted using the `language-pytho
<code class="language-python">print('hello world')</code>
```

### 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 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.

**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
"@RaspberryPiFoundation/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 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

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. 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

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 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.

## 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
Expand Down
2 changes: 2 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
x-app: &x-app
build:
context: .
environment:
NPM_AUTH_TOKEN: ${NPM_AUTH_TOKEN}
volumes:
- .:/app
- node_modules:/app/node_modules
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions src/components/ScratchEditor/ScratchEditor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
8 changes: 8 additions & 0 deletions src/components/ScratchEditor/ScratchEditor.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down
2 changes: 1 addition & 1 deletion src/components/ScratchEditor/ScratchIntegrationHOC.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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" }),
Expand Down
2 changes: 1 addition & 1 deletion src/components/ScratchEditor/WrappedScratchGui.jsx
Original file line number Diff line number Diff line change
@@ -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";

Expand Down
4 changes: 2 additions & 2 deletions src/scratch.html
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
11 changes: 6 additions & 5 deletions webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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",
},
],
Expand Down
Loading