chore(creator-studio): wire up eslint + prettier for this app

Neither tool covered the app. Prettier's root scripts glob "**/*.{ts,tsx}" and
prettier-plugin-svelte was absent, so a .svelte file failed with "No parser could
be inferred". ESLint's root script is scoped to src/ (the Next app), and the root
config cannot parse .svelte at all - @typescript-eslint/parser reads the markup
as TypeScript and throws.

Scoped to this app rather than fixed at the root, since an app-wide eslint/
prettier overhaul is in flight separately.

Prettier is a second major here (3.9 alongside root 2.8) out of necessity: no
prettier-plugin-svelte supports svelte 5 on prettier 2 (@2.10 caps at svelte 4,
svelte 5 support starts at @3.2 which peers prettier ^3). The two majors are NOT
interchangeable - prettier 3 collapses leading-pipe union types that prettier 2
breaks across lines - so root .prettierignore hands this directory over wholesale
rather than letting both format the same files and revert each other forever.
Run `pnpm -F @civitai/creator-studio-app format` from the app.

ESLint needed no version split: eslint-plugin-svelte@2.46 peers eslint ^8 and
still ships eslintrc configs, matching the pinned 8.57.1.

Four rules are adjusted where they are categorically wrong rather than merely
inconvenient, each with the reason inline:
  - prefer-const off in .svelte - svelte 5 REQUIRES let for $props()
    destructuring, so it fired on all 118 props in the app
  - no-undef off - TS resolves types itself; it reported generics and DOM lib
    types (T, FormDataEntryValue) as undefined globals
  - prefer-const destructuring:'all' - the default reports unreassigned bindings
    in a let{...} pattern with a reassigned sibling, unfixable without splitting
    the statement (see getEdgeUrl)
  - svelte/valid-compile off - re-reports compiler diagnostics that svelte-check
    and the build already own, including a custom-element warning that cannot
    apply to an app never compiled as a custom element

