more cleanup

This commit is contained in:
CJ_Clippy 2024-07-04 13:39:06 -08:00
parent ffe9ca2bb5
commit 3a89a076d9
50 changed files with 33 additions and 9886 deletions

View File

@ -4,14 +4,10 @@ pnpm for workspaces.
Kubernetes for Development using Tiltfile Kubernetes for Development using Tiltfile
kubefwd and entr for DNS in dev cluster
dokku for Production, deployed with `git push`.
(dokku is slowly being replaced by Kubernetes)
Kubernetes for Production, deployed using FluxCD Kubernetes for Production, deployed using FluxCD
Tested on VKE v1.30.0+1 (PVCs on other versions may not be fulfilled)
direnv for loading .envrc direnv for loading .envrc
Temporal for work queue Temporal for work queue
@ -21,6 +17,5 @@ Postgres for data storage
S3 for media storage S3 for media storage
Domain Driven Development Domain Driven Development
Test Driven Development
Tested on VKE v1.30.0+1 (PVCs on other versions may not be fulfilled) Test Driven Development

View File

@ -4,3 +4,23 @@ Source Code for https://futureporn.net
See ./ARCHITECTURE.md for overview See ./ARCHITECTURE.md for overview
## Development Mantras
### Move fast and break things
### Make it work, make it right, make it fast (in that order)
### Done is better than perfect
### If it looks like a duck and quacks like a duck, it is a duck.
### If the way is long, the way is wrong
### Good, Fast, Cheap. Pick only two.
### Organizations are fractals
### Focus on what moves the needle
### Alligator energy

View File

@ -1,5 +1,5 @@
# Futureporn node packages # Futureporn node packages
Each folder here is an individual node package Each folder here is an individual node package, each of which can reference each other.
See https://pnpm.io/workspaces See https://pnpm.io/workspaces

3
packages/bot/README.md Normal file
View File

@ -0,0 +1,3 @@
# bot
A.K.A. FutureButt, the discord bot that integrates into FP backend.

3
packages/infra/README.md Normal file
View File

@ -0,0 +1,3 @@
# infra
This module contains scripts that help with miscellaneous infrastructure tasks.

View File

@ -1,3 +0,0 @@
node_modules
.env
*~

View File

@ -1,3 +0,0 @@
PORT=3030
IPFS_URL=http://localhost:5001
API_KEY=changeme

View File

@ -1,144 +0,0 @@
# Created by https://www.toptal.com/developers/gitignore/api/node
# Edit at https://www.toptal.com/developers/gitignore?templates=node
### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
### Node Patch ###
# Serverless Webpack directories
.webpack/
# Optional stylelint cache
# SvelteKit build / generate output
.svelte-kit
# End of https://www.toptal.com/developers/gitignore/api/node

View File

@ -1,3 +0,0 @@
engine-strict=true
use-node-version=20.13.1
node-version=20.13.1

View File

@ -1,20 +0,0 @@
# Reference-- https://pnpm.io/docker
FROM node:20-alpine AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
WORKDIR /app
COPY ./package.json /app
EXPOSE 3939
FROM base AS dev
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install
CMD ["pnpm", "run", "dev"]
FROM base
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod
COPY ./index.js /app
ENTRYPOINT ["pnpm"]
CMD ["start"]

View File

