+4
.oxfmtrc.json
+4
.oxfmtrc.json
+4
-4
PLAN.md
+4
-4
PLAN.md
···
1
# rough notes on how I think this should work
2
3
- we start of with no accounts
4
-
- ``/teal auth`` sends user a link to log in with atproto account
5
- after auth success, we take did and send http request to tap instance to start backfilling for repo
6
- user can now use bot commands
7
8
## planned commands
9
10
-
- ``teal auth`` sends user a link to log in with atproto account
11
-
- ``top <artist>``: top 10 listeners for artist (total amount of plays across all songs / albums)
12
-
- ``recent``: most recent play
13
14
## web interface for account management...maybe..?
15
···
1
# rough notes on how I think this should work
2
3
- we start of with no accounts
4
+
- `/teal auth` sends user a link to log in with atproto account
5
- after auth success, we take did and send http request to tap instance to start backfilling for repo
6
- user can now use bot commands
7
8
## planned commands
9
10
+
- `teal auth` sends user a link to log in with atproto account
11
+
- `top <artist>`: top 10 listeners for artist (total amount of plays across all songs / albums)
12
+
- `recent`: most recent play
13
14
## web interface for account management...maybe..?
15
+6
-4
apps/bot/commands/auth.ts
+6
-4
apps/bot/commands/auth.ts
···
2
import { logger } from "@tealfmbot/common/logger.ts";
3
4
export default {
5
-
data: new SlashCommandBuilder().setName("auth").setDescription(
6
-
"Authenticate your account with the teal.fm bot to start tracking your listens",
7
-
),
8
async execute(interaction: CommandInteraction) {
9
await interaction.reply("placeholder");
10
-
logger.info("auth command sent")
11
},
12
};
···
2
import { logger } from "@tealfmbot/common/logger.ts";
3
4
export default {
5
+
data: new SlashCommandBuilder()
6
+
.setName("auth")
7
+
.setDescription(
8
+
"Authenticate your account with the teal.fm bot to start tracking your listens",
9
+
),
10
async execute(interaction: CommandInteraction) {
11
await interaction.reply("placeholder");
12
+
logger.info("auth command sent");
13
},
14
};
+2
-4
apps/bot/commands/ping.ts
+2
-4
apps/bot/commands/ping.ts
···
2
import { logger } from "@tealfmbot/common/logger.ts";
3
4
export default {
5
-
data: new SlashCommandBuilder().setName("ping").setDescription(
6
-
"replies with pong",
7
-
),
8
async execute(interaction: CommandInteraction) {
9
await interaction.reply("pong lol");
10
-
logger.info("ping command sent")
11
},
12
};
···
2
import { logger } from "@tealfmbot/common/logger.ts";
3
4
export default {
5
+
data: new SlashCommandBuilder().setName("ping").setDescription("replies with pong"),
6
async execute(interaction: CommandInteraction) {
7
await interaction.reply("pong lol");
8
+
logger.info("ping command sent");
9
},
10
};
+4
-4
apps/bot/commands/top.ts
+4
-4
apps/bot/commands/top.ts
···
2
import { logger } from "@tealfmbot/common/logger.ts";
3
4
export default {
5
-
data: new SlashCommandBuilder().setName("top").setDescription(
6
-
"Find the top listeners for the specified artist(s)",
7
-
),
8
async execute(interaction: CommandInteraction) {
9
await interaction.reply("placeholder");
10
-
logger.info("top command sent")
11
},
12
};
···
2
import { logger } from "@tealfmbot/common/logger.ts";
3
4
export default {
5
+
data: new SlashCommandBuilder()
6
+
.setName("top")
7
+
.setDescription("Find the top listeners for the specified artist(s)"),
8
async execute(interaction: CommandInteraction) {
9
await interaction.reply("placeholder");
10
+
logger.info("top command sent");
11
},
12
};
+6
-10
apps/bot/deploy-commands.ts
+6
-10
apps/bot/deploy-commands.ts
···
11
const commandPaths = fs.globSync("commands/**/*.ts");
12
13
for await (const cmdPath of commandPaths) {
14
-
const absoluteCommandPath = path.join(import.meta.dirname, cmdPath)
15
-
const command = await import(absoluteCommandPath)
16
if ("data" in command.default && "execute" in command.default) {
17
commands.push(command.default.data);
18
} else {
···
26
27
(async () => {
28
try {
29
-
console.log(
30
-
`Started refreshing ${commands.length} application (/) commands.`,
31
-
);
32
33
-
const data = await rest.put(
34
Routes.applicationGuildCommands(DISCORD_APPLICATION_ID, DISCORD_GUILD_ID),
35
{ body: commands },
36
-
) as unknown[];
37
38
-
console.log(
39
-
`Successfully reloaded ${data.length} application (/) commands.`,
40
-
);
41
} catch (error) {
42
console.error(error);
43
}
···
11
const commandPaths = fs.globSync("commands/**/*.ts");
12
13
for await (const cmdPath of commandPaths) {
14
+
const absoluteCommandPath = path.join(import.meta.dirname, cmdPath);
15
+
const command = await import(absoluteCommandPath);
16
if ("data" in command.default && "execute" in command.default) {
17
commands.push(command.default.data);
18
} else {
···
26
27
(async () => {
28
try {
29
+
console.log(`Started refreshing ${commands.length} application (/) commands.`);
30
31
+
const data = (await rest.put(
32
Routes.applicationGuildCommands(DISCORD_APPLICATION_ID, DISCORD_GUILD_ID),
33
{ body: commands },
34
+
)) as unknown[];
35
36
+
console.log(`Successfully reloaded ${data.length} application (/) commands.`);
37
} catch (error) {
38
console.error(error);
39
}
+1
-1
apps/bot/discord.d.ts
+1
-1
apps/bot/discord.d.ts
+1
-7
apps/bot/main.ts
+1
-7
apps/bot/main.ts
+1
-1
apps/bot/package.json
+1
-1
apps/bot/package.json
+2
-4
apps/bot/tsconfig.json
+2
-4
apps/bot/tsconfig.json
+1
-2
apps/tapper/index.ts
+1
-2
apps/tapper/index.ts
+1
-1
apps/tapper/package.json
+1
-1
apps/tapper/package.json
+1
-3
apps/tapper/tsconfig.json
+1
-3
apps/tapper/tsconfig.json
+6
lefthook.yml
+6
lefthook.yml
+14
-11
package.json
+14
-11
package.json
···
3
"version": "0.0.1",
4
"private": true,
5
"description": "A discord bot for teal.fm",
6
"scripts": {
7
"bot": "pnpm --filter bot dev",
8
"tap": "pnpm --filter tapper dev",
9
"dev": "pnpm --filter './apps/**' dev",
10
"typecheck": "pnpm --filter './apps/**' typecheck",
11
-
"lint": "oxlint"
12
},
13
-
"keywords": [
14
-
"teal.fm",
15
-
"atprotocol",
16
-
"music"
17
-
],
18
-
"author": "Dane Miller <me@dane.computer>",
19
-
"license": "MIT",
20
-
"packageManager": "pnpm@10.15.0",
21
-
"repository": {},
22
"devDependencies": {
23
"oxlint": "^1.35.0",
24
"typescript": "^5.9.3"
25
-
}
26
}
···
3
"version": "0.0.1",
4
"private": true,
5
"description": "A discord bot for teal.fm",
6
+
"keywords": [
7
+
"atprotocol",
8
+
"music",
9
+
"teal.fm"
10
+
],
11
+
"license": "MIT",
12
+
"author": "Dane Miller <me@dane.computer>",
13
+
"repository": {},
14
"scripts": {
15
"bot": "pnpm --filter bot dev",
16
"tap": "pnpm --filter tapper dev",
17
"dev": "pnpm --filter './apps/**' dev",
18
"typecheck": "pnpm --filter './apps/**' typecheck",
19
+
"lint": "oxlint",
20
+
"format": "oxfmt"
21
},
22
"devDependencies": {
23
+
"lefthook": "^2.0.13",
24
+
"oxfmt": "^0.20.0",
25
"oxlint": "^1.35.0",
26
"typescript": "^5.9.3"
27
+
},
28
+
"packageManager": "pnpm@10.15.0"
29
}
+3
-3
packages/common/constants.ts
+3
-3
packages/common/constants.ts
···
1
-
import { loadEnvFile } from "node:process"
2
-
import path from "node:path"
3
4
-
loadEnvFile(path.join(import.meta.dirname, "../../.env"))
5
6
export const DISCORD_BOT_TOKEN = process.env.DISCORD_BOT_TOKEN as string;
7
export const DISCORD_APPLICATION_ID = process.env.DISCORD_APPLICATION_ID as string;
···
1
+
import { loadEnvFile } from "node:process";
2
+
import path from "node:path";
3
4
+
loadEnvFile(path.join(import.meta.dirname, "../../.env"));
5
6
export const DISCORD_BOT_TOKEN = process.env.DISCORD_BOT_TOKEN as string;
7
export const DISCORD_APPLICATION_ID = process.env.DISCORD_APPLICATION_ID as string;
+5
-5
packages/common/logger.ts
+5
-5
packages/common/logger.ts
+6
-6
packages/common/package.json
+6
-6
packages/common/package.json
···
1
{
2
"name": "@tealfmbot/common",
3
"version": "0.0.0",
4
-
"type": "module",
5
"private": true,
6
"exports": {
7
"./*": "./*"
8
},
9
"scripts": {
10
"typecheck": "tsc --noEmit"
11
},
12
"devDependencies": {
13
"@tealfmbot/tsconfig": "workspace:*",
14
"@types/node": "^22.15.3",
15
-
"typescript": "5.9.2",
16
-
"pino-pretty": "^13.1.3"
17
-
},
18
-
"dependencies": {
19
-
"pino": "^10.1.0"
20
}
21
}
···
1
{
2
"name": "@tealfmbot/common",
3
"version": "0.0.0",
4
"private": true,
5
+
"type": "module",
6
"exports": {
7
"./*": "./*"
8
},
9
"scripts": {
10
"typecheck": "tsc --noEmit"
11
},
12
+
"dependencies": {
13
+
"pino": "^10.1.0"
14
+
},
15
"devDependencies": {
16
"@tealfmbot/tsconfig": "workspace:*",
17
"@types/node": "^22.15.3",
18
+
"pino-pretty": "^13.1.3",
19
+
"typescript": "5.9.2"
20
}
21
}
+1
-1
packages/common/tsconfig.json
+1
-1
packages/common/tsconfig.json
+9
-9
packages/database/db.ts
+9
-9
packages/database/db.ts
···
1
-
import type { DB } from "kysely-codegen"
2
-
import { Pool } from "pg"
3
-
import { Kysely, PostgresDialect } from "kysely"
4
-
import { DATABASE_URL } from "@tealfmbot/common/constants.ts"
5
6
const dialect = new PostgresDialect({
7
pool: new Pool({
8
-
connectionString: DATABASE_URL
9
-
})
10
-
})
11
12
export const db = new Kysely<DB>({
13
-
dialect
14
-
})
···
1
+
import type { DB } from "kysely-codegen";
2
+
import { Pool } from "pg";
3
+
import { Kysely, PostgresDialect } from "kysely";
4
+
import { DATABASE_URL } from "@tealfmbot/common/constants.ts";
5
6
const dialect = new PostgresDialect({
7
pool: new Pool({
8
+
connectionString: DATABASE_URL,
9
+
}),
10
+
});
11
12
export const db = new Kysely<DB>({
13
+
dialect,
14
+
});
+21
-16
packages/database/migrations/schema.ts
+21
-16
packages/database/migrations/schema.ts
···
1
-
import { Kysely, sql } from "kysely"
2
3
export async function up(db: Kysely<any>): Promise<void> {
4
await db.schema
5
-
.createTable("users").ifNotExists()
6
.addColumn("id", "serial", (col) => col.primaryKey())
7
.addColumn("did", "varchar", (col) => col.notNull().unique())
8
-
.addColumn('created_at', 'timestamp', (col) =>
9
-
col.defaultTo(sql`now()`).notNull(),
10
-
)
11
-
.execute()
12
13
await db.schema
14
-
.createTable("plays").ifNotExists()
15
.addColumn("id", "serial", (col) => col.primaryKey())
16
.addColumn("played_time", "timestamptz", (col) => col.notNull())
17
.addColumn("release_name", "varchar", (col) => col.notNull())
18
-
.addColumn("track_name", 'varchar', (col) => col.notNull())
19
.addColumn("indexed_at", "timestamp", (col) => col.defaultTo(sql`now()`).notNull())
20
-
.addColumn("user_id", "integer", (col) => col.references("users.id").onDelete("cascade").notNull())
21
-
.execute()
22
23
await db.schema
24
-
.createTable("artists").ifNotExists()
25
.addColumn("artist_name", "varchar", (col) => col.notNull())
26
-
.addColumn("play_id", "integer", (col) => col.references("plays.id").onDelete("cascade").notNull())
27
-
.execute()
28
}
29
30
export async function down(db: Kysely<any>): Promise<void> {
31
-
await db.schema.dropTable("users").execute()
32
-
await db.schema.dropTable("plays").execute()
33
-
await db.schema.dropTable("artists").execute()
34
}
···
1
+
import { Kysely, sql } from "kysely";
2
3
export async function up(db: Kysely<any>): Promise<void> {
4
await db.schema
5
+
.createTable("users")
6
+
.ifNotExists()
7
.addColumn("id", "serial", (col) => col.primaryKey())
8
.addColumn("did", "varchar", (col) => col.notNull().unique())
9
+
.addColumn("created_at", "timestamp", (col) => col.defaultTo(sql`now()`).notNull())
10
+
.execute();
11
12
await db.schema
13
+
.createTable("plays")
14
+
.ifNotExists()
15
.addColumn("id", "serial", (col) => col.primaryKey())
16
.addColumn("played_time", "timestamptz", (col) => col.notNull())
17
.addColumn("release_name", "varchar", (col) => col.notNull())
18
+
.addColumn("track_name", "varchar", (col) => col.notNull())
19
.addColumn("indexed_at", "timestamp", (col) => col.defaultTo(sql`now()`).notNull())
20
+
.addColumn("user_id", "integer", (col) =>
21
+
col.references("users.id").onDelete("cascade").notNull(),
22
+
)
23
+
.execute();
24
25
await db.schema
26
+
.createTable("artists")
27
+
.ifNotExists()
28
.addColumn("artist_name", "varchar", (col) => col.notNull())
29
+
.addColumn("play_id", "integer", (col) =>
30
+
col.references("plays.id").onDelete("cascade").notNull(),
31
+
)
32
+
.execute();
33
}
34
35
export async function down(db: Kysely<any>): Promise<void> {
36
+
await db.schema.dropTable("users").execute();
37
+
await db.schema.dropTable("plays").execute();
38
+
await db.schema.dropTable("artists").execute();
39
}
+23
-24
packages/database/migrator.ts
+23
-24
packages/database/migrator.ts
···
1
-
import path from "node:path"
2
-
import { Pool } from "pg"
3
-
import fs from "node:fs/promises"
4
-
import type { DB } from "kysely-codegen"
5
-
import { Kysely, Migrator, PostgresDialect, FileMigrationProvider } from "kysely"
6
-
import { DATABASE_URL } from "@tealfmbot/common/constants.ts"
7
8
async function migrateToLatest() {
9
const db = new Kysely<DB>({
10
dialect: new PostgresDialect({
11
pool: new Pool({
12
-
connectionString: DATABASE_URL
13
-
})
14
-
})
15
-
})
16
17
const migrator = new Migrator({
18
db,
19
provider: new FileMigrationProvider({
20
fs,
21
path,
22
-
migrationFolder: path.join(import.meta.dirname, "../migrations")
23
-
})
24
-
})
25
-
26
27
-
const { error, results } = await migrator.migrateToLatest()
28
29
-
results?.forEach(result => {
30
if (result.status === "Success") {
31
-
console.log(`migration ${result.migrationName} was executed successfully`)
32
} else if (result.status === "Error") {
33
-
console.error(`failed to execute migration: "${result.migrationName}" `)
34
}
35
-
})
36
37
if (error) {
38
-
console.error("failed to migrate")
39
-
console.error(error)
40
-
process.exit(1)
41
}
42
43
-
await db.destroy()
44
}
45
46
-
migrateToLatest().catch(err => console.error(err))
···
1
+
import path from "node:path";
2
+
import { Pool } from "pg";
3
+
import fs from "node:fs/promises";
4
+
import type { DB } from "kysely-codegen";
5
+
import { Kysely, Migrator, PostgresDialect, FileMigrationProvider } from "kysely";
6
+
import { DATABASE_URL } from "@tealfmbot/common/constants.ts";
7
8
async function migrateToLatest() {
9
const db = new Kysely<DB>({
10
dialect: new PostgresDialect({
11
pool: new Pool({
12
+
connectionString: DATABASE_URL,
13
+
}),
14
+
}),
15
+
});
16
17
const migrator = new Migrator({
18
db,
19
provider: new FileMigrationProvider({
20
fs,
21
path,
22
+
migrationFolder: path.join(import.meta.dirname, "../migrations"),
23
+
}),
24
+
});
25
26
+
const { error, results } = await migrator.migrateToLatest();
27
28
+
results?.forEach((result) => {
29
if (result.status === "Success") {
30
+
console.log(`migration ${result.migrationName} was executed successfully`);
31
} else if (result.status === "Error") {
32
+
console.error(`failed to execute migration: "${result.migrationName}" `);
33
}
34
+
});
35
36
if (error) {
37
+
console.error("failed to migrate");
38
+
console.error(error);
39
+
process.exit(1);
40
}
41
42
+
await db.destroy();
43
}
44
45
+
migrateToLatest().catch((err) => console.error(err));
+7
-7
packages/database/package.json
+7
-7
packages/database/package.json
···
1
{
2
"name": "@tealfmbot/database",
3
"version": "0.0.0",
4
-
"type": "module",
5
"private": true,
6
"exports": {
7
"./db.ts": "./db.ts"
8
},
···
12
"seed": "tsx seed.ts",
13
"typecheck": "tsc --noEmit"
14
},
15
"devDependencies": {
16
"@types/node": "^22.15.3",
17
"@types/pg": "^8.16.0",
18
"kysely-codegen": "^0.19.0",
19
"tsx": "^4.21.0",
20
"typescript": "5.9.2"
21
-
},
22
-
"dependencies": {
23
-
"@tealfmbot/common": "workspace:*",
24
-
"@tealfmbot/tsconfig": "workspace:*",
25
-
"kysely": "^0.28.9",
26
-
"pg": "^8.16.3"
27
}
28
}
···
1
{
2
"name": "@tealfmbot/database",
3
"version": "0.0.0",
4
"private": true,
5
+
"type": "module",
6
"exports": {
7
"./db.ts": "./db.ts"
8
},
···
12
"seed": "tsx seed.ts",
13
"typecheck": "tsc --noEmit"
14
},
15
+
"dependencies": {
16
+
"@tealfmbot/common": "workspace:*",
17
+
"@tealfmbot/tsconfig": "workspace:*",
18
+
"kysely": "^0.28.9",
19
+
"pg": "^8.16.3"
20
+
},
21
"devDependencies": {
22
"@types/node": "^22.15.3",
23
"@types/pg": "^8.16.0",
24
"kysely-codegen": "^0.19.0",
25
"tsx": "^4.21.0",
26
"typescript": "5.9.2"
27
}
28
}
+1
-2
packages/database/seed.ts
+1
-2
packages/database/seed.ts
···
6
// }).returning(['did', 'created_at'])
7
// .executeTakeFirst()
8
// console.log({ result })
9
-
10
// const users = await db.selectFrom("users").select(["id", "did"]).execute()
11
// console.log({ users })
12
// await db.deleteFrom("users").where("did", "=", "did:plc:qttsv4e7pu2jl3ilanfgc3zn").execute()
···
14
// console.log({ plays })
15
}
16
17
-
main().catch(error => console.error(error))
···
6
// }).returning(['did', 'created_at'])
7
// .executeTakeFirst()
8
// console.log({ result })
9
// const users = await db.selectFrom("users").select(["id", "did"]).execute()
10
// console.log({ users })
11
// await db.deleteFrom("users").where("did", "=", "did:plc:qttsv4e7pu2jl3ilanfgc3zn").execute()
···
13
// console.log({ plays })
14
}
15
16
+
main().catch((error) => console.error(error));
+1
-3
packages/database/tsconfig.json
+1
-3
packages/database/tsconfig.json
+1
-1
packages/database/types.ts
+1
-1
packages/database/types.ts
+1
-1
packages/tsconfig/tsconfig.base.json
+1
-1
packages/tsconfig/tsconfig.base.json
+2
-6
packages/tsconfig/tsconfig.node.json
+2
-6
packages/tsconfig/tsconfig.node.json
+191
pnpm-lock.yaml
+191
pnpm-lock.yaml
···
8
9
.:
10
devDependencies:
11
oxlint:
12
specifier: ^1.35.0
13
version: 1.35.0
···
332
cpu: [x64]
333
os: [win32]
334
335
'@oxlint/darwin-arm64@1.35.0':
336
resolution: {integrity: sha512-ieiYVHkNZPo77Hgrxav595wGS4rRNKuDNrljf+4xhwpJsddrxMpM64IQUf2IvR3MhK4FxdGzhhB6OVmGVHY5/w==}
337
cpu: [arm64]
···
670
resolution: {integrity: sha512-3BeXMoiOhpOwu62CiVpO6lxfq4eS6KMYfQdMsN/2kUCRNuF2YiEr7u0HLHaQU+O4Xu8YXE3bHVkwaQ85i72EuA==}
671
engines: {node: '>=20.0.0'}
672
673
lines-and-columns@1.2.4:
674
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
675
···
705
706
once@1.4.0:
707
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
708
709
oxlint@1.35.0:
710
resolution: {integrity: sha512-QDX1aUgaiqznkGfTM2qHwva2wtKqhVoqPSVXrnPz+yLUhlNadikD3QRuRtppHl7WGuy3wG6nKAuR8lash3aWSg==}
···
913
thread-stream@3.1.0:
914
resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==}
915
916
to-regex-range@5.0.1:
917
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
918
engines: {node: '>=8.0'}
···
1171
'@esbuild/win32-x64@0.27.2':
1172
optional: true
1173
1174
'@oxlint/darwin-arm64@1.35.0':
1175
optional: true
1176
···
1481
1482
kysely@0.28.9: {}
1483
1484
lines-and-columns@1.2.4: {}
1485
1486
lodash.snakecase@4.1.1: {}
···
1509
once@1.4.0:
1510
dependencies:
1511
wrappy: 1.0.2
1512
1513
oxlint@1.35.0:
1514
optionalDependencies:
···
1732
thread-stream@3.1.0:
1733
dependencies:
1734
real-require: 0.2.0
1735
1736
to-regex-range@5.0.1:
1737
dependencies:
···
8
9
.:
10
devDependencies:
11
+
lefthook:
12
+
specifier: ^2.0.13
13
+
version: 2.0.13
14
+
oxfmt:
15
+
specifier: ^0.20.0
16
+
version: 0.20.0
17
oxlint:
18
specifier: ^1.35.0
19
version: 1.35.0
···
338
cpu: [x64]
339
os: [win32]
340
341
+
'@oxfmt/darwin-arm64@0.20.0':
342
+
resolution: {integrity: sha512-bjR5dqvrd9gxKYfYR0ljUu3/T3+TuDVWcwA7d+tsfmx9lqidlw3zhgBTblnjF1mrd1zkPMoc5zzq86GeSEt1cA==}
343
+
cpu: [arm64]
344
+
os: [darwin]
345
+
346
+
'@oxfmt/darwin-x64@0.20.0':
347
+
resolution: {integrity: sha512-esUDes8FlJX3IY4TVjFLgZrnZlIIyPDlhkCaHgGR3+z2eHFZOvQu68kTSpZLCEJmGXdSpU5rlveycQ6n8tk9ew==}
348
+
cpu: [x64]
349
+
os: [darwin]
350
+
351
+
'@oxfmt/linux-arm64-gnu@0.20.0':
352
+
resolution: {integrity: sha512-irE0RO9B0R6ziQE6kUVZtZ6IuTdRyuumn1cPWhDfpa0XUa5sE0ly8pjVsvJbj/J9qerVtidU05txeXBB5CirQg==}
353
+
cpu: [arm64]
354
+
os: [linux]
355
+
356
+
'@oxfmt/linux-arm64-musl@0.20.0':
357
+
resolution: {integrity: sha512-eXPBLwYJm26DCmwMwhelEwQMRwuGNaYhYZOhd+CYYsmVoF+h6L6dtjwj0Ovuu0Gqh18EL8vfsaoUvb+jr3vEBg==}
358
+
cpu: [arm64]
359
+
os: [linux]
360
+
361
+
'@oxfmt/linux-x64-gnu@0.20.0':
362
+
resolution: {integrity: sha512-dTPW38Hjgb7LoD2mNgyQGBaJ1hu5YgPrxImhl5Eb04eiws+ETCM0wrb2TWGduA+Nv3rHKn3vZEkMTEjklZXgRw==}
363
+
cpu: [x64]
364
+
os: [linux]
365
+
366
+
'@oxfmt/linux-x64-musl@0.20.0':
367
+
resolution: {integrity: sha512-b4duw9JGDK/kZoqrPNU9tBOOZQdUW8KJPZ7gW7z54X1eGSqCJ1PT0XLNmZ7SOA1BzQwQ0a3qmQWfFVOsH3a5bw==}
368
+
cpu: [x64]
369
+
os: [linux]
370
+
371
+
'@oxfmt/win32-arm64@0.20.0':
372
+
resolution: {integrity: sha512-XAzvBhw4K+Fe16dBaFgYAdob9WaM8RYEXl0ibbm5NlNaQEq+5bH9xwc0oaYlHFnLfcgXWmn9ceTAYqNlONQRNA==}
373
+
cpu: [arm64]
374
+
os: [win32]
375
+
376
+
'@oxfmt/win32-x64@0.20.0':
377
+
resolution: {integrity: sha512-fkJqHbJaoOMRmrjHSljyb4/7BgXO3xPLBsJSFGtm3mpfW0HHFbAKvd4/6njhqJz9KY+b3RWP1WssjFshcqQQ4w==}
378
+
cpu: [x64]
379
+
os: [win32]
380
+
381
'@oxlint/darwin-arm64@1.35.0':
382
resolution: {integrity: sha512-ieiYVHkNZPo77Hgrxav595wGS4rRNKuDNrljf+4xhwpJsddrxMpM64IQUf2IvR3MhK4FxdGzhhB6OVmGVHY5/w==}
383
cpu: [arm64]
···
716
resolution: {integrity: sha512-3BeXMoiOhpOwu62CiVpO6lxfq4eS6KMYfQdMsN/2kUCRNuF2YiEr7u0HLHaQU+O4Xu8YXE3bHVkwaQ85i72EuA==}
717
engines: {node: '>=20.0.0'}
718
719
+
lefthook-darwin-arm64@2.0.13:
720
+
resolution: {integrity: sha512-KbQqpNSNTugjtPzt97CNcy/XZy5asJ0+uSLoHc4ML8UCJdsXKYJGozJHNwAd0Xfci/rQlj82A7rPOuTdh0jY0Q==}
721
+
cpu: [arm64]
722
+
os: [darwin]
723
+
724
+
lefthook-darwin-x64@2.0.13:
725
+
resolution: {integrity: sha512-s/vI6sEE8/+rE6CONZzs59LxyuSc/KdU+/3adkNx+Q13R1+p/AvQNeszg3LAHzXmF3NqlxYf8jbj/z5vBzEpRw==}
726
+
cpu: [x64]
727
+
os: [darwin]
728
+
729
+
lefthook-freebsd-arm64@2.0.13:
730
+
resolution: {integrity: sha512-iQeJTU7Zl8EJlCMQxNZQpJFAQ9xl40pydUIv5SYnbJ4nqIr9ONuvrioNv6N2LtKP5aBl1nIWQQ9vMjgVyb3k+A==}
731
+
cpu: [arm64]
732
+
os: [freebsd]
733
+
734
+
lefthook-freebsd-x64@2.0.13:
735
+
resolution: {integrity: sha512-99cAXKRIzpq/u3obUXbOQJCHP+0ZkJbN3TF+1ZQZlRo3Y6+mPSCg9fh/oi6dgbtu4gTI5Ifz3o5p2KZzAIF9ZQ==}
736
+
cpu: [x64]
737
+
os: [freebsd]
738
+
739
+
lefthook-linux-arm64@2.0.13:
740
+
resolution: {integrity: sha512-RWarenY3kLy/DT4/8dY2bwDlYwlELRq9MIFq+FiMYmoBHES3ckWcLX2JMMlM49Y672paQc7MbneSrNUn/FQWhg==}
741
+
cpu: [arm64]
742
+
os: [linux]
743
+
744
+
lefthook-linux-x64@2.0.13:
745
+
resolution: {integrity: sha512-QZRcxXGf8Uj/75ITBqoBh0zWhJE7+uFoRxEHwBq0Qjv55Q4KcFm7FBN/IFQCSd14reY5pmY3kDaWVVy60cAGJA==}
746
+
cpu: [x64]
747
+
os: [linux]
748
+
749
+
lefthook-openbsd-arm64@2.0.13:
750
+
resolution: {integrity: sha512-LAuOWwnNmOlRE0RxKMOhIz5Kr9tXi0rCjzXtDARW9lvfAV6Br2wP+47q0rqQ8m/nVwBYoxfJ/RDunLbb86O1nA==}
751
+
cpu: [arm64]
752
+
os: [openbsd]
753
+
754
+
lefthook-openbsd-x64@2.0.13:
755
+
resolution: {integrity: sha512-n9TIN3QLncyxOHomiKKwzDFHKOCm5H28CVNAZFouKqDwEaUGCs5TJI88V85j4/CgmLVUU8uUn4ClVCxIWYG59w==}
756
+
cpu: [x64]
757
+
os: [openbsd]
758
+
759
+
lefthook-windows-arm64@2.0.13:
760
+
resolution: {integrity: sha512-sdSC4F9Di7y0t43Of9MOA5g/0CmvkM4juQ3sKfUhRcoygetLJn4PR2/pvuDOIaGf4mNMXBP5IrcKaeDON9HrcA==}
761
+
cpu: [arm64]
762
+
os: [win32]
763
+
764
+
lefthook-windows-x64@2.0.13:
765
+
resolution: {integrity: sha512-ccl1v7Fl10qYoghEtjXN+JC1x/y/zLM/NSHf3NFGeKEGBNd1P5d/j6w8zVmhfzi+ekS8whXrcNbRAkLdAqUrSw==}
766
+
cpu: [x64]
767
+
os: [win32]
768
+
769
+
lefthook@2.0.13:
770
+
resolution: {integrity: sha512-D39rCVl7/GpqakvhQvqz07SBpzUWTvWjXKnBZyIy8O6D+Lf9xD6tnbHtG5nWXd9iPvv1AKGQwL9R/e5rNtV6SQ==}
771
+
hasBin: true
772
+
773
lines-and-columns@1.2.4:
774
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
775
···
805
806
once@1.4.0:
807
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
808
+
809
+
oxfmt@0.20.0:
810
+
resolution: {integrity: sha512-+7f8eV8iaK3tENN/FUVxZM1g78HjPehybN8/+/dvEA1O893Dcvk6O7/Q1wTQOHMD7wvdwWdujKl+Uo8QMiKDrQ==}
811
+
engines: {node: ^20.19.0 || >=22.12.0}
812
+
hasBin: true
813
814
oxlint@1.35.0:
815
resolution: {integrity: sha512-QDX1aUgaiqznkGfTM2qHwva2wtKqhVoqPSVXrnPz+yLUhlNadikD3QRuRtppHl7WGuy3wG6nKAuR8lash3aWSg==}
···
1018
thread-stream@3.1.0:
1019
resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==}
1020
1021
+
tinypool@2.0.0:
1022
+
resolution: {integrity: sha512-/RX9RzeH2xU5ADE7n2Ykvmi9ED3FBGPAjw9u3zucrNNaEBIO0HPSYgL0NT7+3p147ojeSdaVu08F6hjpv31HJg==}
1023
+
engines: {node: ^20.0.0 || >=22.0.0}
1024
+
1025
to-regex-range@5.0.1:
1026
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
1027
engines: {node: '>=8.0'}
···
1280
'@esbuild/win32-x64@0.27.2':
1281
optional: true
1282
1283
+
'@oxfmt/darwin-arm64@0.20.0':
1284
+
optional: true
1285
+
1286
+
'@oxfmt/darwin-x64@0.20.0':
1287
+
optional: true
1288
+
1289
+
'@oxfmt/linux-arm64-gnu@0.20.0':
1290
+
optional: true
1291
+
1292
+
'@oxfmt/linux-arm64-musl@0.20.0':
1293
+
optional: true
1294
+
1295
+
'@oxfmt/linux-x64-gnu@0.20.0':
1296
+
optional: true
1297
+
1298
+
'@oxfmt/linux-x64-musl@0.20.0':
1299
+
optional: true
1300
+
1301
+
'@oxfmt/win32-arm64@0.20.0':
1302
+
optional: true
1303
+
1304
+
'@oxfmt/win32-x64@0.20.0':
1305
+
optional: true
1306
+
1307
'@oxlint/darwin-arm64@1.35.0':
1308
optional: true
1309
···
1614
1615
kysely@0.28.9: {}
1616
1617
+
lefthook-darwin-arm64@2.0.13:
1618
+
optional: true
1619
+
1620
+
lefthook-darwin-x64@2.0.13:
1621
+
optional: true
1622
+
1623
+
lefthook-freebsd-arm64@2.0.13:
1624
+
optional: true
1625
+
1626
+
lefthook-freebsd-x64@2.0.13:
1627
+
optional: true
1628
+
1629
+
lefthook-linux-arm64@2.0.13:
1630
+
optional: true
1631
+
1632
+
lefthook-linux-x64@2.0.13:
1633
+
optional: true
1634
+
1635
+
lefthook-openbsd-arm64@2.0.13:
1636
+
optional: true
1637
+
1638
+
lefthook-openbsd-x64@2.0.13:
1639
+
optional: true
1640
+
1641
+
lefthook-windows-arm64@2.0.13:
1642
+
optional: true
1643
+
1644
+
lefthook-windows-x64@2.0.13:
1645
+
optional: true
1646
+
1647
+
lefthook@2.0.13:
1648
+
optionalDependencies:
1649
+
lefthook-darwin-arm64: 2.0.13
1650
+
lefthook-darwin-x64: 2.0.13
1651
+
lefthook-freebsd-arm64: 2.0.13
1652
+
lefthook-freebsd-x64: 2.0.13
1653
+
lefthook-linux-arm64: 2.0.13
1654
+
lefthook-linux-x64: 2.0.13
1655
+
lefthook-openbsd-arm64: 2.0.13
1656
+
lefthook-openbsd-x64: 2.0.13
1657
+
lefthook-windows-arm64: 2.0.13
1658
+
lefthook-windows-x64: 2.0.13
1659
+
1660
lines-and-columns@1.2.4: {}
1661
1662
lodash.snakecase@4.1.1: {}
···
1685
once@1.4.0:
1686
dependencies:
1687
wrappy: 1.0.2
1688
+
1689
+
oxfmt@0.20.0:
1690
+
dependencies:
1691
+
tinypool: 2.0.0
1692
+
optionalDependencies:
1693
+
'@oxfmt/darwin-arm64': 0.20.0
1694
+
'@oxfmt/darwin-x64': 0.20.0
1695
+
'@oxfmt/linux-arm64-gnu': 0.20.0
1696
+
'@oxfmt/linux-arm64-musl': 0.20.0
1697
+
'@oxfmt/linux-x64-gnu': 0.20.0
1698
+
'@oxfmt/linux-x64-musl': 0.20.0
1699
+
'@oxfmt/win32-arm64': 0.20.0
1700
+
'@oxfmt/win32-x64': 0.20.0
1701
1702
oxlint@1.35.0:
1703
optionalDependencies:
···
1921
thread-stream@3.1.0:
1922
dependencies:
1923
real-require: 0.2.0
1924
+
1925
+
tinypool@2.0.0: {}
1926
1927
to-regex-range@5.0.1:
1928
dependencies: