React NextJS with framer motion: Linting, CICD and Full Deploy

Published: Sep 19, 2023 by Isaac Johnson

Coming back to the Vercel based React Nodesite app from last week, I’ll work on adding proper linting. The pnpm already had next lint, but we’ll add in BiomeJS for formatting, linting and more. Then tie that into the CICD pipeline. Lastly, we’ll work out a proper Continuous Deployment flow while using tools from the last blog like K1s, KDash and K9s.

Installing Biome

Let’s first install Biome.

I’ll set to at least NodeJS 16.14

builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ nvm use 16.14.2
Now using node v16.14.2 (npm v8.5.0)

In this host, I lacked pnpm, so I added it back

builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ npm install -g pnpm

changed 1 package, and audited 2 packages in 1s

1 package is looking for funding
  run `npm fund` for details

found 0 vulnerabilities
npm notice
npm notice New major version of npm available! 8.5.0 -> 10.1.0
npm notice Changelog: https://github.com/npm/cli/releases/tag/v10.1.0
npm notice Run npm install -g npm@10.1.0 to update!
npm notice

I could then install Biome and save it to the packages.json

builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ pnpm add --save-dev --save-exact @biomejs/biome
Packages: +2
++
Progress: resolved 357, reused 342, downloaded 2, added 2, done
Downloading registry.npmjs.org/@biomejs/cli-linux-x64/1.1.2: 5.90 MB/5.90 MB, done
node_modules/.pnpm/@biomejs+biome@1.1.2/node_modules/@biomejs/biome: Running postinstall script, done in 48ms

devDependencies:
+ @biomejs/biome 1.1.2

Done in 3s

Running

If you just try and run it, you’ll see that it desires a command on all operations

builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ pnpm biome
flags/invalid ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ Failed to parse CLI arguments.

    Caused by:
      expected `COMMAND ...`, pass `--help` for usage information


Our first step is to init

builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ pnpm biome init

Welcome to Biome! Let's get you started...

Files created ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  - biome.json: Your project configuration. Documentation: https://biomejs.dev/configuration

