mirror of
https://github.com/google/blockly.git
synced 2026-05-01 17:40:11 +02:00
chore: merge main into v13
chore: merge main into v13
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
# Code review guidelines
|
||||
|
||||
## General principles
|
||||
- **Style:** Suggest `npm run format` or `npm run lint:fix` for formatting issues; do not comment on individual style nits.
|
||||
- **Patterns:** Enforce existing Blockly patterns and official docs over new conventions.
|
||||
- **Documentation:** Prefer linking to [Blockly Dev Docs](https://developers.google.com/blockly) over duplicating content in comments.
|
||||
- **TSDoc:** Public APIs require TSDoc for behavior, params, and returns. Do not include implementation details or historical context unless essential.
|
||||
|
||||
## Localization
|
||||
- All user-visible strings must use `Blockly.Msg`.
|
||||
- New strings must be added to `msg/messages.js`, `msg/json/qqq.json`, and `msg/json/en.json`.
|
||||
- Link [this guide](https://developers.google.com/blockly/guides/contribute/core/add_localization_token) if strings are missing or misplaced.
|
||||
- PRs that attempt to add translations for non-English strings should be redirected to TranslateWiki via the ([translation guide](hhttps://developers.google.com/blockly/guides/contribute/core/translating)).
|
||||
|
||||
## Breaking changes
|
||||
### Policy
|
||||
- A breaking change is any non-backwards-compatible change to public APIs, behavior, UI, or browser requirements.
|
||||
- **Avoid:** Prefer deprecation with migration paths over removal.
|
||||
- **Compatibility:** Must support Safari 15.4+, latest Chrome, and latest Firefox.
|
||||
- **Identification:** Flag breaking changes unless all of the following are true:
|
||||
1. PR description explicitly notes it.
|
||||
2. Commit type includes `!` (e.g., `feat!:`).
|
||||
3. Target branch is not `main`.
|
||||
|
||||
### Breaking
|
||||
- Removing/renaming public methods, properties, or classes.
|
||||
- Changing signatures or behavior of existing public methods.
|
||||
- Adding required methods to public interfaces.
|
||||
- New keyboard shortcuts or context menu items (potential developer conflicts).
|
||||
- DOM restructures affecting external CSS/JS.
|
||||
- Changes to build output/consumption (e.g., ESM-only).
|
||||
- Changes that affect the output of serialization.
|
||||
|
||||
### Non-breaking (do not flag)
|
||||
- Additive changes (new methods/properties).
|
||||
- Internal refactoring (including items marked `@internal`).
|
||||
- Tooling/workflow changes.
|
||||
- Changes to unreleased code (non-`main` feature branches).
|
||||
@@ -27,6 +27,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Reconfigure git to use HTTP authentication
|
||||
@@ -58,6 +59,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
- name: Use Node.js 24.x
|
||||
uses: actions/setup-node@v5
|
||||
@@ -75,6 +78,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
- name: Use Node.js 24.x
|
||||
uses: actions/setup-node@v5
|
||||
|
||||
@@ -4,17 +4,27 @@ on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
dry_run:
|
||||
description: 'Dry run - print the version that would be published, but do not commit or publish anything.'
|
||||
description: >
|
||||
Dry run — print the version and npm dist-tag that would be used; no commit or publish.
|
||||
Pick the branch to publish from with the "Use workflow from" dropdown.
|
||||
Non-default branches publish to the npm dist-tag `beta` (not `latest`).
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
skip_versioning:
|
||||
description: >
|
||||
Skip version bump - use the version already in the repo
|
||||
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
|
||||
version_override:
|
||||
description: >
|
||||
Optional. Full semver to publish (e.g. 12.6.0-beta.2). Skips conventional bump when set.
|
||||
Leave empty for automatic versioning.
|
||||
required: false
|
||||
default: ''
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -34,6 +44,7 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node.js
|
||||
@@ -48,7 +59,7 @@ jobs:
|
||||
|
||||
- name: Determine version bump
|
||||
id: bump
|
||||
if: ${{ !inputs.skip_versioning }}
|
||||
if: ${{ !inputs.skip_versioning && inputs.version_override == '' }}
|
||||
working-directory: packages/blockly
|
||||
run: |
|
||||
RELEASE_TYPE=$(npx conventional-recommended-bump --preset conventionalcommits -t blockly-)
|
||||
@@ -58,7 +69,35 @@ jobs:
|
||||
- name: Apply version bump
|
||||
if: ${{ !inputs.skip_versioning }}
|
||||
working-directory: packages/blockly
|
||||
run: npm version ${{ steps.bump.outputs.release_type }} --no-git-tag-version
|
||||
env:
|
||||
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
|
||||
REF_NAME: ${{ github.ref_name }}
|
||||
RELEASE_TYPE: ${{ steps.bump.outputs.release_type }}
|
||||
VERSION_OVERRIDE: ${{ inputs.version_override }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ -n "${VERSION_OVERRIDE}" ]; then
|
||||
npm version "${VERSION_OVERRIDE}" --no-git-tag-version
|
||||
exit 0
|
||||
fi
|
||||
if [ "${REF_NAME}" = "${DEFAULT_BRANCH}" ]; then
|
||||
npm version "${RELEASE_TYPE}" --no-git-tag-version
|
||||
exit 0
|
||||
fi
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
if [[ "${VERSION}" == *"-beta."* ]]; then
|
||||
npm version prerelease --preid=beta --no-git-tag-version
|
||||
else
|
||||
case "${RELEASE_TYPE}" in
|
||||
major) npm version premajor --preid=beta --no-git-tag-version ;;
|
||||
minor) npm version preminor --preid=beta --no-git-tag-version ;;
|
||||
patch) npm version prepatch --preid=beta --no-git-tag-version ;;
|
||||
*)
|
||||
echo "::error title=Invalid release bump::conventional-recommended-bump returned '${RELEASE_TYPE}' (expected major, minor, or patch). Fix commits/tags or set version_override." >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
- name: Read package version
|
||||
id: version
|
||||
@@ -68,6 +107,15 @@ jobs:
|
||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||
echo "Version: $VERSION"
|
||||
|
||||
- name: Dry run summary
|
||||
if: ${{ inputs.dry_run }}
|
||||
run: |
|
||||
DIST_TAG="${{ github.ref_name == github.event.repository.default_branch && 'latest' || 'beta' }}"
|
||||
echo "Dry run: would publish version ${{ steps.version.outputs.version }} to npm dist-tag: ${DIST_TAG}"
|
||||
if [ "${{ github.ref_name }}" != "${{ github.event.repository.default_branch }}" ]; then
|
||||
echo "GitHub release would be created as prerelease."
|
||||
fi
|
||||
|
||||
- name: Upload versioned files
|
||||
if: ${{ !inputs.skip_versioning }}
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -82,10 +130,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ !inputs.dry_run }}
|
||||
environment: release
|
||||
env:
|
||||
NPM_DIST_TAG: ${{ github.ref_name == github.event.repository.default_branch && 'latest' || 'beta' }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
fetch-depth: 0
|
||||
ssh-key: ${{ secrets.DEPLOY_PRIVATE_KEY }}
|
||||
|
||||
@@ -119,7 +170,7 @@ jobs:
|
||||
|
||||
- name: Publish to npm
|
||||
working-directory: packages/blockly/dist
|
||||
run: npm publish --verbose
|
||||
run: npm publish --tag "${NPM_DIST_TAG}" --verbose
|
||||
|
||||
- name: Create tarball
|
||||
working-directory: packages/blockly
|
||||
@@ -131,7 +182,15 @@ jobs:
|
||||
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
|
||||
if [ "${{ github.ref_name }}" != "${{ github.event.repository.default_branch }}" ]; then
|
||||
gh release create "blockly-v${{ needs.version.outputs.version }}" "$TARBALL" \
|
||||
--repo "$GITHUB_REPOSITORY" \
|
||||
--title "blockly-v${{ needs.version.outputs.version }}" \
|
||||
--generate-notes \
|
||||
--prerelease
|
||||
else
|
||||
gh release create "blockly-v${{ needs.version.outputs.version }}" "$TARBALL" \
|
||||
--repo "$GITHUB_REPOSITORY" \
|
||||
--title "blockly-v${{ needs.version.outputs.version }}" \
|
||||
--generate-notes
|
||||
fi
|
||||
|
||||
@@ -1728,8 +1728,8 @@ export class Block {
|
||||
|
||||
// Validate that each arg has a corresponding message
|
||||
let n = 0;
|
||||
while (json['args' + n]) {
|
||||
if (json['message' + n] === undefined) {
|
||||
while (json[`args${n}`]) {
|
||||
if (json[`message${n}`] === undefined) {
|
||||
throw Error(
|
||||
warningPrefix +
|
||||
`args${n} must have a corresponding message (message${n}).`,
|
||||
@@ -1739,14 +1739,13 @@ export class Block {
|
||||
}
|
||||
|
||||
// Set basic properties of block.
|
||||
// Makes styles backward compatible with old way of defining hat style.
|
||||
if (json['style'] && json['style'].hat) {
|
||||
this.hat = json['style'].hat;
|
||||
// Handle legacy style object format for backwards compatibility
|
||||
if (json['style'] && typeof json['style'] === 'object') {
|
||||
this.hat = (json['style'] as {hat?: string}).hat;
|
||||
// Must set to null so it doesn't error when checking for style and
|
||||
// colour.
|
||||
json['style'] = null;
|
||||
}
|
||||
|
||||
if (json['style'] && json['colour']) {
|
||||
throw Error(warningPrefix + 'Must not have both a colour and a style.');
|
||||
} else if (json['style']) {
|
||||
@@ -1757,12 +1756,12 @@ export class Block {
|
||||
|
||||
// Interpolate the message blocks.
|
||||
let i = 0;
|
||||
while (json['message' + i] !== undefined) {
|
||||
while (json[`message${i}`] !== undefined) {
|
||||
this.interpolate(
|
||||
json['message' + i],
|
||||
json['args' + i] || [],
|
||||
json[`message${i}`] || '',
|
||||
json[`args${i}`] || [],
|
||||
// Backwards compatibility: lastDummyAlign aliases implicitAlign.
|
||||
json['implicitAlign' + i] || json['lastDummyAlign' + i],
|
||||
json[`implicitAlign${i}`] || (json as any)[`lastDummyAlign${i}`],
|
||||
warningPrefix,
|
||||
);
|
||||
i++;
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {FieldCheckboxFromJsonConfig} from '../field_checkbox.js';
|
||||
import {FieldDropdownFromJsonConfig} from '../field_dropdown';
|
||||
import {FieldImageFromJsonConfig} from '../field_image';
|
||||
import {FieldNumberFromJsonConfig} from '../field_number';
|
||||
import {FieldTextInputFromJsonConfig} from '../field_textinput';
|
||||
import {FieldVariableFromJsonConfig} from '../field_variable';
|
||||
import {Align} from '../inputs/align.js';
|
||||
|
||||
/**
|
||||
* Defines the JSON structure for a block definition.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const blockDef: JsonBlockDefinition = {
|
||||
* type: 'custom_block',
|
||||
* message0: 'move %1 steps',
|
||||
* args0: [
|
||||
* {
|
||||
* 'type': 'field_number',
|
||||
* 'name': 'INPUT',
|
||||
* },
|
||||
* ],
|
||||
* previousStatement: null,
|
||||
* nextStatement: null,
|
||||
* };
|
||||
* ```
|
||||
*/
|
||||
export interface JsonBlockDefinition {
|
||||
type: string;
|
||||
style?: string | null;
|
||||
colour?: string | number;
|
||||
output?: string | string[] | null;
|
||||
previousStatement?: string | string[] | null;
|
||||
nextStatement?: string | string[] | null;
|
||||
outputShape?: number;
|
||||
inputsInline?: boolean;
|
||||
tooltip?: string;
|
||||
helpUrl?: string;
|
||||
extensions?: string[];
|
||||
mutator?: string;
|
||||
enableContextMenu?: boolean;
|
||||
suppressPrefixSuffix?: boolean;
|
||||
|
||||
[key: `message${number}`]: string | undefined;
|
||||
[key: `args${number}`]: JsonBlockArg[] | undefined;
|
||||
[key: `implicitAlign${number}`]: string | undefined;
|
||||
}
|
||||
|
||||
export type JsonBlockArg =
|
||||
| InputValueArg
|
||||
| InputStatementArg
|
||||
| InputDummyArg
|
||||
| InputEndRowArg
|
||||
| FieldInputArg
|
||||
| FieldNumberArg
|
||||
| FieldDropdownArg
|
||||
| FieldCheckboxArg
|
||||
| FieldImageArg
|
||||
| FieldVariableArg
|
||||
| UnknownArg;
|
||||
|
||||
interface UnknownArg {
|
||||
type: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
/** Input args */
|
||||
interface InputValueArg {
|
||||
type: 'input_value';
|
||||
name?: string;
|
||||
check?: string | string[];
|
||||
align?: Align;
|
||||
}
|
||||
|
||||
interface InputStatementArg {
|
||||
type: 'input_statement';
|
||||
name?: string;
|
||||
check?: string | string[];
|
||||
}
|
||||
|
||||
interface InputDummyArg {
|
||||
type: 'input_dummy';
|
||||
name?: string;
|
||||
}
|
||||
|
||||
interface InputEndRowArg {
|
||||
type: 'input_end_row';
|
||||
name?: string;
|
||||
}
|
||||
|
||||
/** Field args */
|
||||
interface FieldInputArg extends FieldTextInputFromJsonConfig {
|
||||
type: 'field_input';
|
||||
name?: string;
|
||||
}
|
||||
|
||||
interface FieldNumberArg extends FieldNumberFromJsonConfig {
|
||||
type: 'field_number';
|
||||
name?: string;
|
||||
}
|
||||
|
||||
interface FieldDropdownArg extends FieldDropdownFromJsonConfig {
|
||||
type: 'field_dropdown';
|
||||
name?: string;
|
||||
}
|
||||
|
||||
interface FieldCheckboxArg extends FieldCheckboxFromJsonConfig {
|
||||
type: 'field_checkbox';
|
||||
name?: string;
|
||||
}
|
||||
|
||||
interface FieldImageArg extends FieldImageFromJsonConfig {
|
||||
type: 'field_image';
|
||||
name?: string;
|
||||
}
|
||||
|
||||
interface FieldVariableArg extends FieldVariableFromJsonConfig {
|
||||
type: 'field_variable';
|
||||
name?: string;
|
||||
}
|
||||
@@ -240,7 +240,9 @@ export function registerCut() {
|
||||
if (focused instanceof BlockSvg) {
|
||||
focused.checkAndDelete();
|
||||
} else if (isIDeletable(focused)) {
|
||||
eventUtils.setGroup(true);
|
||||
focused.dispose();
|
||||
eventUtils.setGroup(false);
|
||||
}
|
||||
return !!copyData;
|
||||
},
|
||||
|
||||
@@ -299,6 +299,11 @@ suite('Keyboard Shortcut Items', function () {
|
||||
this.disposeSpy = sinon.spy(this.comment, 'dispose');
|
||||
|
||||
this.injectionDiv.dispatchEvent(keyEvent);
|
||||
|
||||
const deleteEvents = this.workspace
|
||||
.getUndoStack()
|
||||
.filter((e) => e.type === 'comment_delete');
|
||||
assert(deleteEvents[0].group !== ''); // Group string is not empty
|
||||
sinon.assert.calledOnce(this.copySpy);
|
||||
sinon.assert.calledOnce(this.disposeSpy);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {defineBlocksWithJsonArray} from 'blockly-test/core';
|
||||
import type {JsonBlockDefinition} from 'blockly-test/core/interfaces/i_json_block_definition';
|
||||
|
||||
import './different_user_input';
|
||||
|
||||
const mitosisBlockDefinition: JsonBlockDefinition = {
|
||||
type: 'mitosis_block',
|
||||
message0: 'split cell %1',
|
||||
args0: [
|
||||
{
|
||||
type: 'field_mitosis',
|
||||
name: 'CELL',
|
||||
cellId: 'cell-A',
|
||||
},
|
||||
],
|
||||
previousStatement: null,
|
||||
nextStatement: null,
|
||||
};
|
||||
|
||||
defineBlocksWithJsonArray([mitosisBlockDefinition]);
|
||||
Reference in New Issue
Block a user