@triflare/fx 1.0.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +32 -0
  2. package/bin/fx.js +4 -1160
  3. package/package.json +9 -3
  4. package/product.yml +1 -1
  5. package/src/brand.js +9 -3
  6. package/src/build.js +2 -1
  7. package/src/catalog.js +117 -140
  8. package/src/cli/commands/build.js +98 -0
  9. package/src/cli/commands/catalog.js +42 -0
  10. package/src/cli/commands/check.js +44 -0
  11. package/src/cli/commands/completion.js +54 -0
  12. package/src/cli/commands/decompile.js +266 -0
  13. package/src/cli/commands/dev.js +179 -0
  14. package/src/cli/commands/extension.js +110 -0
  15. package/src/cli/commands/index.js +39 -0
  16. package/src/cli/commands/init.js +169 -0
  17. package/src/cli/commands/package.js +137 -0
  18. package/src/cli/commands/sprite/add.js +85 -0
  19. package/src/cli/commands/sprite/build.js +62 -0
  20. package/src/cli/commands/sprite/helpers.js +66 -0
  21. package/src/cli/commands/sprite/index.js +29 -0
  22. package/src/cli/commands/sprite/list.js +31 -0
  23. package/src/cli/commands/sprite/remove.js +51 -0
  24. package/src/cli/commands/vendor/add.js +98 -0
  25. package/src/cli/commands/vendor/helpers.js +50 -0
  26. package/src/cli/commands/vendor/index.js +39 -0
  27. package/src/cli/commands/vendor/install.js +64 -0
  28. package/src/cli/commands/vendor/list.js +45 -0
  29. package/src/cli/commands/vendor/migrate.js +47 -0
  30. package/src/cli/commands/vendor/outdated.js +51 -0
  31. package/src/cli/commands/vendor/remove.js +34 -0
  32. package/src/cli/commands/vendor/update.js +49 -0
  33. package/src/cli/commands/vendor/verify.js +44 -0
  34. package/src/cli/completion/bash.js +10 -0
  35. package/src/cli/completion/fish.js +9 -0
  36. package/src/cli/completion/index.js +97 -0
  37. package/src/cli/completion/zsh.js +11 -0
  38. package/src/cli/context.js +257 -0
  39. package/src/cli/define-command.js +140 -0
  40. package/src/cli/errors.js +111 -0
  41. package/src/cli/format.js +31 -0
  42. package/src/cli/prompts.js +183 -0
  43. package/src/cli/reporter.js +194 -0
  44. package/src/cli/run.js +248 -0
  45. package/src/cli/templates/index.js +112 -0
  46. package/src/cli/theme.js +84 -0
  47. package/src/compiler.js +32 -3
  48. package/src/config-edit.js +280 -1
  49. package/src/config.js +137 -3
  50. package/src/decompiler.js +12 -2
  51. package/src/extensions.js +311 -48
  52. package/src/index.js +21 -0
  53. package/src/vendor.js +1323 -0
  54. package/vendor/pm-packager-dist/.fx-build.json +1 -1
