@maka/maka-cli 5.129.0 → 5.130.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maka/maka-cli",
3
- "version": "5.129.0",
3
+ "version": "5.130.0",
4
4
  "type": "module",
5
5
  "summary": "A command line tool for scaffolding Meteor 3.x applications using either React.",
6
6
  "description": "A command line tool for scaffolding Meteor 3.x applications using React.",
@@ -777,15 +777,20 @@ export class CastCommand extends BypassCommand {
777
777
  return error ?? `You don't see them here.`;
778
778
  const self = target === this.actor;
779
779
  // Sustained toggle per bearer: casting at them again unravels it, no
780
- // drain to let go. (The caster is the only one who can release it --
781
- // a mundane bearer has no way to drop a spell themselves.)
780
+ // drain to let go. The caster is the only one who can release it
781
+ // (p.282: the caster sustains) -- a weave somebody ELSE is holding
782
+ // on them is that caster's to drop, not yours to overwrite.
782
783
  if (target.sustainedArmor > 0) {
784
+ const holder = target.wornSpellCaster('armor');
785
+ if (holder && holder !== this.actor) {
786
+ return `${holder.name} is holding an armor weave on ${self ? 'you' : target.name} already -- only ${holder.name} can let it go.`;
787
+ }
783
788
  target.setSustainedArmor(0);
784
789
  this.scene.updateStatus();
785
790
  this.actor.performAction('releases armor spell', self ? '' : `on ${target.name}`);
786
791
  return this.finishSpell([self
787
792
  ? `You let the armor spell go. The air stops humming, and the sustaining penalty lifts.`
788
- : `You unravel the armor weave around ${target.name}. The shimmer fades, and their sustaining burden lifts.`]);
793
+ : `You unravel the armor weave around ${target.name}. The shimmer fades, and the burden of holding it lifts from you.`]);
789
794
  }
