diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6ad50ea996e..e38cd5e7b2d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,6 +17,10 @@ on: type: boolean default: false +permissions: + contents: read + packages: write + concurrency: group: "${{ github.workflow }} @ ${{ github.event.compare || github.head_ref || github.ref }}" cancel-in-progress: true @@ -34,6 +38,7 @@ jobs: with: cache: 'npm' node-version-file: '.nvmrc' + registry-url: https://npm.pkg.github.com - name: Debug Info # https://docs.github.com/en/actions/reference/security/secure-use#use-an-intermediate-environment-variable env: @@ -68,8 +73,14 @@ jobs: fi - if: ${{ steps.filter.outputs.any-workspace == 'true' }} + env: + NPM_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} uses: ./.github/actions/install-dependencies + - name: Install Rolldown linux binaries + if: ${{ steps.filter.outputs.any-workspace == 'true' }} + run: npm install @rolldown/binding-linux-x64-gnu --no-save --ignore-scripts + # TODO: Packages currently depend on each other's compiled dist at build time. Fix that and matrix the builds. - name: Build Packages if: ${{ steps.filter.outputs.any-workspace == 'true' }} @@ -86,6 +97,16 @@ jobs: packages/**/dist packages/**/playground + - name: Publish scratch-gui to GitHub Packages + if: ${{ github.ref == 'refs/heads/experience-cs' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') }} + working-directory: ./packages/scratch-gui + env: + NPM_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + RELEASE_VERSION="13.7.3-experience-cs.$(date +'%Y%m%d%H%M%S')" + npm version --no-git-tag-version "$RELEASE_VERSION" + npm publish --access public --tag latest + test: name: Test needs: build diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml deleted file mode 100644 index e14c991145f..00000000000 --- a/.github/workflows/publish.yml +++ /dev/null @@ -1,235 +0,0 @@ -name: Publish - -on: - release: - types: [published] - -jobs: - build: - name: Build - runs-on: ubuntu-latest - outputs: - packages: ${{ steps.list-packages.outputs.packages }} - steps: - - name: Debug Info - # https://docs.github.com/en/actions/reference/security/secure-use#use-an-intermediate-environment-variable - env: - # `env:` values are printed to the log even without using them in `run:` - RELEASE_TAG_NAME: ${{ github.event.release.tag_name }} - RELEASE_TARGET_COMMITISH: ${{ github.event.release.target_commitish }} - run: | - cat <> "$GITHUB_OUTPUT" - - - name: Store Build Artifacts - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 - with: - name: build - retention-days: 90 - path: | - packages/**/build - packages/**/dist - packages/**/playground - - - name: Store Package Manifests - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 - with: - name: package-manifests - retention-days: 90 - path: | - package.json - package-lock.json - packages/*/package.json - - test: - needs: build - uses: ./.github/workflows/test-packages.yml - with: - packages: ${{ needs.build.outputs.packages }} - - preview: - needs: build - uses: ./.github/workflows/deploy-playground-preview.yml - with: - destination_dir: ${{ github.event.release.target_commitish }} - full_commit_message: "Build for release ${{ github.event.release.tag_name }}" - - cd: - name: Publish to npm - needs: - - build - - test - if: ${{ !cancelled() && needs.build.result == 'success' && needs.test.result == 'success' }} - runs-on: ubuntu-latest - permissions: - contents: write # to push the version commit and tag - issues: write # to comment on issues when a fix is released - pull-requests: write # to comment on PRs when a fix is released - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - steps: - - name: Determine NPM Tag - shell: bash - env: - TARGET_COMMITISH: ${{ github.event.release.target_commitish }} - IS_PRERELEASE: ${{ github.event.release.prerelease }} - run: | - case "$TARGET_COMMITISH" in - develop | main | master) - if [[ "$IS_PRERELEASE" == true ]]; then - npm_tag=beta - else - npm_tag=latest - fi - ;; - *) - # use the branch name as the npm tag - npm_tag="$TARGET_COMMITISH" - ;; - esac - echo "Determined NPM tag: [$npm_tag]" - echo "NPM_TAG=${npm_tag}" >> "$GITHUB_ENV" - - - name: Check NPM Tag - run: | - if [ -z "$NPM_TAG" ]; then - echo "Refusing to publish with empty NPM tag." - exit 1 - fi - - - name: Configure GitHub User - shell: bash - run: | - git config --global user.name 'GitHub Actions' - git config --global user.email 'github-actions@localhost' - - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - with: - token: ${{ secrets.PAT_RELEASE_PUSH }} # persists the token for pushing to the repo later - - - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6 - with: - cache: 'npm' - node-version-file: '.nvmrc' - registry-url: 'https://registry.npmjs.org' - - - uses: ./.github/actions/install-dependencies - - - name: Download Package Manifests - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 - with: - name: package-manifests - path: . - - - name: Commit Version Bump - shell: bash - env: - GIT_TAG: ${{ github.event.release.tag_name }} - run: | - NEW_VERSION="${GIT_TAG/v/}" - git add package.json package-lock.json packages/*/package.json - git commit -m "chore(release): $NEW_VERSION [skip ci]" - - - name: Download Build Artifacts - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 - with: - name: build - path: packages - - - name: Publish scratch-svg-renderer - run: npm publish --access=public --tag="$NPM_TAG" --ignore-scripts --workspace=@scratch/scratch-svg-renderer - - - name: Publish scratch-render - run: npm publish --access=public --tag="$NPM_TAG" --ignore-scripts --workspace=@scratch/scratch-render - - - name: Publish scratch-vm - run: npm publish --access=public --tag="$NPM_TAG" --ignore-scripts --workspace=@scratch/scratch-vm - - - name: Publish scratch-gui - run: | - cp ./packages/scratch-gui/package.json ./packages/scratch-gui/package-copy.json - - jq 'del(.exports["./standalone"])' ./packages/scratch-gui/package.json | npx sponge ./packages/scratch-gui/package.json - - npm publish --access=public --tag="$NPM_TAG" --ignore-scripts --workspace=@scratch/scratch-gui - - mv ./packages/scratch-gui/package-copy.json ./packages/scratch-gui/package.json - - - name: Publish scratch-gui-standalone - run: | - bash ./scripts/prepare-standalone-gui.sh - npm publish --access=public --tag="$NPM_TAG" --ignore-scripts --workspace=@scratch/scratch-gui-standalone - - - name: Publish task-herder - run: npm publish --access=public --tag="$NPM_TAG" --ignore-scripts --workspace=@scratch/task-herder - - - name: Publish scratch-media-lib-scripts - run: | - npm run build --workspace @scratch/scratch-media-lib-scripts - npm publish --access=public --tag="$NPM_TAG" --workspace=@scratch/scratch-media-lib-scripts - env: - NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} - - - name: Push to Develop - shell: bash - env: - TAG_NAME: ${{ github.event.release.tag_name }} - run: | - git fetch origin develop - - # HEAD is the version bump commit; HEAD^ is the release target commit - RELEASE_TARGET_COMMIT="$(git rev-parse HEAD^)" - DEVELOP_COMMIT_ID="$(git rev-parse origin/develop)" - - if [ "$RELEASE_TARGET_COMMIT" = "$DEVELOP_COMMIT_ID" ]; then - git push origin HEAD:develop - else - echo "Not pushing to develop because the tag we're operating on is behind" - fi - - # See https://stackoverflow.com/a/24849501 - - name: Change Connected Commit on Release - shell: bash - env: - TAG_NAME: ${{ github.event.release.tag_name }} - run: | - git tag -f "$TAG_NAME" HEAD - git push -f origin "refs/tags/$TAG_NAME" - - - name: Comment on Resolved Issues and Merged PRs - if: ${{ !github.event.release.prerelease }} - continue-on-error: true - uses: apexskier/github-release-commenter@e7813a9625eabd79a875b4bc4046cfcae377ab34 # v1 - with: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - skip-label: dependencies - comment-template: | - :tada: This is included in release {release_link}. diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000000..5cac20b354c --- /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/README.md b/README.md index 309bbff6210..8a1ee14b08e 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,46 @@ Scratch project by pressing "Create" on that website or by visiting =8.0.0" } }, - "node_modules/@scratch/scratch-gui": { - "resolved": "packages/scratch-gui", - "link": true - }, "node_modules/@scratch/scratch-media-lib-scripts": { "resolved": "packages/scratch-media-lib-scripts", "link": true @@ -34406,7 +34406,6 @@ "dev": true, "license": "BSD-2-Clause", "optional": true, - "peer": true, "bin": { "uglifyjs": "bin/uglifyjs" }, @@ -36168,7 +36167,7 @@ } }, "packages/scratch-gui": { - "name": "@scratch/scratch-gui", + "name": "@RaspberryPiFoundation/scratch-gui", "version": "13.7.3", "license": "AGPL-3.0-only", "dependencies": { diff --git a/packages/scratch-gui/package.json b/packages/scratch-gui/package.json index 78902c67653..4e9a1891d03 100644 --- a/packages/scratch-gui/package.json +++ b/packages/scratch-gui/package.json @@ -1,15 +1,18 @@ { - "name": "@scratch/scratch-gui", + "name": "@RaspberryPiFoundation/scratch-gui", "version": "13.7.3", "description": "Graphical User Interface for creating and running Scratch 3.0 projects", "keywords": [], - "homepage": "https://github.com/scratchfoundation/scratch-gui#readme", + "homepage": "https://github.com/RaspberryPiFoundation/scratch-editor#readme", "bugs": { "url": "https://github.com/scratchfoundation/scratch-editor/issues" }, "repository": { "type": "git", - "url": "git+https://github.com/scratchfoundation/scratch-editor.git" + "url": "https://github.com/RaspberryPiFoundation/scratch-editor.git" + }, + "publishConfig": { + "registry": "https://npm.pkg.github.com" }, "license": "AGPL-3.0-only", "author": "Massachusetts Institute of Technology", diff --git a/packages/scratch-gui/src/components/gui/gui.jsx b/packages/scratch-gui/src/components/gui/gui.jsx index 63e59698806..694d64667b9 100644 --- a/packages/scratch-gui/src/components/gui/gui.jsx +++ b/packages/scratch-gui/src/components/gui/gui.jsx @@ -47,6 +47,7 @@ import {setPlatform} from '../../reducers/platform.js'; import {setTheme} from '../../reducers/settings.js'; import {PLATFORM} from '../../lib/platform.js'; import {ModalFocusProvider} from '../../contexts/modal-focus-context.jsx'; +import {LibraryAssetConfigProvider} from '../../contexts/library-asset-config-context.jsx'; const ariaMessages = defineMessages({ menuBar: { @@ -205,6 +206,8 @@ const GUIComponent = props => { username, userOwnsProject, hideTutorialProjects, + libraryAssetHost, + libraryAssetsFetchWithHeaders, vm, ...componentProps } = omit(props, 'dispatch', 'setPlatform'); @@ -272,305 +275,311 @@ const GUIComponent = props => { ) : ( - - {telemetryModalVisible ? ( - - ) : null} - {loading ? ( - - ) : null} - {isCreating ? ( - - ) : null} - {isRendererSupported ? null : ( - - )} - {tipsLibraryVisible ? ( - - ) : null} - {cardsVisible ? ( - - ) : null} - {alertsVisible ? ( - - ) : null} - {connectionModalVisible ? ( - - ) : null} - {costumeLibraryVisible ? ( - - ) : null} - {} - {backdropLibraryVisible ? ( - - ) : null} - {!menuBarHidden && } - - - + {telemetryModalVisible ? ( + + ) : null} + {loading ? ( + + ) : null} + {isCreating ? ( + + ) : null} + {isRendererSupported ? null : ( + + )} + {tipsLibraryVisible ? ( + + ) : null} + {cardsVisible ? ( + + ) : null} + {alertsVisible ? ( + + ) : null} + {connectionModalVisible ? ( + + ) : null} + {costumeLibraryVisible ? ( + + ) : null} + {} + {backdropLibraryVisible ? ( + + ) : null} + {!menuBarHidden && } + + - - - - - - - - - {targetIsStage ? ( + + + + + + {targetIsStage ? ( + + ) : ( + + )} + + + - ) : ( - )} - - + + + + - - - - - - - - + - - - - - - - - {costumesTabVisible ? : null} - - - {soundsTabVisible ? - + + + + + {costumesTabVisible ? : null} - - - {backpackVisible && backpackConfigured ? ( - - ) : null} - + + + {soundsTabVisible ? + : null} + + + {backpackVisible && backpackConfigured ? ( + + ) : null} + - - - + + + + - - + ); }}); @@ -618,6 +627,8 @@ GUIComponent.propTypes = { isRtl: PropTypes.bool, isShared: PropTypes.bool, isTotallyNormal: PropTypes.bool, + libraryAssetHost: PropTypes.string, + libraryAssetsFetchWithHeaders: PropTypes.bool, loading: PropTypes.bool, logo: PropTypes.string, manuallySaveThumbnails: PropTypes.bool, @@ -696,6 +707,7 @@ GUIComponent.defaultProps = { showComingSoon: false, showNewFeatureCallouts: false, stageSizeMode: STAGE_SIZE_MODES.large, + libraryAssetsFetchWithHeaders: false, useExternalPeripheralList: false }; diff --git a/packages/scratch-gui/src/components/library-item/library-item.jsx b/packages/scratch-gui/src/components/library-item/library-item.jsx index 0d2490e997a..a3cd5017567 100644 --- a/packages/scratch-gui/src/components/library-item/library-item.jsx +++ b/packages/scratch-gui/src/components/library-item/library-item.jsx @@ -15,6 +15,7 @@ import memberAssetIconURL from './lib-icon--member-asset.svg'; import intlShape from '../../lib/intlShape'; import {PLATFORM} from '../../lib/platform.js'; +import {LibraryAssetConfigContext} from '../../contexts/library-asset-config-context.jsx'; const messages = defineMessages({ memberAssetImgAlt: { @@ -23,7 +24,29 @@ const messages = defineMessages({ id: 'gui.libraryItem.memberAssetImgAlt' } }); - + +/** + * Load via scratchStorage (ScratchImage) when assets need auth/project headers or + * are bundled for native offline use. Otherwise use a direct src (MIT CDN or rawURL). + * @param {string} platform - PLATFORM value from scratch-gui state. + * @param {boolean} libraryAssetsFetchWithHeaders - Load via scratchStorage so fetch can send headers. + * @param {object} imageSource - uri and/or assetId, assetType, assetServiceUri. + * @returns {boolean} True when ScratchImage should load the thumbnail. + */ +const shouldLoadLibraryImageViaStorage = function (platform, libraryAssetsFetchWithHeaders, imageSource) { + if (!imageSource.assetId || !imageSource.assetType) { + return false; + } + if (platform === PLATFORM.ANDROID || platform === PLATFORM.DESKTOP) { + return true; + } + return Boolean(libraryAssetsFetchWithHeaders); +}; + +const getLibraryImageSrc = function (imageSource) { + return imageSource.uri ?? imageSource.assetServiceUri; +}; + class LibraryItemComponent extends React.PureComponent { constructor (props) { super(props); @@ -31,17 +54,22 @@ class LibraryItemComponent extends React.PureComponent { 'renderImage' ]); } + + static contextType = LibraryAssetConfigContext; renderImage (className, imageSource) { // Scratch Android and Scratch Desktop assume the user is offline and has // local access to the image assets. In those cases we use the `ScratchImage` - // component which loads the local assets by using a queue. In Scratch Web - // we don't have the assets locally and want to directly download them from - // the assets service. + // component which loads the local assets by using a queue. + // In Scratch Web we don't have the assets locally and want to directly download + // them from the assets service via when using the + // public MIT CDN. When libraryAssetsFetchWithHeaders is set (e.g. RPF editor-api), + // load via scratchStorage instead so requests include specified headers. // TODO: Abstract this logic in the `ScratchImage` component itself. - const url = imageSource.uri ?? imageSource.assetServiceUri; - - if (this.props.platform === PLATFORM.ANDROID || - this.props.platform === PLATFORM.DESKTOP) { + if (shouldLoadLibraryImageViaStorage( + this.props.platform, + this.context.libraryAssetsFetchWithHeaders, + imageSource + )) { return (); } render () { diff --git a/packages/scratch-gui/src/components/library/library.jsx b/packages/scratch-gui/src/components/library/library.jsx index 9ac67b6d38d..65fa401e087 100644 --- a/packages/scratch-gui/src/components/library/library.jsx +++ b/packages/scratch-gui/src/components/library/library.jsx @@ -11,6 +11,8 @@ import Divider from '../divider/divider.jsx'; import Filter from '../filter/filter.jsx'; import TagButton from '../../containers/tag-button.jsx'; import {legacyConfig} from '../../legacy-config'; +import {buildLibraryAssetServiceUri} from '../../lib/library-asset-url.js'; +import {LibraryAssetConfigContext} from '../../contexts/library-asset-config-context.jsx'; import Spinner from '../spinner/spinner.jsx'; import {CATEGORIES} from '../../../src/lib/libraries/decks/index.jsx'; @@ -89,12 +91,13 @@ const getAssetTypeForFileExtension = function (fileExtension) { * Otherwise it'll return just one `imageSource`. * @param {object} item - either a library item or one of a library item's costumes. * The latter is used internally as part of processing an animated thumbnail. + * @param {string} libraryAssetHost - Base URL for library thumbnail assets. * @returns {LibraryItem.PropTypes.icons} - an `imageSource` or array of them */ -const getItemIcons = function (item) { +const getItemIcons = function (item, libraryAssetHost) { const costumes = (item.json && item.json.costumes) || item.costumes; if (costumes) { - return costumes.map(getItemIcons); + return costumes.map(costume => getItemIcons(costume, libraryAssetHost)); } if (item.rawURL) { @@ -107,7 +110,11 @@ const getItemIcons = function (item) { return { assetId: item.assetId, assetType: getAssetTypeForFileExtension(item.dataFormat), - assetServiceUri: `https://cdn.assets.scratch.mit.edu/internalapi/asset/${item.assetId}.${item.dataFormat}/get/` + assetServiceUri: buildLibraryAssetServiceUri( + libraryAssetHost, + item.assetId, + item.dataFormat + ) }; } @@ -117,7 +124,7 @@ const getItemIcons = function (item) { return { assetId: assetId, assetType: getAssetTypeForFileExtension(fileExtension), - assetServiceUri: `https://cdn.assets.scratch.mit.edu/internalapi/asset/${md5ext}/get/` + assetServiceUri: buildLibraryAssetServiceUri(libraryAssetHost, md5ext) }; } }; @@ -274,28 +281,34 @@ class LibraryComponent extends React.Component { } renderElement (data) { const key = this.constructKey(data); - const icons = getItemIcons(data); - return (