package/src/catalog.js CHANGED
@@ -1,21 +1,39 @@
1
1
  /**
2
2
  * Triflare Fx block catalog.
3
3
  *
4
- * This module is the single source of truth for how a Triflare Fx
5
- * `category.function(...)` call maps onto a Scratch/PenguinMod opcode: its shape
6
- * (command/reporter/boolean/hat), its inputs, its dropdown fields and any
7
- * mutation it requires. The compiler, the decompiler and the editor tooling all
8
- * read from here instead of hard-coding opcode tables.
4
+ * The single source of truth for how a `category.function(...)` call maps onto
5
+ * a Scratch/PenguinMod opcode: shape, inputs, dropdown fields and mutations.
6
+ * The compiler, the decompiler and the editor tooling all read from here.
9
7
  *
10
- * Input descriptors:
11
- * {type: 'number'|'positive'|'whole'|'integer'|'angle'|'string'|'color'
12
- * |'boolean'|'substack'|'broadcast'|'variable'|'list'}
13
- * {type: 'menu', menu: <shadow opcode>, field: <field name>, default: <value>}
8
+ * Entry conventions:
9
+ * - `kind` is a command unless the entry says otherwise, so only reporters,
10
+ * booleans and hats spell it out (see the default applied below).
11
+ * - `inputs` maps an input name to one of the shared descriptors below.
12
+ * - `fields` maps a dropdown field name to its default value.
13
+ * - `args` describes a hat's parameters (`field` or `input`).
14
+ * - `extension` names the providing extension for non-core opcodes.
15
+ * - `dynamicNext` / `stopBlock` flag blocks with special stack behaviour.
14
16
  *
15
- * Field descriptors are plain objects providing the default dropdown value.
17
+ * Input descriptors:
18
+ * NUMBER, STRING, BOOLEAN, ANGLE, COLOR, INTEGER, POSITIVE, WHOLE, SUBSTACK
19
+ * and BROADCAST are shared `{type}` descriptors;
20
+ * `{type: 'menu', menu, field, default}` wraps a dropdown shadow block;
21
+ * `{type: 'variable'|'list'}` takes a variable/list reference.
16
22
  */
17
23
 
