diff --git a/Dockerfile b/Dockerfile index 6710d286..ea63f07c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,8 +24,8 @@ RUN --mount=type=cache,id=pnpm,target=/pnpm/store \ COPY src ./src COPY .gitignore ./.gitignore -COPY postcss.config.cjs ./postcss.config.cjs -COPY prettier.config.cjs ./prettier.config.cjs +COPY postcss.config.mjs ./postcss.config.mjs +COPY prettier.config.mjs ./prettier.config.mjs COPY eslint.config.mjs ./eslint.config.mjs COPY vite.config.ts ./vite.config.ts COPY tsup.config.ts ./tsup.config.ts diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index dbf621d9..ade30008 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -2,4 +2,4 @@ set -e cd ${ZIPLINE_ROOT:-/zipline} -exec node --enable-source-maps build/server +exec node --enable-source-maps build/server/index.js diff --git a/docker/ziplinectl.sh b/docker/ziplinectl.sh index 40cb94a4..d40bdc04 100755 --- a/docker/ziplinectl.sh +++ b/docker/ziplinectl.sh @@ -2,5 +2,5 @@ set -e cd ${ZIPLINE_ROOT:-/zipline} -exec node --enable-source-maps build/ctl "$@" +exec node --enable-source-maps build/ctl/index.js "$@" diff --git a/package.json b/package.json index 25c3ffde..c7bf11b2 100644 --- a/package.json +++ b/package.json @@ -3,14 +3,15 @@ "private": true, "license": "MIT", "version": "4.6.4", + "type": "module", "scripts": { "build": "tsx scripts/build.ts", - "dev": "cross-env NODE_ENV=development DEBUG=zipline tsx --require ./src/dotenv.js --enable-source-maps ./src/server", - "dev:nd": "cross-env NODE_ENV=development tsx --require ./src/dotenv.js --enable-source-maps ./src/server", - "dev:inspector": "cross-env NODE_ENV=development DEBUG=zipline tsx --require ./src/dotenv.js --inspect=0.0.0.0:9229 --enable-source-maps ./src/server", - "start": "cross-env NODE_ENV=production node --trace-warnings --require ./src/dotenv.js ./build/server", - "start:inspector": "cross-env NODE_ENV=production node --require ./src/dotenv.js --inspect=0.0.0.0:9229 --enable-source-maps ./build/server", - "ctl": "NODE_ENV=production node --require ./src/dotenv.js --enable-source-maps ./build/ctl", + "dev": "cross-env NODE_ENV=development DEBUG=zipline tsx --import ./src/dotenv.js --enable-source-maps ./src/server", + "dev:nd": "cross-env NODE_ENV=development tsx --import ./src/dotenv.js --enable-source-maps ./src/server", + "dev:inspector": "cross-env NODE_ENV=development DEBUG=zipline tsx --import ./src/dotenv.js --inspect=0.0.0.0:9229 --enable-source-maps ./src/server", + "start": "cross-env NODE_ENV=production node --trace-warnings --import ./src/dotenv.js ./build/server/index.js", + "start:inspector": "cross-env NODE_ENV=production node --import ./src/dotenv.js --inspect=0.0.0.0:9229 --enable-source-maps ./build/server/index.js", + "ctl": "NODE_ENV=production node --import ./src/dotenv.js --enable-source-maps ./build/ctl/index.js", "validate": "tsx scripts/validate.ts", "openapi": "tsx scripts/openapi.ts", "db:prototype": "prisma db push --skip-generate && prisma generate --no-hints", diff --git a/postcss.config.cjs b/postcss.config.mjs similarity index 94% rename from postcss.config.cjs rename to postcss.config.mjs index bfba0ddf..0d6062ca 100644 --- a/postcss.config.cjs +++ b/postcss.config.mjs @@ -1,4 +1,4 @@ -module.exports = { +export default { plugins: { 'postcss-preset-mantine': {}, 'postcss-simple-vars': { diff --git a/prettier.config.cjs b/prettier.config.mjs similarity index 85% rename from prettier.config.cjs rename to prettier.config.mjs index fec769a0..6259357a 100644 --- a/prettier.config.cjs +++ b/prettier.config.mjs @@ -1,5 +1,5 @@ /** @type {import('prettier').Config} */ -module.exports = { +export default { singleQuote: true, jsxSingleQuote: true, printWidth: 110, diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 150741ba..af519618 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -1,7 +1,7 @@ generator client { provider = "prisma-client" output = "../src/prisma" - moduleFormat = "cjs" + moduleFormat = "esm" } datasource db { diff --git a/scripts/openapi.ts b/scripts/openapi.ts index e8898735..7d2805ff 100644 --- a/scripts/openapi.ts +++ b/scripts/openapi.ts @@ -1,10 +1,11 @@ import { readFile, writeFile } from 'fs/promises'; import path from 'path'; +import { fileURLToPath } from 'url'; import { run, step } from '.'; import { API_ERRORS, ApiError, ApiErrorCode } from '../src/lib/api/errors'; const ALL_METHODS = ['delete', 'get', 'head', 'patch', 'post', 'put']; -const GEN_PATH = path.resolve(__dirname, '..', 'openapi.json'); +const GEN_PATH = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', 'openapi.json'); const ALL_ERRORS = Object.keys(API_ERRORS) .map((code) => new ApiError(Number(code) as ApiErrorCode).toJSON()) diff --git a/src/ctl/commands/export-config.ts b/src/ctl/commands/export-config.ts index 03e04db9..5665e706 100644 --- a/src/ctl/commands/export-config.ts +++ b/src/ctl/commands/export-config.ts @@ -4,6 +4,7 @@ import { DATABASE_TO_PROP } from '@/lib/config/read/db'; import { ENVS } from '@/lib/config/read/env'; import { getProperty } from '@/lib/config/read/transform'; import { validateConfigObject } from '@/lib/config/validate'; +import { prisma } from '@/lib/db'; import { randomCharacters } from '@/lib/random'; function convertValueToEnv( @@ -57,8 +58,6 @@ export async function exportConfig({ yml, showDefaults }: { yml?: boolean; showD await reloadSettings(); - const { prisma } = await import('@/lib/db/index.js'); - const ziplineTable = await prisma.zipline.findFirst({ omit: { id: true, diff --git a/src/ctl/commands/import-dir.ts b/src/ctl/commands/import-dir.ts index 485000e3..6ac4a2e4 100644 --- a/src/ctl/commands/import-dir.ts +++ b/src/ctl/commands/import-dir.ts @@ -1,5 +1,7 @@ import { bytes } from '@/lib/bytes'; import { config, reloadSettings } from '@/lib/config'; +import { getDatasource } from '@/lib/datasource'; +import { prisma } from '@/lib/db'; import { guess } from '@/lib/mimes'; import { statSync } from 'fs'; import { mkdir, readdir } from 'fs/promises'; @@ -14,7 +16,6 @@ export async function importDir( await reloadSettings(); - const { prisma } = await import('@/lib/db/index.js'); let userId: string; if (id) { @@ -85,7 +86,6 @@ export async function importDir( if (config.datasource.type === 'local') await mkdir(config.datasource.local!.directory, { recursive: true }); - const { getDatasource } = await import('@/lib/datasource/index.js'); const datasource = getDatasource(config); if (!datasource) return console.error('No datasource configured'); diff --git a/src/ctl/commands/list-users.ts b/src/ctl/commands/list-users.ts index be435bf6..ef9e0923 100644 --- a/src/ctl/commands/list-users.ts +++ b/src/ctl/commands/list-users.ts @@ -1,4 +1,5 @@ import { userSelect } from '@/lib/db/models/user'; +import { prisma } from '@/lib/db'; export async function listUsers({ extra, format, id }: { extra?: string[]; format?: boolean; id?: string }) { if (extra?.includes('list')) { @@ -20,7 +21,6 @@ export async function listUsers({ extra, format, id }: { extra?: string[]; forma } } - const { prisma } = await import('@/lib/db/index.js'); const users = await prisma.user.findMany({ where: id ? { id } : undefined, select, diff --git a/src/ctl/commands/read-config.ts b/src/ctl/commands/read-config.ts index 7b1fd739..c23011e8 100644 --- a/src/ctl/commands/read-config.ts +++ b/src/ctl/commands/read-config.ts @@ -1,10 +1,8 @@ -import { reloadSettings } from '@/lib/config'; +import { config, reloadSettings } from '@/lib/config'; export async function readConfig({ format }: { format: boolean }) { await reloadSettings(); - const { config } = await import('@/lib/config/index.js'); - console.log(JSON.stringify(config, null, format ? 2 : 0)); process.exit(0); } diff --git a/src/ctl/commands/set-user.ts b/src/ctl/commands/set-user.ts index 489fafed..1f10d503 100644 --- a/src/ctl/commands/set-user.ts +++ b/src/ctl/commands/set-user.ts @@ -1,10 +1,10 @@ import { hashPassword } from '@/lib/crypto'; +import { prisma } from '@/lib/db'; const SUPPORTED_FIELDS = ['username', 'password', 'role', 'avatar', 'token', 'totpSecret']; export async function setUser(property: string, value: string, { id }: { id: string }) { if (!SUPPORTED_FIELDS.includes(property)) return console.error('Unsupported field:', property); - const { prisma } = await import('@/lib/db/index.js'); const user = await prisma.user.findFirst({ where: { id }, }); diff --git a/src/ctl/index.ts b/src/ctl/index.ts index 43f516e8..ac651d02 100644 --- a/src/ctl/index.ts +++ b/src/ctl/index.ts @@ -1,5 +1,5 @@ import { Command } from 'commander'; -import { version } from '../../package.json'; +import packageJson from '../../package.json' with { type: 'json' }; import { listUsers } from './commands/list-users'; import { readConfig } from './commands/read-config'; import { setUser } from './commands/set-user'; @@ -8,7 +8,7 @@ import { exportConfig } from './commands/export-config'; const cli = new Command(); -cli.name('ziplinectl').version(version).description('controll utility for zipline'); +cli.name('ziplinectl').version(packageJson.version).description('control utility for zipline'); cli .command('read-config') diff --git a/src/lib/config/safe.ts b/src/lib/config/safe.ts index 1f71bf02..d7f185a2 100644 --- a/src/lib/config/safe.ts +++ b/src/lib/config/safe.ts @@ -1,5 +1,5 @@ import enabled from '../oauth/enabled'; -import { version } from '../../../package.json'; +import { version } from '../version'; import { Config } from './validate'; export type SafeConfig = Omit< diff --git a/src/lib/db/migration/index.ts b/src/lib/db/migration/index.ts index a13c7979..c6e26448 100644 --- a/src/lib/db/migration/index.ts +++ b/src/lib/db/migration/index.ts @@ -1,15 +1,19 @@ -import { Migrate } from '@prisma/migrate'; +import migratePkg from '@prisma/migrate'; import { defineConfig } from 'prisma/config'; -import { +import prismaInternals from '@prisma/internals'; +import { log } from '@/lib/logger'; + +import ensureDatabaseExistsPkg from '@prisma/migrate/dist/utils/ensureDatabaseExists.js'; + +const { Migrate } = migratePkg; +const { ensureDatabaseExists } = ensureDatabaseExistsPkg; +const { createSchemaPathInput, getSchemaDatasourceProvider, inferDirectoryConfig, loadSchemaContext, validatePrismaConfigWithDatasource, -} from '@prisma/internals'; -import { log } from '@/lib/logger'; - -import { ensureDatabaseExists } from '@prisma/migrate/dist/utils/ensureDatabaseExists'; +} = prismaInternals; export async function runMigrations() { const baseDir = process.cwd(); diff --git a/src/lib/db/migration/prisma-migrate.d.ts b/src/lib/db/migration/prisma-migrate.d.ts index 5ff47d28..e2f0903d 100644 --- a/src/lib/db/migration/prisma-migrate.d.ts +++ b/src/lib/db/migration/prisma-migrate.d.ts @@ -2,6 +2,6 @@ declare module '@prisma/migrate' { export * from '@prisma/migrate/dist/src/index'; } -declare module '@prisma/migrate/dist/utils/ensureDatabaseExists' { +declare module '@prisma/migrate/dist/utils/ensureDatabaseExists.js' { export * from '@prisma/migrate/dist/src/utils/ensureDatabaseExists'; } diff --git a/src/lib/tasks/index.ts b/src/lib/tasks/index.ts index 391b95de..5ab45614 100644 --- a/src/lib/tasks/index.ts +++ b/src/lib/tasks/index.ts @@ -2,6 +2,7 @@ import { Worker } from 'worker_threads'; import Logger, { log } from '../logger'; import { config } from '../config'; import { MAX_SAFE_TIMEOUT_MS } from '../config/validate'; +import { createWorker } from '../worker'; export interface Task { id: string; @@ -112,7 +113,7 @@ export class Tasks { task.data.config = config; } - const worker = new Worker(task.path, { + const worker = createWorker(task.path, { workerData: task.data, }); diff --git a/src/lib/theme/file.ts b/src/lib/theme/file.ts index 2bab9e98..82a34284 100644 --- a/src/lib/theme/file.ts +++ b/src/lib/theme/file.ts @@ -3,21 +3,21 @@ import { basename, join } from 'path'; import { ZiplineTheme } from '.'; import { exists } from '../fs'; -import dark_gray from './builtins/dark_gray.theme.json'; -import light_gray from './builtins/light_gray.theme.json'; -import black_dark from './builtins/black_dark.theme.json'; +import dark_gray from './builtins/dark_gray.theme.json' with { type: 'json' }; +import light_gray from './builtins/light_gray.theme.json' with { type: 'json' }; +import black_dark from './builtins/black_dark.theme.json' with { type: 'json' }; -import light_blue from './builtins/light_blue.theme.json'; -import dark_blue from './builtins/dark_blue.theme.json'; +import light_blue from './builtins/light_blue.theme.json' with { type: 'json' }; +import dark_blue from './builtins/dark_blue.theme.json' with { type: 'json' }; -import cat_frappe from './builtins/catppuccin_frappe.theme.json'; -import cat_latte from './builtins/catppuccin_latte.theme.json'; -import cat_macchiato from './builtins/catppuccin_macchiato.theme.json'; -import cat_mocha from './builtins/catppuccin_mocha.theme.json'; +import cat_frappe from './builtins/catppuccin_frappe.theme.json' with { type: 'json' }; +import cat_latte from './builtins/catppuccin_latte.theme.json' with { type: 'json' }; +import cat_macchiato from './builtins/catppuccin_macchiato.theme.json' with { type: 'json' }; +import cat_mocha from './builtins/catppuccin_mocha.theme.json' with { type: 'json' }; -import midnight_orange from './builtins/midnight_orange.theme.json'; -import midnight_blue from './builtins/midnight_blue.theme.json'; -import midnight_purple from './builtins/midnight_purple.theme.json'; +import midnight_orange from './builtins/midnight_orange.theme.json' with { type: 'json' }; +import midnight_blue from './builtins/midnight_blue.theme.json' with { type: 'json' }; +import midnight_purple from './builtins/midnight_purple.theme.json' with { type: 'json' }; import { log } from '../logger'; diff --git a/src/lib/version.ts b/src/lib/version.ts index 7fa0a98e..59869424 100644 --- a/src/lib/version.ts +++ b/src/lib/version.ts @@ -1,9 +1,11 @@ -import { version } from '../../package.json'; +import packageJson from '../../package.json' with { type: 'json' }; import { execSync } from 'child_process'; import { log } from './logger'; const logger = log('version'); +export const version = packageJson.version; + export function gitSha() { const envValue = process.env.ZIPLINE_GIT_SHA; if (envValue && envValue !== 'unknown') return envValue; diff --git a/src/lib/worker.ts b/src/lib/worker.ts new file mode 100644 index 00000000..96a9dd64 --- /dev/null +++ b/src/lib/worker.ts @@ -0,0 +1,12 @@ +import { Worker, type WorkerOptions } from 'worker_threads'; + +export function workerUrl(pathFromRoot: string) { + return new URL(pathFromRoot, new URL('../../build/', import.meta.url)); +} + +export function createWorker(pathFromRoot: string, options: WorkerOptions) { + return new Worker(workerUrl(pathFromRoot), { + ...options, + type: 'module', + } as WorkerOptions); +} diff --git a/src/server/index.ts b/src/server/index.ts index a04828e0..e5a11d42 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -4,10 +4,10 @@ import { getDatasource } from '@/lib/datasource'; import { runMigrations } from '@/lib/db/migration'; import { log } from '@/lib/logger'; import type { Tasks } from '@/lib/tasks'; +import { version } from '@/lib/version'; import fastify from 'fastify'; import { ZodTypeProvider } from 'fastify-type-provider-zod'; import { mkdir } from 'fs/promises'; -import { version } from '../../package.json'; import { registerHandlers } from './startup/handlers'; import { listenServer } from './startup/listen'; import { startMemoryLog } from './startup/memory'; diff --git a/src/server/plugins/vite.ts b/src/server/plugins/vite.ts index bdac5fc8..e4de3fb6 100644 --- a/src/server/plugins/vite.ts +++ b/src/server/plugins/vite.ts @@ -69,8 +69,6 @@ async function vitePlugin(fastify: FastifyInstance) { template = await readFile(resolve(`./src/client/ssr-${type}/`, 'index.html'), 'utf-8'); template = await fastify.vite.transformIndexHtml(url, template); - // expose __dirname since dev modules are loaded in esm - global.__dirname = __dirname; render = (await fastify.vite.ssrLoadModule(`/ssr-${type}/server.tsx`)).render; } else { template = await readFile(resolve('./build', `client/ssr-${type}/index.html`), 'utf-8'); diff --git a/src/server/routes/api/server/export.ts b/src/server/routes/api/server/export.ts index 91cf3ab4..5a0a907b 100644 --- a/src/server/routes/api/server/export.ts +++ b/src/server/routes/api/server/export.ts @@ -9,7 +9,7 @@ import { zQsBoolean } from '@/lib/validation'; import typedPlugin from '@/server/typedPlugin'; import { cpus, hostname, platform, release } from 'os'; import z from 'zod'; -import { version } from '../../../../../package.json'; +import { version } from '@/lib/version'; const exportCountsSchema = z.object({ users: z.number(), diff --git a/src/server/routes/api/upload/partial.ts b/src/server/routes/api/upload/partial.ts index 00b27711..ea471cf8 100644 --- a/src/server/routes/api/upload/partial.ts +++ b/src/server/routes/api/upload/partial.ts @@ -16,7 +16,7 @@ import typedPlugin from '@/server/typedPlugin'; import { z } from 'zod'; import { readdir, rename, rm } from 'fs/promises'; import { join } from 'path'; -import { Worker } from 'worker_threads'; +import { createWorker } from '@/lib/worker'; import { ApiUploadResponse } from '.'; const logger = log('api').c('upload').c('partial'); @@ -234,7 +234,7 @@ export default typedPlugin( config.files.route === '/' || config.files.route === '' ? '' : `${config.files.route}` }/${fileUpload.name}`; - const worker = new Worker('./build/offload/partial.js', { + const worker = createWorker('offload/partial.js', { workerData: { user: { id: req.user ? req.user.id : options.folder ? folder?.userId : undefined, diff --git a/src/server/startup/plugins.ts b/src/server/startup/plugins.ts index 231a51e6..49a872e3 100644 --- a/src/server/startup/plugins.ts +++ b/src/server/startup/plugins.ts @@ -10,7 +10,7 @@ import { fastifyStatic } from '@fastify/static'; import fastifySwagger from '@fastify/swagger'; import type { FastifyInstance } from 'fastify'; import { jsonSchemaTransform, serializerCompiler, validatorCompiler } from 'fastify-type-provider-zod'; -import { version } from '../../../package.json'; +import { version } from '@/lib/version'; import { checkRateLimit } from '../plugins/checkRateLimit'; import oauthPlugin from '../plugins/oauth'; import vitePlugin from '../plugins/vite'; diff --git a/src/server/startup/tasks.ts b/src/server/startup/tasks.ts index ac3df400..2e8490b7 100644 --- a/src/server/startup/tasks.ts +++ b/src/server/startup/tasks.ts @@ -32,7 +32,7 @@ export function startTasks(server: FastifyInstance) { for (let i = 0; i !== config.features.thumbnails.num_threads; ++i) { tasks.worker( `thumbnail-${i}`, - './build/offload/thumbnails.js', + 'offload/thumbnails.js', { id: `thumbnail-${i}`, enabled: config.features.thumbnails.enabled, diff --git a/tsup.config.ts b/tsup.config.ts index e02cb016..eb92e4c9 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -7,14 +7,14 @@ export default defineConfig(async (_) => { return [ { platform: 'node', - format: 'cjs', + format: 'esm', outExtension: () => ({ js: '.js' }), clean: true, sourcemap: true, entry: await glob('./src/**/*.ts', { ignore: ['./src/components/**/*.ts', './src/client/**/*.(ts|tsx|html)'], }), - shims: true, + shims: false, esbuildPlugins: [], outDir: 'build', bundle: false, @@ -23,6 +23,8 @@ export default defineConfig(async (_) => { await replaceTscAliasPaths({ configFile: 'tsconfig.json', outDir: 'build', + resolveFullPaths: true, + resolveFullExtension: '.js', }); console.log('[built-ins] copying builtins...'); diff --git a/vite.config.ts b/vite.config.ts index 77289aa8..ece49346 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,7 +1,10 @@ import react from '@vitejs/plugin-react'; -import path from 'path'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; import { defineConfig } from 'vite'; +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + export default defineConfig(({ mode }) => { const isSSR = mode.startsWith('ssr'); @@ -18,7 +21,7 @@ export default defineConfig(({ mode }) => { 'ssr-view-url': path.resolve(__dirname, 'src/client/ssr-view-url/index.html'), }, output: { - format: isSSR ? 'cjs' : 'esm', + format: 'esm', entryFileNames: isSSR ? `${mode}.js` : 'assets/[name]-[hash].js', }, },