castle-web-cli 0.4.113 → 0.4.115

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 (57) hide show
  1. package/dist/castleJson.d.ts +1 -0
  2. package/dist/editorConfig.d.ts +34 -0
  3. package/dist/editorConfig.js +101 -0
  4. package/dist/ide.js +245 -120
  5. package/dist/imports.d.ts +6 -0
  6. package/dist/imports.js +90 -7
  7. package/dist/init.js +15 -2
  8. package/dist/serve.js +16 -0
  9. package/dist/shell/assets/index-CUamb8rK.js +144 -0
  10. package/dist/shell/assets/index-Y6cJRRCX.css +1 -0
  11. package/dist/shell/index.html +2 -2
  12. package/dist/unsupportedMedia.d.ts +1 -0
  13. package/dist/unsupportedMedia.js +54 -0
  14. package/dist/vitePlugins.js +12 -19
  15. package/kits/basic-2d/castle.json +1 -1
  16. package/kits/basic-2d/editors/behaviorRegistry.js +5 -7
  17. package/kits/physics-2d/CLAUDE.md +42 -23
  18. package/kits/physics-2d/{physics/behaviors → behaviors}/AnalogStick.jsx +3 -3
  19. package/kits/physics-2d/behaviors/Collider.jsx +1 -1
  20. package/kits/physics-2d/{physics/behaviors → behaviors}/Draggable.jsx +3 -3
  21. package/kits/physics-2d/{physics/behaviors → behaviors}/Joints.jsx +4 -4
  22. package/kits/physics-2d/{physics/behaviors → behaviors}/RigidBody.jsx +2 -2
  23. package/kits/physics-2d/{physics/behaviors → behaviors}/Slingshot.jsx +3 -3
  24. package/kits/physics-2d/behaviors/Sound.jsx +152 -0
  25. package/kits/physics-2d/behaviors/Sprite.jsx +98 -33
  26. package/kits/physics-2d/behaviors/Tone.jsx +83 -0
  27. package/kits/physics-2d/behaviors/Video.jsx +111 -0
  28. package/kits/physics-2d/behaviors/tint.js +24 -9
  29. package/kits/physics-2d/castle.json +64 -2
  30. package/kits/physics-2d/editors/BlueprintLibrary.jsx +7 -3
  31. package/kits/physics-2d/editors/ImageViewer.jsx +206 -0
  32. package/kits/physics-2d/editors/MediaPlayer.jsx +57 -0
  33. package/kits/physics-2d/editors/SceneEditor.jsx +7 -3
  34. package/kits/physics-2d/editors/SingleEditor.jsx +8 -0
  35. package/kits/physics-2d/editors/behaviorRegistry.js +5 -7
  36. package/kits/physics-2d/editors/mediaFile.js +20 -0
  37. package/kits/physics-2d/editors/mediaViewer.module.css +128 -0
  38. package/kits/physics-2d/engine/ScenePlayer.jsx +1 -0
  39. package/kits/physics-2d/engine/art.js +105 -0
  40. package/kits/physics-2d/engine/assets.js +11 -3
  41. package/kits/physics-2d/engine/audioContext.js +34 -0
  42. package/kits/physics-2d/engine/collider.js +28 -17
  43. package/kits/physics-2d/engine/files.js +74 -0
  44. package/kits/physics-2d/engine/media.js +212 -0
  45. package/kits/physics-2d/{physics → engine/physics}/PhysicsSystem.js +1 -1
  46. package/kits/physics-2d/{physics → engine/physics}/jointArt.js +1 -2
  47. package/kits/physics-2d/{physics → engine/physics}/matterBridge.js +2 -9
  48. package/kits/physics-2d/engine/scene.js +12 -0
  49. package/kits/physics-2d/engine/tone.js +112 -0
  50. package/kits/physics-2d/systems/media.js +34 -0
  51. package/kits/physics-2d/systems/physics.js +12 -3
  52. package/package.json +1 -1
  53. package/dist/shell/assets/index-BjOuaDJM.js +0 -144
  54. package/dist/shell/assets/index-CdXEpv_P.css +0 -1
  55. package/kits/physics-2d/physics/index.js +0 -26
  56. /package/kits/physics-2d/{physics → engine/physics}/controls.js +0 -0
  57. /package/kits/physics-2d/{physics → engine/physics}/joints.js +0 -0
