This commit is contained in:
taojiacheng committed 2026-04-20 10:51:36 +08:00
1 parent 3260b8c879
commit 556141af56
7 files changed
+210 -437

No files matched your search

+74
View File
@@ -0,0 +1,74 @@
# 技能冷却与法力消耗平衡调整计划
## Objective (目标)
调整主动技能(Active Skill)的 `mpCost`(法力消耗)与 `cooldown`(冷却时间)计算规则,使其随着造物材料品质(Quality)的提升而指数级上升(蓝耗)并在 2~8 回合内波动(冷却)。以适应游戏中角色境界(Realm)和基础属性的指数级增长。
## Key Files & Context (受影响的文件)
1. **`engine/creation-v2/config/CreationBalance.ts`**:
- `CREATION_PROJECTION_BALANCE`:新增 `qualityCooldownBonus: [0, 0, 1, 2, 3, 4, 5, 6] as const` 用于控制随品质带来的额外冷却。
2. **`engine/creation-v2/rules/composition/EnergyConversionRules.ts`**:
- 增加指数级法力消耗计算逻辑:提取 `qualityOrder`,使用 `Math.pow(2, qualityOrder)` 作为 `mpCost` 的乘数。
3. **`engine/creation-v2/rules/composition/ProjectionRules.ts`**:
- 修改 `buildSkillPolicy` 中的 `cooldown` 计算逻辑,附加对应 `qualityOrder` 的冷却补偿,并通过 `Math.min(8, baseCooldown + bonus)` 封顶为 8 回合。
## Implementation Steps (实施步骤)
### 1. 更新 `CreationBalance.ts`
在 `CREATION_PROJECTION_BALANCE` 常量中添加 `qualityCooldownBonus` 数组:
```typescript
export const CREATION_PROJECTION_BALANCE = {
// ...
gongfaSpiritBuffBase: 3,
/**
* 主动技能冷却时间品质加成(基于品质 order 0-7)。
* 将在基础冷却上增加该值,且总冷却限制在 2~8 回合。
*/
qualityCooldownBonus: [0, 0, 1, 2, 3, 4, 5, 6] as const,
} as const;
```
### 2. 更新 `EnergyConversionRules.ts`
在 `EnergyConversionRules.apply` 方法中引入 `materialQualityProfile` 并应用指数级增长:
```typescript
const { energySummary, affixes, materialQualityProfile } = facts;
const qualityOrder = materialQualityProfile.weightedAverageOrder;
const qualityMultiplier = Math.pow(2, qualityOrder);
const baseMpCost = Math.round(
energySummary.effectiveTotal / CREATION_PROJECTION_BALANCE.mpCostDivisor,
);
const mpCost = Math.max(
CREATION_SKILL_DEFAULTS.minMpCost * qualityMultiplier,
baseMpCost * qualityMultiplier,
);
```
### 3. 更新 `ProjectionRules.ts`
在 `ProjectionRules.buildSkillPolicy` 方法中,修改冷却时间的计算逻辑:
```typescript
const coreType = coreDef.effectTemplate.type;
const baseCooldown =
coreType === 'heal'
? CREATION_SKILL_DEFAULTS.healCooldown
: coreType === 'apply_buff'
? CREATION_SKILL_DEFAULTS.buffCooldown
: CREATION_SKILL_DEFAULTS.damageCooldown;
const qualityOrder = materialQualityProfile.weightedAverageOrder;
const cooldownBonus = CREATION_PROJECTION_BALANCE.qualityCooldownBonus[qualityOrder] ?? 0;
const cooldown = Math.min(8, baseCooldown + cooldownBonus);
diagnostics.addTrace({
ruleId: this.id,
outcome: 'applied',
message: `cooldown=${cooldown} (coreType=${coreType}, base=${baseCooldown}, bonus=${cooldownBonus})`,
});
```
## Verification & Testing (验证与测试)
- [ ] 运行 `npm test` 确保受影响的现有测试在更新预期的 `mpCost` 和 `cooldown` 断言后仍然通过。
-1
View File
@@ -14,7 +14,6 @@ import {
ListenerGuardConfig,
ListenerScope,
ModifierType,
StackRule,
} from '../contracts/battle';
import type { CreationTagSignalSource } from '../types';
import { AffixCategory, CreationProductType } from '../types';
@@ -1,429 +0,0 @@
/**
* AffixBoundaryValidation.test.ts
*
* 词缀边界规则校验测试(平衡三角论)。
*
* Part 1: 遍历 DEFAULT_AFFIX_REGISTRY 中的所有词缀,验证现有词缀全部合规。
* Part 2: 注册违规 mock 词缀,验证 validateBoundary() 能正确拦截。
*/
/* eslint-disable @typescript-eslint/no-explicit-any */
import { ModifierType } from '../../contracts/battle';
import { GameplayTags } from '@/engine/shared/tag-domain';
import { DEFAULT_AFFIX_REGISTRY, AffixRegistry } from '../../affixes';
import type { AffixDefinition } from '../../affixes';
import { matchAll } from '../../affixes';
/** 构造最小可注册词缀的辅助工厂 */
function makeAffix(overrides: Partial<AffixDefinition>): AffixDefinition {
return {
id: 'test-affix',
displayName: 'Test',
displayDescription: 'Test affix',
category: 'skill_core',
rarity: 'common',
match: matchAll([]),
weight: 1,
energyCost: 1,
effectTemplate: { type: 'damage', params: { value: 10 } },
applicableTo: ['skill'],
...overrides,
} as AffixDefinition;
}
describe('AffixBoundaryValidation', () => {
const allDefs = DEFAULT_AFFIX_REGISTRY.getAll();
// ===== Part 1: 现有词缀合规性审计 =====
it('gongfa 词缀禁止使用 OWNER_AS_TARGET scope', () => {
const violations = allDefs.filter(
(def) =>
def.applicableTo.includes('gongfa') &&
def.listenerSpec?.scope === GameplayTags.SCOPE.OWNER_AS_TARGET,
);
expect(violations.map((d) => d.id)).toEqual([]);
});
it('skill 词缀禁止使用 attribute_modifier effectType', () => {
const violations = allDefs.filter(
(def) =>
def.applicableTo.includes('skill') &&
def.effectTemplate.type === 'attribute_modifier',
);
expect(violations.map((d) => d.id)).toEqual([]);
});
it('artifact 词缀禁止使用 ADD 百分比 attribute_modifier', () => {
const violations = allDefs.filter((def) => {
if (!def.applicableTo.includes('artifact')) {
return false;
}
if (def.effectTemplate.type !== 'attribute_modifier') {
return false;
}
const params = def.effectTemplate.params;
if ('modifiers' in params) {
return params.modifiers.some((modifier) => modifier.modType === ModifierType.ADD);
}
return params.modType === ModifierType.ADD;
});
expect(violations.map((d) => d.id)).toEqual([]);
});
it('artifact 词缀禁止使用 OWNER_AS_CASTER scope', () => {
const violations = allDefs.filter(
(def) =>
def.applicableTo.includes('artifact') &&
def.listenerSpec?.scope === GameplayTags.SCOPE.OWNER_AS_CASTER,
);
expect(violations.map((d) => d.id)).toEqual([]);
});
it('DEFAULT_AFFIX_REGISTRY 应包含所有三种产物类型的词缀', () => {
const hasArtifact = allDefs.some((d) => d.applicableTo.includes('artifact'));
const hasGongfa = allDefs.some((d) => d.applicableTo.includes('gongfa'));
const hasSkill = allDefs.some((d) => d.applicableTo.includes('skill'));
expect(hasArtifact).toBe(true);
expect(hasGongfa).toBe(true);
expect(hasSkill).toBe(true);
});
it('no commonAffixes remain (applicableTo 中不再有跨产物类型的词缀)', () => {
const crossProductAffixes = allDefs.filter(
(def) =>
def.applicableTo.includes('artifact') && def.applicableTo.includes('gongfa'),
);
expect(crossProductAffixes.map((d) => d.id)).toEqual([]);
});
it('池与产物类型强绑定: skill_* 池词缀仅适用于 skill', () => {
const violations = allDefs.filter(
(def) =>
def.category.startsWith('skill_') &&
def.applicableTo.some((t) => t !== 'skill'),
);
expect(violations.map((d) => d.id)).toEqual([]);
});
it('池与产物类型强绑定: gongfa_* 池词缀仅适用于 gongfa', () => {
const violations = allDefs.filter(
(def) =>
def.category.startsWith('gongfa_') &&
def.applicableTo.some((t) => t !== 'gongfa'),
);
expect(violations.map((d) => d.id)).toEqual([]);
});
it('池与产物类型强绑定: artifact_* 池词缀仅适用于 artifact', () => {
const violations = allDefs.filter(
(def) =>
def.category.startsWith('artifact_') &&
def.applicableTo.some((t) => t !== 'artifact'),
);
expect(violations.map((d) => d.id)).toEqual([]);
});
it('稀有池词缀的 rarity 必须为 rare 或 legendary', () => {
const rarePools = ['skill_rare', 'gongfa_secret', 'artifact_treasure'];
const violations = allDefs.filter(
(def) =>
rarePools.includes(def.category) &&
def.rarity !== 'rare' &&
def.rarity !== 'legendary',
);
expect(violations.map((d) => d.id)).toEqual([]);
});
// ===== Part 2: validateBoundary() 拦截测试 =====
describe('validateBoundary() 拦截违规词缀', () => {
it('拦截 pool-product binding 违规: skill_core 词缀不能 applicableTo gongfa', () => {
const registry = new AffixRegistry();
expect(() =>
registry.register([
makeAffix({
id: 'bad-pool-binding',
category: 'skill_core',
applicableTo: ['gongfa'],
}),
]),
).toThrow('pool-product binding violation');
});
it('拦截 pool-product binding 违规: artifact_panel 词缀不能 applicableTo skill', () => {
const registry = new AffixRegistry();
expect(() =>
registry.register([
makeAffix({
id: 'bad-pool-binding-2',
category: 'artifact_panel',
applicableTo: ['skill'],
effectTemplate: {
type: 'attribute_modifier',
params: { attrType: 'ATK' as any, modType: ModifierType.FIXED, value: 10 },
},
}),
]),
).toThrow('pool-product binding violation');
});
it('拦截稀有池 rarity 违规: skill_rare 不接受 common', () => {
const registry = new AffixRegistry();
expect(() =>
registry.register([
makeAffix({
id: 'bad-rarity',
category: 'skill_rare',
rarity: 'common',
applicableTo: ['skill'],
}),
]),
).toThrow('rare pool rarity violation');
});
it('拦截稀有池 rarity 违规: gongfa_secret 不接受 uncommon', () => {
const registry = new AffixRegistry();
expect(() =>
registry.register([
makeAffix({
id: 'bad-rarity-gongfa',
category: 'gongfa_secret',
rarity: 'uncommon',
applicableTo: ['gongfa'],
}),
]),
).toThrow('rare pool rarity violation');
});
it('拦截稀有池 rarity 违规: artifact_treasure 不接受 common', () => {
const registry = new AffixRegistry();
expect(() =>
registry.register([
makeAffix({
id: 'bad-rarity-artifact',
category: 'artifact_treasure',
rarity: 'common',
applicableTo: ['artifact'],
}),
]),
).toThrow('rare pool rarity violation');
});
it('稀有池允许 rare rarity', () => {
const registry = new AffixRegistry();
expect(() =>
registry.register([
makeAffix({
id: 'ok-rare',
category: 'skill_rare',
rarity: 'rare',
applicableTo: ['skill'],
}),
]),
).not.toThrow();
});
it('稀有池允许 legendary rarity', () => {
const registry = new AffixRegistry();
expect(() =>
registry.register([
makeAffix({
id: 'ok-legendary',
category: 'gongfa_secret',
rarity: 'legendary',
applicableTo: ['gongfa'],
}),
]),
).not.toThrow();
});
it('拦截 artifact OWNER_AS_CASTER scope', () => {
const registry = new AffixRegistry();
expect(() =>
registry.register([
makeAffix({
id: 'bad-artifact-scope',
category: 'artifact_defense',
rarity: 'common',
applicableTo: ['artifact'],
listenerSpec: {
scope: GameplayTags.SCOPE.OWNER_AS_CASTER as any,
trigger: 'ON_TURN_START' as any,
priority: 50,
},
}),
]),
).toThrow('artifact affix must not use OWNER_AS_CASTER scope');
});
it('拦截 gongfa OWNER_AS_TARGET scope', () => {
const registry = new AffixRegistry();
expect(() =>
registry.register([
makeAffix({
id: 'bad-gongfa-scope',
category: 'gongfa_school',
rarity: 'common',
applicableTo: ['gongfa'],
listenerSpec: {
scope: GameplayTags.SCOPE.OWNER_AS_TARGET as any,
trigger: 'ON_BEING_HIT' as any,
priority: 50,
},
}),
]),
).toThrow('gongfa affix must not use OWNER_AS_TARGET scope');
});
it('拦截 skill attribute_modifier effectType', () => {
const registry = new AffixRegistry();
expect(() =>
registry.register([
makeAffix({
id: 'bad-skill-attr-mod',
category: 'skill_core',
applicableTo: ['skill'],
effectTemplate: {
type: 'attribute_modifier',
params: { attrType: 'ATK' as any, modType: ModifierType.ADD, value: 10 },
},
}),
]),
).toThrow('skill affix must not use attribute_modifier effect type');
});
it('拦截 gongfa attribute_modifier FIXED modType', () => {
const registry = new AffixRegistry();
expect(() =>
registry.register([
makeAffix({
id: 'bad-gongfa-fixed',
category: 'gongfa_foundation',
applicableTo: ['gongfa'],
effectTemplate: {
type: 'attribute_modifier',
params: { attrType: 'ATK' as any, modType: ModifierType.FIXED, value: 100 },
},
}),
]),
).toThrow('gongfa affix must not use ModifierType.FIXED');
});
it('拦截 gongfa attribute_modifier FIXED modType (modifiers 数组形式)', () => {
const registry = new AffixRegistry();
expect(() =>
registry.register([
makeAffix({
id: 'bad-gongfa-fixed-array',
category: 'gongfa_foundation',
applicableTo: ['gongfa'],
effectTemplate: {
type: 'attribute_modifier',
params: {
modifiers: [
{ attrType: 'ATK' as any, modType: ModifierType.ADD, value: 10 },
{ attrType: 'DEF' as any, modType: ModifierType.FIXED, value: 50 },
],
},
},
}),
]),
).toThrow('gongfa affix must not use ModifierType.FIXED');
});
it('拦截 skill apply_buff 内嵌 OWNER_AS_CASTER listener 且 duration > 1', () => {
const registry = new AffixRegistry();
expect(() =>
registry.register([
makeAffix({
id: 'bad-skill-long-buff',
category: 'skill_variant',
applicableTo: ['skill'],
effectTemplate: {
type: 'apply_buff',
params: {
buffConfig: {
name: 'test',
duration: 3,
listeners: [
{
scope: GameplayTags.SCOPE.OWNER_AS_CASTER,
trigger: 'ON_TURN_START' as any,
effects: [],
},
],
} as any,
},
},
}),
]),
).toThrow('skill affix must not use apply_buff with OWNER_AS_CASTER listener and duration > 1');
});
it('skill apply_buff OWNER_AS_TARGET listener + duration > 1 允许 (DOT/debuff)', () => {
const registry = new AffixRegistry();
expect(() =>
registry.register([
makeAffix({
id: 'ok-skill-dot-buff',
category: 'skill_variant',
applicableTo: ['skill'],
effectTemplate: {
type: 'apply_buff',
params: {
buffConfig: {
name: 'burn',
duration: 3,
listeners: [
{
scope: GameplayTags.SCOPE.OWNER_AS_TARGET,
trigger: 'ON_TURN_START' as any,
effects: [],
},
],
} as any,
},
},
}),
]),
).not.toThrow();
});
it('skill apply_buff duration=1 内嵌 listener 允许通过', () => {
const registry = new AffixRegistry();
expect(() =>
registry.register([
makeAffix({
id: 'ok-skill-short-buff',
category: 'skill_variant',
applicableTo: ['skill'],
effectTemplate: {
type: 'apply_buff',
params: {
buffConfig: {
name: 'test',
duration: 1,
listeners: [
{
scope: GameplayTags.SCOPE.OWNER_AS_CASTER,
trigger: 'ON_TURN_START' as any,
effects: [],
},
],
} as any,
},
},
}),
]),
).not.toThrow();
});
});
});
+7 -7
View File
@@ -1,5 +1,5 @@
// eslint-disable-next-line @typescript-eslint/no-require-imports
const nextJest = require('next/jest');
import type { Config } from 'jest';
import nextJest from 'next/jest.js';
const createJestConfig = nextJest({
// Provide the path to your Next.js app to load next.config.js and .env files in your test environment
@@ -7,10 +7,10 @@ const createJestConfig = nextJest({
});
// Add any custom config to be passed to Jest
const customJestConfig = {
setupFilesAfterEnv: [],
testEnvironment: 'node',
testTimeout: 10000, // 全局默认 10 秒
const config: Config = {
coverageProvider: 'v8',
// Add more setup options before each test is run
// setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/$1',
'^d3-(.+)$': '<rootDir>/node_modules/d3-$1/dist/d3-$1.js',
@@ -18,4 +18,4 @@ const customJestConfig = {
};
// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
module.exports = createJestConfig(customJestConfig);
export default createJestConfig(config);
+127
View File
@@ -54,6 +54,7 @@
"supertest": "^7.2.2",
"tailwindcss": "^4",
"ts-jest": "^29.4.6",
"ts-node": "^10.9.2",
"typescript": "^5",
"wrangler": "^4.72.0"
}
@@ -6294,6 +6295,34 @@
"tailwindcss": "4.2.1"
}
},
"node_modules/@tsconfig/node10": {
"version": "1.0.12",
"resolved": "https://registry.npmmirror.com/@tsconfig/node10/-/node10-1.0.12.tgz",
"integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@tsconfig/node12": {
"version": "1.0.11",
"resolved": "https://registry.npmmirror.com/@tsconfig/node12/-/node12-1.0.11.tgz",
"integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
"dev": true,
"license": "MIT"
},
"node_modules/@tsconfig/node14": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/@tsconfig/node14/-/node14-1.0.3.tgz",
"integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
"dev": true,
"license": "MIT"
},
"node_modules/@tsconfig/node16": {
"version": "1.0.4",
"resolved": "https://registry.npmmirror.com/@tsconfig/node16/-/node16-1.0.4.tgz",
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
"dev": true,
"license": "MIT"
},
"node_modules/@tsconfig/node18": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/@tsconfig/node18/-/node18-1.0.3.tgz",
@@ -7163,6 +7192,19 @@
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
"node_modules/acorn-walk": {
"version": "8.3.5",
"resolved": "https://registry.npmmirror.com/acorn-walk/-/acorn-walk-8.3.5.tgz",
"integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==",
"dev": true,
"license": "MIT",
"dependencies": {
"acorn": "^8.11.0"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/agentkeepalive": {
"version": "4.6.0",
"resolved": "https://registry.npmmirror.com/agentkeepalive/-/agentkeepalive-4.6.0.tgz",
@@ -7284,6 +7326,13 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/arg": {
"version": "4.1.3",
"resolved": "https://registry.npmmirror.com/arg/-/arg-4.1.3.tgz",
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
"dev": true,
"license": "MIT"
},
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz",
@@ -8209,6 +8258,13 @@
"dev": true,
"license": "MIT"
},
"node_modules/create-require": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/create-require/-/create-require-1.1.1.tgz",
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"dev": true,
"license": "MIT"
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -8467,6 +8523,16 @@
"wrappy": "1"
}
},
"node_modules/diff": {
"version": "4.0.4",
"resolved": "https://registry.npmmirror.com/diff/-/diff-4.0.4.tgz",
"integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.3.1"
}
},
"node_modules/doctrine": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/doctrine/-/doctrine-2.1.0.tgz",
@@ -15873,6 +15939,50 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/ts-node": {
"version": "10.9.2",
"resolved": "https://registry.npmmirror.com/ts-node/-/ts-node-10.9.2.tgz",
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@cspotcode/source-map-support": "^0.8.0",
"@tsconfig/node10": "^1.0.7",
"@tsconfig/node12": "^1.0.7",
"@tsconfig/node14": "^1.0.0",
"@tsconfig/node16": "^1.0.2",
"acorn": "^8.4.1",
"acorn-walk": "^8.1.1",
"arg": "^4.1.0",
"create-require": "^1.1.0",
"diff": "^4.0.1",
"make-error": "^1.1.1",
"v8-compile-cache-lib": "^3.0.1",
"yn": "3.1.1"
},
"bin": {
"ts-node": "dist/bin.js",
"ts-node-cwd": "dist/bin-cwd.js",
"ts-node-esm": "dist/bin-esm.js",
"ts-node-script": "dist/bin-script.js",
"ts-node-transpile-only": "dist/bin-transpile.js",
"ts-script": "dist/bin-script-deprecated.js"
},
"peerDependencies": {
"@swc/core": ">=1.2.50",
"@swc/wasm": ">=1.2.50",
"@types/node": "*",
"typescript": ">=2.7"
},
"peerDependenciesMeta": {
"@swc/core": {
"optional": true
},
"@swc/wasm": {
"optional": true
}
}
},
"node_modules/ts-tqdm": {
"version": "0.8.6",
"resolved": "https://registry.npmmirror.com/ts-tqdm/-/ts-tqdm-0.8.6.tgz",
@@ -16254,6 +16364,13 @@
"uuid": "dist-node/bin/uuid"
}
},
"node_modules/v8-compile-cache-lib": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
"dev": true,
"license": "MIT"
},
"node_modules/v8-to-istanbul": {
"version": "9.3.0",
"resolved": "https://registry.npmmirror.com/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
@@ -17163,6 +17280,16 @@
"node": "^20.19.0 || ^22.12.0 || >=23"
}
},
"node_modules/yn": {
"version": "3.1.1",
"resolved": "https://registry.npmmirror.com/yn/-/yn-3.1.1.tgz",
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz",
+1
View File
@@ -62,6 +62,7 @@
"supertest": "^7.2.2",
"tailwindcss": "^4",
"ts-jest": "^29.4.6",
"ts-node": "^10.9.2",
"typescript": "^5",
"wrangler": "^4.72.0"
},
+1
View File
@@ -3,6 +3,7 @@
"target": "ES2024",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"types": ["jest", "node"],
"skipLibCheck": true,
"strict": true,
"noEmit": true,