@caperjs/core 0.2.4 → 0.4.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 (208) hide show
  1. package/build/assetpack.mjs +90 -0
  2. package/build/index.mjs +3 -1
  3. package/build/internal/discovery.mjs +1 -1
  4. package/build/internal/manifest.mjs +8 -3
  5. package/build/internal/validate.mjs +3 -1
  6. package/build/plugins/assetTypes.mjs +4 -4
  7. package/build/plugins/caperConfig.mjs +12 -5
  8. package/build/plugins/lists.mjs +29 -9
  9. package/build/plugins/pruneFallbacks.mjs +25 -2
  10. package/cli/create.mjs +57 -6
  11. package/cli/create.test.mjs +36 -0
  12. package/cli/update.mjs +1 -1
  13. package/lib/{CaptionsPlugin-WPAcfAkx.js → CaptionsPlugin-cSJj-R9M.js} +5 -5
  14. package/lib/{CaptionsPlugin-WPAcfAkx.js.map → CaptionsPlugin-cSJj-R9M.js.map} +1 -1
  15. package/lib/{DataAdapter-DS-twowe.js → DataAdapter-D2tc6hx9.js} +15 -10
  16. package/lib/DataAdapter-D2tc6hx9.js.map +1 -0
  17. package/lib/{DebugRenderer-D1ryrRYx.js → DebugRenderer-CGUKy6Fy.js} +2 -2
  18. package/lib/{DebugRenderer-D1ryrRYx.js.map → DebugRenderer-CGUKy6Fy.js.map} +1 -1
  19. package/lib/{DevToolsPlugin-DH7zX7HU.js → DevToolsPlugin-B8GX3H9b.js} +2 -2
  20. package/lib/{DevToolsPlugin-DH7zX7HU.js.map → DevToolsPlugin-B8GX3H9b.js.map} +1 -1
  21. package/lib/{GSAPPlugin-BBtRjUqe.js → GSAPPlugin-Br9AjuTD.js} +6 -6
  22. package/lib/{GSAPPlugin-BBtRjUqe.js.map → GSAPPlugin-Br9AjuTD.js.map} +1 -1
  23. package/lib/{LayoutPlugin-Co6vcoV4.js → LayoutPlugin-BX3OP7pw.js} +4 -4
  24. package/lib/{LayoutPlugin-Co6vcoV4.js.map → LayoutPlugin-BX3OP7pw.js.map} +1 -1
  25. package/lib/{SpinePlugin-uN641Hmk.js → SpinePlugin-C-JDdXpf.js} +690 -793
  26. package/lib/SpinePlugin-C-JDdXpf.js.map +1 -0
  27. package/lib/{StatsPlugin-vZQRm6wh.js → StatsPlugin-yJlJtoQ2.js} +7 -7
  28. package/lib/StatsPlugin-yJlJtoQ2.js.map +1 -0
  29. package/lib/{VoiceOverPlugin-KlZdGj1G.js → VoiceOverPlugin-DjSZp4Ko.js} +7 -6
  30. package/lib/VoiceOverPlugin-DjSZp4Ko.js.map +1 -0
  31. package/lib/caper.mjs +159 -1917
  32. package/lib/caper.mjs.map +1 -1
  33. package/lib/const-C3sxeYhl.js +1746 -0
  34. package/lib/const-C3sxeYhl.js.map +1 -0
  35. package/lib/core/Application.d.ts +8 -3
  36. package/lib/core/Application.d.ts.map +1 -1
  37. package/lib/core/Application.test.d.ts +2 -0
  38. package/lib/core/Application.test.d.ts.map +1 -0
  39. package/lib/core/config.d.ts +6 -1
  40. package/lib/core/config.d.ts.map +1 -1
  41. package/lib/core/config.test.d.ts +2 -0
  42. package/lib/core/config.test.d.ts.map +1 -0
  43. package/lib/core/create.d.ts.map +1 -1
  44. package/lib/core/interfaces/IApplication.d.ts +6 -6
  45. package/lib/core/interfaces/IApplication.d.ts.map +1 -1
  46. package/lib/core/interfaces/ICoreSignals.d.ts +2 -1
  47. package/lib/core/interfaces/ICoreSignals.d.ts.map +1 -1
  48. package/lib/display/Camera.test.d.ts +2 -0
  49. package/lib/display/Camera.test.d.ts.map +1 -0
  50. package/lib/display/Entity.d.ts +2 -2
  51. package/lib/display/SceneTransition.test.d.ts +1 -0
  52. package/lib/display/SceneTransition.test.d.ts.map +1 -0
  53. package/lib/display/SpineAnimation.d.ts +3 -3
  54. package/lib/mixins/factory/methods.d.ts.map +1 -1
  55. package/lib/padding-1kHDpGlQ.js +41 -0
  56. package/lib/padding-1kHDpGlQ.js.map +1 -0
  57. package/lib/plugins/AssetsPlugin.d.ts.map +1 -1
  58. package/lib/plugins/AssetsPlugin.test.d.ts +2 -0
  59. package/lib/plugins/AssetsPlugin.test.d.ts.map +1 -0
  60. package/lib/plugins/DataAdapter.d.ts.map +1 -1
  61. package/lib/plugins/DataAdapter.test.d.ts +2 -0
  62. package/lib/plugins/DataAdapter.test.d.ts.map +1 -0
  63. package/lib/plugins/FullScreenPlugin.d.ts +0 -5
  64. package/lib/plugins/FullScreenPlugin.d.ts.map +1 -1
  65. package/lib/plugins/FullScreenPlugin.test.d.ts +2 -0
  66. package/lib/plugins/FullScreenPlugin.test.d.ts.map +1 -0
  67. package/lib/plugins/GSAPPlugin.d.ts +0 -2
  68. package/lib/plugins/GSAPPlugin.d.ts.map +1 -1
  69. package/lib/plugins/GSAPPlugin.test.d.ts +2 -0
  70. package/lib/plugins/GSAPPlugin.test.d.ts.map +1 -0
  71. package/lib/plugins/KeyboardPlugin.d.ts +0 -1
  72. package/lib/plugins/KeyboardPlugin.d.ts.map +1 -1
  73. package/lib/plugins/LookupPlugin.d.ts +6 -1
  74. package/lib/plugins/LookupPlugin.d.ts.map +1 -1
  75. package/lib/plugins/LookupPlugin.test.d.ts +2 -0
  76. package/lib/plugins/LookupPlugin.test.d.ts.map +1 -0
  77. package/lib/plugins/Plugin.d.ts +101 -2
  78. package/lib/plugins/Plugin.d.ts.map +1 -1
  79. package/lib/plugins/PopupManagerPlugin.d.ts.map +1 -1
  80. package/lib/plugins/PopupManagerPlugin.test.d.ts +1 -0
  81. package/lib/plugins/PopupManagerPlugin.test.d.ts.map +1 -0
  82. package/lib/plugins/SceneManagerPlugin.d.ts +1 -1
  83. package/lib/plugins/SceneManagerPlugin.d.ts.map +1 -1
  84. package/lib/plugins/SceneManagerPlugin.test.d.ts +2 -0
  85. package/lib/plugins/SceneManagerPlugin.test.d.ts.map +1 -0
  86. package/lib/plugins/StatsPlugin.d.ts.map +1 -1
  87. package/lib/plugins/TimerPlugin.d.ts.map +1 -1
  88. package/lib/plugins/WebEventsPlugin.d.ts +0 -1
  89. package/lib/plugins/WebEventsPlugin.d.ts.map +1 -1
  90. package/lib/plugins/actions/ActionsPlugin.test.d.ts +2 -0
  91. package/lib/plugins/actions/ActionsPlugin.test.d.ts.map +1 -0
  92. package/lib/plugins/audio/AudioChannel.d.ts.map +1 -1
  93. package/lib/plugins/audio/AudioManagerPlugin.d.ts.map +1 -1
  94. package/lib/plugins/audio/AudioManagerPlugin.test.d.ts +2 -0
  95. package/lib/plugins/audio/AudioManagerPlugin.test.d.ts.map +1 -0
  96. package/lib/plugins/audio/VoiceOverPlugin.d.ts.map +1 -1
  97. package/lib/plugins/breakpoints/BreakpointPlugin.d.ts.map +1 -1
  98. package/lib/plugins/captions/CaptionsPlugin.test.d.ts +2 -0
  99. package/lib/plugins/captions/CaptionsPlugin.test.d.ts.map +1 -0
  100. package/lib/plugins/focus/FocusManagerPlugin.d.ts.map +1 -1
  101. package/lib/plugins/focus/FocusManagerPlugin.test.d.ts +2 -0
  102. package/lib/plugins/focus/FocusManagerPlugin.test.d.ts.map +1 -0
  103. package/lib/plugins/gesture/GesturePlugin.d.ts.map +1 -1
  104. package/lib/plugins/input/AbstractControls.d.ts +47 -3
  105. package/lib/plugins/input/AbstractControls.d.ts.map +1 -1
  106. package/lib/plugins/input/Controls.test.d.ts +2 -0
  107. package/lib/plugins/input/Controls.test.d.ts.map +1 -0
  108. package/lib/plugins/input/InputPlugin.d.ts.map +1 -1
  109. package/lib/plugins/input/controlsCore.d.ts +58 -0
  110. package/lib/plugins/input/controlsCore.d.ts.map +1 -0
  111. package/lib/plugins/input/controlsCore.test.d.ts +2 -0
  112. package/lib/plugins/input/controlsCore.test.d.ts.map +1 -0
  113. package/lib/plugins/input/keyboard/KeyboardControls.d.ts +15 -0
  114. package/lib/plugins/input/keyboard/KeyboardControls.d.ts.map +1 -0
  115. package/lib/plugins/input/keyboard/index.d.ts +1 -1
  116. package/lib/plugins/input/keyboard/index.d.ts.map +1 -1
  117. package/lib/plugins/input/touch/VirtualControls.d.ts +8 -14
  118. package/lib/plugins/input/touch/VirtualControls.d.ts.map +1 -1
  119. package/lib/plugins/input/touch/VirtualControls.test.d.ts +2 -0
  120. package/lib/plugins/input/touch/VirtualControls.test.d.ts.map +1 -0
  121. package/lib/{registries-BLrxALZ0.js → registries-BfV-VF7a.js} +665 -597
  122. package/lib/registries-BfV-VF7a.js.map +1 -0
  123. package/lib/store/Store.d.ts.map +1 -1
  124. package/lib/ui/Button.d.ts.map +1 -1
  125. package/lib/ui/UICanvas.d.ts +10 -1
  126. package/lib/ui/UICanvas.d.ts.map +1 -1
  127. package/lib/ui/UICanvas.test.d.ts +0 -1
  128. package/lib/ui/UICanvas.test.d.ts.map +1 -1
  129. package/lib/utils/color.d.ts.map +1 -1
  130. package/lib/utils/color.test.d.ts +2 -0
  131. package/lib/utils/color.test.d.ts.map +1 -0
  132. package/package.json +1 -1
  133. package/src/core/Application.test.ts +168 -0
  134. package/src/core/Application.ts +37 -27
  135. package/src/core/config.test.ts +79 -0
  136. package/src/core/config.ts +28 -6
  137. package/src/core/create.ts +11 -0
  138. package/src/core/interfaces/IApplication.ts +6 -6
  139. package/src/core/interfaces/ICoreSignals.ts +2 -0
  140. package/src/display/Camera.test.ts +40 -0
  141. package/src/display/Camera.ts +3 -3
  142. package/src/display/Entity.ts +2 -2
  143. package/src/display/SceneTransition.test.ts +58 -0
  144. package/src/display/SceneTransition.ts +1 -1
  145. package/src/display/SpineAnimation.ts +4 -4
  146. package/src/mixins/factory/methods.ts +0 -1
  147. package/src/plugins/AssetsPlugin.test.ts +48 -0
  148. package/src/plugins/AssetsPlugin.ts +3 -1
  149. package/src/plugins/DataAdapter.test.ts +32 -0
  150. package/src/plugins/DataAdapter.ts +5 -1
  151. package/src/plugins/FullScreenPlugin.test.ts +48 -0
  152. package/src/plugins/FullScreenPlugin.ts +9 -18
  153. package/src/plugins/GSAPPlugin.test.ts +31 -0
  154. package/src/plugins/GSAPPlugin.ts +1 -4
  155. package/src/plugins/KeyboardPlugin.ts +3 -10
  156. package/src/plugins/LookupPlugin.test.ts +42 -0
  157. package/src/plugins/LookupPlugin.ts +15 -3
  158. package/src/plugins/Plugin.test.ts +130 -2
  159. package/src/plugins/Plugin.ts +159 -3
  160. package/src/plugins/PopupManagerPlugin.test.ts +53 -0
  161. package/src/plugins/PopupManagerPlugin.ts +5 -3
  162. package/src/plugins/SceneManagerPlugin.test.ts +76 -0
  163. package/src/plugins/SceneManagerPlugin.ts +18 -13
  164. package/src/plugins/StatsPlugin.ts +3 -2
  165. package/src/plugins/TimerPlugin.ts +1 -2
  166. package/src/plugins/WebEventsPlugin.test.ts +32 -0
  167. package/src/plugins/WebEventsPlugin.ts +9 -16
  168. package/src/plugins/actions/ActionsPlugin.test.ts +68 -0
  169. package/src/plugins/actions/ActionsPlugin.ts +5 -5
  170. package/src/plugins/audio/AudioChannel.test.ts +19 -0
  171. package/src/plugins/audio/AudioChannel.ts +8 -1
  172. package/src/plugins/audio/AudioManagerPlugin.test.ts +41 -0
  173. package/src/plugins/audio/AudioManagerPlugin.ts +3 -2
  174. package/src/plugins/audio/VoiceOverPlugin.ts +1 -0
  175. package/src/plugins/breakpoints/BreakpointPlugin.ts +1 -2
  176. package/src/plugins/captions/CaptionsPlugin.test.ts +40 -0
  177. package/src/plugins/captions/CaptionsPlugin.ts +2 -2
  178. package/src/plugins/focus/FocusManagerPlugin.test.ts +71 -0
  179. package/src/plugins/focus/FocusManagerPlugin.ts +7 -0
  180. package/src/plugins/gesture/GesturePlugin.ts +4 -8
  181. package/src/plugins/input/AbstractControls.ts +104 -3
  182. package/src/plugins/input/Controls.test.ts +205 -0
  183. package/src/plugins/input/InputPlugin.ts +6 -14
  184. package/src/plugins/input/controlsCore.test.ts +151 -0
  185. package/src/plugins/input/controlsCore.ts +162 -0
  186. package/src/plugins/input/keyboard/KeyboardControls.ts +84 -0
  187. package/src/plugins/input/keyboard/index.ts +1 -1
  188. package/src/plugins/input/touch/VirtualControls.test.ts +38 -0
  189. package/src/plugins/input/touch/VirtualControls.ts +53 -120
  190. package/src/plugins/spine/SpinePlugin.ts +2 -2
  191. package/src/store/Store.test.ts +15 -0
  192. package/src/store/Store.ts +4 -1
  193. package/src/ui/Button.test.ts +12 -0
  194. package/src/ui/Button.ts +1 -0
  195. package/src/ui/UICanvas.test.ts +109 -6
  196. package/src/ui/UICanvas.ts +41 -3
  197. package/src/utils/color.test.ts +16 -0
  198. package/src/utils/color.ts +2 -1
  199. package/src/utils/platform.ts +1 -1
  200. package/src/version.ts +1 -1
  201. package/lib/DataAdapter-DS-twowe.js.map +0 -1
  202. package/lib/SpinePlugin-uN641Hmk.js.map +0 -1
  203. package/lib/StatsPlugin-vZQRm6wh.js.map +0 -1
  204. package/lib/VoiceOverPlugin-KlZdGj1G.js.map +0 -1
  205. package/lib/plugins/input/keyboard/KeyboardContols.d.ts +0 -28
  206. package/lib/plugins/input/keyboard/KeyboardContols.d.ts.map +0 -1
  207. package/lib/registries-BLrxALZ0.js.map +0 -1
  208. package/src/plugins/input/keyboard/KeyboardContols.ts +0 -182
