feat: initial repo scaffolding
Node.js CI / Lint and Test (push) Successful in 37s

Port in some older solutions I've written just to
get the structure established and in place.
This commit is contained in:
2025-11-19 15:35:26 -08:00
parent 5e987c0aae
commit 1f44f2ddd9
14 changed files with 4540 additions and 0 deletions
+38
View File
@@ -0,0 +1,38 @@
name: Node.js CI
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
lint:
name: Lint and Test
steps:
- name: Checkout Source Files
uses: actions/checkout@v4
- name: Use Node.js v22
uses: actions/setup-node@v4
with:
node-version: 22
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 9
- name: Install Dependencies
run: pnpm install
- name: Lint Source Files
run: pnpm run lint
- name: Verify Build
run: pnpm run build
- name: Run Tests
run: pnpm run test
+1
View File
@@ -0,0 +1 @@
node_modules
+6
View File
@@ -0,0 +1,6 @@
{
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"eslint.validate": ["typescript"]
}
+17
View File
@@ -0,0 +1,17 @@
import NaomisConfig from '@nhcarrigan/eslint-config';
export default [
...NaomisConfig,
{
rules: {
'max-lines-per-function': 'off',
'max-statements': 'off',
}
},
{
files: ['src/**/*.spec.ts'],
rules: {
'max-nested-callbacks': 'off',
}
}
];
+27
View File
@@ -0,0 +1,27 @@
{
"name": "dsa",
"version": "1.0.0",
"description": "",
"main": "prod/index.js",
"type": "module",
"scripts": {
"build": "tsc",
"start": "tsx src/index.ts",
"lint": "eslint src --max-warnings 0",
"test": "vitest run --coverage"
},
"keywords": [],
"author": "",
"license": "ISC",
"packageManager": "pnpm@10.22.0",
"devDependencies": {
"@nhcarrigan/eslint-config": "5.2.0",
"@nhcarrigan/typescript-config": "4.0.0",
"@types/node": "24.10.1",
"@vitest/coverage-v8": "4.0.10",
"eslint": "9.39.1",
"tsx": "4.20.6",
"typescript": "5.9.3",
"vitest": "4.0.10"
}
}
+4256
View File
File diff suppressed because it is too large Load Diff
+23
View File
@@ -0,0 +1,23 @@
/**
* @copyright NHCarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { describe, it, expect } from "vitest";
import { mineColor } from "./main.js";
describe("mineColor", () => {
it("should return white if the square is white", () => {
expect(mineColor("a", 8)).toBe("white");
expect(mineColor("f", 5)).toBe("white");
});
it("should return black if the square is black", () => {
expect(mineColor("b", 2)).toBe("black");
});
it("should handle empty strings", () => {
expect(mineColor("", 1)).toBe("white");
});
});
+28
View File
@@ -0,0 +1,28 @@
/**
* @copyright NHCarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
/**
* Determines the color of a square on a chessboard based on its column and row.
* @param column - The column of the square, represented as a letter ('a'-'h').
* @param row - The row of the square, represented as a number (1-8).
* @returns The color of the square, either "white" or "black".
* @see https://www.codewars.com/kata/563319974612f4fa3f0000e0
*/
const mineColor = (column: string, row: number): string => {
// Convert column letter ('a'-'h') to a number (1-8)
const columnNumber
= (column.toLowerCase().codePointAt(0) ?? 0)
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion, stylistic/no-mixed-operators -- "a" clearly has a code point at index 0. We're mixing operators because this is an old solution.
- ("a".codePointAt(0)!) + 1;
// If the sum of column number and row is even, it's white
if ((columnNumber + row) % 2 !== 0) {
return "white";
}
return "black";
};
export { mineColor };
@@ -0,0 +1,24 @@
/**
* @copyright NHCarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { describe, it, expect } from "vitest";
import { convert } from "./main.js";
describe("convert", () => {
it("should return the correct heading", () => {
expect(convert("# My level 1 heading")).toBe("<h1>My level 1 heading</h1>");
expect(convert("My heading")).toBe("Invalid format");
expect(convert("##### My level 5 heading")).toBe(
"<h5>My level 5 heading</h5>",
);
expect(convert("#My heading")).toBe("Invalid format");
expect(convert(" ### My level 3 heading")).toBe(
"<h3>My level 3 heading</h3>",
);
expect(convert("####### My level 7 heading")).toBe("Invalid format");
expect(convert("## My #2 heading")).toBe("<h2>My #2 heading</h2>");
});
});
+22
View File
@@ -0,0 +1,22 @@
/**
* @copyright NHCarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
/**
* Given a string representing a Markdown heading, return the equivalent HTML heading.
* @param heading - The Markdown heading to convert.
* @returns The equivalent HTML heading.
* @see https://www.freecodecamp.org/learn/daily-coding-challenge/2025-11-19
*/
const convert = (heading: string): string => {
if (!/^\s*#{1,6}\s+/.test(heading)) {
return "Invalid format";
}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- We know the regex will match and the group will be present.
const level = /^\s*(?<level>#{1,6})\s+/.exec(heading)!.groups!.level!.length;
return `<h${level.toString()}>${heading.replace(/^\s*#{1,6}\s+/, "").trim()}</h${level.toString()}>`;
};
export { convert };
@@ -0,0 +1,19 @@
/**
* @copyright NHCarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { describe, it, expect } from "vitest";
import { backspaceCompare } from "./main.js";
describe("backspaceCompare", () => {
it("should return true if the strings are equal", () => {
expect(backspaceCompare("ab#c", "ad#c")).toBe(true);
expect(backspaceCompare("ab##", "c#d#")).toBe(true);
});
it("should return false if the strings are not equal", () => {
expect(backspaceCompare("ab#c", "b")).toBe(false);
});
});
@@ -0,0 +1,53 @@
/**
* @copyright NHCarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
/**
* Compares two strings to see if they are equal when both are typed into empty text editors.
* '#' means a backspace character.
* @param s - The first string to compare.
* @param t - The second string to compare.
* @returns True if the strings are equal, false otherwise.
* @see https://leetcode.com/problems/backspace-string-compare/description/
*/
const backspaceCompare = (s: string, t: string): boolean => {
let sIndex = s.length - 1;
let tIndex = t.length - 1;
let sSkips = 0;
let tSkips = 0;
while (sIndex >= 0 || tIndex >= 0) {
while (sIndex >= 0) {
if (s[sIndex] === "#") {
sSkips = sSkips + 1;
sIndex = sIndex - 1;
} else if (sSkips > 0) {
sSkips = sSkips - 1;
sIndex = sIndex - 1;
} else {
break;
}
}
while (tIndex >= 0) {
if (t[tIndex] === "#") {
tSkips = tSkips + 1;
tIndex = tIndex - 1;
} else if (tSkips > 0) {
tSkips = tSkips - 1;
tIndex = tIndex - 1;
} else {
break;
}
}
if (s[sIndex] !== t[tIndex]) {
return false;
}
sIndex = sIndex - 1;
tIndex = tIndex - 1;
}
return true;
};
export { backspaceCompare };
+8
View File
@@ -0,0 +1,8 @@
{
"extends": "@nhcarrigan/typescript-config",
"compilerOptions": {
"rootDir": "./src",
"noEmit": true
},
"exclude": ["vitest.config.ts"]
}
+18
View File
@@ -0,0 +1,18 @@
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
coverage: {
provider: 'v8',
reporter: ['text'],
include: ['src/**/*.ts'],
exclude: ['src/**/*.spec.ts'],
thresholds: {
statements: 100,
branches: 100,
functions: 100,
lines: 100,
},
},
},
})