Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
777a0c7
run prettier
duck-master Aug 5, 2025
31b1684
run npm init @eslint/config
duck-master Aug 5, 2025
b9a430e
explicitly add eslint config
duck-master Aug 5, 2025
2c51cb3
rewrite jest.config.js to use import-style dependencies
duck-master Aug 5, 2025
61ac83c
disable @typescript-eslint/no-explicit-any for now
duck-master Aug 5, 2025
65d91e0
change _apiTypeCheck to a type declaration and remove underscore in s…
duck-master Aug 5, 2025
d112dce
remove parameter name from ws.onopen
duck-master Aug 5, 2025
ef85bdb
move jest.config.js to jest.config.mjs; run prettier again
duck-master Aug 5, 2025
909edf0
Revert "move jest.config.js to jest.config.mjs; run prettier again"
duck-master Aug 5, 2025
2eddb73
use resolveJsonModules in tsconfig.json; run prettier a third time
duck-master Aug 5, 2025
4dd1ce0
sidestep json altogether with tsconfig.js
duck-master Aug 5, 2025
0c5df93
explicitly add tsconfig.js
duck-master Aug 5, 2025
4eed39c
disable @typescript-eslint/no-unused-expressions
duck-master Aug 5, 2025
f749619
remove another require-style import; set esModuleInterop to true
duck-master Aug 5, 2025
289eea1
remove unused variables
duck-master Aug 5, 2025
bad5851
make is-prod.ts import a default import
duck-master Aug 5, 2025
f9fac4f
limit while loop for binary search
duck-master Aug 5, 2025
4a1ad60
remove ts-ignore
duck-master Aug 5, 2025
00a6aee
fix type error by stringifying path in src/util/api.ts
duck-master Aug 5, 2025
bafcbc6
run prettier
duck-master Aug 5, 2025
5036ebd
introduce ci-common.yml
duck-master Aug 6, 2025
f876cb1
undo changes to yarn.lock
duck-master Aug 6, 2025
04df078
fix tsc command in CI, binarysearch iteration loop bound
duck-master Aug 15, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions .github/workflows/ci-common.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
name: CI checks for common workspace

on:
pull_request:
branches: ['main']
paths: ['common/**/*', '!**/*.{txt,md}']
push:
branches: ['main']
paths: ['common/**/*', '!**/*.{txt,md}']

defaults:
run:
working-directory: 'common'
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
cache: 'yarn'
- name: Install yarn
run: npm install -g yarn
- name: Install dependencies
run: yarn
- name: Run tests
run: yarn test
eslint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
cache: 'yarn'
- name: Install yarn
run: npm install -g yarn
- name: Install dependencies
run: yarn
- name: Run ESLint
run: yarn eslint .
prettier:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
cache: 'yarn'
- name: Install yarn
run: npm install -g yarn
- name: Install dependencies
run: yarn
- name: Run Prettier
run: yarn prettier . --check --ignore-unknown
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
cache: 'yarn'
- name: Install yarn
run: npm install -g yarn
- name: Install dependencies
run: yarn
- name: Run tsc to check types
run: yarn tsc --noEmit --strict
17 changes: 17 additions & 0 deletions common/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import globals from 'globals'
import pluginJs from '@eslint/js'
import tseslint from 'typescript-eslint'

/** @type {import('eslint').Linter.Config[]} */
export default [
{ files: ['**/*.{js,mjs,cjs,ts}'] },
{ languageOptions: { globals: { ...globals.browser, ...globals.node } } },
pluginJs.configs.recommended,
...tseslint.configs.recommended,
{
rules: {
'@typescript-eslint/no-explicit-any': 'off', // disable this particular rule for now, since the `any` annotations are very tricky to get rid of
'@typescript-eslint/no-unused-expressions': 'off', // turn this off too for now, otherwise ESLint says "Cannot read properties of undefined"
},
},
]
8 changes: 4 additions & 4 deletions common/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
const { pathsToModuleNameMapper } = require('ts-jest')
const { compilerOptions } = require('./tsconfig')
import { pathsToModuleNameMapper } from 'ts-jest'
import tsconfig from './tsconfig.js'