18
- // Common reusable menu descriptors -----------------------------------------
24
+ // Reusable input descriptors -----------------------------------------------
25
+ const NUMBER = { type: "number" };
26
+ const STRING = { type: "string" };
27
+ const BOOLEAN = { type: "boolean" };
28
+ const ANGLE = { type: "angle" };
29
+ const COLOR = { type: "color" };
30
+ const INTEGER = { type: "integer" };
31
+ const POSITIVE = { type: "positive" };
32
+ const WHOLE = { type: "whole" };
33
+ const SUBSTACK = { type: "substack" };
34
+ const BROADCAST = { type: "broadcast" };
35
+
36
+ // Reusable dropdown (shadow menu) descriptors -------------------------------
19
37
  const GOTO_MENU = {
20
38
  type: "menu",
21
39
  menu: "motion_goto_menu",
@@ -85,82 +103,67 @@ const OF_MENU = {
85
103
  default: "_stage_",
86
104
  };
87
105
 
88
- /** @type {Record<string, object>} */
89
- export const CATALOG = {
106
+ const CORE_CATALOG = {
90
107
  // ---------------------------------------------------------------- motion
91
108
  "motion.moveSteps": {
92
109
  opcode: "motion_movesteps",
93
- kind: "command",
94
- inputs: { STEPS: { type: "number" } },
110
+ inputs: { STEPS: NUMBER },
95
111
  },
96
112
  "motion.turnRight": {
97
113
  opcode: "motion_turnright",
98
- kind: "command",
99
- inputs: { DEGREES: { type: "number" } },
114
+ inputs: { DEGREES: NUMBER },
100
115
  },
101
116
  "motion.turnLeft": {
102
117
  opcode: "motion_turnleft",
103
- kind: "command",
104
- inputs: { DEGREES: { type: "number" } },
118
+ inputs: { DEGREES: NUMBER },
105
119
  },
106
120
  "motion.goToXY": {
107
121
  opcode: "motion_gotoxy",
108
- kind: "command",
109
- inputs: { X: { type: "number" }, Y: { type: "number" } },
122
+ inputs: { X: NUMBER, Y: NUMBER },
110
123
  },
111
124
  "motion.goTo": {
112
125
  opcode: "motion_goto",
113
- kind: "command",
114
126
  inputs: { TO: GOTO_MENU },
115
127
  },
116
128
  "motion.glideToXY": {
117
129
  opcode: "motion_glidesecstoxy",
118
- kind: "command",
119
130
  inputs: {
120
- SECS: { type: "number" },
121
- X: { type: "number" },
122
- Y: { type: "number" },
131
+ SECS: NUMBER,
132
+ X: NUMBER,
133
+ Y: NUMBER,
123
134
  },
124
135
  },
125
136
  "motion.glideTo": {
126
137
  opcode: "motion_glideto",
127
- kind: "command",
128
- inputs: { SECS: { type: "number" }, TO: GLIDE_MENU },
138
+ inputs: { SECS: NUMBER, TO: GLIDE_MENU },
129
139
  },
130
140
  "motion.pointInDirection": {
131
141
  opcode: "motion_pointindirection",
132
- kind: "command",
133
- inputs: { DIRECTION: { type: "angle" } },
142
+ inputs: { DIRECTION: ANGLE },
134
143
  },
135
144
  "motion.pointTowards": {
136
145
  opcode: "motion_pointtowards",
137
- kind: "command",
138
146
  inputs: { TOWARDS: POINT_MENU },
139
147
  },
140
148
  "motion.changeX": {
141
149
  opcode: "motion_changexby",
142
- kind: "command",
143
- inputs: { DX: { type: "number" } },
150
+ inputs: { DX: NUMBER },
144
151
  },
145
152
  "motion.setX": {
146
153
  opcode: "motion_setx",
147
- kind: "command",
148
- inputs: { X: { type: "number" } },
154
+ inputs: { X: NUMBER },
149
155
  },
150
156
  "motion.changeY": {
151
157
  opcode: "motion_changeyby",
152
- kind: "command",
153
- inputs: { DY: { type: "number" } },
158
+ inputs: { DY: NUMBER },
154
159
  },
155
160
  "motion.setY": {
156
161
  opcode: "motion_sety",
157
- kind: "command",
158
- inputs: { Y: { type: "number" } },
162
+ inputs: { Y: NUMBER },
159
163
  },
160
- "motion.ifOnEdgeBounce": { opcode: "motion_ifonedgebounce", kind: "command" },
164
+ "motion.ifOnEdgeBounce": { opcode: "motion_ifonedgebounce" },
161
165
  "motion.setRotationStyle": {
162
166
  opcode: "motion_setrotationstyle",
163
- kind: "command",
164
167
  fields: { STYLE: "all around" },
165
168
  },
166
169
  "motion.xPosition": { opcode: "motion_xposition", kind: "reporter" },
@@ -170,79 +173,65 @@ export const CATALOG = {
170
173
  // ----------------------------------------------------------------- looks
171
174
  "looks.say": {
172
175
  opcode: "looks_say",
173
- kind: "command",
174
- inputs: { MESSAGE: { type: "string" } },
176
+ inputs: { MESSAGE: STRING },
175
177
  },
176
178
  "looks.sayForSecs": {
177
179
  opcode: "looks_sayforsecs",
178
- kind: "command",
179
- inputs: { MESSAGE: { type: "string" }, SECS: { type: "number" } },
180
+ inputs: { MESSAGE: STRING, SECS: NUMBER },
180
181
  },
181
182
  "looks.think": {
182
183
  opcode: "looks_think",
183
- kind: "command",
184
- inputs: { MESSAGE: { type: "string" } },
184
+ inputs: { MESSAGE: STRING },
185
185
  },
186
186
  "looks.thinkForSecs": {
187
187
  opcode: "looks_thinkforsecs",
188
- kind: "command",
189
- inputs: { MESSAGE: { type: "string" }, SECS: { type: "number" } },
188
+ inputs: { MESSAGE: STRING, SECS: NUMBER },
190
189
  },
191
190
  "looks.switchCostume": {
192
191
  opcode: "looks_switchcostumeto",
193
- kind: "command",
194
192
  inputs: { COSTUME: COSTUME_MENU },
195
193
  },
196
- "looks.nextCostume": { opcode: "looks_nextcostume", kind: "command" },
194
+ "looks.nextCostume": { opcode: "looks_nextcostume" },
197
195
  "looks.switchBackdrop": {
198
196
  opcode: "looks_switchbackdropto",
199
- kind: "command",
200
197
  inputs: { BACKDROP: BACKDROP_MENU },
201
198
  },
202
199
  "looks.switchBackdropAndWait": {
203
200
  opcode: "looks_switchbackdroptoandwait",
204
- kind: "command",
205
201
  inputs: { BACKDROP: BACKDROP_MENU },
206
202
  },
207
- "looks.nextBackdrop": { opcode: "looks_nextbackdrop", kind: "command" },
203
+ "looks.nextBackdrop": { opcode: "looks_nextbackdrop" },
208
204
  "looks.changeSize": {
209
205
  opcode: "looks_changesizeby",
210
- kind: "command",
211
- inputs: { CHANGE: { type: "number" } },
206
+ inputs: { CHANGE: NUMBER },
212
207
  },
213
208
  "looks.setSize": {
214
209
  opcode: "looks_setsizeto",
215
- kind: "command",
216
- inputs: { SIZE: { type: "number" } },
210
+ inputs: { SIZE: NUMBER },
217
211
  },
218
212
  "looks.changeEffect": {
219
213
  opcode: "looks_changeeffectby",
220
- kind: "command",
221
214
  fields: { EFFECT: "color" },
222
- inputs: { CHANGE: { type: "number" } },
215
+ inputs: { CHANGE: NUMBER },
223
216
  },
224
217
  "looks.setEffect": {
225
218
  opcode: "looks_seteffectto",
226
- kind: "command",
227
219
  fields: { EFFECT: "color" },
228
- inputs: { VALUE: { type: "number" } },
220
+ inputs: { VALUE: NUMBER },
229
221
  },
230
222
  "looks.clearEffects": {
231
223
  opcode: "looks_cleargraphiceffects",
232
- kind: "command",
233
224
  },
234
- "looks.show": { opcode: "looks_show", kind: "command" },
235
- "looks.hide": { opcode: "looks_hide", kind: "command" },
225
+ "looks.show": { opcode: "looks_show" },
226
+ "looks.hide": { opcode: "looks_hide" },
236
227
  "looks.goToFrontLayer": {
237
228
  opcode: "looks_gotofrontback",
238
- kind: "command",
239
229
  fields: { FRONT_BACK: "front" },
240
230
  },
241
231
  "looks.goLayers": {
242
232
  opcode: "looks_goforwardbackwardlayers",
243
- kind: "command",
244
233
  fields: { FORWARD_BACKWARD: "forward" },
245
- inputs: { NUM: { type: "integer" } },
234
+ inputs: { NUM: INTEGER },
246
235
  },
247
236
  "looks.costumeNumber": {
248
237
  opcode: "looks_costumenumbername",
@@ -259,37 +248,31 @@ export const CATALOG = {
259
248
  // ----------------------------------------------------------------- sound
260
249
  "sound.play": {
261
250
  opcode: "sound_play",
262
- kind: "command",
263
251
  inputs: { SOUND_MENU: SOUND_MENU },
264
252
  },
265
253
  "sound.playUntilDone": {
266
254
  opcode: "sound_playuntildone",
267
- kind: "command",
268
255
  inputs: { SOUND_MENU: SOUND_MENU },
269
256
  },
270
- "sound.stopAllSounds": { opcode: "sound_stopallsounds", kind: "command" },
257
+ "sound.stopAllSounds": { opcode: "sound_stopallsounds" },
271
258
  "sound.changeEffect": {
272
259
  opcode: "sound_changeeffectby",
273
- kind: "command",
274
260
  fields: { EFFECT: "pitch" },
275
- inputs: { VALUE: { type: "number" } },
261
+ inputs: { VALUE: NUMBER },
276
262
  },
277
263
  "sound.setEffect": {
278
264
  opcode: "sound_seteffectto",
279
- kind: "command",
280
265
  fields: { EFFECT: "pitch" },
281
- inputs: { VALUE: { type: "number" } },
266
+ inputs: { VALUE: NUMBER },
282
267
  },
283
- "sound.clearEffects": { opcode: "sound_cleareffects", kind: "command" },
268
+ "sound.clearEffects": { opcode: "sound_cleareffects" },
284
269
  "sound.changeVolume": {
285
270
  opcode: "sound_changevolumeby",
286
- kind: "command",
287
- inputs: { VOLUME: { type: "number" } },
271
+ inputs: { VOLUME: NUMBER },
288
272
  },
289
273
  "sound.setVolume": {
290
274
  opcode: "sound_setvolumeto",
291
- kind: "command",
292
- inputs: { VOLUME: { type: "number" } },
275
+ inputs: { VOLUME: NUMBER },
293
276
  },
294
277
  "sound.volume": { opcode: "sound_volume", kind: "reporter" },
295
278
 
@@ -302,12 +285,12 @@ export const CATALOG = {
302
285
  "sensing.touchingColor": {
303
286
  opcode: "sensing_touchingcolor",
304
287
  kind: "boolean",
305
- inputs: { COLOR: { type: "color" } },
288
+ inputs: { COLOR: COLOR },
306
289
  },
307
290
  "sensing.colorIsTouchingColor": {
308
291
  opcode: "sensing_coloristouchingcolor",
309
292
  kind: "boolean",
310
- inputs: { COLOR: { type: "color" }, COLOR2: { type: "color" } },
293
+ inputs: { COLOR: COLOR, COLOR2: COLOR },
311
294
  },
312
295
  "sensing.distanceTo": {
313
296
  opcode: "sensing_distanceto",
@@ -316,8 +299,7 @@ export const CATALOG = {
316
299
  },
317
300
  "sensing.askAndWait": {
318
301
  opcode: "sensing_askandwait",
319
- kind: "command",
320
- inputs: { QUESTION: { type: "string" } },
302
+ inputs: { QUESTION: STRING },
321
303
  },
322
304
  "sensing.answer": { opcode: "sensing_answer", kind: "reporter" },
323
305
  "sensing.keyPressed": {
@@ -330,7 +312,7 @@ export const CATALOG = {
330
312
  "sensing.mouseY": { opcode: "sensing_mousey", kind: "reporter" },
331
313
  "sensing.loudness": { opcode: "sensing_loudness", kind: "reporter" },
332
314
  "sensing.timer": { opcode: "sensing_timer", kind: "reporter" },
333
- "sensing.resetTimer": { opcode: "sensing_resettimer", kind: "command" },
315
+ "sensing.resetTimer": { opcode: "sensing_resettimer" },
334
316
  "sensing.propertyOf": {
335
317
  opcode: "sensing_of",
336
318
  kind: "reporter",
@@ -352,110 +334,107 @@ export const CATALOG = {
352
334
  "operators.add": {
353
335
  opcode: "operator_add",
354
336
  kind: "reporter",
355
- inputs: { NUM1: { type: "number" }, NUM2: { type: "number" } },
337
+ inputs: { NUM1: NUMBER, NUM2: NUMBER },
356
338
  },
357
339
  "operators.subtract": {
358
340
  opcode: "operator_subtract",
359
341
  kind: "reporter",
360
- inputs: { NUM1: { type: "number" }, NUM2: { type: "number" } },
342
+ inputs: { NUM1: NUMBER, NUM2: NUMBER },
361
343
  },
362
344
  "operators.multiply": {
363
345
  opcode: "operator_multiply",
364
346
  kind: "reporter",
365
- inputs: { NUM1: { type: "number" }, NUM2: { type: "number" } },
347
+ inputs: { NUM1: NUMBER, NUM2: NUMBER },
366
348
  },
367
349
  "operators.divide": {
368
350
  opcode: "operator_divide",
369
351
  kind: "reporter",
370
- inputs: { NUM1: { type: "number" }, NUM2: { type: "number" } },
352
+ inputs: { NUM1: NUMBER, NUM2: NUMBER },
371
353
  },
372
354
  "operators.random": {
373
355
  opcode: "operator_random",
374
356
  kind: "reporter",
375
- inputs: { FROM: { type: "number" }, TO: { type: "number" } },
357
+ inputs: { FROM: NUMBER, TO: NUMBER },
376
358
  },
377
359
  "operators.gt": {
378
360
  opcode: "operator_gt",
379
361
  kind: "boolean",
380
- inputs: { OPERAND1: { type: "string" }, OPERAND2: { type: "string" } },
362
+ inputs: { OPERAND1: STRING, OPERAND2: STRING },
381
363
  },
382
364
  "operators.lt": {
383
365
  opcode: "operator_lt",
384
366
  kind: "boolean",
385
- inputs: { OPERAND1: { type: "string" }, OPERAND2: { type: "string" } },
367
+ inputs: { OPERAND1: STRING, OPERAND2: STRING },
386
368
  },
387
369
  "operators.equals": {
388
370
  opcode: "operator_equals",
389
371
  kind: "boolean",
390
- inputs: { OPERAND1: { type: "string" }, OPERAND2: { type: "string" } },
372
+ inputs: { OPERAND1: STRING, OPERAND2: STRING },
391
373
  },
392
374
  "operators.and": {
393
375
  opcode: "operator_and",
394
376
  kind: "boolean",
395
- inputs: { OPERAND1: { type: "boolean" }, OPERAND2: { type: "boolean" } },
377
+ inputs: { OPERAND1: BOOLEAN, OPERAND2: BOOLEAN },
396
378
  },
397
379
  "operators.or": {
398
380
  opcode: "operator_or",
399
381
  kind: "boolean",
400
- inputs: { OPERAND1: { type: "boolean" }, OPERAND2: { type: "boolean" } },
382
+ inputs: { OPERAND1: BOOLEAN, OPERAND2: BOOLEAN },
401
383
  },
402
384
  "operators.not": {
403
385
  opcode: "operator_not",
404
386
  kind: "boolean",
405
- inputs: { OPERAND: { type: "boolean" } },
387
+ inputs: { OPERAND: BOOLEAN },
406
388
  },
407
389
  "operators.join": {
408
390
  opcode: "operator_join",
409
391
  kind: "reporter",
410
- inputs: { STRING1: { type: "string" }, STRING2: { type: "string" } },
392
+ inputs: { STRING1: STRING, STRING2: STRING },
411
393
  },
412
394
  "operators.letterOf": {
413
395
  opcode: "operator_letter_of",
414
396
  kind: "reporter",
415
- inputs: { LETTER: { type: "number" }, STRING: { type: "string" } },
397
+ inputs: { LETTER: NUMBER, STRING: STRING },
416
398
  },
417
399
  "operators.length": {
418
400
  opcode: "operator_length",
419
401
  kind: "reporter",
420
- inputs: { STRING: { type: "string" } },
402
+ inputs: { STRING: STRING },
421
403
  },
422
404
  "operators.contains": {
423
405
  opcode: "operator_contains",
424
406
  kind: "boolean",
425
- inputs: { STRING1: { type: "string" }, STRING2: { type: "string" } },
407
+ inputs: { STRING1: STRING, STRING2: STRING },
426
408
  },
427
409
  "operators.mod": {
428
410
  opcode: "operator_mod",
429
411
  kind: "reporter",
430
- inputs: { NUM1: { type: "number" }, NUM2: { type: "number" } },
412
+ inputs: { NUM1: NUMBER, NUM2: NUMBER },
431
413
  },
432
414
  "operators.round": {
433
415
  opcode: "operator_round",
434
416
  kind: "reporter",
435
- inputs: { NUM: { type: "number" } },
417
+ inputs: { NUM: NUMBER },
436
418
  },
437
419
  "operators.math": {
438
420
  opcode: "operator_mathop",
439
421
  kind: "reporter",
440
422
  fields: { OPERATOR: "abs" },
441
- inputs: { NUM: { type: "number" } },
423
+ inputs: { NUM: NUMBER },
442
424
  },
443
425
 
444
426
  // --------------------------------------------------------------- control
445
427
  "control.wait": {
446
428
  opcode: "control_wait",
447
- kind: "command",
448
- inputs: { DURATION: { type: "positive" } },
429
+ inputs: { DURATION: POSITIVE },
449
430
  },
450
431
  "control.repeat": {
451
432
  opcode: "control_repeat",
452
- kind: "command",
453
- inputs: { TIMES: { type: "whole" }, SUBSTACK: { type: "substack" } },
433
+ inputs: { TIMES: WHOLE, SUBSTACK: SUBSTACK },
454
434
  },
455
435
  "control.forever": {
456
436
  opcode: "control_forever",
457
- kind: "command",
458
- inputs: { SUBSTACK: { type: "substack" } },
437
+ inputs: { SUBSTACK: SUBSTACK },
459
438
  // PenguinMod builds this loop without a next-statement connection and only
460
439
  // creates one from the block's mutation (`hasbreak` — the nub the editor
461
440
  // adds by itself once a block sits below the loop). Blocks stacked under a
@@ -466,54 +445,45 @@ export const CATALOG = {
466
445
  },
467
446
  "control.if": {
468
447
  opcode: "control_if",
469
- kind: "command",
470
- inputs: { CONDITION: { type: "boolean" }, SUBSTACK: { type: "substack" } },
448
+ inputs: { CONDITION: BOOLEAN, SUBSTACK: SUBSTACK },
471
449
  },
472
450
  "control.ifElse": {
473
451
  opcode: "control_if_else",
474
- kind: "command",
475
452
  inputs: {
476
- CONDITION: { type: "boolean" },
477
- SUBSTACK: { type: "substack" },
478
- SUBSTACK2: { type: "substack" },
453
+ CONDITION: BOOLEAN,
454
+ SUBSTACK: SUBSTACK,
455
+ SUBSTACK2: SUBSTACK,
479
456
  },
480
457
  },
481
458
  "control.waitUntil": {
482
459
  opcode: "control_wait_until",
483
- kind: "command",
484
- inputs: { CONDITION: { type: "boolean" } },
460
+ inputs: { CONDITION: BOOLEAN },
485
461
  },
486
462
  "control.repeatUntil": {
487
463
  opcode: "control_repeat_until",
488
- kind: "command",
489
- inputs: { CONDITION: { type: "boolean" }, SUBSTACK: { type: "substack" } },
464
+ inputs: { CONDITION: BOOLEAN, SUBSTACK: SUBSTACK },
490
465
  },
491
466
  "control.stop": {
492
467
  opcode: "control_stop",
493
- kind: "command",
494
468
  fields: { STOP_OPTION: "all" },
495
469
  stopBlock: true,
496
470
  },
497
471
  "control.clone": {
498
472
  opcode: "control_create_clone_of",
499
- kind: "command",
500
473
  inputs: { CLONE_OPTION: CLONE_MENU },
501
474
  },
502
475
  "control.deleteThisClone": {
503
476
  opcode: "control_delete_this_clone",
504
- kind: "command",
505
477
  },
506
478
 
507
479
  // ---------------------------------------------------------------- events
508
480
  "events.broadcast": {
509
481
  opcode: "event_broadcast",
510
- kind: "command",
511
- inputs: { BROADCAST_INPUT: { type: "broadcast" } },
482
+ inputs: { BROADCAST_INPUT: BROADCAST },
512
483
  },
513
484
  "events.broadcastAndWait": {
514
485
  opcode: "event_broadcastandwait",
515
- kind: "command",
516
- inputs: { BROADCAST_INPUT: { type: "broadcast" } },
486
+ inputs: { BROADCAST_INPUT: BROADCAST },
517
487
  },
518
488
 
519
489
  // --------------------------------------------------------------- hats
@@ -574,26 +544,23 @@ export const CATALOG = {
574
544
  },
575
545
 
576
546
  // ------------------------------------------------------------------ pen
577
- "pen.clear": { opcode: "pen_clear", kind: "command", extension: "pen" },
578
- "pen.stamp": { opcode: "pen_stamp", kind: "command", extension: "pen" },
579
- "pen.penDown": { opcode: "pen_penDown", kind: "command", extension: "pen" },
580
- "pen.penUp": { opcode: "pen_penUp", kind: "command", extension: "pen" },
547
+ "pen.clear": { opcode: "pen_clear", extension: "pen" },
548
+ "pen.stamp": { opcode: "pen_stamp", extension: "pen" },
549
+ "pen.penDown": { opcode: "pen_penDown", extension: "pen" },
550
+ "pen.penUp": { opcode: "pen_penUp", extension: "pen" },
581
551
  "pen.setColor": {
582
552
  opcode: "pen_setPenColorToColor",
583
- kind: "command",
584
- inputs: { COLOR: { type: "color" } },
553
+ inputs: { COLOR: COLOR },
585
554
  extension: "pen",
586
555
  },
587
556
  "pen.changeSize": {
588
557
  opcode: "pen_changePenSizeBy",
589
- kind: "command",
590
- inputs: { SIZE: { type: "number" } },
558
+ inputs: { SIZE: NUMBER },
591
559
  extension: "pen",
592
560
  },
593
561
  "pen.setSize": {
594
562
  opcode: "pen_setPenSizeTo",
595
- kind: "command",
596
- inputs: { SIZE: { type: "number" } },
563
+ inputs: { SIZE: NUMBER },
597
564
  extension: "pen",
598
565
  },
599
566
  // COLOR_PARAM is an input whose shadow is `pen_menu_colorParam`, not a
@@ -602,7 +569,6 @@ export const CATALOG = {
602
569
  // wrapping a shadow menu block (field name `colorParam`).
603
570
  "pen.changeColorParam": {
604
571
  opcode: "pen_changePenColorParamBy",
605
- kind: "command",
606
572
  inputs: {
607
573
  COLOR_PARAM: {
608
574
  type: "menu",
@@ -610,13 +576,12 @@ export const CATALOG = {
610
576
  field: "colorParam",
611
577
  default: "color",
612
578
  },
613
- VALUE: { type: "number" },
579
+ VALUE: NUMBER,
614
580
  },
615
581
  extension: "pen",
616
582
  },
617
583
  "pen.setColorParam": {
618
584
  opcode: "pen_setPenColorParamTo",
619
- kind: "command",
620
585
  inputs: {
621
586
  COLOR_PARAM: {
622
587
  type: "menu",
@@ -624,7 +589,7 @@ export const CATALOG = {
624
589
  field: "colorParam",
625
590
  default: "color",
626
591
  },
627
- VALUE: { type: "number" },
592
+ VALUE: NUMBER,
628
593
  },
629
594
  extension: "pen",
630
595
  },
@@ -647,6 +612,18 @@ export const CATALOG = {
647
612
  },
648
613
  };
649
614
 
615
+ /**
616
+ * Catalog entries with the default `kind` filled in: everything is a command
617
+ * unless the table above said otherwise.
618
+ * @type {Record<string, object>}
619
+ */
620
+ export const CATALOG = Object.fromEntries(
621
+ Object.entries(CORE_CATALOG).map(([name, entry]) => [
622
+ name,
623
+ { kind: "command", ...entry },
624
+ ]),
625
+ );
626
+
650
627
  /**
651
628
  * Native Scratch variable/list mutation opcodes used when a list method is
652
629
  * called (`items.push(x)` etc.).