@@ -33,6 +33,92 @@ function fontWeights() {
33
33
  };
34
34
  }
35
35
 
36
+ /**
37
+ * Pipes an exempted asset must not enter. AssetPack's `mergePipeOptions` reads
38
+ * `asset.settings[pipe.name]` and treats `false` as "skip this pipe for this
39
+ * asset" — the same per-asset switch the `assetSettings` config exposes, only
40
+ * set programmatically here because the page names are inside the font file,
41
+ * not in anything an app author could glob.
42
+ *
43
+ * `cache-buster` is the one that actually breaks production, and it is also the
44
+ * one no tag can turn off (`cacheBuster().test` is `!asset.isFolder`, full
45
+ * stop), which is why this uses settings rather than the `nomip`/`nc` tags.
46
+ */
47
+ const BITMAP_FONT_SKIPPED_PIPES = ['mipmap', 'compress', 'cache-buster'];
48
+
49
+ /** Both text `.fnt` and XML bitmap fonts name their pages as `file="…png"`. */
50
+ const fontPagePattern = /file="([^"]+\.png)"/g;
51
+
52
+ const toPosix = (value) => value.replace(/\\/g, '/');
53
+ const dirOf = (value) => toPosix(value).replace(/\/[^/]*$/, '');
54
+ const baseOf = (value) => toPosix(value).replace(/^.*\//, '');
55
+
56
+ /**
57
+ * AssetPack pipe that keeps pre-rasterized bitmap fonts out of the image
58
+ * pipeline entirely — no resolution variants, no webp conversion, no
59
+ * cache-bust rename — for the descriptor and every texture page it names.
60
+ *
61
+ * A `.fnt` hardcodes `page id=0 file="Syncopate.png"` and PixiJS fetches that
62
+ * exact name relative to the descriptor's own URL; the manifest is never
63
+ * consulted for it. So the moment assetpack hashes the page to
64
+ * `Syncopate-MPx5Vw.webp`, the loader asks for a file that no longer exists,
65
+ * gets vite's SPA fallback HTML back, and the app fails to boot. Resizing is
66
+ * just as fatal in the other direction: the glyph rects in the descriptor are
67
+ * exact pixel coordinates into that one image.
68
+ *
69
+ * Detection is automatic — no folder tagging asked of app authors. `start` runs
70
+ * against the whole asset tree before any transform, which is the only point
71
+ * where a font's descriptor can be read in time to exempt its siblings.
72
+ */
73
+ export function bitmapFontPassthrough() {
74
+ return {
75
+ folder: false,
76
+ name: 'bitmap-font-passthrough',
77
+ defaultOptions: null,
78
+ async start(rootAsset) {
79
+ const files = new Map();
80
+ const descriptors = [];
81
+
82
+ const walk = (asset) => {
83
+ if (!asset.isFolder) {
84
+ files.set(toPosix(asset.path), asset);
85
+ if (/\.(fnt|xml)$/i.test(asset.path)) descriptors.push(asset);
86
+ }
87
+ for (const child of asset.children ?? []) walk(child);
88
+ };
89
+ walk(rootAsset);
90
+
91
+ const exempt = (asset) => {
92
+ asset.settings = { ...asset.settings };
93
+ for (const pipe of BITMAP_FONT_SKIPPED_PIPES) asset.settings[pipe] = false;
94
+ };
95
+
96
+ for (const descriptor of descriptors) {
97
+ let text;
98
+ try {
99
+ // A deleted asset is still on the tree, and a binary .fnt decodes to
100
+ // noise — either way the page match below simply finds nothing.
101
+ text = descriptor.buffer.toString('utf8');
102
+ } catch {
103
+ continue;
104
+ }
105
+
106
+ const pages = [...text.matchAll(fontPagePattern)].map((match) => match[1]);
107
+ // No page reference means it is not a bitmap font — an ordinary .xml
108
+ // data file must keep its cache-bust hash.
109
+ if (!pages.length) continue;
110
+
111
+ exempt(descriptor);
112
+ const directory = dirOf(descriptor.path);
113
+ for (const page of pages) {
114
+ const sibling = files.get(`${directory}/${baseOf(page)}`);
115
+ if (sibling) exempt(sibling);
116
+ }
117
+ }
118
+ },
119
+ };
120
+ }
121
+
36
122
  const defaultManifestUrl = 'assets.json';
37
123
 
38
124
  const defaultPixiPipesConfig = {
@@ -185,6 +271,10 @@ export const assetpackConfig = (manifestUrl = defaultManifestUrl, pixiPipesConfi
185
271
  const wfIdx = pipes.findIndex((p) => p.name === 'webfont');
186
272
  pipes.splice(wfIdx >= 0 ? wfIdx : 0, 0, fontWeights());
187
273
 
274
+ // Position is irrelevant — this one only has a `start` hook, and AssetPack
275
+ // runs every pipe's `start` before the first asset is transformed.
276
+ pipes.unshift(bitmapFontPassthrough());
277
+
188
278
  return {
189
279
  manifestUrl,
190
280
  entry: './assets',
package/build/index.mjs CHANGED
@@ -76,7 +76,9 @@ function caperPluginList({ assets = {}, pwa } = {}) {
76
76
  entityListPlugin(),
77
77
  uiListPlugin(),
78
78
  ...assetPlugins,
79
- caperConfigPlugin(),
79
+ // The manifest name reaches build-time validation this way — it is the only
80
+ // place that knows a project overrode it.
81
+ caperConfigPlugin(true, manifestUrl),
80
82
  caperDevHelperPlugin(),
81
83
  ...(pwa ? caperPwaPlugins(pwa) : []),
82
84
  ];
@@ -37,7 +37,7 @@ export async function findTypeScriptFiles(dir) {
37
37
 
38
38
  if (entry.isDirectory()) {
39
39
  await scan(fullPath);
40
- } else if (entry.isFile() && /\.ts?$/.test(entry.name)) {
40
+ } else if (entry.isFile() && /\.tsx?$/.test(entry.name)) {
41
41
  files.push(fullPath);
42
42
  }
43
43
  }
@@ -8,10 +8,15 @@
8
8
  */
9
9
  import fs from 'node:fs';
10
10
  import path from 'node:path';
11
- import { cwd, logger } from './util.mjs';
11
+ import { logger } from './util.mjs';
12
12
 
13
- export function loadManifestBundleNames(manifestUrl = 'assets.json') {
14
- const manifestPath = path.join(process.cwd(), 'public', 'assets', manifestUrl);
13
+ /**
14
+ * `root` is vite's resolved project root — not `process.cwd()`, which is
15
+ * whatever directory the build was launched from. See `internal/discovery.mjs`
16
+ * for the same contract.
17
+ */
18
+ export function loadManifestBundleNames(root, manifestUrl = 'assets.json') {
19
+ const manifestPath = path.join(root, 'public', 'assets', manifestUrl);
15
20
  if (!fs.existsSync(manifestPath)) return null;
16
21
  try {
17
22
  const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
@@ -12,6 +12,8 @@ import { logger } from './util.mjs';
12
12
 
13
13
  export function runBuildTimeValidation({
14
14
  server,
15
+ root,
16
+ manifestUrl,
15
17
  configPath,
16
18
  configObject,
17
19
  scenes,
@@ -21,7 +23,7 @@ export function runBuildTimeValidation({
21
23
  breakpointsName,
22
24
  }) {
23
25
  const warnings = [];
24
- const bundleNames = loadManifestBundleNames();
26
+ const bundleNames = loadManifestBundleNames(root, manifestUrl);
25
27
 
26
28
  // 1. Scene bundle references
27
29
  if (bundleNames && bundleNames.size > 0) {
@@ -9,7 +9,7 @@
9
9
  import fs from 'node:fs';
10
10
  import path from 'node:path';
11
11
  import { loadManifestBundleNames } from '../internal/manifest.mjs';
12
- import { ASSET_DTS_FILE_NAME, debounce, delay, logger } from '../internal/util.mjs';
12
+ import { ASSET_DTS_FILE_NAME, debounce, delay, env, logger } from '../internal/util.mjs';
13
13
 
14
14
  export { loadManifestBundleNames };
15
15
 
@@ -399,12 +399,12 @@ export function assetTypesPlugin(manifestUrl = 'assets.json') {
399
399
 
400
400
  return {
401
401
  name: 'vite-plugin-asset-types',
402
- config(config) {
403
- ispPwaEnabled = config.plugins.some((p) => p.name === 'vite-plugin-pwa');
404
- },
405
402
  configResolved(config) {
406
403
  publicDir = config.publicDir;
407
404
  root = config.root;
405
+ // Resolved, so the list is flat: in the `config` hook `caper()` is still
406
+ // one nested array and vite-plugin-pwa is never found at the top level.
407
+ ispPwaEnabled = config.plugins.some((p) => p.name === 'vite-plugin-pwa');
408
408
  },
409
409
  async buildStart() {
410
410
  // a short delay to allow assetpack to generate the manifest
@@ -93,7 +93,7 @@ async function validateCaperConfig(server, root) {
93
93
 
94
94
  // write a debounce function
95
95
 
96
- export function caperConfigPlugin(isProject = true) {
96
+ export function caperConfigPlugin(isProject = true, manifestUrl = 'assets.json') {
97
97
  // vite's resolved project root. Everything below resolves against this rather
98
98
  // than process.cwd(), so `vite --root elsewhere` and monorepo invocations from
99
99
  // a parent directory find the right caper.config.ts and src/ tree.
@@ -228,6 +228,8 @@ export function caperConfigPlugin(isProject = true) {
228
228
  // defaultScene pointing at a non-existent scene, duplicate IDs).
229
229
  runBuildTimeValidation({
230
230
  server,
231
+ root,
232
+ manifestUrl,
231
233
  configPath,
232
234
  configObject,
233
235
  scenes,
@@ -444,6 +446,7 @@ declare module '@caperjs/core' {
444
446
  const pluginsDir = path.resolve(root, 'src/plugins');
445
447
  const popupsDir = path.resolve(root, 'src/popups');
446
448
  const entitiesDir = path.resolve(root, 'src/entities');
449
+ const uisDir = path.resolve(root, 'src/ui');
447
450
  const localesDir = path.resolve(root, 'src/locales');
448
451
 
449
452
  const handleFileChange = async (file) => {
@@ -451,10 +454,11 @@ declare module '@caperjs/core' {
451
454
  const isPlugin = file.startsWith(pluginsDir);
452
455
  const isPopup = file.startsWith(popupsDir);
453
456
  const isEntity = file.startsWith(entitiesDir);
457
+ const isUI = file.startsWith(uisDir);
454
458
  const isLocale = file.startsWith(localesDir);
455
459
  const isConfig = file === configPath;
456
460
 
457
- if (!isScene && !isPlugin && !isPopup && !isEntity && !isLocale && !isConfig) return;
461
+ if (!isScene && !isPlugin && !isPopup && !isEntity && !isUI && !isLocale && !isConfig) return;
458
462
 
459
463
  const msg = isScene
460
464
  ? 'Scene file changed'
@@ -464,9 +468,11 @@ declare module '@caperjs/core' {
464
468
  ? 'Popup file changed'
465
469
  : isEntity
466
470
  ? 'Entity file changed'
467
- : isLocale
468
- ? 'Locale file changed'
469
- : 'Config file changed';
471
+ : isUI
472
+ ? 'UI file changed'
473
+ : isLocale
474
+ ? 'Locale file changed'
475
+ : 'Config file changed';
470
476
  await build(`${msg}, regenerating types...`, server);
471
477
  };
472
478
 
@@ -475,6 +481,7 @@ declare module '@caperjs/core' {
475
481
  server.watcher.add(pluginsDir);
476
482
  server.watcher.add(popupsDir);
477
483
  server.watcher.add(entitiesDir);
484
+ server.watcher.add(uisDir);
478
485
  server.watcher.add(localesDir);
479
486
 
480
487
  server.watcher.on('change', handleFileChange);
@@ -9,14 +9,11 @@
9
9
  import path from 'node:path';
10
10
  import {
11
11
  discoverEntities,
12
- discoverLocaleKeys,
13
12
  discoverPlugins,
14
13
  discoverPopups,
15
14
  discoverScenes,
16
15
  discoverUIs,
17
16
  } from '../internal/discovery.mjs';
18
- import { logger } from '../internal/util.mjs';
19
- import { loadManifestBundleNames } from '../internal/manifest.mjs';
20
17
 
21
18
  export function createClassListPlugin({ virtualModuleId, discoverFn, exportName, pluginName }) {
22
19
  // vite's resolved project root, captured for the discovery pass. See
@@ -32,7 +29,15 @@ export function createClassListPlugin({ virtualModuleId, discoverFn, exportName,
32
29
 
33
30
  const generate = (items) => {
34
31
  const staticItems = items.filter((i) => i.module && !i.module.isFunction);
35
- const imports = staticItems.map((i) => `import ${extractClassName(i)} from '${i.module}';`);
32
+ // Basenames alone collide when two files share a name in different
33
+ // directories (e.g. src/entities/a/Foo.ts and src/entities/b/Foo.ts),
34
+ // producing two `import Foo from '...'` lines — a duplicate-binding
35
+ // syntax error. Suffix with the item's index in the static-import list
36
+ // to keep identifiers unique while staying stable and readable, and
37
+ // look them up from the same map when emitting the object literal so
38
+ // the import and its `module:` reference always agree.
39
+ const importNames = new Map(staticItems.map((i, index) => [i, `${extractClassName(i)}_${index}`]));
40
+ const imports = staticItems.map((i) => `import ${importNames.get(i)} from '${i.module}';`);
36
41
 
37
42
  return `
38
43
  ${imports.join('\n')}
@@ -41,7 +46,7 @@ export function createClassListPlugin({ virtualModuleId, discoverFn, exportName,
41
46
  .map((item) => {
42
47
  const moduleExpr =
43
48
  item.module && !item.module.isFunction
44
- ? extractClassName(item)
49
+ ? importNames.get(item)
45
50
  : (item.module?.toString?.() ?? `() => import('${item.importPath}')`);
46
51
  return `{
47
52
  id: '${item.id}',
@@ -157,7 +162,15 @@ export function pluginListPlugin(isProject = true) {
157
162
  // npm static imports would collide on class-name derivation from the pkg
158
163
  // path, so only local plugins support `dynamic = false`. npm packages are
159
164
  // always dynamic.
160
- const imports = staticPlugins.map((p) => `import ${extractClassName(p)} from '${p.module}';`);
165
+ //
166
+ // Basenames alone collide when two files share a name in different
167
+ // directories, producing two `import Foo from '...'` lines — a
168
+ // duplicate-binding syntax error. Suffix with the item's index in the
169
+ // static-import list to keep identifiers unique while staying stable
170
+ // and readable, and look them up from the same map when emitting the
171
+ // object literal so the import and its `module:` reference always agree.
172
+ const importNames = new Map(staticPlugins.map((p, index) => [p, `${extractClassName(p)}_${index}`]));
173
+ const imports = staticPlugins.map((p) => `import ${importNames.get(p)} from '${p.module}';`);
161
174
 
162
175
  return `
163
176
  ${imports.join('\n')}
@@ -166,7 +179,7 @@ export function pluginListPlugin(isProject = true) {
166
179
  .map((plugin) => {
167
180
  const moduleExpr =
168
181
  plugin.module && !plugin.module.isFunction && plugin.isLocal
169
- ? extractClassName(plugin)
182
+ ? importNames.get(plugin)
170
183
  : (plugin.module?.toString?.() ?? `() => import('${plugin.importPath}')`);
171
184
  return `{
172
185
  name: '${plugin.name}',
@@ -223,7 +236,14 @@ export function sceneListPlugin(isProject = true) {
223
236
  // extract non function scenes from the list
224
237
  const nonFunctionScenes = scenes.filter((scene) => !scene.module.isFunction);
225
238
 
226
- const imports = nonFunctionScenes.map((scene) => `import ${extractClassName(scene)} from '${scene.module}';`);
239
+ // Basenames alone collide when two files share a name in different
240
+ // directories, producing two `import Foo from '...'` lines — a
241
+ // duplicate-binding syntax error. Suffix with the item's index in the
242
+ // static-import list to keep identifiers unique while staying stable
243
+ // and readable, and look them up from the same map when emitting the
244
+ // object literal so the import and its `module:` reference always agree.
245
+ const importNames = new Map(nonFunctionScenes.map((scene, index) => [scene, `${extractClassName(scene)}_${index}`]));
246
+ const imports = nonFunctionScenes.map((scene) => `import ${importNames.get(scene)} from '${scene.module}';`);
227
247
 
228
248
  const result = `
229
249
  ${imports.join('\n')}
@@ -233,7 +253,7 @@ export function sceneListPlugin(isProject = true) {
233
253
  (scene) => `{
234
254
  id: '${scene.id}',
235
255
  active: ${scene.active},
236
- module: ${scene.module.isFunction ? scene.module.toString() : extractClassName(scene)},
256
+ module: ${scene.module.isFunction ? scene.module.toString() : importNames.get(scene)},
237
257
  debugLabel: ${JSON.stringify(scene.debugLabel)},
238
258
  debugGroup: ${JSON.stringify(scene.debugGroup)},
239
259
  debugOrder: ${scene.debugOrder},
@@ -28,6 +28,17 @@ import path from 'node:path';
28
28
  /** A png image, a texture-packer descriptor, or a spine atlas for one. */
29
29
  const isPngSrc = (src) => /\.png(\.json|\.atlas)?$/.test(src);
30
30
 
31
+ /** A bitmap font descriptor — text `.fnt` or XML `.fnt`/`.xml`, both quote `file="…"`. */
32
+ const isFontSrc = (src) => /\.(fnt|xml)$/.test(src);
33
+
34
+ /**
35
+ * PixiJS bitmap fonts hardcode their texture page filename inside the
36
+ * descriptor and fetch it relative to the descriptor's own URL — the asset
37
+ * manifest is never consulted for that request, so rewriting `src` there
38
+ * doesn't stop the loader asking for the exact name printed here.
39
+ */
40
+ const fontPageNames = (text) => new Set([...text.matchAll(/file="([^"]+\.png)"/g)].map((match) => path.basename(match[1])));
41
+
31
42
  /**
32
43
  * Works out what to delete without touching the filesystem, so the decision is
33
44
  * testable on its own.
@@ -77,10 +88,22 @@ export function planPngPrune(manifest, readText) {
77
88
  doomed.push(rel);
78
89
  };
79
90
 
91
+ const protectedPngs = new Set();
92
+ for (const bundle of manifest.bundles ?? []) {
93
+ for (const asset of bundle.assets ?? []) {
94
+ for (const src of asset.src ?? []) {
95
+ if (!isFontSrc(src)) continue;
96
+ const text = readText(src);
97
+ if (text) for (const name of fontPageNames(text)) protectedPngs.add(name);
98
+ }
99
+ }
100
+ }
101
+
80
102
  for (const bundle of manifest.bundles ?? []) {
81
103
  for (const asset of bundle.assets ?? []) {
82
- const png = (asset.src ?? []).filter(isPngSrc);
83
- const kept = (asset.src ?? []).filter((src) => !isPngSrc(src));
104
+ const isProtected = (src) => isPngSrc(src) && protectedPngs.has(path.basename(src));
105
+ const png = (asset.src ?? []).filter((src) => isPngSrc(src) && !isProtected(src));
106
+ const kept = (asset.src ?? []).filter((src) => !isPngSrc(src) || isProtected(src));
84
107
  if (!png.length || !kept.length) continue;
85
108
 
86
109
  for (const rel of png) {
package/cli/create.mjs CHANGED
@@ -88,6 +88,60 @@ function getFiles(dirPath) {
88
88
  return files;
89
89
  }
90
90
 
91
+ /**
92
+ * Injects wizard-selected plugin config entries into a caper.config.ts file's
93
+ * `plugins:` array, whether that array is currently empty or already has entries.
94
+ *
95
+ * @param {string} configContents
96
+ * @param {string[]} pluginConfigs
97
+ * @returns {string}
98
+ */
99
+ export function injectPluginsIntoConfig(configContents, pluginConfigs) {
100
+ // Empty array: replace it wholesale with the new entries.
101
+ if (/plugins:\s*\[\s*\]/.test(configContents)) {
102
+ return configContents.replace(
103
+ /plugins:\s*\[\s*\]/,
104
+ `plugins: [\n ${pluginConfigs.join(',\n ')}\n ]`,
105
+ );
106
+ }
107
+
108
+ // Non-empty array: find the `plugins:` array's matching brackets (which may
109
+ // contain nested arrays, e.g. `['name', {...}]` entries) and append the new
110
+ // entries alongside the existing ones.
111
+ const keyMatch = configContents.match(/plugins:\s*\[/);
112
+ if (!keyMatch) {
113
+ return configContents;
114
+ }
115
+
116
+ const openBracketIndex = keyMatch.index + keyMatch[0].length - 1;
117
+ let depth = 0;
118
+ let closeBracketIndex = -1;
119
+ for (let i = openBracketIndex; i < configContents.length; i++) {
120
+ if (configContents[i] === '[') depth++;
121
+ else if (configContents[i] === ']') {
122
+ depth--;
123
+ if (depth === 0) {
124
+ closeBracketIndex = i;
125
+ break;
126
+ }
127
+ }
128
+ }
129
+ if (closeBracketIndex === -1) {
130
+ return configContents;
131
+ }
132
+
133
+ const existingEntries = configContents.slice(openBracketIndex + 1, closeBracketIndex).trim();
134
+ const allEntries = existingEntries
135
+ ? `${existingEntries},\n ${pluginConfigs.join(',\n ')}`
136
+ : pluginConfigs.join(',\n ');
137
+
138
+ return (
139
+ configContents.slice(0, openBracketIndex) +
140
+ `[\n ${allEntries}\n ]` +
141
+ configContents.slice(closeBracketIndex + 1)
142
+ );
143
+ }
144
+
91
145
  /**
92
146
  * @param {string} template
93
147
  * @param {string} applicationNameForPkg
@@ -180,11 +234,8 @@ function write_template_files(cwd, template, applicationNameForPkg, applicationN
180
234
  return `['${shortName}', { autoLoad: false }]`;
181
235
  });
182
236
 
183
- // Replace empty plugins array with configured plugins
184
- configContents = configContents.replace(
185
- /plugins:\s*\[\s*\]/,
186
- `plugins: [\n ${pluginConfigs.join(',\n ')}\n ]`,
187
- );
237
+ // Inject the configured plugins into the plugins array
238
+ configContents = injectPluginsIntoConfig(configContents, pluginConfigs);
188
239
  }
189
240
 
190
241
  fs.writeFileSync(configPath, configContents.replace(/__APPLICATION_NAME__/g, applicationName), 'utf-8');
@@ -304,7 +355,7 @@ export async function create(cwd = '.', packageManagerOverride) {
304
355
  });
305
356
 
306
357
  if (!appName) {
307
- appName = name;
358
+ appName = defaultName;
308
359
  }
309
360
 
310
361
  // derive an application name from the project name by camel casing it
@@ -0,0 +1,36 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { injectPluginsIntoConfig } from './create.mjs';
3
+
4
+ describe('injectPluginsIntoConfig', () => {
5
+ it('inserts plugins into an empty array', () => {
6
+ const configContents = `export default {\n plugins: [],\n};\n`;
7
+ const pluginConfigs = [`['rollbar', { autoLoad: false }]`];
8
+
9
+ const result = injectPluginsIntoConfig(configContents, pluginConfigs);
10
+
11
+ expect(result).toContain(`plugins: [\n ['rollbar', { autoLoad: false }]\n ]`);
12
+ });
13
+
14
+ it('appends plugins alongside an existing non-empty array', () => {
15
+ const configContents = `export default {\n plugins: [['existing', { autoLoad: false }]],\n};\n`;
16
+ const pluginConfigs = [`['rollbar', { autoLoad: false }]`];
17
+
18
+ const result = injectPluginsIntoConfig(configContents, pluginConfigs);
19
+
20
+ expect(result).toContain(`['existing', { autoLoad: false }]`);
21
+ expect(result).toContain(`['rollbar', { autoLoad: false }]`);
22
+ // both entries should end up inside a single well-formed array
23
+ expect(result).toMatch(/plugins: \[[\s\S]*existing[\s\S]*rollbar[\s\S]*\]/);
24
+ });
25
+
26
+ it('appends plugins alongside an existing multiline array', () => {
27
+ const configContents = `export default {\n plugins: [\n ['existing', { autoLoad: false }],\n ],\n};\n`;
28
+ const pluginConfigs = [`['rollbar', { autoLoad: false }]`, `['rive', { autoLoad: false }]`];
29
+
30
+ const result = injectPluginsIntoConfig(configContents, pluginConfigs);
31
+
32
+ expect(result).toContain(`['existing', { autoLoad: false }]`);
33
+ expect(result).toContain(`['rollbar', { autoLoad: false }]`);
34
+ expect(result).toContain(`['rive', { autoLoad: false }]`);
35
+ });
36
+ });
package/cli/update.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
  import {execSync} from 'node:child_process';
3
3
  import {package_manager} from './utils.mjs';
4
4
 
5
- const install_path = 'github:anthonysapp/caper';
5
+ const install_path = '@caperjs/core@latest';
6
6
 
7
7
  export async function update() {
8
8
  return execSync(`${package_manager} install ${install_path}`);
@@ -1,5 +1,5 @@
1
- import { Dt as e, H as t, bt as n, f as r, jt as i } from "./registries-BLrxALZ0.js";
2
- import { ensurePadding as a } from "./caper.mjs";
1
+ import { Dt as e, H as t, bt as n, f as r, jt as i } from "./registries-BfV-VF7a.js";
2
+ import { t as a } from "./padding-1kHDpGlQ.js";
3
3
  import { Assets as o, Container as s, Texture as c } from "pixi.js";
4
4
  import { gsap as l } from "gsap";
5
5
  //#region src/plugins/captions/CaptionsRenderer.ts
@@ -194,13 +194,13 @@ var m = class extends t {
194
194
  postInitialize() {
195
195
  this.app.i18n.onLocaleChanged.connect(this._handleLocaleChange), this.app.voiceover.onVoiceOverStart.connect(this._handleVoiceOverStart), this.app.voiceover.onVoiceOverPaused.connect(this._handleVoiceOverPaused), this.app.voiceover.onVoiceOverResumed.connect(this._handleVoiceOverResumed), this.app.voiceover.onVoiceOverComplete.connect(this._handleVoiceoverComplete), this.app.voiceover.onVoiceOverStopped.connect(this._handleVoiceoverStopped), this.app.stage.addChild(this.view), this._options.maxWidth = p(this._originalOptions.maxWidth, this.app.size.width);
196
196
  let e = this.options.renderer;
197
- this.renderer = this.view.addChild(new e(this)), this.app.ticker.add(this.update), this.app.scenes.onSceneChangeStart.connect(this.stopAllCaptions);
197
+ this.renderer = this.view.addChild(new e(this)), this.addTickerCallback(this.update), this.app.scenes.onSceneChangeStart.connect(this.stopAllCaptions);
198
198
  }
199
199
  async setLocale(e) {
200
200
  return this._locale === e ? this._locale : (this._activeCaptionId && this.stopCaption(this._activeCaptionId), this._activeCaptionId = void 0, await this._loadAndSetLocale(e), this._locale = e, this._locale);
201
201
  }
202
202
  async loadLocale(t) {
203
- if (this._locale === "localeId") return;
203
+ if (this._dicts[t]) return;
204
204
  let r = this._options.files.find((e) => t === e.id);
205
205
  if (!r) {
206
206
  e.error(`i18n:: Could not find locale file for ${t}`);
@@ -275,4 +275,4 @@ var m = class extends t {
275
275
  //#endregion
276
276
  export { m as CaptionsPlugin };
277
277
 
278
- //# sourceMappingURL=CaptionsPlugin-WPAcfAkx.js.map
278
+ //# sourceMappingURL=CaptionsPlugin-cSJj-R9M.js.map