Skip to content

Commit 4163db8

Browse files
authored
chore: merge main into v13
chore: merge main into v13
2 parents b4d21b2 + cdcdaf3 commit 4163db8

8 files changed

Lines changed: 279 additions & 19 deletions

File tree

.github/copilot-instructions.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Code review guidelines
2+
3+
## General principles
4+
- **Style:** Suggest `npm run format` or `npm run lint:fix` for formatting issues; do not comment on individual style nits.
5+
- **Patterns:** Enforce existing Blockly patterns and official docs over new conventions.
6+
- **Documentation:** Prefer linking to [Blockly Dev Docs](https://developers.google.com/blockly) over duplicating content in comments.
7+
- **TSDoc:** Public APIs require TSDoc for behavior, params, and returns. Do not include implementation details or historical context unless essential.
8+
9+
## Localization
10+
- All user-visible strings must use `Blockly.Msg`.
11+
- New strings must be added to `msg/messages.js`, `msg/json/qqq.json`, and `msg/json/en.json`.
12+
- Link [this guide](https://developers.google.com/blockly/guides/contribute/core/add_localization_token) if strings are missing or misplaced.
13+
- 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)).
14+
15+
## Breaking changes
16+
### Policy
17+
- A breaking change is any non-backwards-compatible change to public APIs, behavior, UI, or browser requirements.
18+
- **Avoid:** Prefer deprecation with migration paths over removal.
19+
- **Compatibility:** Must support Safari 15.4+, latest Chrome, and latest Firefox.
20+
- **Identification:** Flag breaking changes unless all of the following are true:
21+
1. PR description explicitly notes it.
22+
2. Commit type includes `!` (e.g., `feat!:`).
23+
3. Target branch is not `main`.
24+
25+
### Breaking
26+
- Removing/renaming public methods, properties, or classes.
27+
- Changing signatures or behavior of existing public methods.
28+
- Adding required methods to public interfaces.
29+
- New keyboard shortcuts or context menu items (potential developer conflicts).
30+
- DOM restructures affecting external CSS/JS.
31+
- Changes to build output/consumption (e.g., ESM-only).
32+
- Changes that affect the output of serialization.
33+
34+
### Non-breaking (do not flag)
35+
- Additive changes (new methods/properties).
36+
- Internal refactoring (including items marked `@internal`).
37+
- Tooling/workflow changes.
38+
- Changes to unreleased code (non-`main` feature branches).

.github/workflows/build.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ jobs:
2727
steps:
2828
- uses: actions/checkout@v5
2929
with:
30+
ref: ${{ github.ref }}
3031
persist-credentials: false
3132

3233
- name: Reconfigure git to use HTTP authentication
@@ -58,6 +59,8 @@ jobs:
5859
runs-on: ubuntu-latest
5960
steps:
6061
- uses: actions/checkout@v5
62+
with:
63+
ref: ${{ github.ref }}
6164

6265
- name: Use Node.js 24.x
6366
uses: actions/setup-node@v5
@@ -75,6 +78,8 @@ jobs:
7578
runs-on: ubuntu-latest
7679
steps:
7780
- uses: actions/checkout@v5
81+
with:
82+
ref: ${{ github.ref }}
7883

7984
- name: Use Node.js 24.x
8085
uses: actions/setup-node@v5

.github/workflows/publish.yml

Lines changed: 68 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,27 @@ on:
44
workflow_dispatch:
55
inputs:
66
dry_run:
7-
description: 'Dry run - print the version that would be published, but do not commit or publish anything.'
7+
description: >
8+
Dry run — print the version and npm dist-tag that would be used; no commit or publish.
9+
Pick the branch to publish from with the "Use workflow from" dropdown.
10+
Non-default branches publish to the npm dist-tag `beta` (not `latest`).
811
required: false
912
default: false
1013
type: boolean
1114
skip_versioning:
1215
description: >
13-
Skip version bump - use the version already in the repo
16+
Skip version bump use the version already in the repo
1417
(e.g. retry after npm publish failed but the release commit is already pushed).
1518
required: false
1619
default: false
1720
type: boolean
21+
version_override:
22+
description: >
23+
Optional. Full semver to publish (e.g. 12.6.0-beta.2). Skips conventional bump when set.
24+
Leave empty for automatic versioning.
25+
required: false
26+
default: ''
27+
type: string
1828