Also uncomment WEBHOOK_TOKEN in .env.example. It is required for permanent paid
access (the main-app endpoint gates the `permanent` flag on it) but shipped
commented out, so deployments provisioned from the template omitted it - which is
how creator-studio came to send `?token=` and every creator got the misleading
403 "Permanent access can only be set from the Creator Studio."

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
briant
2026-07-29 22:38:49 -06:00
parent b3662fb873
commit d6f3defc81
6 changed files with 250 additions and 2 deletions
+6
View File
@@ -0,0 +1,6 @@
# apps/creator-studio formats itself, from its own root, with its own Prettier 3 + prettier-plugin-svelte
# (svelte 5 needs prettier >=3; no prettier-plugin-svelte supports svelte 5 on the root's prettier 2).
# The two majors format TS differently -- prettier 3 collapses leading-pipe unions that prettier 2 breaks
# across lines -- so ownership has to be exclusive or the two fight over the same files forever.
# Run `pnpm -F @civitai/creator-studio-app format` there instead.
apps/creator-studio/
+1 -1
View File
@@ -22,7 +22,7 @@ AUTH_INTERNAL_TOKEN=
# Shared main-app webhook token — required only to set permanent paid access (the main-app endpoint gates the
# `permanent` flag on it). Use the same value as the main app's WEBHOOK_TOKEN.
# WEBHOOK_TOKEN=
WEBHOOK_TOKEN=
# --- Database (Kysely read/write via @civitai/db/kysely, src/lib/server/db.ts) ---
# Point write at the primary and read at a replica (use the same URL for both if you have no replica).
+53
View File
@@ -0,0 +1,53 @@
// Scoped to this app rather than folded into the repo-root .eslintrc.js: root extends
// `next/core-web-vitals` + the tailwind plugin, neither of which applies to a SvelteKit app, and the
// root config can't parse `.svelte` at all (@typescript-eslint/parser reads the markup as TS and
// errors). `root: true` stops that config from being inherited here.
//
// `.cjs` because package.json sets "type": "module" and eslintrc must be CommonJS.
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
extraFileExtensions: ['.svelte'],
},
plugins: ['@typescript-eslint'],
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:svelte/recommended'],
overrides: [
{
files: ['*.svelte'],
parser: 'svelte-eslint-parser',
parserOptions: { parser: '@typescript-eslint/parser' },
rules: {
// Svelte 5 REQUIRES `let` for `$props()` destructuring — the compiler rewrites the binding and
// `const` breaks bindable props. prefer-const flags every prop in every component (118 of them).
'prefer-const': 'off',
},
},
],
env: { browser: true, node: true, es2022: true },
ignorePatterns: ['.svelte-kit/', 'build/', 'node_modules/', '*.cjs'],
rules: {
// Matches the root config's posture: `any` is pervasive in this codebase and failing on it would
// hold new files to a stricter bar than anything already merged.
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
// TS resolves types itself, and no-undef can't see them — it reports generics and DOM lib types
// (`T`, `FormDataEntryValue`) as undefined globals. Disabling it here is typescript-eslint's own
// documented guidance for TS projects.
'no-undef': 'off',
// Default `destructuring: 'any'` flags every unreassigned binding in a `let {...} = obj` even when
// a sibling IS reassigned — unfixable without splitting the statement (see getEdgeUrl in
// lib/media/edge-url.ts). 'all' only reports when nothing in the pattern is reassigned.
'prefer-const': ['error', { destructuring: 'all' }],
// An empty arrow is the idiomatic "swallow this rejection" handler (e.g. play().catch(() => {})
// where blocked autoplay is expected). Named empty functions are still reported. The
// @typescript-eslint variant is the one in force — its recommended config disables the base rule.
'@typescript-eslint/no-empty-function': ['error', { allow: ['arrowFunctions'] }],
// Re-reports Svelte compiler diagnostics, which `pnpm check` (svelte-check) and the build already
// own and report better. Its custom_element_props_identifier warning also cannot apply here — it
// only matters when compiling as a custom element, which this app never does.
'svelte/valid-compile': 'off',
},
};
+8
View File
@@ -0,0 +1,8 @@
{
"trailingComma": "es5",
"tabWidth": 2,
"printWidth": 100,
"singleQuote": true,
"plugins": ["prettier-plugin-svelte"],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
}
+10 -1
View File
@@ -9,7 +9,10 @@
"preview": "vite preview",
"prepare": "svelte-kit sync",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"typecheck": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json"
"typecheck": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"lint": "eslint . --ext .js,.ts,.svelte --cache --cache-strategy metadata",
"format": "prettier --write \"src/**/*.{js,ts,svelte}\"",
"format:check": "prettier --check \"src/**/*.{js,ts,svelte}\""
},
"dependencies": {
"@civitai/auth": "workspace:*",
@@ -32,8 +35,14 @@
"@sveltejs/vite-plugin-svelte": "^6.0.0",
"@tailwindcss/vite": "^4.1.0",
"@types/node": "^20.19.9",
"@typescript-eslint/parser": "^5.62.0",
"eslint": "^8.57.1",
"eslint-plugin-svelte": "^2.46.1",
"prettier": "^3.9.6",
"prettier-plugin-svelte": "^4.1.1",
"svelte": "^5.0.0",
"svelte-check": "^4.0.0",
"svelte-eslint-parser": "^0.43.0",
"tailwindcss": "^4.1.0",
"typescript": "^5.9.2",
"vite": "^7.0.4"
+172
View File
@@ -988,12 +988,30 @@ importers:
'@types/node':
specifier: ^20.19.9
version: 20.19.9
'@typescript-eslint/parser':
specifier: ^5.62.0
version: 5.62.0(eslint@8.57.1)(typescript@5.9.3)
eslint:
specifier: ^8.57.1
version: 8.57.1
eslint-plugin-svelte:
specifier: ^2.46.1
version: 2.46.1(eslint@8.57.1)(svelte@5.56.3(@typescript-eslint/types@8.60.1))(ts-node@10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.17))(@types/node@20.19.9)(typescript@5.9.3))
prettier:
specifier: ^3.9.6
version: 3.9.6
prettier-plugin-svelte:
specifier: ^4.1.1
version: 4.1.1(prettier@3.9.6)(svelte@5.56.3(@typescript-eslint/types@8.60.1))
svelte:
specifier: ^5.0.0
version: 5.56.3(@typescript-eslint/types@8.60.1)
svelte-check:
specifier: ^4.0.0
version: 4.6.0(picomatch@4.0.3)(svelte@5.56.3(@typescript-eslint/types@8.60.1))(typescript@5.9.3)
svelte-eslint-parser:
specifier: ^0.43.0
version: 0.43.0(svelte@5.56.3(@typescript-eslint/types@8.60.1))
tailwindcss:
specifier: ^4.1.0
version: 4.3.2
@@ -7371,6 +7389,12 @@ packages:
resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}
engines: {node: '>=12'}
eslint-compat-utils@0.5.1:
resolution: {integrity: sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==}
engines: {node: '>=12'}
peerDependencies:
eslint: '>=6.0.0'
eslint-config-next@15.5.19:
resolution: {integrity: sha512-UZwkuhBCNxVZfo93MSHRDOVNWXooJJGcAUyTAVIp0+9QFhH4SqJxWY0s6Mk9C2kMi777HPMn3dseOrZshWpG9Q==}
peerDependencies:
@@ -7454,6 +7478,16 @@ packages:
peerDependencies:
eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7
eslint-plugin-svelte@2.46.1:
resolution: {integrity: sha512-7xYr2o4NID/f9OEYMqxsEQsCsj4KaMy4q5sANaKkAb6/QeCjYFxRmDm2S3YC3A3pl1kyPZ/syOx/i7LcWYSbIw==}
engines: {node: ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: ^7.0.0 || ^8.0.0-0 || ^9.0.0-0
svelte: ^3.37.0 || ^4.0.0 || ^5.0.0
peerDependenciesMeta:
svelte:
optional: true
eslint-plugin-tailwindcss@3.18.2:
resolution: {integrity: sha512-QbkMLDC/OkkjFQ1iz/5jkMdHfiMu/uwujUHLAJK5iwNHD8RTxVTlsUezE0toTZ6VhybNBsk+gYGPDq2agfeRNA==}
engines: {node: '>=18.12.0'}
@@ -8856,6 +8890,9 @@ packages:
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
engines: {node: '>=6'}
known-css-properties@0.35.0:
resolution: {integrity: sha512-a/RAk2BfKk+WFGhhOCAYqSiFLc34k8Mt/6NWRI4joER0EYUzXIcFivjjnoD3+XU1DggLn/tZc3DOAgke7l8a4A==}
koa-compose@4.1.0:
resolution: {integrity: sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==}
@@ -10091,6 +10128,18 @@ packages:
peerDependencies:
postcss: ^8.4.21
postcss-load-config@3.1.4:
resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==}
engines: {node: '>= 10'}
peerDependencies:
postcss: '>=8.0.9'
ts-node: '>=9.0.0'
peerDependenciesMeta:
postcss:
optional: true
ts-node:
optional: true
postcss-load-config@4.0.2:
resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==}
engines: {node: '>= 14'}
@@ -10281,6 +10330,18 @@ packages:
peerDependencies:
postcss: ^8.4.32
postcss-safe-parser@6.0.0:
resolution: {integrity: sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==}
engines: {node: '>=12.0'}
peerDependencies:
postcss: ^8.3.3
postcss-scss@4.0.9:
resolution: {integrity: sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==}
engines: {node: '>=12.0'}
peerDependencies:
postcss: ^8.4.29
postcss-selector-parser@6.1.2:
resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==}
engines: {node: '>=4'}
@@ -10353,11 +10414,23 @@ packages:
resolution: {integrity: sha512-PhmXi5XmoyKw1Un4E+opM2KcsJInDvKyuOumcjjw3waw86ZNjHwVUOOWLc4bCzLdcKNaWBH9e99sbWzDQsVaYg==}
engines: {node: '>=0.10.0'}
prettier-plugin-svelte@4.1.1:
resolution: {integrity: sha512-wXvbXMjSvb4C9ENWTHXyd+ihakKCsJ6rJhLP6/8HFNj4GkZr48jqL9PoKsl2sk7SyCZRTnJ7O2TTowUpOxP/KA==}
engines: {node: '>=20'}
peerDependencies:
prettier: ^3.0.0
svelte: ^5.0.0
prettier@2.8.8:
resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==}
engines: {node: '>=10.13.0'}
hasBin: true
prettier@3.9.6:
resolution: {integrity: sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==}
engines: {node: '>=14'}
hasBin: true
pretty-format@29.7.0:
resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -11564,6 +11637,15 @@ packages:
svelte: ^4.0.0 || ^5.0.0-next.0
typescript: '>=5.0.0'
svelte-eslint-parser@0.43.0:
resolution: {integrity: sha512-GpU52uPKKcVnh8tKN5P4UZpJ/fUDndmq7wfsvoVXsyP+aY0anol7Yqo01fyrlaWGMFfm4av5DyrjlaXdLRJvGA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
svelte: ^3.37.0 || ^4.0.0 || ^5.0.0
peerDependenciesMeta:
svelte:
optional: true
svelte-sonner@1.1.1:
resolution: {integrity: sha512-5cd3p7wa4cq0NsqslMwdlPb7x1JglEZ/GKrLePWNr5bCxR1nagAVrY01FRFrXfUGs41miLt3C327+8XJo5BzZw==}
peerDependencies:
@@ -17668,6 +17750,18 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.9.3)':
dependencies:
'@typescript-eslint/scope-manager': 5.62.0
'@typescript-eslint/types': 5.62.0
'@typescript-eslint/typescript-estree': 5.62.0(typescript@5.9.3)
debug: 4.4.1
eslint: 8.57.1
optionalDependencies:
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.2)':
dependencies:
'@typescript-eslint/scope-manager': 6.21.0
@@ -17761,6 +17855,20 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/typescript-estree@5.62.0(typescript@5.9.3)':
dependencies:
'@typescript-eslint/types': 5.62.0
'@typescript-eslint/visitor-keys': 5.62.0
debug: 4.4.3
globby: 11.1.0
is-glob: 4.0.3
semver: 7.7.2
tsutils: 3.21.0(typescript@5.9.3)
optionalDependencies:
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/typescript-estree@6.21.0(typescript@5.9.2)':
dependencies:
'@typescript-eslint/types': 6.21.0
@@ -19887,6 +19995,11 @@ snapshots:
escape-string-regexp@5.0.0: {}
eslint-compat-utils@0.5.1(eslint@8.57.1):
dependencies:
eslint: 8.57.1
semver: 7.8.1
eslint-config-next@15.5.19(eslint@8.57.1)(typescript@5.9.2):
dependencies:
'@next/eslint-plugin-next': 15.5.19
@@ -20060,6 +20173,25 @@ snapshots:
string.prototype.matchall: 4.0.12
string.prototype.repeat: 1.0.0
eslint-plugin-svelte@2.46.1(eslint@8.57.1)(svelte@5.56.3(@typescript-eslint/types@8.60.1))(ts-node@10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.17))(@types/node@20.19.9)(typescript@5.9.3)):
dependencies:
'@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1)
'@jridgewell/sourcemap-codec': 1.5.5
eslint: 8.57.1
eslint-compat-utils: 0.5.1(eslint@8.57.1)
esutils: 2.0.3
known-css-properties: 0.35.0
postcss: 8.5.6
postcss-load-config: 3.1.4(postcss@8.5.6)(ts-node@10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.17))(@types/node@20.19.9)(typescript@5.9.3))
postcss-safe-parser: 6.0.0(postcss@8.5.6)
postcss-selector-parser: 6.1.2
semver: 7.8.1
svelte-eslint-parser: 0.43.0(svelte@5.56.3(@typescript-eslint/types@8.60.1))
optionalDependencies:
svelte: 5.56.3(@typescript-eslint/types@8.60.1)
transitivePeerDependencies:
- ts-node
eslint-plugin-tailwindcss@3.18.2(tailwindcss@3.4.17(ts-node@10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.17))(@types/node@24.13.3)(typescript@5.9.2))):
dependencies:
fast-glob: 3.3.3
@@ -21955,6 +22087,8 @@ snapshots:
kleur@4.1.5: {}
known-css-properties@0.35.0: {}
koa-compose@4.1.0: {}
koa-convert@2.0.0:
@@ -23548,6 +23682,14 @@ snapshots:
camelcase-css: 2.0.1
postcss: 8.5.6
postcss-load-config@3.1.4(postcss@8.5.6)(ts-node@10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.17))(@types/node@20.19.9)(typescript@5.9.3)):
dependencies:
lilconfig: 2.1.0
yaml: 1.10.2
optionalDependencies:
postcss: 8.5.6
ts-node: 10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.17))(@types/node@20.19.9)(typescript@5.9.3)
postcss-load-config@4.0.2(postcss@8.5.6)(ts-node@10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.17))(@types/node@24.13.3)(typescript@5.9.2)):
dependencies:
lilconfig: 3.1.3
@@ -23723,6 +23865,14 @@ snapshots:
postcss: 8.5.6
postcss-value-parser: 4.2.0
postcss-safe-parser@6.0.0(postcss@8.5.6):
dependencies:
postcss: 8.5.6
postcss-scss@4.0.9(postcss@8.5.6):
dependencies:
postcss: 8.5.6
postcss-selector-parser@6.1.2:
dependencies:
cssesc: 3.0.0
@@ -23795,8 +23945,15 @@ snapshots:
prepend-http@1.0.4: {}
prettier-plugin-svelte@4.1.1(prettier@3.9.6)(svelte@5.56.3(@typescript-eslint/types@8.60.1)):
dependencies:
prettier: 3.9.6
svelte: 5.56.3(@typescript-eslint/types@8.60.1)
prettier@2.8.8: {}
prettier@3.9.6: {}
pretty-format@29.7.0:
dependencies:
'@jest/schemas': 29.6.3
@@ -25320,6 +25477,16 @@ snapshots:
transitivePeerDependencies:
- picomatch
svelte-eslint-parser@0.43.0(svelte@5.56.3(@typescript-eslint/types@8.60.1)):
dependencies:
eslint-scope: 7.2.2
eslint-visitor-keys: 3.4.3
espree: 9.6.1
postcss: 8.5.6
postcss-scss: 4.0.9(postcss@8.5.6)
optionalDependencies:
svelte: 5.56.3(@typescript-eslint/types@8.60.1)
svelte-sonner@1.1.1(svelte@5.56.3(@typescript-eslint/types@8.60.1)):
dependencies:
runed: 0.28.0(svelte@5.56.3(@typescript-eslint/types@8.60.1))
@@ -25754,6 +25921,11 @@ snapshots:
tslib: 1.14.1
typescript: 5.9.2
tsutils@3.21.0(typescript@5.9.3):
dependencies:
tslib: 1.14.1
typescript: 5.9.3
tsx@4.20.3:
dependencies:
esbuild: 0.25.8