mirror of
https://github.com/pockethost/pockethost.git
synced 2025-07-06 04:42:29 +00:00
chore: remove old cli package
This commit is contained in:
parent
59158af307
commit
30ed96e589
@ -14,9 +14,22 @@ Highlights in this release:
|
||||
- Improved secrets - secrets are now passed to `pocketbase` executable and are available in JS hooks
|
||||
- Security - All `pocketbase` instances now run in Docker sandboxes in isolated environments. Reduces security risks and bad neighbor effects.
|
||||
- Started using `pb_hooks` internally to replace some complex listener logic
|
||||
- SSG frontend for better SEO and load times
|
||||
|
||||
## Change log
|
||||
|
||||
- chore: remove old cli package
|
||||
- enh: update sftp link
|
||||
- chore: add permalink to live publishing step
|
||||
- enh: Cloudflare Pages SSG publishing
|
||||
- enh: SSG
|
||||
- fix: sveltekit environment variables
|
||||
- enh: invocation indexes
|
||||
- chore: comment template environment variables
|
||||
- enh: run PocketBase in debugging mode when DEBUG=true
|
||||
- enh: gitignore update
|
||||
- fix: db migrations
|
||||
- fix: secondsThisMonth in users table
|
||||
- enh: usage tracking to JS hooks
|
||||
- enh: add docker-compose sample for better dx
|
||||
- enh: mothership backup script
|
||||
|
1
packages/cli/.gitignore
vendored
1
packages/cli/.gitignore
vendored
@ -1 +0,0 @@
|
||||
dist
|
@ -1,3 +0,0 @@
|
||||
src
|
||||
tsconfig.json
|
||||
dist/index.*
|
@ -1,58 +0,0 @@
|
||||
{
|
||||
"name": "pockethost",
|
||||
"version": "0.0.5",
|
||||
"main": "./dist/index.js",
|
||||
"bin": {
|
||||
"pbscript": "./dist/pbscript"
|
||||
},
|
||||
"author": {
|
||||
"name": "Ben Allfree",
|
||||
"url": "https://github.com/benallfree"
|
||||
},
|
||||
"repository": {
|
||||
"url": "https://github.com/benallfree/pockethost"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "chokidar package.json 'src/**' './node_modules/**' -c 'yarn build' --initial",
|
||||
"build": "parcel build --no-cache && yarn build:shebang && npm i -g --force .",
|
||||
"build:shebang": "echo \"#!/usr/bin/env node\"|cat - ./dist/index.js > ./dist/pbscript",
|
||||
"publish": "npm publish --access public"
|
||||
},
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"parcel": "^2.7.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/eventsource": "^1.1.9",
|
||||
"@types/node": "^18.8.2",
|
||||
"@types/prompts": "^2.4.1",
|
||||
"@types/tmp": "^0.2.3",
|
||||
"commander": "^9.4.0",
|
||||
"cross-fetch": "^3.1.5",
|
||||
"eventsource": "^2.0.2",
|
||||
"find-up": "^6.3.0",
|
||||
"pocketbase": "^0.8.0",
|
||||
"prompts": "^2.4.2",
|
||||
"tmp": "^0.2.1"
|
||||
},
|
||||
"targets": {
|
||||
"node": {
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
},
|
||||
"source": "./src/index.ts",
|
||||
"context": "node",
|
||||
"outputFormat": "commonjs",
|
||||
"includeNodeModules": [
|
||||
"@s-libs/micro-dash",
|
||||
"find-up",
|
||||
"locate-path",
|
||||
"path-exists",
|
||||
"p-locate",
|
||||
"p-limit",
|
||||
"yocto-queue",
|
||||
"pocketbase"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
# TS/JS Cloud Functions for PocketBase
|
||||
|
||||
[PBScript](https://github.com/benallfree/pbscript) allows you to write [PocketBase](https://pocketbase.io) server-side functions in Typescript or Javascript without recompiling.
|
||||
|
||||
This package is the CLI tool.
|
||||
|
||||
```bash
|
||||
npm i -g pbscript
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```bash
|
||||
npx pbscript --help
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```bash
|
||||
npm i -D pbscript
|
||||
```
|
||||
|
||||
And then reference from `package.json`.
|
||||
|
||||
See official docs at the [PBScript repo](https://github.com/benallfree/pbscript).
|
@ -1,90 +0,0 @@
|
||||
import { Parcel } from '@parcel/core'
|
||||
import { debounce } from '@s-libs/micro-dash'
|
||||
import chokidar from 'chokidar'
|
||||
import { Command } from 'commander'
|
||||
import { mkdirSync } from 'fs'
|
||||
import { join } from 'path'
|
||||
import { cwd } from 'process'
|
||||
import { getProjectRoot, readSettings } from '../util/project'
|
||||
|
||||
export type BuildConfig = {
|
||||
dist: string
|
||||
src: string
|
||||
}
|
||||
|
||||
export const addDevCommand = (program: Command) => {
|
||||
program
|
||||
.command('build')
|
||||
.description('Build the JS bundle')
|
||||
.option(
|
||||
'--src <path>',
|
||||
`Path to source (default: <project>/src/index.{ts|js})`,
|
||||
)
|
||||
.option('--dist <path>', `Path to dist (default: <project>/dist/index.js)`)
|
||||
.action(async (options) => {
|
||||
const defaultSrc = options.src
|
||||
const defaultDist = options.dist
|
||||
|
||||
const config: BuildConfig = {
|
||||
src: join(getProjectRoot(), './src/index.ts'),
|
||||
dist: join(getProjectRoot(), './dist/index.js'),
|
||||
...readSettings('build'),
|
||||
}
|
||||
if (defaultDist) config.dist = defaultDist
|
||||
if (defaultSrc) config.src = defaultSrc
|
||||
|
||||
const { src, dist } = config
|
||||
mkdirSync(dist, { recursive: true })
|
||||
console.log(cwd())
|
||||
|
||||
const bundler = new Parcel({
|
||||
entries: './src/index.ts',
|
||||
defaultConfig: '@parcel/config-default',
|
||||
mode: 'production',
|
||||
defaultTargetOptions: {
|
||||
distDir: join(cwd(), 'dist'),
|
||||
outputFormat: 'global',
|
||||
sourceMaps: false,
|
||||
},
|
||||
shouldDisableCache: true,
|
||||
// targets: {
|
||||
// iife: {
|
||||
// distDir: './dist',
|
||||
// source: './src/index.ts',
|
||||
// context: 'browser',
|
||||
// outputFormat: 'global',
|
||||
// sourceMap: {
|
||||
// inline: true,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
})
|
||||
|
||||
const build = debounce(() => {
|
||||
console.log(`Building...`)
|
||||
bundler
|
||||
.run()
|
||||
.then((e) => {
|
||||
console.log(`Build succeeded`, e)
|
||||
console.log(e.bundleGraph.getBundles({ includeInline: true }))
|
||||
let { bundleGraph } = e
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(`Build failed with ${e}`)
|
||||
})
|
||||
}, 100)
|
||||
|
||||
build()
|
||||
console.log(join(getProjectRoot(), 'src/**'))
|
||||
chokidar
|
||||
.watch([
|
||||
join(getProjectRoot(), 'src/**'),
|
||||
join(getProjectRoot(), 'node_modules/**'),
|
||||
])
|
||||
.on('all', (event, path) => {
|
||||
build()
|
||||
})
|
||||
|
||||
console.log('here')
|
||||
})
|
||||
}
|
@ -1,99 +0,0 @@
|
||||
import { DEFAULT_PB_DEV_URL } from '$constants'
|
||||
import { Parcel } from '@parcel/core'
|
||||
import { debounce } from '@s-libs/micro-dash'
|
||||
import chokidar from 'chokidar'
|
||||
import { Command } from 'commander'
|
||||
import { join } from 'path'
|
||||
import { cwd } from 'process'
|
||||
import tmp from 'tmp'
|
||||
import { SessionState } from '../providers/CustomAuthStore'
|
||||
import { ensureAdminClient } from '../util/ensureAdminClient'
|
||||
import { getProjectRoot, readSettings } from '../util/project'
|
||||
|
||||
export type DevConfig = {
|
||||
session: SessionState
|
||||
host: string
|
||||
src: string
|
||||
dist: string
|
||||
}
|
||||
export const addDevCommand = (program: Command) => {
|
||||
program
|
||||
.command('dev')
|
||||
.description('Watch for source code changes in development mode')
|
||||
.option(
|
||||
'--src <path>',
|
||||
`Path to source (default: <project>/src/index.{ts|js})`,
|
||||
)
|
||||
.option('--dist <path>', `Path to dist (default: <project>/dist/index.js)`)
|
||||
.option('--host', 'PocketBase host', DEFAULT_PB_DEV_URL)
|
||||
.action(async (options) => {
|
||||
const defaultSrc = options.src
|
||||
const defaultDist = options.dist
|
||||
const defaultHost = options.host
|
||||
|
||||
const config: DevConfig = {
|
||||
session: { token: '', model: null },
|
||||
host: DEFAULT_PB_DEV_URL,
|
||||
src: join(getProjectRoot(), './src/index.ts'),
|
||||
dist: join(getProjectRoot(), './dist/index.js'),
|
||||
...readSettings('dev'),
|
||||
}
|
||||
if (defaultDist) config.dist = defaultDist
|
||||
if (defaultSrc) config.src = defaultSrc
|
||||
|
||||
const client = await ensureAdminClient('dev', config)
|
||||
|
||||
const distDir = tmp.dirSync().name
|
||||
console.log(cwd())
|
||||
|
||||
const bundler = new Parcel({
|
||||
entries: './src/index.ts',
|
||||
defaultConfig: '@parcel/config-default',
|
||||
mode: 'production',
|
||||
defaultTargetOptions: {
|
||||
distDir: join(cwd(), 'dist'),
|
||||
outputFormat: 'global',
|
||||
sourceMaps: false,
|
||||
},
|
||||
shouldDisableCache: true,
|
||||
// targets: {
|
||||
// iife: {
|
||||
// distDir: './dist',
|
||||
// source: './src/index.ts',
|
||||
// context: 'browser',
|
||||
// outputFormat: 'global',
|
||||
// sourceMap: {
|
||||
// inline: true,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
})
|
||||
|
||||
const build = debounce(() => {
|
||||
console.log(`Building...`)
|
||||
bundler
|
||||
.run()
|
||||
.then((e) => {
|
||||
console.log(`Build succeeded`, e)
|
||||
console.log(e.bundleGraph.getBundles({ includeInline: true }))
|
||||
let { bundleGraph } = e
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(`Build failed with ${e}`)
|
||||
})
|
||||
}, 100)
|
||||
|
||||
build()
|
||||
console.log(join(getProjectRoot(), 'src/**'))
|
||||
chokidar
|
||||
.watch([
|
||||
join(getProjectRoot(), 'src/**'),
|
||||
join(getProjectRoot(), 'node_modules/**'),
|
||||
])
|
||||
.on('all', (event, path) => {
|
||||
build()
|
||||
})
|
||||
|
||||
console.log('here')
|
||||
})
|
||||
}
|
@ -1,100 +0,0 @@
|
||||
import { DEFAULT_PB_DEV_URL } from '$constants'
|
||||
import { Command } from 'commander'
|
||||
import { existsSync, readFileSync } from 'fs'
|
||||
import { join } from 'path'
|
||||
import pocketbaseEs from 'pocketbase'
|
||||
import { SessionState } from '../providers/CustomAuthStore'
|
||||
import { die } from '../util/die'
|
||||
import { ensureAdminClient } from '../util/ensureAdminClient'
|
||||
import { getProjectRoot, readSettings } from '../util/project'
|
||||
|
||||
export type PublishConfig = {
|
||||
session: SessionState
|
||||
host: string
|
||||
dist: string
|
||||
}
|
||||
|
||||
const migrate = async (client: pocketbaseEs) => {
|
||||
{
|
||||
// VERSION 1
|
||||
const res = await client.collections.getList(1, 1, {
|
||||
filter: `name='pbscript'`,
|
||||
})
|
||||
const [item] = res.items
|
||||
if (!item) {
|
||||
await client.collections.create({
|
||||
name: 'pbscript',
|
||||
schema: [
|
||||
{
|
||||
name: 'type',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'isActive',
|
||||
type: 'bool',
|
||||
},
|
||||
{
|
||||
name: 'data',
|
||||
type: 'json',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const publish = async (client: pocketbaseEs, fname: string) => {
|
||||
const js = readFileSync(fname).toString()
|
||||
const url = `${client.baseUrl}/api/pbscript/deploy`
|
||||
const res = await client
|
||||
.send(`api/pbscript/deploy`, {
|
||||
method: 'post',
|
||||
body: JSON.stringify({
|
||||
source: js,
|
||||
}),
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e)
|
||||
throw e
|
||||
})
|
||||
}
|
||||
|
||||
export const addPublishCommand = (program: Command) => {
|
||||
const _srcDefault = join(getProjectRoot(), './dist/index.js')
|
||||
const _hostDefault = DEFAULT_PB_DEV_URL
|
||||
program
|
||||
.command('publish')
|
||||
.description('Publish JS bundle to PBScript-enabled PocketBase instance')
|
||||
.option(
|
||||
'--dist <src>',
|
||||
`Path to dist bundle (default: <project>/dist/index.js)`,
|
||||
)
|
||||
.option('--host <host>', `PocketBase host (default: ${DEFAULT_PB_DEV_URL})`)
|
||||
.action(async (options) => {
|
||||
const defaultHost = options.host
|
||||
const defaultDist = options.dist
|
||||
|
||||
const config: PublishConfig = {
|
||||
session: { token: '', model: null },
|
||||
host: DEFAULT_PB_DEV_URL,
|
||||
dist: join(getProjectRoot(), './dist/index.js'),
|
||||
...readSettings<PublishConfig>('publish'),
|
||||
}
|
||||
if (defaultHost) config.host = defaultHost
|
||||
if (defaultDist) config.dist = defaultDist
|
||||
|
||||
const { host, dist } = config
|
||||
|
||||
if (!existsSync(dist)) {
|
||||
die(`${dist} does not exist. Nothing to publish.`)
|
||||
}
|
||||
|
||||
const client = await ensureAdminClient('publish', config)
|
||||
console.log(`Deploying from ${dist} to ${host}`)
|
||||
await migrate(client)
|
||||
await publish(client, dist)
|
||||
})
|
||||
}
|
@ -1 +0,0 @@
|
||||
export const DEFAULT_PB_DEV_URL = `http://127.0.0.1:8090`
|
@ -1,13 +0,0 @@
|
||||
import { map } from '@s-libs/micro-dash'
|
||||
import { Pb_Any_Record_Db, Pb_QueryParams } from '../schema/base'
|
||||
|
||||
export type FieldStruct<TRec extends Pb_Any_Record_Db> = Partial<{
|
||||
[_ in keyof TRec]: TRec[_]
|
||||
}>
|
||||
|
||||
export const buildQueryFilter = <TRec extends Pb_Any_Record_Db>(
|
||||
fields: FieldStruct<TRec>,
|
||||
): Pb_QueryParams => {
|
||||
const filter = map(fields, (v, k) => `${k.toString()} = "${v}"`).join(' and ')
|
||||
return { filter }
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
import { assertTruthy } from 'util/assert'
|
||||
import { client } from '../client'
|
||||
import {
|
||||
Pb_Any_Collection_Name,
|
||||
Pb_Any_Record_Db,
|
||||
Pb_Untrusted_Db,
|
||||
} from '../schema/base'
|
||||
import { FieldStruct, buildQueryFilter } from './buildQueryFilter'
|
||||
|
||||
export const getOne = async <
|
||||
TRec extends Pb_Any_Record_Db,
|
||||
TFields extends FieldStruct<TRec> = FieldStruct<TRec>,
|
||||
>(
|
||||
collectionName: Pb_Any_Collection_Name,
|
||||
fields: TFields,
|
||||
) => {
|
||||
const queryParams = buildQueryFilter(fields)
|
||||
const recs = await client.records.getList(collectionName, 1, 2, queryParams)
|
||||
assertTruthy(recs.totalItems < 2, `Expected exactly 0 or 1 items here`)
|
||||
const [item] = recs.items
|
||||
return item ? (recs.items[0] as unknown as Pb_Untrusted_Db<TRec>) : null
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
export * from './getOne'
|
||||
export * from './onAuthStateChanged'
|
||||
export * from './pbUid'
|
||||
export * from './signInAnonymously'
|
||||
export * from './upsert'
|
@ -1,27 +0,0 @@
|
||||
import { forEach, isObject } from '@s-libs/micro-dash'
|
||||
|
||||
export const mergeDeep = <TObject>(dst: any, src: TObject) => {
|
||||
forEach(src, (v, k) => {
|
||||
if (isObject(v)) {
|
||||
if (dst[k] === undefined) dst[k] = {}
|
||||
if (!isObject(dst[k])) {
|
||||
throw new Error(
|
||||
`${k.toString()} is an object in default, but not in target`,
|
||||
)
|
||||
}
|
||||
dst[k] = mergeDeep(dst[k], v)
|
||||
} else {
|
||||
if (isObject(dst[k])) {
|
||||
throw new Error(
|
||||
`${k.toString()} is an object in target, but not in default`,
|
||||
)
|
||||
}
|
||||
// The magic: if the target has no value for this field, use the
|
||||
// default value
|
||||
if (dst[k] === undefined) {
|
||||
dst[k] = v
|
||||
}
|
||||
}
|
||||
})
|
||||
return dst as TObject
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
import { UnsubFunc } from 'store/backend/types'
|
||||
import { client } from '../client'
|
||||
|
||||
export const onAuthStateChanged = (
|
||||
cb: (user: typeof client.authStore.model) => void,
|
||||
): UnsubFunc => {
|
||||
setTimeout(() => cb(client.authStore.model), 0)
|
||||
return client.authStore.onChange(() => {
|
||||
cb(client.authStore.model)
|
||||
})
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
import { identity } from 'ts-brand'
|
||||
import { client } from '../client'
|
||||
import { Pb_UserId } from '../schema/base'
|
||||
|
||||
export const pbUid = () => {
|
||||
const { id } = client.authStore.model || {}
|
||||
if (!id) return
|
||||
return identity<Pb_UserId>(id)
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
import { customAlphabet } from 'nanoid'
|
||||
import { identity } from 'ts-brand'
|
||||
import { client } from '../client'
|
||||
import { Email, Password, PbCreds } from '../schema/base'
|
||||
|
||||
export const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz')
|
||||
|
||||
export const signInAnonymously = () => {
|
||||
const { email, password } = (() => {
|
||||
const credsJson = localStorage.getItem('__pb_creds')
|
||||
if (credsJson) {
|
||||
return JSON.parse(credsJson) as PbCreds
|
||||
}
|
||||
const email = identity<Email>(`${nanoid()}@harvest.io`)
|
||||
const password = identity<Password>(nanoid())
|
||||
return { email, password }
|
||||
})()
|
||||
|
||||
return client.users.authViaEmail(email, password).catch((e) => {
|
||||
console.error(`Couldn't long in anonymously: ${e}`)
|
||||
})
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
import { reduce } from '@s-libs/micro-dash'
|
||||
import produce, { Draft, produceWithPatches } from 'immer'
|
||||
import { assertExists, assertTruthy } from 'util/assert'
|
||||
import { client } from '../client'
|
||||
import {
|
||||
Pb_Any_Collection_Name,
|
||||
Pb_Any_Record_Db,
|
||||
Pb_Untrusted_Db,
|
||||
Pb_UserFields,
|
||||
} from '../schema/base'
|
||||
import { FieldStruct, buildQueryFilter } from './buildQueryFilter'
|
||||
import { mergeDeep } from './mergeDeep'
|
||||
|
||||
export const upsert = async <TRow extends Pb_Any_Record_Db>(
|
||||
collectionName: Pb_Any_Collection_Name,
|
||||
filterFields: FieldStruct<TRow>,
|
||||
mutate: (draft: Draft<Pb_UserFields<TRow>>) => void,
|
||||
defaultRec: Pb_UserFields<TRow>,
|
||||
) => {
|
||||
const queryParams = buildQueryFilter(filterFields)
|
||||
const recs = await client.records.getList(collectionName, 1, 2, queryParams)
|
||||
assertTruthy(recs.totalItems < 2, `Expected exactly 0 or 1 item to upsert`)
|
||||
if (recs.totalItems === 0) {
|
||||
// Insert
|
||||
client.records.create(collectionName, produce(defaultRec, mutate))
|
||||
} else {
|
||||
// Update
|
||||
const [item] = recs.items as unknown as Pb_Untrusted_Db<TRow>[]
|
||||
assertExists(item, `Expected item here`)
|
||||
const { id } = item
|
||||
const safeItem = mergeDeep(item, defaultRec)
|
||||
const [rec, patches] = produceWithPatches(safeItem, mutate)
|
||||
console.log({ patches })
|
||||
const final = reduce(
|
||||
patches,
|
||||
(carry, patch) => {
|
||||
if (patch.op === 'add' || patch.op === 'replace') {
|
||||
const [key] = patch.path
|
||||
assertExists(key, `Expected key here`)
|
||||
const _key = `${key}` as keyof Pb_UserFields<TRow>
|
||||
carry[_key] = rec[_key]
|
||||
}
|
||||
return carry
|
||||
},
|
||||
{} as Partial<Pb_UserFields<TRow>>,
|
||||
)
|
||||
client.records.update(collectionName, id, final)
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
import { program } from 'commander'
|
||||
|
||||
declare global {
|
||||
let __go_app: any
|
||||
}
|
||||
|
||||
import 'cross-fetch/polyfill'
|
||||
import 'eventsource'
|
||||
import packagex from '../package.json'
|
||||
import { addPublishCommand } from './commands/publish'
|
||||
console.log(`PBScript ${packagex.version}`)
|
||||
|
||||
program
|
||||
.name('pbscript')
|
||||
.description('CLI for JavaScript extensions for PocketBase ')
|
||||
.version('0.0.1')
|
||||
addPublishCommand(program)
|
||||
// addDevCommand(program)
|
||||
|
||||
program.parse()
|
@ -1,57 +0,0 @@
|
||||
import { Admin, BaseAuthStore, User } from 'pocketbase'
|
||||
|
||||
export interface SerializeOptions {
|
||||
encode?: (val: string | number | boolean) => string
|
||||
maxAge?: number
|
||||
domain?: string
|
||||
path?: string
|
||||
expires?: Date
|
||||
httpOnly?: boolean
|
||||
secure?: boolean
|
||||
priority?: string
|
||||
sameSite?: boolean | string
|
||||
}
|
||||
|
||||
export type SessionState = { token: string; model: User | Admin | null }
|
||||
export type SessionStateSaver = (state: SessionState) => void
|
||||
|
||||
export class CustomAuthStore extends BaseAuthStore {
|
||||
_save: SessionStateSaver
|
||||
|
||||
constructor(state: SessionState, _save: SessionStateSaver) {
|
||||
super()
|
||||
const { token, model } = state
|
||||
this.baseToken = token
|
||||
this.baseModel = model
|
||||
this._save = _save
|
||||
}
|
||||
get get() {
|
||||
// console.log(`Get token`)
|
||||
return this.baseToken
|
||||
}
|
||||
get model(): User | Admin | null {
|
||||
// console.log(`get model`)
|
||||
return this.baseModel
|
||||
}
|
||||
get isValid(): boolean {
|
||||
// console.log(`isValid`)
|
||||
return !!this.baseToken
|
||||
}
|
||||
save(token: string, model: User | Admin | null) {
|
||||
this._save({ token, model })
|
||||
this.baseModel = model
|
||||
this.baseToken = token
|
||||
}
|
||||
clear(): void {
|
||||
throw new Error(`Unsupported clear()`)
|
||||
}
|
||||
loadFromCookie(cookie: string, key?: string | undefined): void {
|
||||
throw new Error(`Unsupported loadFromCookie()`)
|
||||
}
|
||||
exportToCookie(
|
||||
options?: SerializeOptions | undefined,
|
||||
key?: string | undefined,
|
||||
): string {
|
||||
throw new Error(`Unsupported exportToCookie()`)
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
export function assertExists<TType>(
|
||||
v: TType,
|
||||
message = `Value does not exist`,
|
||||
): asserts v is NonNullable<TType> {
|
||||
if (typeof v === 'undefined') {
|
||||
throw new Error(message)
|
||||
}
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
export function die(msg: string): never {
|
||||
console.error(msg)
|
||||
process.exit(1)
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
import prompts from 'prompts'
|
||||
import { SessionState } from '../providers/CustomAuthStore'
|
||||
import { die } from './die'
|
||||
import { isAdmin, pbClient } from './pbClient'
|
||||
import { mkProjectSaver } from './project'
|
||||
|
||||
function isEmail(email: string) {
|
||||
var emailFormat = /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/
|
||||
if (email !== '' && email.match(emailFormat)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
export type ConnectionConfig = {
|
||||
session: SessionState
|
||||
host: string
|
||||
}
|
||||
|
||||
export const ensureAdminClient = async (
|
||||
slug: string,
|
||||
config: ConnectionConfig,
|
||||
) => {
|
||||
const saver = mkProjectSaver<ConnectionConfig>(slug)
|
||||
const client = pbClient(config, (session) =>
|
||||
saver((config) => ({ ...config, session })),
|
||||
)
|
||||
const _isAdmin = await isAdmin(client)
|
||||
if (_isAdmin) {
|
||||
return client
|
||||
}
|
||||
|
||||
const { host } = config
|
||||
|
||||
console.log(
|
||||
`You must be logged in to ${host}/_ as a PocketBase admin to continue.`,
|
||||
)
|
||||
|
||||
while (true) {
|
||||
const response = await prompts(
|
||||
[
|
||||
{
|
||||
type: 'text',
|
||||
name: 'username',
|
||||
message: 'Username (email):',
|
||||
validate: (value: string) =>
|
||||
isEmail(value) ? true : `Enter an email address`,
|
||||
},
|
||||
{
|
||||
type: 'password',
|
||||
name: 'password',
|
||||
message: 'Password:',
|
||||
validate: (value: string) =>
|
||||
value.length > 0 ? true : `Enter a password`,
|
||||
},
|
||||
],
|
||||
{ onCancel: () => die(`Exited.`) },
|
||||
)
|
||||
const { username, password } = response
|
||||
try {
|
||||
await client.admins.authViaEmail(username, password)
|
||||
saver((config) => ({ ...config, host }))
|
||||
console.log(`Successfully logged in as ${username} for project `)
|
||||
break
|
||||
} catch (e) {
|
||||
console.error(`Login failed for ${username}. Try again.`)
|
||||
}
|
||||
}
|
||||
return client
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
import { default as PocketBase, default as pocketbaseEs } from 'pocketbase'
|
||||
import {
|
||||
CustomAuthStore,
|
||||
SessionStateSaver,
|
||||
} from '../providers/CustomAuthStore'
|
||||
import { assertExists } from './assert'
|
||||
import { die } from './die'
|
||||
import { ConnectionConfig } from './ensureAdminClient'
|
||||
|
||||
export const pbClient = (
|
||||
config: ConnectionConfig,
|
||||
saver: SessionStateSaver,
|
||||
) => {
|
||||
const { host, session } = config
|
||||
const client = new PocketBase(
|
||||
host,
|
||||
'en-US',
|
||||
new CustomAuthStore(session, saver),
|
||||
)
|
||||
return client
|
||||
}
|
||||
|
||||
export const isAdmin = async (client: pocketbaseEs) => {
|
||||
if (!client.authStore.isValid) return false
|
||||
const { model } = client.authStore
|
||||
if (!model) return false
|
||||
const res = await client.admins.getOne(model.id)
|
||||
if (!res) return false
|
||||
return true
|
||||
}
|
||||
|
||||
export const adminPbClient = async (
|
||||
config: ConnectionConfig,
|
||||
saver: SessionStateSaver,
|
||||
) => {
|
||||
const client = pbClient(config, saver)
|
||||
if (!client.authStore.isValid) {
|
||||
die(`Must be logged in to PocketBase as an admin.`)
|
||||
}
|
||||
const { model } = client.authStore
|
||||
assertExists(model, `Expected a valid model here`)
|
||||
const res = await client.admins.getOne(model.id)
|
||||
if (!res) {
|
||||
die(`User must be an admin user.`)
|
||||
}
|
||||
return client
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
import { findUpSync } from 'find-up'
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'
|
||||
import { dirname, join } from 'path'
|
||||
import { cwd } from 'process'
|
||||
import { SessionState } from '../providers/CustomAuthStore'
|
||||
|
||||
export type TConfigProjectConfig = {
|
||||
dev: {
|
||||
session: SessionState
|
||||
host: string
|
||||
}
|
||||
publish: {
|
||||
session: SessionState
|
||||
host: string
|
||||
}
|
||||
src: string
|
||||
dist: string
|
||||
}
|
||||
|
||||
export const CACHE_FNAME = '.pbcache'
|
||||
|
||||
export const mkProjectSaver = <TConfig>(slug: string) => {
|
||||
type ConfigMutator = (config: Partial<TConfig>) => Partial<TConfig>
|
||||
return (m: ConfigMutator) => {
|
||||
const root = getProjectRoot()
|
||||
const cachePath = join(root, CACHE_FNAME)
|
||||
if (!existsSync(cachePath)) {
|
||||
mkdirSync(cachePath, { recursive: true })
|
||||
}
|
||||
const currentConfig = readSettings<TConfig>(slug)
|
||||
const nextConfig = m(currentConfig)
|
||||
const fname = join(cachePath, slug)
|
||||
const json = JSON.stringify(nextConfig, null, 2)
|
||||
console.log(`Saving to ${fname}`, json)
|
||||
writeFileSync(`${fname}.json`, json)
|
||||
}
|
||||
}
|
||||
|
||||
export const getProjectRoot = () => {
|
||||
const root = findUpSync(`package.json`)
|
||||
if (!root) return cwd()
|
||||
return dirname(root)
|
||||
}
|
||||
|
||||
export const readSettings = <TConfig>(name: string): Partial<TConfig> => {
|
||||
const root = getProjectRoot()
|
||||
const fname = join(root, CACHE_FNAME, `${name}.json`)
|
||||
if (!existsSync(fname)) return {}
|
||||
const json = readFileSync(fname).toString()
|
||||
const settings = JSON.parse(json)
|
||||
return settings as Partial<TConfig>
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"module": "ESNext",
|
||||
"target": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"strictNullChecks": true
|
||||
},
|
||||
"include": ["./src"]
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user