From 13cc37d5a2997e950c604f1611b2d4068da32670 Mon Sep 17 00:00:00 2001 From: C4illin Date: Sun, 19 May 2024 23:51:27 +0200 Subject: [PATCH] start on pandoc --- .dockerignore | 15 + .eslintrc.cjs | 96 +++---- .gitignore | 3 +- Dockerfile | 45 +++ README.Docker.md | 22 ++ biome.json | 118 ++++---- bun.lockb | Bin 99577 -> 99545 bytes compose.yaml | 10 + package.json | 1 - prettier.config.cjs | 1 - src/components/base.tsx | 2 +- src/components/header.tsx | 2 +- src/converters/main.ts | 113 +++++--- src/converters/pandoc.ts | 17 ++ src/index.tsx | 581 ++++++++++++++++++++++++++------------ src/pages/register.html | 36 --- src/public/script.js | 126 +++++---- tsconfig.json | 2 +- 18 files changed, 782 insertions(+), 408 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 README.Docker.md create mode 100644 compose.yaml create mode 100644 src/converters/pandoc.ts delete mode 100644 src/pages/register.html diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..9b49524 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,15 @@ +node_modules +Dockerfile* +docker-compose* +.dockerignore +.git +.gitignore +README.md +LICENSE +.vscode +Makefile +helm-charts +.env +.editorconfig +.idea +coverage* \ No newline at end of file diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 4e89d35..c6d2a93 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -1,55 +1,55 @@ /** @type {import("eslint").Linter.Config} */ const config = { - root: true, - parser: "@typescript-eslint/parser", - plugins: ["isaacscript", "import"], - extends: [ - "plugin:@typescript-eslint/recommended-type-checked", - "plugin:@typescript-eslint/stylistic-type-checked", - "plugin:prettier/recommended", - ], - parserOptions: { - ecmaVersion: "latest", - sourceType: "module", - tsconfigRootDir: __dirname, - project: [ - "./tsconfig.json", - "./cli/tsconfig.eslint.json", // separate eslint config for the CLI since we want to lint and typecheck differently due to template files - "./upgrade/tsconfig.json", - "./www/tsconfig.json", - ], - }, - overrides: [ - // Template files don't have reliable type information - { - files: ["./cli/template/**/*.{ts,tsx}"], - extends: ["plugin:@typescript-eslint/disable-type-checked"], - }, - ], - rules: { - // These off/not-configured-the-way-we-want lint rules we like & opt into - "@typescript-eslint/no-explicit-any": "error", - "@typescript-eslint/no-unused-vars": [ - "error", - { argsIgnorePattern: "^_", destructuredArrayIgnorePattern: "^_" }, - ], - "@typescript-eslint/consistent-type-imports": [ - "error", - { prefer: "type-imports", fixStyle: "inline-type-imports" }, - ], - "import/consistent-type-specifier-style": ["error", "prefer-inline"], + root: true, + parser: "@typescript-eslint/parser", + plugins: ["isaacscript", "import"], + extends: [ + "plugin:@typescript-eslint/recommended-type-checked", + "plugin:@typescript-eslint/stylistic-type-checked", + "plugin:prettier/recommended", + ], + parserOptions: { + ecmaVersion: "latest", + sourceType: "module", + tsconfigRootDir: __dirname, + project: [ + "./tsconfig.json", + "./cli/tsconfig.eslint.json", // separate eslint config for the CLI since we want to lint and typecheck differently due to template files + "./upgrade/tsconfig.json", + "./www/tsconfig.json", + ], + }, + overrides: [ + // Template files don't have reliable type information + { + files: ["./cli/template/**/*.{ts,tsx}"], + extends: ["plugin:@typescript-eslint/disable-type-checked"], + }, + ], + rules: { + // These off/not-configured-the-way-we-want lint rules we like & opt into + "@typescript-eslint/no-explicit-any": "error", + "@typescript-eslint/no-unused-vars": [ + "error", + { argsIgnorePattern: "^_", destructuredArrayIgnorePattern: "^_" }, + ], + "@typescript-eslint/consistent-type-imports": [ + "error", + { prefer: "type-imports", fixStyle: "inline-type-imports" }, + ], + "import/consistent-type-specifier-style": ["error", "prefer-inline"], - // For educational purposes we format our comments/jsdoc nicely - "isaacscript/complete-sentences-jsdoc": "warn", - "isaacscript/format-jsdoc-comments": "warn", + // For educational purposes we format our comments/jsdoc nicely + "isaacscript/complete-sentences-jsdoc": "warn", + "isaacscript/format-jsdoc-comments": "warn", - // These lint rules don't make sense for us but are enabled in the preset configs - "@typescript-eslint/no-confusing-void-expression": "off", - "@typescript-eslint/restrict-template-expressions": "off", + // These lint rules don't make sense for us but are enabled in the preset configs + "@typescript-eslint/no-confusing-void-expression": "off", + "@typescript-eslint/restrict-template-expressions": "off", - // This rule doesn't seem to be working properly - "@typescript-eslint/prefer-nullish-coalescing": "off", - }, + // This rule doesn't seem to be working properly + "@typescript-eslint/prefer-nullish-coalescing": "off", + }, }; -module.exports = config; \ No newline at end of file +module.exports = config; diff --git a/.gitignore b/.gitignore index d493115..1bd4c7c 100644 --- a/.gitignore +++ b/.gitignore @@ -45,4 +45,5 @@ package-lock.json /mydb.sqlite /output /db -/data \ No newline at end of file +/data +/Bruno \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..523c247 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,45 @@ +# use the official Bun image +# see all versions at https://hub.docker.com/r/oven/bun/tags +FROM oven/bun:1-debian as base +WORKDIR /app + +# install dependencies into temp directory +# this will cache them and speed up future builds +FROM base AS install +RUN mkdir -p /temp/dev +COPY package.json bun.lockb /temp/dev/ +RUN cd /temp/dev && bun install --frozen-lockfile + +# install with --production (exclude devDependencies) +RUN mkdir -p /temp/prod +COPY package.json bun.lockb /temp/prod/ +RUN cd /temp/prod && bun install --frozen-lockfile --production + +# install pandoc +RUN apt-get update && apt-get install -y pandoc + +# copy node_modules from temp directory +# then copy all (non-ignored) project files into the image +FROM base AS prerelease +COPY --from=install /temp/dev/node_modules node_modules +COPY . . + +# [optional] tests & build +ENV NODE_ENV=production +# RUN bun test +# RUN bun run build + +# copy production dependencies and source code into final image +FROM base AS release +COPY --from=install /temp/prod/node_modules node_modules +COPY --from=prerelease /app/src/index.tsx /app/src/ +COPY --from=prerelease /app/package.json . +COPY . . + +# copy pandoc +COPY --from=install /usr/bin/pandoc /usr/bin/pandoc + +# run the app +USER bun +EXPOSE 3000/tcp +ENTRYPOINT [ "bun", "run", "./src/index.tsx" ] \ No newline at end of file diff --git a/README.Docker.md b/README.Docker.md new file mode 100644 index 0000000..fa3048f --- /dev/null +++ b/README.Docker.md @@ -0,0 +1,22 @@ +### Building and running your application + +When you're ready, start your application by running: +`docker compose up --build`. + +Your application will be available at http://localhost:3000. + +### Deploying your application to the cloud + +First, build your image, e.g.: `docker build -t myapp .`. +If your cloud uses a different CPU architecture than your development +machine (e.g., you are on a Mac M1 and your cloud provider is amd64), +you'll want to build the image for that platform, e.g.: +`docker build --platform=linux/amd64 -t myapp .`. + +Then, push it to your registry, e.g. `docker push myregistry.com/myapp`. + +Consult Docker's [getting started](https://docs.docker.com/go/get-started-sharing/) +docs for more detail on building and pushing. + +### References +* [Docker's Node.js guide](https://docs.docker.com/language/nodejs/) \ No newline at end of file diff --git a/biome.json b/biome.json index 168c992..4ded3d0 100644 --- a/biome.json +++ b/biome.json @@ -1,62 +1,60 @@ { - "$schema": "https://biomejs.dev/schemas/1.7.3/schema.json", - "formatter": { - "enabled": true, - "formatWithErrors": true, - "indentStyle": "space", - "indentWidth": 2, - "lineEnding": "lf", - "lineWidth": 80, - "attributePosition": "auto" - }, - "organizeImports": { "enabled": true }, - "linter": { - "enabled": true, - "rules": { - "recommended": false, - "complexity": { - "noBannedTypes": "error", - "noUselessThisAlias": "error", - "noUselessTypeConstraint": "error", - "useArrowFunction": "off", - "useLiteralKeys": "error", - "useOptionalChain": "error" - }, - "correctness": { "noPrecisionLoss": "error", "noUnusedVariables": "off" }, - "style": { - "noInferrableTypes": "error", - "noNamespace": "error", - "useAsConstAssertion": "error", - "useBlockStatements": "off", - "useConsistentArrayType": "error", - "useForOf": "error", - "useImportType": "error", - "useShorthandFunctionType": "error" - }, - "suspicious": { - "noEmptyBlockStatements": "error", - "noEmptyInterface": "error", - "noExplicitAny": "error", - "noExtraNonNullAssertion": "error", - "noMisleadingInstantiator": "error", - "noUnsafeDeclarationMerging": "error", - "useAwait": "error", - "useNamespaceKeyword": "error" - } - } - }, - "javascript": { - "formatter": { - "jsxQuoteStyle": "double", - "quoteProperties": "asNeeded", - "trailingComma": "all", - "semicolons": "always", - "arrowParentheses": "always", - "bracketSpacing": true, - "bracketSameLine": false, - "quoteStyle": "double", - "attributePosition": "auto" - } - }, - "overrides": [{ "include": ["./cli/template/**/*.{ts,tsx}"] }] + "$schema": "https://biomejs.dev/schemas/1.7.3/schema.json", + "formatter": { + "enabled": true, + "formatWithErrors": true, + "indentStyle": "space", + "indentWidth": 2, + "lineEnding": "lf", + "lineWidth": 80, + "attributePosition": "auto" + }, + "organizeImports": { "enabled": true }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "complexity": { + "noBannedTypes": "error", + "noUselessThisAlias": "error", + "noUselessTypeConstraint": "error", + "useArrowFunction": "off", + "useLiteralKeys": "error", + "useOptionalChain": "error" + }, + "correctness": { "noPrecisionLoss": "error", "noUnusedVariables": "off" }, + "style": { + "noInferrableTypes": "error", + "noNamespace": "error", + "useAsConstAssertion": "error", + "useBlockStatements": "off", + "useConsistentArrayType": "error", + "useForOf": "error", + "useImportType": "error", + "useShorthandFunctionType": "error" + }, + "suspicious": { + "noEmptyBlockStatements": "error", + "noEmptyInterface": "error", + "noExplicitAny": "error", + "noExtraNonNullAssertion": "error", + "noMisleadingInstantiator": "error", + "noUnsafeDeclarationMerging": "error", + "useAwait": "error", + "useNamespaceKeyword": "error" + } + } + }, + "javascript": { + "formatter": { + "jsxQuoteStyle": "double", + "quoteProperties": "asNeeded", + "semicolons": "always", + "arrowParentheses": "always", + "bracketSpacing": true, + "bracketSameLine": false, + "quoteStyle": "double", + "attributePosition": "auto" + } + } } diff --git a/bun.lockb b/bun.lockb index ec4e08752a4f10fd2a1c71a4acbf3c23a9d48c04..0d538dc0aee01d3791588e35eee15c474b32bc67 100644 GIT binary patch delta 11661 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 ( {title} - +