package/dist/imports.js CHANGED
@@ -5,7 +5,7 @@ import * as api from './api.js';
5
5
  import { runTar } from './save-deck.js';
6
6
  import { normalizeDeckPackageJson } from './normalize.js';
7
7
  import { getKitsDir } from './localPaths.js';
8
- import { readCastleJsonOrThrow as readCastleJson, } from './castleJson.js';
8
+ import { readCastleJson as tryReadCastleJson, readCastleJsonOrThrow as readCastleJson, } from './castleJson.js';
9
9
  // Adding another deck as a dependency (`castle-web add-import`; removing one is a
10
10
  // later command). Deliberately NOT `get-deck`: that one
11
11
  // replaces THIS deck's own source from the server (and carries guards for the
@@ -503,6 +503,94 @@ export async function addImport(dir, options = {}) {
503
503
  if (result.dependencies.length > 0)
504
504
  console.log('Run `castle-web install` to install them.');
505
505
  }
506
+ // Take one import's update: replace its files with the version the server has
507
+ // now, then follow that version's own pins for anything newly required. Both
508
+ // `update-import` and the serve-start auto update come through here, so there
509
+ // is one path that replaces an import's files rather than two that have to be
510
+ // kept in step. `log` is how the caller labels the lines it produces.
511
+ async function applyImportUpdate(deckDir, alias, update, source, log) {
512
+ await placeImport(deckDir, alias, update.deckId, source, update.via);
513
+ log(`Updated ${alias} (${update.from} -> ${source.updatedAt})`);
514
+ // Its own dependencies may have moved too.
515
+ const transitive = await addTransitiveImports(deckDir, alias, importedDeckIds(deckDir));
516
+ for (const t of transitive)
517
+ log(`Also imported ${IMPORTS_DIR}/${t} (needed by ${alias})`);
518
+ }
519
+ // Whether the deck behind an import asked to be tracked rather than pinned --
520
+ // `autoUpdateWhenImported` in ITS castle.json, which is a statement its author
521
+ // makes about their own deck (see castleJson.ts).
522
+ //
523
+ // Read from the copy already on disk, NOT from the version about to be fetched.
524
+ // The flag the importer has is the one that was there when they took this
525
+ // version; letting the incoming version answer would let any deck start pushing
526
+ // to everyone who already imported it, after the fact. The cost is a one-update
527
+ // lag both ways -- turning the flag on reaches an existing importer only once
528
+ // they take an update by hand, and turning it off gets delivered by one last
529
+ // auto update -- which is the safe direction for both.
530
+ function importOptsIntoAutoUpdate(deckDir, alias) {
531
+ const config = tryReadCastleJson(path.join(deckDir, IMPORTS_DIR, alias));
532
+ return config?.autoUpdateWhenImported === true;
533
+ }
534
+ // Auto-update is honored only for imports of decks on the castle account. The
535
+ // flag is the mechanism and this is a leash on it: a deck that pushes new code
536
+ // to everyone who imported it is asking for a lot of trust, and until decks
537
+ // carry some other signal of it, the only ones that get it are the ones we
538
+ // publish (the framework kits -- the same account `init` qualifies their aliases
539
+ // with).
540
+ //
541
+ // Deliberately not a setting: nothing a deck or a person can flip, since that
542
+ // would be the leash undoing itself. The env var is a test seam -- the automated
543
+ // check can't publish a deck to the castle account (nothing should), so it
544
+ // substitutes which account counts and exercises both branches for real.
545
+ const AUTO_UPDATE_ACCOUNT = process.env.CASTLE_AUTO_UPDATE_TRUSTED_ACCOUNT ?? 'castle';
546
+ // Who published a deck. Readable without auth for the same decks webDeckSource
547
+ // is, so this answers in a sandbox with no login. A lookup that fails answers
548
+ // "not trusted": the failure direction has to be the one that leaves an import
549
+ // where its deck's author pinned it.
550
+ async function importIsOnTrustedAccount(deckId) {
551
+ const meta = await api.deckMeta(deckId).catch(() => null);
552
+ return meta?.creator?.username === AUTO_UPDATE_ACCOUNT;
553
+ }
554
+ // Move every opted-in import that the account gate allows to the version the
555
+ // server has now.
556
+ //
557
+ // Called once from serve start (see serve.ts) and nowhere else: that is the one
558
+ // moment where replacing a dependency's files can't pull the rug out from under
559
+ // anything -- no editor is holding unsaved state and no play session is running
560
+ // yet. A page reload happens mid-session, so it deliberately does not do this.
561
+ //
562
+ // Silent when nothing moves. Nothing here is fatal: an import that can't be
563
+ // checked (offline, no source on the server, a bad archive) stays exactly as it
564
+ // is pinned, and the serve starts anyway.
565
+ export async function autoUpdateImports(deckDir) {
566
+ const pins = tryReadCastleJson(deckDir)?.imports ?? {};
567
+ const updated = [];
568
+ for (const [alias, pin] of Object.entries(pins)) {
569
+ // A kit copied from the CLI has no deck to ask; it moves when the CLI does.
570
+ if (!pin?.deckId)
571
+ continue;
572
+ if (!importOptsIntoAutoUpdate(deckDir, alias))
573
+ continue;
574
+ try {
575
+ if (!(await importIsOnTrustedAccount(pin.deckId)))
576
+ continue;
577
+ const source = await api.webDeckSource(pin.deckId);
578
+ if (!source || source.updatedAt === pin.version)
579
+ continue;
580
+ await applyImportUpdate(deckDir, alias, { deckId: pin.deckId, from: pin.version, via: pin.via }, source, (message) => console.log(`auto-update: ${message}`));
581
+ updated.push({ alias, from: pin.version, to: source.updatedAt });
582
+ }
583
+ catch (e) {
584
+ console.warn(`auto-update: left "${alias}" as it is -- ${e instanceof Error ? e.message : String(e)}`);
585
+ }
586
+ }
587
+ if (updated.length > 0) {
588
+ for (const dep of syncImportDependencies(deckDir)) {
589
+ console.log(`auto-update: added dependency ${dep}`);
590
+ }
591
+ }
592
+ return updated;
593
+ }
506
594
  // `castle-web update-import [alias] [dir]`: re-fetch an import (or all of them)