Next Steps ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  1. Setup an editor extension
     Get live errors as you type and format when you save. Learn more: https://biomejs.dev/editors

  2. Try a command
     biome ci checks for lint errors and verifies formatting. Run biome --help for a full list of commands and options.

  3. Read the documentation
     Our website serves as a comprehensive source of guides and documentation: https://biomejs.dev

  4. Get involved in the community
     Ask questions, get support, or contribute by participating on GitHub (https://github.com/biomejs/biome),
     or join our community Discord (https://discord.gg/BypW39g6Yc)

The initial JSON is pretty straightforward:

builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ cat biome.json
{
        "$schema": "https://biomejs.dev/schemas/1.1.2/schema.json",
        "organizeImports": {
                "enabled": true
        },
        "linter": {
                "enabled": true,
                "rules": {
                        "recommended": true
                }
        }
}

CI Linting

Running the first time through found quite a lot:

builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ pnpm biome ci --formatter-enabled=true --linter-enabled=true ./src/
./src/app/components/common/index.ts format ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ File content differs from formatting output

    2 2 │   // export * from "./Footer";
    3 3 │   export * from "./Spinner";
    4   │ - //·export·*·from·"./SocialHome";
      4 │ + //·export·*·from·"./SocialHome";
      5 │ +


./src/app/components/common/index.ts ci ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ The file contains diagnostics that needs to be addressed.


./src/app/guestbook/page.tsx organizeImports ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ Import statements differs from the output

     1    │ - import·"../globals.css";
     2    │ - import·NavBar·from·"@/app/components/Navbar";
        1 │ + import·NavBar·from·"@/app/components/Navbar";
        2 │ + import·"../globals.css";
     3  3 │
     4  4 │   export const metadata = {


./src/app/components/common/UnderLine.tsx:4:20 lint/style/noUnusedTemplateLiteral  FIXABLE  ━━━━━━━━━━

  ✖ Do not use template literals if interpolation and special-character handling are not needed.

    2 │     return (
    3 │       <div
  > 4 │         className={`items-center h-0 border-t md:flex md:justify-between border-theme-600 my-4`}
      │                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    5 │       />
    6 │     );

  ℹ Suggested fix: Replace with string literal

    2 2 │       return (
    3 3 │         <div
    4   │ - ········className={`items-center·h-0·border-t·md:flex·md:justify-between·border-theme-600·my-4`}
      4 │ + ········className={"items-center·h-0·border-t·md:flex·md:justify-between·border-theme-600·my-4"}
    5 5 │         />
    6 6 │       );


./src/app/components/common/UnderLine.tsx lint ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ The file contains diagnostics that needs to be addressed.


./src/app/fun/page.tsx organizeImports ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ Import statements differs from the output

     1    │ - import·"../globals.css";
     2    │ - import·NavBar·from·"@/app/components/Navbar";
        1 │ + import·NavBar·from·"@/app/components/Navbar";
        2 │ + import·"../globals.css";
     3  3 │
     4  4 │   export const metadata = {


./src/app/types/index.d.ts format ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ File content differs from formatting output

     1  1 │   export interface postMetadata {
     2    │ - ····title:·string;
     3    │ - ····subtitle:·string;
     4    │ - ····date:·string;
     5    │ - ····slug:·string;
     6    │ - ····tags:·string[];
     7    │ - ··}
     8    │ - ··
     9    │ - ··export·interface·portfolioMetadata·{
    10    │ - ····title:·string;
    11    │ - ····subtitle:·string;
    12    │ - ····date:·sting;
    13    │ - ····slug:·string;
    14    │ - ····previewImg:·string;
    15    │ - ··}
    16    │ - ··
    17    │ - ··export·interface·icon·{
    18    │ - ····size?:·number;
    19    │ - ··}
        2 │ + → title:·string;
        3 │ + → subtitle:·string;
        4 │ + → date:·string;
        5 │ + → slug:·string;
        6 │ + → tags:·string[];
        7 │ + }
        8 │ +
        9 │ + export·interface·portfolioMetadata·{
       10 │ + → title:·string;
       11 │ + → subtitle:·string;
       12 │ + → date:·sting;
       13 │ + → slug:·string;
       14 │ + → previewImg:·string;
       15 │ + }
       16 │ +
       17 │ + export·interface·icon·{
       18 │ + → size?:·number;
       19 │ + }
       20 │ +


./src/app/types/index.d.ts ci ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ The file contains diagnostics that needs to be addressed.


./src/app/blog/page.tsx organizeImports ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ Import statements differs from the output

     1    │ - import·"../globals.css";
     2    │ - import·NavBar·from·"@/app/components/Navbar";
        1 │ + import·NavBar·from·"@/app/components/Navbar";
        2 │ + import·"../globals.css";
     3  3 │
     4  4 │
     5    │ - import·PostPreview·from·"@/app/components/preview/PostPreview";
     6    │ - import·UnderLine·from·"@/app/components/common/UnderLine";
        5 │ + import·UnderLine·from·"@/app/components/common/UnderLine";
        6 │ + import·PostPreview·from·"@/app/components/preview/PostPreview";
     7  7 │   import getPostMetadata from "@/app/libs/Metadata/getPostMetadata";
     8  8 │   import PageWrapper from "@/app/providers/PageWrapper";


./src/app/layout.tsx organizeImports ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ Import statements differs from the output

     1    │ - import·"./globals.css";
     2    │ - import·NavBar·from·"@/app/components/Navbar";
        1 │ + import·NavBar·from·"@/app/components/Navbar";
        2 │ + import·"./globals.css";
     3  3 │
     4  4 │   export const metadata = {


./src/app/libs/twclsx.ts organizeImports ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ Import statements differs from the output

    1 1 │   import clsx from "clsx";
    2   │ - import·{·twMerge·}·from·"tailwind-merge";
    3   │ - import·type·{·ClassValue·}·from·'clsx'
      2 │ + import·type·{·ClassValue·}·from·'clsx'
      3 │ + import·{·twMerge·}·from·"tailwind-merge";
    4 4 │
    5 5 │   export const twclsx = (...args: ClassValue[]) => twMerge(clsx(...args))


./src/app/components/common/UnderLine.tsx format ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ File content differs from formatting output

    1  1 │   const UnderLine = () => {
    2    │ - ····return·(
    3    │ - ······<div
    4    │ - ········className={`items-center·h-0·border-t·md:flex·md:justify-between·border-theme-600·my-4`}
    5    │ - ······/>
    6    │ - ····);
    7    │ - ··};
    8    │ - ··
    9    │ - ··export·default·UnderLine;
       2 │ + → return·(
       3 │ + → → <div
       4 │ + → → → className={`items-center·h-0·border-t·md:flex·md:justify-between·border-theme-600·my-4`}
       5 │ + → → />
       6 │ + → );
       7 │ + };
       8 │ +
       9 │ + export·default·UnderLine;
      10 │ +


./src/app/components/common/UnderLine.tsx ci ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ The file contains diagnostics that needs to be addressed.


./src/app/providers/PageWrapper.tsx format ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ File content differs from formatting output

     2  2 │   import { motion } from "framer-motion";
     3  3 │   const PageWrapper = ({ children }: { children: React.ReactNode }) => {
     4    │ - ··return·(
     5    │ - ····<motion.div
     6    │ - ······initial={{·opacity:·0,·y:·30·}}
     7    │ - ······animate={{·opacity:·1,·y:·0·}}
     8    │ - ······exit={{·opacity:·0,·y:·30·}}
     9    │ - ····>
    10    │ - ······{children}
    11    │ - ····</motion.div>
    12    │ - ··);
        4 │ + → return·(
        5 │ + → → <motion.div
        6 │ + → → → initial={{·opacity:·0,·y:·30·}}
        7 │ + → → → animate={{·opacity:·1,·y:·0·}}
        8 │ + → → → exit={{·opacity:·0,·y:·30·}}
        9 │ + → → >
       10 │ + → → → {children}
       11 │ + → → </motion.div>
       12 │ + → );
    13 13 │   };
    14 14 │
    15    │ - export·default·PageWrapper;
       15 │ + export·default·PageWrapper;
       16 │ +


./src/app/guestbook/page.tsx format ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ File content differs from formatting output

     3  3 │
     4  4 │   export const metadata = {
     5    │ - ··title:·"Create·NextJS·App",
     6    │ - ··description:·"Generated·by·create·next·app",
        5 │ + → title:·"Create·NextJS·App",
        6 │ + → description:·"Generated·by·create·next·app",
     7  7 │   };
     8  8 │
     9  9 │   // <!-- use http://caius.github.io/github_id/ -->
    10 10 │   const page = () => {
    11    │ - ··return·(
    12    │ - ············<div·className="flex·flex-col·gap-2·justify-center">
    13    │ - ··············<h2·className="mb-0·text-zinc-100·font-bold">and</h2>
    14    │ - ··············<p·className="mb-0·text-zinc-400·font-semibold·leading-none">
    15    │ - ················....·guestbook·here·.....
    16    │ - ··············</p>
    17    │ - ············</div>
    18    │ - ··);
    19    │ - }
       11 │ + → return·(
       12 │ + → → <div·className="flex·flex-col·gap-2·justify-center">
       13 │ + → → → <h2·className="mb-0·text-zinc-100·font-bold">and</h2>
       14 │ + → → → <p·className="mb-0·text-zinc-400·font-semibold·leading-none">
       15 │ + → → → → ....·guestbook·here·.....
       16 │ + → → → </p>
       17 │ + → → </div>
       18 │ + → );
       19 │ + };
    20 20 │
    21    │ - export·default·page;
       21 │ + export·default·page;
       22 │ +


./src/app/fun/page.tsx format ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ File content differs from formatting output

     3  3 │
     4  4 │   export const metadata = {
     5    │ - ··title:·"Create·NextJS·App",
     6    │ - ··description:·"Generated·by·create·next·app",
        5 │ + → title:·"Create·NextJS·App",
        6 │ + → description:·"Generated·by·create·next·app",
     7  7 │   };
     8  8 │
     9  9 │   // <!-- use http://caius.github.io/github_id/ -->
    10 10 │   const page = () => {
    11    │ - ··return·(
    12    │ - ············<div·className="flex·flex-col·gap-2·justify-center">
    13    │ - ··············<h2·className="mb-0·text-zinc-100·font-bold">and</h2>
    14    │ - ··············<p·className="mb-0·text-zinc-400·font-semibold·leading-none">
    15    │ - ················....·fun·here·.....
    16    │ - ··············</p>
    17    │ - ············</div>
    18    │ - ··);
    19    │ - }
       11 │ + → return·(
       12 │ + → → <div·className="flex·flex-col·gap-2·justify-center">
       13 │ + → → → <h2·className="mb-0·text-zinc-100·font-bold">and</h2>
       14 │ + → → → <p·className="mb-0·text-zinc-400·font-semibold·leading-none">
       15 │ + → → → → ....·fun·here·.....
       16 │ + → → → </p>
       17 │ + → → </div>
       18 │ + → );
       19 │ + };
    20 20 │
    21    │ - export·default·page;
       21 │ + export·default·page;
       22 │ +


./src/app/providers/PageWrapper.tsx ci ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ The file contains diagnostics that needs to be addressed.


./src/app/guestbook/page.tsx ci ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ The file contains diagnostics that needs to be addressed.


./src/app/fun/page.tsx ci ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ The file contains diagnostics that needs to be addressed.


./src/app/libs/twclsx.ts format ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ File content differs from formatting output

    1 1 │   import clsx from "clsx";
    2 2 │   import { twMerge } from "tailwind-merge";
    3   │ - import·type·{·ClassValue·}·from·'clsx'
      3 │ + import·type·{·ClassValue·}·from·"clsx";
    4 4 │
    5   │ - export·const·twclsx·=·(...args:·ClassValue[])·=>·twMerge(clsx(...args))
      5 │ + export·const·twclsx·=·(...args:·ClassValue[])·=>·twMerge(clsx(...args));
      6 │ +


./src/app/libs/twclsx.ts ci ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ The file contains diagnostics that needs to be addressed.


./src/app/libs/Metadata/getPostMetadata.ts:18:44 lint/suspicious/noExplicitAny ━━━━━━━━━━━━━━━━━━━━━

  ✖ Unexpected any. Specify a different type.

    16 │       subtitle: matterResult.data.subtitle,
    17 │       slug: fileName.replace(".md", ""),
  > 18 │       tags: matterResult.data.tags.map((e: any) => e)
       │                                            ^^^
    19 │     };
    20 │   });

  ℹ any disables many type checking rules. Its use should be avoided.


