Merge branch 'master' of github.com:benallfree/pockethost

This commit is contained in:
Ben Allfree 2023-11-05 10:33:41 +00:00
commit a3cc606061
9 changed files with 171 additions and 121 deletions

View File

@ -7,4 +7,5 @@ attic
build
*.njk
_site
forks
forks
src/mothership-app/pb_hooks/src/versions.pb.js

View File

@ -9,6 +9,7 @@
"check:types:pockethost": "tsc --noEmit --skipLibCheck",
"lint": "prettier -c \"./**/*.{ts,js,cjs,svelte,json}\"",
"lint:fix": "prettier -w \"./**/*.{ts,js,cjs,svelte,json}\"",
"download-versions": "tsx ./src/cli/download.ts",
"build": "concurrently 'pnpm:build:*'",
"build-pockethost": "concurrently 'pnpm:build:pockethost:*'",
"build-frontends": "concurrently 'pnpm:build:frontend:*'",

View File

@ -1,24 +1,21 @@
import { DEBUG, PH_BIN_CACHE } from '$constants'
import {
DEBUG,
DefaultSettingsService,
PH_BIN_CACHE,
SETTINGS,
} from '$constants'
import { PocketbaseReleaseDownloadService } from '$services'
import { LogLevelName, LoggerService } from '$shared'
// gen:import
const [major, minor, patch] = process.versions.node.split('.').map(Number)
const check = async () => {
DefaultSettingsService(SETTINGS)
LoggerService({
level: DEBUG() ? LogLevelName.Debug : LogLevelName.Info,
errorTrace: !DEBUG(),
})
if ((major || 0) < 18) {
throw new Error(`Node 18 or higher required.`)
}
LoggerService({
level: DEBUG() ? LogLevelName.Debug : LogLevelName.Info,
errorTrace: !DEBUG(),
})
// npm install eventsource --save
// @ts-ignore
global.EventSource = require('eventsource')
;(async () => {
const logger = LoggerService().create(`download.ts`)
const { dbg, error, info, warn } = logger
info(`Starting`)
@ -26,4 +23,6 @@ global.EventSource = require('eventsource')
cachePath: PH_BIN_CACHE(),
})
await check()
})()
}
check()

View File

@ -0,0 +1,14 @@
/// <reference path="../types/types.d.ts" />
/**
* Return a list of available PocketBase versions
*/
routerAdd(
'GET',
'/api/versions',
(c) => {
const { versions } = require(`${__hooks}/versions.pb.js`)
return c.json(200, { versions })
} /* optional middlewares */,
)

View File

@ -0,0 +1 @@
module.exports = {"versions":["0.19.*","0.19.2","0.19.1","0.19.0","0.18.*","0.18.10","0.18.9","0.18.8","0.18.7","0.18.6","0.18.5","0.18.4","0.18.3","0.18.2","0.18.1","0.18.0","0.17.*","0.17.7","0.17.6","0.17.5","0.17.4","0.17.3","0.17.2","0.17.1","0.17.0","0.16.*","0.16.10","0.16.9","0.16.8","0.16.7","0.16.6","0.16.5","0.16.4","0.16.3","0.16.2","0.16.1","0.16.0","0.15.*","0.15.3","0.15.2","0.15.1","0.15.0","0.14.*","0.14.5","0.14.4","0.14.3","0.14.2","0.14.1","0.14.0","0.13.*","0.13.4","0.13.3","0.13.2","0.13.1","0.13.0","0.12.*","0.12.3","0.12.2","0.12.1","0.12.0","0.11.*","0.11.4","0.11.3","0.11.2","0.11.1","0.11.0","0.10.*","0.10.4","0.10.3","0.10.2","0.10.1","0.10.0","0.9.*","0.9.2","0.9.1","0.9.0","0.8.*","0.8.0","0.7.*","0.7.10","0.7.9","0.7.8","0.7.7","0.7.6","0.7.5","0.7.4","0.7.3","0.7.2","0.7.1","0.7.0","0.6.*","0.6.0","0.5.*","0.5.2","0.5.1","0.5.0","0.4.*","0.4.2","0.4.1","0.4.0","0.3.*","0.3.4","0.3.3","0.3.2","0.3.1","0.3.0","0.2.*","0.2.8","0.2.7","0.2.6","0.2.5","0.2.4","0.2.3"]}

