diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..cbd5164 --- /dev/null +++ b/.env.example @@ -0,0 +1,7 @@ +## SERVER: +# Set log verbosity [3]:integer +# (0=none <- 1=error <- 2=warn <- 3=info <- 4=debug) +#LOGLEVEL=3 + +# Port for the server [3000]:integer +#PORT=3000 \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml index a42e059..1302829 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,4 +6,4 @@ updates: schedule: interval: weekly labels: - - dependency:gha \ No newline at end of file + - dependency:gha diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a23f4df..abca265 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,7 +23,7 @@ jobs: container: if: ${{ github.repository_owner == 'jspaste' && inputs.image-action != 'none' }} name: Build container image - runs-on: ubuntu-24.04 + runs-on: ubuntu-latest env: REGISTRY: ghcr.io @@ -34,7 +34,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 with: egress-policy: audit @@ -66,12 +66,12 @@ jobs: echo "tags=${TAGS[*]}" >>"$GITHUB_OUTPUT" - name: Checkout - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - - name: Setup production dependencies - run: bun install --frozen-lockfile --production + - name: Setup dependencies + run: bun install --frozen-lockfile - name: Run build:server run: bun run build:server @@ -106,8 +106,8 @@ jobs: - if: ${{ inputs.image-action == 'build-release' }} name: Attest image - uses: actions/attest-build-provenance@1c608d11d69870c2092266b3f9a6f3abbf17002c # v1.4.3 + uses: actions/attest-build-provenance@ef244123eb79f2f7a7e75d99086184180e6d0018 # v1.4.4 with: - subject-name: ${{ env.REGISTRY }}/${{ steps.build-image.outputs.image }} + subject-name: "${{ env.REGISTRY }}/${{ steps.build-image.outputs.image }}" subject-digest: ${{ steps.push-image.outputs.digest }} - push-to-registry: false \ No newline at end of file + push-to-registry: false diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml deleted file mode 100644 index 4a7a444..0000000 --- a/.github/workflows/security.yml +++ /dev/null @@ -1,87 +0,0 @@ -name: CI -> Security -on: - branch_protection_rule: - schedule: - - cron: 33 3 * * 1 - - push: - branches: - - dev - paths-ignore: - - '*.md' - - '.*ignore' - - pull_request: - branches: - - dev - paths-ignore: - - '*.md' - - '.*ignore' - -permissions: read-all - -jobs: - codeql: - name: CodeQL - runs-on: ubuntu-24.04 - strategy: - fail-fast: false - matrix: - include: - - language: javascript-typescript - build-mode: none - - permissions: - security-events: write - - steps: - - name: Harden Runner - uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 - with: - egress-policy: audit - - - name: Checkout - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - with: - persist-credentials: false - - - name: Setup CodeQL - uses: github/codeql-action/init@6db8d6351fd0be61f9ed8ebd12ccd35dcec51fea # v3.26.11 - with: - languages: ${{ matrix.language }} - build-mode: ${{ matrix.build-mode }} - - - name: Run analysis - uses: github/codeql-action/analyze@6db8d6351fd0be61f9ed8ebd12ccd35dcec51fea # v3.26.11 - with: - category: /language:${{matrix.language}} - - scoreboard: - name: Scorecard - runs-on: ubuntu-24.04 - permissions: - security-events: write - id-token: write - - steps: - - name: Harden Runner - uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 - with: - egress-policy: audit - - - name: Checkout - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - with: - persist-credentials: false - - - name: Run analysis - uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0 - with: - results_file: scoreboard.sarif - results_format: sarif - publish_results: true - - - name: Upload to code-scanning - uses: github/codeql-action/upload-sarif@6db8d6351fd0be61f9ed8ebd12ccd35dcec51fea # v3.26.11 - with: - sarif_file: scoreboard.sarif \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2aea9f9..a8af781 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -23,12 +23,12 @@ permissions: contents: read jobs: - lint: + test: name: Test - runs-on: ubuntu-24.04 + runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 with: egress-policy: audit @@ -36,18 +36,15 @@ jobs: uses: oven-sh/setup-bun@4bc047ad259df6fc24a6c9b0f9a0cb08cf17fbe5 # v2.0.1 - name: Checkout - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - - name: Setup production dependencies - run: bun install --frozen-lockfile --production + - name: Setup dependencies + run: bun install --frozen-lockfile - name: Run build:server run: bun run build:server - - name: Setup development dependencies - run: bun install --frozen-lockfile - - name: Run lint - run: bun run lint \ No newline at end of file + run: bun run lint diff --git a/Dockerfile b/Dockerfile index d26ac90..2874ca6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM docker.io/oven/bun:1-distroless WORKDIR /frontend/ -COPY --chown=nonroot ./dist ./ +COPY --chown=nonroot ./dist/ ./ COPY --chown=nonroot ./LICENSE ./ LABEL org.opencontainers.image.url="https://jspaste.eu" \ diff --git a/README.md b/README.md index a57dcdd..899b25b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,14 @@ # Frontend This repository contains the frontend code for [JSPaste](https://jspaste.eu). + +## Supported Browsers + +In case you are using an older browser than the ones listed, please do not open issues about it. + +- Chromium >= 111 +- Firefox >= 121 +- Safari >= 16 + +*Last checked commit +[`5982621`](https://github.com/JSPaste/Frontend/commit/59826216a47e08e0f150486b6cf983ba7cd8fa57) on 27 October 2024* \ No newline at end of file diff --git a/biome.json b/biome.json index bb47e78..ce7a1b0 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/1.9.3/schema.json", + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", "files": { "ignore": ["./dist/**", "*.spec.ts"], "ignoreUnknown": true @@ -26,7 +26,8 @@ "noStaticOnlyClass": "off" }, "suspicious": { - "noAssignInExpressions": "off" + "noAssignInExpressions": "off", + "noConsoleLog": "error" } } }, diff --git a/bun.lockb b/bun.lockb index 8cd6ba1..f6516be 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/bunfig.toml b/bunfig.toml index 0fe4641..c112426 100644 --- a/bunfig.toml +++ b/bunfig.toml @@ -5,4 +5,4 @@ auto = "disable" [run] bun = true -silent = true \ No newline at end of file +silent = true diff --git a/package.json b/package.json index 9527ca9..486c2df 100644 --- a/package.json +++ b/package.json @@ -5,9 +5,16 @@ "license": "EUPL-1.2", "type": "module", "scripts": { + "analyse:bundle": "bun run analyze:bundle", + "analyze:bundle": "rm -rf ./dist/ && bun vite build && mv ./dist/client/bundle.html ./dist/client.bundle.html && mv ./dist/server/bundle.html ./dist/server.bundle.html", "build": "bun run build:server", - "build:server": "rm -rf ./dist/ && bun --bun vite build && bun run script:standalone && bun run script:minify && bun run script:compress", - "build:server:safe": "rm -rf ./dist/ && bun --bun vite build && bun run script:standalone", + "build:server": "rm -rf ./dist/ && bun vite build && bun run script:standalone && bun run script:compress", + "clean:git:all": "bun run clean:git:untracked && bun run clean:git:gc && bun run clean:git:hooks", + "clean:git:all:force": "bun run clean:git:untracked:force && bun run clean:git:gc && bun run clean:git:hooks", + "clean:git:gc": "git gc --aggressive --prune", + "clean:git:hooks": "rm -rf ./.git/hooks/ && bun install -f", + "clean:git:untracked": "git clean -d -x -i", + "clean:git:untracked:force": "git clean -d -x -f", "dev": "bun run start:dev", "fix": "bun run fix:biome; bun run fix:package", "fix:biome": "bun biome check --write", @@ -16,18 +23,15 @@ "lint:biome": "bun biome lint", "lint:tsc": "bun tsc --noEmit", "script:compress": "bun run ./scripts/compress.ts", - "script:minify": "bun run ./scripts/minify.ts", "script:standalone": "bun run ./scripts/standalone.ts", "start": "bun run start:server", - "start:dev": "bun --bun vite", + "start:dev": "bun vite", "start:rebuild": "bun run build:server && bun run start:server", - "start:rebuild:safe": "bun run build:server:safe && bun run start:server", "start:server": "NODE_ENV=production bun run --cwd=./dist/ ./server/index.js" }, "dependencies": { - "@codemirror/autocomplete": "~6.18.1", - "@codemirror/commands": "~6.7.0", - "@codemirror/lang-angular": "~0.1.3", + "@codemirror/autocomplete": "~6.18.3", + "@codemirror/commands": "~6.7.1", "@codemirror/lang-cpp": "~6.0.2", "@codemirror/lang-css": "~6.3.0", "@codemirror/lang-go": "~6.0.1", @@ -35,41 +39,41 @@ "@codemirror/lang-java": "~6.0.1", "@codemirror/lang-javascript": "~6.2.2", "@codemirror/lang-json": "~6.0.1", - "@codemirror/lang-liquid": "~6.2.1", - "@codemirror/lang-markdown": "~6.3.0", + "@codemirror/lang-markdown": "~6.3.1", "@codemirror/lang-php": "~6.0.1", "@codemirror/lang-python": "~6.1.6", "@codemirror/lang-rust": "~6.0.1", - "@codemirror/lang-sass": "~6.0.2", - "@codemirror/lang-vue": "~0.1.3", "@codemirror/lang-xml": "~6.1.0", "@codemirror/lang-yaml": "~6.1.1", "@codemirror/language": "~6.10.3", "@codemirror/state": "~6.4.1", - "@codemirror/view": "~6.34.1", + "@codemirror/view": "~6.35.0", "@solid-primitives/media": "~2.2.9", - "@solid-primitives/scheduled": "~1.4.3", + "@solid-primitives/scheduled": "~1.4.4", "@solid-primitives/storage": "~4.2.1", - "@tabler/icons-solidjs": "~3.19.0", - "@types/bun": "~1.1.10", - "@uiw/codemirror-extensions-hyper-link": "~4.23.5", - "@uiw/codemirror-themes": "~4.23.5", - "autoprefixer": "~10.4.20", - "chalk": "~5.3.0", - "daisyui": "~4.12.12", - "loglevel": "~1.9.2", - "postcss": "~8.4.47", - "solid-js": "~1.9.2", - "tailwindcss": "~3.4.13", - "typescript": "~5.6.2", - "vike": "~0.4.198", + "@tabler/icons-solidjs": "~3.22.0", + "@tailwindcss/vite": "4.0.0-beta.2", + "@uiw/codemirror-extensions-hyper-link": "~4.23.6", + "@uiw/codemirror-themes": "~4.23.6", + "daisyui": "5.0.0-alpha.39", + "env-var": "~7.5.0", + "hono": "~4.6.11", + "solid-js": "~1.9.3", + "tailwindcss": "4.0.0-beta.2", + "vike": "~0.4.204", "vike-solid": "~0.7.6", - "vite": "~5.4.8" + "vite": "~5.4.11" }, "devDependencies": { - "@biomejs/biome": "~1.9.3", - "lefthook": "~1.7.18", - "sort-package-json": "~2.10.1" + "@biomejs/biome": "~1.9.4", + "@types/bun": "^1.1.13", + "browserslist": "^4.24.2", + "lefthook": "~1.8.4", + "rollup-plugin-visualizer": "~5.12.0", + "sort-package-json": "~2.11.0" + }, + "peerDependencies": { + "typescript": "5.5.4" }, "trustedDependencies": [ "@biomejs/biome", diff --git a/postcss.config.js b/postcss.config.js deleted file mode 100644 index 0f77216..0000000 --- a/postcss.config.js +++ /dev/null @@ -1,6 +0,0 @@ -export default { - plugins: { - tailwindcss: {}, - autoprefixer: {} - } -}; diff --git a/scripts/compress.ts b/scripts/compress.ts index 3a4324c..3a299eb 100644 --- a/scripts/compress.ts +++ b/scripts/compress.ts @@ -1,5 +1,5 @@ import { brotliCompressSync, gzipSync } from 'node:zlib'; -import { findFiles, writeFile } from './utils.ts'; +import { findFiles, writeFile } from '@x-util/fs.ts'; const rootClientDirectory = './dist/client/'; const relativeClientFiles = await findFiles(rootClientDirectory, undefined, /\.(js|mjs|cjs|json|css|html|wasm|svg)$/); @@ -9,15 +9,9 @@ await Promise.all( rootClientFiles.map(async (file) => { const fileContent = await Bun.file(file).arrayBuffer(); - console.debug('[COMPRESS] Compressing:', file); + console.info('[BUILD] Compressing:', file); - await writeFile( - `${file}.gz`, - gzipSync(fileContent, { - level: 9 - }).buffer - ); - - await writeFile(`${file}.br`, brotliCompressSync(fileContent).buffer); + await writeFile(`${file}.gz`, gzipSync(fileContent)); + await writeFile(`${file}.br`, brotliCompressSync(fileContent)); }) ); diff --git a/scripts/minify.ts b/scripts/minify.ts deleted file mode 100644 index 70898b2..0000000 --- a/scripts/minify.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { findFiles } from './utils.ts'; - -const serverAssets = async () => { - const rootStaticDirectory = './dist/server/'; - const relativeStaticFilesPath = await findFiles(rootStaticDirectory, '**/*.js'); - const rootStaticFilesPath = relativeStaticFilesPath.map((file) => rootStaticDirectory + file); - - const result = await Bun.build({ - entrypoints: rootStaticFilesPath, - target: 'browser', - format: 'esm', - splitting: false, - packages: 'external', - sourcemap: 'none', - minify: true - }); - - if (!result.success) { - console.error(result.logs); - process.exit(1); - } -}; - -const clientAssets = async () => { - const rootStaticDirectory = './dist/client/'; - const relativeStaticFilesPath = await findFiles(rootStaticDirectory, '**/*.js'); - const rootStaticFilesPath = relativeStaticFilesPath.map((file) => rootStaticDirectory + file); - - const result = await Bun.build({ - entrypoints: rootStaticFilesPath, - target: 'browser', - format: 'esm', - splitting: false, - packages: 'external', - sourcemap: 'none', - minify: true - }); - - if (!result.success) { - console.error(result.logs); - process.exit(1); - } -}; - -console.info('[MINIFY] Minifying client...'); -await clientAssets(); -console.info('[MINIFY] Minifying server...'); -await serverAssets(); diff --git a/scripts/standalone.ts b/scripts/standalone.ts index 39789be..3aed567 100644 --- a/scripts/standalone.ts +++ b/scripts/standalone.ts @@ -1,53 +1,37 @@ -import { readdir, rm } from 'node:fs/promises'; -import { dirname } from 'node:path'; import { join } from 'node:path/posix'; -import esbuild from 'esbuild'; +import { $, build } from 'bun'; const root = process.cwd(); const serverOutDir = './dist/server/'; const serverOutDirAbs = join(root, serverOutDir); -const serverEntrypoint = ['./src/server/index.ts']; +const serverEntrypoint = ['./src/server.ts']; const buildStandalone = async () => { - const result = await esbuild.build({ - platform: 'node', - target: 'esnext', - format: 'esm', - bundle: true, - minify: true, - treeShaking: true, - external: ['bun'], - entryPoints: serverEntrypoint, - sourcemap: false, + const result = await build({ + entrypoints: serverEntrypoint, + target: 'bun', outdir: serverOutDirAbs, + format: 'esm', + naming: 'index.js', splitting: false, - allowOverwrite: true, - metafile: true, - logOverride: { 'ignored-bare-import': 'silent' } + sourcemap: 'inline', + minify: true }); - const bundledFilesFromOutDir = Object.keys(result.metafile.inputs).filter( - (relativeFile) => relativeFile.endsWith(relativeFile) && relativeFile.startsWith('dist/') - ); + if (!result.success) { + console.error(result.logs); + process.exit(1); + } - await Promise.all( - bundledFilesFromOutDir.map(async (relativeFile) => { - await rm(join(root, relativeFile)); - }) - ); + // Cleanup + // TODO: https://github.com/oven-sh/bun/pull/15167 + await $`rm -rf ./dist/server/assets/`; + await $`rm -rf ./dist/server/chunks/`; + await $`rm -rf ./dist/server/entries/`; + await $`rm -rf ./dist/server/*.mjs`; - const relativeDirs = new Set(bundledFilesFromOutDir.map((file) => dirname(file))); - for (const relativeDir of relativeDirs) { - const absDir = join(root, relativeDir); - const files = await readdir(absDir); - if (!files.length) { - await rm(absDir, { recursive: true }); - if (relativeDir.startsWith(serverOutDir)) { - relativeDirs.add(dirname(relativeDir)); - } - } - } + await $`rm -rf ./dist/client/bundle.html`; }; -console.info('[STANDALONE] Running...'); +console.info('[BUILD] Creating standalone...'); await buildStandalone(); diff --git a/src/components/Editor.tsx b/src/components/Editor.tsx index da607bb..1ff080e 100644 --- a/src/components/Editor.tsx +++ b/src/components/Editor.tsx @@ -18,8 +18,8 @@ import { import { debounce } from '@solid-primitives/scheduled'; import { hyperLinkExtension, hyperLinkStyle } from '@uiw/codemirror-extensions-hyper-link'; import type { Cursor } from '@x-component/screens/Editor'; -import { editorThemes } from '@x-util/editorThemes'; import { getLanguage, language, theme } from '@x-util/store'; +import { editorThemes } from '@x-util/themes'; import { type Accessor, type Setter, createEffect, createSignal, on, onCleanup, onMount } from 'solid-js'; type EditorProps = { @@ -149,5 +149,5 @@ export default function Editor(props: EditorProps) { setEditorView(undefined); }); - return
; + return
; } diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx index cf016d3..27f87a6 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -24,8 +24,8 @@ export default function Footer(props: FooterProps) { }; return ( -
-
+
+ } label={props.value() ? 'Save' : 'You need to write something to save!'} diff --git a/src/components/Header.tsx b/src/components/Header.tsx index f00228e..365cf5f 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -15,10 +15,13 @@ export default function Header(props: HeaderProps) { label={`Ln ${props.cursor().line.toString().padStart(2, '0')} Col ${props.cursor().column.toString().padStart(2, '0')}`} /> - +
- {/* TODO: Expose Backend API route location */} - } onClick={() => window.open('/api/docs')} /> + } + onClick={() => window.open('https://github.com/jspaste/backend/tree/stable?tab=readme-ov-file#api')} + /> } diff --git a/src/components/HeaderLabel.tsx b/src/components/HeaderLabel.tsx index 4137021..beeaedd 100644 --- a/src/components/HeaderLabel.tsx +++ b/src/components/HeaderLabel.tsx @@ -9,11 +9,9 @@ type HeaderLabelProps = { export default function HeaderLabel(props: HeaderLabelProps) { return (
{props.icon}

{props.label}

diff --git a/src/components/modals/settings/LanguageSection.tsx b/src/components/modals/settings/LanguageSection.tsx index e627ad4..077f565 100644 --- a/src/components/modals/settings/LanguageSection.tsx +++ b/src/components/modals/settings/LanguageSection.tsx @@ -8,11 +8,13 @@ export default function LanguageSection() {

Editor lang:

); diff --git a/src/components/modals/settings/Settings.tsx b/src/components/modals/settings/Settings.tsx index bf9b576..169956e 100644 --- a/src/components/modals/settings/Settings.tsx +++ b/src/components/modals/settings/Settings.tsx @@ -7,8 +7,7 @@ export default function Settings() {