move shared package to cli

This commit is contained in:
Niccolo Borgioli 2024-09-02 10:19:35 +02:00
parent 868b49c1c3
commit 7b919f2a53
16 changed files with 58 additions and 78 deletions

View File

@ -11,9 +11,6 @@
}, },
{ {
"path": "packages/cli" "path": "packages/cli"
},
{
"path": "packages/shared"
} }
], ],
"settings": { "settings": {

View File

@ -1,13 +1,14 @@
import pkg from './package.json' with { type: 'json' }
import { build } from 'tsup' import { build } from 'tsup'
import pkg from './package.json' with { type: 'json' }
const watch = process.argv.slice(2)[0] === '--watch' const watch = process.argv.slice(2)[0] === '--watch'
await build({ await build({
entry: ['src/index.ts', 'src/cli.ts'], entry: ['src/index.ts', 'src/cli.ts', 'src/shared/shared.ts'],
dts: true, dts: true,
minify: true, minify: true,
format: ['esm', 'cjs'], format: ['esm', 'cjs'],
target: 'es2020',
clean: true, clean: true,
define: { VERSION: `"${pkg.version}"` }, define: { VERSION: `"${pkg.version}"` },
watch, watch,

View File

@ -9,7 +9,11 @@
}, },
"type": "module", "type": "module",
"exports": { "exports": {
".": "./dist/index.js" ".": "./dist/index.js",
"./shared": {
"import": "./dist/shared/shared.js",
"types": "./dist/shared/shared.d.ts"
}
}, },
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",
"bin": { "bin": {
@ -25,15 +29,14 @@
"prepublishOnly": "run-s build" "prepublishOnly": "run-s build"
}, },
"devDependencies": { "devDependencies": {
"@commander-js/extra-typings": "^12.0.1", "@commander-js/extra-typings": "^12.1.0",
"@cryptgeon/shared": "workspace:*",
"@types/inquirer": "^9.0.7", "@types/inquirer": "^9.0.7",
"@types/mime": "^3.0.4", "@types/mime": "^4.0.0",
"@types/node": "^20.11.24", "@types/node": "^20.11.24",
"commander": "^12.0.0", "commander": "^12.1.0",
"inquirer": "^9.2.15", "inquirer": "^9.2.15",
"mime": "^4.0.1", "mime": "^4.0.1",
"occulto": "^2.0.3", "occulto": "^2.0.6",
"pretty-bytes": "^6.1.1", "pretty-bytes": "^6.1.1",
"tsup": "^8.2.4", "tsup": "^8.2.4",
"typescript": "^5.3.3" "typescript": "^5.3.3"

View File

@ -1,14 +1,15 @@
import { Adapters, get, info, setOptions } from '@cryptgeon/shared'
import inquirer from 'inquirer' import inquirer from 'inquirer'
import { access, constants, writeFile } from 'node:fs/promises' import { access, constants, writeFile } from 'node:fs/promises'
import { basename, resolve } from 'node:path' import { basename, resolve } from 'node:path'
import { AES, Hex } from 'occulto' import { AES, Hex } from 'occulto'
import pretty from 'pretty-bytes' import pretty from 'pretty-bytes'
import { Adapters } from '../shared/adapters.js'
import { API } from '../shared/api.js'
export async function download(url: URL, all: boolean, suggestedPassword?: string) { export async function download(url: URL, all: boolean, suggestedPassword?: string) {
setOptions({ server: url.origin }) API.setOptions({ server: url.origin })
const id = url.pathname.split('/')[2] const id = url.pathname.split('/')[2]
const preview = await info(id).catch(() => { const preview = await API.info(id).catch(() => {
throw new Error('Note does not exist or is expired') throw new Error('Note does not exist or is expired')
}) })
@ -33,7 +34,7 @@ export async function download(url: URL, all: boolean, suggestedPassword?: strin
} }
const key = derivation ? (await AES.derive(password, derivation))[0] : Hex.decode(password) const key = derivation ? (await AES.derive(password, derivation))[0] : Hex.decode(password)
const note = await get(id) const note = await API.get(id)
const couldNotDecrypt = new Error('Could not decrypt note. Probably an invalid password') const couldNotDecrypt = new Error('Could not decrypt note. Probably an invalid password')
switch (note.meta.type) { switch (note.meta.type) {

View File

@ -1,9 +1,10 @@
import { readFile, stat } from 'node:fs/promises' import { readFile, stat } from 'node:fs/promises'
import { basename } from 'node:path' import { basename } from 'node:path'
import { Adapters, create, getOptions, FileDTO, Note, NoteMeta } from '@cryptgeon/shared'
import mime from 'mime' import mime from 'mime'
import { AES, Hex } from 'occulto' import { AES, Hex } from 'occulto'
import { Adapters } from '../shared/adapters.js'
import { API, FileDTO, Note, NoteMeta } from '../shared/api.js'
export type UploadOptions = Pick<Note, 'views' | 'expiration'> & { password?: string } export type UploadOptions = Pick<Note, 'views' | 'expiration'> & { password?: string }
@ -38,8 +39,8 @@ export async function upload(input: string | string[], options: UploadOptions):
// Create the actual note and upload it. // Create the actual note and upload it.
const note: Note = { ...noteOptions, contents, meta: { type, derivation: derived?.[1] } } const note: Note = { ...noteOptions, contents, meta: { type, derivation: derived?.[1] } }
const result = await create(note) const result = await API.create(note)
let url = `${getOptions().server}/note/${result.id}` let url = `${API.getOptions().server}/note/${result.id}`
if (!derived) url += `#${Hex.encode(key)}` if (!derived) url += `#${Hex.encode(key)}`
return url return url
} }

View File

@ -1,14 +1,14 @@
#!/usr/bin/env node #!/usr/bin/env node
import { Argument, Option, program } from '@commander-js/extra-typings' import { Argument, Option, program } from '@commander-js/extra-typings'
import { setOptions, status } from '@cryptgeon/shared'
import prettyBytes from 'pretty-bytes' import prettyBytes from 'pretty-bytes'
import { download } from './download.js' import { download } from './actions/download.js'
import { parseFile, parseNumber } from './parsers.js' import { upload } from './actions/upload.js'
import { getStdin } from './stdin.js' import { API } from './shared/api.js'
import { upload } from './upload.js' import { parseFile, parseNumber } from './utils/parsers.js'
import { checkConstrains, exit } from './utils.js' import { getStdin } from './utils/stdin.js'
import { checkConstrains, exit } from './utils/utils.js'
const defaultServer = process.env['CRYPTGEON_SERVER'] || 'https://cryptgeon.org' const defaultServer = process.env['CRYPTGEON_SERVER'] || 'https://cryptgeon.org'
const server = new Option('-s --server <url>', 'the cryptgeon server to use').default(defaultServer) const server = new Option('-s --server <url>', 'the cryptgeon server to use').default(defaultServer)
@ -33,8 +33,8 @@ program
.description('show information about the server') .description('show information about the server')
.addOption(server) .addOption(server)
.action(async (options) => { .action(async (options) => {
setOptions({ server: options.server }) API.setOptions({ server: options.server })
const response = await status() const response = await API.status()
const formatted = { const formatted = {
...response, ...response,
max_size: prettyBytes(response.max_size), max_size: prettyBytes(response.max_size),
@ -54,7 +54,7 @@ send
.addOption(minutes) .addOption(minutes)
.addOption(password) .addOption(password)
.action(async (files, options) => { .action(async (files, options) => {
setOptions({ server: options.server }) API.setOptions({ server: options.server })
await checkConstrains(options) await checkConstrains(options)
options.password ||= await getStdin() options.password ||= await getStdin()
try { try {
@ -72,7 +72,7 @@ send
.addOption(minutes) .addOption(minutes)
.addOption(password) .addOption(password)
.action(async (text, options) => { .action(async (text, options) => {
setOptions({ server: options.server }) API.setOptions({ server: options.server })
await checkConstrains(options) await checkConstrains(options)
options.password ||= await getStdin() options.password ||= await getStdin()
try { try {

View File

@ -1,4 +1,4 @@
export * from '@cryptgeon/shared' export * from './actions/download.js'
export * from './download.js' export * from './actions/upload.js'
export * from './upload.js' export * from './shared/adapters.js'
export * from './utils.js' export * from './shared/api.js'

View File

@ -39,15 +39,15 @@ export let client: ClientOptions = {
server: '', server: '',
} }
export function setOptions(options: Partial<ClientOptions>) { function setOptions(options: Partial<ClientOptions>) {
client = { ...client, ...options } client = { ...client, ...options }
} }
export function getOptions(): ClientOptions { function getOptions(): ClientOptions {
return client return client
} }
export async function call(options: CallOptions) { async function call(options: CallOptions) {
const url = client.server + '/api/' + options.url const url = client.server + '/api/' + options.url
const response = await fetch(url, { const response = await fetch(url, {
method: options.method, method: options.method,
@ -65,7 +65,7 @@ export async function call(options: CallOptions) {
return response.json() return response.json()
} }
export async function create(note: Note) { async function create(note: Note) {
const { meta, ...rest } = note const { meta, ...rest } = note
const body: NoteCreate = { const body: NoteCreate = {
...rest, ...rest,
@ -79,7 +79,7 @@ export async function create(note: Note) {
return data as { id: string } return data as { id: string }
} }
export async function get(id: string): Promise<NotePublic> { async function get(id: string): Promise<NotePublic> {
const data = await call({ const data = await call({
url: `notes/${id}`, url: `notes/${id}`,
method: 'delete', method: 'delete',
@ -93,7 +93,7 @@ export async function get(id: string): Promise<NotePublic> {
return note return note
} }
export async function info(id: string): Promise<NoteInfo> { async function info(id: string): Promise<NoteInfo> {
const data = await call({ const data = await call({
url: `notes/${id}`, url: `notes/${id}`,
method: 'get', method: 'get',
@ -112,6 +112,7 @@ export type Status = {
max_views: number max_views: number
max_expiration: number max_expiration: number
allow_advanced: boolean allow_advanced: boolean
allow_files: boolean
theme_image: string theme_image: string
theme_text: string theme_text: string
theme_favicon: string theme_favicon: string
@ -119,10 +120,19 @@ export type Status = {
theme_new_note_notice: boolean theme_new_note_notice: boolean
} }
export async function status() { async function status() {
const data = await call({ const data = await call({
url: 'status/', url: 'status/',
method: 'get', method: 'get',
}) })
return data as Status return data as Status
} }
export const API = {
setOptions,
getOptions,
create,
get,
info,
status,
}

View File

@ -21,7 +21,7 @@ export function parseURL(value: string, _: URL): URL {
} }
export function parseNumber(value: string, _: number): number { export function parseNumber(value: string, _: number): number {
const n = parseInt(value, 10) const n = Number.parseInt(value, 10)
if (isNaN(n)) throw new InvalidOptionArgumentError('invalid number') if (Number.isNaN(n)) throw new InvalidOptionArgumentError('invalid number')
return n return n
} }

View File

@ -18,6 +18,7 @@ export function getStdin(timeout: number = 10): Promise<string> {
resolve('') resolve('')
}, timeout) }, timeout)
process.stdin.on('error', reject)
process.stdin.on('data', dataHandler) process.stdin.on('data', dataHandler)
process.stdin.on('end', endHandler) process.stdin.on('end', endHandler)
}) })

View File

@ -1,5 +1,5 @@
import { status } from '@cryptgeon/shared'
import { exit as exitNode } from 'node:process' import { exit as exitNode } from 'node:process'
import { API } from '../shared/api.js'
export function exit(message: string) { export function exit(message: string) {
console.error(message) console.error(message)
@ -11,7 +11,7 @@ export async function checkConstrains(constrains: { views?: number; minutes?: nu
if (views && minutes) exit('cannot set view and minutes constrains simultaneously') if (views && minutes) exit('cannot set view and minutes constrains simultaneously')
if (!views && !minutes) constrains.views = 1 if (!views && !minutes) constrains.views = 1
const response = await status() const response = await API.status()
if (views && views > response.max_views) if (views && views > response.max_views)
exit(`Only a maximum of ${response.max_views} views allowed. ${views} given.`) exit(`Only a maximum of ${response.max_views} views allowed. ${views} given.`)
if (minutes && minutes > response.max_expiration) if (minutes && minutes > response.max_expiration)

View File

@ -2,7 +2,7 @@
"compilerOptions": { "compilerOptions": {
"target": "es2022", "target": "es2022",
"module": "es2022", "module": "es2022",
"moduleResolution": "node", "moduleResolution": "Bundler",
"declaration": true, "declaration": true,
"emitDeclarationOnly": true, "emitDeclarationOnly": true,
"strict": true, "strict": true,

View File

@ -1,22 +0,0 @@
{
"private": true,
"name": "@cryptgeon/shared",
"type": "module",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"scripts": {
"dev": "tsc -w",
"build": "tsc"
},
"devDependencies": {
"typescript": "^5.3.3"
},
"dependencies": {
"occulto": "^2.0.3"
}
}

View File

@ -1,12 +0,0 @@
{
"compilerOptions": {
"incremental": true,
"composite": true,
"target": "es2022",
"module": "es2022",
"rootDir": "./src",
"moduleResolution": "node",
"outDir": "./dist",
"strict": true
}
}