@ -1,90 +0,0 @@
# link2cid
## Motivation
I wish I could give [kubo](https://github.com/ipfs/kubo) or [IPFS cluster](https://ipfscluster.io/) a URI to a file and then they would download the file and add to ipfs, returning me a [CID](https://docs.ipfs.tech/concepts/glossary/#cid).
However, neither kubo nor IPFS cluster can do this.
link2cid solves this issue with a REST API for adding a file at `url` to IPFS.
## Usage
Configure environment
Create a `.env` file. See `.env.example` for an example. Important environment variables are `API_KEY`, `PORT`, and `IPFS_URL`.
Install and run
```bash
pnpm install
pnpm start
```
Make a GET REST request to `/add` with `url` as a query parameter. Expect a [SSE](https://wikipedia.org/wiki/Server-sent_events) response.
## dokku
dokku builder-dockerfile:set link2cid dockerfile-path link2cid.Dockerfile
### Examples
#### [HTTPIE](https://httpie.io)
```bash
http -A bearer -a $API_KEY --stream 'http://localhost:3939/add?url=https://upload.wikimedia.org/wikipedia/commons/7/70/Example.png' Accept:text/event-stream
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Cache-Control: no-cache
Connection: keep-alive
Content-Type: text/event-stream; charset=utf-8
Date: Thu, 21 Dec 2023 11:20:24 GMT
Transfer-Encoding: identity
X-Powered-By: Express
:ok
event: dlProgress
data: {
"percent": 100
}
event: addProgress
data: {
"percent": 100
}
event: end
data: {
"cid": "bafkreidj3jo7efguloaixz6vgivljlmowagagjtqv4yanyqgty2hrvg6km"
}
```
#### Javascript
@todo this is incomplete/untested
```js
await fetch('http://localhost:3939/add?url=https://upload.wikimedia.org/wikipedia/commons/7/70/Example.png', {
headers: {
'accept': 'text/event-stream',
'authorization': `Bearer ${API_KEY}`
}
});
```
## Dev notes
### Generate API_KEY
```js
require('crypto').randomBytes(64).toString('hex')
```
### `TypeError: data.split is not a function`
If you see this error, make sure data in SSE event payload is a string, not a number.

View File

@ -1,14 +0,0 @@
{
"healthchecks": {
"web": [
{
"type": "startup",
"name": "web check",
"description": "Checking for expecting string at /health",
"path": "/health",
"content": "link2cid",
"attempts": 3
}
]
}
}

View File

@ -1,9 +0,0 @@
require('dotenv').config()
const app = require('./src/app.js')
const port = process.env.PORT || 3000
const version = require('./package.json').version
app.listen(port, () => {
console.log(`link2cid ${version} listening on port ${port}`)
})

View File

@ -1,34 +0,0 @@
{
"name": "@futureporn/link2cid",
"version": "4.3.0",
"description": "REST API for adding files via URL to IPFS",
"main": "index.js",
"scripts": {
"test": "mocha \"./src/**/*.spec.js\"",
"dev": "pnpm nodemon ./index.js",
"start": "node index.js"
},
"keywords": [
"IPFS",
"CID",
"HTTP",
"REST"
],
"author": "@CJ_Clippy",
"license": "Unlicense",
"dependencies": {
"@paralleldrive/cuid2": "^2.2.2",
"@types/express": "^4.17.21",
"better-queue": "^3.8.12",
"body-parser": "^1.20.2",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"express": "^4.18.2"
},
"devDependencies": {
"chai": "^5.1.0",
"mocha": "^10.4.0",
"nodemon": "^3.0.3",
"supertest": "^6.3.4"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,33 +0,0 @@
'use strict';
require('dotenv').config();
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
const fs = require('fs');
const fsp = require('fs/promises');
const { openAsBlob } = require('node:fs');
const { rm, stat } = require('fs/promises');
const os = require('os');
const path = require('path');
const { authenticate } = require('./middleware/auth.js')
const { createTask, readTask, deleteTask } = require('./models/task.js')
const readHeath = require('./models/health.js')
const store = require('./middleware/store.js');
const queue = require('./middleware/queue.js');
const app = express();
app.use(store);
app.use(queue);
app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.post('/task', authenticate, createTask)
app.get('/task', readTask)
app.delete('/task', authenticate, deleteTask)
app.get('/health', readHeath)
module.exports = app

View File

@ -1,109 +0,0 @@
const app = require('./app.js')
const request = require('supertest')
const qs = require('querystring')
require('dotenv').config()
describe('app', function () {
it('should exist', function (done) {
if (!app?.mountpath) throw new Error('app doesnt exist');
done()
})
describe('/health', function () {
it('should be publicly readable', function (done) {
request(app)
.get('/health')
.set('Accept', 'text/html')
.expect('Content-Type', /text/)
.expect(/piss/)
.expect(200, done)
})
})
describe('/task', function () {
describe('POST', function () {
it('should create a task', function (done) {
request(app)
.post('/task')
.set('Authorization', `Bearer ${process.env.API_KEY}`)
.set('Accept', 'application/json')
.send({
url: 'https://futureporn-b2.b-cdn.net/projekt-melody.jpg'
})
.expect('Content-Type', /json/)
.expect((res) => {
if (!res.body?.data) throw new Error('response body was missing data')
if (!res.body?.data?.id) throw new Error('response body was missing id')
return true
})
.expect(200, done)
})
})
describe('GET', function () {
it('should show all tasks specifications', async function () {
await request(app).post('/task').set('Authorization', `Bearer ${process.env.API_KEY}`).send({ url: 'https://example.com/my.jpg' })
await request(app).post('/task').set('Authorization', `Bearer ${process.env.API_KEY}`).send({ url: 'https://example.com/your.png' })
return request(app)
.get(`/task`)
.set('Authorization', `Bearer ${process.env.API_KEY}`)
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect((res) => {
if (!res?.body?.data) throw new Error('there was no data in response')
})
.expect(200)
})
it('should accept task id as query param and return task specification', function (done) {
const seed = request(app).post('/task').set('Authorization', `Bearer ${process.env.API_KEY}`).send({ url: 'https://example.com/z.jpg' })
seed.then((res) => {
const query = qs.stringify({
id: res.body.data.id
})
request(app)
.get(`/task?${query}`)
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect((res) => {
if (res?.body?.error) throw new Error('there was an error in the response: '+res.body?.message)
if (!res?.body?.data?.url) throw new Error('data.url was missing')
if (!res?.body?.data?.createdAt) throw new Error('data.createdAt was missing')
return true
})
.expect(200, done)
})
})
it('should show all tasks by default', function (done) {
request(app)
.get('/task')
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect((res) => {
if (res.body?.error) throw new Error('there was an error in the response'+res.error)
if (!res.body?.data) throw new Error('data was missing')
return true
})
.expect(200, done)
})
})
describe('DELETE', function () {
const query = qs.stringify({
id: 'awejf9wiejf9we'
})
it('should delete a single task', function (done) {
request(app)
.delete(`/task?${query}`)
.set('Authorization', `Bearer ${process.env.API_KEY}`)
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect(200, done);
});
})
})
})

View File

@ -1 +0,0 @@
hello worlds

View File

@ -1,16 +0,0 @@
module.exports.authenticate = function authenticate(req, res, next) {
const bearerToken = req.headers?.authorization.split(' ').at(1);
if (!bearerToken) {
const msg = `authorization bearer token was missing from request headers`;
console.error(msg);
return res.status(401).json({ error: true, message: msg });
}
if (bearerToken !== process.env.API_KEY) {
const msg = 'INCORRECT API_KEY (wrong token)';
console.error(msg);
return res.status(403).json({ error: true, message: msg });
} else {
next();
}
}

View File

@ -1,21 +0,0 @@
const Queue = require('better-queue');
const taskProcess = require('../utils/taskProcess.js');
const options = {
id: 'id',
maxRetries: 3,
concurrent: 1
// @todo better-queue has batching and concurrency. might be useful to implement in the future
// @see https://github.com/diamondio/better-queue?tab=readme-ov-file#queue-management
}
let q = new Queue(taskProcess, options)
// Middleware function to attach db to request
const queueMiddleware = (req, res, next) => {
req.queue = q;
next();
};
module.exports = queueMiddleware

View File

@ -1,10 +0,0 @@
const store = {
tasks: {}
}
const storeMiddleware = (req, res, next) => {
req.store = store
next();
};
module.exports = storeMiddleware;

View File

@ -1,3 +0,0 @@
module.exports = function readHealth (req, res) {
return res.send('**link2cid pisses on the floor**')
}

View File

@ -1,59 +0,0 @@
const { createId } = require('@paralleldrive/cuid2');
const { getTmpFilePath } = require('../utils/paths.js');
const fsp = require('fs/promises');
module.exports.createTask = function createTask (req, res) {
const url = req.body.url
const task = {
id: createId(),
url: url,
filePath: getTmpFilePath(url),
fileSize: null,
createdAt: new Date().toISOString(),
cid: null,
downloadProgress: null,
addProgress: null
}
if (!req?.body?.url) return res.status(400).json({ error: true, message: 'request body was missing a url' });
req.store.tasks[task.id] = task;
req.queue.push(task, function (err, result) {
if (err) throw err;
console.log('the following is the result of the queued task being complete')
console.log(result)
})
return res.json({ error: false, data: task })
}
module.exports.readTask = function readTask (req, res) {
const id = req?.query?.id
// If we get an id in the query, show the one task.
// Otherwise, we show all tasks.
if (!!id) {
const task = req.store.tasks[id]
if (!task) return res.json({ error: true, message: 'there was no task in the store with that id' });
return res.json({ error: false, data: task })
} else {
const tasks = req.store.tasks
return res.json({ error: false, data: tasks })
}
}
module.exports.deleteTask = async function deleteTask (req, res) {
const id = req?.query?.id;
const task = req.store.tasks[id];
try {
if (task?.filePath) await fsp.unlink(task.filePath);
} catch (err) {}
delete req.store.tasks[id];
return res.json({ error: false, message: 'task deleted' });
if (err) return res.json({ error: true, message: err });
}

View File

@ -1,20 +0,0 @@
const fs = require("fs");
const { Readable } = require('stream');
const { finished } = require('stream/promises');
const path = require("path");
/**
* Download a file at url to local disk filePath
* @param {String} url
* @param {String} filePath
*
* greetz https://stackoverflow.com/a/51302466/1004931
*/
const download = (async (url, filePath) => {
const res = await fetch(url);
const fileStream = fs.createWriteStream(filePath, { flags: 'wx' });
await finished(Readable.fromWeb(res.body).pipe(fileStream));
});
module.exports = download;

View File

@ -1,12 +0,0 @@
const download = require('./download.js');
const fsp = require('fs/promises');
describe('download', function () {
it('should download a file from url', async function () {
const testFilePath = '/tmp/pmel.jpg'
try {
await fsp.unlink(testFilePath)
} catch (e) {}
await download('https://futureporn-b2.b-cdn.net/projekt-melody.jpg', testFilePath)
})
})

View File

@ -1,100 +0,0 @@
require('dotenv').config();
const { openAsBlob } = require('node:fs');
const { rm, stat } = require('fs/promises');
const path = require('path');
if (!process.env.IPFS_URL) throw new Error('IPFS_URL was missing in env');
async function streamingPostFetch(
url,
formData,
basename,
filesize
) {
// console.log(`streamingPostFetch with url=${url}, formData=${formData.get('file')}, basename=${basename}, filesize=${filesize}`);
try {
const res = await fetch(url, {
method: 'POST',
body: formData
});
if (!res.ok) {
throw new Error(`HTTP error! Status-- ${res.status}`);
}
const reader = res.body?.getReader();
if (!reader) {
throw new Error('Failed to get reader from response body');
}
while (true) {
const { done, value } = await reader.read();
const chunk = new TextDecoder().decode(value);
const lines = chunk.split('\n');
for (const line of lines) {
const trimmedLine = line.trim()
if (!!trimmedLine) {
// console.log(trimmedLine);
const json = JSON.parse(trimmedLine);
// console.log(`comparing json.Name=${json.Name} with basename=${basename}`);
if (json.Name === basename && json.Hash && json.Size) {
// this is the last chunk
return json;
}
}
}
if (done) {
throw new Error('Response reader finished before receiving a CID which indicates a failiure.');
}
}
} catch (error) {
console.error('An error occurred:', error);
throw error;
}
}
async function getFormStuff(filePath) {
const url = `${process.env.IPFS_URL}/api/v0/add?progress=false&cid-version=1&pin=true`;
const blob = await openAsBlob(filePath);
const basename = path.basename(filePath);
const filesize = (await stat(filePath)).size;
const formData = new FormData();
return {
url,
blob,
basename,
filesize,
formData
}
}
/**
* @param {String} filePath
* @returns {String} CID
*/
const ipfsAdd = async function (filePath) {
const { url: kuboUrl, blob, basename, filesize, formData } = await getFormStuff(filePath);
formData.append('file', blob, basename);
const output = await streamingPostFetch(kuboUrl, formData, basename, filesize);
if (!output?.Hash) throw new Error('No CID was received from remote IPFS node.');
const cid = output.Hash;
return cid
}
module.exports = ipfsAdd;

View File

@ -1,13 +0,0 @@
const ipfsAdd = require('./ipfsAdd.js')
const path = require('path');
describe('ipfs', function () {
describe('ipfsAdd', function () {
it('should add a file from disk to ipfs and return a {string} CID', async function () {
const expectedCid = 'bafkreibxh3ly47pr3emvrqtax6ieq2ybom4ywyil3yurxnlwirtcvb5pfi'
const file = path.join(__dirname, '..', 'fixtures', 'hello-worlds.txt')
const cid = await ipfsAdd(file, { cidVersion: 1 })
if (cid !== expectedCid) throw new Error(`expected ${cid} to match ${expectedCid}`)
})
})
})

View File

@ -1,12 +0,0 @@
const os = require('os');
const path = require('path');
const getTmpFilePath = function (url) {
const timestamp = new Date().valueOf()
return path.join(os.tmpdir(), timestamp+'-'+path.basename(url))
}
module.exports = {
getTmpFilePath
}

View File

@ -1,11 +0,0 @@
const paths = require('./paths.js')
describe('paths', function () {
describe('getTmpFilePath', function () {
it('should accept a url and receive a /tmp/<datestamp><basename> path on disk', function () {
const url = 'https://example.com/my.jpg'
const p = paths.getTmpFilePath(url)
if (!/\/tmp\/\d+-my\.jpg/.test(p)) throw new Error(`expected ${p} to use format /tmp/<datestamp><basename>`)
})
})
})

View File

@ -1,25 +0,0 @@
const os = require('os');
const path = require('path');
const downloadFile = require('./download');
const ipfsAdd = require('./ipfsAdd');
const taskProcess = async function (taskSpec, cb) {
console.log('downloading')
this.progressTask(1, 3, "downloading")
await downloadFile(taskSpec.url, taskSpec.filePath)
console.log('adding')
this.progressTask(2, 3, "adding")
const cid = await ipfsAdd(taskSpec.filePath)
taskSpec.cid = cid
cb(null, taskSpec)
}
module.exports = taskProcess;

View File

@ -1,44 +0,0 @@
const http = require('node:http');
const faye = require('faye');
const url = require('url');
const pkg = require('./package.json')
require('dotenv').config()
var server = http.createServer(),
bayeux = new faye.NodeAdapter({ mount: '/faye' });
bayeux.on('subscribe', function (clientId, channel) {
console.log(`bayeux client ${clientId} subscribed to ${channel}`)
})
const client = bayeux.getClient()
if (process.env.NODE_ENV === 'development') {
function publish(msg) {
client.publish('/signals', {
text: msg
})
}
setInterval(() => {
publish('Hello mocha!')
}, 1000);
}
server.on('request', (req, res) => {
const parsedUrl = url.parse(req.url, true);
if (parsedUrl.pathname === '/metrics') {
// Handle the /metrics route
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end(`version=${pkg.version}`);
}
});
bayeux.attach(server);
const port = process.env.PORT || 5000;
console.log(`listening on ${port}`);
server.listen(port);

View File

@ -1,21 +0,0 @@
{
"name": "futureporn-realtime",
"version": "1.1.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
},
"keywords": [],
"author": "",
"license": "Unlicense",
"dependencies": {
"dotenv": "^16.4.5",
"faye": "^1.4.0"
},
"devDependencies": {
"chai": "^4.4.1",
"mocha": "^10.4.0"
}
}

View File

@ -1,754 +0,0 @@
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
dependencies:
dotenv:
specifier: ^16.4.5
version: 16.4.5
faye:
specifier: ^1.4.0
version: 1.4.0
devDependencies:
chai:
specifier: ^4.4.1
version: 4.4.1
mocha:
specifier: ^10.4.0
version: 10.4.0
packages:
ansi-colors@4.1.1:
resolution: {integrity: sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==}
engines: {node: '>=6'}
ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
ansi-styles@4.3.0:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
anymatch@3.1.3:
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
engines: {node: '>= 8'}
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
asap@2.0.6:
resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==}
assertion-error@1.1.0:
resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
binary-extensions@2.3.0:
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
engines: {node: '>=8'}
brace-expansion@2.0.1:
resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
braces@3.0.2:
resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
engines: {node: '>=8'}
browser-stdout@1.3.1:
resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==}
camelcase@6.3.0:
resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
engines: {node: '>=10'}
chai@4.4.1:
resolution: {integrity: sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==}
engines: {node: '>=4'}
chalk@4.1.2:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
check-error@1.0.3:
resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==}
chokidar@3.5.3:
resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
engines: {node: '>= 8.10.0'}
cliui@7.0.4:
resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==}
color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
csprng@0.1.2:
resolution: {integrity: sha512-D3WAbvvgUVIqSxUfdvLeGjuotsB32bvfVPd+AaaTWMtyUeC9zgCnw5xs94no89yFLVsafvY9dMZEhTwsY/ZecA==}
engines: {node: '>=0.6.0'}
debug@4.3.4:
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
decamelize@4.0.0:
resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==}
engines: {node: '>=10'}
deep-eql@4.1.3:
resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==}
engines: {node: '>=6'}
diff@5.0.0:
resolution: {integrity: sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==}
engines: {node: '>=0.3.1'}
dotenv@16.4.5:
resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==}
engines: {node: '>=12'}
emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
escalade@3.1.2:
resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==}
engines: {node: '>=6'}
escape-string-regexp@4.0.0:
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
engines: {node: '>=10'}
faye-websocket@0.11.4:
resolution: {integrity: sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==}
engines: {node: '>=0.8.0'}
faye@1.4.0:
resolution: {integrity: sha512-kRrIg4be8VNYhycS2PY//hpBJSzZPr/DBbcy9VWelhZMW3KhyLkQR0HL0k0MNpmVoNFF4EdfMFkNAWjTP65g6w==}
engines: {node: '>=0.8.0'}
fill-range@7.0.1:
resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
engines: {node: '>=8'}
find-up@5.0.0:
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
engines: {node: '>=10'}
flat@5.0.2:
resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==}
hasBin: true
fs.realpath@1.0.0:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
get-caller-file@2.0.5:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*}
get-func-name@2.0.2:
resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==}
glob-parent@5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
glob@8.1.0:
resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==}
engines: {node: '>=12'}
has-flag@4.0.0:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
he@1.2.0:
resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
hasBin: true
http-parser-js@0.5.8:
resolution: {integrity: sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==}
inflight@1.0.6:
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
is-binary-path@2.1.0:
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
engines: {node: '>=8'}
is-extglob@2.1.1:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
is-fullwidth-code-point@3.0.0:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
engines: {node: '>=8'}
is-glob@4.0.3:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
is-number@7.0.0:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'}
is-plain-obj@2.1.0:
resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==}
engines: {node: '>=8'}
is-unicode-supported@0.1.0:
resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==}
engines: {node: '>=10'}
js-yaml@4.1.0:
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
hasBin: true
locate-path@6.0.0:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'}
log-symbols@4.1.0:
resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==}
engines: {node: '>=10'}
loupe@2.3.7:
resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==}
minimatch@5.0.1:
resolution: {integrity: sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==}
engines: {node: '>=10'}
mocha@10.4.0:
resolution: {integrity: sha512-eqhGB8JKapEYcC4ytX/xrzKforgEc3j1pGlAXVy3eRwrtAy5/nIfT1SvgGzfN0XZZxeLq0aQWkOUAmqIJiv+bA==}
engines: {node: '>= 14.0.0'}
hasBin: true
ms@2.1.2:
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
normalize-path@3.0.0:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'}
once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
p-limit@3.1.0:
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
engines: {node: '>=10'}
p-locate@5.0.0:
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
engines: {node: '>=10'}
path-exists@4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'}
pathval@1.1.1:
resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
picomatch@2.3.1:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'}
psl@1.9.0:
resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==}
punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
querystringify@2.2.0:
resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==}
randombytes@2.1.0:
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
readdirp@3.6.0:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'}
require-directory@2.1.1:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'}
requires-port@1.0.0:
resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
sequin@0.1.1:
resolution: {integrity: sha512-hJWMZRwP75ocoBM+1/YaCsvS0j5MTPeBHJkS2/wruehl9xwtX30HlDF1Gt6UZ8HHHY8SJa2/IL+jo+JJCd59rA==}
engines: {node: '>=0.4.0'}
serialize-javascript@6.0.0:
resolution: {integrity: sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==}
string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
strip-ansi@6.0.1:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
strip-json-comments@3.1.1:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
supports-color@7.2.0:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
engines: {node: '>=8'}
supports-color@8.1.1:
resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==}
engines: {node: '>=10'}
to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
tough-cookie@4.1.4:
resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==}
engines: {node: '>=6'}
tunnel-agent@0.6.0:
resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==}
type-detect@4.0.8:
resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==}
engines: {node: '>=4'}
universalify@0.2.0:
resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==}
engines: {node: '>= 4.0.0'}
url-parse@1.5.10:
resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==}
websocket-driver@0.7.4:
resolution: {integrity: sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==}
engines: {node: '>=0.8.0'}
websocket-extensions@0.1.4:
resolution: {integrity: sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==}
engines: {node: '>=0.8.0'}
workerpool@6.2.1:
resolution: {integrity: sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==}
wrap-ansi@7.0.0:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'}
wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'}
yargs-parser@20.2.4:
resolution: {integrity: sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==}
engines: {node: '>=10'}
yargs-unparser@2.0.0:
resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==}
engines: {node: '>=10'}
yargs@16.2.0:
resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==}
engines: {node: '>=10'}
yocto-queue@0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
snapshots:
ansi-colors@4.1.1: {}
ansi-regex@5.0.1: {}
ansi-styles@4.3.0:
dependencies:
color-convert: 2.0.1
anymatch@3.1.3:
dependencies:
normalize-path: 3.0.0
picomatch: 2.3.1
argparse@2.0.1: {}
asap@2.0.6: {}
assertion-error@1.1.0: {}
balanced-match@1.0.2: {}
binary-extensions@2.3.0: {}
brace-expansion@2.0.1:
dependencies:
balanced-match: 1.0.2
braces@3.0.2:
dependencies:
fill-range: 7.0.1
browser-stdout@1.3.1: {}
camelcase@6.3.0: {}
chai@4.4.1:
dependencies:
assertion-error: 1.1.0
check-error: 1.0.3
deep-eql: 4.1.3
get-func-name: 2.0.2
loupe: 2.3.7
pathval: 1.1.1
type-detect: 4.0.8
chalk@4.1.2:
dependencies:
ansi-styles: 4.3.0
supports-color: 7.2.0
check-error@1.0.3:
dependencies:
get-func-name: 2.0.2
chokidar@3.5.3:
dependencies:
anymatch: 3.1.3
braces: 3.0.2
glob-parent: 5.1.2
is-binary-path: 2.1.0
is-glob: 4.0.3
normalize-path: 3.0.0
readdirp: 3.6.0
optionalDependencies:
fsevents: 2.3.3
cliui@7.0.4:
dependencies:
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi: 7.0.0
color-convert@2.0.1:
dependencies:
color-name: 1.1.4
color-name@1.1.4: {}
csprng@0.1.2:
dependencies:
sequin: 0.1.1
debug@4.3.4(supports-color@8.1.1):
dependencies:
ms: 2.1.2
supports-color: 8.1.1
decamelize@4.0.0: {}
deep-eql@4.1.3:
dependencies:
type-detect: 4.0.8
diff@5.0.0: {}
dotenv@16.4.5: {}
emoji-regex@8.0.0: {}
escalade@3.1.2: {}
escape-string-regexp@4.0.0: {}
faye-websocket@0.11.4:
dependencies:
websocket-driver: 0.7.4
faye@1.4.0:
dependencies:
asap: 2.0.6
csprng: 0.1.2
faye-websocket: 0.11.4
safe-buffer: 5.2.1
tough-cookie: 4.1.4
tunnel-agent: 0.6.0
fill-range@7.0.1:
dependencies:
to-regex-range: 5.0.1
find-up@5.0.0:
dependencies:
locate-path: 6.0.0
path-exists: 4.0.0
flat@5.0.2: {}
fs.realpath@1.0.0: {}
fsevents@2.3.3:
optional: true
get-caller-file@2.0.5: {}
get-func-name@2.0.2: {}
glob-parent@5.1.2:
dependencies:
is-glob: 4.0.3
glob@8.1.0:
dependencies:
fs.realpath: 1.0.0
inflight: 1.0.6
inherits: 2.0.4
minimatch: 5.0.1
once: 1.4.0
has-flag@4.0.0: {}
he@1.2.0: {}
http-parser-js@0.5.8: {}
inflight@1.0.6:
dependencies:
once: 1.4.0
wrappy: 1.0.2
inherits@2.0.4: {}
is-binary-path@2.1.0:
dependencies:
binary-extensions: 2.3.0
is-extglob@2.1.1: {}
is-fullwidth-code-point@3.0.0: {}
is-glob@4.0.3:
dependencies:
is-extglob: 2.1.1
is-number@7.0.0: {}
is-plain-obj@2.1.0: {}
is-unicode-supported@0.1.0: {}
js-yaml@4.1.0:
dependencies:
argparse: 2.0.1
locate-path@6.0.0:
dependencies:
p-locate: 5.0.0
log-symbols@4.1.0:
dependencies:
chalk: 4.1.2
is-unicode-supported: 0.1.0
loupe@2.3.7:
dependencies:
get-func-name: 2.0.2
minimatch@5.0.1:
dependencies:
brace-expansion: 2.0.1
mocha@10.4.0:
dependencies:
ansi-colors: 4.1.1
browser-stdout: 1.3.1
chokidar: 3.5.3
debug: 4.3.4(supports-color@8.1.1)
diff: 5.0.0
escape-string-regexp: 4.0.0
find-up: 5.0.0
glob: 8.1.0
he: 1.2.0
js-yaml: 4.1.0
log-symbols: 4.1.0
minimatch: 5.0.1
ms: 2.1.3
serialize-javascript: 6.0.0
strip-json-comments: 3.1.1
supports-color: 8.1.1
workerpool: 6.2.1
yargs: 16.2.0
yargs-parser: 20.2.4
yargs-unparser: 2.0.0
ms@2.1.2: {}
ms@2.1.3: {}
normalize-path@3.0.0: {}
once@1.4.0:
dependencies:
wrappy: 1.0.2
p-limit@3.1.0:
dependencies:
yocto-queue: 0.1.0
p-locate@5.0.0:
dependencies:
p-limit: 3.1.0
path-exists@4.0.0: {}
pathval@1.1.1: {}
picomatch@2.3.1: {}
psl@1.9.0: {}
punycode@2.3.1: {}
querystringify@2.2.0: {}
randombytes@2.1.0:
dependencies:
safe-buffer: 5.2.1
readdirp@3.6.0:
dependencies:
picomatch: 2.3.1
require-directory@2.1.1: {}
requires-port@1.0.0: {}
safe-buffer@5.2.1: {}
sequin@0.1.1: {}
serialize-javascript@6.0.0:
dependencies:
randombytes: 2.1.0
string-width@4.2.3:
dependencies:
emoji-regex: 8.0.0
is-fullwidth-code-point: 3.0.0
strip-ansi: 6.0.1
strip-ansi@6.0.1:
dependencies:
ansi-regex: 5.0.1
strip-json-comments@3.1.1: {}
supports-color@7.2.0:
dependencies:
has-flag: 4.0.0
supports-color@8.1.1:
dependencies:
has-flag: 4.0.0
to-regex-range@5.0.1:
dependencies:
is-number: 7.0.0
tough-cookie@4.1.4:
dependencies:
psl: 1.9.0
punycode: 2.3.1
universalify: 0.2.0
url-parse: 1.5.10
tunnel-agent@0.6.0:
dependencies:
safe-buffer: 5.2.1
type-detect@4.0.8: {}
universalify@0.2.0: {}
url-parse@1.5.10:
dependencies:
querystringify: 2.2.0
requires-port: 1.0.0
websocket-driver@0.7.4:
dependencies:
http-parser-js: 0.5.8
safe-buffer: 5.2.1
websocket-extensions: 0.1.4
websocket-extensions@0.1.4: {}
workerpool@6.2.1: {}
wrap-ansi@7.0.0:
dependencies:
ansi-styles: 4.3.0
string-width: 4.2.3
strip-ansi: 6.0.1
wrappy@1.0.2: {}
y18n@5.0.8: {}
yargs-parser@20.2.4: {}
yargs-unparser@2.0.0:
dependencies:
camelcase: 6.3.0
decamelize: 4.0.0
flat: 5.0.2
is-plain-obj: 2.1.0
yargs@16.2.0:
dependencies:
cliui: 7.0.4
escalade: 3.1.2
get-caller-file: 2.0.5
require-directory: 2.1.1
string-width: 4.2.3
y18n: 5.0.8
yargs-parser: 20.2.4
yocto-queue@0.1.0: {}