./src/app/blog/[slug]/page.tsx:24:26 lint/suspicious/noExplicitAny ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ Unexpected any. Specify a different type.

    22 │ };
    23 │
  > 24 │ const PostPage = (props: any) => {
       │                          ^^^
    25 │   const slug = props.params.slug;
    26 │   const post = getPostContent(slug);

  ℹ any disables many type checking rules. Its use should be avoided.


./src/app/components/common/Tag.tsx:3:40 lint/suspicious/noExplicitAny ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ Unexpected any. Specify a different type.

    1 │ import clsx from "clsx";
    2 │
  > 3 │ const Tag = ({ tagString, className }: any) => {
      │                                        ^^^
    4 │   return (
    5 │     <div className={clsx(`px-1 rounded-sm bg-theme-500 ${className}`)}>

  ℹ any disables many type checking rules. Its use should be avoided.


./src/app/page.tsx:25:26 lint/style/noUnusedTemplateLiteral  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ Do not use template literals if interpolation and special-character handling are not needed.

    23 │           rel="noopener noreferrer"
    24 │         >
  > 25 │           <h2 className={`mb-3 text-2xl font-semibold`}>
       │                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    26 │             Docs{' '}
    27 │             <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">

  ℹ Suggested fix: Replace with string literal

    23 23 │             rel="noopener noreferrer"
    24 24 │           >
    25    │ - ··········<h2·className={`mb-3·text-2xl·font-semibold`}>
       25 │ + ··········<h2·className={"mb-3·text-2xl·font-semibold"}>
    26 26 │               Docs{' '}
    27 27 │               <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">


./src/app/page.tsx:31:25 lint/style/noUnusedTemplateLiteral  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ Do not use template literals if interpolation and special-character handling are not needed.

    29 │             </span>
    30 │           </h2>
  > 31 │           <p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
       │                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    32 │             Find in-depth information about Next.js features and API.
    33 │           </p>

  ℹ Suggested fix: Replace with string literal

    29 29 │               </span>
    30 30 │             </h2>
    31    │ - ··········<p·className={`m-0·max-w-[30ch]·text-sm·opacity-50`}>
       31 │ + ··········<p·className={"m-0·max-w-[30ch]·text-sm·opacity-50"}>
    32 32 │               Find in-depth information about Next.js features and API.
    33 33 │             </p>


./src/app/page.tsx:42:26 lint/style/noUnusedTemplateLiteral  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ Do not use template literals if interpolation and special-character handling are not needed.

    40 │           rel="noopener noreferrer"
    41 │         >
  > 42 │           <h2 className={`mb-3 text-2xl font-semibold`}>
       │                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    43 │             Learn{' '}
    44 │             <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">

  ℹ Suggested fix: Replace with string literal

    40 40 │             rel="noopener noreferrer"
    41 41 │           >
    42    │ - ··········<h2·className={`mb-3·text-2xl·font-semibold`}>
       42 │ + ··········<h2·className={"mb-3·text-2xl·font-semibold"}>
    43 43 │               Learn{' '}
    44 44 │               <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">


./src/app/page.tsx:48:25 lint/style/noUnusedTemplateLiteral  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ Do not use template literals if interpolation and special-character handling are not needed.

    46 │             </span>
    47 │           </h2>
  > 48 │           <p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
       │                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    49 │             Learn about Next.js in an interactive course with&nbsp;quizzes!
    50 │           </p>

  ℹ Suggested fix: Replace with string literal

    46 46 │               </span>
    47 47 │             </h2>
    48    │ - ··········<p·className={`m-0·max-w-[30ch]·text-sm·opacity-50`}>
       48 │ + ··········<p·className={"m-0·max-w-[30ch]·text-sm·opacity-50"}>
    49 49 │               Learn about Next.js in an interactive course with&nbsp;quizzes!
    50 50 │             </p>


./src/app/page.tsx:59:26 lint/style/noUnusedTemplateLiteral  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ Do not use template literals if interpolation and special-character handling are not needed.

    57 │           rel="noopener noreferrer"
    58 │         >
  > 59 │           <h2 className={`mb-3 text-2xl font-semibold`}>
       │                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    60 │             Templates{' '}
    61 │             <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">

  ℹ Suggested fix: Replace with string literal

    57 57 │             rel="noopener noreferrer"
    58 58 │           >
    59    │ - ··········<h2·className={`mb-3·text-2xl·font-semibold`}>
       59 │ + ··········<h2·className={"mb-3·text-2xl·font-semibold"}>
    60 60 │               Templates{' '}
    61 61 │               <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">


./src/app/page.tsx:65:25 lint/style/noUnusedTemplateLiteral  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ Do not use template literals if interpolation and special-character handling are not needed.

    63 │             </span>
    64 │           </h2>
  > 65 │           <p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
       │                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    66 │             Explore the Next.js 13 playground.
    67 │           </p>

  ℹ Suggested fix: Replace with string literal

    63 63 │               </span>
    64 64 │             </h2>
    65    │ - ··········<p·className={`m-0·max-w-[30ch]·text-sm·opacity-50`}>
       65 │ + ··········<p·className={"m-0·max-w-[30ch]·text-sm·opacity-50"}>
    66 66 │               Explore the Next.js 13 playground.
    67 67 │             </p>


./src/app/page.tsx:76:26 lint/style/noUnusedTemplateLiteral  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ Do not use template literals if interpolation and special-character handling are not needed.

    74 │           rel="noopener noreferrer"
    75 │         >
  > 76 │           <h2 className={`mb-3 text-2xl font-semibold`}>
       │                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    77 │             Deploy{' '}
    78 │             <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">

  ℹ Suggested fix: Replace with string literal

    74 74 │             rel="noopener noreferrer"
    75 75 │           >
    76    │ - ··········<h2·className={`mb-3·text-2xl·font-semibold`}>
       76 │ + ··········<h2·className={"mb-3·text-2xl·font-semibold"}>
    77 77 │               Deploy{' '}
    78 78 │               <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">


./src/app/page.tsx:82:25 lint/style/noUnusedTemplateLiteral  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ Do not use template literals if interpolation and special-character handling are not needed.

    80 │             </span>
    81 │           </h2>
  > 82 │           <p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
       │                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    83 │             Instantly deploy your Next.js site to a shareable URL with Vercel.
    84 │           </p>

  ℹ Suggested fix: Replace with string literal

    80 80 │               </span>
    81 81 │             </h2>
    82    │ - ··········<p·className={`m-0·max-w-[30ch]·text-sm·opacity-50`}>
       82 │ + ··········<p·className={"m-0·max-w-[30ch]·text-sm·opacity-50"}>
    83 83 │               Instantly deploy your Next.js site to a shareable URL with Vercel.
    84 84 │             </p>


Checked 16 file(s) in 6ms
Found 57 error(s)
ci ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ Some errors were emitted while running checks.

Assuming I wish to fix these things, I could format them

builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ pnpm biome format ./src/ --write
Formatted 16 file(s) in 2ms

rechecking, we do see a reduction from 57 errors to 33

builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ pnpm biome ci --formatter-enabled=true --linter-enabled=true ./src/
./src/app/libs/twclsx.ts organizeImports ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ Import statements differs from the output

    1 1 │   import clsx from "clsx";
    2   │ - import·{·twMerge·}·from·"tailwind-merge";
    3   │ - import·type·{·ClassValue·}·from·"clsx";
      2 │ + import·type·{·ClassValue·}·from·"clsx";
      3 │ + import·{·twMerge·}·from·"tailwind-merge";
    4 4 │
    5 5 │   export const twclsx = (...args: ClassValue[]) => twMerge(clsx(...args));


./src/app/guestbook/page.tsx organizeImports ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ Import statements differs from the output

     1    │ - import·"../globals.css";
     2    │ - import·NavBar·from·"@/app/components/Navbar";
        1 │ + import·NavBar·from·"@/app/components/Navbar";
        2 │ + import·"../globals.css";
     3  3 │
     4  4 │   export const metadata = {


./src/app/libs/twclsx.ts ci ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ The file contains diagnostics that needs to be addressed.


./src/app/libs/Metadata/getPostMetadata.ts:18:41 lint/suspicious/noExplicitAny ━━━━━━━━━━━━━━━━━━━━━

  ✖ Unexpected any. Specify a different type.

    16 │                        subtitle: matterResult.data.subtitle,
    17 │                        slug: fileName.replace(".md", ""),
  > 18 │                        tags: matterResult.data.tags.map((e: any) => e),
       │                                                             ^^^
    19 │                };
    20 │        });

  ℹ any disables many type checking rules. Its use should be avoided.


./src/app/components/common/UnderLine.tsx:4:15 lint/style/noUnusedTemplateLiteral  FIXABLE  ━━━━━━━━━━

  ✖ Do not use template literals if interpolation and special-character handling are not needed.

    2 │         return (
    3 │                 <div
  > 4 │                         className={`items-center h-0 border-t md:flex md:justify-between border-theme-600 my-4`}
      │                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    5 │                 />
    6 │         );

  ℹ Suggested fix: Replace with string literal

     2  2 │     return (
     3  3 │             <div
     4    │ - → → → className={`items-center·h-0·border-t·md:flex·md:justify-between·border-theme-600·my-4`}
        4 │ + → → → className={"items-center·h-0·border-t·md:flex·md:justify-between·border-theme-600·my-4"}
     5  5 │             />
     6  6 │     );


./src/app/components/common/UnderLine.tsx lint ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ The file contains diagnostics that needs to be addressed.


./src/app/libs/Metadata/getPostMetadata.ts lint ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ The file contains diagnostics that needs to be addressed.


./src/app/components/common/Tag.tsx:3:40 lint/suspicious/noExplicitAny ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ Unexpected any. Specify a different type.

    1 │ import clsx from "clsx";
    2 │
  > 3 │ const Tag = ({ tagString, className }: any) => {
      │                                        ^^^
    4 │         return (
    5 │                 <div className={clsx(`px-1 rounded-sm bg-theme-500 ${className}`)}>

  ℹ any disables many type checking rules. Its use should be avoided.


./src/app/components/common/Tag.tsx lint ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ The file contains diagnostics that needs to be addressed.


./src/app/libs/Metadata/getPostMetadata.ts organizeImports ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ Import statements differs from the output

     1  1 │   import fs from "fs";
     2    │ - import·{·postMetadata·}·from·"../../types";
     3    │ - import·matter·from·"gray-matter";
        2 │ + import·matter·from·"gray-matter";
        3 │ + import·{·postMetadata·}·from·"../../types";
     4  4 │
     5  5 │   const getPostMetadata = (): postMetadata[] => {


./src/app/blog/page.tsx organizeImports ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ Import statements differs from the output

     1    │ - import·"../globals.css";
     2    │ - import·NavBar·from·"@/app/components/Navbar";
        1 │ + import·NavBar·from·"@/app/components/Navbar";
        2 │ + import·"../globals.css";
     3  3 │
     4    │ - import·PostPreview·from·"@/app/components/preview/PostPreview";
     5    │ - import·UnderLine·from·"@/app/components/common/UnderLine";
        4 │ + import·UnderLine·from·"@/app/components/common/UnderLine";
        5 │ + import·PostPreview·from·"@/app/components/preview/PostPreview";
     6  6 │   import getPostMetadata from "@/app/libs/Metadata/getPostMetadata";
     7  7 │   import PageWrapper from "@/app/providers/PageWrapper";


./src/app/layout.tsx organizeImports ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ Import statements differs from the output

     1    │ - import·"./globals.css";
     2    │ - import·NavBar·from·"@/app/components/Navbar";
        1 │ + import·NavBar·from·"@/app/components/Navbar";
        2 │ + import·"./globals.css";
     3  3 │
     4  4 │   export const metadata = {


./src/app/guestbook/page.tsx ci ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ The file contains diagnostics that needs to be addressed.


./src/app/components/Navbar.tsx organizeImports ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ Import statements differs from the output

     2  2 │
     3  3 │   import { motion } from "framer-motion";
     4    │ - import·{·useState·}·from·"react";
        4 │ + import·Link·from·"next/link";
     5  5 │   import { usePathname } from "next/navigation";
     6    │ - import·Link·from·"next/link";
        6 │ + import·{·useState·}·from·"react";
     7  7 │
     8  8 │   const navItems = [


./src/app/layout.tsx ci ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ The file contains diagnostics that needs to be addressed.


./src/app/blog/page.tsx ci ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ The file contains diagnostics that needs to be addressed.


./src/app/libs/Metadata/getPostMetadata.ts ci ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ The file contains diagnostics that needs to be addressed.


./src/app/blog/[slug]/page.tsx:24:26 lint/suspicious/noExplicitAny ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ Unexpected any. Specify a different type.

    22 │ };
    23 │
  > 24 │ const PostPage = (props: any) => {
       │                          ^^^
    25 │        const slug = props.params.slug;
    26 │        const post = getPostContent(slug);

  ℹ any disables many type checking rules. Its use should be avoided.


./src/app/blog/[slug]/page.tsx lint ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ The file contains diagnostics that needs to be addressed.


./src/app/components/Navbar.tsx ci ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ The file contains diagnostics that needs to be addressed.


./src/app/blog/[slug]/page.tsx organizeImports ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ Import statements differs from the output

     1  1 │   import fs from "fs";
     2    │ - import·matter·from·"gray-matter";
        2 │ + import·Tag·from·"@/app/components/common/Tag";
        3 │ + import·getPostMetadata·from·"@/app/libs/Metadata/getPostMetadata";
        4 │ + import·matter·from·"gray-matter";
     3  5 │   import Markdown from "markdown-to-jsx";
     4    │ - import·getPostMetadata·from·"@/app/libs/Metadata/getPostMetadata";
     5    │ - import·Link·from·"next/link";
     6    │ - import·{·AiOutlineCaretLeft·}·from·"react-icons/ai";
     7    │ - import·Tag·from·"@/app/components/common/Tag";
        6 │ + import·Link·from·"next/link";
        7 │ + import·{·AiOutlineCaretLeft·}·from·"react-icons/ai";
     8  8 │
     9  9 │   const getPostContent = (slug: string) => {


./src/app/fun/page.tsx organizeImports ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ Import statements differs from the output

     1    │ - import·"../globals.css";
     2    │ - import·NavBar·from·"@/app/components/Navbar";
        1 │ + import·NavBar·from·"@/app/components/Navbar";
        2 │ + import·"../globals.css";
     3  3 │
     4  4 │   export const metadata = {


./src/app/fun/page.tsx ci ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ The file contains diagnostics that needs to be addressed.


./src/app/blog/[slug]/page.tsx ci ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ The file contains diagnostics that needs to be addressed.


./src/app/page.tsx:24:21 lint/style/noUnusedTemplateLiteral  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ Do not use template literals if interpolation and special-character handling are not needed.

    22 │                                        rel="noopener noreferrer"
    23 │                                >
  > 24 │                                        <h2 className={`mb-3 text-2xl font-semibold`}>
       │                                                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    25 │                                                Docs{" "}
    26 │                                                <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">

  ℹ Suggested fix: Replace with string literal

    22 22 │                                     rel="noopener noreferrer"
    23 23 │                             >
    24    │ - → → → → → <h2·className={`mb-3·text-2xl·font-semibold`}>
       24 │ + → → → → → <h2·className={"mb-3·text-2xl·font-semibold"}>
    25 25 │                                             Docs{" "}
    26 26 │                                             <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">


./src/app/page.tsx:30:20 lint/style/noUnusedTemplateLiteral  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ Do not use template literals if interpolation and special-character handling are not needed.

    28 │                                                </span>
    29 │                                        </h2>
  > 30 │                                        <p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
       │                                                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    31 │                                                Find in-depth information about Next.js features and API.
    32 │                                        </p>

  ℹ Suggested fix: Replace with string literal

    28 28 │                                             </span>
    29 29 │                                     </h2>
    30    │ - → → → → → <p·className={`m-0·max-w-[30ch]·text-sm·opacity-50`}>
       30 │ + → → → → → <p·className={"m-0·max-w-[30ch]·text-sm·opacity-50"}>
    31 31 │                                             Find in-depth information about Next.js features and API.
    32 32 │                                     </p>


./src/app/page.tsx:41:21 lint/style/noUnusedTemplateLiteral  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ Do not use template literals if interpolation and special-character handling are not needed.

    39 │                                        rel="noopener noreferrer"
    40 │                                >
  > 41 │                                        <h2 className={`mb-3 text-2xl font-semibold`}>
       │                                                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    42 │                                                Learn{" "}
    43 │                                                <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">

  ℹ Suggested fix: Replace with string literal

    39 39 │                                     rel="noopener noreferrer"
    40 40 │                             >
    41    │ - → → → → → <h2·className={`mb-3·text-2xl·font-semibold`}>
       41 │ + → → → → → <h2·className={"mb-3·text-2xl·font-semibold"}>
    42 42 │                                             Learn{" "}
    43 43 │                                             <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">


./src/app/page.tsx:47:20 lint/style/noUnusedTemplateLiteral  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ Do not use template literals if interpolation and special-character handling are not needed.

    45 │                                                </span>
    46 │                                        </h2>
  > 47 │                                        <p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
       │                                                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    48 │                                                Learn about Next.js in an interactive course with&nbsp;quizzes!
    49 │                                        </p>

  ℹ Suggested fix: Replace with string literal

    45 45 │                                             </span>
    46 46 │                                     </h2>
    47    │ - → → → → → <p·className={`m-0·max-w-[30ch]·text-sm·opacity-50`}>
       47 │ + → → → → → <p·className={"m-0·max-w-[30ch]·text-sm·opacity-50"}>
    48 48 │                                             Learn about Next.js in an interactive course with&nbsp;quizzes!
    49 49 │                                     </p>


./src/app/page.tsx:58:21 lint/style/noUnusedTemplateLiteral  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ Do not use template literals if interpolation and special-character handling are not needed.

    56 │                                        rel="noopener noreferrer"
    57 │                                >
  > 58 │                                        <h2 className={`mb-3 text-2xl font-semibold`}>
       │                                                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    59 │                                                Templates{" "}
    60 │                                                <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">

  ℹ Suggested fix: Replace with string literal

    56 56 │                                     rel="noopener noreferrer"
    57 57 │                             >
    58    │ - → → → → → <h2·className={`mb-3·text-2xl·font-semibold`}>
       58 │ + → → → → → <h2·className={"mb-3·text-2xl·font-semibold"}>
    59 59 │                                             Templates{" "}
    60 60 │                                             <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">


./src/app/page.tsx:64:20 lint/style/noUnusedTemplateLiteral  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ Do not use template literals if interpolation and special-character handling are not needed.

    62 │                                                </span>
    63 │                                        </h2>
  > 64 │                                        <p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
       │                                                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    65 │                                                Explore the Next.js 13 playground.
    66 │                                        </p>

  ℹ Suggested fix: Replace with string literal

    62 62 │                                             </span>
    63 63 │                                     </h2>
    64    │ - → → → → → <p·className={`m-0·max-w-[30ch]·text-sm·opacity-50`}>
       64 │ + → → → → → <p·className={"m-0·max-w-[30ch]·text-sm·opacity-50"}>
    65 65 │                                             Explore the Next.js 13 playground.
    66 66 │                                     </p>


./src/app/page.tsx:75:21 lint/style/noUnusedTemplateLiteral  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ Do not use template literals if interpolation and special-character handling are not needed.

    73 │                                        rel="noopener noreferrer"
    74 │                                >
  > 75 │                                        <h2 className={`mb-3 text-2xl font-semibold`}>
       │                                                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    76 │                                                Deploy{" "}
    77 │                                                <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">

  ℹ Suggested fix: Replace with string literal

    73 73 │                                     rel="noopener noreferrer"
    74 74 │                             >
    75    │ - → → → → → <h2·className={`mb-3·text-2xl·font-semibold`}>
       75 │ + → → → → → <h2·className={"mb-3·text-2xl·font-semibold"}>
    76 76 │                                             Deploy{" "}
    77 77 │                                             <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">


./src/app/page.tsx:81:20 lint/style/noUnusedTemplateLiteral  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ Do not use template literals if interpolation and special-character handling are not needed.

    79 │                                                </span>
    80 │                                        </h2>
  > 81 │                                        <p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
       │                                                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    82 │                                                Instantly deploy your Next.js site to a shareable URL with Vercel.
    83 │                                        </p>

  ℹ Suggested fix: Replace with string literal

    79 79 │                                             </span>
    80 80 │                                     </h2>
    81    │ - → → → → → <p·className={`m-0·max-w-[30ch]·text-sm·opacity-50`}>
       81 │ + → → → → → <p·className={"m-0·max-w-[30ch]·text-sm·opacity-50"}>
    82 82 │                                             Instantly deploy your Next.js site to a shareable URL with Vercel.
    83 83 │                                     </p>


Checked 16 file(s) in 7ms
Found 33 error(s)
ci ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ Some errors were emitted while running checks.

I also have evidence of the changes in my changes ready for staging

builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ git status
On branch linter
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   package.json
        modified:   pnpm-lock.yaml
        modified:   src/app/blog/[slug]/page.tsx
        modified:   src/app/blog/page.tsx
        modified:   src/app/components/Navbar.tsx
        modified:   src/app/components/common/Spinner.tsx
        modified:   src/app/components/common/Tag.tsx
        modified:   src/app/components/common/UnderLine.tsx
        modified:   src/app/components/common/index.ts
        modified:   src/app/components/preview/PostPreview.tsx
        modified:   src/app/fun/page.tsx
        modified:   src/app/guestbook/page.tsx
        modified:   src/app/layout.tsx
        modified:   src/app/libs/Metadata/getPostMetadata.ts
        modified:   src/app/libs/twclsx.ts
        modified:   src/app/page.tsx
        modified:   src/app/providers/PageWrapper.tsx
        modified:   src/app/types/index.d.ts

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        biome.json

no changes added to commit (use "git add" and/or "git commit -a")

such as

builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ git diff src/app/components/preview/PostPreview.tsx
diff --git a/src/app/components/preview/PostPreview.tsx b/src/app/components/preview/PostPreview.tsx
index 14ed857..5350dfe 100644
--- a/src/app/components/preview/PostPreview.tsx
+++ b/src/app/components/preview/PostPreview.tsx
@@ -2,28 +2,22 @@ import Link from "next/link";
 import { postMetadata } from "../../types/index";

 const PostPreview = (props: postMetadata) => {
-  return (
-    <div
-      key={props.slug}
-      className="mb-0 text-zinc-400 px-2 py-1 transition ease-in-out rounded-lg hover:dark:bg-[#313131] hover:bg-primary-100 hover:-translate-y-1 cursor-pointer"
-    >
-      <Link href={`blog/${props.slug}`}>
-        <h3>
-          <span className="">
-            {props.title}
-          </span>
-        </h3>
-      </Link>
-      <div className="mb-0 text-zinc-400 flex flex-wrap justify-between">
-        <span>
-          {props.subtitle}
-        </span>
-        <span>
-          {props.date}
-        </span>
-      </div>
-    </div>
-  );
+       return (
+               <div
+                       key={props.slug}
+                       className="mb-0 text-zinc-400 px-2 py-1 transition ease-in-out rounded-lg hover:dark:bg-[#313131] hover:bg-primary-100 hover:-translate-y-1 cursor-pointer"
+               >
+                       <Link href={`blog/${props.slug}`}>
+                               <h3>
+                                       <span className="">{props.title}</span>
+                               </h3>
+                       </Link>
+                       <div className="mb-0 text-zinc-400 flex flex-wrap justify-between">
+                               <span>{props.subtitle}</span>
+                               <span>{props.date}</span>
+                       </div>
+               </div>
+       );
 };

-export default PostPreview;
\ No newline at end of file
+export default PostPreview;

And I see that biome was added to my packages and lock files

builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ git diff pnpm-lock.yaml
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 5f334e7..44350da 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -60,6 +60,11 @@ dependencies:
     specifier: 5.2.2
     version: 5.2.2

+devDependencies:
+  '@biomejs/biome':
+    specifier: 1.1.2
+    version: 1.1.2
+
 packages:

   /@aashutoshrathi/word-wrap@1.2.6:
@@ -79,6 +84,74 @@ packages:
       regenerator-runtime: 0.14.0
     dev: false

+  /@biomejs/biome@1.1.2:
+    resolution: {integrity: sha512-JEVWchqo0Xhl86IJgOh0xESWnNRUXBUDByCBR8TA4lIPzm/6U6Tv77+MblNkZ8MvwCtP6PlBNGdQcGKKabtuHA==}
+    engines: {node: '>=14.*'}
+    hasBin: true
+    requiresBuild: true
+    optionalDependencies:
+      '@biomejs/cli-darwin-arm64': 1.1.2
+      '@biomejs/cli-darwin-x64': 1.1.2
+      '@biomejs/cli-linux-arm64': 1.1.2
+      '@biomejs/cli-linux-x64': 1.1.2
+      '@biomejs/cli-win32-arm64': 1.1.2
+      '@biomejs/cli-win32-x64': 1.1.2
+    dev: true
+
+  /@biomejs/cli-darwin-arm64@1.1.2:
+    resolution: {integrity: sha512-YyqWeNZchPxlvxtdo2vMBkzrwllaNS3+DZ6j01mUCVIZE9kAzF/edMV2O38L2AEtnRLU1TI1f71Jai3ThILClg==}
+    engines: {node: '>=14.*'}
+    cpu: [arm64]
+    os: [darwin]
+    requiresBuild: true
+    dev: true
+    optional: true
...snip...

At this point, it’s worth firing up a dev instance just to be sure we didn’t break anything:

/content/images/2023/09/linter-01.png

I’ll now add to a commit and push to a new branch.

builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ git add src/
builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ git add package.json
builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ git add pnpm-lock.yaml
builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ git add biome.json
builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ git commit -m "First Linter Updates"
[linter 5affa2a] First Linter Updates
 19 files changed, 570 insertions(+), 491 deletions(-)
 create mode 100644 biome.json
 rewrite src/app/blog/[slug]/page.tsx (74%)
 rewrite src/app/blog/page.tsx (69%)
 rewrite src/app/components/Navbar.tsx (88%)
 rewrite src/app/components/common/Spinner.tsx (82%)
 rewrite src/app/components/preview/PostPreview.tsx (82%)
 rewrite src/app/fun/page.tsx (69%)
 rewrite src/app/guestbook/page.tsx (69%)
 rewrite src/app/layout.tsx (84%)
 rewrite src/app/libs/Metadata/getPostMetadata.ts (79%)
 rewrite src/app/page.tsx (99%)
builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ git push
fatal: The current branch linter has no upstream branch.
To push the current branch and set the remote as upstream, use

    git push --set-upstream origin linter

builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ git push --set-upstream origin linter
Enumerating objects: 66, done.
Counting objects: 100% (66/66), done.
Delta compression using up to 16 threads
Compressing objects: 100% (26/26), done.
Writing objects: 100% (34/34), 5.63 KiB | 2.81 MiB/s, done.
Total 34 (delta 18), reused 0 (delta 0)
remote: Resolving deltas: 100% (18/18), completed with 17 local objects.
remote:
remote: Create a pull request for 'linter' on GitHub by visiting:
remote:      https://github.com/idjohnson/nodesite/pull/new/linter
remote:
To https://github.com/idjohnson/nodesite.git
 * [new branch]      linter -> linter
Branch 'linter' set up to track remote branch 'linter' from 'origin'.

I created a PR - this is also to ensure we can build an image without issue (that Docker is also fine).

/content/images/2023/09/linter-02.png

Biome Ignores

Say that Biome flags something you know should be the way it is.

For instance, I have a generic page.tsx that is the basis for any blog entry. Biome said it was in poor form to allow any props:

builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ pnpm biome check ./src/ 

...snip...


./src/app/blog/[slug]/page.tsx:24:26 lint/suspicious/noExplicitAny ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ Unexpected any. Specify a different type.

    22 │ };
    23 │
  > 24 │ const PostPage = (props: any) => {
       │                          ^^^
    25 │        const slug = props.params.slug;
    26 │        const post = getPostContent(slug);

  ℹ any disables many type checking rules. Its use should be avoided.


The number of diagnostics exceeds the number allowed by Biome.
Diagnostics not shown: 13.
Checked 16 file(s) in 5ms
Found 25 error(s)
check ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ Some errors were emitted while running checks.

I then added a “biome-ignore” comment to skip this file with an explanation. (note: this didn’t always work to stop the check)

const PostPage = 
// biome-ignore format: This is a generic page for blogs that could have a growing variety of props.  I would not be ideal to restrict to just specific props.
(props: any) => {
	const slug = props.params.slug;
	const post = getPostContent(slug);
	const tempDate = new Date(post.data.date);
	const date = tempDate.toLocaleDateString("en-US", {
		year: "numeric",
		month: "long",
		day: "numeric",
	});

and now it skips that line, albeit not that file. It still flagged my out of order imports:



./src/app/blog/[slug]/page.tsx organizeImports ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ℹ Import statements could be sorted:

     1  1 │   import fs from "fs";
     2    │ - import·matter·from·"gray-matter";
        2 │ + import·Tag·from·"@/app/components/common/Tag";
        3 │ + import·getPostMetadata·from·"@/app/libs/Metadata/getPostMetadata";
        4 │ + import·matter·from·"gray-matter";
     3  5 │   import Markdown from "markdown-to-jsx";
     4    │ - import·getPostMetadata·from·"@/app/libs/Metadata/getPostMetadata";
     5    │ - import·Link·from·"next/link";
     6    │ - import·{·AiOutlineCaretLeft·}·from·"react-icons/ai";
     7    │ - import·Tag·from·"@/app/components/common/Tag";
        6 │ + import·Link·from·"next/link";
        7 │ + import·{·AiOutlineCaretLeft·}·from·"react-icons/ai";
     8  8 │
     9  9 │   const getPostContent = (slug: string) => {


./src/app/components/Navbar.tsx organizeImports ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ℹ Import statements could be sorted:

     2  2 │
     3  3 │   import { motion } from "framer-motion";
     4    │ - import·{·useState·}·from·"react";
        4 │ + import·Link·from·"next/link";
     5  5 │   import { usePathname } from "next/navigation";
     6    │ - import·Link·from·"next/link";
        6 │ + import·{·useState·}·from·"react";
     7  7 │
     8  8 │   const navItems = [


The number of diagnostics exceeds the number allowed by Biome.
Diagnostics not shown: 14.
Checked 16 file(s) in 6ms
Found 25 error(s)
check ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ Some errors were emitted while running checks.

In the end, I found I really didn’t want to figure out how to make exceptions for every any used. This was fine to me.

We can see some of the catches:

./src/app/libs/Metadata/getPostMetadata.ts:18:41 lint/suspicious/noExplicitAny ━━━━━━━━━━━━━━━━━━━━━

  ✖ Unexpected any. Specify a different type.

    16 │                        subtitle: matterResult.data.subtitle,
    17 │                        slug: fileName.replace(".md", ""),
  > 18 │                        tags: matterResult.data.tags.map((e: any) => e),
       │                                                             ^^^
    19 │                };
    20 │        });

  ℹ any disables many type checking rules. Its use should be avoided.


./src/app/libs/Metadata/getPostMetadata.ts lint ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ The file contains diagnostics that needs to be addressed.


./src/app/blog/[slug]/page.tsx:24:17 suppressions/parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ failed to parse category "noExplicitAny"

    22 │ };
    23 │
  > 24 │ // biome-ignore noExplicitAny: This is a generic page for blogs that could have a growing variety of props.  I would not be ideal to restrict to just specific props.
       │                 ^^^^^^^^^^^^^
    25 │ const PostPage = (props: any) => {
    26 │        const slug = props.params.slug;


./src/app/blog/[slug]/page.tsx:25:26 lint/suspicious/noExplicitAny ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ Unexpected any. Specify a different type.

It took me a bit to figure out this next part as there doesn’t exist documentation to explain.

That error: “lint/suspicious/noExplicitAny” can be skipped by adding a “off” block in our rules section of the biome.json by adding

    "suspicious": {
            "noExplicitAny": "off"
    }

This made the Biome config file now look as such:

builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ cat biome.json
{
        "$schema": "https://biomejs.dev/schemas/1.1.2/schema.json",
        "organizeImports": {
                "enabled": true
        },
        "linter": {
                "enabled": true,
                "rules": {
                        "recommended": true,
                        "suspicious": {
                                "noExplicitAny": "off"
                        }
                }
        }
}

With all the other corrections applied:

builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ pnpm biome check ./src/
Checked 16 file(s) in 3ms


builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ pnpm biome ci ./src/
Checked 16 file(s) in 2ms

YAY! we did it…

/content/images/2023/09/7z4181.jpg

But ideally, we would always check. Even better if in PRs.

Adding to our CI checks

I really wanted to add the lint in the Dockerfile

I even tried putting it into npm and running that way

FROM node:16.14.2-alpine AS BUILD_IMAGE

WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN npm install -g pnpm

# install dependencies
RUN pnpm install
COPY . .

# ci check 
COPY biome.json ./
RUN npm run biomecheck

# build
RUN pnpm build
# remove dev dependencies
RUN pnpm prune --production

FROM node:16.14.2-alpine

WORKDIR /app

RUN npm install -g pnpm

# copy from build image
COPY --from=BUILD_IMAGE /app/package.json ./package.json
COPY --from=BUILD_IMAGE /app/node_modules ./node_modules
COPY --from=BUILD_IMAGE /app/.next ./.next
COPY --from=BUILD_IMAGE /app/public ./public


EXPOSE 3000

# ENTRYPOINT ["tail", "-f", "/dev/null"]
CMD ["pnpm", "start"]

But it always errors on an ENOENT error


#14 [build_image  8/10] RUN npm run biomecheck
#14 0.486 
#14 0.486 > freshbrewed-app@0.1.0 biomecheck
#14 0.486 > pnpm biome check ./src/
#14 0.486 
#14 1.209 /app/node_modules/.pnpm/@biomejs+biome@1.1.2/node_modules/@biomejs/biome/bin/biome:40
#14 1.209 		throw result.error;
#14 1.209 		^
#14 1.209 
#14 1.209 Error: spawnSync /app/node_modules/.pnpm/@biomejs+cli-linux-x64@1.1.2/node_modules/@biomejs/cli-linux-x64/biome ENOENT
#14 1.209     at Object.spawnSync (node:internal/child_process:1111:20)
#14 1.209     at Object.spawnSync (node:child_process:814:24)
#14 1.209     at Object.<anonymous> (/app/node_modules/.pnpm/@biomejs+biome@1.1.2/node_modules/@biomejs/biome/bin/biome:22:42)
#14 1.209     at Module._compile (node:internal/modules/cjs/loader:1103:14)
#14 1.209     at Object.Module._extensions..js (node:internal/modules/cjs/loader:1157:10)
#14 1.209     at Module.load (node:internal/modules/cjs/loader:981:32)
#14 1.209     at Function.Module._load (node:internal/modules/cjs/loader:822:12)
#14 1.209     at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:77:12)
#14 1.209     at node:internal/main/run_main_module:17:47 {
#14 1.209   errno: -2,
#14 1.209   code: 'ENOENT',
#14 1.209   syscall: 'spawnSync /app/node_modules/.pnpm/@biomejs+cli-linux-x64@1.1.2/node_modules/@biomejs/cli-linux-x64/biome',
#14 1.209   path: '/app/node_modules/.pnpm/@biomejs+cli-linux-x64@1.1.2/node_modules/@biomejs/cli-linux-x64/biome',
#14 1.209   spawnargs: [ 'check', './src/' ]
#14 1.209 }
#14 ERROR: process "/bin/sh -c npm run biomecheck" did not complete successfully: exit code: 1
------
 > [build_image  8/10] RUN npm run biomecheck:
1.209     at Module.load (node:internal/modules/cjs/loader:981:32)
1.209     at Function.Module._load (node:internal/modules/cjs/loader:822:12)
1.209     at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:77:12)
1.209     at node:internal/main/run_main_module:17:47 {
1.209   errno: -2,
1.209   code: 'ENOENT',
1.209   syscall: 'spawnSync /app/node_modules/.pnpm/@biomejs+cli-linux-x64@1.1.2/node_modules/@biomejs/cli-linux-x64/biome',
1.209   path: '/app/node_modules/.pnpm/@biomejs+cli-linux-x64@1.1.2/node_modules/@biomejs/cli-linux-x64/biome',
1.209   spawnargs: [ 'check', './src/' ]
1.209 }
------
WARNING: No output specified with docker-container driver. Build result will only remain in the build cache. To push result image into registry use --push or to load image into docker use --load
Dockerfile:13
--------------------
  11 |     # ci check 
  12 |     COPY biome.json ./
  13 | >>> RUN npm run biomecheck
  14 |     
  15 |     # build
--------------------
ERROR: failed to solve: process "/bin/sh -c npm run biomecheck" did not complete successfully: exit code: 1
Error: buildx failed with: ERROR: failed to solve: process "/bin/sh -c npm run biomecheck" did not complete successfully: exit code: 1

However, I was able to add it as a standard GH action step.

I needed to add NodeJS 16, then install npm and run lint (eslint) and biomecheck (biome)

jobs:
  build:

    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
      # This is used to complete the identity challenge
      # with sigstore/fulcio when running outside of PRs.
      id-token: write

    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      - name: Use Node.js 16.x
        uses: actions/setup-node@v3
        with:
          node-version: 16.x

      - run: npm install

      - run: npm run lint

      - run: npm run biomecheck

      # Install the cosign tool except on PR
      # https://github.com/sigstore/cosign-installer
      - name: Install cosign
        if: github.event_name != 'pull_request'
        uses: sigstore/cosign-installer@6e04d228eb30da1757ee4e1dd75a0ec73a653e06 #v3.1.1
        with:
          cosign-release: 'v2.1.1'

Though this left a lot of junk locally that broke docker in later steps

/content/images/2023/09/linter-03.png

I broke into two jobs to address that:

jobs:
  ciCheck:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
      # This is used to complete the identity challenge
      # with sigstore/fulcio when running outside of PRs.
      id-token: write

    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      - name: Use Node.js 16.x
        uses: actions/setup-node@v3
        with:
          node-version: 16.x

      - run: npm install

      - run: npm run lint

      - run: npm run biomecheck

  build:
    needs: ciCheck
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
      ... snip ..

/content/images/2023/09/linter-04.png

We can now see the flow in action

This prompted me to set it to NOT deploy PR builds since they aren’t actually pushed to GHCR anyways


      - name: SetupDemoKConfig
        if: ${{ github.event_name != 'pull_request' }}
        run: |
          echo ${{ secrets.KCONFIGTEST }} | base64 --decode > /tmp/kconfig
          kubectl get nodes --insecure-skip-tls-verify --kubeconfig=/tmp/kconfig

          # Deploy
          kubectl delete -f ./deploy/k8sRSGH.yaml --insecure-skip-tls-verify --kubeconfig=/tmp/kconfig || true
          kubectl apply -f ./deploy/k8sRSGH.yaml --insecure-skip-tls-verify --kubeconfig=/tmp/kconfig

Now it skipped that step on the PR

/content/images/2023/09/linter-05.png

Let’s merge and see what happens. It should build, push and host on the cluster

Let’s rebuild to just to see the deploy update at that step

TLS Ingress

Let’s assume we want to host in our Production cluster.

I’ll first want to create an A Record to route here.

I’ll create and apply an A record

builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ cat r53-nodesite.json
{
    "Comment": "CREATE nodesite fb.s A record ",
    "Changes": [
      {
        "Action": "CREATE",
        "ResourceRecordSet": {
          "Name": "nodesite.freshbrewed.science",
          "Type": "A",
          "TTL": 300,
          "ResourceRecords": [
            {
              "Value": "75.73.224.240"
            }
          ]
        }
      }
    ]
  }
builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ aws route53 change-resource-record-sets --hosted-zone-id Z39E8QFU0F9PZP --change-batch file://r53-nodesite.json
{
    "ChangeInfo": {
        "Id": "/change/C084834118M5X7DOE7IAO",
        "Status": "PENDING",
        "SubmittedAt": "2023-09-15T11:16:40.835Z",
        "Comment": "CREATE nodesite fb.s A record "
    }
}

For the ingress, we need to remind ourselves of the deployment yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: fbnextgh-deployment
spec:
  replicas: 3 # You can adjust the number of replicas as needed
  selector:
    matchLabels:
      app: fbnext-app
  template:
    metadata:
      labels:
        app: fbnext-app
    spec:
      containers:
        - name: fbnext-container
          image: ghcr.io/idjohnson/nodesite:main
          ports:
            - containerPort: 3000
---
apiVersion: v1
kind: Service
metadata:
  name: fbnextgh-service
spec:
  selector:
    app: fbnext-app
  ports:
    - protocol: TCP
      port: 80
      targetPort: 3000
  type: ClusterIP

While the Deployment and ReplicaSet use “fbnext-app” on 3000, the service is “fbnextgh-service” using the standard HTTP port.

builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ cat ingress-nodesite.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    kubernetes.io/ingress.class: nginx
    kubernetes.io/tls-acme: "true"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
    nginx.org/websocket-services: nodesite-external-ip
  labels:
    app.kubernetes.io/instance: nodesiteingress
  name: nodesiteingress
spec:
  rules:
  - host: nodesite.freshbrewed.science
    http:
      paths:
      - backend:
          service:
            name: fbnextgh-service
            port:
              number: 80
        path: /
        pathType: ImplementationSpecific
  tls:
  - hosts:
    - nodesite.freshbrewed.science
    secretName: nodesite-tls
builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ kubectl apply -f ingress-nodesite.yaml
ingress.networking.k8s.io/nodesiteingress created

The last bit of fun is to swap out the kubeconfig for the primary cluster. None of this will do anything if we aren’t deploying into the exposed cluster!

/content/images/2023/09/linter-06.png

I updated the Kubeconfig as a base64 value in the secrets

/content/images/2023/09/linter-07.png

This likely will something to consider replacing ith a private agent runner. That is, I have automations to deal with the occasional changing of my external IP, but this will have the fixed IP.

I could make one depend on the other. Something like:

$ export DETIPADD=`host -4 nodesite.freshbrewed.science | tail -n1 | sed 's/.* //' | tr -d '\n'`
$ sed -i "s/server: https:\/\/[^:]*:/server: https:\/\/$DETIPADD:/" /tmp/kconfig

I updated the step in a new branch

      - name: SetupDemoKConfig
        if: ${{ github.event_name != 'pull_request' }}
        run: |
          set +x
          echo ${{ secrets.KCONFIGTEST }} | base64 --decode > /tmp/kconfig
          set -x

          # Autocorrect IP
          export DETIPADD=`host -4 nodesite.freshbrewed.science | tail -n1 | sed 's/.* //' | tr -d '\n'`
          sed -i "s/server: https:\/\/[^:]*:/server: https:\/\/$DETIPADD:/" /tmp/kconfig

          # Test
          kubectl get nodes --insecure-skip-tls-verify --kubeconfig=/tmp/kconfig

          # Deploy
          kubectl delete -f ./deploy/k8sRSGH.yaml --insecure-skip-tls-verify --kubeconfig=/tmp/kconfig || true
          kubectl apply -f ./deploy/k8sRSGH.yaml --insecure-skip-tls-verify --kubeconfig=/tmp/kconfig

I wanted to circle back on tools. I was using KDash we spoke on last week and noticed something:

That errant websocket entry was breaking the Ingress and I hadn’t caught on yet.

Fixing gives a 502 Bad Gateway which is what I expect when there isn’t a matching service (yet).

Which brings us to the final merge push and deploy where you can see the end to end flow.

I used k9s above as KDash doesn’t have a search/filter feature. The sample site is now live.

Summary

We covered a lot today. We got linting going with Biome and used it to cleanup a lot of code. We figured out how to add specific skips then moved on to CICD where we added it to the build. Trying to use the Dockerfile proved unsuccessful but just adding build steps worked. We split the build into multiple stages and lastly, worked out proper PR vs Main pipeline steps and a production build to a routable TLS endpoint.

At this point, if one was to update or create blog entry and push the PR, it would show up live to nodesite.freshbrewed.science.

Vercel React NodeJS Biome KDash k9s k1s Github Kuberenetes

Have something to add? Feedback? Try our new forums

Isaac Johnson

Isaac Johnson

Cloud Solutions Architect

Isaac is a CSA and DevOps engineer who focuses on cloud migrations and devops processes. He also is a dad to three wonderful daughters (hence the references to Princess King sprinkled throughout the blog).

Theme built by C.S. Rhymes