ember-vite-hmr 2.3.0 → 2.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.
package/dist/lib/hmr.js CHANGED
@@ -13,12 +13,18 @@ import { parseSync } from '@babel/core';
13
13
  import traverseModule from '@babel/traverse';
14
14
  const traverse = traverseModule.default || traverseModule;
15
15
  import { readFile } from 'fs/promises';
16
+ import { hmrImportMetadataCache } from './babel-plugin.js';
16
17
  const __filename = fileURLToPath(import.meta.url);
17
18
  const __dirname = path.dirname(__filename);
18
19
  // Above this many distinct named blocks, fall back to forwarding all of them
19
20
  // unconditionally rather than generating an exponential number of branches
20
21
  // (see generateInvocations below for why branching is needed at all).
21
22
  const MAX_COMBINATORIAL_YIELDS = 8;
23
+ // Safety cap on how many `extends` links resolveYieldSource will walk while
24
+ // looking for an inherited template (see its own doc comment) - real
25
+ // component hierarchies never get remotely this deep, this only guards
26
+ // against a pathological/cyclic chain.
27
+ const MAX_EXTENDS_DEPTH = 20;
22
28
  function blockTag(y) {
23
29
  return `<:${y} as |a b c d e f g h i j k l|>{{yield a b c d e f g h i j k l to='${y}'}}</:${y}>`;
24
30
  }
@@ -78,6 +84,7 @@ const hotCallbacks = new Set();
78
84
 