View File

@ -0,0 +1,46 @@
function compareSemVer(a: string, b: string): number {
// Consider wildcards as higher than any version number, hence represented by a large number for comparison
let splitA = a
.split('.')
.map((x) => (x === '*' ? Number.MAX_SAFE_INTEGER : parseInt(x)))
let splitB = b
.split('.')
.map((x) => (x === '*' ? Number.MAX_SAFE_INTEGER : parseInt(x)))
// Compare each part starting from major, minor, then patch
for (let i = 0; i < 3; i++) {
if (splitA[i] !== splitB[i]) {
return splitB[i]! - splitA[i]! // For descending order, compare b - a
}
}
// If all parts are equal or both have wildcards
return 0
}
export function expandAndSortSemVers(semvers: string[]): string[] {
let expandedVersions = new Set<string>() // Use a set to avoid duplicates
// Helper function to add wildcard versions
const addWildcards = (version: string) => {
const parts = version.split('.')
if (parts.length === 3) {
if (parts[0] !== '0') expandedVersions.add(`${parts[0]}.*.*`)
expandedVersions.add(`${parts[0]}.${parts[1]}.*`)
if (parts[0] === '0' && parts[1] !== '0')
expandedVersions.add(`0.${parts[1]}.*`)
}
}
// Add wildcards and original versions to the set
semvers.forEach((version) => {
expandedVersions.add(version)
addWildcards(version)
})
// Add the global wildcard for the latest version
// expandedVersions.add('*')
// Convert the set to an array and sort it using the custom semver comparison function
return Array.from(expandedVersions).sort(compareSemVer)
}

View File