module.exports = {
export default {
preset: 'ts-jest',
testEnvironment: 'node',
moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, {
moduleNameMapper: pathsToModuleNameMapper(tsconfig.compilerOptions.paths, {
prefix: '<rootDir>/',
}),
testMatch: ['**/*.test.ts'],
Expand Down
7 changes: 6 additions & 1 deletion common/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"name": "common",
"type": "module",
"version": "1.0.0",
"private": true,
"scripts": {
Expand All @@ -25,11 +26,15 @@
"zod": "3.21.4"
},
"devDependencies": {
"@eslint/js": "9.32.0",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify ESLint dependency versions for compatibility and security.

The ESLint-related dependencies look reasonable, but ensure these versions are compatible with each other and free from known vulnerabilities.

Also applies to: 33-34, 37-38


🏁 Script executed:

#!/bin/bash
# Description: Check for compatibility issues and security advisories for ESLint dependencies

# Check for npm audit issues with the new dependencies
echo "Checking for security vulnerabilities..."
npm audit --package-lock-only --audit-level=moderate

# Verify ESLint and typescript-eslint compatibility
echo "Checking ESLint version compatibility..."
curl -s https://registry.npmjs.org/typescript-eslint/8.39.0 | jq '.peerDependencies.eslint'

Length of output: 697


🏁 Script executed:

#!/bin/bash
# List all ESLint-related devDependencies and their versions
echo "ESLint-related devDependencies in common/package.json:"
jq -r '.devDependencies 
  | to_entries[] 
  | select(.key | test("eslint")) 
  | "\(.key)@\(.value)"'

echo
# For each, fetch its peerDependencies.eslint requirement
echo "peerDependencies.eslint for each:"
for dep in $(jq -r '.devDependencies | keys[] | select(test("eslint"))' common/package.json); do
  version=$(jq -r ".devDependencies[\"$dep\"]" common/package.json)
  requirement=$(curl -s "https://registry.npmjs.org/$dep/$version" \
    | jq -r '.peerDependencies.eslint // "none"')
  echo "$dep@$version$requirement"
done

Length of output: 1505


ESLint dependencies compatibility confirmed; add a lockfile for security auditing

  • common/package.json devDependencies:
    • eslint@9.32.0
    • @eslint/js@9.32.0
    • typescript-eslint@8.39.0 → requires eslint ^8.57.0 || ^9.0.0 (9.32.0 satisfies this)
  • No other ESLint-related peer dependencies detected.
  • A lockfile is missing, so npm audit cannot run. Please generate a package-lock.json (e.g. npm i --package-lock-only) and rerun npm audit --audit-level=moderate to surface any vulnerabilities.

Also applies to: lines 33–34, 37–38

🤖 Prompt for AI Agents
In common/package.json around lines 29, 33-34, and 37-38, ESLint-related
dependencies versions are compatible, but there is no lockfile present. Generate
a package-lock.json by running `npm i --package-lock-only` in the common
directory, then rerun `npm audit --audit-level=moderate` to identify any
vulnerabilities and ensure security auditing is enabled.

"@types/jest": "29.2.4",
"@types/lodash": "4.14.178",
"@types/string-similarity": "4.0.0",
"eslint": "9.32.0",
"globals": "16.3.0",
"jest": "29.3.1",
"supabase": "2.15.8",
"ts-jest": "29.0.3"
"ts-jest": "29.0.3",
"typescript-eslint": "8.39.0"
}
}
6 changes: 3 additions & 3 deletions common/src/api/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ type APIGenericSchema = {
cache?: string
}

let _apiTypeCheck: { [x: string]: APIGenericSchema }
type apiTypeCheck = { [x: string]: APIGenericSchema }

export const API = (_apiTypeCheck = {
export const API: apiTypeCheck = {
health: {
method: 'GET',
authed: false,
Expand Down Expand Up @@ -476,7 +476,7 @@ export const API = (_apiTypeCheck = {
radius: z.number().min(1).max(500),
}),
},
} as const)
} as const

export type APIPath = keyof typeof API
export type APISchema<N extends APIPath> = (typeof API)[N]
Expand Down
2 changes: 1 addition & 1 deletion common/src/api/websocket-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export class APIRealtimeClient {
// to connect, so we need to turn on our reconnect in that case
this.waitAndReconnect()
}
this.ws.onopen = (_ev) => {
this.ws.onopen = () => {
if (VERBOSE_LOGGING) {
console.info('API websocket opened.')
}
Expand Down
1 change: 0 additions & 1 deletion common/src/api/zod-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ export const arraybeSchema = z
.or(z.string())
.transform(arrify)

// @ts-ignore
export const contentSchema: z.ZodType<JSONContent> = z.lazy(() =>
z.intersection(
z.record(z.any()),
Expand Down
4 changes: 2 additions & 2 deletions common/src/envs/is-prod.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import admin from 'firebase-admin'

export const isProd = () => {
// For cloud run API service
if (process.env.ENVIRONMENT) {
Expand All @@ -7,8 +9,6 @@ export const isProd = () => {
return process.env.NEXT_PUBLIC_FIREBASE_ENV == 'PROD'
} else {
// For local scripts and cloud functions
// eslint-disable-next-line @typescript-eslint/no-var-requires
const admin = require('firebase-admin')
return admin.app().options.projectId === 'polylove'
}
}
2 changes: 1 addition & 1 deletion common/src/love/lover.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Row, run, SupabaseClient } from 'common/supabase/utils'
import { User } from 'common/user'
import { Database } from 'common/supabase/schema'
//import { Database } from 'common/supabase/schema'

export type LoverRow = Row<'lovers'>
export type Lover = LoverRow & { user: User }
Expand Down
2 changes: 1 addition & 1 deletion common/src/secrets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export const getServiceAccountCredentials = (env: 'PROD' | 'DEV') => {

try {
return JSON.parse(readFileSync(keyPath, { encoding: 'utf8' }))
} catch (e) {
} catch {
throw new Error(`Failed to load service account key from ${keyPath}.`)
}
}
66 changes: 49 additions & 17 deletions common/src/socials.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ describe('strip', () => {

describe('instagram', () => {
it('should strip instagram URLs', () => {
expect(strip('instagram', 'https://instagram.com/username')).toBe('username')
expect(strip('instagram', 'https://instagram.com/username')).toBe(
'username'
)
expect(strip('instagram', 'instagram.com/username')).toBe('username')
expect(strip('instagram', '@username')).toBe('username')
expect(strip('instagram', 'username')).toBe('username')
Expand All @@ -32,7 +34,9 @@ describe('strip', () => {

describe('bluesky', () => {
it('should strip bluesky URLs', () => {
expect(strip('bluesky', 'https://bsky.app/profile/username')).toBe('username')
expect(strip('bluesky', 'https://bsky.app/profile/username')).toBe(
'username'
)
expect(strip('bluesky', 'bsky.app/profile/username')).toBe('username')
expect(strip('bluesky', '@username')).toBe('username')
expect(strip('bluesky', 'username')).toBe('username')
Expand All @@ -41,16 +45,24 @@ describe('strip', () => {

describe('mastodon', () => {
it('should handle mastodon handles', () => {
expect(strip('mastodon', '@user@instance.social')).toBe('user@instance.social')
expect(strip('mastodon', 'user@instance.social')).toBe('user@instance.social')
expect(strip('mastodon', '@user@instance.social')).toBe(
'user@instance.social'
)
expect(strip('mastodon', 'user@instance.social')).toBe(
'user@instance.social'
)
})
})

describe('linkedin', () => {
it('should strip linkedin URLs', () => {
expect(strip('linkedin', 'https://linkedin.com/in/username')).toBe('username')
expect(strip('linkedin', 'https://linkedin.com/in/username')).toBe(
'username'
)
expect(strip('linkedin', 'linkedin.com/in/username')).toBe('username')
expect(strip('linkedin', 'https://linkedin.com/company/companyname')).toBe('companyname')
expect(
strip('linkedin', 'https://linkedin.com/company/companyname')
).toBe('companyname')
expect(strip('linkedin', 'username')).toBe('username')
})
})
Expand All @@ -59,22 +71,42 @@ describe('strip', () => {
describe('getSocialUrl', () => {
it('should generate correct URLs for each platform', () => {
expect(getSocialUrl('x', 'username')).toBe('https://x.com/username')
expect(getSocialUrl('github', 'username')).toBe('https://github.com/username')
expect(getSocialUrl('instagram', 'username')).toBe('https://instagram.com/username')
expect(getSocialUrl('bluesky', 'username')).toBe('https://bsky.app/profile/username')
expect(getSocialUrl('mastodon', 'user@instance.social')).toBe('https://instance.social/@user')
expect(getSocialUrl('linkedin', 'username')).toBe('https://linkedin.com/in/username')
expect(getSocialUrl('facebook', 'username')).toBe('https://facebook.com/username')
expect(getSocialUrl('spotify', 'username')).toBe('https://open.spotify.com/user/username')
expect(getSocialUrl('github', 'username')).toBe(
'https://github.com/username'
)
expect(getSocialUrl('instagram', 'username')).toBe(
'https://instagram.com/username'
)
expect(getSocialUrl('bluesky', 'username')).toBe(
'https://bsky.app/profile/username'
)
expect(getSocialUrl('mastodon', 'user@instance.social')).toBe(
'https://instance.social/@user'
)
expect(getSocialUrl('linkedin', 'username')).toBe(
'https://linkedin.com/in/username'
)
expect(getSocialUrl('facebook', 'username')).toBe(
'https://facebook.com/username'
)
expect(getSocialUrl('spotify', 'username')).toBe(
'https://open.spotify.com/user/username'
)
})

it('should handle custom website URLs', () => {
expect(getSocialUrl('site', 'example.com')).toBe('https://example.com')
expect(getSocialUrl('site', 'https://example.com')).toBe('https://example.com')
expect(getSocialUrl('site', 'https://example.com')).toBe(
'https://example.com'
)
})

it('should handle discord user IDs and default invite', () => {
expect(getSocialUrl('discord', '123456789012345678')).toBe('https://discord.com/users/123456789012345678')
expect(getSocialUrl('discord', 'not-an-id')).toBe('https://discord.com/invite/AYDw8dbrGS')
expect(getSocialUrl('discord', '123456789012345678')).toBe(
'https://discord.com/users/123456789012345678'
)
expect(getSocialUrl('discord', 'not-an-id')).toBe(
'https://discord.com/invite/AYDw8dbrGS'
)
})
})
})
2 changes: 1 addition & 1 deletion common/src/util/algos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export function binarySearch(
) {
let mid = 0
let i = 0
while (true) {
while (i <= 100000) {
mid = min + (max - min) / 2

// Break once we've reached max precision.
Expand Down
2 changes: 1 addition & 1 deletion common/src/util/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const typedAPICall = <P extends APIPath>(
) => {
// parse any params that should part of the path (like market/:id)
const newParams: any = {}
let url = getApiUrl(path)
let url: string = getApiUrl(String(path)) // `path` could be a string or number, we don't know
forEach(params, (v, k) => {
if (url.includes(`:${k}`)) {
url = url.replace(`:${k}`, v + '')
Expand Down
4 changes: 2 additions & 2 deletions common/src/util/json.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export const safeJsonParse = (json: string | undefined | null) => {
try {
return JSON.parse(json ?? '')
} catch (e) {
return JSON.parse(json ?? '')
} catch {
return null
}
}
2 changes: 1 addition & 1 deletion common/src/util/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {
getText,
getSchema,
getTextSerializersFromSchema,
Node,
//Node,
JSONContent,
} from '@tiptap/core'
import { Node as ProseMirrorNode } from '@tiptap/pm/model'
Expand Down
2 changes: 1 addition & 1 deletion common/src/util/random.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,4 @@ export const shuffle = (array: unknown[], rand: () => number) => {
const swapIndex = i + Math.floor(rand() * (array.length - i))
;[array[i], array[swapIndex]] = [array[swapIndex], array[i]]
}
}
}
2 changes: 1 addition & 1 deletion common/src/util/string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ export function removeEmojis(str: string) {
/[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F700}-\u{1F77F}\u{1F780}-\u{1F7FF}\u{1F800}-\u{1F8FF}\u{1F900}-\u{1F9FF}\u{1FA00}-\u{1FA6F}\u{1FA70}-\u{1FAFF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}\u{2300}-\u{23FF}\u{2B50}\u{2B55}\u{2934}\u{2935}\u{2B05}\u{2B06}\u{2B07}\u{2B1B}\u{2B1C}\u{2B50}\u{2B55}\u{3030}\u{303D}\u{3297}\u{3299}\u{FE0F}]/gu,
''
)
}
}
22 changes: 22 additions & 0 deletions common/tsconfig.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// hacky workaround for not being able to import tsconfig.json directly
export default {
compilerOptions: {
rootDir: 'src',
composite: true,
module: 'commonjs',
moduleResolution: 'node',
noImplicitReturns: true,
outDir: 'lib',
tsBuildInfoFile: 'lib/tsconfig.tsbuildinfo',
sourceMap: true,
strict: true,
target: 'es2022',
skipLibCheck: true,
paths: {
'common/*': ['./src/*', '../lib/*'],
},
resolveJsonModule: true,
esModuleInterop: true,
},
include: ['src/**/*.ts'],
}
4 changes: 3 additions & 1 deletion common/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
"skipLibCheck": true,
"paths": {
"common/*": ["./src/*", "../lib/*"]
}
},
"resolveJsonModule": true,
"esModuleInterop": true
},
"include": ["src/**/*.ts"]
}