├── .nvmrc
├── .dockerignore
├── public
├── robots.txt
├── og-image.png
├── sitemap.xml
└── logo.svg
├── containers
├── index.js
├── submit-new-rom-form.js
└── devices-list.js
├── db
├── index.js
├── db.sqlite3
├── status_enum.js
├── db.js
└── options.js
├── jsconfig.json
├── lib
├── search-utils.js
├── analytical-utils.js
├── mods-to-style.js
├── date-utils.js
├── filter-devices.js
└── sdk.js
├── components
├── input.js
├── index.js
├── note.js
├── link.js
├── button.js
├── box.js
├── footer.js
├── header.js
├── head.js
└── icons.js
├── lefthook.yml
├── postcss.config.cjs
├── pages
├── _app.js
├── submit-rom.js
├── devices.js
└── index.js
├── scripts
├── generate-missing-releases.js
├── sync-search-index.js
├── sync-aospa.js
├── sync-lineage-os.js
├── sync-legionos.js
├── sync-potatorom.js
├── sync-aospextended.js
├── sync-pixys-os.js
├── sync-pixel-experience.js
├── sync-dotos.js
├── fill-release-dates.js
├── sync-coltos.js
├── sync.js
├── sync-havocos.js
├── import-from-devices.js
├── sync-crdroid.js
├── sync-arrowos.js
└── sync-manual-devices.js
├── knexfile.js
├── migrations
├── 20220723112117_add-search-index.js
└── 20220718185631_init.js
├── .gitignore
├── .github
└── workflows
│ ├── fly-deploy.yml
│ └── cron.yml
├── fly.toml
├── seeds
└── options.js
├── tailwind.config.js
├── Dockerfile
├── LICENSE
├── package.json
├── .husky
├── pre-commit
└── prepare-commit-msg
├── README.md
├── server.mjs
├── styles
└── styles.css
└── biome.json
/.nvmrc:
--------------------------------------------------------------------------------
1 | v20.17.0
2 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | /node_modules
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow:
--------------------------------------------------------------------------------
/containers/index.js:
--------------------------------------------------------------------------------
1 | export * from './devices-list'
2 |
--------------------------------------------------------------------------------
/db/index.js:
--------------------------------------------------------------------------------
1 | export * from './db'
2 | export * from './status_enum'
3 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "./"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/db/db.sqlite3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/barelyhuman/custom-rom-index/HEAD/db/db.sqlite3
--------------------------------------------------------------------------------
/public/og-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/barelyhuman/custom-rom-index/HEAD/public/og-image.png
--------------------------------------------------------------------------------
/db/status_enum.js:
--------------------------------------------------------------------------------
1 | export const STATUS_ENUM = {
2 | active: 'active',
3 | discontinued: 'discontinued',
4 | unknown: 'unknown',
5 | }
6 |
--------------------------------------------------------------------------------
/lib/search-utils.js:
--------------------------------------------------------------------------------
1 | export function normaliseSearchableString(str) {
2 | return str
3 | .trim()
4 | .toLowerCase()
5 | .replace(/[-_.()[\] ]/g, '')
6 | }
7 |
--------------------------------------------------------------------------------
/components/input.js:
--------------------------------------------------------------------------------
1 | import Box from './box'
2 |
3 | export function Input({ label, ...props }) {
4 | return
5 | }
6 |
--------------------------------------------------------------------------------
/components/index.js:
--------------------------------------------------------------------------------
1 | export * from './input'
2 | export * from './button'
3 | export * from './header'
4 | export * from './head'
5 | export * from './footer'
6 | export * from './icons'
7 | export * from './note'
8 |
--------------------------------------------------------------------------------
/lefthook.yml:
--------------------------------------------------------------------------------
1 | pre-commit:
2 | parallel: true
3 | commands:
4 | linter:
5 | files: git diff --name-only @{push}
6 | glob: "*.{js,ts,jsx,tsx}"
7 | run: npx biome check --write {staged_files}
8 |
--------------------------------------------------------------------------------
/components/note.js:
--------------------------------------------------------------------------------
1 | export function Note({ children, ...props }) {
2 | return (
3 | <>
4 |
5 | Note:
6 | {children}
7 |
8 | >
9 | )
10 | }
11 |
--------------------------------------------------------------------------------
/postcss.config.cjs:
--------------------------------------------------------------------------------
1 | // If you want to use other PostCSS plugins, see the following:
2 | // https://tailwindcss.com/docs/using-with-preprocessors
3 | module.exports = {
4 | plugins: {
5 | tailwindcss: {},
6 | autoprefixer: {},
7 | },
8 | }
9 |
--------------------------------------------------------------------------------
/components/link.js:
--------------------------------------------------------------------------------
1 | import Box from './box.js'
2 |
3 | export function Link({ children, primary, ...props }) {
4 | return (
5 | <>
6 |
7 | {children}
8 |
9 | >
10 | )
11 | }
12 |
--------------------------------------------------------------------------------
/components/button.js:
--------------------------------------------------------------------------------
1 | import Box from './box'
2 |
3 | export function Button({ children, primary, ...props }) {
4 | return (
5 | <>
6 |
7 | {children}
8 |
9 | >
10 | )
11 | }
12 |
--------------------------------------------------------------------------------
/pages/_app.js:
--------------------------------------------------------------------------------
1 | import 'styles/styles.css'
2 | import { Head } from 'components'
3 |
4 | function MyApp({ Component, pageProps }) {
5 | return (
6 |
7 |
9 |
10 |
11 |
12 |
13 |
14 | )
15 | }
16 |
17 | export default MyApp
18 |
--------------------------------------------------------------------------------
/db/db.js:
--------------------------------------------------------------------------------
1 | import knex from 'knex'
2 | import kconfig from '../knexfile.js'
3 |
4 | /**
5 | * @type { import("knex").Knex }
6 | */
7 | let connection
8 |
9 | const createConnection = () => {
10 | if (connection) return connection
11 |
12 | connection = knex({
13 | ...kconfig[process.env.NODE_ENV || 'development'],
14 | useNullAsDefault: true,
15 | })
16 |
17 | return connection
18 | }
19 |
20 | export const db = createConnection()
21 |
--------------------------------------------------------------------------------
/components/box.js:
--------------------------------------------------------------------------------
1 | import { h } from 'preact'
2 | import { modsToStyle } from '../lib/mods-to-style'
3 |
4 | const Box = function ({ elm = 'div', children, ...props }) {
5 | const { style, sanitizedProps } = modsToStyle(props, '') // pass dimension as an empty string so it used the actual numbers
6 |
7 | return h(
8 | elm,
9 | {
10 | style,
11 | ...sanitizedProps,
12 | },
13 | children
14 | )
15 | }
16 |
17 | export default Box
18 |
--------------------------------------------------------------------------------
/scripts/generate-missing-releases.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import { writeFileSync } from 'fs'
4 | import path from 'path'
5 | import devicesJSON from '../db/devices.json'
6 |
7 | const missing = {}
8 |
9 | devicesJSON.devices.forEach(item => {
10 | if (missing[item.codename]) return
11 |
12 | missing[item.codename] = ''
13 | })
14 |
15 | const filePath = path.join(__dirname, '../db/missing-releases.json')
16 | writeFileSync(filePath, JSON.stringify(missing, null, 2))
17 |
--------------------------------------------------------------------------------
/knexfile.js:
--------------------------------------------------------------------------------
1 | import path from 'node:path'
2 | // Update with your config settings.
3 |
4 | /**
5 | * @type { Object. }
6 | */
7 |
8 | const dbPath = path.resolve(path.join(process.cwd(), 'db', 'db.sqlite3'))
9 |
10 | export default {
11 | development: {
12 | client: 'sqlite3',
13 | connection: {
14 | filename: dbPath,
15 | },
16 | },
17 | production: {
18 | client: 'sqlite3',
19 | connection: {
20 | filename: dbPath,
21 | },
22 | },
23 | }
24 |
--------------------------------------------------------------------------------
/migrations/20220723112117_add-search-index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @param { import("knex").Knex } knex
3 | * @returns { Promise }
4 | */
5 | export const up = function (knex) {
6 | return knex.schema.raw(
7 | `CREATE VIRTUAL TABLE roms_search_index USING fts4(rom_mapping_id,keywords)`
8 | )
9 | }
10 |
11 | /**
12 | * @param { import("knex").Knex } knex
13 | * @returns { Promise }
14 | */
15 | export const down = function (knex) {
16 | return knex.schema.raw(`DROP TABLE IF EXISTS roms_search_index`)
17 | }
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env.local
29 | .env.development.local
30 | .env.test.local
31 | .env.production.local
32 |
33 | # vercel
34 | .vercel
35 |
--------------------------------------------------------------------------------
/.github/workflows/fly-deploy.yml:
--------------------------------------------------------------------------------
1 | name: Fly Deploy
2 | on:
3 | push:
4 | branches:
5 | - dev
6 | workflow_dispatch:
7 | schedule:
8 | - cron: "5 0 * * *"
9 |
10 |
11 | jobs:
12 | deploy:
13 | name: Deploy app
14 | runs-on: ubuntu-latest
15 | concurrency: deploy-group
16 | steps:
17 | - uses: actions/checkout@v4
18 | - uses: superfly/flyctl-actions/setup-flyctl@master
19 | - run: flyctl deploy --remote-only --ha=false
20 | env:
21 | FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
22 |
--------------------------------------------------------------------------------
/fly.toml:
--------------------------------------------------------------------------------
1 | # fly.toml app configuration file generated for custom-rom-index on 2025-01-02T16:17:50+05:30
2 | #
3 | # See https://fly.io/docs/reference/configuration/ for information about how to use this file.
4 | #
5 |
6 | app = 'custom-rom-index'
7 | primary_region = 'sin'
8 |
9 | [build]
10 |
11 | [http_service]
12 | internal_port = 3000
13 | force_https = true
14 | auto_stop_machines = 'stop'
15 | auto_start_machines = true
16 | min_machines_running = 0
17 | processes = ['app']
18 |
19 | [[vm]]
20 | memory = '256mb'
21 | cpu_kind = 'shared'
22 | cpus = 1
23 |
--------------------------------------------------------------------------------
/seeds/options.js:
--------------------------------------------------------------------------------
1 | const { options } = require('../db/options')
2 |
3 | /**
4 | * @param { import("knex").Knex } knex
5 | * @returns { Promise }
6 | */
7 | exports.seed = async function (knex) {
8 | // Deletes ALL existing entries
9 | await knex('options').del()
10 | const rows = []
11 | Object.entries(options).forEach(([identifier, optionDef]) => {
12 | Object.entries(optionDef).forEach(([_, optValue]) => {
13 | rows.push({
14 | identifier,
15 | value: optValue.value,
16 | sequence: optValue.sequence,
17 | label: optValue.label,
18 | })
19 | })
20 | })
21 |
22 | await knex('options').insert(rows)
23 | }
24 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | mode: 'jit',
3 | content: [
4 | './pages/**/*.{js,ts,jsx,tsx}',
5 | './components/**/*.{js,ts,jsx,tsx}',
6 | './containers/**/*.{js,ts,jsx,tsx}',
7 | ],
8 | darkMode: 'media', // or 'media' or 'class'
9 | theme: {
10 | extend: {
11 | colors: {
12 | base: 'var(--base)',
13 | surface: 'var(--surface)',
14 | overlay: 'var(--overlay)',
15 | dimmer: 'var(--dimmer)',
16 | dim: 'var(--dim)',
17 | text: 'var(--text)',
18 | success: 'var(--success)',
19 | error: 'var(--error)',
20 | warn: 'var(--warn)',
21 | },
22 | },
23 | },
24 | variants: {
25 | extend: {},
26 | },
27 | plugins: [],
28 | }
29 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:20-alpine AS base
2 | RUN apk update && apk upgrade && apk add --no-cache sqlite
3 |
4 | FROM base AS deps
5 | WORKDIR /app
6 | COPY package.json .
7 | COPY pnpm-lock.yaml .
8 | RUN corepack enable && \
9 | pnpm i --frozen-lockfile
10 |
11 | FROM base AS builder
12 | WORKDIR /app
13 | COPY --from=deps /app/node_modules ./node_modules
14 | COPY . .
15 | RUN corepack enable && \
16 | pnpm build
17 |
18 | FROM base AS dist
19 | WORKDIR /app
20 | COPY --from=builder /app/out ./out
21 | COPY --from=deps /app/node_modules ./node_modules
22 | COPY server.mjs .
23 | COPY package.json .
24 | COPY ./public ./public
25 | COPY ./db ./db
26 | COPY ./knexfile.js .
27 | RUN corepack enable
28 |
29 | EXPOSE 3000
30 |
31 | CMD ["pnpm","start"]
--------------------------------------------------------------------------------
/.github/workflows/cron.yml:
--------------------------------------------------------------------------------
1 | name: Cron
2 |
3 | on:
4 | push:
5 | branches:
6 | - dev
7 | schedule:
8 | - cron: "0 0 * * *"
9 |
10 | jobs:
11 | build:
12 | runs-on: ubuntu-latest
13 |
14 | permissions:
15 | contents: write
16 |
17 | steps:
18 | - uses: actions/checkout@v2
19 | - uses: pnpm/action-setup@v4
20 | with:
21 | version: 9.9.0
22 | - run: pnpm i --frozen-lockfile
23 | - run: export GH_TOKEN=${{ secrets.GITHUB_TOKEN }}; npm run sync; node scripts/fill-release-dates.js;
24 | - name: Commit changes
25 | uses: stefanzweifel/git-auto-commit-action@v5
26 | with:
27 | commit_message: ":robot: Sync Services!"
28 | branch: ${{ github.head_ref }}
29 |
--------------------------------------------------------------------------------
/db/options.js:
--------------------------------------------------------------------------------
1 | const options = {
2 | STATUS: {
3 | unknown: {
4 | value: 0,
5 | label: 'Unknown',
6 | sequence: 0,
7 | },
8 | active: {
9 | value: 1,
10 | label: 'Active',
11 | sequence: 1,
12 | },
13 | discontinued: {
14 | value: 2,
15 | label: 'Discontinued',
16 | sequence: 2,
17 | },
18 | },
19 | }
20 |
21 | const findInOptions = (identifier, value) => {
22 | let opt
23 | Object.entries(options).forEach(([k, v]) => {
24 | if (k !== identifier) return
25 | Object.entries(v).forEach(([k1, v1]) => {
26 | if (v1.value !== value) return
27 | opt = v[k1]
28 | })
29 | })
30 |
31 | if (!opt) return null
32 |
33 | return opt
34 | }
35 |
36 | export { findInOptions, options }
37 |
--------------------------------------------------------------------------------
/containers/submit-new-rom-form.js:
--------------------------------------------------------------------------------
1 | import { Button, Input } from 'components'
2 |
3 | export function SubmitNewRomForm() {
4 | return (
5 | <>
6 |
7 |
8 |
9 |
10 | Submit a new rom ?
11 |
12 |
13 |
14 | {/* TODO: Replace with spacer */}
15 |
16 |
17 |
Thank you for the contribution!
18 |
19 |
20 |
21 | >
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/components/footer.js:
--------------------------------------------------------------------------------
1 | import { GithubIcon, TwitterIcon } from 'components'
2 |
3 | export function Footer({ ...props }) {
4 | return (
5 |
30 | )
31 | }
32 |
--------------------------------------------------------------------------------
/public/sitemap.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 | https://cri.barelyhuman.dev/
12 | 2021-08-22T08:01:37+00:00
13 | 1.00
14 |
15 |
16 | https://cri.barelyhuman.dev/devices
17 | 2021-08-22T08:01:37+00:00
18 | 0.80
19 |
20 |
21 | https://cri.barelyhuman.dev/submit-rom
22 | 2021-08-22T08:01:37+00:00
23 | 0.80
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/scripts/sync-search-index.js:
--------------------------------------------------------------------------------
1 | import { fileURLToPath } from 'node:url'
2 | import { conch } from '@barelyreaper/conch'
3 | import { db } from '../db/db.js'
4 |
5 | async function main() {
6 | await db('roms_search_index').del()
7 | const romData = await db('roms_devices_mapping')
8 | .leftJoin('roms', 'roms_devices_mapping.rom_id', 'roms.id')
9 | .leftJoin('devices', 'roms_devices_mapping.device_id', 'devices.id')
10 | .select(
11 | 'roms_devices_mapping.*',
12 | 'roms.name as rom_name',
13 | 'devices.basename as device_name',
14 | 'devices.codename as device_codename'
15 | )
16 |
17 | await conch(
18 | romData,
19 | async row => {
20 | const execChain = await db('roms_search_index').insert({
21 | rom_mapping_id: row.id,
22 | keywords: [row.rom_name, row.device_name, row.device_codename].join(
23 | ', '
24 | ),
25 | })
26 | },
27 | { limit: 20 }
28 | )
29 | }
30 |
31 | export const syncSearchIndex = main
32 |
33 | if (fileURLToPath(import.meta.url) === process.argv[1])
34 | main().then(() => process.exit(0))
35 |
--------------------------------------------------------------------------------
/public/logo.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021-present Reaper (https://reaper.is)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/components/header.js:
--------------------------------------------------------------------------------
1 | export function Header() {
2 | return (
3 | <>
4 |
38 | >
39 | )
40 | }
41 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "type": "module",
4 | "scripts": {
5 | "start": "node server.mjs",
6 | "build": "node build.mjs",
7 | "fix": "biome check --write",
8 | "sync": "node scripts/sync.js"
9 | },
10 | "dependencies": {
11 | "@barelyreaper/conch": "^1.2.0",
12 | "@fastify/static": "^8.0.3",
13 | "fastify": "^5.3.2",
14 | "got": "^11.8.6",
15 | "kleur": "^4.1.5",
16 | "knex": "^2.5.1",
17 | "logcons": "^0.0.4",
18 | "luxon": "^3.5.0",
19 | "preact": "^10.25.3",
20 | "sqlite3": "^5.1.7",
21 | "tiny-glob": "^0.2.9",
22 | "yaml": "^1.10.2"
23 | },
24 | "devDependencies": {
25 | "@babel/generator": "^7.26.3",
26 | "@babel/parser": "^7.26.3",
27 | "@babel/traverse": "^7.26.4",
28 | "@barelyhuman/prettier-config": "^1.1.0",
29 | "@biomejs/biome": "^1.9.4",
30 | "@evilmartians/lefthook": "^0.8.0",
31 | "autoprefixer": "^10.4.20",
32 | "esbuild": "^0.24.0",
33 | "esbuild-plugin-node-externals": "^1.0.1",
34 | "esbuild-plugin-postcss": "^0.2.1",
35 | "esbuild-postcss": "^0.0.4",
36 | "postcss": "^8.4.49",
37 | "preact-render-to-string": "^6.5.12",
38 | "shx": "^0.3.4",
39 | "tailwindcss": "^3.4.17"
40 | },
41 | "packageManager": "pnpm@9.9.0"
42 | }
43 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | if [ "$LEFTHOOK" = "0" ]; then
4 | exit 0
5 | fi
6 |
7 | if [ -t 1 ] ; then
8 | exec < /dev/tty ; # <- enables interactive shell
9 | fi
10 |
11 | dir="$(git rev-parse --show-toplevel)"
12 | osArch=$(echo "$(uname)" | tr '[:upper:]' '[:lower:]')
13 | cpuArch=$(echo "$(uname -m)" | sed 's/aarch64/arm64/')
14 |
15 | call_lefthook()
16 | {
17 | if lefthook -h >/dev/null 2>&1
18 | then
19 | eval lefthook $@
20 | elif test -f "$dir/node_modules/@evilmartians/lefthook/bin/lefthook_${osArch}_${cpuArch}/lefthook"
21 | then
22 | eval "$dir/node_modules/@evilmartians/lefthook/bin/lefthook_${osArch}_${cpuArch}/lefthook $@"
23 | elif test -f "$dir/node_modules/@evilmartians/lefthook-installer/bin/lefthook_${osArch}_${cpuArch}/lefthook"
24 | then
25 | eval "$dir/node_modules/@evilmartians/lefthook-installer/bin/lefthook_${osArch}_${cpuArch}/lefthook $@"
26 | elif bundle exec lefthook -h >/dev/null 2>&1
27 | then
28 | bundle exec lefthook $@
29 | elif yarn lefthook -h >/dev/null 2>&1
30 | then
31 | yarn lefthook $@
32 | elif npx @evilmartians/lefthook -h >/dev/null 2>&1
33 | then
34 | npx @evilmartians/lefthook $@
35 | else
36 | echo "Can't find lefthook in PATH"
37 | fi
38 | }
39 |
40 |
41 |
42 | call_lefthook "run pre-commit $@"
43 |
--------------------------------------------------------------------------------
/scripts/sync-aospa.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import { fileURLToPath } from 'node:url'
4 | import got from 'got'
5 | import kluer from 'kleur'
6 | import { logcons } from 'logcons'
7 | import { STATUS_ENUM } from '../db/status_enum.js'
8 | import { upsertDevice } from '../lib/sdk'
9 |
10 | const success = kluer.green().bold
11 |
12 | const URL = 'https://api.aospa.co/devices'
13 |
14 | async function main() {
15 | const response = await got(URL)
16 | const deviceList = JSON.parse(response.body)
17 |
18 | const promises = deviceList.devices.map(async deviceItem => {
19 | await upsertDevice({
20 | deviceName: deviceItem.manufacturer + deviceItem.description,
21 | codename: deviceItem.name,
22 | rom: {
23 | name: 'Paranoid Android',
24 | status: STATUS_ENUM.unknown,
25 | links: [`https://aospa.co/downloads/${deviceItem.name}`],
26 | },
27 | })
28 | })
29 |
30 | await Promise.all(promises)
31 |
32 | console.log(
33 | success(`${logcons.tick()} Done, Syncing AOSPA - Paranoid Android...`)
34 | )
35 | }
36 |
37 | export const syncParanoidAndroid = main
38 |
39 | if (fileURLToPath(import.meta.url) === process.argv[1])
40 | main()
41 | .then(() => process.exit(0))
42 | .catch(err => {
43 | console.error(err)
44 | process.exit(1)
45 | })
46 |
--------------------------------------------------------------------------------
/lib/analytical-utils.js:
--------------------------------------------------------------------------------
1 | import { db } from 'db'
2 | import { options } from 'db/options'
3 |
4 | export async function totalDevices() {
5 | const groupedByCodename = await db('devices').count('id').first()
6 | return groupedByCodename['count(`id`)']
7 | }
8 |
9 | export async function totalRoms() {
10 | const allRoms = await db('roms_devices_mapping').count('id').first()
11 | return allRoms['count(`id`)']
12 | }
13 |
14 | export async function totalActiveRoms() {
15 | const activeRoms = await db('roms_devices_mapping')
16 | .where({
17 | status: options.STATUS.active.value,
18 | })
19 | .count('id')
20 | .first()
21 | return activeRoms['count(`id`)']
22 | }
23 |
24 | export function topDevicesInROMCount() {
25 | const groupedByCodename = devices.reduce((acc, deviceItem) => {
26 | ;(acc[deviceItem.codename] || (acc[deviceItem.codename] = [])).push(
27 | deviceItem
28 | )
29 | return acc
30 | }, {})
31 |
32 | const sortByHighestFirst = Object.keys(groupedByCodename).sort(
33 | (x, y) => groupedByCodename[y].length - groupedByCodename[x].length
34 | )
35 |
36 | return sortByHighestFirst.slice(0, 10).map(item => {
37 | return {
38 | name: groupedByCodename[item][0].deviceName,
39 | count: groupedByCodename[item].length,
40 | releasedOn: groupedByCodename[item][0].releasedOn,
41 | }
42 | })
43 | }
44 |
--------------------------------------------------------------------------------
/scripts/sync-lineage-os.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import { fileURLToPath } from 'node:url'
3 | import got from 'got'
4 | import kluer from 'kleur'
5 | import { logcons } from 'logcons'
6 | import { STATUS_ENUM } from '../db/status_enum.js'
7 | import { upsertDevice } from '../lib/sdk.js'
8 |
9 | const success = kluer.green().bold
10 |
11 | const URL =
12 | 'https://raw.githubusercontent.com/LineageOS/hudson/main/updater/devices.json'
13 |
14 | async function main() {
15 | const response = await got(URL)
16 |
17 | const promises = (JSON.parse(response.body) || []).map(async deviceItem => {
18 | const codename = deviceItem.model
19 | const deviceName = `${deviceItem.oem} ${deviceItem.name}`
20 |
21 | await upsertDevice({
22 | deviceName,
23 | codename,
24 | rom: {
25 | status: STATUS_ENUM.unknown,
26 | androidVersion: ['N/A'],
27 | links: [`https://download.lineageos.org/${codename}`],
28 | name: 'LineageOS',
29 | },
30 | })
31 | })
32 |
33 | await Promise.all(promises)
34 |
35 | console.log(success(`${logcons.tick()} Done, Syncing Lineage OS`))
36 | }
37 |
38 | export const syncLineageOS = main
39 |
40 | if (fileURLToPath(import.meta.url) === process.argv[1]) {
41 | main()
42 | .then(() => process.exit(0))
43 | .catch(err => {
44 | console.error(err)
45 | process.exit(1)
46 | })
47 | }
48 |
--------------------------------------------------------------------------------
/.husky/prepare-commit-msg:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | if [ "$LEFTHOOK" = "0" ]; then
4 | exit 0
5 | fi
6 |
7 | if [ -t 1 ] ; then
8 | exec < /dev/tty ; # <- enables interactive shell
9 | fi
10 |
11 | dir="$(git rev-parse --show-toplevel)"
12 | osArch=$(echo "$(uname)" | tr '[:upper:]' '[:lower:]')
13 | cpuArch=$(echo "$(uname -m)" | sed 's/aarch64/arm64/')
14 |
15 | call_lefthook()
16 | {
17 | if lefthook -h >/dev/null 2>&1
18 | then
19 | eval lefthook $@
20 | elif test -f "$dir/node_modules/@evilmartians/lefthook/bin/lefthook_${osArch}_${cpuArch}/lefthook"
21 | then
22 | eval "$dir/node_modules/@evilmartians/lefthook/bin/lefthook_${osArch}_${cpuArch}/lefthook $@"
23 | elif test -f "$dir/node_modules/@evilmartians/lefthook-installer/bin/lefthook_${osArch}_${cpuArch}/lefthook"
24 | then
25 | eval "$dir/node_modules/@evilmartians/lefthook-installer/bin/lefthook_${osArch}_${cpuArch}/lefthook $@"
26 | elif bundle exec lefthook -h >/dev/null 2>&1
27 | then
28 | bundle exec lefthook $@
29 | elif yarn lefthook -h >/dev/null 2>&1
30 | then
31 | yarn lefthook $@
32 | elif npx @evilmartians/lefthook -h >/dev/null 2>&1
33 | then
34 | npx @evilmartians/lefthook $@
35 | else
36 | echo "Can't find lefthook in PATH"
37 | fi
38 | }
39 |
40 | # lefthook_version: fe323e3692fa170cc6cd9236e2fb5d81
41 |
42 | call_lefthook "install"
43 |
44 | call_lefthook "run prepare-commit-msg $@"
45 |
--------------------------------------------------------------------------------
/scripts/sync-legionos.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import got from 'got'
3 | import kluer from 'kleur'
4 | import { logcons } from 'logcons'
5 | import { STATUS_ENUM } from '../db/status_enum.js'
6 | import { upsertDevice } from '../lib/sdk.js'
7 |
8 | import { fileURLToPath } from 'node:url'
9 |
10 | const success = kluer.green().bold
11 |
12 | const URL =
13 | 'https://raw.githubusercontent.com/legionos-devices/OTA/11/devices.json'
14 |
15 | async function main() {
16 | const response = await got(URL)
17 |
18 | const promises = (JSON.parse(response.body) || []).map(async deviceItem => {
19 | const codename = deviceItem.codename
20 | const deviceName = `${deviceItem.brand} ${deviceItem.name}`
21 |
22 | await upsertDevice({
23 | deviceName,
24 | codename,
25 | rom: {
26 | status: deviceItem.active ? STATUS_ENUM.active : STATUS_ENUM.unknown,
27 | androidVersion: ['11'],
28 | links: [deviceItem.xda_thread],
29 | name: 'LegionOS',
30 | },
31 | })
32 | })
33 |
34 | await Promise.all(promises)
35 |
36 | console.log(success(`${logcons.tick()} Done, Syncing Legion OS`))
37 | }
38 |
39 | export const syncLegionOS = main
40 |
41 | if (fileURLToPath(import.meta.url) === process.argv[1]) {
42 | main()
43 | .then(() => process.exit(0))
44 | .catch(err => {
45 | console.error(err)
46 | process.exit(1)
47 | })
48 | }
49 |
--------------------------------------------------------------------------------
/scripts/sync-potatorom.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import { fileURLToPath } from 'node:url'
3 | import got from 'got'
4 | import kluer from 'kleur'
5 | import { logcons } from 'logcons'
6 | import { STATUS_ENUM } from '../db/status_enum.js'
7 | import { upsertDevice } from '../lib/sdk.js'
8 |
9 | const success = kluer.green().bold
10 |
11 | const URL =
12 | 'https://raw.githubusercontent.com/PotatoProject/vendor_potato/92e407b1696ac962dbd3ef2acaff8dc560268010/devices.json'
13 |
14 | async function main() {
15 | const response = await got(URL)
16 | const deviceList = JSON.parse(response.body) || {}
17 | const promises = Object.keys(deviceList).map(async deviceCodeName => {
18 | const codename = deviceCodeName
19 | const device = deviceList[deviceCodeName]
20 |
21 | await upsertDevice({
22 | deviceName: '',
23 | codename,
24 | rom: {
25 | status: STATUS_ENUM.active,
26 | androidVersion: ['N/A'],
27 | links: [`${device.repo}/releases/latest`],
28 | name: 'Potato Open Source Project | POSP',
29 | },
30 | })
31 | })
32 |
33 | await Promise.all(promises)
34 |
35 | console.log(success(`${logcons.tick()} Done, Syncing Potato Project`))
36 | }
37 |
38 | export const syncPotatoProject = main
39 |
40 | if (fileURLToPath(import.meta.url) === process.argv[1]) {
41 | main()
42 | .then(() => process.exit(0))
43 | .catch(err => {
44 | console.error(err)
45 | process.exit(1)
46 | })
47 | }
48 |
--------------------------------------------------------------------------------
/pages/submit-rom.js:
--------------------------------------------------------------------------------
1 | import { Button, Header } from 'components'
2 |
3 | function SubmitRom({ ...props }) {
4 | return (
5 | <>
6 |
7 |
8 |
9 |
Want to add a new rom to the list?
10 |
11 | If you wish to add a rom to the list for whatever reason, follow the
12 | below steps
13 |
14 |
15 | -
16 | Visit
17 |
21 | {' '}
22 | https://github.com/barelyhuman/custom-rom-index
23 | {' '}
24 | or click on Submit ROM below
25 |
26 | - Fork the repository
27 | -
28 | Modify
scripts/sync-manual-devices.js to add your rom
29 | with the needed data
30 |
31 | - Raise a new Pull Request
32 | - Done!
33 |
34 |
35 | Your ROM will be added as soon as we're done
36 | reviewing the addition
37 |
38 |
45 |
46 |
47 | >
48 | )
49 | }
50 |
51 | export default SubmitRom
52 |
--------------------------------------------------------------------------------
/components/head.js:
--------------------------------------------------------------------------------
1 | export function Head({ ...props }) {
2 | return (
3 |
4 | Custom Rom Index | CRI
5 |
6 |
7 |
8 |
9 | {/* Primary Meta Tags */}
10 |
11 |
15 | {/* Open Graph / Facebook */}
16 |
17 |
18 |
19 |
23 |
27 | {/* Twitter */}
28 |
29 |
30 |
31 |
35 |
39 | {props.children}
40 |
41 | )
42 | }
43 |
--------------------------------------------------------------------------------
/scripts/sync-aospextended.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import { fileURLToPath } from 'node:url'
4 | import got from 'got'
5 | import kluer from 'kleur'
6 | import { logcons } from 'logcons'
7 | import { STATUS_ENUM } from '../db/status_enum.js'
8 | import { upsertDevice } from '../lib/sdk.js'
9 | const success = kluer.green().bold
10 |
11 | const URL =
12 | 'https://raw.githubusercontent.com/AospExtended/official_devices/main/devices.json'
13 |
14 | async function main() {
15 | const response = await got(URL)
16 |
17 | const promises = (JSON.parse(response.body) || []).map(async deviceItem => {
18 | const codename = deviceItem.codename
19 | const deviceName = `${deviceItem.brand} ${deviceItem.name}`
20 |
21 | const _internalPromises = (deviceItem.supported_versions || []).map(
22 | async device => {
23 | if (device.version_code !== 'q' && device.version_code !== 'r') return
24 |
25 | let version
26 | if (device.version_code === 'q') version = 10
27 |
28 | if (device.version_code === 'r') version = 11
29 |
30 | await upsertDevice({
31 | deviceName,
32 | codename,
33 | rom: {
34 | status: STATUS_ENUM.active,
35 | androidVersion: [version],
36 | links: [device.xda_thread],
37 | name: 'AospExtended',
38 | },
39 | })
40 | }
41 | )
42 |
43 | await Promise.all(_internalPromises)
44 | })
45 |
46 | await Promise.all(promises)
47 |
48 | console.log(success(`${logcons.tick()} Done, Syncing AOSPExtended`))
49 | }
50 |
51 | export const syncAospExtended = main
52 |
53 | if (fileURLToPath(import.meta.url) === process.argv[1])
54 | main()
55 | .then(() => process.exit(0))
56 | .catch(err => {
57 | console.error(err)
58 | process.exit(1)
59 | })
60 |
--------------------------------------------------------------------------------
/scripts/sync-pixys-os.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import { fileURLToPath } from 'node:url'
3 | import got from 'got'
4 | import kluer from 'kleur'
5 | import { logcons } from 'logcons'
6 | import { STATUS_ENUM } from '../db/status_enum.js'
7 | import { upsertDevice } from '../lib/sdk.js'
8 |
9 | const success = kluer.green().bold
10 |
11 | const URL =
12 | 'https://raw.githubusercontent.com/PixysOS/official_devices/master/devices.json'
13 |
14 | async function main() {
15 | const response = await got(URL)
16 |
17 | const promises = (JSON.parse(response.body) || []).map(async deviceItem => {
18 | const codename = deviceItem.codename
19 | const deviceName = `${deviceItem.brand} ${deviceItem.name}`
20 |
21 | const _internalPromises = (deviceItem.supported_bases || []).map(
22 | async versionDef => {
23 | const version =
24 | (versionDef.name === 'ten' && 10) ||
25 | (versionDef.name === 'eleven' && 11) ||
26 | (versionDef.name === 'twelve' && 12) ||
27 | (versionDef.name === 'thirteen' && 13) ||
28 | (versionDef.name === 'fourteen' && 14)
29 | await upsertDevice({
30 | deviceName,
31 | codename,
32 | rom: {
33 | status: STATUS_ENUM.active,
34 | androidVersion: [version],
35 | links: [versionDef.xda_thread],
36 | name: 'Pixys OS',
37 | },
38 | })
39 | }
40 | )
41 |
42 | await Promise.all(_internalPromises)
43 | })
44 |
45 | await Promise.all(promises)
46 |
47 | console.log(success(`${logcons.tick()} Done, Syncing PixysOS`))
48 | }
49 |
50 | export const syncPixysOS = main
51 |
52 | if (fileURLToPath(import.meta.url) === process.argv[1]) {
53 | main()
54 | .then(() => process.exit(0))
55 | .catch(err => {
56 | console.error(err)
57 | process.exit(1)
58 | })
59 | }
60 |
--------------------------------------------------------------------------------
/scripts/sync-pixel-experience.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import got from 'got'
3 | import kluer from 'kleur'
4 | import { logcons } from 'logcons'
5 | import { STATUS_ENUM } from '../db/status_enum.js'
6 | import { upsertDevice } from '../lib/sdk.js'
7 |
8 | import { fileURLToPath } from 'node:url'
9 |
10 | const success = kluer.green().bold
11 |
12 | const URL =
13 | 'https://raw.githubusercontent.com/PixelExperience/official_devices/master/devices.json'
14 |
15 | const ignoreVersionKeys = '_plus'
16 |
17 | async function main() {
18 | const response = await got(URL)
19 | ;(JSON.parse(response.body) || []).forEach(deviceItem => {
20 | const codename = deviceItem.codename
21 | const deviceName = `${deviceItem.brand} ${deviceItem.name}`
22 |
23 | deviceItem.supported_versions
24 | .filter(x => !x.version_code.includes(ignoreVersionKeys))
25 | .forEach(deviceVersionItem => {
26 | const versions = []
27 |
28 | if (deviceVersionItem.version_code === 'eleven') versions.push(11)
29 |
30 | if (deviceVersionItem.version_code === 'ten') versions.push(10)
31 |
32 | upsertDevice({
33 | deviceName,
34 | codename,
35 | rom: {
36 | status: deviceVersionItem.deprecated
37 | ? STATUS_ENUM.discontinued
38 | : STATUS_ENUM.active,
39 | androidVersion: versions,
40 | links: [`https://download.pixelexperience.org/${codename}`],
41 | name: 'Pixel Experience',
42 | },
43 | })
44 | })
45 | })
46 |
47 | console.log(success(`${logcons.tick()} Done, Syncing Pixel Experience`))
48 | }
49 |
50 | export const syncPixelExperience = main
51 |
52 | if (fileURLToPath(import.meta.url) === process.argv[1]) {
53 | main()
54 | .then(() => process.exit(0))
55 | .catch(err => {
56 | console.error(err)
57 | process.exit(1)
58 | })
59 | }
60 |
--------------------------------------------------------------------------------
/pages/devices.js:
--------------------------------------------------------------------------------
1 | import { Header } from 'components'
2 | import { DevicesListTable } from 'containers'
3 |
4 | import { getDevices } from '../lib/sdk.js'
5 |
6 | function Devices({
7 | deviceList,
8 | searchTerm,
9 | sort,
10 | status,
11 | limit,
12 | currPage,
13 | maxPage,
14 | }) {
15 | return (
16 | <>
17 |
18 |
26 | {
27 |
36 | }
37 | >
38 | )
39 | }
40 |
41 | export default Devices
42 |
43 | export async function getServerSideProps({ query }) {
44 | const defaultLimit = 15
45 | const order = {
46 | release: 'desc',
47 | }
48 | const limit = query.limit || defaultLimit
49 |
50 | switch (query.sort) {
51 | case 'releasedOn:asc': {
52 | order.release = 'asc'
53 | break
54 | }
55 | case 'releasedOn:desc': {
56 | order.release = 'desc'
57 | break
58 | }
59 | default: {
60 | order.release = 'desc'
61 | break
62 | }
63 | }
64 |
65 | const { deviceList, count } = await getDevices({
66 | page: query.page || 0,
67 | limit,
68 | status: query.status || 'all',
69 | searchTerm: query.q || '',
70 | order,
71 | })
72 |
73 | return {
74 | props: {
75 | deviceList,
76 | searchTerm: query.q || '',
77 | sort: query.sort || 'releasedOn:desc',
78 | status: query.status || 'all',
79 | limit,
80 | currPage: query.page || 0,
81 | maxPage: Math.floor(count / limit),
82 | },
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | CRI
3 |
4 |
5 |
6 | Custom Rom Index
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | As an enthusiast, I’ve wasted hours together to find a working ROM
16 | for my devices and also, sometimes I feel like it’s better to
17 | spend on devices that already have a great collection of roms.
18 | Which is why this website was created so you don’t have to waste
19 | time like I did.
20 |
21 | ## Link
22 |
23 | You can access all the devices at [https://cri.barelyhuman.dev](https://cri.barelyhuman.dev).
24 |
25 | ## Contribute
26 |
27 | If you wish to add more devices/ROMs to the list you can follow the steps [here](https://cri.barelyhuman.dev/submit-rom).
28 |
29 | ## Data Credits
30 |
31 | While most of them are being added manually right now, we are using a lot of data from the open source repositories listed here,
32 | they make it easier to handle syncing of data
33 |
34 | - [PixelExperience](https://github.com/PixelExperience)
35 | - [DotOS](https://github.com/DotOS)
36 | - [LineageOS](https://github.com/LineageOS)
37 | - [Potato Project](https://github.com/PotatoProject)
38 | - [Dot OS](https://github.com/DotOS)
39 | - [Havoc OS](https://github.com/Havoc-OS)
40 | - [Colt OS](https://github.com/ColtOS-Devices)
41 | - [AospExtended](https://github.com/AospExtended)
42 | - [crDroid](https://github.com/crdroidandroid/)
43 | - [ArrowOS](https://github.com/ArrowOS)
44 |
45 | ## Appreciations
46 |
47 | Every ROM developer that's out there doing amazing work for the community! THANKS!
48 |
--------------------------------------------------------------------------------
/scripts/sync-dotos.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import _got from 'got'
4 |
5 | import { fileURLToPath } from 'node:url'
6 | import { conch } from '@barelyreaper/conch'
7 | import kluer from 'kleur'
8 | import { logcons } from 'logcons'
9 | import { STATUS_ENUM } from '../db/status_enum.js'
10 | import { upsertDevice } from '../lib/sdk.js'
11 |
12 | const success = kluer.green().bold
13 |
14 | const URL =
15 | 'https://api.github.com/repos/DotOS/official_devices/contents/devices'
16 |
17 | async function main() {
18 | const { parse } = JSON
19 | const response = await got(URL)
20 | const devices = parse(response.body)
21 |
22 | await conch(devices, item => addDotOSToDevices(item), {
23 | limit: 1,
24 | })
25 |
26 | console.log(success(`${logcons.tick()} Done, Syncing DotOS`))
27 | }
28 |
29 | async function addDotOSToDevices(item) {
30 | const { parse } = JSON
31 | const deviceBlob = await got(item.url)
32 | const fileContent = Buffer.from(
33 | parse(deviceBlob.body).content,
34 | 'base64'
35 | ).toString('utf8')
36 |
37 | if (!fileContent) return true
38 |
39 | let parsedFileData
40 | try {
41 | parsedFileData = parse(fileContent)
42 | } catch (_) {
43 | parsedFileData = false
44 | }
45 |
46 | if (!parsedFileData) return true
47 |
48 | const deviceData = parsedFileData || false
49 |
50 | if (!deviceData) return true
51 |
52 | const codename = deviceData.codename
53 | const deviceName = `${deviceData.brandName} ${deviceData.deviceName}`
54 |
55 | await upsertDevice({
56 | deviceName,
57 | codename,
58 | rom: {
59 | status: STATUS_ENUM.active,
60 | androidVersion: ['11'],
61 | links: [deviceData.links.xda],
62 | name: 'Dot OS',
63 | },
64 | })
65 | }
66 |
67 | function got(url) {
68 | return _got(url, {
69 | headers: {
70 | Authorization: `token ${process.env.GH_TOKEN}`,
71 | },
72 | })
73 | }
74 |
75 | export const syncDotOS = main
76 |
77 | if (fileURLToPath(import.meta.url) === process.argv[1]) {
78 | main()
79 | .then(() => process.exit(0))
80 | .catch(err => {
81 | console.error(err)
82 | process.exit(1)
83 | })
84 | }
85 |
--------------------------------------------------------------------------------
/scripts/fill-release-dates.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import got from 'got'
4 | import kluer from 'kleur'
5 | import YAML from 'yaml'
6 | const info = kluer.cyan().bold
7 | import { fileURLToPath } from 'url'
8 | import { conch } from '@barelyreaper/conch'
9 | import { db } from '../db/db.js'
10 | import { getReleasedOn } from '../lib/date-utils.js'
11 |
12 | const URL_TEMPLATE = deviceCodeName =>
13 | `https://raw.githubusercontent.com/PixelExperience/wiki/master/_data/devices/${deviceCodeName}.yml`
14 |
15 | const URL_TEMPLATE_TWO = deviceCodeName =>
16 | `https://raw.githubusercontent.com/LineageOS/lineage_wiki/master/_data/devices/${deviceCodeName}.yml`
17 |
18 | function parseYAML(text) {
19 | try {
20 | return text && YAML.parse(text)
21 | } catch (err) {
22 | return null
23 | }
24 | }
25 |
26 | async function deviceInfoAPI(codename) {
27 | try {
28 | const response = await got(URL_TEMPLATE(codename)).catch(err => err)
29 | const responseTwo = await got(URL_TEMPLATE_TWO(codename)).catch(err => err)
30 | const text = response.body
31 | const textTwo = responseTwo.body
32 | const parsed = parseYAML(text)
33 | const parsedTwo = parseYAML(textTwo)
34 | return {
35 | dataSourceOne: parsed,
36 | dataSourceTwo: parsedTwo,
37 | }
38 | } catch (err) {
39 | console.log(`Failed: ${codename}`)
40 | }
41 | }
42 |
43 | async function main() {
44 | const devices = await db('devices').where({ released_on: null })
45 | const mapper = async device => {
46 | const { dataSourceOne, dataSourceTwo } = await deviceInfoAPI(
47 | device.codename
48 | )
49 | const releaseDate =
50 | (dataSourceOne && dataSourceOne.release) ||
51 | (dataSourceTwo && dataSourceTwo.release)
52 |
53 | if (!releaseDate) return
54 |
55 | const dateData = getReleasedOn(releaseDate)
56 |
57 | if (dateData) {
58 | const _date = dateData
59 | await db('devices')
60 | .update({
61 | released_on: _date,
62 | })
63 | .where({
64 | id: device.id,
65 | })
66 | }
67 | }
68 |
69 | await conch(devices, mapper, { limit: 20 })
70 | }
71 |
72 | if (fileURLToPath(import.meta.url) === process.argv[1])
73 | main().then(() => process.exit(0))
74 |
--------------------------------------------------------------------------------
/scripts/sync-coltos.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import { fileURLToPath } from 'node:url'
3 | import { conch } from '@barelyreaper/conch'
4 | import _got from 'got'
5 | import kluer from 'kleur'
6 | import { logcons } from 'logcons'
7 | import { STATUS_ENUM } from '../db/status_enum.js'
8 | import { upsertDevice } from '../lib/sdk.js'
9 |
10 | const info = kluer.cyan().bold
11 | const success = kluer.green().bold
12 |
13 | const URL =
14 | 'https://api.github.com/repos/ColtOS-Devices/official_devices/contents/builds'
15 |
16 | async function main() {
17 | await syncColtOSDevices()
18 | console.log(success(`${logcons.tick()} Done, Syncing ColtOS`))
19 | }
20 |
21 | async function syncColtOSDevices() {
22 | const { parse } = JSON
23 | const response = await got(URL)
24 | const devices = parse(response.body)
25 |
26 | await conch(devices, item => addColtOSToDevices(item), {
27 | limit: 1,
28 | })
29 | console.log(info(`${logcons.info()} Synced: Colt OS`))
30 | }
31 |
32 | async function addColtOSToDevices(item) {
33 | const { parse } = JSON
34 | const deviceBlob = await got(item.url)
35 | const fileContent = Buffer.from(
36 | parse(deviceBlob.body).content,
37 | 'base64'
38 | ).toString('utf8')
39 |
40 | if (!fileContent) return true
41 |
42 | let parsedFileData
43 | try {
44 | parsedFileData = parse(fileContent)
45 | } catch (_) {
46 | parsedFileData = false
47 | }
48 |
49 | if (!parsedFileData) return true
50 |
51 | const deviceData = parsedFileData || false
52 |
53 | if (!deviceData) return true
54 |
55 | const codename = item.name.replace('.json', '')
56 |
57 | await upsertDevice({
58 | deviceName: deviceData.devicename,
59 | codename,
60 | rom: {
61 | status: STATUS_ENUM.active,
62 | androidVersion: [parseInt(deviceData.version, 10)],
63 | links: [deviceData.url],
64 | name: 'ColtOS',
65 | },
66 | })
67 | }
68 |
69 | function got(url) {
70 | return _got(url, {
71 | headers: {
72 | Authorization: `token ${process.env.GH_TOKEN}`,
73 | },
74 | })
75 | }
76 |
77 | export const syncColtOS = main
78 |
79 | if (fileURLToPath(import.meta.url) === process.argv[1]) {
80 | main()
81 | .then(() => process.exit(0))
82 | .catch(err => {
83 | console.error(err)
84 | process.exit(1)
85 | })
86 | }
87 |
--------------------------------------------------------------------------------
/lib/mods-to-style.js:
--------------------------------------------------------------------------------
1 | const mgnPattern = /(margin)[TBRLXY]*-/
2 | const padPattern = /(padding)[TBRLXY]*-/
3 | const XPattern = /X$/
4 | const YPattern = /Y$/
5 |
6 | const toDims = (value, unit) => {
7 | if (isNaN(value)) return value
8 |
9 | if (unit) return value + unit
10 |
11 | return +value
12 | }
13 |
14 | const normalizeModifer = prop => {
15 | let x = prop
16 | return (
17 | (x.endsWith('T') && (x += 'op')) ||
18 | (x.endsWith('L') && (x += 'eft')) ||
19 | (x.endsWith('B') && (x += 'ottom')) ||
20 | (x.endsWith('R') && (x += 'ight')) ||
21 | prop
22 | )
23 | }
24 |
25 | /**
26 | * @name modsToStyle
27 | * @description convert given modifiers object of the form
28 | * {margin-10:true} into a style object => {margin:10} with an appended dimensionUnit (eg: "px")
29 | * @param {Object} mods Object of spacing modifiers
30 | * @param {string} dimUnit Dimension to append to the style value `(default: "px")
31 | * @returns {Object} result
32 | */
33 | export function modsToStyle(mods, dimUnit = 'px') {
34 | const style = {}
35 |
36 | // collection of props that do not have the above modifiers
37 | const sanitizedProps = { ...mods }
38 |
39 | for (const key of Object.keys(mods)) {
40 | if (!(mgnPattern.test(key) || padPattern.test(key))) continue
41 |
42 | // delete from the sanitize as it's a valid modifier
43 | delete sanitizedProps[key]
44 |
45 | const propSplits = key.split('-')
46 |
47 | // Change the accessor to it's style equivalent eg: marginR => marginRight
48 | const modifier = normalizeModifer(propSplits[0])
49 |
50 | propSplits[1] = propSplits[1] || 0
51 |
52 | // If not one of the above then check for accessor as X or Y , eg: marginX or marginY
53 | if (XPattern.test(modifier)) {
54 | const xProp = modifier.replace(XPattern, '')
55 | style[xProp + 'Left'] = toDims(propSplits[1], dimUnit)
56 | style[xProp + 'Right'] = toDims(propSplits[1], dimUnit)
57 | } else if (YPattern.test(modifier)) {
58 | const yProp = modifier.replace(YPattern, '')
59 | style[yProp + 'Top'] = toDims(propSplits[1], dimUnit)
60 | style[yProp + 'Bottom'] = toDims(propSplits[1], dimUnit)
61 | } else {
62 | style[modifier] = toDims(propSplits[1], dimUnit)
63 | }
64 | }
65 |
66 | return { style, sanitizedProps }
67 | }
68 |
--------------------------------------------------------------------------------
/server.mjs:
--------------------------------------------------------------------------------
1 | import path, { dirname } from 'path'
2 | import { fileURLToPath } from 'url'
3 | import fastifyStatic from '@fastify/static'
4 | import fastify from 'fastify'
5 | import { h } from 'preact'
6 | import { renderToString } from 'preact-render-to-string'
7 |
8 | const app = fastify({ logger: true })
9 |
10 | const __dirname = dirname(fileURLToPath(import.meta.url))
11 |
12 | app.register(fastifyStatic, {
13 | root: path.join(__dirname, 'out/client'),
14 | prefix: '/client',
15 | })
16 |
17 | app.register(fastifyStatic, {
18 | decorateReply: false,
19 | root: path.join(__dirname, 'public'),
20 | prefix: '/',
21 | })
22 |
23 | async function mapComponent(path, req, res) {
24 | const appFile = await import('./out/_app.js')
25 | const Component = await import(path)
26 | let pageProps = {
27 | props: {},
28 | }
29 | if ('getServerSideProps' in Component) {
30 | Object.assign(
31 | pageProps,
32 | await Component.getServerSideProps({
33 | request: req,
34 | response: res,
35 | query: req.query,
36 | })
37 | )
38 | }
39 | const mod = 'default' in appFile ? appFile.default : appFile
40 | const FinalComponent = () =>
41 | h(mod, {
42 | Component: Component.default,
43 | pageProps: pageProps.props,
44 | })
45 | const toString = renderToString(h(FinalComponent))
46 | return toString
47 | .replace(
48 | /<\/head\>/,
49 | ''
50 | )
51 | .replace(
52 | /<\/body\>/,
53 | `
57 |
60 |