View File

@ -1,49 +0,0 @@
const expect = require('chai').expect
const Faye = require('faye')
// for reference of _state contants,
// @see https://github.com/faye/faye/blob/60141e8d3942a88ba3de6a9b3aef9503bc1bb1e6/src/protocol/client.js#L178
const UNCONNECTED = 1
const CONNECTING = 2
const CONNECTED = 3
const DISCONNECTED = 4
describe('faye', function () {
describe('sending event', function () {
let client = new Faye.Client('http://localhost:3535/faye');
// let client = new Faye.Client('https://realtime.futureporn.net/faye');
client.connect()
it('should send a signal', function (done) {
const pub = client.publish('/signals', {
text: 'hello worldy 123!'
})
pub.callback(function () {
expect(client._state).to.equal(CONNECTED)
done()
})
pub.errback(function (e) {
throw new Error(e)
})
})
it('should receive a signal', function (done) {
const sub = client.subscribe('/signals', function(message) {
console.log('Got a message: ' + message.text);
expect(message.text).to.equal('Hello mocha!')
done()
});
sub.callback(function () {
expect(client._state).to.equal(CONNECTED)
})
sub.errback(function (e) {
throw new Error(e)
})
})
this.afterEach(function () {
expect(client._state).to.equal(CONNECTED)
})
this.afterAll(function () {
client.disconnect()
expect(client._state).to.equal(DISCONNECTED)
})
})
})

