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

@ -4,14 +4,10 @@ pnpm for workspaces.
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
Tested on VKE v1.30.0+1 (PVCs on other versions may not be fulfilled)
direnv for loading .envrc
Temporal for work queue
@ -21,6 +17,5 @@ Postgres for data storage
S3 for media storage
Domain Driven Development
Test Driven Development
Tested on VKE v1.30.0+1 (PVCs on other versions may not be fulfilled)
Test Driven Development

@ -4,3 +4,23 @@ Source Code for https://futureporn.net
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

@ -1,5 +1,5 @@
# 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

3
packages/bot/README.md Normal 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

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

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

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

@ -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

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

@ -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"]

@ -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.

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

@ -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}`)
})

@ -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

@ -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

@ -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);
});
})
})
})

@ -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();
}
}

@ -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

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

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

@ -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 });
}

@ -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;

@ -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)
})
})

@ -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;

@ -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}`)
})
})
})

@ -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
}

@ -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>`)
})
})
})

@ -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;

@ -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);

@ -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"
}
}

@ -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: {}

@ -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)
})
})
})

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

@ -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}`]),
],
},
},
],
};

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

@ -1 +0,0 @@
20

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

@ -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

@ -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

@ -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() });
}

@ -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);
});

@ -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);
});

@ -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());
}

@ -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"]
}

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