507
595
  // at the version the server has now. Updates happen while editing, never on
508
596
  // someone else's behalf at play time -- the deck that gets published is one that
@@ -552,12 +640,7 @@ export async function updateImport(dir, options = {}) {
552
640
  changed = true;
553
641
  continue;
554
642
  }
555
- await placeImport(deckDir, alias, pin.deckId, source, pin.via);
556
- console.log(`Updated ${alias} (${pin.version} -> ${source.updatedAt})`);
557
- // Its own dependencies may have moved too.
558
- const transitive = await addTransitiveImports(deckDir, alias, importedDeckIds(deckDir));
559
- for (const t of transitive)
560
- console.log(`Also imported ${IMPORTS_DIR}/${t} (needed by ${alias})`);
643
+ await applyImportUpdate(deckDir, alias, { deckId: pin.deckId, from: pin.version, via: pin.via }, source, (message) => console.log(message));
561
644
  changed = true;
562
645
  }
563
646
  if (!changed)
package/dist/init.js CHANGED
@@ -36,7 +36,7 @@ const DEFAULT_KIT = "physics-2d";
36
36
  // Registry version of castle-web-sdk to inject when scaffolding from a
37
37
  // globally-installed castle-web (not from inside the workspace). Bumped
38
38
  // alongside cli/sdk version bumps.
