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
|
- 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.
|
- 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
|
- Started using `pb_hooks` internally to replace some complex listener logic
|
||||||
|
- SSG frontend for better SEO and load times
|
||||||
|
|
||||||
## Change log
|
## 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: usage tracking to JS hooks
|
||||||
- enh: add docker-compose sample for better dx
|
- enh: add docker-compose sample for better dx
|
||||||
- enh: mothership backup script
|
- 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