@ -1,11 +1,12 @@
import { PH_BIN_CACHE } from '$constants'
import { LoggerService, mkSingleton, SingletonBaseConfig } from '$shared'
import { MOTHERSHIP_HOOKS_DIR, PH_BIN_CACHE } from '$constants'
import { LoggerService, SingletonBaseConfig, mkSingleton } from '$shared'
import { downloadAndExtract, mergeConfig, smartFetch } from '$util'
import { keys } from '@s-libs/micro-dash'
import Bottleneck from 'bottleneck'
import { chmodSync, existsSync } from 'fs'
import { chmodSync, existsSync, writeFileSync } from 'fs'
import { join } from 'path'
import { rsort } from 'semver'
import { expandAndSortSemVers } from './expandAndSortSemVers'
type Release = {
url: string
@ -100,6 +101,15 @@ export const PocketbaseReleaseDownloadService = mkSingleton(
}),
)
await Promise.all(promises)
console.log(`***keys`, keys(binPaths))
const sortedSemVers = expandAndSortSemVers(keys(binPaths))
writeFileSync(
join(MOTHERSHIP_HOOKS_DIR(), `versions.pb.js`),
`module.exports = ${JSON.stringify({ versions: sortedSemVers })}`,
)
console.log(JSON.stringify(sortedSemVers))
if (keys(binPaths).length === 0) {
throw new Error(
`No version found, probably mismatched architecture and OS (${osName}/${cpuArchitecture})`,

View File

@ -2,120 +2,100 @@ import { mkSingleton } from '$shared'
import { boolean as castToBoolean } from 'boolean'
import { existsSync, mkdirSync } from 'fs'
export type HandlerFactory<TValue> = (key: string) => {
export type Caster<TValue, TConfig = {}> = {
stringToType: (value: string, config?: Partial<TConfig>) => TValue
typeToString: (value: TValue, config?: Partial<TConfig>) => string
}
export type Handler<TValue> = {
get: () => TValue
set: (value: TValue) => void
}
export type HandlerFactory<TValue> = (key: string) => Handler<TValue>
export type Maker<TValue, TConfig = {}> = (
_default?: TValue,
config?: Partial<TConfig>,
) => HandlerFactory<TValue>
export const mkBoolean: Maker<boolean> = (_default) => (name: string) => {
return {
get() {
const v = process.env[name]
if (typeof v === `undefined`) {
if (typeof _default === `undefined`)
throw new Error(`${name} must be defined`)
return _default
}
return castToBoolean(v)
},
set(v) {
process.env[name] = `${v}`
},
}
}
export const mkNumber: Maker<number> = (_default) => (name: string) => {
return {
get() {
const v = process.env[name]
if (typeof v === `undefined`) {
if (typeof _default === `undefined`)
throw new Error(`${name} must be defined`)
return _default
}
return parseInt(v, 10)
},
set(v) {
process.env[name] = v.toString()
},
}
}
export const mkPath: Maker<string, { create: boolean; required: boolean }> =
(_default, options = {}) =>
const mkMaker =
<TValue, TConfig = {}>(
caster: Caster<TValue, TConfig>,
): Maker<TValue, TConfig> =>
(_default, config) =>
(name: string) => {
const { create = false, required = true } = options
return {
get() {
const v = (() => {
const v = process.env[name]
if (typeof v === `undefined`) {
if (typeof _default === `undefined`)
throw new Error(`${name} must be defined`)
return _default
}
return v
})()
if (create) {
mkdirSync(v, { recursive: true })
}
if (required && !existsSync(v)) {
throw new Error(`${name} (${v}) must exist.`)
}
return v
},
set(v) {
if (!existsSync(v)) {
throw new Error(`${name} (${v}) must exist.`)
}
process.env[name] = v
},
}
}
export const mkString: Maker<string> = (_default) => (name: string) => {
return {
get() {
const v = process.env[name]
if (typeof v === `undefined`) {
if (typeof _default === `undefined`)
throw new Error(`${name} must be defined`)
return _default
}
return v
},
set(v) {
process.env[name] = v
},
}
}
export const mkCsvString: Maker<string[]> = (_default) => (name: string) => {
return {
get() {
return (() => {
get(): TValue {
const v = process.env[name]
if (typeof v === `undefined`) {
if (typeof _default === `undefined`)
throw new Error(`${name} must be defined`)
return _default
this.set(_default)
return this.get()
}
return v
.split(/,/)
.map((s) => s.trim())
.filter((v) => !!v)
})()
},
set(v) {
process.env[name] = v.join(',')
},
try {
return caster.stringToType(v, config)
} catch (e) {
throw new Error(`${name}: ${e}`)
}
},
set(v: TValue) {
try {
process.env[name] = caster.typeToString(v, config)
} catch (e) {
throw new Error(`${name}: ${e}`)
}
},
}
}
}
export const mkBoolean = mkMaker<boolean>({
stringToType: (v) => castToBoolean(v),
typeToString: (v) => `${v}`,
})
export const mkNumber = mkMaker<number>({
stringToType: (s) => parseInt(s, 10),
typeToString: (v) => v.toString(),
})
export const mkPath = mkMaker<string, { create: boolean; required: boolean }>({
stringToType: (v, options) => {
const { create = false, required = true } = options || {}
if (create) {
mkdirSync(v, { recursive: true })
}
if (required && !existsSync(v)) {
throw new Error(`${v} must exist.`)
}
return v
},
typeToString: (v, options) => {
const { create = false, required = true } = options || {}
if (create) {
mkdirSync(v, { recursive: true })
}
if (required && !existsSync(v)) {
throw new Error(`${v} must exist.`)
}
return v
},
})
export const mkString = mkMaker<string>({
typeToString: (v) => v,
stringToType: (v) => v,
})
export const mkCsvString = mkMaker<string[]>({
typeToString: (v) => v.join(','),
stringToType: (s) =>
s
.split(/,/)
.map((s) => s.trim())
.filter((v) => !!v),
})
type Config<T> = {
[K in keyof T]: HandlerFactory<T[K]>
@ -132,6 +112,7 @@ export const SettingsService = <T extends Object>(config: Config<T>) => {
set: (value) => handler.set(value),
enumerable: true,
})
handler.get() // Initialize process.env
}
return lookup as T

View File

@ -24,9 +24,6 @@
"$shared": ["src/shared"]
}
},
"ts-node": {
"esm": true
},
"include": ["./src"],
"exclude": ["src/mothership-app"]
}