790
795
  if (target.isIncapacitated()) {
791
796
  return `${target.name} is down -- armor won't help now.`;
@@ -795,19 +800,20 @@ export class CastCommand extends BypassCommand {
795
800
  const focus = this.actor.getBestFocus();
796
801
  const bonus = Math.ceil(force / 3) + (focus?.ratingValue ?? 0); // the focus's Force (catalog v13)
797
802
  const spell = spellFor('armor');
798
- // The weave taxes its BEARER: they get the soak and carry the -2
799
- // sustaining burden. The caster pays only the drain.
803
+ // The BEARER wears the weave (the soak is theirs); the CASTER
804
+ // sustains it and carries the -2 (p.282), on top of the drain.
800
805
  target.setSustainedArmor(bonus);
801
806
  target.noteSustainedForce('armor', force);
807
+ target.sustainedBy('armor', this.actor);
802
808
  this.actor.performAction('casts armor', self ? '' : `on ${target.name}`);
803
809
  this.scene.addWorldEvent(`${this.actor.name} wrapped ${self ? 'themselves' : target.name} in a shimmering armor spell.`);
804
810
  this.scene.updateStatus();
805
- // The burden is the bearer's ledger's, not a restated -2: a Focused
811
+ // The burden is the CASTER's ledger's, not a restated -2: a Focused
806
812
  // Concentration holder is told "no penalty" the moment it applies.
807
- const burden = sustainBurden(target.sustainedLedger().find(w => w.key === 'armor'), ` to everything else ${self ? 'you do' : 'they do'}`);
813
+ const burden = sustainBurden(this.actor.sustainedLedger().find(w => w.key === 'armor' && (self ? w.bearer === undefined : w.bearer === target.name)), ' to everything else you do');
808
814
  const lines = [
809
815
  `${this.actor.name} casts armor at Force ${force}${self ? '' : ` on ${target.name}`}, ${this.castingLabel()}:`,
810
- ` The air hardens around ${self ? 'you' : target.name} -- +${bonus} soak dice while sustained, ${burden}.`,
816
+ ` The air hardens around ${self ? 'you' : target.name} -- +${bonus} soak dice while sustained${self ? '' : '; you hold the weave up'}, ${burden}.`,
811
817
  hint(` (Cast armor${self ? '' : ` on ${target.name}`} again to release it.)`),
812
818
  ...this.applyDrain(spell, force, overcast),
813
819
  ];
@@ -882,8 +888,8 @@ export class CastCommand extends BypassCommand {
882
888
  }
883
889
  /**
884
890
  * The sustained utility pair (invisibility / increase reflexes), same
885
- * toggle shape as the armor spell: the BEARER carries the effect and
886
- * its -2 sustaining burden, the caster pays the drain, and casting at
891
+ * toggle shape as the armor spell: the BEARER wears the effect, the
892
+ * CASTER sustains it (-2, p.282) and pays the drain, and casting at
887
893
  * the same bearer again releases it for free.
888
894
  */
889
895
  async castSustainedUtility(spellKey, force, overcast, rest = []) {
@@ -894,12 +900,16 @@ export class CastCommand extends BypassCommand {
894
900
  const flag = spellKey === 'invisibility' ? 'sustainedInvisibility' : 'sustainedReflexes';
895
901
  const label = spellKey === 'invisibility' ? 'invisibility' : 'increase reflexes';
896
902
  if (target[flag]) {
903
+ const holder = target.wornSpellCaster(spellKey);
904
+ if (holder && holder !== this.actor) {
905
+ return `${holder.name} is holding ${label} on ${self ? 'you' : target.name} already -- only ${holder.name} can let it go.`;
906
+ }
897
907
  target[flag] = false;
898
908
  this.scene.updateStatus();
899
909
  this.actor.performAction(`releases ${label}`, self ? '' : `on ${target.name}`);
900
910
  return this.finishSpell([spellKey === 'invisibility'
901
- ? `${self ? 'You shimmer back into the world' : `${target.name} shimmers back into the world`} -- the light remembers ${self ? 'you' : 'them'}, and the sustaining burden lifts.`
902
- : `${self ? 'Your' : `${target.name}'s`} nerves cool back to human speed. The sustaining burden lifts.`]);
911
+ ? `${self ? 'You shimmer back into the world' : `${target.name} shimmers back into the world`} -- the light remembers ${self ? 'you' : 'them'}, and the sustaining burden lifts${self ? '' : ' from you'}.`
912
+ : `${self ? 'Your' : `${target.name}'s`} nerves cool back to human speed. The sustaining burden lifts${self ? '' : ' from you'}.`]);
903
913
  }
904
914
  if (target.isIncapacitated()) {
905
915
  return `${target.name} is down -- ${label} won't help now.`;
@@ -911,15 +921,18 @@ export class CastCommand extends BypassCommand {
911
921
  const spell = spellFor(spellKey);
912
922
  target[flag] = true;
913
923
  target.noteSustainedForce(spellKey, force);
924
+ target.sustainedBy(spellKey, this.actor);
914
925
  this.actor.performAction(`casts ${label}`, self ? '' : `on ${target.name}`);
915
926
  this.scene.addWorldEvent(spellKey === 'invisibility'
916
927
  ? `${this.actor.name} wove an invisibility spell${self ? '' : ` over ${target.name}`}.`
917
928
  : `${this.actor.name} cast increase reflexes${self ? '' : ` on ${target.name}`}.`);
918
929
  this.scene.updateStatus();
919
- const burden = sustainBurden(target.sustainedLedger().find(w => w.key === spellKey), ` to everything else ${self ? 'you do' : 'they do'}`);
930
+ // The CASTER's ledger: they hold it, they pay (p.282).
931
+ const burden = sustainBurden(this.actor.sustainedLedger().find(w => w.key === spellKey && (self ? w.bearer === undefined : w.bearer === target.name)), ' to everything else you do');
932
+ const holding = self ? '' : '; you hold it up';
920
933
  const effectLine = spellKey === 'invisibility'
921
- ? ` The light slides around ${self ? 'you' : target.name} like water around a stone -- +3 dice on sneak contests while sustained, ${burden}. Loud acts still announce ${self ? 'you' : 'them'}.`
922
- : ` ${self ? 'Your' : `${target.name}'s`} nerves run HOT -- +2 initiative while sustained, ${burden}.`;
934
+ ? ` The light slides around ${self ? 'you' : target.name} like water around a stone -- +3 dice on sneak contests while sustained${holding}, ${burden}. Loud acts still announce ${self ? 'you' : 'them'}.`
935
+ : ` ${self ? 'Your' : `${target.name}'s`} nerves run HOT -- +2 initiative while sustained${holding}, ${burden}.`;
923
936
  const lines = [
924
937
  `${this.actor.name} casts ${label} at Force ${force}${self ? '' : ` on ${target.name}`}, ${this.castingLabel()}:`,
925
938
  effectLine,
@@ -173,7 +173,7 @@ export class SheetCommand extends Command {
173
173
  ...(a.initiationGrade > 0 ? [row(a.isTechnomancer() ? 'Submersion' : 'Initiation', `grade ${a.initiationGrade} -- ${catalogMetamagics().filter(m => a.hasMetamagic(m.key)).map(m => m.name).join(', ')}`)] : []),
174
174
  ...(a.lockedFatigue > 0 ? [row('Fatigue', `${a.lockedFatigue} stun LOCKED until the need is met`)] : []),
175
175
  ...(a.woundModifier < 0 ? [row('Wounds', `${a.woundModifier} dice on everything`)] : []),
176
- ...(a.sustainedArmor > 0 ? [row('Sustained', `armor +${a.sustainedArmor} soak (${sustainBurden(a.sustainedLedger().find(w => w.key === 'armor'))})`)] : []),
176
+ ...(a.sustainedArmor > 0 ? [row('Sustained', `armor +${a.sustainedArmor} soak (${a.wornSpellCaster('armor') ? `held by ${a.wornSpellCaster('armor').name}` : sustainBurden(a.sustainedLedger().find(w => w.key === 'armor' && w.bearer === undefined))})`)] : []),
177
177
  ];
178
178
  return lines.join('\n');
179
179
  }
@@ -70,14 +70,25 @@ export class SpellsCommand extends Command {
70
70
  // The cost per working is the engine's (Player.sustainedLedger): a
71
71
  // Focused Concentration holder reads "no penalty" here, not a
72
72
  // restated -2.
73
+ // The CASTER pays (p.282): a spell somebody else holds on you shows
74
+ // as worn, with the holder named; what you hold on others is listed
75
+ // after, with its bearer.
73
76
  const ledger = actor.sustainedLedger();
74
- const burden = (key) => sustainBurden(ledger.find(w => w.key === key), ' to everything else');
77
+ const burden = (key) => {
78
+ const holder = actor.wornSpellCaster(key);
79
+ return holder ? `held by ${holder.name} -- their burden` : sustainBurden(ledger.find(w => w.key === key && w.bearer === undefined), ' to everything else');
80
+ };
75
81
  if (actor.sustainedArmor > 0) {
76
82
  lines.push(`Sustained: armor spell, +${actor.sustainedArmor} soak (${burden('armor')}).${hint(' Cast armor again to release.')}`);
77
83
  }
78
84
  if (actor.sustainedInvisibility) {
79
85
  lines.push(`Sustained: invisibility, +3 on sneak contests (${burden('invisibility')}).${hint(' Cast invisibility again to release.')}`);
80
86
  }
87
+ for (const w of ledger) {
88
+ if (w.bearer === undefined)
89
+ continue;
90
+ lines.push(`Sustaining on ${w.bearer}: ${w.key === 'reflexes' ? 'increase reflexes' : w.key} (${sustainBurden(w, ' to everything else')}).${hint(` Cast ${w.key} on ${w.bearer} again to release.`)}`);
91
+ }
81
92
  if (actor.sustainedBarrier) {
82
93
  lines.push(`Sustained: force barrier sealing ${actor.sustainedBarrier.direction} in ${actor.sustainedBarrier.roomName}, Force ${actor.sustainedBarrier.force} (${burden('barrier')}).${hint(' Cast barrier again to release.')}`);
83
94
  }
@@ -5281,9 +5281,16 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
5281
5281
  // literal -2 until 2026-09-06, while sustainingPenalty was already
5282
5282
  // waiving a Focused Concentration holder's spell -- the HUD told the
5283
5283
  // player they were paying for what the pools were not charging.
5284
+ //
5285
+ // THE CASTER PAYS (p.282, 2026-09-06). A spell somebody else holds
5286
+ // on this character is on their sheet as WORN -- soak and all --
5287
+ // with the holder named and no cost; what this character holds on
5288
+ // others rides the ledger below with its bearer named.
5284
5289
  const ledger = player.sustainedLedger();
5290
+ const heldBy = (key) => player.wornSpellCaster(key)?.name;
5285
5291
  if (player.sustainedArmor > 0) {
5286
- lines.push(`{magenta-fg}Sustained: armor +${player.sustainedArmor} soak (${sustainBurden(ledger.find(w => w.key === 'armor'), ' to everything else')}){/magenta-fg}`);
5292
+ const holder = heldBy('armor');
5293
+ lines.push(`{magenta-fg}Sustained: armor +${player.sustainedArmor} soak (${holder ? `held by ${holder} -- their burden` : sustainBurden(ledger.find(w => w.key === 'armor' && w.bearer === undefined), ' to everything else')}){/magenta-fg}`);
5287
5294
  }
5288
5295
  // THE MAGE'S HALF OF THE SAME QUESTION (TNf9WrZGxCmCzqA4L, scope
5289
5296
  // completed by ruling).
@@ -5309,10 +5316,11 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
5309
5316
  // Naming it belongs with the condition monitors if it belongs
5310
5317
  // anywhere; that is a separate call and not this item's.
5311
5318
  const heldSpells = [];
5319
+ const worn = (label, key) => heldSpells.push(heldBy(key) ? `${label} (held by ${heldBy(key)})` : label);
5312
5320
  if (player.sustainedInvisibility)
5313
- heldSpells.push('invisibility');
5321
+ worn('invisibility', 'invisibility');
5314
5322
  if (player.sustainedReflexes)
5315
- heldSpells.push('increased reflexes');
5323
+ worn('increased reflexes', 'reflexes');
5316
5324
  // The barriers say WHERE, because a wall you forgot you put up is
5317
5325
  // the one that strands the team -- and both of them seal an exit
5318
5326
  // that the caster cannot pass either.
@@ -5322,8 +5330,14 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
5322
5330
  if (player.sustainedManaBarrier) {
5323
5331
  heldSpells.push(`mana barrier (${player.sustainedManaBarrier.roomName} ${player.sustainedManaBarrier.direction})`);
5324
5332
  }
5333
+ // What this character holds on OTHERS -- the caster's burden, named
5334
+ // by bearer so the mage knows whose armor is costing them two dice.
5335
+ for (const w of ledger) {
5336
+ if (w.bearer !== undefined)
5337
+ heldSpells.push(`${w.key === 'reflexes' ? 'increased reflexes' : w.key} on ${w.bearer}`);
5338
+ }
5325
5339
  if (heldSpells.length > 0) {
5326
- const these = ledger.filter(w => w.kind === 'spell' && w.key !== 'armor');
5340
+ const these = ledger.filter(w => w.kind === 'spell' && !(w.key === 'armor' && w.bearer === undefined));
5327
5341
  lines.push(`{magenta-fg}✴ Sustained: ${heldSpells.join(', ')} (${sustainTotal(these)}){/magenta-fg}`);
5328
5342
  }
5329
5343
  // ACTIVE THREADS, WITHOUT ASKING (TNf9WrZGxCmCzqA4L: "instead of
@@ -824,8 +824,8 @@ Available in-game commands (use with [COMMAND] prefix):
824
824
  "manabolt" (combat damage resisted by body, not armor), "heal [on <ally>]" (heal
825
825
  physical wounds -- your own by default, or an ally's in your room; healing a
826
826
  wounded ally is a strong support move), "armor [on <ally>]" (sustained protection
827
- on yourself or an ally; its bearer gets +soak at -2 dice to all else; cast at them
828
- again to release). EVERY cast costs drain -- stun damage to YOU the caster; fill
827
+ on yourself or an ally; its bearer gets +soak, and YOU carry -2 dice to all else
828
+ while you hold it; cast at them again to release). EVERY cast costs drain -- stun damage to YOU the caster; fill
829
829
  your stun track and you're out cold. Never cast if you're mundane with no magic gear.
830
830
  - cast — bare "cast" attempts to magically bypass the ward/barrier in this room
831
831
  without knowing the answer (requires the same talent or gear; failing burns the
@@ -3631,7 +3631,7 @@ export class Player extends AbstractPlayer {
3631
3631
  return;
3632
3632
  this._sustainedArmor = next;
3633
3633
  if (next === 0)
3634
- this.sustainedForces.delete('armor');
3634
+ this.onSustainDropped('armor');
3635
3635
  // SUSTAINS BEAT ON FLIP (web report 2026-09-02: "I cast armor on myself
3636
3636
  // and don't see it in the sustained list"): the sheet only caught up on
3637
3637
  // the next unrelated beat. Every sustain mutator fires the hook now.
@@ -3682,8 +3682,27 @@ export class Player extends AbstractPlayer {
3682
3682
  * spells in `sustainedForces`.
3683
3683
  */
3684
3684
  sustainedWorkings() {
3685
+ const mine = this.wornWorkings().filter(w => w.caster === undefined).map(({ caster: _c, ...w }) => w);
3686
+ const onOthers = [];
3687
+ for (const bearer of this.sustainedBearers) {
3688
+ const held = bearer.wornWorkings().filter(w => w.caster === this);
3689
+ if (held.length === 0) {
3690
+ this.sustainedBearers.delete(bearer);
3691
+ continue;
3692
+ }
3693
+ for (const { caster: _c, ...w } of held)
3694
+ onOthers.push({ ...w, bearer: bearer.name });
3695
+ }
3696
+ return [...mine, ...onOthers];
3697
+ }
3698
+ /**
3699
+ * Every working whose STATE is on this character, with who holds it
3700
+ * up when that is somebody else. The bearer's view; sustainedWorkings
3701
+ * is the holder's.
3702
+ */
3703
+ wornWorkings() {
3685
3704
  const spell = (key, force) => ({
3686
- key, kind: 'spell', force, category: spellFor(key)?.category.toLowerCase(),
3705
+ key, kind: 'spell', force, category: spellFor(key)?.category.toLowerCase(), caster: this.sustainedCasters.get(key),
3687
3706
  });
3688
3707
  return [
3689
3708
  ...this.sustainedForms.map((f) => ({ key: f.key, kind: 'form', force: f.level })),
@@ -3749,15 +3768,22 @@ export class Player extends AbstractPlayer {
3749
3768
  }
3750
3769
  }
3751
3770
  releaseSustainedWorkings() {
3752
- const count = this.sustainedWorkings().length;
3753
- this.sustainedForms = [];
3754
- this._sustainedArmor = 0;
3755
- this.sustainedInvisibility = false;
3756
- this.sustainedReflexes = false;
3757
- this.sustainedBarrier = undefined;
3758
- this.sustainedManaBarrier = undefined;
3759
- this.sustainedForces.clear();
3760
- return count;
3771
+ // What this character HOLDS -- their own, and what they hold on
3772
+ // others (which ends on the bearer). A spell somebody else holds on
3773
+ // this character is that caster's concentration, and sleeping in
3774
+ // it does not break it.
3775
+ const held = this.sustainedWorkings();
3776
+ for (const w of held) {
3777
+ if (w.bearer === undefined) {
3778
+ this.dropSustainedWorking(w.key);
3779
+ continue;
3780
+ }
3781
+ for (const bearer of this.sustainedBearers) {
3782
+ if (bearer.name === w.bearer)
3783
+ bearer.dropSustainedWorking(w.key);
3784
+ }
3785
+ }
3786
+ return held.length;
3761
3787
  }
3762
3788
  // The other sustained spells (RAW gear/spell pass): invisibility bends
3763
3789
  // light around the bearer (+3 on sneak contests, npc.ts), increase
@@ -3770,7 +3796,7 @@ export class Player extends AbstractPlayer {
3770
3796
  return;
3771
3797
  this._sustainedInvisibility = v;
3772
3798
  if (!v)
3773
- this.sustainedForces.delete('invisibility');
3799
+ this.onSustainDropped('invisibility');
3774
3800
  this.noteConditionChanged();
3775
3801
  }
3776
3802
  _sustainedReflexes = false;
@@ -3780,9 +3806,72 @@ export class Player extends AbstractPlayer {
3780
3806
  return;
3781
3807
  this._sustainedReflexes = v;
3782
3808
  if (!v)
3783
- this.sustainedForces.delete('reflexes');
3809
+ this.onSustainDropped('reflexes');
3784
3810
  this.noteConditionChanged();
3785
3811
  }
3812
+ /**
3813
+ * WHO HOLDS A SPELL THAT IS ON THIS CHARACTER, when it is not them
3814
+ * (2026-09-06: "bill the caster, not the bearer"). SR5 p.282 puts the
3815
+ * -2 on whoever SUSTAINS the spell; the subject only wears it. Until
3816
+ * this the engine taxed the bearer -- a mundane street sam with an
3817
+ * ally's armor on them lost two dice off every pool, and the mage
3818
+ * holding it up paid nothing.
3819
+ *
3820
+ * The effect state stays on the bearer (that is what grants the
3821
+ * soak); this map is the other half, and the caster's ledger reads
3822
+ * it through `sustainedBearers`. Session-only, like every sustain.
3823
+ */
3824
+ sustainedCasters = new Map();
3825
+ /** The characters this one holds a spell on. Pruned at read time. */
3826
+ sustainedBearers = new Set();
3827
+ /** Record that `caster` is the one holding `key` up on this character. */
3828
+ sustainedBy(key, caster) {
3829
+ if (caster === this) {
3830
+ this.sustainedCasters.delete(key);
3831
+ return;
3832
+ }
3833
+ this.sustainedCasters.set(key, caster);
3834
+ caster.sustainedBearers.add(this);
3835
+ caster.noteConditionChanged();
3836
+ }
3837
+ /** The caster holding `key` on this character, when it is somebody else. */
3838
+ wornSpellCaster(key) {
3839
+ return this.sustainedCasters.get(key);
3840
+ }
3841
+ /** Bookkeeping when a spell on this character ends, whoever ended it. */
3842
+ onSustainDropped(key) {
3843
+ this.sustainedForces.delete(key);
3844
+ const caster = this.sustainedCasters.get(key);
3845
+ if (caster) {
3846
+ this.sustainedCasters.delete(key);
3847
+ caster.noteConditionChanged();
3848
+ }
3849
+ }
3850
+ /** End one working by key, whatever kind it is. */
3851
+ dropSustainedWorking(key) {
3852
+ switch (key) {
3853
+ case 'armor':
3854
+ this.setSustainedArmor(0);
3855
+ return;
3856
+ case 'invisibility':
3857
+ this.sustainedInvisibility = false;
3858
+ return;
3859
+ case 'reflexes':
3860
+ this.sustainedReflexes = false;
3861
+ return;
3862
+ case 'barrier':
3863
+ this.sustainedBarrier = undefined;
3864
+ return;
3865
+ case 'manabarrier':
3866
+ this.sustainedManaBarrier = undefined;
3867
+ return;
3868
+ default: {
3869
+ const i = this.sustainedForms.findIndex(f => f.key === key);
3870
+ if (i >= 0)
3871
+ this.sustainedForms.splice(i, 1);
3872
+ }
3873
+ }
3874
+ }
3786
3875
  /** The sustained PHYSICAL BARRIER spell (ruling 2026-08-24): seals
3787
3876
  * one exit of one room while held -- go.ts and move refuse passage
3788
3877
  * for everyone (the caster included; walls don't play favorites).
@@ -3793,6 +3882,8 @@ export class Player extends AbstractPlayer {
3793
3882
  if (this._sustainedBarrier === v)
3794
3883
  return;
3795
3884
  this._sustainedBarrier = v;
3885
+ if (!v)
3886
+ this.onSustainDropped('barrier');
3796
3887
  this.noteConditionChanged();
3797
3888
  }
3798
3889
  /** The MANA BARRIER twin (canon p.294, RAG-checked): blocks SPIRITS
@@ -3804,6 +3895,8 @@ export class Player extends AbstractPlayer {
3804
3895
  if (this._sustainedManaBarrier === v)
3805
3896
  return;
3806
3897
  this._sustainedManaBarrier = v;
3898
+ if (!v)
3899
+ this.onSustainDropped('manabarrier');
3807
3900
  this.noteConditionChanged();
3808
3901
  }
3809
3902
  // THREADED COMPLEX FORMS (M4.15, see commands/thread.ts): the
@@ -105,7 +105,18 @@ export function parseCatalogVersion(raw) {
105
105
  const n = Number(raw);
106
106
  return Number.isFinite(n) ? n : undefined;
107
107
  }
108
+ /**
109
+ * Set by resetCatalogForTest and never cleared: a suite that has reset
110
+ * the catalog wants ONLY what it applies. Found 2026-09-06, the day the
111
+ * developer's cache moved to v17 -- every fixture pinned at 16 lost the
112
+ * "highest version wins" contest to a file in the home directory, and
113
+ * eleven accessor tests went red on one machine and green on every
114
+ * other. Exactly the failure the note above says a test must not have.
115
+ */
116
+ let diskCacheDisabled = false;
108
117
  function readCache() {
118
+ if (diskCacheDisabled)
119
+ return undefined;
109
120
  try {
110
121
  const parsed = JSON.parse(fs.readFileSync(cachePath(), 'utf8'));
111
122
  if (Array.isArray(parsed?.items) && parsed.items.length > 0)
@@ -571,6 +582,10 @@ export function resetCatalogForTest() {
571
582
  liveSource = undefined;
572
583
  resolved = undefined;
573
584
  fetchAttempted = false;
585
+ // The machine's cache is out of the contest from here on: a fixture
586
+ // applied after this is the whole catalog, whatever version it says.
587
+ // A test that wants a cache passes its own reader (CacheReader).
588
+ diskCacheDisabled = true;
574
589
  resetCatalogIndexes();
575
590
  }
576
591
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maka/maka-cli",
3
- "version": "5.129.0",
3
+ "version": "5.130.0",
4
4
  "type": "module",
5
5
  "summary": "A command line tool for scaffolding Meteor 3.x applications using either React.",
6
6
  "description": "A command line tool for scaffolding Meteor 3.x applications using React.",