Compare commits
3
Commits
084e090ba3
...
f6c44ab2df
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f6c44ab2df | ||
|
|
2ee5ba5c4c | ||
|
|
d587749b50 |
No files matched your search
+8
-8
@@ -35,11 +35,11 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@base-ui/react": "^1.6.0",
|
||||
"@base-ui/react": "^1.7.0",
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@hookform/resolvers": "^5.5.7",
|
||||
"@hookform/resolvers": "^5.7.1",
|
||||
"@tanstack/react-virtual": "^3.14.9",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
@@ -48,7 +48,7 @@
|
||||
"drizzle-orm": "^0.45.2",
|
||||
"hash-wasm": "^4.12.0",
|
||||
"ioredis": "^5.11.1",
|
||||
"isomorphic-dompurify": "^3.20.0",
|
||||
"isomorphic-dompurify": "^3.21.0",
|
||||
"jpeg-js": "^0.4.4",
|
||||
"json5": "^2.2.3",
|
||||
"jszip": "^3.10.1",
|
||||
@@ -58,14 +58,14 @@
|
||||
"motion": "^12.43.0",
|
||||
"music-metadata": "^11.14.0",
|
||||
"mysql2": "^3.23.2",
|
||||
"next": "^16.2.12",
|
||||
"next": "^16.3.0",
|
||||
"next-auth": "5.0.0-beta.32",
|
||||
"next-intl": "^4.13.4",
|
||||
"otplib": "^13.4.1",
|
||||
"pino": "^10.3.1",
|
||||
"react": "^19.2.8",
|
||||
"react-dom": "^19.2.8",
|
||||
"react-hook-form": "^7.83.0",
|
||||
"react-hook-form": "^7.84.0",
|
||||
"resend": "^6.18.1",
|
||||
"server-only": "^0.0.1",
|
||||
"sharp": "^0.35.3",
|
||||
@@ -76,7 +76,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "2.5.6",
|
||||
"@next/bundle-analyzer": "^16.2.12",
|
||||
"@next/bundle-analyzer": "^16.3.0",
|
||||
"@tailwindcss/forms": "^0.5.11",
|
||||
"@tailwindcss/postcss": "^4.3.3",
|
||||
"@tailwindcss/typography": "^0.5.20",
|
||||
@@ -88,12 +88,12 @@
|
||||
"dotenv": "^17.4.2",
|
||||
"drizzle-kit": "^0.31.10",
|
||||
"husky": "^9.1.7",
|
||||
"knip": "^6.30.0",
|
||||
"knip": "^6.31.0",
|
||||
"lint-staged": "^17.3.0",
|
||||
"pino-pretty": "^13.1.3",
|
||||
"postcss": "^8.5.25",
|
||||
"tailwindcss": "^4.3.3",
|
||||
"tsx": "^4.23.1",
|
||||
"tsx": "^4.23.5",
|
||||
"typescript": "^7.0.2",
|
||||
"vite": "8.2.0",
|
||||
"vitest": "4.1.10"
|
||||
|
||||
Generated
+100
-100
@@ -21,8 +21,8 @@ importers:
|
||||
.:
|
||||
dependencies:
|
||||
'@base-ui/react':
|
||||
specifier: ^1.6.0
|
||||
version: 1.6.0(@types/[email protected])([email protected]([email protected]))([email protected])
|
||||
specifier: ^1.7.0
|
||||
version: 1.7.0(@types/[email protected])([email protected]([email protected]))([email protected])
|
||||
'@dnd-kit/core':
|
||||
specifier: ^6.3.1
|
||||
version: 6.3.1([email protected]([email protected]))([email protected])
|
||||
@@ -33,8 +33,8 @@ importers:
|
||||
specifier: ^3.2.2
|
||||
version: 3.2.2([email protected])
|
||||
'@hookform/resolvers':
|
||||
specifier: ^5.5.7
|
||||
version: 5.5.7(@standard-schema/[email protected])([email protected]([email protected]))([email protected])([email protected])([email protected]3.0([email protected]))([email protected]([email protected]))([email protected])
|
||||
specifier: ^5.7.1
|
||||
version: 5.7.1(@standard-schema/[email protected])([email protected]([email protected]))([email protected])([email protected])([email protected]4.0([email protected]))([email protected]([email protected]))([email protected])
|
||||
'@tanstack/react-virtual':
|
||||
specifier: ^3.14.9
|
||||
version: 3.14.9([email protected]([email protected]))([email protected])
|
||||
@@ -60,8 +60,8 @@ importers:
|
||||
specifier: ^5.11.1
|
||||
version: 5.11.1
|
||||
isomorphic-dompurify:
|
||||
specifier: ^3.20.0
|
||||
version: 3.20.0(@noble/[email protected])
|
||||
specifier: ^3.21.0
|
||||
version: 3.21.0(@noble/[email protected])
|
||||
jpeg-js:
|
||||
specifier: ^0.4.4
|
||||
version: 0.4.4
|
||||
@@ -90,14 +90,14 @@ importers:
|
||||
specifier: ^3.23.2
|
||||
version: 3.23.2(@types/[email protected])
|
||||
next:
|
||||
specifier: ^16.2.12
|
||||
version: 16.2.12(@opentelemetry/[email protected])(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])
|
||||
specifier: ^16.3.0
|
||||
version: 16.3.0(@opentelemetry/[email protected])(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])
|
||||
next-auth:
|
||||
specifier: 5.0.0-beta.32
|
||||
version: 5.0.0-beta.32(next@16.2.12(@opentelemetry/[email protected])(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected]))([email protected])
|
||||
version: 5.0.0-beta.32(next@16.3.0(@opentelemetry/[email protected])(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected]))([email protected])
|
||||
next-intl:
|
||||
specifier: ^4.13.4
|
||||
version: 4.13.4(next@16.2.12(@opentelemetry/[email protected])(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected]))([email protected])([email protected])
|
||||
version: 4.13.4(next@16.3.0(@opentelemetry/[email protected])(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected]))([email protected])([email protected])
|
||||
otplib:
|
||||
specifier: ^13.4.1
|
||||
version: 13.4.1
|
||||
@@ -111,8 +111,8 @@ importers:
|
||||
specifier: ^19.2.8
|
||||
version: 19.2.8([email protected])
|
||||
react-hook-form:
|
||||
specifier: ^7.83.0
|
||||
version: 7.83.0([email protected])
|
||||
specifier: ^7.84.0
|
||||
version: 7.84.0([email protected])
|
||||
resend:
|
||||
specifier: ^6.18.1
|
||||
version: 6.18.1
|
||||
@@ -139,8 +139,8 @@ importers:
|
||||
specifier: 2.5.6
|
||||
version: 2.5.6
|
||||
'@next/bundle-analyzer':
|
||||
specifier: ^16.2.12
|
||||
version: 16.2.12
|
||||
specifier: ^16.3.0
|
||||
version: 16.3.0
|
||||
'@tailwindcss/forms':
|
||||
specifier: ^0.5.11
|
||||
version: 0.5.11([email protected])
|
||||
@@ -175,8 +175,8 @@ importers:
|
||||
specifier: ^9.1.7
|
||||
version: 9.1.7
|
||||
knip:
|
||||
specifier: ^6.30.0
|
||||
version: 6.30.0
|
||||
specifier: ^6.31.0
|
||||
version: 6.31.0
|
||||
lint-staged:
|
||||
specifier: ^17.3.0
|
||||
version: 17.3.0
|
||||
@@ -190,17 +190,17 @@ importers:
|
||||
specifier: ^4.3.3
|
||||
version: 4.3.3
|
||||
tsx:
|
||||
specifier: ^4.23.1
|
||||
version: 4.23.1
|
||||
specifier: ^4.23.5
|
||||
version: 4.23.5
|
||||
typescript:
|
||||
specifier: ^7.0.2
|
||||
version: 7.0.2
|
||||
vite:
|
||||
specifier: 8.2.0
|
||||
version: 8.2.0(@types/[email protected])([email protected])([email protected])([email protected])([email protected].1)([email protected])
|
||||
version: 8.2.0(@types/[email protected])([email protected])([email protected])([email protected])([email protected].5)([email protected])
|
||||
vitest:
|
||||
specifier: 4.1.10
|
||||
version: 4.1.10(@opentelemetry/[email protected])(@types/[email protected])(@vitest/[email protected])([email protected](@noble/[email protected]))([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected].1)([email protected]))
|
||||
version: 4.1.10(@opentelemetry/[email protected])(@types/[email protected])(@vitest/[email protected])([email protected](@noble/[email protected]))([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected].5)([email protected]))
|
||||
|
||||
packages:
|
||||
|
||||
@@ -251,8 +251,8 @@ packages:
|
||||
resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@base-ui/react@1.6.0':
|
||||
resolution: {integrity: sha512-/jzjTWJYXhRFO45Bev9lc3cHbmjzCMpUqbMZ2AgKy/z25mY9B6shGSNcXcjQar9n5doM0KYW1W8fcFv2jZBuMw==}
|
||||
'@base-ui/react@1.7.0':
|
||||
resolution: {integrity: sha512-j+8QjX44C32jrXD/qyEAGpFr70FRpGL2CY61mQd9nBPWN737CK0xxD1ceJ055rW4RtdvFDT1e7otzdlfxvsYug==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
'@date-fns/tz': ^1.2.0
|
||||
@@ -268,8 +268,8 @@ packages:
|
||||
date-fns:
|
||||
optional: true
|
||||
|
||||
'@base-ui/[email protected].1':
|
||||
resolution: {integrity: sha512-gFFiltORVmW/N6IILTGxizP3PBpVpysqML1ALY5Vk0mH+7faVkCknOU31goYHN5Aoek2dkjxva1XOD2Ce9WuIg==}
|
||||
'@base-ui/[email protected].2':
|
||||
resolution: {integrity: sha512-oWy1aq/I2GmYjpl4PhEAhzflF8VPGKgZeq0xAWTbfD5KBWyxcN0ZP2+WHSUm/5Z6lVMBDLReLcoXwSYoRc/zNQ==}
|
||||
peerDependencies:
|
||||
'@types/react': ^17 || ^18 || ^19
|
||||
react: ^17 || ^18 || ^19
|
||||
@@ -934,18 +934,18 @@ packages:
|
||||
'@formatjs/[email protected]':
|
||||
resolution: {integrity: sha512-kHEAFOkeJSPNi7c5PaKaRjxcBrJwzzt81ifUu+8uve1EDW/VJl83KsxmqgqNZLzcFEhSliZGvx3+pk/RH0IOmg==}
|
||||
|
||||
'@hookform/resolvers@5.5.7':
|
||||
resolution: {integrity: sha512-CyPCYV8/KlXfEXLWj8HHHhVsR/IZ6Ckm3b/a4fWtO/lRnRK1huqncb8LlAWrmpPRsse5glF5aVuDRMHEr3UGag==}
|
||||
'@hookform/resolvers@5.7.1':
|
||||
resolution: {integrity: sha512-8wS/P4UDr5sQDe4nFaV51TVyfDPrWgNIXweqG0Bs9Z5LSuzKLb+RQNPvkN2oHM5SRrJyWrVH/F+LOUcFjUyvwQ==}
|
||||
peerDependencies:
|
||||
'@sinclair/typebox': '>=0.25.24'
|
||||
'@standard-schema/spec': ^1.0.0
|
||||
'@typeschema/main': '>=0.13.7'
|
||||
'@vinejs/vine': ^2.0.0 || ^3.0.0
|
||||
'@vinejs/vine': ^2.0.0 || ^3.0.0 || ^4.0.0
|
||||
ajv: ^8.12.0
|
||||
ajv-errors: ^3.0.0
|
||||
ajv-formats: ^2.1.1
|
||||
arktype: ^2.0.0
|
||||
ata-validator: ^0.7.0
|
||||
ata-validator: ^1.2.0
|
||||
class-transformer: '>=0.4.0'
|
||||
class-validator: '>=0.12.0'
|
||||
computed-types: ^1.0.0
|
||||
@@ -1203,60 +1203,60 @@ packages:
|
||||
'@emnapi/core': ^2.0.0-alpha.3
|
||||
'@emnapi/runtime': ^2.0.0-alpha.3
|
||||
|
||||
'@next/bundle-analyzer@16.2.12':
|
||||
resolution: {integrity: sha512-0dYhmCYsTMFYUFaoEUx+VKw/oYP6b3XiGgq47EujXTE2UGgwVWAaur2tbko2pxfUka3WRZ9YWxa3PlSv3luWNQ==}
|
||||
'@next/bundle-analyzer@16.3.0':
|
||||
resolution: {integrity: sha512-o8OiXKIATcSC6kHm5BIEmaDEDTFUDhpy9POQIbzAQTlb8NWwFhEQheTLFSbyQXxH2ywhjZ/e8EUlbOjj2k22Yg==}
|
||||
|
||||
'@next/env@16.2.12':
|
||||
resolution: {integrity: sha512-d0Z5Bc13Fa4nR8pFAKx2jay2yhJM16vlfHbTzYnUQAxlNb6B6lmn4hjt69lYNt4kRtyYP6gEM49lPRHNbIyneg==}
|
||||
'@next/env@16.3.0':
|
||||
resolution: {integrity: sha512-o9r1S0BNiNreHP9Vs+Qnqd9kviDkJh8xIACY7UFZSmiGbbQRzPBBosvHzAU4TULHOIuOj/18RSsyz2qrREmIFw==}
|
||||
|
||||
'@next/swc-darwin-arm64@16.2.12':
|
||||
resolution: {integrity: sha512-0W1R0teHWJrqKX0FH20IzzIWAOuGtBxPGuObrxy1lE8hQvCFj49KE8a3WUg0D7sq6rn6zkM4c7YGUnhudBS6oA==}
|
||||
'@next/swc-darwin-arm64@16.3.0':
|
||||
resolution: {integrity: sha512-55hpqq18bEVAlxedlTt3tFqZmKg2nUXT1kn1G/BGEy0R13h3LwtwHPVzzjG6P4LLeOHE32PFDQUVaJEWvBEZBw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@next/swc-darwin-x64@16.2.12':
|
||||
resolution: {integrity: sha512-Hy5Ls099+aFUmOLmIgPfLqNi6iCwhL3uQCssz5rWk+5Nkc6TUKCE83DY5BbNylfm3+mfwcSFnLRfrZDJhVxdtw==}
|
||||
'@next/swc-darwin-x64@16.3.0':
|
||||
resolution: {integrity: sha512-SOi96kSaF5T+0wW4koiM1bWzSPwjzTesC1p3df+FjdOi5LIQkBK/blxh7HdoKnNuI4PURF1OO7TZqtfnbWDSgw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@next/swc-linux-arm64-gnu@16.2.12':
|
||||
resolution: {integrity: sha512-+YqU2h1cQkHsGfvjAsrSmst8UIFBibBGm5x3Xgel8NLMiDQtNOM4sM2GOEMvG5YiOBNeN/Ykk8cQC2S0Xrqljg==}
|
||||
'@next/swc-linux-arm64-gnu@16.3.0':
|
||||
resolution: {integrity: sha512-P0gZAoPMF4dyTRzhmkV4PrqVzSOB6t4mC1oI3c4dqijJ+OVEVx5clIXAKR4/uQpsqw2KKM/0D5tVumcR2r5blg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@next/swc-linux-arm64-musl@16.2.12':
|
||||
resolution: {integrity: sha512-0qjhiYBaKAqF63LA1ZWAAnKTzFUguAaZiRa5etMLGGPj/B6uEVjtIZldIzFEp3wHlB0koK6aTzqPtSdplTCjoA==}
|
||||
'@next/swc-linux-arm64-musl@16.3.0':
|
||||
resolution: {integrity: sha512-tXXGKJw0m37O0eKJARVTX/TheKPhz0QFVtVVZXmOig+9YKLQOSP6hvf2pxv5DO7CLEJyTHx3Pg043CDQkv1G4Q==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@next/swc-linux-x64-gnu@16.2.12':
|
||||
resolution: {integrity: sha512-7A3q26W+h7gnA15uqBToNuDqBEFZZcqh0mW2mn4AJh/G5pdg2RVE3n4slzLEliASZFG3NmsbEzng/x2Sh09mBg==}
|
||||
'@next/swc-linux-x64-gnu@16.3.0':
|
||||
resolution: {integrity: sha512-pjGxK5EY7yWml78ALejFkWmgHsU7wbFQrISiugpH6FbUJhgEvw3xFZ/EBAtLl7QtL0WdQKiG9eWJ3mOKGTukHw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@next/swc-linux-x64-musl@16.2.12':
|
||||
resolution: {integrity: sha512-qSjL/uppm+cbh21s72Ss8gkiOhQ4dExWHNGOWy6eZV7STj5WsKehgxT61beSsOj+YYQuTplL376lOCdMQU5T8w==}
|
||||
'@next/swc-linux-x64-musl@16.3.0':
|
||||
resolution: {integrity: sha512-sjo++Xx+lomlPs3HRsHWhVDyGG6ms1kGW5EtHLERdII8AyG1i+f6aq68xHREO6AEMlhjTNEWBSmfJfqm9orf7g==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@next/swc-win32-arm64-msvc@16.2.12':
|
||||
resolution: {integrity: sha512-X6hzsOUJac/e7AWSbn9gQ9nzHld1xWP5iyjHpYWvud8pufB679O1xg4JDyKr8Xd69Jvd+kM2Der6uftiZCmjYA==}
|
||||
'@next/swc-win32-arm64-msvc@16.3.0':
|
||||
resolution: {integrity: sha512-C5JSgiO54wURdaxdEUIXqkz04uMqC9UmPX1gtDrV/5Tf1UowdWYI8uA5hfFbPolTlp0q4KZ60xlHePNibf0VIw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@next/swc-win32-x64-msvc@16.2.12':
|
||||
resolution: {integrity: sha512-F6fakeHuFTLOPt0bslQJdf+xtT+WIP9DVn/m4y1w1mRnVPyh3D/cNvzlRkxM444xfm+IvvYNSOrKiA2CDJ0Uxw==}
|
||||
'@next/swc-win32-x64-msvc@16.3.0':
|
||||
resolution: {integrity: sha512-fDOggsweNb5SSw0ZKVk6U+gxSyGFFlIBY/LBc1r8GUj4u/6t6oArL+Pmkg0MBnsgR+KkdsURilVH4F3GXUGepA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
@@ -3072,8 +3072,8 @@ packages:
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
||||
|
||||
[email protected]0.0:
|
||||
resolution: {integrity: sha512-WH3UcdmOrqLJF6qHaGnGINNMRwkQXVtvxlhEdygP3FdbuPjfFEsvjaqA5E16uYlNvAT5uGqK4MPvJBvtTtriEA==}
|
||||
[email protected]1.0:
|
||||
resolution: {integrity: sha512-Bg5mqtMnMdBBwz7MjvH3oAuLOA/j0xtawVkcAZsOpVhOsfaLYwnAvhEvz94f8585Q6LCnnNr2XNApbXlB0J3GQ==}
|
||||
engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0}
|
||||
|
||||
[email protected]:
|
||||
@@ -3125,8 +3125,8 @@ packages:
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==}
|
||||
|
||||
[email protected]0.0:
|
||||
resolution: {integrity: sha512-C5MsWT17QqMLtrvXIH4N9czRpt/T1GzwfkR/10qW3pdCHFUCoGF+Eeyq8FCKIkv56Gm5zSDv8fq3fs4whktehA==}
|
||||
[email protected]1.0:
|
||||
resolution: {integrity: sha512-NbeIEmUS2VUMjAkbiSNOKPJeV9wpCsr0660sUyKyMQbk4Iom0++nTLInVp4MJ+LfR4kORnw67bDi5tvO7YLnzA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
hasBin: true
|
||||
|
||||
@@ -3430,8 +3430,8 @@ packages:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
next@16.2.12:
|
||||
resolution: {integrity: sha512-iD59eYQWmbFcEbX7v/acG5DRym9iw1DdaPoD0WTA920naWsE25wShzJW4+UvAs8MK9EC2kBfIH6vtto1H1PHGw==}
|
||||
next@16.3.0:
|
||||
resolution: {integrity: sha512-NEdGOzH+08eTXMUp9UYkA99Nhi5N6Thrhc1jgFOQgfgnGK/dA2hRwBpXep+exdFQrnwlRf/3Wixyp8lLBUpE2A==}
|
||||
engines: {node: '>=20.9.0'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
@@ -3604,8 +3604,8 @@ packages:
|
||||
peerDependencies:
|
||||
react: ^19.2.8
|
||||
|
||||
[email protected]3.0:
|
||||
resolution: {integrity: sha512-AXt8cMCmx5a7u4uvpb2uRFVrWQhllI4pV+LSykxIac/hjt44TnQkmX9BKuQi2i+LDC62esmiLpilkav+kjVf/A==}
|
||||
[email protected]4.0:
|
||||
resolution: {integrity: sha512-+hWvQP6GLco56mDwrbU4XnHix8t1z90ltZsDIrREl+jnQFQxYLX8oAzqe/Xn8nHpmoXTY5M6oEXrAhbP1qevNQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17 || ^18 || ^19
|
||||
@@ -3924,8 +3924,8 @@ packages:
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
||||
|
||||
[email protected].1:
|
||||
resolution: {integrity: sha512-GQHnkIfxyx1wYCOS/wonik5MVRZU9hi1TEZmzGZSCJB1y9YgoZ8H6itNE/u4suE+yLmOzuE4E5S4TZ/ZX2wcWQ==}
|
||||
[email protected].5:
|
||||
resolution: {integrity: sha512-rw55FUaqOoI7RvlQwLbhO4nSDApnQ4/CykPuiQ/EPvtrX3WA9Ig55jIt9VvbBJbzJuj12ueRu4PMZ2SxPVbihg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
hasBin: true
|
||||
|
||||
@@ -4191,10 +4191,10 @@ snapshots:
|
||||
'@babel/helper-string-parser': 7.29.7
|
||||
'@babel/helper-validator-identifier': 7.29.7
|
||||
|
||||
'@base-ui/react@1.6.0(@types/[email protected])([email protected]([email protected]))([email protected])':
|
||||
'@base-ui/react@1.7.0(@types/[email protected])([email protected]([email protected]))([email protected])':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.7
|
||||
'@base-ui/utils': 0.3.1(@types/[email protected])([email protected]([email protected]))([email protected])
|
||||
'@base-ui/utils': 0.3.2(@types/[email protected])([email protected]([email protected]))([email protected])
|
||||
'@floating-ui/react-dom': 2.1.9([email protected]([email protected]))([email protected])
|
||||
'@floating-ui/utils': 0.2.12
|
||||
react: 19.2.8
|
||||
@@ -4203,7 +4203,7 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.18
|
||||
|
||||
'@base-ui/[email protected].1(@types/[email protected])([email protected]([email protected]))([email protected])':
|
||||
'@base-ui/[email protected].2(@types/[email protected])([email protected]([email protected]))([email protected])':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.7
|
||||
'@floating-ui/utils': 0.2.12
|
||||
@@ -4625,10 +4625,10 @@ snapshots:
|
||||
dependencies:
|
||||
'@formatjs/fast-memoize': 3.1.7
|
||||
|
||||
'@hookform/resolvers@5.5.7(@standard-schema/[email protected])([email protected]([email protected]))([email protected])([email protected])([email protected]3.0([email protected]))([email protected]([email protected]))([email protected])':
|
||||
'@hookform/resolvers@5.7.1(@standard-schema/[email protected])([email protected]([email protected]))([email protected])([email protected])([email protected]4.0([email protected]))([email protected]([email protected]))([email protected])':
|
||||
dependencies:
|
||||
'@standard-schema/utils': 0.3.0
|
||||
react-hook-form: 7.83.0([email protected])
|
||||
react-hook-form: 7.84.0([email protected])
|
||||
optionalDependencies:
|
||||
'@standard-schema/spec': 1.1.0
|
||||
ajv: 8.20.0
|
||||
@@ -4784,37 +4784,37 @@ snapshots:
|
||||
'@tybys/wasm-util': 0.10.3
|
||||
optional: true
|
||||
|
||||
'@next/bundle-analyzer@16.2.12':
|
||||
'@next/bundle-analyzer@16.3.0':
|
||||
dependencies:
|
||||
webpack-bundle-analyzer: 4.10.1
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- utf-8-validate
|
||||
|
||||
'@next/env@16.2.12': {}
|
||||
'@next/env@16.3.0': {}
|
||||
|
||||
'@next/swc-darwin-arm64@16.2.12':
|
||||
'@next/swc-darwin-arm64@16.3.0':
|
||||
optional: true
|
||||
|
||||
'@next/swc-darwin-x64@16.2.12':
|
||||
'@next/swc-darwin-x64@16.3.0':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-arm64-gnu@16.2.12':
|
||||
'@next/swc-linux-arm64-gnu@16.3.0':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-arm64-musl@16.2.12':
|
||||
'@next/swc-linux-arm64-musl@16.3.0':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-x64-gnu@16.2.12':
|
||||
'@next/swc-linux-x64-gnu@16.3.0':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-x64-musl@16.2.12':
|
||||
'@next/swc-linux-x64-musl@16.3.0':
|
||||
optional: true
|
||||
|
||||
'@next/swc-win32-arm64-msvc@16.2.12':
|
||||
'@next/swc-win32-arm64-msvc@16.3.0':
|
||||
optional: true
|
||||
|
||||
'@next/swc-win32-x64-msvc@16.2.12':
|
||||
'@next/swc-win32-x64-msvc@16.3.0':
|
||||
optional: true
|
||||
|
||||
'@noble/[email protected]': {}
|
||||
@@ -5806,7 +5806,7 @@ snapshots:
|
||||
obug: 2.1.4
|
||||
std-env: 4.2.0
|
||||
tinyrainbow: 3.1.1
|
||||
vitest: 4.1.10(@opentelemetry/[email protected])(@types/[email protected])(@vitest/[email protected])([email protected](@noble/[email protected]))([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected].1)([email protected]))
|
||||
vitest: 4.1.10(@opentelemetry/[email protected])(@types/[email protected])(@vitest/[email protected])([email protected](@noble/[email protected]))([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected].5)([email protected]))
|
||||
|
||||
'@vitest/[email protected]':
|
||||
dependencies:
|
||||
@@ -5817,13 +5817,13 @@ snapshots:
|
||||
chai: 6.2.2
|
||||
tinyrainbow: 3.1.1
|
||||
|
||||
'@vitest/[email protected]([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected].1)([email protected]))':
|
||||
'@vitest/[email protected]([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected].5)([email protected]))':
|
||||
dependencies:
|
||||
'@vitest/spy': 4.1.10
|
||||
estree-walker: 3.0.3
|
||||
magic-string: 0.30.21
|
||||
optionalDependencies:
|
||||
vite: 8.2.0(@types/[email protected])([email protected])([email protected])([email protected])([email protected].1)([email protected])
|
||||
vite: 8.2.0(@types/[email protected])([email protected])([email protected])([email protected])([email protected].5)([email protected])
|
||||
|
||||
'@vitest/[email protected]':
|
||||
dependencies:
|
||||
@@ -6091,7 +6091,7 @@ snapshots:
|
||||
'@drizzle-team/brocli': 0.10.2
|
||||
'@esbuild-kit/esm-loader': 2.6.5
|
||||
esbuild: 0.25.12
|
||||
tsx: 4.23.1
|
||||
tsx: 4.23.5
|
||||
|
||||
[email protected](@electric-sql/[email protected])(@opentelemetry/[email protected])(@prisma/[email protected]([email protected](@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]))([email protected](@types/[email protected]))([email protected])([email protected](@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected])):
|
||||
optionalDependencies:
|
||||
@@ -6395,7 +6395,7 @@ snapshots:
|
||||
[email protected]:
|
||||
optional: true
|
||||
|
||||
[email protected]0.0(@noble/[email protected]):
|
||||
[email protected]1.0(@noble/[email protected]):
|
||||
dependencies:
|
||||
dompurify: 3.4.12
|
||||
jsdom: 30.0.1(@noble/[email protected])
|
||||
@@ -6464,7 +6464,7 @@ snapshots:
|
||||
readable-stream: 2.3.8
|
||||
setimmediate: 1.0.5
|
||||
|
||||
[email protected]0.0:
|
||||
[email protected]1.0:
|
||||
dependencies:
|
||||
fdir: 6.5.0([email protected])
|
||||
formatly: 0.3.0
|
||||
@@ -6697,22 +6697,22 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected](next@16.2.12(@opentelemetry/[email protected])(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected]))([email protected]):
|
||||
[email protected](next@16.3.0(@opentelemetry/[email protected])(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected]))([email protected]):
|
||||
dependencies:
|
||||
'@auth/core': 0.41.3
|
||||
next: 16.2.12(@opentelemetry/[email protected])(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])
|
||||
next: 16.3.0(@opentelemetry/[email protected])(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])
|
||||
react: 19.2.8
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected](next@16.2.12(@opentelemetry/[email protected])(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected]))([email protected])([email protected]):
|
||||
[email protected](next@16.3.0(@opentelemetry/[email protected])(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected]))([email protected])([email protected]):
|
||||
dependencies:
|
||||
'@formatjs/intl-localematcher': 0.8.13
|
||||
'@parcel/watcher': 2.6.0
|
||||
'@swc/core': 1.15.46
|
||||
icu-minify: 4.13.4
|
||||
negotiator: 1.0.0
|
||||
next: 16.2.12(@opentelemetry/[email protected])(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])
|
||||
next: 16.3.0(@opentelemetry/[email protected])(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])
|
||||
next-intl-swc-plugin-extractor: 4.13.4
|
||||
po-parser: 2.1.1
|
||||
react: 19.2.8
|
||||
@@ -6722,9 +6722,9 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- '@swc/helpers'
|
||||
|
||||
next@16.2.12(@opentelemetry/[email protected])(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected]):
|
||||
next@16.3.0(@opentelemetry/[email protected])(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected]):
|
||||
dependencies:
|
||||
'@next/env': 16.2.12
|
||||
'@next/env': 16.3.0
|
||||
'@swc/helpers': 0.5.15
|
||||
baseline-browser-mapping: 2.11.6
|
||||
caniuse-lite: 1.0.30001806
|
||||
@@ -6733,14 +6733,14 @@ snapshots:
|
||||
react-dom: 19.2.8([email protected])
|
||||
styled-jsx: 5.1.6([email protected])
|
||||
optionalDependencies:
|
||||
'@next/swc-darwin-arm64': 16.2.12
|
||||
'@next/swc-darwin-x64': 16.2.12
|
||||
'@next/swc-linux-arm64-gnu': 16.2.12
|
||||
'@next/swc-linux-arm64-musl': 16.2.12
|
||||
'@next/swc-linux-x64-gnu': 16.2.12
|
||||
'@next/swc-linux-x64-musl': 16.2.12
|
||||
'@next/swc-win32-arm64-msvc': 16.2.12
|
||||
'@next/swc-win32-x64-msvc': 16.2.12
|
||||
'@next/swc-darwin-arm64': 16.3.0
|
||||
'@next/swc-darwin-x64': 16.3.0
|
||||
'@next/swc-linux-arm64-gnu': 16.3.0
|
||||
'@next/swc-linux-arm64-musl': 16.3.0
|
||||
'@next/swc-linux-x64-gnu': 16.3.0
|
||||
'@next/swc-linux-x64-musl': 16.3.0
|
||||
'@next/swc-win32-arm64-msvc': 16.3.0
|
||||
'@next/swc-win32-x64-msvc': 16.3.0
|
||||
'@opentelemetry/api': 1.9.1
|
||||
'@playwright/test': 1.62.0
|
||||
babel-plugin-react-compiler: 1.0.0
|
||||
@@ -6970,7 +6970,7 @@ snapshots:
|
||||
react: 19.2.8
|
||||
scheduler: 0.27.0
|
||||
|
||||
[email protected]3.0([email protected]):
|
||||
[email protected]4.0([email protected]):
|
||||
dependencies:
|
||||
react: 19.2.8
|
||||
|
||||
@@ -7274,7 +7274,7 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected].1:
|
||||
[email protected].5:
|
||||
dependencies:
|
||||
esbuild: 0.28.1
|
||||
optionalDependencies:
|
||||
@@ -7345,7 +7345,7 @@ snapshots:
|
||||
typescript: 7.0.2
|
||||
optional: true
|
||||
|
||||
[email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected].1)([email protected]):
|
||||
[email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected].5)([email protected]):
|
||||
dependencies:
|
||||
lightningcss: 1.33.0
|
||||
picomatch: 4.0.5
|
||||
@@ -7358,13 +7358,13 @@ snapshots:
|
||||
fsevents: 2.3.3
|
||||
jiti: 2.7.0
|
||||
terser: 5.49.0
|
||||
tsx: 4.23.1
|
||||
tsx: 4.23.5
|
||||
yaml: 2.9.0
|
||||
|
||||
[email protected](@opentelemetry/[email protected])(@types/[email protected])(@vitest/[email protected])([email protected](@noble/[email protected]))([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected].1)([email protected])):
|
||||
[email protected](@opentelemetry/[email protected])(@types/[email protected])(@vitest/[email protected])([email protected](@noble/[email protected]))([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected].5)([email protected])):
|
||||
dependencies:
|
||||
'@vitest/expect': 4.1.10
|
||||
'@vitest/mocker': 4.1.10([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected].1)([email protected]))
|
||||
'@vitest/mocker': 4.1.10([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected].5)([email protected]))
|
||||
'@vitest/pretty-format': 4.1.10
|
||||
'@vitest/runner': 4.1.10
|
||||
'@vitest/snapshot': 4.1.10
|
||||
@@ -7381,7 +7381,7 @@ snapshots:
|
||||
tinyexec: 1.2.4
|
||||
tinyglobby: 0.2.17
|
||||
tinyrainbow: 3.1.1
|
||||
vite: 8.2.0(@types/[email protected])([email protected])([email protected])([email protected])([email protected].1)([email protected])
|
||||
vite: 8.2.0(@types/[email protected])([email protected])([email protected])([email protected])([email protected].5)([email protected])
|
||||
why-is-node-running: 2.3.0
|
||||
optionalDependencies:
|
||||
'@opentelemetry/api': 1.9.1
|
||||
|
||||
@@ -32,6 +32,12 @@ interface MissingFromSource {
|
||||
itemName: string;
|
||||
}
|
||||
|
||||
interface UnrepairableItem {
|
||||
classname: string;
|
||||
itemId: number;
|
||||
missing: Array<"nitro" | "icon">;
|
||||
}
|
||||
|
||||
interface AuditSummary {
|
||||
totalItemsBase: number;
|
||||
totalCatalogItems: number;
|
||||
@@ -41,6 +47,7 @@ interface AuditSummary {
|
||||
missingIcon: number;
|
||||
duplicates: number;
|
||||
missingFromSourcesTotal: number;
|
||||
unrepairableCount: number;
|
||||
iconsRepaired: number;
|
||||
iconsStillMissing: number;
|
||||
nitrosRepaired: number;
|
||||
@@ -71,6 +78,7 @@ interface AuditEvent {
|
||||
section?: string;
|
||||
issues?: AuditIssue[];
|
||||
missingFromSources?: MissingFromSource[];
|
||||
unrepairable?: UnrepairableItem[];
|
||||
summary?: AuditSummary;
|
||||
repair?: RepairStats;
|
||||
repairNitro?: RepairStats;
|
||||
@@ -138,7 +146,9 @@ function AuditCheckbox({
|
||||
export function AuditClient() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [progress, setProgress] = useState("");
|
||||
const [activeTab, setActiveTab] = useState<"issues" | "sources">("issues");
|
||||
const [activeTab, setActiveTab] = useState<
|
||||
"issues" | "sources" | "unrepairable"
|
||||
>("issues");
|
||||
const [repairEnabled, setRepairEnabled] = useState(true);
|
||||
const [repairNitros, setRepairNitros] = useState(true);
|
||||
const [generateSql, setGenerateSql] = useState(false);
|
||||
@@ -150,6 +160,7 @@ export function AuditClient() {
|
||||
const [missingFromSources, setMissingFromSources] = useState<
|
||||
MissingFromSource[]
|
||||
>([]);
|
||||
const [unrepairable, setUnrepairable] = useState<UnrepairableItem[]>([]);
|
||||
const [summary, setSummary] = useState<AuditSummary | null>(null);
|
||||
const [repairStats, setRepairStats] = useState<RepairStats | null>(null);
|
||||
const [nitroRepairStats, setNitroRepairStats] = useState<RepairStats | null>(
|
||||
@@ -185,6 +196,7 @@ export function AuditClient() {
|
||||
setProgress("Starting audit…");
|
||||
setIssues([]);
|
||||
setMissingFromSources([]);
|
||||
setUnrepairable([]);
|
||||
setSummary(null);
|
||||
setRepairStats(null);
|
||||
setNitroRepairStats(null);
|
||||
@@ -298,21 +310,22 @@ export function AuditClient() {
|
||||
if (evt.type === "batch_complete") {
|
||||
setIssues(evt.issues ?? []);
|
||||
setMissingFromSources(evt.missingFromSources ?? []);
|
||||
setUnrepairable(evt.unrepairable ?? []);
|
||||
if (evt.summary) setSummary(evt.summary);
|
||||
setCheckProgress(null);
|
||||
setProgress("Done");
|
||||
if (
|
||||
evt.issues &&
|
||||
evt.issues.filter((i) => i.severity === "error").length > 0
|
||||
) {
|
||||
toast.error(
|
||||
`${evt.issues.filter((i) => i.severity === "error").length} error(s) found`,
|
||||
);
|
||||
} else if (
|
||||
evt.issues &&
|
||||
evt.issues.filter((i) => i.severity === "warning").length > 0
|
||||
) {
|
||||
const errCount =
|
||||
evt.issues?.filter((i) => i.severity === "error").length ?? 0;
|
||||
const warnCount =
|
||||
evt.issues?.filter((i) => i.severity === "warning").length ??
|
||||
0;
|
||||
const legacyCount = evt.unrepairable?.length ?? 0;
|
||||
if (errCount > 0) {
|
||||
toast.error(`${errCount} error(s) found`);
|
||||
} else if (warnCount > 0) {
|
||||
toast.warning("Warnings found");
|
||||
} else if (legacyCount > 0) {
|
||||
toast.info(`${legacyCount} legacy item(s) can't be repaired`);
|
||||
} else {
|
||||
toast.success("No issues found");
|
||||
}
|
||||
@@ -529,6 +542,17 @@ export function AuditClient() {
|
||||
sources
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{summary.unrepairableCount > 0 ? (
|
||||
<AlertCircle className="w-4 h-4 text-[var(--admin-text-muted)]" />
|
||||
) : (
|
||||
<CheckCircle2 className="w-4 h-4 text-[var(--admin-success)]" />
|
||||
)}
|
||||
<span>
|
||||
{summary.unrepairableCount} item(s) can't be repaired
|
||||
(legacy/broken)
|
||||
</span>
|
||||
</div>
|
||||
{repairStats && (
|
||||
<div className="flex items-center gap-2 col-span-full">
|
||||
<span className="text-xs text-muted-foreground">
|
||||
@@ -690,7 +714,9 @@ export function AuditClient() {
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
{issues.length > 0 || missingFromSources.length > 0 ? (
|
||||
{issues.length > 0 ||
|
||||
missingFromSources.length > 0 ||
|
||||
unrepairable.length > 0 ? (
|
||||
<div className="border-b border-border">
|
||||
<div className="flex gap-4">
|
||||
<button
|
||||
@@ -715,6 +741,17 @@ export function AuditClient() {
|
||||
>
|
||||
Missing from sources ({missingFromSources.length})
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setActiveTab("unrepairable")}
|
||||
className={`pb-2 text-sm font-medium border-b-2 transition-colors ${
|
||||
activeTab === "unrepairable"
|
||||
? "border-primary text-foreground"
|
||||
: "border-transparent text-muted-foreground hover:text-foreground"
|
||||
}`}
|
||||
>
|
||||
Unrepairable (legacy) ({unrepairable.length})
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
@@ -796,6 +833,27 @@ export function AuditClient() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Unrepairable (legacy) list */}
|
||||
{activeTab === "unrepairable" && unrepairable.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
{unrepairable.map((item) => (
|
||||
<div
|
||||
key={item.classname}
|
||||
className="rounded-xl border bg-card p-3 text-sm flex items-center gap-2"
|
||||
>
|
||||
<AlertCircle className="w-3.5 h-3.5 text-[var(--admin-text-muted)] shrink-0" />
|
||||
<code className="font-mono text-xs">{item.classname}</code>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
(ID {item.itemId})
|
||||
</span>
|
||||
<span className="text-xs text-[var(--admin-warning)] ml-auto">
|
||||
missing {item.missing.join(" + ")}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === "issues" &&
|
||||
issues.length === 0 &&
|
||||
missingFromSources.length === 0 && (
|
||||
@@ -814,6 +872,7 @@ export function AuditClient() {
|
||||
setSummary(null);
|
||||
setIssues([]);
|
||||
setMissingFromSources([]);
|
||||
setUnrepairable([]);
|
||||
setRepairStats(null);
|
||||
setNitroRepairStats(null);
|
||||
setSqlText(null);
|
||||
|
||||
@@ -17,6 +17,14 @@ import {
|
||||
repairOrphanedCatalog,
|
||||
} from "./catalog-repair";
|
||||
import { ensureDirectories } from "./furni-import";
|
||||
import {
|
||||
assetNameCandidates,
|
||||
type CatalogItemKind,
|
||||
classifyCatalogItem,
|
||||
isBadgeType,
|
||||
NO_ASSET_KINDS,
|
||||
NO_CATALOG_KINDS,
|
||||
} from "./item-kind";
|
||||
import { repairMissingIcons } from "./repair-icons";
|
||||
import { repairMissingNitros } from "./repair-nitros";
|
||||
|
||||
@@ -39,6 +47,7 @@ export interface AuditEvent {
|
||||
section?: string;
|
||||
issues?: AuditIssue[];
|
||||
missingFromSources?: MissingFromSource[];
|
||||
unrepairable?: UnrepairableItem[];
|
||||
summary?: AuditSummary;
|
||||
repair?: RepairStats;
|
||||
repairNitro?: RepairStats;
|
||||
@@ -80,6 +89,12 @@ export interface MissingFromSource {
|
||||
itemName: string;
|
||||
}
|
||||
|
||||
export interface UnrepairableItem {
|
||||
classname: string;
|
||||
itemId: number;
|
||||
missing: Array<"nitro" | "icon">;
|
||||
}
|
||||
|
||||
export interface AuditSummary {
|
||||
totalItemsBase: number;
|
||||
totalCatalogItems: number;
|
||||
@@ -89,6 +104,7 @@ export interface AuditSummary {
|
||||
missingIcon: number;
|
||||
duplicates: number;
|
||||
missingFromSourcesTotal: number;
|
||||
unrepairableCount: number;
|
||||
iconsRepaired: number;
|
||||
iconsStillMissing: number;
|
||||
nitrosRepaired: number;
|
||||
@@ -119,6 +135,13 @@ export async function runCatalogAudit(
|
||||
): Promise<void> {
|
||||
let issues: AuditIssue[] = [];
|
||||
const missingFromSources: MissingFromSource[] = [];
|
||||
// Items whose missing assets could not be restored from any source even
|
||||
// after a repair attempt — legacy / broken furniture. Keyed by classname so
|
||||
// an item missing both assets is listed once.
|
||||
const unrepairable = new Map<
|
||||
string,
|
||||
{ itemId: number; missing: Set<"nitro" | "icon"> }
|
||||
>();
|
||||
|
||||
try {
|
||||
await ensureDirectories();
|
||||
@@ -161,7 +184,10 @@ export async function runCatalogAudit(
|
||||
]);
|
||||
const iconSet = new Set(iconFiles);
|
||||
const nitroSet = new Set(nitroFiles);
|
||||
const badgeSet = new Set(
|
||||
// Badge detection: `items_base.type = 'b'` is authoritative. The album1584
|
||||
// `.gif` listing is kept as a fallback for hotels that store badges with a
|
||||
// non-standard type.
|
||||
const badgeGifSet = new Set(
|
||||
badgeGifFiles.filter((f) => f.endsWith(".gif")).map((f) => f.slice(0, -4)),
|
||||
);
|
||||
|
||||
@@ -172,16 +198,19 @@ export async function runCatalogAudit(
|
||||
item_name: string;
|
||||
public_name: string;
|
||||
type: string;
|
||||
interaction_type: string;
|
||||
}>;
|
||||
try {
|
||||
const [rows] = (await db.execute(sql`
|
||||
SELECT id, item_name, public_name, type FROM items_base ORDER BY id
|
||||
SELECT id, item_name, public_name, type, interaction_type
|
||||
FROM items_base ORDER BY id
|
||||
`)) as unknown as [
|
||||
Array<{
|
||||
id: number;
|
||||
item_name: string;
|
||||
public_name: string;
|
||||
type: string;
|
||||
interaction_type: string;
|
||||
}>,
|
||||
unknown,
|
||||
];
|
||||
@@ -194,6 +223,21 @@ export async function runCatalogAudit(
|
||||
return;
|
||||
}
|
||||
|
||||
// item_name → catalog item kind (badge / pet / effect / bot / sticky / furni).
|
||||
const kindByName = new Map<string, CatalogItemKind>();
|
||||
for (const item of items) {
|
||||
let kind = classifyCatalogItem(item);
|
||||
if (
|
||||
kind === "furni" &&
|
||||
(isBadgeType(item.type) || badgeGifSet.has(item.item_name))
|
||||
) {
|
||||
kind = "badge";
|
||||
}
|
||||
kindByName.set(item.item_name, kind);
|
||||
}
|
||||
const kindOf = (name: string): CatalogItemKind =>
|
||||
kindByName.get(name) ?? "furni";
|
||||
|
||||
onEvent?.({ type: "items_loaded", total: items.length });
|
||||
|
||||
onEvent?.({ type: "progress", message: "Loading catalog_items…" });
|
||||
@@ -250,6 +294,10 @@ export async function runCatalogAudit(
|
||||
const missingCatalogEntries: MissingCatalogEntry[] = [];
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const item = items[i];
|
||||
const kind = kindOf(item.item_name);
|
||||
// Badges and system items (effects, bots, sticky notes) are awarded or
|
||||
// system-managed, not catalog-purchasable — don't flag or auto-add them.
|
||||
if (NO_CATALOG_KINDS.has(kind)) continue;
|
||||
if (!itemIdsWithCatalog.has(item.id)) {
|
||||
issues.push({
|
||||
type: "missing_catalog",
|
||||
@@ -258,11 +306,12 @@ export async function runCatalogAudit(
|
||||
itemId: item.id,
|
||||
message: `"${item.item_name}" (ID ${item.id}) has no catalog_items entry — not purchasable`,
|
||||
});
|
||||
// Badge items use .gif files, not .nitro bundles.
|
||||
if (
|
||||
!badgeSet.has(item.item_name) &&
|
||||
nitroSet.has(`${item.item_name}.nitro`)
|
||||
) {
|
||||
const hasNitro = assetNameCandidates(item.item_name).nitro.some((n) =>
|
||||
nitroSet.has(n),
|
||||
);
|
||||
// Pets ship their assets through the pet pipeline, not as furni
|
||||
// .nitro bundles, so they can get a catalog entry regardless.
|
||||
if (kind === "pet" || hasNitro) {
|
||||
missingCatalogEntries.push({
|
||||
classname: item.item_name,
|
||||
itemId: item.id,
|
||||
@@ -331,9 +380,13 @@ export async function runCatalogAudit(
|
||||
const missingNitroClassnames: string[] = [];
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const item = items[i];
|
||||
// Badge items use .gif files, not .nitro bundles.
|
||||
if (badgeSet.has(item.item_name)) continue;
|
||||
if (!nitroSet.has(`${item.item_name}.nitro`)) {
|
||||
// Badges, pets, effects, bots and sticky notes never ship a furni
|
||||
// .nitro bundle — skip the asset check for them.
|
||||
if (NO_ASSET_KINDS.has(kindOf(item.item_name))) continue;
|
||||
const hasNitro = assetNameCandidates(item.item_name).nitro.some((n) =>
|
||||
nitroSet.has(n),
|
||||
);
|
||||
if (!hasNitro) {
|
||||
missingNitroClassnames.push(item.item_name);
|
||||
issues.push({
|
||||
type: "missing_nitro",
|
||||
@@ -358,10 +411,13 @@ export async function runCatalogAudit(
|
||||
const missingIconClassnames: string[] = [];
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const item = items[i];
|
||||
// Badge items use .gif icons (stored in album1584), not .png icon files.
|
||||
if (badgeSet.has(item.item_name)) continue;
|
||||
const iconFileName = `${item.item_name}_icon.png`;
|
||||
if (!iconSet.has(iconFileName)) {
|
||||
// Badges use album1584 .gif icons; pets, effects, bots and sticky notes
|
||||
// never ship a furni icon file — skip the asset check for them.
|
||||
if (NO_ASSET_KINDS.has(kindOf(item.item_name))) continue;
|
||||
const hasIcon = assetNameCandidates(item.item_name).icon.some((n) =>
|
||||
iconSet.has(n),
|
||||
);
|
||||
if (!hasIcon) {
|
||||
missingIconClassnames.push(item.item_name);
|
||||
issues.push({
|
||||
type: "missing_icon",
|
||||
@@ -409,17 +465,26 @@ export async function runCatalogAudit(
|
||||
});
|
||||
|
||||
// Re-check which of the previously-missing icons are still missing
|
||||
// and drop the ones the repair fixed from the issue list.
|
||||
// and drop the ones the repair fixed from the issue list. The ones
|
||||
// left over cannot be restored from any source → legacy/broken.
|
||||
const stillMissing: string[] = [];
|
||||
for (const classname of missingIconClassnames) {
|
||||
if (!iconSet.has(`${classname}_icon.png`)) {
|
||||
const hasIcon = assetNameCandidates(classname).icon.some((n) =>
|
||||
iconSet.has(n),
|
||||
);
|
||||
if (!hasIcon) {
|
||||
stillMissing.push(classname);
|
||||
}
|
||||
}
|
||||
issues = issues.filter(
|
||||
(i) =>
|
||||
i.type !== "missing_icon" || stillMissing.includes(i.classname ?? ""),
|
||||
);
|
||||
issues = issues.filter((i) => i.type !== "missing_icon");
|
||||
for (const classname of stillMissing) {
|
||||
const entry = unrepairable.get(classname) ?? {
|
||||
itemId: itemByName.get(classname)?.id ?? 0,
|
||||
missing: new Set(),
|
||||
};
|
||||
entry.missing.add("icon");
|
||||
unrepairable.set(classname, entry);
|
||||
}
|
||||
|
||||
repairStats = {
|
||||
succeeded: result.succeeded,
|
||||
@@ -464,18 +529,26 @@ export async function runCatalogAudit(
|
||||
});
|
||||
|
||||
// Re-check which of the previously-missing nitros are still missing
|
||||
// and drop the ones the repair fixed from the issue list.
|
||||
// and drop the ones the repair fixed from the issue list. The ones
|
||||
// left over cannot be restored from any source → legacy/broken.
|
||||
const stillMissing: string[] = [];
|
||||
for (const classname of missingNitroClassnames) {
|
||||
if (!nitroSet.has(`${classname}.nitro`)) {
|
||||
const hasNitro = assetNameCandidates(classname).nitro.some((n) =>
|
||||
nitroSet.has(n),
|
||||
);
|
||||
if (!hasNitro) {
|
||||
stillMissing.push(classname);
|
||||
}
|
||||
}
|
||||
issues = issues.filter(
|
||||
(i) =>
|
||||
i.type !== "missing_nitro" ||
|
||||
stillMissing.includes(i.classname ?? ""),
|
||||
);
|
||||
issues = issues.filter((i) => i.type !== "missing_nitro");
|
||||
for (const classname of stillMissing) {
|
||||
const entry = unrepairable.get(classname) ?? {
|
||||
itemId: itemByName.get(classname)?.id ?? 0,
|
||||
missing: new Set(),
|
||||
};
|
||||
entry.missing.add("nitro");
|
||||
unrepairable.set(classname, entry);
|
||||
}
|
||||
|
||||
nitroRepairStats = {
|
||||
succeeded: result.succeeded,
|
||||
@@ -673,11 +746,24 @@ export async function runCatalogAudit(
|
||||
}
|
||||
}
|
||||
|
||||
// Items that a repair attempt could not fix are legacy/broken furniture —
|
||||
// surfaced as a separate informational group (not counted as errors) so a
|
||||
// fully repaired catalog can reach 0 errors while still listing exactly
|
||||
// what is not restorable.
|
||||
|
||||
issues.sort((a, b) => {
|
||||
const sev = { error: 0, warning: 1, info: 2 };
|
||||
return (sev[a.severity] ?? 0) - (sev[b.severity] ?? 0);
|
||||
});
|
||||
|
||||
const unrepairableList: UnrepairableItem[] = [...unrepairable.entries()]
|
||||
.sort(([a], [b]) => a.localeCompare(b))
|
||||
.map(([classname, entry]) => ({
|
||||
classname,
|
||||
itemId: entry.itemId,
|
||||
missing: [...entry.missing].sort(),
|
||||
}));
|
||||
|
||||
const summary: AuditSummary = {
|
||||
totalItemsBase: items.length,
|
||||
totalCatalogItems: catalogItems.length,
|
||||
@@ -687,6 +773,7 @@ export async function runCatalogAudit(
|
||||
missingIcon: issues.filter((i) => i.type === "missing_icon").length,
|
||||
duplicates: issues.filter((i) => i.type === "duplicate_classname").length,
|
||||
missingFromSourcesTotal: missingFromSources.length,
|
||||
unrepairableCount: unrepairableList.length,
|
||||
iconsRepaired: repairStats?.succeeded ?? 0,
|
||||
iconsStillMissing:
|
||||
repairStats?.stillMissing ?? missingIconClassnames.length,
|
||||
@@ -708,6 +795,7 @@ export async function runCatalogAudit(
|
||||
type: "batch_complete",
|
||||
issues,
|
||||
missingFromSources,
|
||||
unrepairable: unrepairableList,
|
||||
summary,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
assetNameCandidates,
|
||||
classifyCatalogItem,
|
||||
isBadgeType,
|
||||
isPetClassname,
|
||||
isPetInteraction,
|
||||
NO_ASSET_KINDS,
|
||||
NO_CATALOG_KINDS,
|
||||
} from "./item-kind";
|
||||
|
||||
describe("isBadgeType", () => {
|
||||
it("returns true for badge type b (any case)", () => {
|
||||
expect(isBadgeType("b")).toBe(true);
|
||||
expect(isBadgeType("B")).toBe(true);
|
||||
expect(isBadgeType(" b ")).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false for furni types", () => {
|
||||
expect(isBadgeType("s")).toBe(false);
|
||||
expect(isBadgeType("i")).toBe(false);
|
||||
expect(isBadgeType("")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isPetClassname / isPetInteraction", () => {
|
||||
it("recognises a0 pet<N> classnames", () => {
|
||||
expect(isPetClassname("a0 pet0")).toBe(true);
|
||||
expect(isPetClassname("a0 pet80")).toBe(true);
|
||||
expect(isPetClassname(" a0 pet13 ")).toBe(true);
|
||||
expect(isPetClassname("a0_pet0")).toBe(false);
|
||||
expect(isPetClassname("bunny")).toBe(false);
|
||||
});
|
||||
|
||||
it("recognises pet<N> interaction types", () => {
|
||||
expect(isPetInteraction("pet0")).toBe(true);
|
||||
expect(isPetInteraction(" pet13 ")).toBe(true);
|
||||
expect(isPetInteraction("pet0 ")).toBe(true);
|
||||
expect(isPetInteraction("postit")).toBe(false);
|
||||
expect(isPetInteraction("default")).toBe(false);
|
||||
expect(isPetInteraction(null)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("classifyCatalogItem", () => {
|
||||
it("classifies badges by type b", () => {
|
||||
expect(
|
||||
classifyCatalogItem({
|
||||
type: "b",
|
||||
item_name: "YZI68",
|
||||
interaction_type: "default",
|
||||
}),
|
||||
).toBe("badge");
|
||||
expect(
|
||||
classifyCatalogItem({
|
||||
type: "b",
|
||||
item_name: "horse",
|
||||
interaction_type: "default",
|
||||
}),
|
||||
).toBe("badge");
|
||||
});
|
||||
|
||||
it("classifies pets by classname and interaction type", () => {
|
||||
expect(
|
||||
classifyCatalogItem({
|
||||
type: "s",
|
||||
item_name: "a0 pet0",
|
||||
interaction_type: "pet0 ",
|
||||
}),
|
||||
).toBe("pet");
|
||||
expect(
|
||||
classifyCatalogItem({
|
||||
type: "s",
|
||||
item_name: "a0 pet37",
|
||||
interaction_type: "pet13",
|
||||
}),
|
||||
).toBe("pet");
|
||||
});
|
||||
|
||||
it("classifies effects, bots and sticky notes", () => {
|
||||
expect(
|
||||
classifyCatalogItem({
|
||||
type: "s",
|
||||
item_name: "avatar_effect158",
|
||||
interaction_type: "default",
|
||||
}),
|
||||
).toBe("effect");
|
||||
expect(
|
||||
classifyCatalogItem({
|
||||
type: "s",
|
||||
item_name: "bot_generic",
|
||||
interaction_type: "default",
|
||||
}),
|
||||
).toBe("bot");
|
||||
expect(
|
||||
classifyCatalogItem({
|
||||
type: "s",
|
||||
item_name: "rentable_bot_visitor_log",
|
||||
interaction_type: "default",
|
||||
}),
|
||||
).toBe("bot");
|
||||
expect(
|
||||
classifyCatalogItem({
|
||||
type: "s",
|
||||
item_name: "post.it",
|
||||
interaction_type: "postit",
|
||||
}),
|
||||
).toBe("sticky");
|
||||
});
|
||||
|
||||
it("does not mistake furni classnames that share prefixes", () => {
|
||||
expect(
|
||||
classifyCatalogItem({
|
||||
type: "s",
|
||||
item_name: "botanical_garden",
|
||||
interaction_type: "default",
|
||||
}),
|
||||
).toBe("furni");
|
||||
// `effect_faketile` is a real purchasable furni item.
|
||||
expect(
|
||||
classifyCatalogItem({
|
||||
type: "s",
|
||||
item_name: "effect_faketile",
|
||||
interaction_type: "default",
|
||||
}),
|
||||
).toBe("furni");
|
||||
});
|
||||
|
||||
it("classifies normal furniture", () => {
|
||||
expect(
|
||||
classifyCatalogItem({
|
||||
type: "s",
|
||||
item_name: "table_silo_med",
|
||||
interaction_type: "default",
|
||||
}),
|
||||
).toBe("furni");
|
||||
expect(
|
||||
classifyCatalogItem({
|
||||
type: "i",
|
||||
item_name: "wall_poster",
|
||||
interaction_type: "default",
|
||||
}),
|
||||
).toBe("furni");
|
||||
});
|
||||
});
|
||||
|
||||
describe("kind sets", () => {
|
||||
it("badges and pets never expect furni assets", () => {
|
||||
expect(NO_ASSET_KINDS.has("badge")).toBe(true);
|
||||
expect(NO_ASSET_KINDS.has("pet")).toBe(true);
|
||||
expect(NO_ASSET_KINDS.has("effect")).toBe(true);
|
||||
expect(NO_ASSET_KINDS.has("bot")).toBe(true);
|
||||
expect(NO_ASSET_KINDS.has("sticky")).toBe(true);
|
||||
expect(NO_ASSET_KINDS.has("furni")).toBe(false);
|
||||
});
|
||||
|
||||
it("pets are catalog-purchasable, badges and system items are not", () => {
|
||||
expect(NO_CATALOG_KINDS.has("badge")).toBe(true);
|
||||
expect(NO_CATALOG_KINDS.has("effect")).toBe(true);
|
||||
expect(NO_CATALOG_KINDS.has("bot")).toBe(true);
|
||||
expect(NO_CATALOG_KINDS.has("sticky")).toBe(true);
|
||||
expect(NO_CATALOG_KINDS.has("pet")).toBe(false);
|
||||
expect(NO_CATALOG_KINDS.has("furni")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("assetNameCandidates", () => {
|
||||
it("returns the exact name first for plain classnames", () => {
|
||||
expect(assetNameCandidates("table_silo_med")).toEqual({
|
||||
nitro: ["table_silo_med.nitro"],
|
||||
icon: ["table_silo_med_icon.png"],
|
||||
});
|
||||
});
|
||||
|
||||
it("adds the base classname for star variants", () => {
|
||||
expect(assetNameCandidates("table_silo_med*10").nitro).toContain(
|
||||
"table_silo_med.nitro",
|
||||
);
|
||||
expect(assetNameCandidates("table_silo_med*10").icon).toContain(
|
||||
"table_silo_med_10_icon.png",
|
||||
);
|
||||
});
|
||||
|
||||
it("adds the underscore name for dotted classnames", () => {
|
||||
expect(assetNameCandidates("post.it").nitro).toContain("post_it.nitro");
|
||||
expect(assetNameCandidates("post.it").icon).toContain("post_it_icon.png");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* Classification of `items_base` rows so the catalog audit and repairs know
|
||||
* exactly which items are expected to ship normal furni assets (`.nitro`
|
||||
* bundles + `_icon.png` files) and which ones are special:
|
||||
*
|
||||
* - **badge** — stored with type `b`. Badges use album1584 `.gif` files and
|
||||
* never ship furni `.nitro`/`_icon.png` assets. They are awarded, not
|
||||
* purchased through the catalog.
|
||||
* - **pet** — pet purchase items (`a0 pet<N>`, interaction `pet<N>`). Pet
|
||||
* assets live in the pet asset pipeline (`public/nitro-assets/bundled/pet`),
|
||||
* not the furniture bundle dirs.
|
||||
* - **effect / bot / sticky** — system items with no furniture visuals at all.
|
||||
* - **furni** — everything else; must ship a `.nitro` and `_icon.png`.
|
||||
*/
|
||||
|
||||
export type CatalogItemKind =
|
||||
| "badge"
|
||||
| "pet"
|
||||
| "effect"
|
||||
| "bot"
|
||||
| "sticky"
|
||||
| "furni";
|
||||
|
||||
export interface CatalogItemLike {
|
||||
type: string;
|
||||
item_name: string;
|
||||
interaction_type?: string | null;
|
||||
}
|
||||
|
||||
/** Badges are stored in `items_base` with type `b`. */
|
||||
export function isBadgeType(type: string): boolean {
|
||||
return type.trim().toLowerCase() === "b";
|
||||
}
|
||||
|
||||
/** Pet purchase items use classnames like `a0 pet0` … `a0 pet80`. */
|
||||
export function isPetClassname(itemName: string): boolean {
|
||||
return /^a0\s*pet\d+$/i.test(itemName.trim());
|
||||
}
|
||||
|
||||
/** Pet purchase items use `pet<N>` as their interaction type. */
|
||||
export function isPetInteraction(interactionType?: string | null): boolean {
|
||||
return /^pet\d+$/i.test((interactionType ?? "").trim());
|
||||
}
|
||||
|
||||
/** Classify an `items_base` row into a catalog item kind. */
|
||||
export function classifyCatalogItem(item: CatalogItemLike): CatalogItemKind {
|
||||
const name = item.item_name.trim();
|
||||
if (isBadgeType(item.type)) return "badge";
|
||||
if (isPetClassname(name) || isPetInteraction(item.interaction_type)) {
|
||||
return "pet";
|
||||
}
|
||||
if (/^avatar_effect/i.test(name)) return "effect";
|
||||
if (/^(bot_|rentable_bot_)/i.test(name)) return "bot";
|
||||
if (/^post[._]?it/i.test(name)) return "sticky";
|
||||
return "furni";
|
||||
}
|
||||
|
||||
/** Kinds that never ship furni `.nitro` / `_icon.png` assets. */
|
||||
export const NO_ASSET_KINDS: ReadonlySet<CatalogItemKind> = new Set([
|
||||
"badge",
|
||||
"pet",
|
||||
"effect",
|
||||
"bot",
|
||||
"sticky",
|
||||
]);
|
||||
|
||||
/** Kinds that are not purchasable furniture and should not get catalog entries. */
|
||||
export const NO_CATALOG_KINDS: ReadonlySet<CatalogItemKind> = new Set([
|
||||
"badge",
|
||||
"effect",
|
||||
"bot",
|
||||
"sticky",
|
||||
]);
|
||||
|
||||
/**
|
||||
* Asset file names for a classname, mirroring how `importSingleFurni` writes
|
||||
* files. Returns `.nitro` and icon candidates (most specific first):
|
||||
* the exact classname, the star-variant base, the star→underscore name and the
|
||||
* dot→underscore name (`post.it` → `post_it.nitro`).
|
||||
*/
|
||||
export function assetNameCandidates(classname: string): {
|
||||
nitro: string[];
|
||||
icon: string[];
|
||||
} {
|
||||
const unique = (names: string[]): string[] => {
|
||||
const seen = new Set<string>();
|
||||
const out: string[] = [];
|
||||
for (const n of names) {
|
||||
if (!n || seen.has(n)) continue;
|
||||
seen.add(n);
|
||||
out.push(n);
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
const starBase = classname.split("*")[0];
|
||||
const starUnderscore = classname.replace(/\*/g, "_");
|
||||
const dotUnderscore = classname.replace(/\./g, "_");
|
||||
|
||||
return {
|
||||
nitro: unique([classname, starBase, dotUnderscore, starUnderscore]).map(
|
||||
(n) => `${n}.nitro`,
|
||||
),
|
||||
icon: unique([classname, starUnderscore, dotUnderscore, starBase]).map(
|
||||
(n) => `${n}_icon.png`,
|
||||
),
|
||||
};
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
getGamedataRoot,
|
||||
} from "@/lib/services/furni-asset-dirs";
|
||||
import { downloadFile } from "@/lib/services/import/core/download";
|
||||
import { classifyCatalogItem, NO_ASSET_KINDS } from "@/lib/services/item-kind";
|
||||
import { ensureDirectories } from "./furni-import";
|
||||
|
||||
const REPAIR_CONCURRENCY = 6;
|
||||
@@ -141,28 +142,44 @@ export async function repairMissingIcons(
|
||||
...targets.mirrorDirs.map((d) => d.nitroDir),
|
||||
]);
|
||||
|
||||
// Load known badge codes from the album directory so we can skip badge
|
||||
// items — they use .gif files, not .png icons.
|
||||
let badgeSet: Set<string> | null = null;
|
||||
// Badges and other special items (pets, effects, bots, sticky notes) never
|
||||
// ship furni `_icon.png` files — resolve the skip set from the DB so badge
|
||||
// detection is precise (`items_base.type = 'b'`) instead of relying only on
|
||||
// which .gif files happen to exist in the album directory.
|
||||
let skipSet: Set<string> | null = null;
|
||||
try {
|
||||
const gamedataRoot = await getGamedataRoot();
|
||||
if (gamedataRoot) {
|
||||
const badgeFiles = await fs
|
||||
.readdir(path.join(gamedataRoot, "album1584"))
|
||||
.catch(() => [] as string[]);
|
||||
badgeSet = new Set(
|
||||
const albumBadgeSet = new Set(
|
||||
badgeFiles.filter((f) => f.endsWith(".gif")).map((f) => f.slice(0, -4)),
|
||||
);
|
||||
skipSet = albumBadgeSet;
|
||||
}
|
||||
} catch {
|
||||
badgeSet = null;
|
||||
skipSet = null;
|
||||
}
|
||||
|
||||
let items: Array<{ id: number; item_name: string }>;
|
||||
let items: Array<{
|
||||
id: number;
|
||||
item_name: string;
|
||||
type: string;
|
||||
interaction_type: string;
|
||||
}>;
|
||||
try {
|
||||
const [rows] = (await db.execute(sql`
|
||||
SELECT id, item_name FROM items_base ORDER BY id
|
||||
`)) as unknown as [Array<{ id: number; item_name: string }>, unknown];
|
||||
SELECT id, item_name, type, interaction_type FROM items_base ORDER BY id
|
||||
`)) as unknown as [
|
||||
Array<{
|
||||
id: number;
|
||||
item_name: string;
|
||||
type: string;
|
||||
interaction_type: string;
|
||||
}>,
|
||||
unknown,
|
||||
];
|
||||
items = rows;
|
||||
} catch (err) {
|
||||
onEvent?.({
|
||||
@@ -172,13 +189,24 @@ export async function repairMissingIcons(
|
||||
return { succeeded: 0, failed: 0, skipped: 0 };
|
||||
}
|
||||
|
||||
// Build the authoritative skip set from the DB, supplemented by album .gifs.
|
||||
const dbSkipSet = new Set<string>();
|
||||
for (const item of items) {
|
||||
if (NO_ASSET_KINDS.has(classifyCatalogItem(item))) {
|
||||
dbSkipSet.add(item.item_name);
|
||||
}
|
||||
}
|
||||
for (const code of skipSet ?? []) {
|
||||
dbSkipSet.add(code);
|
||||
}
|
||||
|
||||
const total = items.length;
|
||||
onEvent?.({ type: "started", total });
|
||||
|
||||
const processItem = async (classname: string) => {
|
||||
// Badge items that only exist as .gif files (in album1584) don't have
|
||||
// Special items (badges, pets, effects, bots, sticky notes) don't have
|
||||
// .png icon files to repair. Skip them.
|
||||
if (badgeSet?.has(classname)) {
|
||||
if (dbSkipSet.has(classname)) {
|
||||
return { status: "skipped" } as const;
|
||||
}
|
||||
const fileName = `${classname}_icon.png`;
|
||||
@@ -269,6 +297,11 @@ export async function repairMissingIcons(
|
||||
`${classname.replace(/\*/g, "_")}_icon.png`,
|
||||
]
|
||||
: []),
|
||||
// Classnames with dots (`post.it`) are stored on disk with
|
||||
// underscores (`post_it_icon.png`).
|
||||
...(classname.includes(".")
|
||||
? [`${classname.replace(/\./g, "_")}_icon.png`]
|
||||
: []),
|
||||
];
|
||||
|
||||
for (const iconName of iconCandidates) {
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
getGamedataRoot,
|
||||
} from "@/lib/services/furni-asset-dirs";
|
||||
import { downloadFile } from "@/lib/services/import/core/download";
|
||||
import { classifyCatalogItem, NO_ASSET_KINDS } from "@/lib/services/item-kind";
|
||||
import { parseNitroBundle } from "@/lib/services/swf/nitro-builder";
|
||||
import { ensureDirectories } from "./furni-import";
|
||||
|
||||
@@ -137,28 +138,43 @@ export async function repairMissingNitros(
|
||||
...targets.mirrorDirs.map((d) => d.nitroDir),
|
||||
]);
|
||||
|
||||
// Load known badge codes from the album directory so we can skip badge
|
||||
// items — they use .gif files, not .nitro bundles.
|
||||
let badgeSet: Set<string> | null = null;
|
||||
// Badges and other special items (pets, effects, bots, sticky notes) never
|
||||
// ship furni `.nitro` bundles — resolve the skip set from the DB so badge
|
||||
// detection is precise (`items_base.type = 'b'`) instead of relying only on
|
||||
// which .gif files happen to exist in the album directory.
|
||||
let albumBadgeSet: Set<string> | null = null;
|
||||
try {
|
||||
const gamedataRoot = await getGamedataRoot();
|
||||
if (gamedataRoot) {
|
||||
const badgeFiles = await fs
|
||||
.readdir(path.join(gamedataRoot, "album1584"))
|
||||
.catch(() => [] as string[]);
|
||||
badgeSet = new Set(
|
||||
albumBadgeSet = new Set(
|
||||
badgeFiles.filter((f) => f.endsWith(".gif")).map((f) => f.slice(0, -4)),
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
badgeSet = null;
|
||||
albumBadgeSet = null;
|
||||
}
|
||||
|
||||
let items: Array<{ id: number; item_name: string }>;
|
||||
let items: Array<{
|
||||
id: number;
|
||||
item_name: string;
|
||||
type: string;
|
||||
interaction_type: string;
|
||||
}>;
|
||||
try {
|
||||
const [rows] = (await db.execute(sql`
|
||||
SELECT id, item_name FROM items_base ORDER BY id
|
||||
`)) as unknown as [Array<{ id: number; item_name: string }>, unknown];
|
||||
SELECT id, item_name, type, interaction_type FROM items_base ORDER BY id
|
||||
`)) as unknown as [
|
||||
Array<{
|
||||
id: number;
|
||||
item_name: string;
|
||||
type: string;
|
||||
interaction_type: string;
|
||||
}>,
|
||||
unknown,
|
||||
];
|
||||
items = rows;
|
||||
} catch (err) {
|
||||
onEvent?.({
|
||||
@@ -168,12 +184,24 @@ export async function repairMissingNitros(
|
||||
return { succeeded: 0, failed: 0, skipped: 0 };
|
||||
}
|
||||
|
||||
// Build the authoritative skip set from the DB, supplemented by album .gifs.
|
||||
const skipSet = new Set<string>();
|
||||
for (const item of items) {
|
||||
if (NO_ASSET_KINDS.has(classifyCatalogItem(item))) {
|
||||
skipSet.add(item.item_name);
|
||||
}
|
||||
}
|
||||
for (const code of albumBadgeSet ?? []) {
|
||||
skipSet.add(code);
|
||||
}
|
||||
|
||||
const total = items.length;
|
||||
onEvent?.({ type: "started", total });
|
||||
|
||||
const processItem = async (classname: string) => {
|
||||
// Badge items use .gif files, not .nitro bundles.
|
||||
if (badgeSet?.has(classname) ?? false) {
|
||||
// Special items (badges, pets, effects, bots, sticky notes) don't have
|
||||
// furni .nitro bundles to repair. Skip them.
|
||||
if (skipSet.has(classname)) {
|
||||
return { status: "skipped" } as const;
|
||||
}
|
||||
const fileName = `${classname}.nitro`;
|
||||
@@ -229,6 +257,11 @@ export async function repairMissingNitros(
|
||||
`${classname.replace(/\*/g, "_")}.nitro`,
|
||||
]
|
||||
: []),
|
||||
// Classnames with dots (`post.it`) are stored on disk with
|
||||
// underscores (`post_it.nitro`).
|
||||
...(classname.includes(".")
|
||||
? [`${classname.replace(/\./g, "_")}.nitro`]
|
||||
: []),
|
||||
];
|
||||
|
||||
for (const source of sources) {
|
||||
|
||||
Reference in new issue
Block a user