79
85
  if (import.meta.hot) {
80
86
  import.meta.hot.accept('${imp}', (module) => {
87
+ import.meta.hot.data.latestModule = module;
81
88
  for (const callback of hotCallbacks) {
82
89
  callback(module);
83
90
  }
@@ -106,7 +113,11 @@ export default class HotComponent extends Component {
106
113
  named[name] = capturedArgs?.named[name] ?? createComputeRef(() => args[name]);
107
114
  }
108
115
  const CurriedComponent = 0;
109
- this.curried = curry(CurriedComponent, TargetComponent, owner, { positional, named});
116
+ // After an accepted update this module is not re-evaluated, so the static
117
+ // TargetComponent binding still points at the pre-update module. Instances
118
+ // created after the update must curry the latest accepted class instead.
119
+ const Target = import.meta.hot?.data.latestModule?.default ?? TargetComponent;
120
+ this.curried = curry(CurriedComponent, Target, owner, { positional, named});
110
121
  if (import.meta.hot) {
111
122
  const callback = (module) => {
112
123
  this.curried = curry(CurriedComponent, module.default, owner, { positional, named});
@@ -188,6 +199,188 @@ function normalizePath(inputPath) {
188
199
  // Always convert backslashes to forward slashes
189
200
  return inputPath.replace(/\\/g, '/');
190
201
  }
202
+ // The compiled backing class embeds an import specifier as a browser-facing
203
+ // URL (i.e. prefixed with the configured vite `base`), since that's what
204
+ // gets sent to the client. But `server.transformRequest` is an internal API
205
+ // keyed by root-relative ids, so the base has to be stripped back off before
206
+ // reusing that specifier, or a lookup 404s under a non-root base.
207
+ function stripBase(specifier, base) {
208
+ if (base !== '/' && specifier.startsWith(base)) {
209
+ return `/${specifier.slice(base.length)}`;
210
+ }
211
+ return specifier;
212
+ }
213
+ // A component's compiled module only calls `setComponentTemplate` on itself
214
+ // when it owns a template directly (an inline `<template>`); it's absent
215
+ // both for a classic component whose template lives in a separately
216
+ // resolved colocated `.hbs` (see the `.hbs` import check this feeds into)
217
+ // and for a component with no template of its own at all, which inherits
218
+ // whatever's registered on its nearest ancestor class instead (Glimmer's
219
+ // `getComponentTemplate`/`setComponentTemplate`, vendored in ember-source's
220
+ // `@glimmer/manager`, resolve a component's template by walking
221
+ // `Object.getPrototypeOf` up the prototype chain - the same mechanism
222
+ // `getInternalComponentManager` uses, see the `shadowManager` comment above).
223
+ function findSuperclassImportSource(content) {
224
+ let result;
225
+ try {
226
+ result = parseSync(content, {
227
+ filename: 'hmr-extends-check.js',
228
+ ast: true,
229
+ code: false,
230
+ configFile: false,
231
+ babelrc: false,
232
+ plugins: [
233
+ ['@babel/plugin-syntax-typescript', { isTSX: true }],
234
+ ['@babel/plugin-proposal-decorators', { version: '2022-03' }],
235
+ ],
236
+ });
237
+ }
238
+ catch (_a) {
239
+ return null;
240
+ }
241
+ if (!result) {
242
+ return null;
243
+ }
244
+ // Prefer the default-exported class specifically (that's always the
245
+ // component itself) over "the first class with a superclass in the file",
246
+ // since a file can declare other, unrelated classes above it (a local
247
+ // helper class, etc.) that would otherwise be matched instead.
248
+ let superName = null;
249
+ traverse(result, {
250
+ ExportDefaultDeclaration(path) {
251
+ const declaration = path.node.declaration;
252
+ let classNode = declaration;
253
+ if (declaration.type === 'Identifier' && declaration.name) {
254
+ const binding = path.scope.getBinding(declaration.name);
255
+ if ((binding === null || binding === void 0 ? void 0 : binding.path.isClassDeclaration()) ||
256
+ (binding === null || binding === void 0 ? void 0 : binding.path.isClassExpression())) {
257
+ classNode = binding.path.node;
258
+ }
259
+ }
260
+ const superClass = classNode.superClass;
261
+ if ((superClass === null || superClass === void 0 ? void 0 : superClass.type) === 'Identifier' && superClass.name) {
262
+ superName = superClass.name;
263
+ }
264
+ },
265
+ });
266
+ if (!superName) {
267
+ // Fall back to the first class with a superclass anywhere in the file
268
+ // (e.g. a non-default-exported component, or an export form the check
269
+ // above doesn't specifically handle).
270
+ traverse(result, {
271
+ 'ClassDeclaration|ClassExpression'(path) {
272
+ if (superName) {
273
+ return;
274
+ }
275
+ const superClass = path.node.superClass;
276
+ if ((superClass === null || superClass === void 0 ? void 0 : superClass.type) === 'Identifier' && superClass.name) {
277
+ superName = superClass.name;
278
+ }
279
+ },
280
+ });
281
+ }
282
+ if (!superName) {
283
+ return null;
284
+ }
285
+ let source = null;
286
+ traverse(result, {
287
+ ImportDeclaration(path) {
288
+ if (source) {
289
+ return;
290
+ }
291
+ if (path.node.specifiers.some((s) => s.local.name === superName)) {
292
+ source = path.node.source.value;
293
+ }
294
+ },
295
+ });
296
+ return source;
297
+ }
298
+ // If `content` (already run through `server.transformRequest`) registers a
299
+ // template on itself - either inline, or via a colocated `.hbs` import -
300
+ // returns that template's own source. Returns null when this file has no
301
+ // template of its own to scan, meaning any named blocks it uses have to be
302
+ // found on an ancestor class instead (see resolveYieldSource).
303
+ function resolveOwnTemplateSource(server, base, filename, content) {
304
+ return __awaiter(this, void 0, void 0, function* () {
305
+ var _a;
306
+ // For a classic component with a separately resolved template (a colocated
307
+ // `.hbs` file next to a backing class, or a template-only component), the
308
+ // compiled backing module only *imports* its template (e.g. `import TEMPLATE
309
+ // from "./foo.hbs?import"`); it doesn't inline it. Any {{yield ... to="..."}}
310
+ // usage lives in that template file, so it has to be fetched and scanned
311
+ // separately, otherwise named blocks other than "default" are never detected
312
+ // and get silently dropped by the generated hot-reload wrapper.
313
+ const templateImportMatch = content.match(/\bfrom\s*['"]([^'"]+\.hbs(?:\?[^'"]*)?)['"]/);
314
+ if (templateImportMatch) {
315
+ const templateSpecifier = stripBase(templateImportMatch[1], base);
316
+ const templateRes = yield server.transformRequest(templateSpecifier);
317
+ return { filename: templateSpecifier, content: (_a = templateRes === null || templateRes === void 0 ? void 0 : templateRes.code) !== null && _a !== void 0 ? _a : '' };
318
+ }
319
+ if (content.includes('setComponentTemplate(')) {
320
+ return { filename, content };
321
+ }
322
+ return null;
323
+ });
324
+ }
325
+ // Determines which file's source should be scanned for named-block
326
+ // (`{{yield ... to="..."}}`) usage for the component at `filename`/`content`.
327
+ // Usually that's the component's own file (or its colocated `.hbs`, handled
328
+ // by resolveOwnTemplateSource). But a component with no template of its own
329
+ // renders whatever template is registered on its nearest ancestor class (see
330
+ // resolveOwnTemplateSource's doc comment) - previously this only ever
331
+ // scanned the child's own (template-less) file, silently dropping every
332
+ // named block other than "default" for any component subclassed without its
333
+ // own template. This walks the `extends` chain, resolving each ancestor's
334
+ // import the same way Vite already resolved it in the already-transformed
335
+ // source, until one with its own template is found.
336
+ function resolveYieldSource(server, base, filename, content) {
337
+ return __awaiter(this, void 0, void 0, function* () {
338
+ const own = yield resolveOwnTemplateSource(server, base, filename, content);
339
+ if (own) {
340
+ return own;
341
+ }
342
+ const seen = new Set([filename]);
343
+ let currentContent = content;
344
+ for (let i = 0; i < MAX_EXTENDS_DEPTH; i++) {
345
+ const superSource = findSuperclassImportSource(currentContent);
346
+ if (!superSource) {
347
+ break;
348
+ }
349
+ const superSpecifier = stripBase(superSource, base);
350
+ // Framework/addon base classes live in node_modules and are never
351
+ // hot-tracked by this plugin (see the node_modules skip in `transform`);
352
+ // stop there rather than walking into vendored internals.
353
+ if (seen.has(superSpecifier) || superSpecifier.includes('node_modules')) {
354
+ break;
355
+ }
356
+ seen.add(superSpecifier);
357
+ let superContent;
358
+ try {
359
+ // Unlike the `.hbs` specifier resolveOwnTemplateSource requests
360
+ // (always something Vite itself already resolved for a file we
361
+ // successfully transformed), this specifier can be anything a
362
+ // superclass import resolved to, including forms
363
+ // `transformRequest` can't handle. Failing to resolve an ancestor's
364
+ // template is the same "no named blocks found" outcome as before
365
+ // this fix existed - it must never fail the whole component load.
366
+ const superRes = yield server.transformRequest(superSpecifier);
367
+ superContent = superRes === null || superRes === void 0 ? void 0 : superRes.code;
368
+ }
369
+ catch (_a) {
370
+ break;
371
+ }
372
+ if (!superContent) {
373
+ break;
374
+ }
375
+ const superOwn = yield resolveOwnTemplateSource(server, base, superSpecifier, superContent);
376
+ if (superOwn) {
377
+ return superOwn;
378
+ }
379
+ currentContent = superContent;
380
+ }
381
+ return { filename, content };
382
+ });
383
+ }
191
384
  const virtualPrefix = '/ember-vite-hmr/virtual/component:';
192
385
  // Embroider's resolver registry. It's the binding the app entry's HMR hook
193
386
  // mutates, so we use it to locate that entry. Embroider keeps this as an
@@ -301,31 +494,7 @@ export function hmr(enableViteHmrForModes = ['development']) {
301
494
  }
302
495
  const res = yield server.transformRequest(filename);
303
496
  const content = res === null || res === void 0 ? void 0 : res.code;
304
- // For a classic component with a separately resolved template (a colocated
305
- // `.hbs` file next to a backing class, or a template-only component), the
306
- // compiled backing module only *imports* its template (e.g. `import TEMPLATE
307
- // from "./foo.hbs?import"`); it doesn't inline it. Any {{yield ... to="..."}}
308
- // usage lives in that template file, so it has to be fetched and scanned
309
- // separately, otherwise named blocks other than "default" are never detected
310
- // and get silently dropped by the generated hot-reload wrapper.
311
- let yieldSourceFilename = filename;
312
- let yieldSourceContent = content;
313
- const templateImportMatch = content === null || content === void 0 ? void 0 : content.match(/\bfrom\s*['"]([^'"]+\.hbs(?:\?[^'"]*)?)['"]/);
314
- if (templateImportMatch) {
315
- // The compiled backing class embeds the template import as a
316
- // browser-facing URL (i.e. prefixed with the configured vite
317
- // `base`), since that's what gets sent to the client. But
318
- // `server.transformRequest` is an internal API keyed by root-
319
- // relative ids, so the base has to be stripped back off before
320
- // reusing that specifier here, or this 404s under a non-root base.
321
- let templateSpecifier = templateImportMatch[1];
322
- if (base !== '/' && templateSpecifier.startsWith(base)) {
323
- templateSpecifier = `/${templateSpecifier.slice(base.length)}`;
324
- }
325
- const templateRes = yield server.transformRequest(templateSpecifier);
326
- yieldSourceFilename = templateSpecifier;
327
- yieldSourceContent = templateRes === null || templateRes === void 0 ? void 0 : templateRes.code;
328
- }
497
+ const { filename: yieldSourceFilename, content: yieldSourceContent } = yield resolveYieldSource(server, base, filename, content !== null && content !== void 0 ? content : '');
329
498
  const resId = yield this.resolve(yieldSourceFilename, path.resolve(process.cwd(), 'package.json'));
330
499
  // Strip any query string so the cache key matches the plain file path that
331
500
  // `hotUpdate` below receives when that template file is edited.
@@ -430,103 +599,124 @@ export function hmr(enableViteHmrForModes = ['development']) {
430
599
  if (resourcePath.includes('node_modules')) {
431
600
  return source;
432
601
  }
433
- // Add hot reload statements for tracked imports (only non-node_modules)
434
- // Use babel visitor to extract __hmr_import_metadata__
435
- const result = parseSync(source, {
436
- filename: resourcePath,
437
- ast: true,
438
- code: false,
439
- configFile: false,
440
- babelrc: false,
441
- plugins: [
442
- ['@babel/plugin-syntax-typescript', { isTSX: true }],
443
- ['@babel/plugin-proposal-decorators', { version: '2022-03' }],
444
- ],
445
- });
446
- if (result) {
447
- let importVar = null;
448
- let bindings = [];
449
- const importStatements = [];
450
- // First pass: Extract metadata
451
- traverse(result, {
452
- ExportNamedDeclaration(path) {
453
- var _a, _b, _c;
454
- const declaration = path.node.declaration;
455
- // Check if this is: export const __hmr_import_metadata__ = {...}
456
- if ((declaration === null || declaration === void 0 ? void 0 : declaration.type) === 'VariableDeclaration' &&
457
- ((_c = (_b = (_a = declaration.declarations) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.id) === null || _c === void 0 ? void 0 : _c.name) ===
458
- '__hmr_import_metadata__') {
459
- const init = declaration.declarations[0].init;
460
- if ((init === null || init === void 0 ? void 0 : init.type) === 'ObjectExpression') {
461
- // Extract importVar and bindings from the object
462
- for (const prop of init.properties) {
463
- if (prop.type === 'ObjectProperty' &&
464
- prop.key.type === 'Identifier') {
465
- if (prop.key.name === 'importVar' &&
466
- prop.value.type === 'StringLiteral') {
467
- importVar = prop.value.value;
468
- }
469
- else if (prop.key.name === 'bindings' &&
470
- prop.value.type === 'ArrayExpression') {
471
- bindings = prop.value.elements
472
- .filter((el) => (el === null || el === void 0 ? void 0 : el.type) === 'StringLiteral')
473
- .map((el) => el.value);
474
- }
475
- }
476
- }
477
- }
478
- }
479
- },
602
+ // Add hot reload statements for tracked imports.
603
+ //
604
+ // lib/babel-plugin.ts's hotReplaceAst already computes this exact
605
+ // metadata (importVar/bindings/importStatements) while babel processes
606
+ // this same file earlier in the pipeline, and shares it via
607
+ // hmrImportMetadataCache keyed by filename -- so the common case skips
608
+ // re-parsing and re-traversing this file from scratch. Only fall back
609
+ // to recovering it the slow way (parsing the already-babel-transformed
610
+ // source and pulling the __hmr_import_metadata__ export back out of
611
+ // it) when there's no cache entry, e.g. because this file's babel pass
612
+ // didn't go through hotReplaceAst in this process, or a concurrent
613
+ // babel pass for another file raced the cache write out (see that
614
+ // cache's own doc comment).
615
+ let importVar = null;
616
+ let bindings = [];
617
+ let importStatements = [];
618
+ const cached = hmrImportMetadataCache.get(resourcePath);
619
+ if (cached) {
620
+ importVar = cached.importVar;
621
+ bindings = cached.bindings;
622
+ importStatements = cached.importStatements;
623
+ }
624
+ else {
625
+ const result = parseSync(source, {
626
+ filename: resourcePath,
627
+ ast: true,
628
+ code: false,
629
+ configFile: false,
630
+ babelrc: false,
631
+ plugins: [
632
+ ['@babel/plugin-syntax-typescript', { isTSX: true }],
633
+ ['@babel/plugin-proposal-decorators', { version: '2022-03' }],
634
+ ],
480
635
  });
481
- // Second pass: Find matching imports (only if we have bindings to match)
482
- if (importVar && bindings.length > 0) {
636
+ if (result) {
637
+ // First pass: Extract metadata
483
638
  traverse(result, {
484
- ImportDeclaration(path) {
485
- const importSource = path.node.source.value;
486
- for (const specifier of path.node.specifiers) {
487
- const local = specifier.local.name;
488
- if (bindings.includes(local)) {
489
- let specifierName = 'default';
490
- if (specifier.type === 'ImportDefaultSpecifier') {
491
- specifierName = 'default';
492
- }
493
- else if (specifier.type === 'ImportSpecifier') {
494
- // For named imports, use the imported name
495
- // Handle both Identifier and StringLiteral types
496
- const imported = specifier.imported;
497
- specifierName =
498
- imported.type === 'Identifier'
499
- ? imported.name
500
- : imported.value;
501
- }
502
- else if (specifier.type === 'ImportNamespaceSpecifier') {
503
- specifierName = '*';
639
+ ExportNamedDeclaration(path) {
640
+ var _a, _b, _c;
641
+ const declaration = path.node.declaration;
642
+ // Check if this is: export const __hmr_import_metadata__ = {...}
643
+ if ((declaration === null || declaration === void 0 ? void 0 : declaration.type) === 'VariableDeclaration' &&
644
+ ((_c = (_b = (_a = declaration.declarations) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.id) === null || _c === void 0 ? void 0 : _c.name) ===
645
+ '__hmr_import_metadata__') {
646
+ const init = declaration.declarations[0].init;
647
+ if ((init === null || init === void 0 ? void 0 : init.type) === 'ObjectExpression') {
648
+ // Extract importVar and bindings from the object
649
+ for (const prop of init.properties) {
650
+ if (prop.type === 'ObjectProperty' &&
651
+ prop.key.type === 'Identifier') {
652
+ if (prop.key.name === 'importVar' &&
653
+ prop.value.type === 'StringLiteral') {
654
+ importVar = prop.value.value;
655
+ }
656
+ else if (prop.key.name === 'bindings' &&
657
+ prop.value.type === 'ArrayExpression') {
658
+ bindings = prop.value.elements
659
+ .filter((el) => (el === null || el === void 0 ? void 0 : el.type) ===
660
+ 'StringLiteral')
661
+ .map((el) => el.value);
662
+ }
663
+ }
504
664
  }
505
- importStatements.push({
506
- local,
507
- source: importSource,
508
- specifier: specifierName,
509
- });
510
665
  }
511
666
  }
512
667
  },
513
668
  });
669
+ // Second pass: Find matching imports (only if we have bindings to match)
670
+ if (importVar && bindings.length > 0) {
671
+ traverse(result, {
672
+ ImportDeclaration(path) {
673
+ const importSource = path.node.source.value;
674
+ for (const specifier of path.node.specifiers) {
675
+ const local = specifier.local.name;
676
+ if (bindings.includes(local)) {
677
+ let specifierName = 'default';
678
+ if (specifier.type === 'ImportDefaultSpecifier') {
679
+ specifierName = 'default';
680
+ }
681
+ else if (specifier.type === 'ImportSpecifier') {
682
+ // For named imports, use the imported name
683
+ // Handle both Identifier and StringLiteral types
684
+ const imported = specifier.imported;
685
+ specifierName =
686
+ imported.type === 'Identifier'
687
+ ? imported.name
688
+ : imported.value;
689
+ }
690
+ else if (specifier.type === 'ImportNamespaceSpecifier') {
691
+ specifierName = '*';
692
+ }
693
+ importStatements.push({
694
+ local,
695
+ source: importSource,
696
+ specifier: specifierName,
697
+ });
698
+ }
699
+ }
700
+ },
701
+ });
702
+ }
514
703
  }
515
- // Process metadata if we found importVar (even with empty bindings)
516
- if (importVar) {
517
- // Generate hot reload code for each import (only if we have bindings)
518
- const hotReloadStatements = [];
519
- for (const imp of importStatements) {
520
- // Resolve the import to check if it's from node_modules
521
- const resolved = yield this.resolve(imp.source, resourcePath, {});
522
- if ((resolved === null || resolved === void 0 ? void 0 : resolved.id) &&
523
- normalizePath(resolved.id).includes('node_modules')) {
524
- // Skip node_modules imports
525
- continue;
526
- }
527
- const sourceId = imp.source.replace(/@embroider\/virtual/g, 'embroider_virtual');
528
- const virtualPath = `/ember-vite-hmr/virtual/component:${sourceId}::${imp.specifier}.gjs`;
529
- hotReloadStatements.push(`
704
+ }
705
+ // Process metadata if we found importVar (even with empty bindings)
706
+ if (importVar) {
707
+ // Generate hot reload code for each import (only if we have bindings)
708
+ const hotReloadStatements = [];
709
+ for (const imp of importStatements) {
710
+ // Resolve the import to check if it's from node_modules
711
+ const resolved = yield this.resolve(imp.source, resourcePath, {});
712
+ if ((resolved === null || resolved === void 0 ? void 0 : resolved.id) &&
713
+ normalizePath(resolved.id).includes('node_modules')) {
714
+ // Skip node_modules imports
715
+ continue;
716
+ }
717
+ const sourceId = imp.source.replace(/@embroider\/virtual/g, 'embroider_virtual');
718
+ const virtualPath = `/ember-vite-hmr/virtual/component:${sourceId}::${imp.specifier}.gjs`;
719
+ hotReloadStatements.push(`
530
720
  (async () => {
531
721
  const GlimmerComponent = (await import('@glimmer/component')).default;
532
722
  const { hasInternalComponentManager } = await import('@glimmer/manager');
@@ -548,17 +738,16 @@ export function hmr(enableViteHmrForModes = ['development']) {
548
738
  import.meta.hot.accept('${imp.source}');
549
739
  }
550
740
  })();`);
551
- }
552
- // Always remove the metadata export
553
- source = source.replace(/export const __hmr_import_metadata__[^;]+;/, '');
554
- // Add HMR code if we have any statements OR if we have bindings (even if all were skipped)
555
- if (hotReloadStatements.length > 0 || bindings.length > 0) {
556
- const hotReloadCode = `
741
+ }
742
+ // Always remove the metadata export
743
+ source = source.replace(/export const __hmr_import_metadata__[^;]+;/, '');
744
+ // Add HMR code if we have any statements OR if we have bindings (even if all were skipped)
745
+ if (hotReloadStatements.length > 0 || bindings.length > 0) {
746
+ const hotReloadCode = `
557
747
  if (import.meta.hot) {
558
748
  ${hotReloadStatements.join('\n')}
559
749
  }`;
560
- source = source + hotReloadCode;
561
- }
750
+ source = source + hotReloadCode;
562
751
  }
563
752
  }
564
753
  const supportedPaths = ['routers', 'controllers', 'routes', 'templates'];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ember-vite-hmr",
3
- "version": "2.3.0",
3
+ "version": "2.4.0",
4
4
  "private": false,
5
5
  "description": "provides hmr support for ember apps",
6
6
  "keywords": [
@@ -51,6 +51,7 @@
51
51
  "@babel/eslint-parser": "^7.28.6",
52
52
  "@babel/plugin-proposal-class-properties": "^7.16.7",
53
53
  "@babel/plugin-proposal-decorators": "^7.28.0",
54
+ "@babel/plugin-syntax-decorators": "^7.29.7",
54
55
  "@babel/plugin-transform-class-static-block": "^7.28.6",
55
56
  "@babel/runtime": "^7.28.4",
56
57
  "@embroider/addon-dev": "^8.3.0",