1929
permissions:
2030
contents: write
@@ -34,6 +44,7 @@ jobs:
3444
- name: Checkout
3545
uses: actions/checkout@v5
3646
with:
47+
ref: ${{ github.ref }}
3748
fetch-depth: 0
3849

3950
- name: Setup Node.js
@@ -48,7 +59,7 @@ jobs:
4859

4960
- name: Determine version bump
5061
id: bump
51-
if: ${{ !inputs.skip_versioning }}
62+
if: ${{ !inputs.skip_versioning && inputs.version_override == '' }}
5263
working-directory: packages/blockly
5364
run: |
5465
RELEASE_TYPE=$(npx conventional-recommended-bump --preset conventionalcommits -t blockly-)
@@ -58,7 +69,35 @@ jobs:
5869
- name: Apply version bump
5970
if: ${{ !inputs.skip_versioning }}
6071
working-directory: packages/blockly
61-
run: npm version ${{ steps.bump.outputs.release_type }} --no-git-tag-version
72+
env:
73+
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
74+
REF_NAME: ${{ github.ref_name }}
75+
RELEASE_TYPE: ${{ steps.bump.outputs.release_type }}
76+
VERSION_OVERRIDE: ${{ inputs.version_override }}
77+
run: |
78+
set -euo pipefail
79+
if [ -n "${VERSION_OVERRIDE}" ]; then
80+
npm version "${VERSION_OVERRIDE}" --no-git-tag-version
81+
exit 0
82+
fi
83+
if [ "${REF_NAME}" = "${DEFAULT_BRANCH}" ]; then
84+
npm version "${RELEASE_TYPE}" --no-git-tag-version
85+
exit 0
86+
fi
87+
VERSION=$(node -p "require('./package.json').version")
88+
if [[ "${VERSION}" == *"-beta."* ]]; then
89+
npm version prerelease --preid=beta --no-git-tag-version
90+
else
91+
case "${RELEASE_TYPE}" in
92+
major) npm version premajor --preid=beta --no-git-tag-version ;;
93+
minor) npm version preminor --preid=beta --no-git-tag-version ;;
94+
patch) npm version prepatch --preid=beta --no-git-tag-version ;;
95+
*)
96+
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
97+
exit 1
98+
;;
99+
esac
100+
fi
62101
63102
- name: Read package version
64103
id: version
@@ -68,6 +107,15 @@ jobs:
68107
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
69108
echo "Version: $VERSION"
70109
110+
- name: Dry run summary
111+
if: ${{ inputs.dry_run }}
112+
run: |
113+
DIST_TAG="${{ github.ref_name == github.event.repository.default_branch && 'latest' || 'beta' }}"
114+
echo "Dry run: would publish version ${{ steps.version.outputs.version }} to npm dist-tag: ${DIST_TAG}"
115+
if [ "${{ github.ref_name }}" != "${{ github.event.repository.default_branch }}" ]; then
116+
echo "GitHub release would be created as prerelease."
117+
fi
118+
71119
- name: Upload versioned files
72120
if: ${{ !inputs.skip_versioning }}
73121
uses: actions/upload-artifact@v4
@@ -82,10 +130,13 @@ jobs:
82130
runs-on: ubuntu-latest
83131
if: ${{ !inputs.dry_run }}
84132
environment: release
133+
env:
134+
NPM_DIST_TAG: ${{ github.ref_name == github.event.repository.default_branch && 'latest' || 'beta' }}
85135
steps:
86136
- name: Checkout
87137
uses: actions/checkout@v5
88138
with:
139+
ref: ${{ github.ref }}
89140
fetch-depth: 0
90141
ssh-key: ${{ secrets.DEPLOY_PRIVATE_KEY }}
91142