View File

@ -1,3 +0,0 @@
node_modules
lib
.eslintrc.js

View File

@ -1,48 +0,0 @@
const { builtinModules } = require('module');
const ALLOWED_NODE_BUILTINS = new Set(['assert']);
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
parserOptions: {
project: './tsconfig.json',
tsconfigRootDir: __dirname,
},
plugins: ['@typescript-eslint', 'deprecation'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
'prettier',
],
rules: {
// recommended for safety
'@typescript-eslint/no-floating-promises': 'error', // forgetting to await Activities and Workflow APIs is bad
'deprecation/deprecation': 'warn',
// code style preference
'object-shorthand': ['error', 'always'],
// relaxed rules, for convenience
'@typescript-eslint/no-unused-vars': [
'warn',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
},
],
'@typescript-eslint/no-explicit-any': 'off',
},
overrides: [
{
files: ['src/workflows.ts', 'src/workflows-*.ts', 'src/workflows/*.ts'],
rules: {
'no-restricted-imports': [
'error',
...builtinModules.filter((m) => !ALLOWED_NODE_BUILTINS.has(m)).flatMap((m) => [m, `node:${m}`]),
],
},
},
],
};

View File

@ -1,2 +0,0 @@
lib
node_modules

View File

@ -1 +0,0 @@
20

