diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f0cd08796e6..6fbacad1911 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,7 +3,9 @@ name: Node.js CI -on: [pull_request] +on: + pull_request: + workflow_call: permissions: contents: read diff --git a/.github/workflows/keyboard_plugin_test.yml b/.github/workflows/keyboard_plugin_test.yml deleted file mode 100644 index e64efe983c6..00000000000 --- a/.github/workflows/keyboard_plugin_test.yml +++ /dev/null @@ -1,66 +0,0 @@ -# Workflow for running the keyboard navigation plugin's automated tests. - -name: Keyboard Navigation Automated Tests - -on: - workflow_dispatch: - pull_request: - push: - branches: - - main - -permissions: - contents: read - -jobs: - webdriverio_tests: - name: WebdriverIO tests - timeout-minutes: 10 - runs-on: ${{ matrix.os }} - - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest] - - steps: - - name: Checkout core Blockly - uses: actions/checkout@v5 - with: - path: core-blockly - - - name: Checkout keyboard navigation plugin - uses: actions/checkout@v5 - with: - repository: 'google/blockly-keyboard-experimentation' - ref: 'main' - path: blockly-keyboard-experimentation - - - name: Use Node.js 20.x - uses: actions/setup-node@v5 - with: - node-version: 20.x - - - name: NPM install - run: | - cd core-blockly - npm install - cd .. - cd blockly-keyboard-experimentation - npm install - cd .. - - - name: Link latest core main with plugin - run: | - cd core-blockly/packages/blockly - npm run package - cd dist - npm link - cd ../../../../blockly-keyboard-experimentation - npm link blockly - cd .. - - - name: Run keyboard navigation plugin tests - run: | - cd blockly-keyboard-experimentation - npm run test diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 00000000000..0f403f4479a --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,137 @@ +name: Publish to npm + +on: + workflow_dispatch: + inputs: + dry_run: + description: 'Dry run - print the version that would be published, but do not commit or publish anything.' + required: false + default: false + type: boolean + skip_versioning: + description: > + Skip version bump - use the version already in the repo + (e.g. retry after npm publish failed but the release commit is already pushed). + required: false + default: false + type: boolean + +permissions: + contents: write + id-token: write + +jobs: + ci: + uses: ./.github/workflows/build.yml + + version: + needs: ci + runs-on: ubuntu-latest + timeout-minutes: 30 + outputs: + version: ${{ steps.version.outputs.version }} + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v5 + with: + node-version: 24.x + registry-url: 'https://registry.npmjs.org' + + - name: Install dependencies + if: ${{ !inputs.skip_versioning }} + run: npm ci + + - name: Determine version bump + id: bump + if: ${{ !inputs.skip_versioning }} + working-directory: packages/blockly + run: | + RELEASE_TYPE=$(npx conventional-recommended-bump --preset conventionalcommits -t blockly-) + echo "release_type=$RELEASE_TYPE" >> "$GITHUB_OUTPUT" + echo "Recommended bump: $RELEASE_TYPE" + + - name: Apply version bump + if: ${{ !inputs.skip_versioning }} + working-directory: packages/blockly + run: npm version ${{ steps.bump.outputs.release_type }} --no-git-tag-version + + - name: Read package version + id: version + working-directory: packages/blockly + run: | + VERSION=$(node -p "require('./package.json').version") + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "Version: $VERSION" + + - name: Upload versioned files + if: ${{ !inputs.skip_versioning }} + uses: actions/upload-artifact@v4 + with: + name: versioned-files + path: | + packages/blockly/package.json + package-lock.json + + publish: + needs: version + runs-on: ubuntu-latest + if: ${{ !inputs.dry_run }} + environment: release + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + fetch-depth: 0 + ssh-key: ${{ secrets.DEPLOY_PRIVATE_KEY }} + + - name: Download versioned files + if: ${{ !inputs.skip_versioning }} + uses: actions/download-artifact@v4 + with: + name: versioned-files + + - name: Commit and push version bump + if: ${{ !inputs.skip_versioning }} + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add packages/blockly/package.json package-lock.json + git commit -m "release: v${{ needs.version.outputs.version }}" + git push + + - name: Setup Node.js + uses: actions/setup-node@v5 + with: + node-version: 24.x + registry-url: 'https://registry.npmjs.org' + + - name: Install dependencies + run: npm ci + + - name: Build package + working-directory: packages/blockly + run: npm run package + + - name: Publish to npm + working-directory: packages/blockly/dist + run: npm publish --verbose + + - name: Create tarball + working-directory: packages/blockly + run: npm pack ./dist + + - name: Create GitHub release + working-directory: packages/blockly + env: + GH_TOKEN: ${{ github.token }} + run: | + TARBALL="blockly-${{ needs.version.outputs.version }}.tgz" + gh release create "blockly-v${{ needs.version.outputs.version }}" "$TARBALL" \ + --repo "$GITHUB_REPOSITORY" \ + --title "blockly-v${{ needs.version.outputs.version }}" \ + --generate-notes diff --git a/.github/workflows/update-gh-pages.yml b/.github/workflows/update-gh-pages.yml new file mode 100644 index 00000000000..e45aca688a9 --- /dev/null +++ b/.github/workflows/update-gh-pages.yml @@ -0,0 +1,36 @@ +# Manual workflow to update GitHub Pages from a chosen source branch. +# The gulp updateGithubPages task builds the repo and force-pushes to gh-pages. + +name: Update GitHub Pages + +on: + workflow_dispatch: + inputs: + source_branch: + description: 'Source branch to build and deploy to GitHub Pages' + required: true + type: string + default: main + +permissions: + contents: write + +jobs: + update-gh-pages: + timeout-minutes: 15 + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v5 + with: + ref: ${{ inputs.source_branch }} + fetch-depth: 0 + + - name: Use Node.js + uses: actions/setup-node@v5 + with: + node-version: 24.x + + - name: Update GitHub Pages + working-directory: ./packages/blockly + run: npm run updateGithubPages:staging diff --git a/package-lock.json b/package-lock.json index 1eb2975365b..6d53d3e1949 100644 --- a/package-lock.json +++ b/package-lock.json @@ -514,6 +514,26 @@ "node": ">=16" } }, + "node_modules/conventional-changelog-preset-loader": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-4.1.0.tgz", + "integrity": "sha512-HozQjJicZTuRhCRTq4rZbefaiCzRM2pr6u2NL3XhrmQm4RMnDXfESU6JKu/pnKwx5xtdkYfNCsbhN5exhiKGJA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/conventional-commits-filter": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-4.0.0.tgz", + "integrity": "sha512-rnpnibcSOdFcdclpFwWa+pPlZJhXE7l+XK04zxhbWrhgpR96h33QLz8hITTXbcYICxVr3HZFtbtUAQ+4LdBo9A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, "node_modules/conventional-commits-parser": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-5.0.0.tgz", @@ -533,6 +553,27 @@ "node": ">=16" } }, + "node_modules/conventional-recommended-bump": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/conventional-recommended-bump/-/conventional-recommended-bump-9.0.0.tgz", + "integrity": "sha512-HR1yD0G5HgYAu6K0wJjLd7QGRK8MQDqqj6Tn1n/ja1dFwBCE6QmV+iSgQ5F7hkx7OUR/8bHpxJqYtXj2f/opPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "conventional-changelog-preset-loader": "^4.1.0", + "conventional-commits-filter": "^4.0.0", + "conventional-commits-parser": "^5.0.0", + "git-raw-commits": "^4.0.0", + "git-semver-tags": "^7.0.0", + "meow": "^12.0.1" + }, + "bin": { + "conventional-recommended-bump": "cli.mjs" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/cosmiconfig": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", @@ -728,6 +769,24 @@ "node": ">=16" } }, + "node_modules/git-semver-tags": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/git-semver-tags/-/git-semver-tags-7.0.1.tgz", + "integrity": "sha512-NY0ZHjJzyyNXHTDZmj+GG7PyuAKtMsyWSwh07CR2hOZFa+/yoTsXci/nF2obzL8UDhakFNkD9gNdt/Ed+cxh2Q==", + "deprecated": "This package is no longer maintained. For the JavaScript API, please use @conventional-changelog/git-client instead.", + "dev": true, + "license": "MIT", + "dependencies": { + "meow": "^12.0.1", + "semver": "^7.5.2" + }, + "bin": { + "git-semver-tags": "cli.mjs" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/global-directory": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz", @@ -1471,7 +1530,7 @@ } }, "packages/blockly": { - "version": "12.4.1", + "version": "12.5.1", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -1490,6 +1549,8 @@ "async-done": "^2.0.0", "chai": "^6.0.1", "concurrently": "^9.0.1", + "conventional-changelog-conventionalcommits": "^7.0.2", + "conventional-recommended-bump": "^9.0.0", "eslint": "^9.15.0", "eslint-config-google": "^0.14.0", "eslint-config-prettier": "^10.1.1", @@ -1503,11 +1564,8 @@ "gulp-concat": "^2.6.1", "gulp-gzip": "^1.4.2", "gulp-header": "^2.0.9", - "gulp-insert": "^0.5.0", "gulp-rename": "^2.0.0", "gulp-replace": "^1.0.0", - "gulp-series": "^1.0.2", - "gulp-shell": "^0.8.0", "gulp-sourcemaps": "^3.0.0", "gulp-umd": "^2.0.0", "http-server": "^14.0.0", @@ -6193,36 +6251,6 @@ "xtend": "~4.0.1" } }, - "packages/blockly/node_modules/gulp-insert": { - "version": "0.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "readable-stream": "^1.0.26-4", - "streamqueue": "0.0.6" - } - }, - "packages/blockly/node_modules/gulp-insert/node_modules/isarray": { - "version": "0.0.1", - "dev": true, - "license": "MIT" - }, - "packages/blockly/node_modules/gulp-insert/node_modules/readable-stream": { - "version": "1.1.14", - "dev": true, - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "packages/blockly/node_modules/gulp-insert/node_modules/string_decoder": { - "version": "0.10.31", - "dev": true, - "license": "MIT" - }, "packages/blockly/node_modules/gulp-rename": { "version": "2.1.0", "dev": true, @@ -6246,48 +6274,6 @@ "node": ">=10" } }, - "packages/blockly/node_modules/gulp-series": { - "version": "1.0.2", - "dev": true, - "license": "MIT" - }, - "packages/blockly/node_modules/gulp-shell": { - "version": "0.8.0", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^3.0.0", - "fancy-log": "^1.3.3", - "lodash.template": "^4.5.0", - "plugin-error": "^1.0.1", - "through2": "^3.0.1", - "tslib": "^1.10.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "packages/blockly/node_modules/gulp-shell/node_modules/chalk": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "packages/blockly/node_modules/gulp-shell/node_modules/through2": { - "version": "3.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.4", - "readable-stream": "2 || 3" - } - }, "packages/blockly/node_modules/gulp-sourcemaps": { "version": "3.0.0", "dev": true, @@ -8935,37 +8921,6 @@ "any-promise": "^1.1.0" } }, - "packages/blockly/node_modules/streamqueue": { - "version": "0.0.6", - "dev": true, - "dependencies": { - "readable-stream": "^1.0.26-2" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "packages/blockly/node_modules/streamqueue/node_modules/isarray": { - "version": "0.0.1", - "dev": true, - "license": "MIT" - }, - "packages/blockly/node_modules/streamqueue/node_modules/readable-stream": { - "version": "1.1.14", - "dev": true, - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "packages/blockly/node_modules/streamqueue/node_modules/string_decoder": { - "version": "0.10.31", - "dev": true, - "license": "MIT" - }, "packages/blockly/node_modules/string_decoder": { "version": "1.1.1", "dev": true, @@ -9237,11 +9192,6 @@ "typescript": ">=4.8.4" } }, - "packages/blockly/node_modules/tslib": { - "version": "1.14.1", - "dev": true, - "license": "0BSD" - }, "packages/blockly/node_modules/type": { "version": "1.2.0", "dev": true, diff --git a/packages/blockly/core/connection.ts b/packages/blockly/core/connection.ts index a55c2505915..a79b7b9b143 100644 --- a/packages/blockly/core/connection.ts +++ b/packages/blockly/core/connection.ts @@ -291,10 +291,7 @@ export class Connection { } let event; - if ( - eventUtils.isEnabled() && - !childConnection.getSourceBlock().isDeadOrDying() - ) { + if (eventUtils.isEnabled()) { event = new (eventUtils.get(EventType.BLOCK_MOVE))( childConnection.getSourceBlock(), ) as BlockMove; diff --git a/packages/blockly/core/css.ts b/packages/blockly/core/css.ts index de9e682f87e..60ad8604914 100644 --- a/packages/blockly/core/css.ts +++ b/packages/blockly/core/css.ts @@ -84,14 +84,7 @@ let content = ` .blocklyBlockCanvas.blocklyCanvasTransitioning, .blocklyBubbleCanvas.blocklyCanvasTransitioning { - transition: transform .15s; -} - -@media (prefers-reduced-motion) { - .blocklyBlockCanvas.blocklyCanvasTransitioning, - .blocklyBubbleCanvas.blocklyCanvasTransitioning { - transition: none; - } + transition: transform .5s; } .blocklyEmboss { @@ -452,17 +445,14 @@ input[type=number] { } /* State: selected/checked. */ -.blocklyMenuItemCheckbox { - height: 16px; - position: absolute; - width: 16px; -} - .blocklyMenuItemSelected .blocklyMenuItemCheckbox { background: url(<<>>/sprites.png) no-repeat -48px -16px; float: left; margin-left: -24px; + width: 16px; + height: 16px; position: static; /* Scroll with the menu. */ + display: block; } .blocklyMenuItemRtl .blocklyMenuItemCheckbox { diff --git a/packages/blockly/core/layer_manager.ts b/packages/blockly/core/layer_manager.ts index 7d253b11042..1142bcf58d2 100644 --- a/packages/blockly/core/layer_manager.ts +++ b/packages/blockly/core/layer_manager.ts @@ -73,11 +73,11 @@ export class LayerManager { * @internal */ appendToAnimationLayer(elem: IRenderedElement) { - const currentTransform = this.dragLayer?.style.transform; + const currentTransform = this.dragLayer?.getAttribute('transform'); // Only update the current transform when appending, so animations don't // move if the workspace moves. - if (currentTransform && this.animationLayer) { - this.animationLayer.style.transform = currentTransform; + if (currentTransform) { + this.animationLayer?.setAttribute('transform', currentTransform); } this.animationLayer?.appendChild(elem.getSvgRoot()); } @@ -88,12 +88,10 @@ export class LayerManager { * @internal */ translateLayers(newCoord: Coordinate, newScale: number) { - const translation = `translate(${newCoord.x}px, ${newCoord.y}px) scale(${newScale})`; - if (this.dragLayer) { - this.dragLayer.style.transform = translation; - } + const translation = `translate(${newCoord.x}, ${newCoord.y}) scale(${newScale})`; + this.dragLayer?.setAttribute('transform', translation); for (const [_, layer] of this.layers) { - layer.style.transform = translation; + layer.setAttribute('transform', translation); } } diff --git a/packages/blockly/core/toolbox/toolbox.ts b/packages/blockly/core/toolbox/toolbox.ts index 6f4daf4ed71..f58536257d3 100644 --- a/packages/blockly/core/toolbox/toolbox.ts +++ b/packages/blockly/core/toolbox/toolbox.ts @@ -11,7 +11,6 @@ */ // Former goog.module ID: Blockly.Toolbox -// Unused import preserved for side-effects. Remove if unneeded. import {BlockSvg} from '../block_svg.js'; import * as browserEvents from '../browser_events.js'; import * as common from '../common.js'; @@ -192,7 +191,7 @@ export class Toolbox aria.setRole(this.contentsDiv_, aria.Role.TREE); container.appendChild(this.contentsDiv_); - svg.parentNode!.insertBefore(container, svg); + svg.parentNode?.insertBefore(container, svg); this.attachEvents_(container, this.contentsDiv_); return container; @@ -281,7 +280,7 @@ export class Toolbox const itemId = (targetElement as Element).getAttribute('id'); if (itemId) { const item = this.getToolboxItemById(itemId); - if (item!.isSelectable()) { + if (item?.isSelectable()) { this.setSelectedItem(item); (item as ISelectableToolboxItem).onClick(e); } @@ -396,7 +395,7 @@ export class Toolbox const toolboxItemDef = toolboxDef[i]; this.createToolboxItem(toolboxItemDef, fragment); } - this.contentsDiv_!.appendChild(fragment); + this.contentsDiv_?.appendChild(fragment); } /** @@ -435,9 +434,7 @@ export class Toolbox } // Adds the ID to the HTML element that can receive a click. // This is used in onClick_ to find the toolboxItem that was clicked. - if (toolboxItem.getClickTarget()) { - toolboxItem.getClickTarget()!.setAttribute('id', toolboxItem.getId()); - } + toolboxItem.getClickTarget()?.setAttribute('id', toolboxItem.getId()); } } @@ -722,7 +719,7 @@ export class Toolbox this.width_ = toolboxDiv.offsetWidth; this.height_ = workspaceMetrics.viewHeight; } - this.flyout!.position(); + this.flyout?.position(); } /** @@ -731,10 +728,11 @@ export class Toolbox * @internal */ handleToolboxItemResize() { + if (!this.HtmlDiv) return; // Reposition the workspace so that (0,0) is in the correct position // relative to the new absolute edge (ie toolbox edge). const workspace = this.workspace_; - const rect = this.HtmlDiv!.getBoundingClientRect(); + const rect = this.HtmlDiv.getBoundingClientRect(); const flyout = this.getFlyout(); const newX = this.toolboxPosition === toolbox.Position.LEFT @@ -786,7 +784,7 @@ export class Toolbox this.selectedItem_.isSelectable() && this.selectedItem_.getContents().length ) { - this.flyout!.show(this.selectedItem_.getContents()); + this.flyout?.show(this.selectedItem_.getContents()); } } @@ -800,7 +798,9 @@ export class Toolbox return; } - this.HtmlDiv!.style.display = isVisible ? 'block' : 'none'; + if (this.HtmlDiv) { + this.HtmlDiv.style.display = isVisible ? 'block' : 'none'; + } this.isVisible_ = isVisible; // Invisible toolbox is ignored as drag targets and must have the drag // target updated. @@ -944,10 +944,10 @@ export class Toolbox (oldItem === newItem && !newItem.isCollapsible()) || !newItem.getContents().length ) { - this.flyout!.hide(); + this.flyout?.hide(); } else { - this.flyout!.show(newItem.getContents()); - this.flyout!.scrollToStart(); + this.flyout?.show(newItem.getContents()); + this.flyout?.scrollToStart(); } } @@ -992,10 +992,7 @@ export class Toolbox const collapsibleItem = this.selectedItem_ as ICollapsibleToolboxItem; collapsibleItem.toggleExpanded(); return true; - } else if ( - this.selectedItem_.getParent() && - this.selectedItem_.getParent()!.isSelectable() - ) { + } else if (this.selectedItem_.getParent()?.isSelectable()) { this.setSelectedItem(this.selectedItem_.getParent()); return true; } @@ -1075,7 +1072,7 @@ export class Toolbox /** Disposes of this toolbox. */ dispose() { this.workspace_.getComponentManager().removeComponent('toolbox'); - this.flyout!.dispose(); + this.flyout?.dispose(); this.contents.forEach((item) => item.dispose()); for (let j = 0; j < this.boundEvents_.length; j++) { diff --git a/packages/blockly/core/zoom_controls.ts b/packages/blockly/core/zoom_controls.ts index 6bd1194231b..4f14b73bed6 100644 --- a/packages/blockly/core/zoom_controls.ts +++ b/packages/blockly/core/zoom_controls.ts @@ -373,10 +373,8 @@ export class ZoomControls implements IPositionable { * @param e A mouse down event. */ private zoom(amount: number, e: PointerEvent) { - this.workspace.beginCanvasTransition(); this.workspace.markFocused(); this.workspace.zoomCenter(amount); - setTimeout(this.workspace.endCanvasTransition.bind(this.workspace), 150); this.fireZoomEvent(); Touch.clearTouchIdentifier(); // Don't block future drags. e.stopPropagation(); // Don't start a workspace scroll. @@ -461,7 +459,7 @@ export class ZoomControls implements IPositionable { this.workspace.zoomCenter(amount); this.workspace.scrollCenter(); - setTimeout(this.workspace.endCanvasTransition.bind(this.workspace), 150); + setTimeout(this.workspace.endCanvasTransition.bind(this.workspace), 500); this.fireZoomEvent(); Touch.clearTouchIdentifier(); // Don't block future drags. e.stopPropagation(); // Don't start a workspace scroll. diff --git a/packages/blockly/gulpfile.mjs b/packages/blockly/gulpfile.mjs index ad61bcb516d..378d6ce5efa 100644 --- a/packages/blockly/gulpfile.mjs +++ b/packages/blockly/gulpfile.mjs @@ -33,18 +33,9 @@ import { tsc, } from './scripts/gulpfiles/build_tasks.mjs'; import {docs} from './scripts/gulpfiles/docs_tasks.mjs'; -import { - createRC, - syncDevelop, - syncMaster, - updateGithubPages, -} from './scripts/gulpfiles/git_tasks.mjs'; +import {updateGithubPages} from './scripts/gulpfiles/git_tasks.mjs'; import {cleanReleaseDir, pack} from './scripts/gulpfiles/package_tasks.mjs'; -import { - publish, - publishBeta, - recompile, -} from './scripts/gulpfiles/release_tasks.mjs'; +import {publish, publishBeta} from './scripts/gulpfiles/release_tasks.mjs'; import { generators, interactiveMocha, @@ -72,7 +63,7 @@ export { prepareDemos, deployDemosBeta, deployDemos, - updateGithubPages as gitUpdateGithubPages, + updateGithubPages, } // Manually-invokable targets that also invoke prerequisites where @@ -86,15 +77,5 @@ export { generators as testGenerators, interactiveMocha, buildAdvancedCompilationTest, - createRC as gitCreateRC, docs, } - -// Legacy targets, to be deleted. -// -// prettier-ignore -export { - recompile, - syncDevelop as gitSyncDevelop, - syncMaster as gitSyncMaster, -} diff --git a/packages/blockly/package.json b/packages/blockly/package.json index ad4a58688c6..623e967a312 100644 --- a/packages/blockly/package.json +++ b/packages/blockly/package.json @@ -1,16 +1,16 @@ { "name": "blockly", - "version": "12.4.1", + "version": "12.5.1", "description": "Blockly is a library for building visual programming editors.", "keywords": [ "blockly" ], "repository": { "type": "git", - "url": "git+https://github.com/google/blockly.git" + "url": "git+https://github.com/RaspberryPiFoundation/blockly.git" }, "bugs": { - "url": "https://github.com/google/blockly/issues" + "url": "https://github.com/RaspberryPiFoundation/blockly/issues" }, "homepage": "https://developers.google.com/blockly/", "author": { @@ -38,8 +38,6 @@ "prepareDemos": "gulp prepareDemos", "publish": "npm ci && gulp publish", "publish:beta": "npm ci && gulp publishBeta", - "recompile": "gulp recompile", - "release": "gulp gitCreateRC", "start": "npm run build && concurrently -n tsc,server \"tsc --watch --preserveWatchOutput --outDir \"build/src\" --declarationDir \"build/declarations\"\" \"http-server ./ -s -o /tests/playground.html -c-1\"", "tsc": "gulp tsc", "test": "gulp test", @@ -47,7 +45,8 @@ "test:generators": "gulp testGenerators", "test:mocha:interactive": "npm run build && concurrently -n tsc,server \"tsc --watch --preserveWatchOutput --outDir \"build/src\" --declarationDir \"build/declarations\"\" \"gulp interactiveMocha\"", "test:compile:advanced": "gulp buildAdvancedCompilationTest --debug", - "updateGithubPages": "npm ci && gulp gitUpdateGithubPages" + "updateGithubPages": "npm ci && gulp updateGithubPages --upstream", + "updateGithubPages:staging": "npm ci && gulp updateGithubPages --use-local" }, "exports": { ".": { @@ -112,6 +111,8 @@ "async-done": "^2.0.0", "chai": "^6.0.1", "concurrently": "^9.0.1", + "conventional-changelog-conventionalcommits": "^7.0.2", + "conventional-recommended-bump": "^9.0.0", "eslint": "^9.15.0", "eslint-config-google": "^0.14.0", "eslint-config-prettier": "^10.1.1", @@ -125,11 +126,8 @@ "gulp-concat": "^2.6.1", "gulp-gzip": "^1.4.2", "gulp-header": "^2.0.9", - "gulp-insert": "^0.5.0", "gulp-rename": "^2.0.0", "gulp-replace": "^1.0.0", - "gulp-series": "^1.0.2", - "gulp-shell": "^0.8.0", "gulp-sourcemaps": "^3.0.0", "gulp-umd": "^2.0.0", "http-server": "^14.0.0", diff --git a/packages/blockly/scripts/gulpfiles/git_tasks.mjs b/packages/blockly/scripts/gulpfiles/git_tasks.mjs index 2b08e16b38b..6a68b54becf 100644 --- a/packages/blockly/scripts/gulpfiles/git_tasks.mjs +++ b/packages/blockly/scripts/gulpfiles/git_tasks.mjs @@ -8,17 +8,36 @@ * @fileoverview Git-related gulp tasks for Blockly. */ + import * as gulp from 'gulp'; import {execSync} from 'child_process'; +import yargs from 'yargs'; +import { hideBin } from 'yargs/helpers'; import * as buildTasks from './build_tasks.mjs'; import * as packageTasks from './package_tasks.mjs'; -const UPSTREAM_URL = 'https://github.com/google/blockly.git'; +const UPSTREAM_URL = 'git@github.com:RaspberryPiFoundation/blockly.git'; + +// Use yargs to parse --remote argument +const argv = yargs(hideBin(process.argv)).option('remote', { + type: 'string', + describe: 'Remote to push gh-pages to', + demandOption: false +}).option('upstream', { + type: 'boolean', + describe: 'Push to RaspberryPiFoundation/blockly instead of origin', + demandOption: false +}).option('use-local', { + type: 'boolean', + describe: 'Build and push from current branch instead of syncing with main', + demandOption: false +}).help().argv; +const remoteToUse = argv.upstream ? UPSTREAM_URL : resolveRemote(argv.remote); /** * Extra paths to include in the gh_pages branch (beyond the normal - * contents of master / develop). Passed to shell unquoted, so can + * contents of main). Passed to shell unquoted, so can * include globs. */ const EXTRAS = [ @@ -28,140 +47,122 @@ const EXTRAS = [ 'build/*.loader.mjs', ]; -let upstream = null; - /** - * Get name of git remote for upstream (typically 'upstream', but this - * is just convention and can be changed.) - */ -function getUpstream() { - if (upstream) return upstream; - for (const line of String(execSync('git remote -v')).split('\n')) { - if (line.includes('google/blockly')) { - upstream = line.split('\t')[0]; - return upstream; - } - } - throw new Error('Unable to determine upstream URL'); -} - -/** - * Stash current state, check out the named branch, and sync with - * google/blockly. + * Stash current state, check out the named branch, and pull + * changes from RaspberryPiFoundation/blockly. */ function syncBranch(branchName) { return function(done) { execSync('git stash save -m "Stash for sync"', { stdio: 'inherit' }); checkoutBranch(branchName); execSync(`git pull ${UPSTREAM_URL} ${branchName}`, { stdio: 'inherit' }); - execSync(`git push origin ${branchName}`, { stdio: 'inherit' }); done(); }; } /** - * Stash current state, check out develop, and sync with - * google/blockly. + * Stash current state, check out main, and sync with + * RaspberryPiFoundation/blockly. */ -export function syncDevelop() { - return syncBranch('develop'); +export function syncMain() { + return syncBranch('main'); }; /** - * Stash current state, check out master, and sync with - * google/blockly. - */ -export function syncMaster() { - return syncBranch('master'); -}; - -/** - * Helper function: get a name for a rebuild branch. Format: - * rebuild_mm_dd_yyyy. - */ -function getRebuildBranchName() { - const date = new Date(); - const mm = date.getMonth() + 1; // Month, 0-11 - const dd = date.getDate(); // Day of the month, 1-31 - const yyyy = date.getFullYear(); - return `rebuild_${mm}_${dd}_${yyyy}`; -}; - -/** - * Helper function: get a name for a rebuild branch. Format: - * rebuild_yyyy_mm. - */ -function getRCBranchName() { - const date = new Date(); - const mm = date.getMonth() + 1; // Month, 0-11 - const yyyy = date.getFullYear(); - return `rc_${yyyy}_${mm}`; -}; - -/** - * If branch does not exist then create the branch. * If branch exists switch to branch. + * If branch does not exist then create the branch. */ function checkoutBranch(branchName) { - execSync(`git switch -c ${branchName}`, + execSync(`git switch ${branchName} || git switch -c ${branchName}`, { stdio: 'inherit' }); } /** - * Create and push an RC branch. - * Note that this pushes to google/blockly. - */ -export const createRC = gulp.series( - syncDevelop(), - function(done) { - const branchName = getRCBranchName(); - execSync(`git switch -C ${branchName}`, { stdio: 'inherit' }); - execSync(`git push ${UPSTREAM_URL} ${branchName}`, { stdio: 'inherit' }); - done(); - } -); - -/** Create the rebuild branch. */ -export function createRebuildBranch(done) { - const branchName = getRebuildBranchName(); - console.log(`make-rebuild-branch: creating branch ${branchName}`); - execSync(`git switch -C ${branchName}`, { stdio: 'inherit' }); - done(); -} - -/** Push the rebuild branch to origin. */ -export function pushRebuildBranch(done) { - console.log('push-rebuild-branch: committing rebuild'); - execSync('git commit -am "Rebuild"', { stdio: 'inherit' }); - const branchName = getRebuildBranchName(); - execSync(`git push origin ${branchName}`, { stdio: 'inherit' }); - console.log(`Branch ${branchName} pushed to GitHub.`); - console.log('Next step: create a pull request against develop.'); - done(); -} - -/** - * Update github pages with what is currently in develop. + * Update github pages with what is currently in main (or current branch if --use-local). * * Prerequisites (invoked): clean, build. + * + * Usage: + * gulp updateGithubPages # sync main, then use origin if exists + * gulp updateGithubPages --upstream # uses hardcoded upstream + * gulp updateGithubPages --remote # uses named remote + * gulp updateGithubPages --use-local # build from current branch, skip syncing main + * */ export const updateGithubPages = gulp.series( - function(done) { - execSync('git stash save -m "Stash for sync"', { stdio: 'inherit' }); - execSync('git switch -C gh-pages', { stdio: 'inherit' }); - execSync(`git fetch ${getUpstream()}`, { stdio: 'inherit' }); - execSync(`git reset --hard ${getUpstream()}/develop`, { stdio: 'inherit' }); - done(); - }, - buildTasks.cleanBuildDir, - packageTasks.cleanReleaseDir, - buildTasks.build, - function(done) { - // Extra paths (e.g. build/, dist/ etc.) are normally gitignored, - // so we have to force add. - execSync(`git add -f ${EXTRAS.join(' ')}`, {stdio: 'inherit'}); - execSync('git commit -am "Rebuild"', {stdio: 'inherit'}); - execSync(`git push ${UPSTREAM_URL} gh-pages --force`, {stdio: 'inherit'}); - done(); + function (done) { + if (!remoteToUse) { + const attemptedRemote = argv.remote || 'origin'; + const remoteLabel = argv.remote + ? `Remote '${attemptedRemote}'` + : "Remote 'origin' (default)"; + const errMsg = `${remoteLabel} not found in git remotes. ` + + 'Please add that remote or use --upstream.\n' + + 'Usage: gulp updateGithubPages [--remote | --upstream]'; + console.error(errMsg); + done(new Error(errMsg)); + return; + } + done(); + }, + function (done) { + if (!argv.useLocal) { + done(); + return; + } + const status = execSync('git status --porcelain', { encoding: 'utf8' }); + if (status.trim()) { + const errMsg = + 'You cannot push the local branch with uncommitted changes. ' + + 'Please commit or stash your changes first.'; + console.error(errMsg); + done(new Error(errMsg)); + return; + } + done(); + }, + function (done) { + if (argv.useLocal) { + done(); + return; + } + syncMain()(done); + }, + function(done) { + const sourceRef = argv.useLocal + ? execSync('git rev-parse HEAD', { encoding: 'utf8' }).trim() + : 'main'; + execSync('git switch -C gh-pages', { stdio: 'inherit' }); + execSync(`git reset --hard ${sourceRef}`, { stdio: 'inherit' }); + done(); + }, + buildTasks.cleanBuildDir, + packageTasks.cleanReleaseDir, + buildTasks.build, + function(done) { + // Extra paths (e.g. build/, dist/ etc.) are normally gitignored, + // so we have to force add. + execSync(`git add -f ${EXTRAS.join(' ')}`, {stdio: 'inherit'}); + execSync('git commit -am "Rebuild"', {stdio: 'inherit'}); + execSync(`git push ${remoteToUse} gh-pages --force`, {stdio: 'inherit'}); + done(); + } + ); + +/** + * Resolves which remote to use for pushing gh-pages. + * @param {string} remoteArg + * @returns {string|undefined} The remote name, or undefined if not found. + */ +function resolveRemote(remoteArg) { + const remoteName = remoteArg || 'origin'; + try { + const remotes = execSync('git remote', {encoding: 'utf8'}).split(/\r?\n/).map(r => r.trim()).filter(Boolean); + if (remotes.includes(remoteName)) { + return remoteName; + } + return undefined; + } catch (e) { + return undefined; } -); +} diff --git a/packages/blockly/scripts/gulpfiles/release_tasks.mjs b/packages/blockly/scripts/gulpfiles/release_tasks.mjs index a678a4f2436..ca9c9ffa1bc 100644 --- a/packages/blockly/scripts/gulpfiles/release_tasks.mjs +++ b/packages/blockly/scripts/gulpfiles/release_tasks.mjs @@ -18,55 +18,6 @@ import * as packageTasks from './package_tasks.mjs'; import {getPackageJson} from './helper_tasks.mjs'; import {RELEASE_DIR} from './config.mjs'; - -// Gets the current major version. -function getMajorVersion() { - const { version } = getPackageJson(); - const re = new RegExp(/^(\d)./); - const match = re.exec(version); - if (!match[0]) { - return null; - } - console.log(match[0]); - return parseInt(match[0]); -} - -// Updates the version depending on user input. -function updateVersion(done, updateType) { - const majorVersion = getMajorVersion(); - if (!majorVersion) { - done(new Error('Something went wrong when getting the major version number.')); - } else if (!updateType) { - // User selected to cancel. - done(new Error('Cancelling process.')); - } - - switch (updateType.toLowerCase()) { - case 'major': - majorVersion++; - execSync(`npm --no-git-tag-version version ${majorVersion}.$(date +'%Y%m%d').0`, {stdio: 'inherit'}); - done(); - break; - case 'minor': - execSync(`npm --no-git-tag-version version ${majorVersion}.$(date +'%Y%m%d').0`, {stdio: 'inherit'}); - done(); - break; - case 'patch': - execSync(`npm --no-git-tag-version version patch`, {stdio: 'inherit'}); - done(); - break; - default: - done(new Error('Unexpected update type was chosen.')) - } -} - -// Prompt the user to figure out what kind of version update we should do. -function updateVersionPrompt(done) { - const releaseTypes = ['Major', 'Minor', 'Patch']; - const index = readlineSync.keyInSelect(releaseTypes, 'Which version type?'); - updateVersion(done, releaseTypes[index]); -} - // Checks with the user that they are on the correct git branch. function checkBranch(done) { const gitBranchName = execSync('git rev-parse --abbrev-ref HEAD').toString(); @@ -162,13 +113,3 @@ export const publishBeta = gulp.series( checkReleaseDir, loginAndPublishBeta ); - -// Switch to a new branch, update the version number, build Blockly -// and check in the resulting built files. -export const recompile = gulp.series( - gitTasks.syncDevelop(), - gitTasks.createRebuildBranch, - updateVersionPrompt, - packageTasks.pack, // Does clean + build. - gitTasks.pushRebuildBranch - );