@@ -119,7 +170,7 @@ jobs:
119170

120171
- name: Publish to npm
121172
working-directory: packages/blockly/dist
122-
run: npm publish --verbose
173+
run: npm publish --tag "${NPM_DIST_TAG}" --verbose
123174

124175
- name: Create tarball
125176
working-directory: packages/blockly
@@ -131,7 +182,15 @@ jobs:
131182
GH_TOKEN: ${{ github.token }}
132183
run: |
133184
TARBALL="blockly-${{ needs.version.outputs.version }}.tgz"
134-
gh release create "blockly-v${{ needs.version.outputs.version }}" "$TARBALL" \
135-
--repo "$GITHUB_REPOSITORY" \
136-
--title "blockly-v${{ needs.version.outputs.version }}" \
137-
--generate-notes
185+
if [ "${{ github.ref_name }}" != "${{ github.event.repository.default_branch }}" ]; then
186+
gh release create "blockly-v${{ needs.version.outputs.version }}" "$TARBALL" \
187+
--repo "$GITHUB_REPOSITORY" \
188+
--title "blockly-v${{ needs.version.outputs.version }}" \
189+
--generate-notes \
190+
--prerelease
191+
else
192+
gh release create "blockly-v${{ needs.version.outputs.version }}" "$TARBALL" \
193+
--repo "$GITHUB_REPOSITORY" \
194+
--title "blockly-v${{ needs.version.outputs.version }}" \
195+
--generate-notes
196+
fi