View File

@ -1 +0,0 @@
lib

View File

@ -1,2 +0,0 @@
printWidth: 120
singleQuote: true

View File

@ -1,46 +0,0 @@
# Cron Workflows
_DEPRECATED: use [Schedules](https://github.com/temporalio/samples-typescript/tree/main/schedules) instead._
This example demonstrates a working Cron workflow. Note the limitations and caveats listed in the [docs](https://docs.temporal.io/content/what-is-a-temporal-cron-job/).
Differences from the hello world demo:
- The Workflow is started with the `cronSchedule: '* * * * *',` option: [`src/client.ts`](./src/client.ts).
- The Activity actually prints a log, instead of returning a string.
- The Workflow runs forever, so if we want it to stop, we have to cancel it. In our `client.ts` script, we cancel it using the handle (when `Ctrl/Cmd-C` is hit). Usually, we'd use the Workflow ID to cancel—for example:
```js
const handle = client.getHandle('1e793a6c-31e2-41c9-8139-53d114293a9e');
await handle.cancel();
```
Note that when we're changing code and restarting Workers, unless we cancel all previous Workflows, they may get picked up by our Worker (since we likely didn't change our Workflow name or task queue), and their output may conflict/mix with new Workflows we're starting. We can check what is still running in Temporal Web ([localhost:8088](http://localhost:8088) in case we need to kill all previous Workflows.
### Running this sample
1. `temporal server start-dev` to start [Temporal Server](https://github.com/temporalio/cli/#installation).
2. `npm install` to install dependencies.
3. `npm run start.watch` to start the Worker.
4. In another shell, `npm run workflow` to run the Workflow.
Example Worker output:
```bash
Hello from my-schedule, Temporal!
Workflow time: 1636333860201
Activity time: 1636333860241
Hello from my-schedule, Temporal!
Workflow time: 1636333920319
Activity time: 1636333920340
```
The difference between "Workflow time" and "Activity time" reflects the latency between scheduling an Activity and actually starting it.
Each new Workflow is `continuedAsNew` under the hood:
![image](https://user-images.githubusercontent.com/6764957/137712906-2a1d821b-d664-442c-8f17-a174b284c722.png)
And you can see the details in the event history:
![image](https://user-images.githubusercontent.com/6764957/137713250-f19a2987-4e9f-4e76-8e35-c17507731a20.png)

File diff suppressed because it is too large Load Diff

View File

@ -1,41 +0,0 @@
{
"name": "cron-workflows",
"version": "0.1.0",
"private": true,
"scripts": {
"build": "tsc --build",
"build.watch": "tsc --build --watch",
"lint": "eslint .",
"start": "ts-node src/worker.ts",
"start.watch": "nodemon src/worker.ts",
"workflow": "ts-node src/client.ts"
},
"nodemonConfig": {
"execMap": {
"ts": "ts-node"
},
"ext": "ts",
"watch": [
"src"
]
},
"dependencies": {
"@temporalio/activity": "^1.9.0",
"@temporalio/client": "^1.9.0",
"@temporalio/worker": "^1.9.0",
"@temporalio/workflow": "^1.9.0"
},
"devDependencies": {
"@tsconfig/node18": "^1.0.0",
"@types/node": "^16.11.43",
"@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-deprecation": "^1.2.1",
"nodemon": "^2.0.12",
"prettier": "^2.8.8",
"ts-node": "^10.2.1",
"typescript": "^4.4.2"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +0,0 @@
import { log, activityInfo } from '@temporalio/activity';
export async function logTime(name: string, wfTime: string): Promise<void> {
const { workflowExecution } = activityInfo();
log.info(`Hello from ${workflowExecution.workflowId}, ${name}!`, { workflowTime: wfTime, activityTime: Date.now() });
}

View File

@ -1,39 +0,0 @@
import { Client } from '@temporalio/client';
import { scheduledWorkflow } from './workflows';
// Save this to later terminate or cancel this schedule
const workflowId = 'my-schedule';
async function run() {
const client = new Client();
const handle = await client.workflow.start(scheduledWorkflow, {
taskQueue: 'cron-workflows',
workflowId: 'my-schedule', // Save this to later terminate or cancel this schedule
cronSchedule: '* * * * *', // start every minute
args: ['Temporal'],
});
console.log('Cron started');
try {
await handle.result(); // await completion of Workflow, which doesn't happen since it's a cron Workflow
} catch (err: any) {
console.error(err.message + ':' + handle.workflowId);
}
}
// just for this demo - cancel the workflow on Ctrl+C
process.on('SIGINT', async () => {
const client = new Client();
const handle = client.workflow.getHandle(workflowId);
await handle.cancel();
console.log(`\nCanceled Workflow ${handle.workflowId}`);
process.exit(0);
});
// you cannot catch SIGKILL
run().catch((err) => {
console.error(err);
process.exit(1);
});

View File

@ -1,17 +0,0 @@
import { Worker } from '@temporalio/worker';
import * as activities from './activities';
async function run() {
// nothing different here compared to hello world
const worker = await Worker.create({
workflowsPath: require.resolve('./workflows'),
activities,
taskQueue: 'cron-workflows',
});
await worker.run();
}
run().catch((err) => {
console.error(err);
process.exit(1);
});

View File

@ -1,11 +0,0 @@
import { proxyActivities } from '@temporalio/workflow';
import type * as activities from './activities';
const { logTime } = proxyActivities<typeof activities>({
startToCloseTimeout: '1 minute',
});
/** A workflow that simply calls an activity */
export async function scheduledWorkflow(name: string): Promise<void> {
await logTime(name, '' + Date.now());
}

View File

@ -1,12 +0,0 @@
{
"extends": "@tsconfig/node18/tsconfig.json",
"version": "4.4.2",
"compilerOptions": {
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"rootDir": "./src",
"outDir": "./lib"
},
"include": ["src/**/*.ts"]
}

View File

@ -1,4 +1,6 @@
# fp-uppy # uppy
This is the server component which handles user uploads.
## B2 Bucket CORS configuration ## B2 Bucket CORS configuration