39
- const PUBLISHED_SDK_VERSION = "0.4.11";
39
+ const PUBLISHED_SDK_VERSION = "0.4.12";
40
40
  // The account the first-party kits are (to be) published under, so a kit
41
41
  // imported from the CLI's copy is named the same as one fetched from the server.
42
42
  const KIT_AUTHOR = "castle";
@@ -296,6 +296,13 @@ function makeKitPin(kitConfig, kit) {
296
296
  }
297
297
  return { source: "builtin", kit, version: getCliVersion() };
298
298
  }
299
+ // The kit's editor block minus `fileTypes` -- the layout and file filters a new
300
+ // deck starts from, without the vocabulary it resolves from the kit itself.
301
+ function withoutFileTypes(editor) {
302
+ const rest = { ...(editor ?? {}) };
303
+ delete rest.fileTypes;
304
+ return rest;
305
+ }
299
306
  // Scaffold a deck that IMPORTS its kit instead of copying it. The kit's files
300
307
  // land in `imports/<kit>/` (read-only, like any dependency) and the deck itself
301
308
  // holds only what is genuinely its own: an entry point, its config, and its
@@ -331,8 +338,14 @@ function scaffoldFromKitImport(kit, projectDir) {
331
338
  // The deck's own castle.json: the kit's editor config (panel layout, file
332
339
  // filters) as a starting point -- it is the deck's to edit from here -- plus
333
340
  // the import pin.
341
+ //
342
+ // `fileTypes` is deliberately NOT copied. It is the kit's own vocabulary, and
343
+ // the deck resolves it live from every import's castle.json (see
344
+ // editorConfig.resolveFileTypes) -- so the kit keeps owning it, a kit fix
345
+ // reaches the deck like any other, and a kit imported LATER contributes its
346
+ // types too, which a copy taken at scaffold time never could.
334
347
  writeJsonFile(path.join(projectDir, "castle.json"), {
335
- ...(kitConfig.editor ? { editor: kitConfig.editor } : {}),
348
+ ...(kitConfig.editor ? { editor: withoutFileTypes(kitConfig.editor) } : {}),
336
349
  imports: { [alias]: makeKitPin(kitConfig, kit) },
337
350
  });
338
351
  // The kit's code runs from THIS deck's node_modules, so its dependencies are
package/dist/serve.js CHANGED
@@ -8,6 +8,7 @@ import { createIdeServer, FAVICON_LINK_TAGS } from './ide.js';
8
8
  import { createAgentServer } from './agent.js';
9
9
  import { sceneFilesPlugin, importsAliasPlugin } from './vitePlugins.js';
10
10
  import { installFilesChangedWatcher } from './filesChanged.js';
11
+ import { autoUpdateImports } from './imports.js';
11
12
  import * as config from './config.js';
12
13
  import { graphql as castleGraphql } from './api.js';
13
14
  import { executeCommand } from './castle-host/host.js';
@@ -214,6 +215,21 @@ export async function serve(dir, options = {}) {
214
215
  await serveDetached(projectDir, options);
215
216
  return;
216
217
  }
218
+ // Imports whose decks opted into auto-update move to their latest version
219
+ // now, before anything is served. Serve start is the only safe moment for it:
220
+ // the process is booting, so no editor holds unsaved state and no play
221
+ // session is running against the copy being replaced.
222
+ //
223
+ // Skipped when this deck already has a live serve. That deck IS open -- the
224
+ // dedup above normally returns before here, but an explicit --port/--host
225
+ // asks for a second serve, and a second serve is not a reason to replace
226
+ // files under the first one's session.
227
+ if (!existingServe(projectDir)) {
228
+ // A failure to check must never keep the deck from opening.
229
+ await autoUpdateImports(projectDir).catch((e) => {
230
+ console.warn(`auto-update: skipped -- ${e instanceof Error ? e.message : String(e)}`);
231
+ });
232
+ }
217
233
  // Close stdin when not attached to a TTY so the process can be cleanly
218
234
  // backgrounded with `&` (otherwise stdin reads can block or hold the parent).
219
235
  if (!process.stdin.isTTY) {