packages/blockly/core/block.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1728,8 +1728,8 @@ export class Block {
17281728

17291729
// Validate that each arg has a corresponding message
17301730
let n = 0;
1731-
while (json['args' + n]) {
1732-
if (json['message' + n] === undefined) {
1731+
while (json[`args${n}`]) {
1732+
if (json[`message${n}`] === undefined) {
17331733
throw Error(
17341734
warningPrefix +
17351735
`args${n} must have a corresponding message (message${n}).`,
@@ -1739,14 +1739,13 @@ export class Block {
17391739
}
17401740

17411741
// Set basic properties of block.
1742-
// Makes styles backward compatible with old way of defining hat style.
1743-
if (json['style'] && json['style'].hat) {
1744-
this.hat = json['style'].hat;
1742+
// Handle legacy style object format for backwards compatibility
1743+
if (json['style'] && typeof json['style'] === 'object') {
1744+
this.hat = (json['style'] as {hat?: string}).hat;
17451745
// Must set to null so it doesn't error when checking for style and
17461746
// colour.
17471747
json['style'] = null;
17481748
}
1749-
17501749
if (json['style'] && json['colour']) {
17511750
throw Error(warningPrefix + 'Must not have both a colour and a style.');
17521751
} else if (json['style']) {
@@ -1757,12 +1756,12 @@ export class Block {
17571756

17581757
// Interpolate the message blocks.
17591758
let i = 0;
1760-
while (json['message' + i] !== undefined) {
1759+
while (json[`message${i}`] !== undefined) {
17611760
this.interpolate(
1762-
json['message' + i],
1763-
json['args' + i] || [],
1761+
json[`message${i}`] || '',
1762+
json[`args${i}`] || [],
17641763
// Backwards compatibility: lastDummyAlign aliases implicitAlign.
1765-
json['implicitAlign' + i] || json['lastDummyAlign' + i],
1764+
json[`implicitAlign${i}`] || (json as any)[`lastDummyAlign${i}`],
17661765
warningPrefix,
17671766
);
17681767
i++;
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import {FieldCheckboxFromJsonConfig} from '../field_checkbox.js';
8+
import {FieldDropdownFromJsonConfig} from '../field_dropdown';
9+
import {FieldImageFromJsonConfig} from '../field_image';
10+
import {FieldNumberFromJsonConfig} from '../field_number';
11+
import {FieldTextInputFromJsonConfig} from '../field_textinput';
12+
import {FieldVariableFromJsonConfig} from '../field_variable';
13+
import {Align} from '../inputs/align.js';
14+
15+
/**
16+
* Defines the JSON structure for a block definition.
17+
*
18+
* @example
19+
* ```typescript
20+
* const blockDef: JsonBlockDefinition = {
21+
* type: 'custom_block',
22+
* message0: 'move %1 steps',
23+
* args0: [
24+
* {
25+
* 'type': 'field_number',
26+
* 'name': 'INPUT',
27+
* },
28+
* ],
29+
* previousStatement: null,
30+
* nextStatement: null,
31+
* };
32+
* ```
33+
*/
34+
export interface JsonBlockDefinition {
35+
type: string;
36+
style?: string | null;
37+
colour?: string | number;
38+
output?: string | string[] | null;
39+
previousStatement?: string | string[] | null;
40+
nextStatement?: string | string[] | null;
41+
outputShape?: number;
42+
inputsInline?: boolean;
43+
tooltip?: string;
44+
helpUrl?: string;
45+
extensions?: string[];
46+
mutator?: string;
47+
enableContextMenu?: boolean;
48+
suppressPrefixSuffix?: boolean;
49+
50+
[key: `message${number}`]: string | undefined;
51+
[key: `args${number}`]: JsonBlockArg[] | undefined;
52+
[key: `implicitAlign${number}`]: string | undefined;
53+
}
54+
55+
export type JsonBlockArg =
56+
| InputValueArg
57+
| InputStatementArg
58+
| InputDummyArg
59+
| InputEndRowArg
60+
| FieldInputArg
61+
| FieldNumberArg
62+
| FieldDropdownArg
63+
| FieldCheckboxArg
64+
| FieldImageArg
65+
| FieldVariableArg
66+
| UnknownArg;
67+
68+
interface UnknownArg {
69+
type: string;
70+
[key: string]: unknown;
71+
}
72+
73+
/** Input args */
74+
interface InputValueArg {
75+
type: 'input_value';
76+
name?: string;
77+
check?: string | string[];
78+
align?: Align;
79+
}
80+
81+
interface InputStatementArg {
82+
type: 'input_statement';
83+
name?: string;
84+
check?: string | string[];
85+
}
86+
87+
interface InputDummyArg {
88+
type: 'input_dummy';
89+
name?: string;
90+
}
91+
92+
interface InputEndRowArg {
93+
type: 'input_end_row';
94+
name?: string;
95+
}
96+
97+
/** Field args */
98+
interface FieldInputArg extends FieldTextInputFromJsonConfig {
99+
type: 'field_input';
100+
name?: string;
101+
}
102+
103+
interface FieldNumberArg extends FieldNumberFromJsonConfig {
104+
type: 'field_number';
105+
name?: string;
106+
}
107+
108+
interface FieldDropdownArg extends FieldDropdownFromJsonConfig {
109+
type: 'field_dropdown';
110+
name?: string;
111+
}
112+
113+
interface FieldCheckboxArg extends FieldCheckboxFromJsonConfig {
114+
type: 'field_checkbox';
115+
name?: string;
116+
}
117+
118+
interface FieldImageArg extends FieldImageFromJsonConfig {
119+
type: 'field_image';
120+
name?: string;
121+
}
122+
123+
interface FieldVariableArg extends FieldVariableFromJsonConfig {
124+
type: 'field_variable';
125+
name?: string;
126+
}

packages/blockly/core/shortcut_items.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,9 @@ export function registerCut() {
240240
if (focused instanceof BlockSvg) {
241241
focused.checkAndDelete();
242242
} else if (isIDeletable(focused)) {
243+
eventUtils.setGroup(true);
243244
focused.dispose();
245+
eventUtils.setGroup(false);
244246
}
245247
return !!copyData;
246248
},

0 commit comments

Comments
 (0)