From ea8e9de6e41b792290fa907d6ad707d92487816b Mon Sep 17 00:00:00 2001 From: pike Date: Sat, 30 Sep 2023 17:01:52 +0200 Subject: [PATCH 01/25] feat: Add logging by log4js --- .gitignore | 2 + index.ts | 6 +- package-lock.json | 180 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + src/Feed.ts | 11 ++- src/Folder.ts | 3 +- src/Logger.ts | 4 ++ src/Platform.ts | 10 +-- src/Post.ts | 5 +- 9 files changed, 211 insertions(+), 11 deletions(-) create mode 100644 src/Logger.ts diff --git a/.gitignore b/.gitignore index c148fce..6b16ecb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ # Feed feed .DS_Store +build +*.log # Logs logs diff --git a/index.ts b/index.ts index 0b36ee7..52aa835 100644 --- a/index.ts +++ b/index.ts @@ -3,6 +3,7 @@ Fairpost cli handler */ +import Logger from './src/Logger'; import Feed from './src/Feed'; import { PostStatus } from './src/Post'; import { PlatformSlug } from './src/platforms'; @@ -11,8 +12,8 @@ import { PlatformSlug } from './src/platforms'; const COMMAND = process.argv[2] ?? 'help' // options -const DRY_RUN = !!getOption('dry-run') ?? false; const CONFIG = (getOption('config') as string ) ?? '.env'; +const DRY_RUN = !!getOption('dry-run') ?? false; const REPORT = (getOption('report') as string ) ?? 'text'; const PLATFORMS = (getOption('platforms') as string)?.split(',') as PlatformSlug[] ?? undefined; const FOLDERS = (getOption('folders') as string)?.split(',') ?? undefined; @@ -37,8 +38,7 @@ async function main() { let report = ''; const feed = new Feed(CONFIG); - report += 'Fairpost '+feed.path+' starting .. ',DRY_RUN?'dry-run':''; - report += '\n'; + Logger.trace('Fairpost '+feed.path+' '+COMMAND,DRY_RUN?' dry-run':''); try { diff --git a/package-lock.json b/package-lock.json index 26bb308..b12eccb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "ISC", "dependencies": { "dotenv": "^16.0.3", + "log4js": "^6.9.1", "node-fetch": "^2.6.7", "sharp": "0.31.1" }, @@ -213,6 +214,30 @@ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true }, + "node_modules/date-format": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", + "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/decompress-response": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", @@ -279,16 +304,39 @@ "node": ">=6" } }, + "node_modules/flatted": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==" + }, "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -323,6 +371,29 @@ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/log4js": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz", + "integrity": "sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==", + "dependencies": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "flatted": "^3.2.7", + "rfdc": "^1.3.0", + "streamroller": "^3.1.5" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -364,6 +435,11 @@ "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, "node_modules/napi-build-utils": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", @@ -473,6 +549,11 @@ "node": ">= 6" } }, + "node_modules/rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==" + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -579,6 +660,19 @@ "is-arrayish": "^0.3.1" } }, + "node_modules/streamroller": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", + "integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==", + "dependencies": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -693,6 +787,14 @@ "node": ">=14.17" } }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -885,6 +987,19 @@ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true }, + "date-format": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", + "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==" + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, "decompress-response": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", @@ -927,16 +1042,36 @@ "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==" }, + "flatted": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==" + }, "fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, "github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" }, + "graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, "ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -957,6 +1092,26 @@ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "log4js": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz", + "integrity": "sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==", + "requires": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "flatted": "^3.2.7", + "rfdc": "^1.3.0", + "streamroller": "^3.1.5" + } + }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -986,6 +1141,11 @@ "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, "napi-build-utils": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", @@ -1069,6 +1229,11 @@ "util-deprecate": "^1.0.1" } }, + "rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==" + }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -1120,6 +1285,16 @@ "is-arrayish": "^0.3.1" } }, + "streamroller": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", + "integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==", + "requires": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" + } + }, "string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -1196,6 +1371,11 @@ "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", "dev": true }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index 09a3c62..010dc77 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "license": "ISC", "dependencies": { "dotenv": "^16.0.3", + "log4js": "^6.9.1", "node-fetch": "^2.6.7", "sharp": "0.31.1" }, diff --git a/src/Feed.ts b/src/Feed.ts index a78f119..aebe7c8 100644 --- a/src/Feed.ts +++ b/src/Feed.ts @@ -2,6 +2,7 @@ import * as fs from 'fs'; import * as path from 'path'; import * as dotenv from 'dotenv'; +import Logger from './Logger'; import Platform from "./Platform"; import Folder from "./Folder"; import Post from "./Post"; @@ -47,10 +48,12 @@ export default class Feed { } getPlatforms(platforms?:PlatformSlug[]): Platform[] { + Logger.trace('Feed','getPlatforms'); return platforms?.map(platform=>this.platforms[platform]) ?? Object.values(this.platforms); } getAllFolders(): Folder[] { + Logger.trace('Feed','getAllFolders'); if (this.folders.length) { return this.folders; } @@ -68,6 +71,7 @@ export default class Feed { } getFolders(paths?: string[]): Folder[] { + Logger.trace('Feed','getFolders'); return paths?.map(path=>new Folder(this.path+'/'+path)) ?? this.getAllFolders(); } @@ -76,6 +80,7 @@ export default class Feed { platforms?:PlatformSlug[], status?:PostStatus }): Post[] { + Logger.trace('Feed','getPosts'); const posts: Post[] = []; const platforms = this.getPlatforms(filters?.platforms); const folders = this.getFolders(filters?.paths); @@ -94,7 +99,7 @@ export default class Feed { paths?:string[] platforms?:PlatformSlug[] }): Promise { - + Logger.trace('Feed','preparePosts'); const posts: Post[] = []; const platforms = this.getPlatforms(filters?.platforms); const folders = this.getFolders(filters?.paths); @@ -114,6 +119,7 @@ export default class Feed { getLastPost(platform:PlatformSlug): Post | void { + Logger.trace('Feed','getLastPost'); let lastPost: Post = undefined; const posts = this.getPosts({ platforms: [platform], @@ -129,6 +135,7 @@ export default class Feed { getNextPostDate(platform:PlatformSlug): Date { + Logger.trace('Feed','getNextPostDate'); let nextDate = null; const lastPost = this.getLastPost(platform); if (lastPost) { @@ -144,6 +151,7 @@ export default class Feed { paths?:string[] platforms?:PlatformSlug[] }): Post[] { + Logger.trace('Feed','scheduleNextPosts'); const posts: Post[] = []; const platforms = this.getPlatforms(filters?.platforms); const folders = this.getFolders(filters?.paths); @@ -166,6 +174,7 @@ export default class Feed { paths?:string[] platforms?:PlatformSlug[] }, dryrun:boolean = false): Promise { + Logger.trace('Feed','publishDuePosts'); const now = new Date(); const posts: Post[] = []; const platforms = this.getPlatforms(filters?.platforms); diff --git a/src/Folder.ts b/src/Folder.ts index 3473b9d..e5e7fa1 100644 --- a/src/Folder.ts +++ b/src/Folder.ts @@ -1,5 +1,5 @@ import * as fs from 'fs'; -import * as path from 'path'; +import Logger from './Logger'; export default class Folder { @@ -16,6 +16,7 @@ export default class Folder { } getFiles() { + Logger.trace('Folder','getFiles'); if (this.files!=undefined) { return { text: [ ...this.files.text ], diff --git a/src/Logger.ts b/src/Logger.ts new file mode 100644 index 0000000..920b96f --- /dev/null +++ b/src/Logger.ts @@ -0,0 +1,4 @@ +import * as log4js from "log4js"; +import * as path from 'path'; +log4js.configure(path.resolve(__dirname+'/../../log4js.json')); +export default log4js.getLogger('default'); diff --git a/src/Platform.ts b/src/Platform.ts index 1a81b8e..8d09a18 100644 --- a/src/Platform.ts +++ b/src/Platform.ts @@ -1,12 +1,10 @@ import * as fs from 'fs'; -import * as path from 'path'; +import Logger from './Logger'; import Folder from "./Folder"; import Post from "./Post"; import { PostStatus } from "./Post"; import { PlatformSlug } from "./platforms"; - - export default class Platform { active: boolean = false; @@ -32,7 +30,7 @@ export default class Platform { getPost(folder: Folder): Post | undefined { - //console.log(this.slug,'getPost',folder.path); + Logger.trace('Platform','getPost'); if (fs.existsSync(folder.path+'/'+this.getPostFileName())) { const data = JSON.parse(fs.readFileSync(folder.path+'/'+this.getPostFileName(), 'utf8')); @@ -56,7 +54,7 @@ export default class Platform { */ async preparePost(folder: Folder): Promise { - console.log(this.slug,'preparePost',folder.path); + Logger.trace('Platform','preparePost'); const post = this.getPost(folder) ?? new Post(folder,this); if (post.status===PostStatus.PUBLISHED) { @@ -114,6 +112,8 @@ export default class Platform { */ async publishPost(post: Post, dryrun:boolean = false): Promise { + + Logger.trace('Platform','publishPost'); post.posted = new Date(); post.results.push({ error: 'publishing not implemented for '+this.slug diff --git a/src/Post.ts b/src/Post.ts index 081eecf..1fd6042 100644 --- a/src/Post.ts +++ b/src/Post.ts @@ -1,6 +1,6 @@ import * as fs from 'fs'; -import * as path from 'path'; +import Logger from './Logger'; import Folder from "./Folder"; import Platform from "./Platform"; @@ -41,6 +41,7 @@ export default class Post { */ save(): void { + Logger.trace('Post','save'); const data = { ...this}; delete data.folder; delete data.platform; @@ -51,12 +52,14 @@ export default class Post { } schedule(date:Date): void { + Logger.trace('Post','schedule'); this.scheduled = date; this.status = PostStatus.SCHEDULED; this.save(); } report(): string { + Logger.trace('Post','report'); let report = ''; report += '\nPost: '+this.platform.slug+' : '+this.folder.path; report += '\n - valid: '+this.valid; From 8768384ade862abeae06607ab6c418ed3f40b471 Mon Sep 17 00:00:00 2001 From: pike Date: Sat, 30 Sep 2023 17:18:31 +0200 Subject: [PATCH 02/25] fix: Forgot log file ? --- log4js.json | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 log4js.json diff --git a/log4js.json b/log4js.json new file mode 100644 index 0000000..e709666 --- /dev/null +++ b/log4js.json @@ -0,0 +1,8 @@ +{ + "appenders": { + "app": { "type": "file", "filename" : "fairpost.log" } + }, + "categories": { + "default": { "appenders": ["app"], "level": "trace" } + } +} \ No newline at end of file From 9511c8864e605b7a668e4217367af24a7c93466f Mon Sep 17 00:00:00 2001 From: pike Date: Sun, 1 Oct 2023 17:47:53 +0200 Subject: [PATCH 03/25] Update README.md --- README.md | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index c16cfae..4a550db 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,26 @@ # Fairpost - - -Ayrshare feed helps you manage your ayrshare -feed from the command line. +Fairpost helps you manage your social +feeds from the command line, using Node. Each post is a folder, containing at least one text file (the post body) and optionally images or a video. +To smoothly maintain your feed, you can run +fairpost every day. Fairpost will prepare and +schedule new content to be posted on all your +platforms on a regular basis, while +you can just focus on creating new content. + +Or, if you prefer, you can manually publish one +specific post on all supported and enabled +platforms at once. + Edit the .env file to manage the platforms -you want to support, amongst others. +you want to support, the interval for new posts, +etcetera. For each platform, you'll have to +register the app to post on your behalf. ## Setting up ``` From b4783eb5af4a8b8d8e73a6a61a3268d8bfc40bff Mon Sep 17 00:00:00 2001 From: pike Date: Sun, 1 Oct 2023 17:52:41 +0200 Subject: [PATCH 04/25] Update README.md --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4a550db..4a60dcd 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,13 @@ folder. ``` fairpost.js schedule-next-post ``` -The next post can then be `scheduled`. For each platform this just updates the json file in one post to set the status to scheduled, and set the schedule date. By default the date will be `FAIRPOST_FEED_INTERVAL` days after the last post for that platform, or `now`, whichever is latest. +The next post can then be `scheduled`. For each platform, +if there is not already a scheduled post, this will update +the json file in one post to set the status to scheduled, +and set the schedule date. +By default the date will be `FAIRPOST_FEED_INTERVAL` days +after the last post for that platform, or `now`, whichever +is latest. ## Publish ``` From 0fad37cf36027643c08f5fe62fe0c08ff71547b0 Mon Sep 17 00:00:00 2001 From: pike Date: Fri, 6 Oct 2023 22:48:43 +0200 Subject: [PATCH 05/25] feat: Facebook docs and setup --- index.ts | 19 +++++++++++++++++-- src/platforms/index.ts | 3 ++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/index.ts b/index.ts index 52aa835..c593686 100644 --- a/index.ts +++ b/index.ts @@ -7,6 +7,7 @@ import Logger from './src/Logger'; import Feed from './src/Feed'; import { PostStatus } from './src/Post'; import { PlatformSlug } from './src/platforms'; +import Facebook from './src/platforms/Facebook'; // arguments const COMMAND = process.argv[2] ?? 'help' @@ -102,6 +103,19 @@ async function main() { }); result = nextposts; break; + case 'facebook-get-page-token': + const userToken = (getOption('user-token') as string ); + if (!userToken) { + throw new Error('Missing parameter: user-token'); + } + const appUserId = (getOption('app-user-id') as string ); + if (!appUserId) { + throw new Error('Missing parameter: app-user-id'); + } + const facebook = new Facebook(); + result = await facebook.getPageToken(appUserId, userToken); + report = 'Page Token: '+result; + break; default: const cmd = process.argv[1]; result = [ @@ -112,12 +126,13 @@ async function main() { `${cmd} prepare-posts [--platforms=xxx,xxx] [--folders=xxx,xxx]`, `${cmd} get-posts [--status=xxx] [--platforms=xxx,xxx] [--folders=xxx,xxx]`, `${cmd} schedule-next-post [--date=xxxx-xx-xx] [--platforms=xxx,xxx] [--folders=xxx,xxx]`, - `${cmd} publish-due-posts [--platforms=xxx,xxx] [--folders=xxx,xxx] [--dry-run]` + `${cmd} publish-due-posts [--platforms=xxx,xxx] [--folders=xxx,xxx] [--dry-run]`, + `${cmd} facebook-get-page-token --app-user-id=xxx --user-token=xxx` ]; result.forEach(line => report += '\n'+line); } } catch (e) { - console.error(e.getMessage()); + console.error(e.message); } switch(REPORT) { diff --git a/src/platforms/index.ts b/src/platforms/index.ts index 72c22bd..ca15e5d 100644 --- a/src/platforms/index.ts +++ b/src/platforms/index.ts @@ -14,5 +14,6 @@ export enum PlatformSlug { ASTWITTER = "astwitter", ASTIKTOK = "astiktok", ASLINKEDIN = "aslinkedin", - ASREDDIT = "asreddit" + ASREDDIT = "asreddit", + FACEBOOK = "facebook" } \ No newline at end of file From 3fb7cfa2f58e36ca8f70fd65631a946d746e33b8 Mon Sep 17 00:00:00 2001 From: pike Date: Sat, 7 Oct 2023 11:09:12 +0200 Subject: [PATCH 06/25] feat: Add test-platforms --- index.ts | 7 ++++++- src/Feed.ts | 11 ++++++++++- src/Platform.ts | 11 +++++++++++ src/platforms/index.ts | 1 + 4 files changed, 28 insertions(+), 2 deletions(-) diff --git a/index.ts b/index.ts index c593686..8dd1ccc 100644 --- a/index.ts +++ b/index.ts @@ -35,7 +35,7 @@ function getOption(key:string):boolean|string|null { /* main */ async function main() { - let result: any = ''; + let result: any; let report = ''; const feed = new Feed(CONFIG); @@ -55,6 +55,10 @@ async function main() { }); result = platforms; break; + case 'test-platforms': + result = await feed.testPlatforms(PLATFORMS); + report = "Result: \n"+ JSON.stringify(result,null,'\t'); + break; case 'get-folders': const folders = feed.getFolders(FOLDERS); folders.forEach(folder => { @@ -121,6 +125,7 @@ async function main() { result = [ `${cmd} help`, `${cmd} get-feed [--config=xxx]`, + `${cmd} test [--platforms=xxx,xxx]`, `${cmd} get-platforms [--platforms=xxx,xxx]`, `${cmd} get-folders [--folders=xxx,xxx]`, `${cmd} prepare-posts [--platforms=xxx,xxx] [--folders=xxx,xxx]`, diff --git a/src/Feed.ts b/src/Feed.ts index aebe7c8..2a74a03 100644 --- a/src/Feed.ts +++ b/src/Feed.ts @@ -37,6 +37,7 @@ export default class Feed { const platformClasses = fs.readdirSync(path.resolve(__dirname+'/platforms')); platformClasses.forEach(file=> { const constructor = file.replace('.ts','').replace('.js',''); + // nb import * as platforms loaded the constructors if (platforms[constructor] !== undefined) { const platform = new platforms[constructor](); platform.active = activePlatformSlugs.includes(platform.slug); @@ -48,10 +49,18 @@ export default class Feed { } getPlatforms(platforms?:PlatformSlug[]): Platform[] { - Logger.trace('Feed','getPlatforms'); + Logger.trace('Feed','getPlatforms',platforms); return platforms?.map(platform=>this.platforms[platform]) ?? Object.values(this.platforms); } + async testPlatforms(platforms?:PlatformSlug[]): Promise<{ [slug:string] : {}}> { + const results = {}; + for (const platform of this.getPlatforms(platforms)) { + results[platform.slug] = await platform.test(); + } + return results; + } + getAllFolders(): Folder[] { Logger.trace('Feed','getAllFolders'); if (this.folders.length) { diff --git a/src/Platform.ts b/src/Platform.ts index 8d09a18..1e3e180 100644 --- a/src/Platform.ts +++ b/src/Platform.ts @@ -122,6 +122,17 @@ export default class Platform { post.save(); return false; } + + /* + * test + * + * Test the platform installation. This should not post + * anything, but test access tokens et al. It can return + * anything. + */ + async test(): Promise { + return 'No tests'; + } } diff --git a/src/platforms/index.ts b/src/platforms/index.ts index ca15e5d..7c5e71f 100644 --- a/src/platforms/index.ts +++ b/src/platforms/index.ts @@ -5,6 +5,7 @@ export { default as AsFacebook } from "./AsFacebook"; export { default as AsTikTok } from "./AsTikTok"; export { default as AsLinkedIn } from "./AsLinkedIn"; export { default as AsReddit } from "./AsReddit"; +export { default as Facebook } from "./Facebook"; export enum PlatformSlug { UNKNOWN = "unknown", From 5b92762fe1e4aab15c0bdbcd81461c4d035ad7d9 Mon Sep 17 00:00:00 2001 From: pike Date: Sat, 7 Oct 2023 11:10:50 +0200 Subject: [PATCH 07/25] feat: Add basic facebook platform --- src/platforms/Facebook.ts | 140 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 src/platforms/Facebook.ts diff --git a/src/platforms/Facebook.ts b/src/platforms/Facebook.ts new file mode 100644 index 0000000..fdd42b8 --- /dev/null +++ b/src/platforms/Facebook.ts @@ -0,0 +1,140 @@ + +import Logger from "../Logger"; +import Platform from "../Platform"; +import { PlatformSlug } from "."; +import Folder from "../Folder"; +import Post from "../Post"; +import * as fs from 'fs'; +import * as sharp from 'sharp'; + +export default class Facebook extends Platform { + slug: PlatformSlug = PlatformSlug.FACEBOOK; + GRAPH_API_VERSION: string = 'v18.0'; + + constructor() { + super(); + } + + async preparePost(folder: Folder): Promise { + const post = await super.preparePost(folder); + if (post && post.files) { + // facebook : max 10mb images + for (const image of post.files.image) { + var size = fs.statSync(post.folder.path+'/'+image).size / (1024*1024); + if (size>=10) { + console.log('Resizing '+image+' for facebook ..'); + await sharp(post.folder.path+'/'+image).resize({ + width: 1200 + }).toFile(post.folder.path+'/_facebook-'+image); + post.files.image.push('_facebook-'+image); + post.files.image = post.files.image.filter(file => file !== image); + } + } + post.save(); + } + return post; + } + + async publishPost(post: Post, dryrun:boolean = false): Promise { + return super.publishPost(post,dryrun); + } + + /* + * Return a long lived page access token. + * + * UserAccessToken: a shortlived user access token + */ + async getPageToken(appUserId: string, userAccessToken :string): Promise { + + // get a long lived UserAccessToken + /* + let url1 = `https://graph.facebook.com/${this.GRAPH_API_VERSION}`; + url1 += '/oauth/access_token?'; + url1 += "grant_type=fb_exchange_token&"; + url1 += `client_id=${process.env.FAIRPOST_FACEBOOK_APP_ID}&`; + url1 += `client_secret=${process.env.FAIRPOST_FACEBOOK_APP_SECRET}&`; + url1 += `fb_exchange_token=${userAccessToken}`; + */ + + const url = new URL('https://graph.facebook.com'); + url.pathname = this.GRAPH_API_VERSION + "/oauth/access_token"; + const query = { + grant_type : "fb_exchange_token", + client_id : process.env.FAIRPOST_FACEBOOK_APP_ID, + client_secret : process.env.FAIRPOST_FACEBOOK_APP_SECRET, + fb_exchange_token : userAccessToken + }; + url.search = new URLSearchParams(query).toString(); + + Logger.trace('fetching',url); + const res1 = await fetch(url,{ + method: 'GET', + headers: { 'Accept': 'application/json'}, + }); + const data1 = await res1.json(); + const llUserAccessToken = data1['access_token']; + + if (!llUserAccessToken) { + console.error(data1); + throw new Error('No llUserAccessToken access_token in response.'); + } + + /*let url2 = `https://graph.facebook.com/${this.GRAPH_API_VERSION}`; + url2 += `/${appUserId}/accounts?`; + url2 += `access_token=${llUserAccessToken}`; + + Logger.trace('fetching',url2); + const res2 = await fetch(url2,{ + method: 'GET', + headers: { 'Accept': 'application/json'}, + }); + const data2 = await res2.json(); + */ + + const data2 = await this.get('accounts',{ + 'access_token' : llUserAccessToken + }); + + let llPageAccessToken = ''; + if (data2.data) { + data2.data.forEach(page=> { + if (page.id===process.env.FAIRPOST_FACEBOOK_PAGE_ID) { + llPageAccessToken = page.access_token; + } + }) + } + if (!llPageAccessToken) { + console.error(data2); + throw new Error('No llPageAccessToken for page '+process.env.FAIRPOST_FACEBOOK_PAGE_ID+' in response.'); + } + + return llPageAccessToken; + } + + async test() { + return this.get(); + } + + private async get( + method: string = '', + query: { [key:string]: string } = {} + ) { + const url = new URL('https://graph.facebook.com'); + url.pathname = this.GRAPH_API_VERSION + "/" + process.env.FAIRPOST_FACEBOOK_PAGE_ID +"/" + method, + url.search = new URLSearchParams(query).toString(); + Logger.trace('fetching',url.href); + const res = await fetch(url,{ + method: 'GET', + headers: process.env.FAIRPOST_FACEBOOK_PAGE_ACCESS_TOKEN ? { + 'Accept': 'application/json', + 'Authorization': 'Bearer '+process.env.FAIRPOST_FACEBOOK_PAGE_ACCESS_TOKEN + }: { + 'Accept': 'application/json' + } + }); + const result = await res.json(); + console.log(result); + return result; + } + +} \ No newline at end of file From 4f54de5314f00dd583181a72a3add2834ec9991d Mon Sep 17 00:00:00 2001 From: pike Date: Sat, 7 Oct 2023 11:11:13 +0200 Subject: [PATCH 08/25] feat: Add docs --- docs/Facebook.md | 54 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 docs/Facebook.md diff --git a/docs/Facebook.md b/docs/Facebook.md new file mode 100644 index 0000000..e9b5752 --- /dev/null +++ b/docs/Facebook.md @@ -0,0 +1,54 @@ +# Platform: Facebook + +The `facebook` platform manage a facebook **page* (not your feed) +using the plain graph api - no extensions installed. + +## Setting up the Facebook platform + +https://dev.to/xaypanya/how-to-connect-your-nodejs-server-to-facebook-page-api-1hol +https://developers.facebook.com/docs/pages/getting-started +https://developers.facebook.com/docs/pages-api/posts + +### Create a new App in your facebook account + - go to https://developers.facebook.com/ + - create an app that can manage pages + - for instagram, you'll need to attach a business account (...) that is connected to a facebook page + - under 'settings', find your app ID + - save this as `FAIRPOST_FACEBOOK_APP_ID` in your .env + - under 'settings', find your app secret + - save this as `FAIRPOST_FACEBOOK_APP_SECRET` in your .env + +### Find the page id of the page you want the app to manage + - go to https://business.facebook.com/ + - find your page (currently under 'settings > business assets') + - note the page id + - save this as `FAIRPOST_FACEBOOK_PAGE_ID` in your .env + +### Get a (short lived) Page Access Token for the page you want the app to manage + +This is good for testing, but you'll have to refresh this token often. + + - go to https://developers.facebook.com/tools/explorer/ + - select your app + - add permission `pages_manage_posts` and `business_management` to your app + - request a (short lived) page access token + - save this as `FAIRPOST_FACEBOOK_PAGE_ACCESS_TOKEN` in your .env + +### Get a (long lived) Page Access Token for the page you want the app to manage + +This token should last forever. It involves get a long-lived user token and then requesting the 'accounts' for your 'app scoped user id'; but this app provides a tool to help you do that: + + - go to https://developers.facebook.com/tools/explorer/ + - select your app + - add permission `pages_manage_posts` and `business_management` to your app + - request a (short lived) user access token + - click 'submit' to submit the default `?me` query + - remember the `id` in the response as your id + - call `./fairpost.js facebook-get-page-token + --app-user-id={your id} --user-token={your token}` + - note the token returned + - save this as `FAIRPOST_FACEBOOK_PAGE_ACCESS_TOKEN` in your .env + +### Enable and test the facebook platform + - Add 'facebook' to your `FAIRPOST_FEED_PLATFORMS` in `.env` + - call `./fairpost.js facebook-test` From e26ec2a37dd3da52798dac4b8510e679499cfae6 Mon Sep 17 00:00:00 2001 From: pike Date: Sat, 7 Oct 2023 11:26:02 +0200 Subject: [PATCH 09/25] chore: Clean up code and docs --- docs/Facebook.md | 16 ++++++++++++++-- src/platforms/Facebook.ts | 17 +++++++++++++---- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/docs/Facebook.md b/docs/Facebook.md index e9b5752..d3d7164 100644 --- a/docs/Facebook.md +++ b/docs/Facebook.md @@ -30,7 +30,13 @@ This is good for testing, but you'll have to refresh this token often. - go to https://developers.facebook.com/tools/explorer/ - select your app - - add permission `pages_manage_posts` and `business_management` to your app + - add permissions + - pages_manage_engagement + - pages_manage_posts + - pages_read_engagement + - pages_read_user_engagement + - publish_video + - business_management - request a (short lived) page access token - save this as `FAIRPOST_FACEBOOK_PAGE_ACCESS_TOKEN` in your .env @@ -40,7 +46,13 @@ This token should last forever. It involves get a long-lived user token and then - go to https://developers.facebook.com/tools/explorer/ - select your app - - add permission `pages_manage_posts` and `business_management` to your app + - add permissions + - pages_manage_engagement + - pages_manage_posts + - pages_read_engagement + - pages_read_user_engagement + - publish_video + - business_management - request a (short lived) user access token - click 'submit' to submit the default `?me` query - remember the `id` in the response as your id diff --git a/src/platforms/Facebook.ts b/src/platforms/Facebook.ts index fdd42b8..52e0115 100644 --- a/src/platforms/Facebook.ts +++ b/src/platforms/Facebook.ts @@ -66,7 +66,7 @@ export default class Facebook extends Platform { }; url.search = new URLSearchParams(query).toString(); - Logger.trace('fetching',url); + Logger.trace('fetching',url.href); const res1 = await fetch(url,{ method: 'GET', headers: { 'Accept': 'application/json'}, @@ -91,10 +91,19 @@ export default class Facebook extends Platform { const data2 = await res2.json(); */ - const data2 = await this.get('accounts',{ - 'access_token' : llUserAccessToken + const url2 = new URL('https://graph.facebook.com'); + url2.pathname = appUserId + "/accounts"; + const query2 = { + access_token : llUserAccessToken + }; + url2.search = new URLSearchParams(query2).toString(); + Logger.trace('fetching',url.href); + const res2 = await fetch(url2,{ + method: 'GET', + headers: { 'Accept': 'application/json'}, }); - + const data2 = await res2.json(); + let llPageAccessToken = ''; if (data2.data) { data2.data.forEach(page=> { From 181814dc88b0432d0fba3c5bffaff891d6a98527 Mon Sep 17 00:00:00 2001 From: pike Date: Sat, 7 Oct 2023 11:46:51 +0200 Subject: [PATCH 10/25] docs: Improve docs --- docs/Facebook.md | 14 +++++++++++--- src/platforms/Facebook.ts | 23 +++++++++++++++++++++-- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/docs/Facebook.md b/docs/Facebook.md index d3d7164..e3a11b4 100644 --- a/docs/Facebook.md +++ b/docs/Facebook.md @@ -5,9 +5,6 @@ using the plain graph api - no extensions installed. ## Setting up the Facebook platform -https://dev.to/xaypanya/how-to-connect-your-nodejs-server-to-facebook-page-api-1hol -https://developers.facebook.com/docs/pages/getting-started -https://developers.facebook.com/docs/pages-api/posts ### Create a new App in your facebook account - go to https://developers.facebook.com/ @@ -64,3 +61,14 @@ This token should last forever. It involves get a long-lived user token and then ### Enable and test the facebook platform - Add 'facebook' to your `FAIRPOST_FEED_PLATFORMS` in `.env` - call `./fairpost.js facebook-test` + +# Random documentation + +https://dev.to/xaypanya/how-to-connect-your-nodejs-server-to-facebook-page-api-1hol +https://developers.facebook.com/docs/pages/getting-started +https://developers.facebook.com/docs/pages-api/posts +https://developers.facebook.com/docs/graph-api/reference/page/photos/ +https://developers.facebook.com/docs/video-api/guides/publishing + +large uploads: +https://developers.facebook.com/docs/graph-api/guides/upload/ diff --git a/src/platforms/Facebook.ts b/src/platforms/Facebook.ts index 52e0115..bc766aa 100644 --- a/src/platforms/Facebook.ts +++ b/src/platforms/Facebook.ts @@ -131,7 +131,7 @@ export default class Facebook extends Platform { const url = new URL('https://graph.facebook.com'); url.pathname = this.GRAPH_API_VERSION + "/" + process.env.FAIRPOST_FACEBOOK_PAGE_ID +"/" + method, url.search = new URLSearchParams(query).toString(); - Logger.trace('fetching',url.href); + Logger.trace('GET',url.href); const res = await fetch(url,{ method: 'GET', headers: process.env.FAIRPOST_FACEBOOK_PAGE_ACCESS_TOKEN ? { @@ -142,7 +142,26 @@ export default class Facebook extends Platform { } }); const result = await res.json(); - console.log(result); + return result; + } + + private async post( + method: string = '', + body = {} + ) { + const url = new URL('https://graph.facebook.com'); + url.pathname = this.GRAPH_API_VERSION + "/" + process.env.FAIRPOST_FACEBOOK_PAGE_ID +"/" + method, + Logger.trace('POST',url.href); + const res = await fetch(url,{ + method: 'POST', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'Authorization': 'Bearer '+process.env.FAIRPOST_FACEBOOK_PAGE_ACCESS_TOKEN + }, + body: JSON.stringify(body) + }); + const result = await res.json(); return result; } From 13cc11dcc61d326ac724f52ad69f2f31a393ffb8 Mon Sep 17 00:00:00 2001 From: pike Date: Sat, 7 Oct 2023 18:17:46 +0200 Subject: [PATCH 11/25] feat: Add facebook postImage and postVideo --- docs/Facebook.md | 27 ++++++++- index.ts | 1 + src/platforms/Facebook.ts | 122 +++++++++++++++++++++++++++++--------- 3 files changed, 122 insertions(+), 28 deletions(-) diff --git a/docs/Facebook.md b/docs/Facebook.md index e3a11b4..f19484a 100644 --- a/docs/Facebook.md +++ b/docs/Facebook.md @@ -60,7 +60,29 @@ This token should last forever. It involves get a long-lived user token and then ### Enable and test the facebook platform - Add 'facebook' to your `FAIRPOST_FEED_PLATFORMS` in `.env` - - call `./fairpost.js facebook-test` + - call `./fairpost.js test --platforms=facebook` + +# Limitations + +## Images + +From https://developers.facebook.com/docs/graph-api/reference/page/photos/ : + +Facebook strips all location metadata before publishing and resizes images to different dimensions to best support rendering in multiple sizes. + + +### Supported Formats +Facebook supports the following formats: + - JPEG + - BMP + - PNG + - GIF + - TIFF + +### File Size + +Files must be 4MB or smaller in size. +For PNG files, try keep the file size below 1 MB. PNG files larger than 1 MB may appear pixelated after upload. # Random documentation @@ -72,3 +94,6 @@ https://developers.facebook.com/docs/video-api/guides/publishing large uploads: https://developers.facebook.com/docs/graph-api/guides/upload/ + +https://www.npmjs.com/package/formdata-node +https://medium.com/deno-the-complete-reference/sending-form-data-using-fetch-in-node-js-8cedd0b2af85 \ No newline at end of file diff --git a/index.ts b/index.ts index 8dd1ccc..a2f5e1d 100644 --- a/index.ts +++ b/index.ts @@ -127,6 +127,7 @@ async function main() { `${cmd} get-feed [--config=xxx]`, `${cmd} test [--platforms=xxx,xxx]`, `${cmd} get-platforms [--platforms=xxx,xxx]`, + `${cmd} test-platforms [--platforms=xxx,xxx]`, `${cmd} get-folders [--folders=xxx,xxx]`, `${cmd} prepare-posts [--platforms=xxx,xxx] [--folders=xxx,xxx]`, `${cmd} get-posts [--status=xxx] [--platforms=xxx,xxx] [--folders=xxx,xxx]`, diff --git a/src/platforms/Facebook.ts b/src/platforms/Facebook.ts index bc766aa..f231795 100644 --- a/src/platforms/Facebook.ts +++ b/src/platforms/Facebook.ts @@ -5,6 +5,7 @@ import { PlatformSlug } from "."; import Folder from "../Folder"; import Post from "../Post"; import * as fs from 'fs'; +import * as path from 'path'; import * as sharp from 'sharp'; export default class Facebook extends Platform { @@ -39,6 +40,10 @@ export default class Facebook extends Platform { return super.publishPost(post,dryrun); } + async test() { + return this.get(); + } + /* * Return a long lived page access token. * @@ -47,14 +52,6 @@ export default class Facebook extends Platform { async getPageToken(appUserId: string, userAccessToken :string): Promise { // get a long lived UserAccessToken - /* - let url1 = `https://graph.facebook.com/${this.GRAPH_API_VERSION}`; - url1 += '/oauth/access_token?'; - url1 += "grant_type=fb_exchange_token&"; - url1 += `client_id=${process.env.FAIRPOST_FACEBOOK_APP_ID}&`; - url1 += `client_secret=${process.env.FAIRPOST_FACEBOOK_APP_SECRET}&`; - url1 += `fb_exchange_token=${userAccessToken}`; - */ const url = new URL('https://graph.facebook.com'); url.pathname = this.GRAPH_API_VERSION + "/oauth/access_token"; @@ -79,17 +76,7 @@ export default class Facebook extends Platform { throw new Error('No llUserAccessToken access_token in response.'); } - /*let url2 = `https://graph.facebook.com/${this.GRAPH_API_VERSION}`; - url2 += `/${appUserId}/accounts?`; - url2 += `access_token=${llUserAccessToken}`; - - Logger.trace('fetching',url2); - const res2 = await fetch(url2,{ - method: 'GET', - headers: { 'Accept': 'application/json'}, - }); - const data2 = await res2.json(); - */ + // get a long lived PageAccessToken const url2 = new URL('https://graph.facebook.com'); url2.pathname = appUserId + "/accounts"; @@ -120,16 +107,13 @@ export default class Facebook extends Platform { return llPageAccessToken; } - async test() { - return this.get(); - } - private async get( - method: string = '', + call: string = '', query: { [key:string]: string } = {} ) { const url = new URL('https://graph.facebook.com'); - url.pathname = this.GRAPH_API_VERSION + "/" + process.env.FAIRPOST_FACEBOOK_PAGE_ID +"/" + method, + url.pathname = this.GRAPH_API_VERSION + "/" + process.env.FAIRPOST_FACEBOOK_PAGE_ID; + url.pathname += "/" + call, url.search = new URLSearchParams(query).toString(); Logger.trace('GET',url.href); const res = await fetch(url,{ @@ -145,12 +129,25 @@ export default class Facebook extends Platform { return result; } + private async testPost() { + return this.post( + 'feed', + { + "message":"test", + "link":"https://test.com", + "published":"false", + "scheduled_publish_time":"tomorrow", + } + ); + } + private async post( - method: string = '', + call: string = '', body = {} ) { const url = new URL('https://graph.facebook.com'); - url.pathname = this.GRAPH_API_VERSION + "/" + process.env.FAIRPOST_FACEBOOK_PAGE_ID +"/" + method, + url.pathname = this.GRAPH_API_VERSION + "/" + process.env.FAIRPOST_FACEBOOK_PAGE_ID; + url.pathname += "/" + call, Logger.trace('POST',url.href); const res = await fetch(url,{ method: 'POST', @@ -163,6 +160,77 @@ export default class Facebook extends Platform { }); const result = await res.json(); return result; + + } + + private async postImage( + file: string = '' + ): Promise { + + Logger.trace('Reading file',file); + const rawData = fs.readFileSync(file); + const blob = new Blob([rawData]); + + const url = new URL('https://graph.facebook.com'); + url.pathname = this.GRAPH_API_VERSION + "/" + process.env.FAIRPOST_FACEBOOK_PAGE_ID; + url.pathname += "/photos"; + + const body = new FormData(); + body.set("published", "false"); + body.set("source", blob, path.basename(file)); + + Logger.trace('POST',url.href); + const res = await fetch(url,{ + method: 'POST', + headers: { + 'Accept': 'application/json', + 'Authorization': 'Bearer '+process.env.FAIRPOST_FACEBOOK_PAGE_ACCESS_TOKEN + }, + body + }); + + const result = await res.json(); + if (!result['id']) { + console.error(result); + throw new Error('No id returned when uploading photo'); + } + return result['id']; + + } + + private async postVideo( + file: string = '' + ): Promise { + + Logger.trace('Reading file',file); + const rawData = fs.readFileSync(file); + const blob = new Blob([rawData]); + + const url = new URL('https://graph.facebook.com'); + url.pathname = this.GRAPH_API_VERSION + "/" + process.env.FAIRPOST_FACEBOOK_PAGE_ID; + url.pathname += "/videos"; + + const body = new FormData(); + body.set("published", "false"); + body.set("source", blob, path.basename(file)); + + Logger.trace('POST',url.href); + const res = await fetch(url,{ + method: 'POST', + headers: { + 'Accept': 'application/json', + 'Authorization': 'Bearer '+process.env.FAIRPOST_FACEBOOK_PAGE_ACCESS_TOKEN + }, + body + }); + + const result = await res.json(); + if (!result['id']) { + console.error(result); + throw new Error('No id returned when uploading video'); + } + return result['id']; + } } \ No newline at end of file From ee2593cf894480460e9b7a0f7a6a2a622c1a2ec9 Mon Sep 17 00:00:00 2001 From: pike Date: Fri, 13 Oct 2023 22:26:12 +0200 Subject: [PATCH 12/25] feat: Facebook roundup and general code impro --- index.ts | 74 +++++++++++++++++---- src/Feed.ts | 64 +++++++++++++++++- src/platforms/AsFacebook.ts | 1 + src/platforms/AsInstagram.ts | 7 +- src/platforms/AsLinkedIn.ts | 3 +- src/platforms/AsReddit.ts | 2 +- src/platforms/AsTikTok.ts | 2 +- src/platforms/AsTwitter.ts | 4 +- src/platforms/AsYouTube.ts | 2 +- src/platforms/Ayrshare.ts | 7 +- src/platforms/Facebook.ts | 123 +++++++++++++++++++++++++++-------- 11 files changed, 234 insertions(+), 55 deletions(-) diff --git a/index.ts b/index.ts index a2f5e1d..7afb0ea 100644 --- a/index.ts +++ b/index.ts @@ -3,6 +3,7 @@ Fairpost cli handler */ +import * as path from 'path'; import Logger from './src/Logger'; import Feed from './src/Feed'; import { PostStatus } from './src/Post'; @@ -17,7 +18,9 @@ const CONFIG = (getOption('config') as string ) ?? '.env'; const DRY_RUN = !!getOption('dry-run') ?? false; const REPORT = (getOption('report') as string ) ?? 'text'; const PLATFORMS = (getOption('platforms') as string)?.split(',') as PlatformSlug[] ?? undefined; +const PLATFORM = (getOption('platform') as string) as PlatformSlug ?? undefined; const FOLDERS = (getOption('folders') as string)?.split(',') ?? undefined; +const FOLDER = (getOption('folder') as string) ?? undefined; const DATE = (getOption('date') as string) ?? undefined; const STATUS = (getOption('status') as PostStatus) ?? undefined; @@ -48,6 +51,11 @@ async function main() { result = feed; report = 'Feed: '+feed.path; break; + case 'get-platform': + const platform = feed.getPlatform(PLATFORM); + report += 'Platform: '+platform.slug+'\n'; + result = platform; + break; case 'get-platforms': const platforms = feed.getPlatforms(PLATFORMS); platforms.forEach(platform => { @@ -55,10 +63,19 @@ async function main() { }); result = platforms; break; + case 'test-platform': + result = await feed.testPlatform(PLATFORM); + report = "Result: \n"+ JSON.stringify(result,null,'\t'); + break; case 'test-platforms': result = await feed.testPlatforms(PLATFORMS); report = "Result: \n"+ JSON.stringify(result,null,'\t'); break; + case 'get-folder': + const folder = feed.getFolder(FOLDER); + report += 'Folder: '+folder.path+'\n'; + result = folder; + break; case 'get-folders': const folders = feed.getFolders(FOLDERS); folders.forEach(folder => { @@ -66,6 +83,11 @@ async function main() { }); result = folders; break; + case 'get-post': + const post = feed.getPost(FOLDER, PLATFORM); + report += post.report(); + result = post; + break; case 'get-posts': const allposts = feed.getPosts({ paths:FOLDERS, @@ -77,6 +99,11 @@ async function main() { }); result = allposts; break; + case 'prepare-post': + const preppost = await feed.preparePost(FOLDER,PLATFORM); + report += preppost.report(); + result = preppost; + break; case 'prepare-posts': const prepposts = await feed.preparePosts({ paths:FOLDERS, @@ -87,6 +114,21 @@ async function main() { }); result = prepposts; break; + case 'schedule-post': + const schedpost = feed.schedulePost( + FOLDER,PLATFORM, new Date(DATE), + ); + report += schedpost.report(); + result = schedpost; + break; + + case 'publish-post': + const pubpost = await feed.publishPost(FOLDER,PLATFORM, DRY_RUN); + report += pubpost.report(); + result = pubpost; + break; + + /* feed planning */ case 'schedule-next-posts': const nextposts = feed.scheduleNextPosts(DATE ? new Date(DATE): undefined,{ paths:FOLDERS, @@ -107,32 +149,38 @@ async function main() { }); result = nextposts; break; + + /* platform speific tools */ case 'facebook-get-page-token': const userToken = (getOption('user-token') as string ); - if (!userToken) { - throw new Error('Missing parameter: user-token'); - } const appUserId = (getOption('app-user-id') as string ); - if (!appUserId) { - throw new Error('Missing parameter: app-user-id'); - } const facebook = new Facebook(); result = await facebook.getPageToken(appUserId, userToken); report = 'Page Token: '+result; break; + default: - const cmd = process.argv[1]; + const cmd = path.basename(process.argv[1]); result = [ + 'basic commands:', `${cmd} help`, `${cmd} get-feed [--config=xxx]`, - `${cmd} test [--platforms=xxx,xxx]`, - `${cmd} get-platforms [--platforms=xxx,xxx]`, + `${cmd} test-platform --platform=xxx`, `${cmd} test-platforms [--platforms=xxx,xxx]`, + `${cmd} get-platform --platform=xxx`, + `${cmd} get-platforms [--platforms=xxx,xxx]`, + `${cmd} get-folder --folder=xxx`, `${cmd} get-folders [--folders=xxx,xxx]`, - `${cmd} prepare-posts [--platforms=xxx,xxx] [--folders=xxx,xxx]`, - `${cmd} get-posts [--status=xxx] [--platforms=xxx,xxx] [--folders=xxx,xxx]`, - `${cmd} schedule-next-post [--date=xxxx-xx-xx] [--platforms=xxx,xxx] [--folders=xxx,xxx]`, - `${cmd} publish-due-posts [--platforms=xxx,xxx] [--folders=xxx,xxx] [--dry-run]`, + `${cmd} get-post --folder=xxx --platform=xxx`, + `${cmd} get-posts [--status=xxx] [--folders=xxx,xxx] [--platforms=xxx,xxx] `, + `${cmd} prepare-post --folder=xxx --platform=xxx`, + `${cmd} prepare-posts [--folders=xxx,xxx] [--platforms=xxx,xxx]`, + `${cmd} schedule-post --folder=xxx --platform=xxx --date=xxxx-xx-xx `, + `${cmd} publish-post --folders=xxx --platforms=xxx [--dry-run]`, + '\nfeed planning:', + `${cmd} schedule-next-post [--date=xxxx-xx-xx] [--folders=xxx,xxx] [--platforms=xxx,xxx] `, + `${cmd} publish-due-posts [--folders=xxx,xxx] [--platforms=xxx,xxx] [--dry-run]`, + '\nplatform tools:', `${cmd} facebook-get-page-token --app-user-id=xxx --user-token=xxx` ]; result.forEach(line => report += '\n'+line); diff --git a/src/Feed.ts b/src/Feed.ts index 2a74a03..595d14b 100644 --- a/src/Feed.ts +++ b/src/Feed.ts @@ -48,12 +48,24 @@ export default class Feed { }); } + getPlatform(platform:PlatformSlug): Platform { + Logger.trace('Feed','getPlatform',platform); + return this.getPlatforms([platform])[0]; + } + getPlatforms(platforms?:PlatformSlug[]): Platform[] { Logger.trace('Feed','getPlatforms',platforms); return platforms?.map(platform=>this.platforms[platform]) ?? Object.values(this.platforms); } + async testPlatform(platform:PlatformSlug): Promise<{}> { + Logger.trace('Feed','testPlatform',platform); + const results = await this.testPlatforms([platform]); + return results[platform]; + } + async testPlatforms(platforms?:PlatformSlug[]): Promise<{ [slug:string] : {}}> { + Logger.trace('Feed','testPlatforms',platforms); const results = {}; for (const platform of this.getPlatforms(platforms)) { results[platform.slug] = await platform.test(); @@ -79,11 +91,21 @@ export default class Feed { return this.folders; } + getFolder(path: string): Folder | undefined { + Logger.trace('Feed','getFolder',path); + return this.getFolders([path])[0]; + } + getFolders(paths?: string[]): Folder[] { - Logger.trace('Feed','getFolders'); + Logger.trace('Feed','getFolders',paths); return paths?.map(path=>new Folder(this.path+'/'+path)) ?? this.getAllFolders(); } + getPost(path: string, platform: PlatformSlug): Post | undefined { + Logger.trace('Feed','getPost'); + return this.getPosts({paths:[path],platforms:[platform]})[0]; + } + getPosts(filters?: { paths?:string[] platforms?:PlatformSlug[], @@ -104,11 +126,16 @@ export default class Feed { return posts; } + async preparePost(path: string, platform: PlatformSlug): Promise { + Logger.trace('Feed','preparePost',path,platform); + return (await this.preparePosts({paths:[path],platforms:[platform]}))[0]; + } + async preparePosts(filters?: { paths?:string[] platforms?:PlatformSlug[] }): Promise { - Logger.trace('Feed','preparePosts'); + Logger.trace('Feed','preparePosts',filters); const posts: Post[] = []; const platforms = this.getPlatforms(filters?.platforms); const folders = this.getFolders(filters?.paths); @@ -126,7 +153,38 @@ export default class Feed { return posts; } - + schedulePost(path: string, platform: PlatformSlug, date: Date): Post { + Logger.trace('Feed','schedulePost'); + const post = this.getPost(path,platform); + if (!post.valid) { + throw new Error('Post is not valid'); + } + if (post.status!==PostStatus.UNSCHEDULED) { + throw new Error('Post is not unscheduled'); + } + post.schedule(date); + return post; + } + + async publishPost( + path:string, + slug:PlatformSlug, + dryrun:boolean = false + ): Promise { + Logger.trace('Feed','publishPost'); + const now = new Date(); + const post = this.getPost(path,slug); + this.schedulePost(path,slug,now); + const platform = this.getPlatform(slug); + console.log('Posting',slug,path); + await platform.publishPost(post,dryrun); + return post; + } + + /* + feed planning + */ + getLastPost(platform:PlatformSlug): Post | void { Logger.trace('Feed','getLastPost'); let lastPost: Post = undefined; diff --git a/src/platforms/AsFacebook.ts b/src/platforms/AsFacebook.ts index 17a1105..e17eaf0 100644 --- a/src/platforms/AsFacebook.ts +++ b/src/platforms/AsFacebook.ts @@ -1,4 +1,5 @@ +import Logger from '../Logger'; import Ayrshare from "./Ayrshare"; import { PlatformSlug } from "."; import Folder from "../Folder"; diff --git a/src/platforms/AsInstagram.ts b/src/platforms/AsInstagram.ts index 425943c..91c384b 100644 --- a/src/platforms/AsInstagram.ts +++ b/src/platforms/AsInstagram.ts @@ -1,4 +1,5 @@ +import Logger from '../Logger'; import Ayrshare from "./Ayrshare"; import { PlatformSlug } from "."; import Folder from "../Folder"; @@ -17,10 +18,10 @@ export default class AsInstagram extends Ayrshare { if (post) { // instagram: 1 video for reel if (post.files.video.length) { - console.log('Removing images for instagram reel..'); + Logger.trace('Removing images for instagram reel..'); post.files.image = []; if (post.files.video.length > 1) { - console.log('Using first video for instagram reel..'); + Logger.trace('Using first video for instagram reel..'); post.files.video = [post.files.video[0]]; } } @@ -28,7 +29,7 @@ export default class AsInstagram extends Ayrshare { for (const image of post.files.image) { const metadata = await sharp(post.folder.path+'/'+image).metadata(); if (metadata.width > 1440) { - console.log('Resizing '+image+' for instagram ..'); + Logger.trace('Resizing '+image+' for instagram ..'); await sharp(post.folder.path+'/'+image).resize({ width: 1440 }).toFile(post.folder.path+'/_instagram-'+image); diff --git a/src/platforms/AsLinkedIn.ts b/src/platforms/AsLinkedIn.ts index cd2fd6a..ab44b07 100644 --- a/src/platforms/AsLinkedIn.ts +++ b/src/platforms/AsLinkedIn.ts @@ -1,4 +1,5 @@ +import Logger from '../Logger'; import Ayrshare from "./Ayrshare"; import { PlatformSlug } from "."; import Folder from "../Folder"; @@ -27,7 +28,7 @@ export default class AsLinkedIn extends Ayrshare { for (const image of post.files.image) { var size = fs.statSync(post.folder.path+'/'+image).size / (1024*1024); if (size>=5) { - console.log('Resizing '+image+' for linkedin ..'); + Logger.trace('Resizing '+image+' for linkedin ..'); await sharp(post.folder.path+'/'+image).resize({ width: 1200 }).toFile(post.folder.path+'/_linkedin-'+image); diff --git a/src/platforms/AsReddit.ts b/src/platforms/AsReddit.ts index fa80d19..e0c2577 100644 --- a/src/platforms/AsReddit.ts +++ b/src/platforms/AsReddit.ts @@ -1,4 +1,4 @@ - +import Logger from '../Logger'; import Ayrshare from "./Ayrshare"; import { PlatformSlug } from "."; import Folder from "../Folder"; diff --git a/src/platforms/AsTikTok.ts b/src/platforms/AsTikTok.ts index 12cd2a3..aa1e871 100644 --- a/src/platforms/AsTikTok.ts +++ b/src/platforms/AsTikTok.ts @@ -1,4 +1,4 @@ - +import Logger from '../Logger'; import Ayrshare from "./Ayrshare"; import { PlatformSlug } from "."; import Folder from "../Folder"; diff --git a/src/platforms/AsTwitter.ts b/src/platforms/AsTwitter.ts index 9a35071..4db6804 100644 --- a/src/platforms/AsTwitter.ts +++ b/src/platforms/AsTwitter.ts @@ -1,4 +1,4 @@ - +import Logger from '../Logger'; import Ayrshare from "./Ayrshare"; import { PlatformSlug } from "."; import Folder from "../Folder"; @@ -26,7 +26,7 @@ export default class AsTwitter extends Ayrshare { for (const image of post.files.image) { var size = fs.statSync(post.folder.path+'/'+image).size / (1024*1024); if (size>=5) { - console.log('Resizing '+image+' for twitter ..'); + Logger.trace('Resizing '+image+' for twitter ..'); await sharp(post.folder.path+'/'+image).resize({ width: 1200 }).toFile(post.folder.path+'/_twitter-'+image); diff --git a/src/platforms/AsYouTube.ts b/src/platforms/AsYouTube.ts index e74513c..9cdf0ef 100644 --- a/src/platforms/AsYouTube.ts +++ b/src/platforms/AsYouTube.ts @@ -1,4 +1,4 @@ - +import Logger from '../Logger'; import Ayrshare from "./Ayrshare"; import { PlatformSlug } from "."; import Folder from "../Folder"; diff --git a/src/platforms/Ayrshare.ts b/src/platforms/Ayrshare.ts index 7d0e7b1..3f2d125 100644 --- a/src/platforms/Ayrshare.ts +++ b/src/platforms/Ayrshare.ts @@ -1,4 +1,5 @@ +import Logger from '../Logger'; import * as fs from 'fs'; import * as path from 'path'; import { randomUUID } from 'crypto'; @@ -80,7 +81,7 @@ export default abstract class Ayrshare extends Platform { const ext = path.extname(file); const basename = path.basename(file, ext); const uname = basename+'-'+randomUUID()+ext; - console.log('fetching uploadid...',file); + Logger.trace('fetching uploadid...',file); const res1 = await fetch("https://app.ayrshare.com/api/media/uploadUrl?fileName="+uname+"&contentType="+ext.substring(1), { method: "GET", headers: { @@ -94,7 +95,7 @@ export default abstract class Ayrshare extends Platform { const data = await res1.json(); //console.log(data); - console.log('uploading..',uname); + Logger.trace('uploading..',uname); const uploadUrl = data.uploadUrl; const contentType = data.contentType; const accessUrl = data.accessUrl; @@ -162,7 +163,7 @@ export default abstract class Ayrshare extends Platform { scheduleDate: scheduleDate, requiresApproval: this.requiresApproval }); - console.log('scheduling...',postPlatform); + Logger.trace('scheduling...',postPlatform); //console.log(body); const res = await fetch("https://app.ayrshare.com/api/post", { method: "POST", diff --git a/src/platforms/Facebook.ts b/src/platforms/Facebook.ts index f231795..462fa89 100644 --- a/src/platforms/Facebook.ts +++ b/src/platforms/Facebook.ts @@ -4,6 +4,7 @@ import Platform from "../Platform"; import { PlatformSlug } from "."; import Folder from "../Folder"; import Post from "../Post"; +import { PostStatus } from "../Post"; import * as fs from 'fs'; import * as path from 'path'; import * as sharp from 'sharp'; @@ -19,11 +20,16 @@ export default class Facebook extends Platform { async preparePost(folder: Folder): Promise { const post = await super.preparePost(folder); if (post && post.files) { - // facebook : max 10mb images + // facebook: video post can only contain 1 video + if (post.files.video.length) { + post.files.video.length = 1; + post.files.image = []; + } + // facebook : max 4mb images for (const image of post.files.image) { var size = fs.statSync(post.folder.path+'/'+image).size / (1024*1024); - if (size>=10) { - console.log('Resizing '+image+' for facebook ..'); + if (size>=4) { + Logger.trace('Resizing '+image+' for facebook ..'); await sharp(post.folder.path+'/'+image).resize({ width: 1200 }).toFile(post.folder.path+'/_facebook-'+image); @@ -37,7 +43,42 @@ export default class Facebook extends Platform { } async publishPost(post: Post, dryrun:boolean = false): Promise { - return super.publishPost(post,dryrun); + + let result = dryrun ? { id: '-99' } : {} as {id: string}; + + if (post.files.video) { + if (!dryrun) { + result = await this.publishVideo(post.files.video[0],post.title,post.body); + } + } else { + const attachments = []; + if (post.files.image.length) { + for (const image of post.files.image) { + attachments.push({"media_fbid": this.uploadPhoto(post.folder.path+'/'+image)}); + } + } + if (!dryrun) { + result = await this.post( + 'feed', + { + "message":post.body, + "published":process.env.FAIRPOST_FACEBOOK_PUBLISH_POSTS, + "scheduled_publish_time":"tomorrow", + "attached_media": attachments + } + ); + } + } + + post.results.push(result); + if (result.id) { + post.status = PostStatus.PUBLISHED; + } else { + console.error(this.slug,"No id returned in post",result); + } + post.save(); + return !!result.id; + } async test() { @@ -107,13 +148,21 @@ export default class Facebook extends Platform { return llPageAccessToken; } + /* + * Do a GET request on the page. + * + * arguments: + * endpoint: the path to call + * query: query string as object + */ + private async get( - call: string = '', + endpoint: string = '', query: { [key:string]: string } = {} ) { const url = new URL('https://graph.facebook.com'); url.pathname = this.GRAPH_API_VERSION + "/" + process.env.FAIRPOST_FACEBOOK_PAGE_ID; - url.pathname += "/" + call, + url.pathname += "/" + endpoint, url.search = new URLSearchParams(query).toString(); Logger.trace('GET',url.href); const res = await fetch(url,{ @@ -129,25 +178,21 @@ export default class Facebook extends Platform { return result; } - private async testPost() { - return this.post( - 'feed', - { - "message":"test", - "link":"https://test.com", - "published":"false", - "scheduled_publish_time":"tomorrow", - } - ); - } + /* + * Do a POST request on the page. + * + * arguments: + * endpoint: the path to call + * body: body as object + */ private async post( - call: string = '', + endpoint: string = '', body = {} ) { const url = new URL('https://graph.facebook.com'); url.pathname = this.GRAPH_API_VERSION + "/" + process.env.FAIRPOST_FACEBOOK_PAGE_ID; - url.pathname += "/" + call, + url.pathname += "/" + endpoint, Logger.trace('POST',url.href); const res = await fetch(url,{ method: 'POST', @@ -163,8 +208,18 @@ export default class Facebook extends Platform { } - private async postImage( - file: string = '' + /* + * POST an image to the /photos endpoint using multipart/form-data + * + * arguments: + * file: path to the file to post + * + * returns: + * id of the uploaded photo to use in post attachments + */ + private async uploadPhoto( + file: string = '', + published = false ): Promise { Logger.trace('Reading file',file); @@ -176,7 +231,7 @@ export default class Facebook extends Platform { url.pathname += "/photos"; const body = new FormData(); - body.set("published", "false"); + body.set("published", published? "true":"false"); body.set("source", blob, path.basename(file)); Logger.trace('POST',url.href); @@ -198,9 +253,21 @@ export default class Facebook extends Platform { } - private async postVideo( - file: string = '' - ): Promise { + /* + * POST a video to the page using multipart/form-data + * + * arguments: + * file: path to the video to post + * published: wether to publish it or not + * + * returns: + * { id: string } + */ + private async publishVideo( + file: string, + title: string, + description: string + ): Promise<{ id: string }> { Logger.trace('Reading file',file); const rawData = fs.readFileSync(file); @@ -211,7 +278,9 @@ export default class Facebook extends Platform { url.pathname += "/videos"; const body = new FormData(); - body.set("published", "false"); + body.set("title", title); + body.set("description",description); + body.set("published", process.env.FAIRPOST_FACEBOOK_PUBLISH_POSTS); body.set("source", blob, path.basename(file)); Logger.trace('POST',url.href); @@ -229,7 +298,7 @@ export default class Facebook extends Platform { console.error(result); throw new Error('No id returned when uploading video'); } - return result['id']; + return result; } From b998cf2f8e53b4bade5793d4189c2a1549a70f3f Mon Sep 17 00:00:00 2001 From: pike Date: Fri, 13 Oct 2023 23:44:36 +0200 Subject: [PATCH 13/25] feat: Facebook et al tested --- .env.dist | 15 ++++- README.md | 42 ++++++++---- index.ts | 36 ++++++++-- src/Feed.ts | 66 ++++++++++++++++-- src/platforms/Facebook.ts | 138 ++++++++++++++++++++------------------ 5 files changed, 203 insertions(+), 94 deletions(-) diff --git a/.env.dist b/.env.dist index 3885c58..a496be8 100644 --- a/.env.dist +++ b/.env.dist @@ -1,7 +1,18 @@ FAIRPOST_FEED_PATH=feed FAIRPOST_FEED_INTERVAL=6 #days -FAIRPOST_FEED_PLATFORMS=asyoutube,asfacebook,aslinkedin,asinstagram,astiktok,asreddit,astwitter +FAIRPOST_FEED_PLATFORMS= +# FAIRPOST_FEED_PLATFORMS=facebook,asyoutube,asfacebook,aslinkedin,asinstagram,astiktok,asreddit,astwitter + +# AYRSHARE FAIRPOST_AYRSHARE_API_KEY=xxxx -FAIRPOST_REDDIT_SUBREDDIT=generative \ No newline at end of file +FAIRPOST_REDDIT_SUBREDDIT=generative + + +# facebook +# FAIRPOST_FACEBOOK_APP_ID=xxx +# FAIRPOST_FACEBOOK_APP_SECRET=xxx +# FAIRPOST_FACEBOOK_PAGE_ID=xxx +# FAIRPOST_FACEBOOK_PAGE_ACCESS_TOKEN=xxx +# FAIRPOST_FACEBOOK_PUBLISH_POSTS=true \ No newline at end of file diff --git a/README.md b/README.md index 4a60dcd..61a1bfe 100644 --- a/README.md +++ b/README.md @@ -71,32 +71,50 @@ fairpost.js publish-due-posts This will publish any scheduled posts that are past their due date. -## Arguments +## Other commands -Each of these commands (and others) accept `--arguments` +Other commands accept `--arguments` that may help you, for example, to immediately publish a certain post to a certain platform if you like. But more commonly, you would call this script -every day and just add posts to the feed folder as -time goes by. -The script will then automatically prepare these posts, +every day. +The script will then automatically prepare the posts, schedule the next post using a certain interval, publish any post when it is due, and schedule the -next post automatically. +next post automatically. All you have to do is +add folders with content. ## Cli ``` -fairpost.js help -fairpost.js get-feed +# basic commands: +# basic commands: +fairpost.js help +fairpost.js get-feed [--config=xxx] +fairpost.js test-platform --platform=xxx +fairpost.js test-platforms [--platforms=xxx,xxx] +fairpost.js get-platform --platform=xxx +fairpost.js get-platforms [--platforms=xxx,xxx] +fairpost.js get-folder --folder=xxx fairpost.js get-folders [--folders=xxx,xxx] -fairpost.js prepare-posts [--platforms=xxx,xxx] [--folders=xxx,xxx] -fairpost.js get-posts [--status=xxx] [--platforms=xxx,xxx] [--folders=xxx,xxx] -fairpost.js schedule-next-post [--date=xxxx-xx-xx] [--platforms=xxx,xxx] [--folders=xxx,xxx] -fairpost.js publish-due-posts [--platforms=xxx,xxx] [--folders=xxx,xxx] [--dry-run] +fairpost.js get-post --folder=xxx --platform=xxx +fairpost.js get-posts [--status=xxx] [--folders=xxx,xxx] [--platforms=xxx,xxx] +fairpost.js prepare-post --folder=xxx --platform=xxx +fairpost.js prepare-posts [--folders=xxx,xxx] [--platforms=xxx,xxx] +fairpost.js schedule-post --folder=xxx --platform=xxx --date=xxxx-xx-xx +fairpost.js schedule-posts [--folders=xxx,xxx] [--platforms=xxx,xxx] --date=xxxx-xx-xx +fairpost.js publish-post --folders=xxx --platforms=xxx [--dry-run] +fairpost.js publish-posts [--folders=xxx,xxx] [--platforms=xxx,xxx] + +# feed planning: +fairpost.js schedule-next-post [--date=xxxx-xx-xx] [--folders=xxx,xxx] [--platforms=xxx,xxx] +fairpost.js publish-due-posts [--folders=xxx,xxx] [--platforms=xxx,xxx] [--dry-run] + +# platform tools: +fairpost.js facebook-get-page-token --app-user-id=xxx --user-token=xxx ``` ### Common arguments diff --git a/index.ts b/index.ts index 7afb0ea..052d22d 100644 --- a/index.ts +++ b/index.ts @@ -121,12 +121,32 @@ async function main() { report += schedpost.report(); result = schedpost; break; - + case 'schedule-posts': + const schedposts = feed.schedulePosts({ + paths: FOLDERS, + platforms: PLATFORMS + }, new Date(DATE)); + schedposts.forEach(post => { + report += post.report(); + }); + result = schedposts; + break; case 'publish-post': const pubpost = await feed.publishPost(FOLDER,PLATFORM, DRY_RUN); report += pubpost.report(); result = pubpost; break; + + case 'publish-posts': + const pubposts = await feed.publishPosts({ + paths:FOLDERS, + platforms:PLATFORMS + }, DRY_RUN); + pubposts.forEach(post => { + report += post.report(); + }); + result = pubposts; + break; /* feed planning */ case 'schedule-next-posts': @@ -140,17 +160,17 @@ async function main() { result = nextposts; break; case 'publish-due-posts': - const pubposts = await feed.publishDuePosts({ + const dueposts = await feed.publishDuePosts({ paths:FOLDERS, platforms:PLATFORMS }, DRY_RUN); pubposts.forEach(post => { report += post.report(); }); - result = nextposts; + result = dueposts; break; - /* platform speific tools */ + /* platform specific tools */ case 'facebook-get-page-token': const userToken = (getOption('user-token') as string ); const appUserId = (getOption('app-user-id') as string ); @@ -162,7 +182,7 @@ async function main() { default: const cmd = path.basename(process.argv[1]); result = [ - 'basic commands:', + '# basic commands:', `${cmd} help`, `${cmd} get-feed [--config=xxx]`, `${cmd} test-platform --platform=xxx`, @@ -176,11 +196,13 @@ async function main() { `${cmd} prepare-post --folder=xxx --platform=xxx`, `${cmd} prepare-posts [--folders=xxx,xxx] [--platforms=xxx,xxx]`, `${cmd} schedule-post --folder=xxx --platform=xxx --date=xxxx-xx-xx `, + `${cmd} schedule-posts [--folders=xxx,xxx] [--platforms=xxx,xxx] --date=xxxx-xx-xx`, `${cmd} publish-post --folders=xxx --platforms=xxx [--dry-run]`, - '\nfeed planning:', + `${cmd} publish-posts [--folders=xxx,xxx] [--platforms=xxx,xxx]`, + '\n# feed planning:', `${cmd} schedule-next-post [--date=xxxx-xx-xx] [--folders=xxx,xxx] [--platforms=xxx,xxx] `, `${cmd} publish-due-posts [--folders=xxx,xxx] [--platforms=xxx,xxx] [--dry-run]`, - '\nplatform tools:', + '\n# platform tools:', `${cmd} facebook-get-page-token --app-user-id=xxx --user-token=xxx` ]; result.forEach(line => report += '\n'+line); diff --git a/src/Feed.ts b/src/Feed.ts index 595d14b..ff523cb 100644 --- a/src/Feed.ts +++ b/src/Feed.ts @@ -154,7 +154,7 @@ export default class Feed { } schedulePost(path: string, platform: PlatformSlug, date: Date): Post { - Logger.trace('Feed','schedulePost'); + Logger.trace('Feed','schedulePost',path,platform,date); const post = this.getPost(path,platform); if (!post.valid) { throw new Error('Post is not valid'); @@ -166,21 +166,75 @@ export default class Feed { return post; } + schedulePosts(filters: { + paths?:string[] + platforms?:PlatformSlug[] + }, date: Date): Post[] { + Logger.trace('Feed','schedulePosts',filters,date); + const posts: Post[] = []; + const platforms = this.getPlatforms(filters?.platforms); + const folders = this.getFolders(filters?.paths); + for (const platform of platforms) { + for (const folder of folders) { + const post = platform.getPost(folder); + if (!post.valid) { + throw new Error('Post is not valid'); + } + if (post.status!==PostStatus.UNSCHEDULED) { + throw new Error('Post is not unscheduled'); + } + post.schedule(date); + posts.push(post); + } + } + return posts; + } + async publishPost( path:string, slug:PlatformSlug, dryrun:boolean = false ): Promise { - Logger.trace('Feed','publishPost'); + Logger.trace('Feed','publishPost',path,slug,dryrun); const now = new Date(); - const post = this.getPost(path,slug); - this.schedulePost(path,slug,now); const platform = this.getPlatform(slug); - console.log('Posting',slug,path); - await platform.publishPost(post,dryrun); + const folder = this.getFolder(path); + const post = platform.getPost(folder); + if (post.valid) { + post.schedule(now); + Logger.trace('Posting',slug,path); + await platform.publishPost(post,dryrun); + } else { + throw new Error('Post is not valid'); + } return post; } + async publishPosts(filters?: { + paths?:string[] + platforms?:PlatformSlug[] + }, dryrun:boolean = false): Promise { + Logger.trace('Feed','publishPosts',filters,dryrun); + const now = new Date(); + const posts: Post[] = []; + const platforms = this.getPlatforms(filters?.platforms); + const folders = this.getFolders(filters?.paths); + for (const platform of platforms) { + for (const folder of folders) { + const post = platform.getPost(folder); + if (post.valid) { + post.schedule(now); + Logger.trace('Posting',platform.slug,folder.path); + await platform.publishPost(post,dryrun); + posts.push(post); + } else { + Logger.warn('Skipping invalid post',platform.slug,folder.path); + } + } + } + return posts; + } + /* feed planning */ diff --git a/src/platforms/Facebook.ts b/src/platforms/Facebook.ts index 462fa89..aac7b27 100644 --- a/src/platforms/Facebook.ts +++ b/src/platforms/Facebook.ts @@ -46,7 +46,7 @@ export default class Facebook extends Platform { let result = dryrun ? { id: '-99' } : {} as {id: string}; - if (post.files.video) { + if (post.files.video.length) { if (!dryrun) { result = await this.publishVideo(post.files.video[0],post.title,post.body); } @@ -54,7 +54,7 @@ export default class Facebook extends Platform { const attachments = []; if (post.files.image.length) { for (const image of post.files.image) { - attachments.push({"media_fbid": this.uploadPhoto(post.folder.path+'/'+image)}); + attachments.push({"media_fbid": await this.uploadPhoto(post.folder.path+'/'+image)}); } } if (!dryrun) { @@ -63,7 +63,7 @@ export default class Facebook extends Platform { { "message":post.body, "published":process.env.FAIRPOST_FACEBOOK_PUBLISH_POSTS, - "scheduled_publish_time":"tomorrow", + //"scheduled_publish_time":"tomorrow", "attached_media": attachments } ); @@ -72,7 +72,9 @@ export default class Facebook extends Platform { post.results.push(result); if (result.id) { - post.status = PostStatus.PUBLISHED; + if (!dryrun) { + post.status = PostStatus.PUBLISHED; + } } else { console.error(this.slug,"No id returned in post",result); } @@ -85,69 +87,7 @@ export default class Facebook extends Platform { return this.get(); } - /* - * Return a long lived page access token. - * - * UserAccessToken: a shortlived user access token - */ - async getPageToken(appUserId: string, userAccessToken :string): Promise { - - // get a long lived UserAccessToken - - const url = new URL('https://graph.facebook.com'); - url.pathname = this.GRAPH_API_VERSION + "/oauth/access_token"; - const query = { - grant_type : "fb_exchange_token", - client_id : process.env.FAIRPOST_FACEBOOK_APP_ID, - client_secret : process.env.FAIRPOST_FACEBOOK_APP_SECRET, - fb_exchange_token : userAccessToken - }; - url.search = new URLSearchParams(query).toString(); - - Logger.trace('fetching',url.href); - const res1 = await fetch(url,{ - method: 'GET', - headers: { 'Accept': 'application/json'}, - }); - const data1 = await res1.json(); - const llUserAccessToken = data1['access_token']; - - if (!llUserAccessToken) { - console.error(data1); - throw new Error('No llUserAccessToken access_token in response.'); - } - - // get a long lived PageAccessToken - - const url2 = new URL('https://graph.facebook.com'); - url2.pathname = appUserId + "/accounts"; - const query2 = { - access_token : llUserAccessToken - }; - url2.search = new URLSearchParams(query2).toString(); - Logger.trace('fetching',url.href); - const res2 = await fetch(url2,{ - method: 'GET', - headers: { 'Accept': 'application/json'}, - }); - const data2 = await res2.json(); - - let llPageAccessToken = ''; - if (data2.data) { - data2.data.forEach(page=> { - if (page.id===process.env.FAIRPOST_FACEBOOK_PAGE_ID) { - llPageAccessToken = page.access_token; - } - }) - } - if (!llPageAccessToken) { - console.error(data2); - throw new Error('No llPageAccessToken for page '+process.env.FAIRPOST_FACEBOOK_PAGE_ID+' in response.'); - } - - return llPageAccessToken; - } - + /* * Do a GET request on the page. * @@ -302,4 +242,68 @@ export default class Facebook extends Platform { } + /* + * Return a long lived page access token. + * + * UserAccessToken: a shortlived user access token + */ + async getPageToken(appUserId: string, userAccessToken :string): Promise { + + // get a long lived UserAccessToken + + const url = new URL('https://graph.facebook.com'); + url.pathname = this.GRAPH_API_VERSION + "/oauth/access_token"; + const query = { + grant_type : "fb_exchange_token", + client_id : process.env.FAIRPOST_FACEBOOK_APP_ID, + client_secret : process.env.FAIRPOST_FACEBOOK_APP_SECRET, + fb_exchange_token : userAccessToken + }; + url.search = new URLSearchParams(query).toString(); + + Logger.trace('fetching',url.href); + const res1 = await fetch(url,{ + method: 'GET', + headers: { 'Accept': 'application/json'}, + }); + const data1 = await res1.json(); + const llUserAccessToken = data1['access_token']; + + if (!llUserAccessToken) { + console.error(data1); + throw new Error('No llUserAccessToken access_token in response.'); + } + + // get a long lived PageAccessToken + + const url2 = new URL('https://graph.facebook.com'); + url2.pathname = appUserId + "/accounts"; + const query2 = { + access_token : llUserAccessToken + }; + url2.search = new URLSearchParams(query2).toString(); + Logger.trace('fetching',url.href); + const res2 = await fetch(url2,{ + method: 'GET', + headers: { 'Accept': 'application/json'}, + }); + const data2 = await res2.json(); + + let llPageAccessToken = ''; + if (data2.data) { + data2.data.forEach(page=> { + if (page.id===process.env.FAIRPOST_FACEBOOK_PAGE_ID) { + llPageAccessToken = page.access_token; + } + }) + } + if (!llPageAccessToken) { + console.error(data2); + throw new Error('No llPageAccessToken for page '+process.env.FAIRPOST_FACEBOOK_PAGE_ID+' in response.'); + } + + return llPageAccessToken; + } + + } \ No newline at end of file From 79926f76f7ababa1b7d3b086d92a4f77e9d55b71 Mon Sep 17 00:00:00 2001 From: pike Date: Fri, 13 Oct 2023 23:50:42 +0200 Subject: [PATCH 14/25] feat: Update doc --- README.md | 3 +-- index.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 61a1bfe..ef83979 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,6 @@ add folders with content. ``` # basic commands: -# basic commands: fairpost.js help fairpost.js get-feed [--config=xxx] fairpost.js test-platform --platform=xxx @@ -103,13 +102,13 @@ fairpost.js get-folders [--folders=xxx,xxx] fairpost.js get-post --folder=xxx --platform=xxx fairpost.js get-posts [--status=xxx] [--folders=xxx,xxx] [--platforms=xxx,xxx] fairpost.js prepare-post --folder=xxx --platform=xxx -fairpost.js prepare-posts [--folders=xxx,xxx] [--platforms=xxx,xxx] fairpost.js schedule-post --folder=xxx --platform=xxx --date=xxxx-xx-xx fairpost.js schedule-posts [--folders=xxx,xxx] [--platforms=xxx,xxx] --date=xxxx-xx-xx fairpost.js publish-post --folders=xxx --platforms=xxx [--dry-run] fairpost.js publish-posts [--folders=xxx,xxx] [--platforms=xxx,xxx] # feed planning: +fairpost.js prepare-posts [--folders=xxx,xxx] [--platforms=xxx,xxx] fairpost.js schedule-next-post [--date=xxxx-xx-xx] [--folders=xxx,xxx] [--platforms=xxx,xxx] fairpost.js publish-due-posts [--folders=xxx,xxx] [--platforms=xxx,xxx] [--dry-run] diff --git a/index.ts b/index.ts index 052d22d..389d4c1 100644 --- a/index.ts +++ b/index.ts @@ -194,12 +194,12 @@ async function main() { `${cmd} get-post --folder=xxx --platform=xxx`, `${cmd} get-posts [--status=xxx] [--folders=xxx,xxx] [--platforms=xxx,xxx] `, `${cmd} prepare-post --folder=xxx --platform=xxx`, - `${cmd} prepare-posts [--folders=xxx,xxx] [--platforms=xxx,xxx]`, `${cmd} schedule-post --folder=xxx --platform=xxx --date=xxxx-xx-xx `, `${cmd} schedule-posts [--folders=xxx,xxx] [--platforms=xxx,xxx] --date=xxxx-xx-xx`, `${cmd} publish-post --folders=xxx --platforms=xxx [--dry-run]`, `${cmd} publish-posts [--folders=xxx,xxx] [--platforms=xxx,xxx]`, '\n# feed planning:', + `${cmd} prepare-posts [--folders=xxx,xxx] [--platforms=xxx,xxx]`, `${cmd} schedule-next-post [--date=xxxx-xx-xx] [--folders=xxx,xxx] [--platforms=xxx,xxx] `, `${cmd} publish-due-posts [--folders=xxx,xxx] [--platforms=xxx,xxx] [--dry-run]`, '\n# platform tools:', From 1c2a75795d30b4c62a0de09c144507c8075ff684 Mon Sep 17 00:00:00 2001 From: pike Date: Sat, 14 Oct 2023 09:13:26 +0200 Subject: [PATCH 15/25] feat: Change slug to id --- README.md | 2 +- index.ts | 4 ++-- src/Feed.ts | 28 ++++++++++++++-------------- src/Platform.ts | 6 +++--- src/Post.ts | 2 +- src/platforms/AsFacebook.ts | 2 +- src/platforms/AsInstagram.ts | 2 +- src/platforms/AsLinkedIn.ts | 2 +- src/platforms/AsReddit.ts | 2 +- src/platforms/AsTikTok.ts | 2 +- src/platforms/AsTwitter.ts | 2 +- src/platforms/AsYouTube.ts | 2 +- src/platforms/Ayrshare.ts | 6 +++--- src/platforms/Facebook.ts | 4 ++-- 14 files changed, 33 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index ef83979..c1dc47c 100644 --- a/README.md +++ b/README.md @@ -135,7 +135,7 @@ extending `src/classes/Platform`. You want to override at least the method `preparePost(folder: Folder)` and `publishPost(post: Post, dryrun:boolean = false)`. -Then add a slug for your platform to `src/platforms/index.js` and +Then add a platformId for your platform to `src/platforms/index.js` and enable your platform in your `.env`. diff --git a/index.ts b/index.ts index 389d4c1..f4084de 100644 --- a/index.ts +++ b/index.ts @@ -53,13 +53,13 @@ async function main() { break; case 'get-platform': const platform = feed.getPlatform(PLATFORM); - report += 'Platform: '+platform.slug+'\n'; + report += 'Platform: '+platform.id+'\n'; result = platform; break; case 'get-platforms': const platforms = feed.getPlatforms(PLATFORMS); platforms.forEach(platform => { - report += 'Platform: '+platform.slug+'\n'; + report += 'Platform: '+platform.id+'\n'; }); result = platforms; break; diff --git a/src/Feed.ts b/src/Feed.ts index ff523cb..396b7b1 100644 --- a/src/Feed.ts +++ b/src/Feed.ts @@ -14,7 +14,7 @@ export default class Feed { path: string = ''; platforms: { - [slug in PlatformSlug]? : Platform; + [id in PlatformSlug]? : Platform; } = {}; folders: Folder[] = []; interval: number; @@ -33,16 +33,16 @@ export default class Feed { } this.interval = Number(process.env.FAIRPOST_FEED_INTERVAL ?? 7); - const activePlatformSlugs = process.env.FAIRPOST_FEED_PLATFORMS.split(','); + const activePlatformIds = process.env.FAIRPOST_FEED_PLATFORMS.split(','); const platformClasses = fs.readdirSync(path.resolve(__dirname+'/platforms')); platformClasses.forEach(file=> { const constructor = file.replace('.ts','').replace('.js',''); // nb import * as platforms loaded the constructors if (platforms[constructor] !== undefined) { const platform = new platforms[constructor](); - platform.active = activePlatformSlugs.includes(platform.slug); + platform.active = activePlatformIds.includes(platform.id); if (platform.active) { - this.platforms[platform.slug] = platform; + this.platforms[platform.id] = platform; } } }); @@ -64,11 +64,11 @@ export default class Feed { return results[platform]; } - async testPlatforms(platforms?:PlatformSlug[]): Promise<{ [slug:string] : {}}> { + async testPlatforms(platforms?:PlatformSlug[]): Promise<{ [id:string] : {}}> { Logger.trace('Feed','testPlatforms',platforms); const results = {}; for (const platform of this.getPlatforms(platforms)) { - results[platform.slug] = await platform.test(); + results[platform.id] = await platform.test(); } return results; } @@ -192,17 +192,17 @@ export default class Feed { async publishPost( path:string, - slug:PlatformSlug, + id:PlatformSlug, dryrun:boolean = false ): Promise { - Logger.trace('Feed','publishPost',path,slug,dryrun); + Logger.trace('Feed','publishPost',path,id,dryrun); const now = new Date(); - const platform = this.getPlatform(slug); + const platform = this.getPlatform(id); const folder = this.getFolder(path); const post = platform.getPost(folder); if (post.valid) { post.schedule(now); - Logger.trace('Posting',slug,path); + Logger.trace('Posting',id,path); await platform.publishPost(post,dryrun); } else { throw new Error('Post is not valid'); @@ -224,11 +224,11 @@ export default class Feed { const post = platform.getPost(folder); if (post.valid) { post.schedule(now); - Logger.trace('Posting',platform.slug,folder.path); + Logger.trace('Posting',platform.id,folder.path); await platform.publishPost(post,dryrun); posts.push(post); } else { - Logger.warn('Skipping invalid post',platform.slug,folder.path); + Logger.warn('Skipping invalid post',platform.id,folder.path); } } } @@ -277,7 +277,7 @@ export default class Feed { const platforms = this.getPlatforms(filters?.platforms); const folders = this.getFolders(filters?.paths); for (const platform of platforms) { - const nextDate = date?date:this.getNextPostDate(platform.slug); + const nextDate = date?date:this.getNextPostDate(platform.id); for (const folder of folders) { const post = platform.getPost(folder); if (post.valid && post?.status===PostStatus.UNSCHEDULED) { @@ -305,7 +305,7 @@ export default class Feed { const post = platform.getPost(folder); if (post?.status===PostStatus.SCHEDULED) { if (post.scheduled <= now) { - console.log('Posting',platform.slug,folder.path); + console.log('Posting',platform.id,folder.path); await platform.publishPost(post,dryrun); posts.push(post); break; diff --git a/src/Platform.ts b/src/Platform.ts index 1e3e180..320935b 100644 --- a/src/Platform.ts +++ b/src/Platform.ts @@ -8,7 +8,7 @@ import { PlatformSlug } from "./platforms"; export default class Platform { active: boolean = false; - slug: PlatformSlug = PlatformSlug.UNKNOWN; + id: PlatformSlug = PlatformSlug.UNKNOWN; defaultBody: string = "Fairpost feed"; /* @@ -18,7 +18,7 @@ export default class Platform { * platform to be saved in this folder. */ getPostFileName() { - return '_'+this.slug+'.json'; + return '_'+this.id+'.json'; } /* @@ -116,7 +116,7 @@ export default class Platform { Logger.trace('Platform','publishPost'); post.posted = new Date(); post.results.push({ - error: 'publishing not implemented for '+this.slug + error: 'publishing not implemented for '+this.id }); post.status = PostStatus.FAILED; post.save(); diff --git a/src/Post.ts b/src/Post.ts index 1fd6042..840acbe 100644 --- a/src/Post.ts +++ b/src/Post.ts @@ -61,7 +61,7 @@ export default class Post { report(): string { Logger.trace('Post','report'); let report = ''; - report += '\nPost: '+this.platform.slug+' : '+this.folder.path; + report += '\nPost: '+this.platform.id+' : '+this.folder.path; report += '\n - valid: '+this.valid; report += '\n - status: '+this.status; report += '\n - scheduled: '+this.scheduled; diff --git a/src/platforms/AsFacebook.ts b/src/platforms/AsFacebook.ts index e17eaf0..e382911 100644 --- a/src/platforms/AsFacebook.ts +++ b/src/platforms/AsFacebook.ts @@ -8,7 +8,7 @@ import * as fs from 'fs'; import * as sharp from 'sharp'; export default class AsFacebook extends Ayrshare { - slug: PlatformSlug = PlatformSlug.ASFACEBOOK; + id: PlatformSlug = PlatformSlug.ASFACEBOOK; constructor() { super(); diff --git a/src/platforms/AsInstagram.ts b/src/platforms/AsInstagram.ts index 91c384b..007e213 100644 --- a/src/platforms/AsInstagram.ts +++ b/src/platforms/AsInstagram.ts @@ -7,7 +7,7 @@ import Post from "../Post"; import * as sharp from 'sharp'; export default class AsInstagram extends Ayrshare { - slug = PlatformSlug.ASINSTAGRAM; + id = PlatformSlug.ASINSTAGRAM; constructor() { super(); diff --git a/src/platforms/AsLinkedIn.ts b/src/platforms/AsLinkedIn.ts index ab44b07..fafb6fc 100644 --- a/src/platforms/AsLinkedIn.ts +++ b/src/platforms/AsLinkedIn.ts @@ -8,7 +8,7 @@ import * as fs from 'fs'; import * as sharp from 'sharp'; export default class AsLinkedIn extends Ayrshare { - slug = PlatformSlug.ASLINKEDIN; + id = PlatformSlug.ASLINKEDIN; constructor() { super(); diff --git a/src/platforms/AsReddit.ts b/src/platforms/AsReddit.ts index e0c2577..36149b0 100644 --- a/src/platforms/AsReddit.ts +++ b/src/platforms/AsReddit.ts @@ -5,7 +5,7 @@ import Folder from "../Folder"; import Post from "../Post"; export default class AsReddit extends Ayrshare { - slug = PlatformSlug.ASREDDIT; + id = PlatformSlug.ASREDDIT; SUBREDDIT: string; constructor() { diff --git a/src/platforms/AsTikTok.ts b/src/platforms/AsTikTok.ts index aa1e871..b7c9126 100644 --- a/src/platforms/AsTikTok.ts +++ b/src/platforms/AsTikTok.ts @@ -5,7 +5,7 @@ import Folder from "../Folder"; import Post from "../Post"; export default class AsTikTok extends Ayrshare { - slug = PlatformSlug.ASTIKTOK; + id = PlatformSlug.ASTIKTOK; constructor() { super(); diff --git a/src/platforms/AsTwitter.ts b/src/platforms/AsTwitter.ts index 4db6804..6c7bc41 100644 --- a/src/platforms/AsTwitter.ts +++ b/src/platforms/AsTwitter.ts @@ -7,7 +7,7 @@ import * as fs from 'fs'; import * as sharp from 'sharp'; export default class AsTwitter extends Ayrshare { - slug = PlatformSlug.ASTWITTER; + id = PlatformSlug.ASTWITTER; constructor() { super(); diff --git a/src/platforms/AsYouTube.ts b/src/platforms/AsYouTube.ts index 9cdf0ef..03c2806 100644 --- a/src/platforms/AsYouTube.ts +++ b/src/platforms/AsYouTube.ts @@ -5,7 +5,7 @@ import Folder from "../Folder"; import Post from "../Post"; export default class AsYouTube extends Ayrshare { - slug = PlatformSlug.ASYOUTUBE; + id = PlatformSlug.ASYOUTUBE; constructor() { super(); diff --git a/src/platforms/Ayrshare.ts b/src/platforms/Ayrshare.ts index 3f2d125..f82e753 100644 --- a/src/platforms/Ayrshare.ts +++ b/src/platforms/Ayrshare.ts @@ -22,7 +22,7 @@ export default abstract class Ayrshare extends Platform { // map fairpost platforms to ayrshare platforms platforms: { - [slug in PlatformSlug]?: string + [platformId in PlatformSlug]?: string } = { [PlatformSlug.ASYOUTUBE]: "youtube", [PlatformSlug.ASINSTAGRAM]: "instagram", @@ -131,9 +131,9 @@ export default abstract class Ayrshare extends Platform { response: {} } as AyrshareResult; - const postPlatform = this.platforms[this.slug]; + const postPlatform = this.platforms[this.id]; if (!postPlatform) { - result.error = new Error('No ayrshare platform associated with platform '+this.slug); + result.error = new Error('No ayrshare platform associated with platform '+this.id); return result; } const body = JSON.stringify(uploads.length?{ diff --git a/src/platforms/Facebook.ts b/src/platforms/Facebook.ts index aac7b27..db53afe 100644 --- a/src/platforms/Facebook.ts +++ b/src/platforms/Facebook.ts @@ -10,7 +10,7 @@ import * as path from 'path'; import * as sharp from 'sharp'; export default class Facebook extends Platform { - slug: PlatformSlug = PlatformSlug.FACEBOOK; + id: PlatformSlug = PlatformSlug.FACEBOOK; GRAPH_API_VERSION: string = 'v18.0'; constructor() { @@ -76,7 +76,7 @@ export default class Facebook extends Platform { post.status = PostStatus.PUBLISHED; } } else { - console.error(this.slug,"No id returned in post",result); + console.error(this.id,"No id returned in post",result); } post.save(); return !!result.id; From d4058d706d37dfdea9794d88cf41630604f8883b Mon Sep 17 00:00:00 2001 From: pike Date: Sat, 14 Oct 2023 09:16:30 +0200 Subject: [PATCH 16/25] feat: Change PlatformSLug to PlatformId --- index.ts | 6 +++--- src/Feed.ts | 36 ++++++++++++++++++------------------ src/Platform.ts | 4 ++-- src/platforms/AsFacebook.ts | 4 ++-- src/platforms/AsInstagram.ts | 4 ++-- src/platforms/AsLinkedIn.ts | 4 ++-- src/platforms/AsReddit.ts | 4 ++-- src/platforms/AsTikTok.ts | 4 ++-- src/platforms/AsTwitter.ts | 4 ++-- src/platforms/AsYouTube.ts | 4 ++-- src/platforms/Ayrshare.ts | 18 +++++++++--------- src/platforms/Facebook.ts | 4 ++-- src/platforms/index.ts | 2 +- 13 files changed, 49 insertions(+), 49 deletions(-) diff --git a/index.ts b/index.ts index f4084de..0af7c8d 100644 --- a/index.ts +++ b/index.ts @@ -7,7 +7,7 @@ import * as path from 'path'; import Logger from './src/Logger'; import Feed from './src/Feed'; import { PostStatus } from './src/Post'; -import { PlatformSlug } from './src/platforms'; +import { PlatformId } from './src/platforms'; import Facebook from './src/platforms/Facebook'; // arguments @@ -17,8 +17,8 @@ const COMMAND = process.argv[2] ?? 'help' const CONFIG = (getOption('config') as string ) ?? '.env'; const DRY_RUN = !!getOption('dry-run') ?? false; const REPORT = (getOption('report') as string ) ?? 'text'; -const PLATFORMS = (getOption('platforms') as string)?.split(',') as PlatformSlug[] ?? undefined; -const PLATFORM = (getOption('platform') as string) as PlatformSlug ?? undefined; +const PLATFORMS = (getOption('platforms') as string)?.split(',') as PlatformId[] ?? undefined; +const PLATFORM = (getOption('platform') as string) as PlatformId ?? undefined; const FOLDERS = (getOption('folders') as string)?.split(',') ?? undefined; const FOLDER = (getOption('folder') as string) ?? undefined; const DATE = (getOption('date') as string) ?? undefined; diff --git a/src/Feed.ts b/src/Feed.ts index 396b7b1..f35ae6a 100644 --- a/src/Feed.ts +++ b/src/Feed.ts @@ -8,13 +8,13 @@ import Folder from "./Folder"; import Post from "./Post"; import { PostStatus } from "./Post"; import * as platforms from './platforms'; -import { PlatformSlug } from './platforms'; +import { PlatformId } from './platforms'; export default class Feed { path: string = ''; platforms: { - [id in PlatformSlug]? : Platform; + [id in PlatformId]? : Platform; } = {}; folders: Folder[] = []; interval: number; @@ -48,23 +48,23 @@ export default class Feed { }); } - getPlatform(platform:PlatformSlug): Platform { + getPlatform(platform:PlatformId): Platform { Logger.trace('Feed','getPlatform',platform); return this.getPlatforms([platform])[0]; } - getPlatforms(platforms?:PlatformSlug[]): Platform[] { + getPlatforms(platforms?:PlatformId[]): Platform[] { Logger.trace('Feed','getPlatforms',platforms); return platforms?.map(platform=>this.platforms[platform]) ?? Object.values(this.platforms); } - async testPlatform(platform:PlatformSlug): Promise<{}> { + async testPlatform(platform:PlatformId): Promise<{}> { Logger.trace('Feed','testPlatform',platform); const results = await this.testPlatforms([platform]); return results[platform]; } - async testPlatforms(platforms?:PlatformSlug[]): Promise<{ [id:string] : {}}> { + async testPlatforms(platforms?:PlatformId[]): Promise<{ [id:string] : {}}> { Logger.trace('Feed','testPlatforms',platforms); const results = {}; for (const platform of this.getPlatforms(platforms)) { @@ -101,14 +101,14 @@ export default class Feed { return paths?.map(path=>new Folder(this.path+'/'+path)) ?? this.getAllFolders(); } - getPost(path: string, platform: PlatformSlug): Post | undefined { + getPost(path: string, platform: PlatformId): Post | undefined { Logger.trace('Feed','getPost'); return this.getPosts({paths:[path],platforms:[platform]})[0]; } getPosts(filters?: { paths?:string[] - platforms?:PlatformSlug[], + platforms?:PlatformId[], status?:PostStatus }): Post[] { Logger.trace('Feed','getPosts'); @@ -126,14 +126,14 @@ export default class Feed { return posts; } - async preparePost(path: string, platform: PlatformSlug): Promise { + async preparePost(path: string, platform: PlatformId): Promise { Logger.trace('Feed','preparePost',path,platform); return (await this.preparePosts({paths:[path],platforms:[platform]}))[0]; } async preparePosts(filters?: { paths?:string[] - platforms?:PlatformSlug[] + platforms?:PlatformId[] }): Promise { Logger.trace('Feed','preparePosts',filters); const posts: Post[] = []; @@ -153,7 +153,7 @@ export default class Feed { return posts; } - schedulePost(path: string, platform: PlatformSlug, date: Date): Post { + schedulePost(path: string, platform: PlatformId, date: Date): Post { Logger.trace('Feed','schedulePost',path,platform,date); const post = this.getPost(path,platform); if (!post.valid) { @@ -168,7 +168,7 @@ export default class Feed { schedulePosts(filters: { paths?:string[] - platforms?:PlatformSlug[] + platforms?:PlatformId[] }, date: Date): Post[] { Logger.trace('Feed','schedulePosts',filters,date); const posts: Post[] = []; @@ -192,7 +192,7 @@ export default class Feed { async publishPost( path:string, - id:PlatformSlug, + id:PlatformId, dryrun:boolean = false ): Promise { Logger.trace('Feed','publishPost',path,id,dryrun); @@ -212,7 +212,7 @@ export default class Feed { async publishPosts(filters?: { paths?:string[] - platforms?:PlatformSlug[] + platforms?:PlatformId[] }, dryrun:boolean = false): Promise { Logger.trace('Feed','publishPosts',filters,dryrun); const now = new Date(); @@ -239,7 +239,7 @@ export default class Feed { feed planning */ - getLastPost(platform:PlatformSlug): Post | void { + getLastPost(platform:PlatformId): Post | void { Logger.trace('Feed','getLastPost'); let lastPost: Post = undefined; const posts = this.getPosts({ @@ -255,7 +255,7 @@ export default class Feed { } - getNextPostDate(platform:PlatformSlug): Date { + getNextPostDate(platform:PlatformId): Date { Logger.trace('Feed','getNextPostDate'); let nextDate = null; const lastPost = this.getLastPost(platform); @@ -270,7 +270,7 @@ export default class Feed { scheduleNextPosts(date?: Date, filters?: { paths?:string[] - platforms?:PlatformSlug[] + platforms?:PlatformId[] }): Post[] { Logger.trace('Feed','scheduleNextPosts'); const posts: Post[] = []; @@ -293,7 +293,7 @@ export default class Feed { async publishDuePosts(filters?: { paths?:string[] - platforms?:PlatformSlug[] + platforms?:PlatformId[] }, dryrun:boolean = false): Promise { Logger.trace('Feed','publishDuePosts'); const now = new Date(); diff --git a/src/Platform.ts b/src/Platform.ts index 320935b..41f5e51 100644 --- a/src/Platform.ts +++ b/src/Platform.ts @@ -3,12 +3,12 @@ import Logger from './Logger'; import Folder from "./Folder"; import Post from "./Post"; import { PostStatus } from "./Post"; -import { PlatformSlug } from "./platforms"; +import { PlatformId } from "./platforms"; export default class Platform { active: boolean = false; - id: PlatformSlug = PlatformSlug.UNKNOWN; + id: PlatformId = PlatformId.UNKNOWN; defaultBody: string = "Fairpost feed"; /* diff --git a/src/platforms/AsFacebook.ts b/src/platforms/AsFacebook.ts index e382911..b43e80f 100644 --- a/src/platforms/AsFacebook.ts +++ b/src/platforms/AsFacebook.ts @@ -1,14 +1,14 @@ import Logger from '../Logger'; import Ayrshare from "./Ayrshare"; -import { PlatformSlug } from "."; +import { PlatformId } from "."; import Folder from "../Folder"; import Post from "../Post"; import * as fs from 'fs'; import * as sharp from 'sharp'; export default class AsFacebook extends Ayrshare { - id: PlatformSlug = PlatformSlug.ASFACEBOOK; + id: PlatformId = PlatformId.ASFACEBOOK; constructor() { super(); diff --git a/src/platforms/AsInstagram.ts b/src/platforms/AsInstagram.ts index 007e213..3cd4211 100644 --- a/src/platforms/AsInstagram.ts +++ b/src/platforms/AsInstagram.ts @@ -1,13 +1,13 @@ import Logger from '../Logger'; import Ayrshare from "./Ayrshare"; -import { PlatformSlug } from "."; +import { PlatformId } from "."; import Folder from "../Folder"; import Post from "../Post"; import * as sharp from 'sharp'; export default class AsInstagram extends Ayrshare { - id = PlatformSlug.ASINSTAGRAM; + id = PlatformId.ASINSTAGRAM; constructor() { super(); diff --git a/src/platforms/AsLinkedIn.ts b/src/platforms/AsLinkedIn.ts index fafb6fc..166bf4c 100644 --- a/src/platforms/AsLinkedIn.ts +++ b/src/platforms/AsLinkedIn.ts @@ -1,14 +1,14 @@ import Logger from '../Logger'; import Ayrshare from "./Ayrshare"; -import { PlatformSlug } from "."; +import { PlatformId } from "."; import Folder from "../Folder"; import Post from "../Post"; import * as fs from 'fs'; import * as sharp from 'sharp'; export default class AsLinkedIn extends Ayrshare { - id = PlatformSlug.ASLINKEDIN; + id = PlatformId.ASLINKEDIN; constructor() { super(); diff --git a/src/platforms/AsReddit.ts b/src/platforms/AsReddit.ts index 36149b0..b65886e 100644 --- a/src/platforms/AsReddit.ts +++ b/src/platforms/AsReddit.ts @@ -1,11 +1,11 @@ import Logger from '../Logger'; import Ayrshare from "./Ayrshare"; -import { PlatformSlug } from "."; +import { PlatformId } from "."; import Folder from "../Folder"; import Post from "../Post"; export default class AsReddit extends Ayrshare { - id = PlatformSlug.ASREDDIT; + id = PlatformId.ASREDDIT; SUBREDDIT: string; constructor() { diff --git a/src/platforms/AsTikTok.ts b/src/platforms/AsTikTok.ts index b7c9126..93a11bf 100644 --- a/src/platforms/AsTikTok.ts +++ b/src/platforms/AsTikTok.ts @@ -1,11 +1,11 @@ import Logger from '../Logger'; import Ayrshare from "./Ayrshare"; -import { PlatformSlug } from "."; +import { PlatformId } from "."; import Folder from "../Folder"; import Post from "../Post"; export default class AsTikTok extends Ayrshare { - id = PlatformSlug.ASTIKTOK; + id = PlatformId.ASTIKTOK; constructor() { super(); diff --git a/src/platforms/AsTwitter.ts b/src/platforms/AsTwitter.ts index 6c7bc41..6b32d2d 100644 --- a/src/platforms/AsTwitter.ts +++ b/src/platforms/AsTwitter.ts @@ -1,13 +1,13 @@ import Logger from '../Logger'; import Ayrshare from "./Ayrshare"; -import { PlatformSlug } from "."; +import { PlatformId } from "."; import Folder from "../Folder"; import Post from "../Post"; import * as fs from 'fs'; import * as sharp from 'sharp'; export default class AsTwitter extends Ayrshare { - id = PlatformSlug.ASTWITTER; + id = PlatformId.ASTWITTER; constructor() { super(); diff --git a/src/platforms/AsYouTube.ts b/src/platforms/AsYouTube.ts index 03c2806..defbca9 100644 --- a/src/platforms/AsYouTube.ts +++ b/src/platforms/AsYouTube.ts @@ -1,11 +1,11 @@ import Logger from '../Logger'; import Ayrshare from "./Ayrshare"; -import { PlatformSlug } from "."; +import { PlatformId } from "."; import Folder from "../Folder"; import Post from "../Post"; export default class AsYouTube extends Ayrshare { - id = PlatformSlug.ASYOUTUBE; + id = PlatformId.ASYOUTUBE; constructor() { super(); diff --git a/src/platforms/Ayrshare.ts b/src/platforms/Ayrshare.ts index f82e753..abda237 100644 --- a/src/platforms/Ayrshare.ts +++ b/src/platforms/Ayrshare.ts @@ -3,7 +3,7 @@ import Logger from '../Logger'; import * as fs from 'fs'; import * as path from 'path'; import { randomUUID } from 'crypto'; -import { PlatformSlug } from "."; +import { PlatformId } from "."; import Platform from "../Platform"; import Folder from "../Folder"; import Post from "../Post"; @@ -22,15 +22,15 @@ export default abstract class Ayrshare extends Platform { // map fairpost platforms to ayrshare platforms platforms: { - [platformId in PlatformSlug]?: string + [platformId in PlatformId]?: string } = { - [PlatformSlug.ASYOUTUBE]: "youtube", - [PlatformSlug.ASINSTAGRAM]: "instagram", - [PlatformSlug.ASFACEBOOK]: "facebook", - [PlatformSlug.ASTWITTER]: "twitter", - [PlatformSlug.ASTIKTOK]: "tiktok", - [PlatformSlug.ASLINKEDIN]: "linkedin", - [PlatformSlug.ASREDDIT]: "reddit" + [PlatformId.ASYOUTUBE]: "youtube", + [PlatformId.ASINSTAGRAM]: "instagram", + [PlatformId.ASFACEBOOK]: "facebook", + [PlatformId.ASTWITTER]: "twitter", + [PlatformId.ASTIKTOK]: "tiktok", + [PlatformId.ASLINKEDIN]: "linkedin", + [PlatformId.ASREDDIT]: "reddit" }; constructor() { diff --git a/src/platforms/Facebook.ts b/src/platforms/Facebook.ts index db53afe..70c534b 100644 --- a/src/platforms/Facebook.ts +++ b/src/platforms/Facebook.ts @@ -1,7 +1,7 @@ import Logger from "../Logger"; import Platform from "../Platform"; -import { PlatformSlug } from "."; +import { PlatformId } from "."; import Folder from "../Folder"; import Post from "../Post"; import { PostStatus } from "../Post"; @@ -10,7 +10,7 @@ import * as path from 'path'; import * as sharp from 'sharp'; export default class Facebook extends Platform { - id: PlatformSlug = PlatformSlug.FACEBOOK; + id: PlatformId = PlatformId.FACEBOOK; GRAPH_API_VERSION: string = 'v18.0'; constructor() { diff --git a/src/platforms/index.ts b/src/platforms/index.ts index 7c5e71f..9c6366b 100644 --- a/src/platforms/index.ts +++ b/src/platforms/index.ts @@ -7,7 +7,7 @@ export { default as AsLinkedIn } from "./AsLinkedIn"; export { default as AsReddit } from "./AsReddit"; export { default as Facebook } from "./Facebook"; -export enum PlatformSlug { +export enum PlatformId { UNKNOWN = "unknown", ASYOUTUBE = "asyoutube", ASINSTAGRAM = "asinstagram", From 2dc0be4689d0c542c9e96186e9a5afd2f18a19cc Mon Sep 17 00:00:00 2001 From: pike Date: Sat, 14 Oct 2023 09:24:49 +0200 Subject: [PATCH 17/25] feat: Change some 'platform' to 'apltformId' --- src/Feed.ts | 58 ++++++++++++++++++++++++++--------------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/src/Feed.ts b/src/Feed.ts index f35ae6a..2932b4b 100644 --- a/src/Feed.ts +++ b/src/Feed.ts @@ -48,26 +48,26 @@ export default class Feed { }); } - getPlatform(platform:PlatformId): Platform { - Logger.trace('Feed','getPlatform',platform); - return this.getPlatforms([platform])[0]; + getPlatform(platformId:PlatformId): Platform { + Logger.trace('Feed','getPlatform',platformId); + return this.getPlatforms([platformId])[0]; } - getPlatforms(platforms?:PlatformId[]): Platform[] { - Logger.trace('Feed','getPlatforms',platforms); - return platforms?.map(platform=>this.platforms[platform]) ?? Object.values(this.platforms); + getPlatforms(platformIds?:PlatformId[]): Platform[] { + Logger.trace('Feed','getPlatforms',platformIds); + return platformIds?.map(platformId=>this.platforms[platformId]) ?? Object.values(this.platforms); } - async testPlatform(platform:PlatformId): Promise<{}> { - Logger.trace('Feed','testPlatform',platform); - const results = await this.testPlatforms([platform]); - return results[platform]; + async testPlatform(platformId:PlatformId): Promise<{}> { + Logger.trace('Feed','testPlatform',platformId); + const results = await this.testPlatforms([platformId]); + return results[platformId]; } - async testPlatforms(platforms?:PlatformId[]): Promise<{ [id:string] : {}}> { - Logger.trace('Feed','testPlatforms',platforms); + async testPlatforms(platformsIds?:PlatformId[]): Promise<{ [id:string] : {}}> { + Logger.trace('Feed','testPlatforms',platformsIds); const results = {}; - for (const platform of this.getPlatforms(platforms)) { + for (const platform of this.getPlatforms(platformsIds)) { results[platform.id] = await platform.test(); } return results; @@ -101,9 +101,9 @@ export default class Feed { return paths?.map(path=>new Folder(this.path+'/'+path)) ?? this.getAllFolders(); } - getPost(path: string, platform: PlatformId): Post | undefined { + getPost(path: string, platformId: PlatformId): Post | undefined { Logger.trace('Feed','getPost'); - return this.getPosts({paths:[path],platforms:[platform]})[0]; + return this.getPosts({paths:[path],platforms:[platformId]})[0]; } getPosts(filters?: { @@ -126,9 +126,9 @@ export default class Feed { return posts; } - async preparePost(path: string, platform: PlatformId): Promise { - Logger.trace('Feed','preparePost',path,platform); - return (await this.preparePosts({paths:[path],platforms:[platform]}))[0]; + async preparePost(path: string, platformId: PlatformId): Promise { + Logger.trace('Feed','preparePost',path,platformId); + return (await this.preparePosts({paths:[path],platforms:[platformId]}))[0]; } async preparePosts(filters?: { @@ -153,9 +153,9 @@ export default class Feed { return posts; } - schedulePost(path: string, platform: PlatformId, date: Date): Post { - Logger.trace('Feed','schedulePost',path,platform,date); - const post = this.getPost(path,platform); + schedulePost(path: string, platformId: PlatformId, date: Date): Post { + Logger.trace('Feed','schedulePost',path,platformId,date); + const post = this.getPost(path,platformId); if (!post.valid) { throw new Error('Post is not valid'); } @@ -192,17 +192,17 @@ export default class Feed { async publishPost( path:string, - id:PlatformId, + platformId:PlatformId, dryrun:boolean = false ): Promise { - Logger.trace('Feed','publishPost',path,id,dryrun); + Logger.trace('Feed','publishPost',path,platformId,dryrun); const now = new Date(); - const platform = this.getPlatform(id); + const platform = this.getPlatform(platformId); const folder = this.getFolder(path); const post = platform.getPost(folder); if (post.valid) { post.schedule(now); - Logger.trace('Posting',id,path); + Logger.info('Posting',platformId,path); await platform.publishPost(post,dryrun); } else { throw new Error('Post is not valid'); @@ -239,11 +239,11 @@ export default class Feed { feed planning */ - getLastPost(platform:PlatformId): Post | void { + getLastPost(platformId:PlatformId): Post | void { Logger.trace('Feed','getLastPost'); let lastPost: Post = undefined; const posts = this.getPosts({ - platforms: [platform], + platforms: [platformId], status: PostStatus.PUBLISHED }); for (const post of posts) { @@ -255,10 +255,10 @@ export default class Feed { } - getNextPostDate(platform:PlatformId): Date { + getNextPostDate(platformId:PlatformId): Date { Logger.trace('Feed','getNextPostDate'); let nextDate = null; - const lastPost = this.getLastPost(platform); + const lastPost = this.getLastPost(platformId); if (lastPost) { nextDate = new Date(lastPost.posted); nextDate.setDate(nextDate.getDate()+this.interval); From d99fa41085fa9f2d464302403f9e3d8c27ab1663 Mon Sep 17 00:00:00 2001 From: pike Date: Sat, 14 Oct 2023 09:29:01 +0200 Subject: [PATCH 18/25] feat: Change post.posted to post.published --- README.md | 4 ++-- src/Feed.ts | 4 ++-- src/Platform.ts | 4 ++-- src/Post.ts | 6 +++--- src/platforms/Ayrshare.ts | 1 + src/platforms/Facebook.ts | 1 + 6 files changed, 11 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index c1dc47c..dc56310 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ or a video. To smoothly maintain your feed, you can run fairpost every day. Fairpost will prepare and -schedule new content to be posted on all your +schedule new content to be published on all your platforms on a regular basis, while you can just focus on creating new content. @@ -42,7 +42,7 @@ cp .env.dist .env && nano .env fairpost.js prepare-posts ``` Folders need to be `prepared` (iow turned into posts) -before they can be posted to a platform. +before they can be published to a platform. Each platform, as defined in src/platforms, will handle the folder contents by itself. It may decide to modify the media (eg, scale images) diff --git a/src/Feed.ts b/src/Feed.ts index 2932b4b..96a5f32 100644 --- a/src/Feed.ts +++ b/src/Feed.ts @@ -247,7 +247,7 @@ export default class Feed { status: PostStatus.PUBLISHED }); for (const post of posts) { - if (!lastPost || post.posted >= lastPost.posted) { + if (!lastPost || post.published >= lastPost.published) { lastPost = post; } } @@ -260,7 +260,7 @@ export default class Feed { let nextDate = null; const lastPost = this.getLastPost(platformId); if (lastPost) { - nextDate = new Date(lastPost.posted); + nextDate = new Date(lastPost.published); nextDate.setDate(nextDate.getDate()+this.interval); } else { nextDate = new Date(); diff --git a/src/Platform.ts b/src/Platform.ts index 41f5e51..8495cc6 100644 --- a/src/Platform.ts +++ b/src/Platform.ts @@ -105,7 +105,7 @@ export default class Platform { * publishPost * * publish the post for this platform, sync. - * set the posted date to now. + * set the published date to now. * add the result to post.results * on success, set the status to published and return true, * else set the status to failed and return false @@ -114,7 +114,7 @@ export default class Platform { async publishPost(post: Post, dryrun:boolean = false): Promise { Logger.trace('Platform','publishPost'); - post.posted = new Date(); + post.published = new Date(); post.results.push({ error: 'publishing not implemented for '+this.id }); diff --git a/src/Post.ts b/src/Post.ts index 840acbe..3090a9e 100644 --- a/src/Post.ts +++ b/src/Post.ts @@ -10,7 +10,7 @@ export default class Post { valid: boolean = false; status: PostStatus = PostStatus.UNKNOWN; scheduled?: Date; - posted?: Date; + published?: Date; results: {}[] = []; title: string = ''; body?: string; @@ -28,7 +28,7 @@ export default class Post { if (data) { Object.assign(this, data); this.scheduled = data.scheduled ? new Date(data.scheduled): undefined; - this.posted = data.posted ? new Date(data.posted): undefined; + this.published = data.published ? new Date(data.published): undefined; } } @@ -65,7 +65,7 @@ export default class Post { report += '\n - valid: '+this.valid; report += '\n - status: '+this.status; report += '\n - scheduled: '+this.scheduled; - report += '\n - posted: '+this.posted; + report += '\n - published: '+this.published; return report; } diff --git a/src/platforms/Ayrshare.ts b/src/platforms/Ayrshare.ts index abda237..d09dc1d 100644 --- a/src/platforms/Ayrshare.ts +++ b/src/platforms/Ayrshare.ts @@ -64,6 +64,7 @@ export default abstract class Ayrshare extends Platform { post.results.push(result); if (result.success) { post.status = PostStatus.PUBLISHED; + post.published = new Date(); } post.save(); diff --git a/src/platforms/Facebook.ts b/src/platforms/Facebook.ts index 70c534b..5706d4c 100644 --- a/src/platforms/Facebook.ts +++ b/src/platforms/Facebook.ts @@ -74,6 +74,7 @@ export default class Facebook extends Platform { if (result.id) { if (!dryrun) { post.status = PostStatus.PUBLISHED; + post.published = new Date(); } } else { console.error(this.id,"No id returned in post",result); From b24cdde5fb98f75f4e0113b7222fdf923fd315af Mon Sep 17 00:00:00 2001 From: pike Date: Sat, 14 Oct 2023 09:59:06 +0200 Subject: [PATCH 19/25] feat: Add feed.id and folder.id , same as path --- index.ts | 20 ++++++++++---------- src/Feed.ts | 36 +++++++++++++++++++----------------- src/Folder.ts | 2 ++ src/Platform.ts | 4 ++-- src/Post.ts | 2 +- 5 files changed, 34 insertions(+), 30 deletions(-) diff --git a/index.ts b/index.ts index 0af7c8d..2af175b 100644 --- a/index.ts +++ b/index.ts @@ -42,14 +42,14 @@ async function main() { let report = ''; const feed = new Feed(CONFIG); - Logger.trace('Fairpost '+feed.path+' '+COMMAND,DRY_RUN?' dry-run':''); + Logger.trace('Fairpost '+feed.id+' '+COMMAND,DRY_RUN?' dry-run':''); try { switch(COMMAND) { case 'get-feed': result = feed; - report = 'Feed: '+feed.path; + report = 'Feed: '+feed.id; break; case 'get-platform': const platform = feed.getPlatform(PLATFORM); @@ -73,13 +73,13 @@ async function main() { break; case 'get-folder': const folder = feed.getFolder(FOLDER); - report += 'Folder: '+folder.path+'\n'; + report += 'Folder: '+folder.id+'\n'; result = folder; break; case 'get-folders': const folders = feed.getFolders(FOLDERS); folders.forEach(folder => { - report += 'Folder: '+folder.path+'\n'; + report += 'Folder: '+folder.id+'\n'; }); result = folders; break; @@ -90,7 +90,7 @@ async function main() { break; case 'get-posts': const allposts = feed.getPosts({ - paths:FOLDERS, + folders:FOLDERS, platforms:PLATFORMS, status: STATUS }); @@ -106,7 +106,7 @@ async function main() { break; case 'prepare-posts': const prepposts = await feed.preparePosts({ - paths:FOLDERS, + folders:FOLDERS, platforms:PLATFORMS }); prepposts.forEach(post => { @@ -123,7 +123,7 @@ async function main() { break; case 'schedule-posts': const schedposts = feed.schedulePosts({ - paths: FOLDERS, + folders: FOLDERS, platforms: PLATFORMS }, new Date(DATE)); schedposts.forEach(post => { @@ -139,7 +139,7 @@ async function main() { case 'publish-posts': const pubposts = await feed.publishPosts({ - paths:FOLDERS, + folders:FOLDERS, platforms:PLATFORMS }, DRY_RUN); pubposts.forEach(post => { @@ -151,7 +151,7 @@ async function main() { /* feed planning */ case 'schedule-next-posts': const nextposts = feed.scheduleNextPosts(DATE ? new Date(DATE): undefined,{ - paths:FOLDERS, + folders:FOLDERS, platforms:PLATFORMS }); nextposts.forEach(post => { @@ -161,7 +161,7 @@ async function main() { break; case 'publish-due-posts': const dueposts = await feed.publishDuePosts({ - paths:FOLDERS, + folders:FOLDERS, platforms:PLATFORMS }, DRY_RUN); pubposts.forEach(post => { diff --git a/src/Feed.ts b/src/Feed.ts index 96a5f32..06290c6 100644 --- a/src/Feed.ts +++ b/src/Feed.ts @@ -12,6 +12,7 @@ import { PlatformId } from './platforms'; export default class Feed { + id: string = ''; path: string = ''; platforms: { [id in PlatformId]? : Platform; @@ -28,6 +29,7 @@ export default class Feed { } if (process.env.FAIRPOST_FEED_PATH) { this.path = process.env.FAIRPOST_FEED_PATH; + this.id = this.path; } else { throw new Error('Problem reading .env config file'); } @@ -103,18 +105,18 @@ export default class Feed { getPost(path: string, platformId: PlatformId): Post | undefined { Logger.trace('Feed','getPost'); - return this.getPosts({paths:[path],platforms:[platformId]})[0]; + return this.getPosts({folders:[path],platforms:[platformId]})[0]; } getPosts(filters?: { - paths?:string[] + folders?:string[] platforms?:PlatformId[], status?:PostStatus }): Post[] { Logger.trace('Feed','getPosts'); const posts: Post[] = []; const platforms = this.getPlatforms(filters?.platforms); - const folders = this.getFolders(filters?.paths); + const folders = this.getFolders(filters?.folders); for (const folder of folders) { for (const platform of platforms) { const post = platform.getPost(folder); @@ -128,17 +130,17 @@ export default class Feed { async preparePost(path: string, platformId: PlatformId): Promise { Logger.trace('Feed','preparePost',path,platformId); - return (await this.preparePosts({paths:[path],platforms:[platformId]}))[0]; + return (await this.preparePosts({folders:[path],platforms:[platformId]}))[0]; } async preparePosts(filters?: { - paths?:string[] + folders?:string[] platforms?:PlatformId[] }): Promise { Logger.trace('Feed','preparePosts',filters); const posts: Post[] = []; const platforms = this.getPlatforms(filters?.platforms); - const folders = this.getFolders(filters?.paths); + const folders = this.getFolders(filters?.folders); for (const folder of folders) { for (const platform of platforms) { const post = platform.getPost(folder); @@ -167,13 +169,13 @@ export default class Feed { } schedulePosts(filters: { - paths?:string[] + folders?:string[] platforms?:PlatformId[] }, date: Date): Post[] { Logger.trace('Feed','schedulePosts',filters,date); const posts: Post[] = []; const platforms = this.getPlatforms(filters?.platforms); - const folders = this.getFolders(filters?.paths); + const folders = this.getFolders(filters?.folders); for (const platform of platforms) { for (const folder of folders) { const post = platform.getPost(folder); @@ -211,24 +213,24 @@ export default class Feed { } async publishPosts(filters?: { - paths?:string[] + folders?:string[] platforms?:PlatformId[] }, dryrun:boolean = false): Promise { Logger.trace('Feed','publishPosts',filters,dryrun); const now = new Date(); const posts: Post[] = []; const platforms = this.getPlatforms(filters?.platforms); - const folders = this.getFolders(filters?.paths); + const folders = this.getFolders(filters?.folders); for (const platform of platforms) { for (const folder of folders) { const post = platform.getPost(folder); if (post.valid) { post.schedule(now); - Logger.trace('Posting',platform.id,folder.path); + Logger.trace('Posting',platform.id,folder.id); await platform.publishPost(post,dryrun); posts.push(post); } else { - Logger.warn('Skipping invalid post',platform.id,folder.path); + Logger.warn('Skipping invalid post',platform.id,folder.id); } } } @@ -269,13 +271,13 @@ export default class Feed { } scheduleNextPosts(date?: Date, filters?: { - paths?:string[] + folders?:string[] platforms?:PlatformId[] }): Post[] { Logger.trace('Feed','scheduleNextPosts'); const posts: Post[] = []; const platforms = this.getPlatforms(filters?.platforms); - const folders = this.getFolders(filters?.paths); + const folders = this.getFolders(filters?.folders); for (const platform of platforms) { const nextDate = date?date:this.getNextPostDate(platform.id); for (const folder of folders) { @@ -292,20 +294,20 @@ export default class Feed { } async publishDuePosts(filters?: { - paths?:string[] + folders?:string[] platforms?:PlatformId[] }, dryrun:boolean = false): Promise { Logger.trace('Feed','publishDuePosts'); const now = new Date(); const posts: Post[] = []; const platforms = this.getPlatforms(filters?.platforms); - const folders = this.getFolders(filters?.paths); + const folders = this.getFolders(filters?.folders); for (const platform of platforms) { for (const folder of folders) { const post = platform.getPost(folder); if (post?.status===PostStatus.SCHEDULED) { if (post.scheduled <= now) { - console.log('Posting',platform.id,folder.path); + console.log('Posting',platform.id,folder.id); await platform.publishPost(post,dryrun); posts.push(post); break; diff --git a/src/Folder.ts b/src/Folder.ts index e5e7fa1..540b6ec 100644 --- a/src/Folder.ts +++ b/src/Folder.ts @@ -3,6 +3,7 @@ import Logger from './Logger'; export default class Folder { + id: string; path: string; files?: { text: string[], @@ -12,6 +13,7 @@ export default class Folder { }; constructor(path: string) { + this.id = path; this.path = path; } diff --git a/src/Platform.ts b/src/Platform.ts index 8495cc6..474f12c 100644 --- a/src/Platform.ts +++ b/src/Platform.ts @@ -105,7 +105,7 @@ export default class Platform { * publishPost * * publish the post for this platform, sync. - * set the published date to now. + * set the posted date to now. * add the result to post.results * on success, set the status to published and return true, * else set the status to failed and return false @@ -114,10 +114,10 @@ export default class Platform { async publishPost(post: Post, dryrun:boolean = false): Promise { Logger.trace('Platform','publishPost'); - post.published = new Date(); post.results.push({ error: 'publishing not implemented for '+this.id }); + post.published = undefined; post.status = PostStatus.FAILED; post.save(); return false; diff --git a/src/Post.ts b/src/Post.ts index 3090a9e..936eacb 100644 --- a/src/Post.ts +++ b/src/Post.ts @@ -61,7 +61,7 @@ export default class Post { report(): string { Logger.trace('Post','report'); let report = ''; - report += '\nPost: '+this.platform.id+' : '+this.folder.path; + report += '\nPost: '+this.platform.id+' : '+this.folder.id; report += '\n - valid: '+this.valid; report += '\n - status: '+this.status; report += '\n - scheduled: '+this.scheduled; From e7d799e81923a744146d7607c1accc0a178b291b Mon Sep 17 00:00:00 2001 From: pike Date: Sat, 14 Oct 2023 10:00:29 +0200 Subject: [PATCH 20/25] feat: Add post.id --- src/Post.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Post.ts b/src/Post.ts index 936eacb..556863e 100644 --- a/src/Post.ts +++ b/src/Post.ts @@ -5,6 +5,7 @@ import Folder from "./Folder"; import Platform from "./Platform"; export default class Post { + id: string; folder: Folder; platform: Platform; valid: boolean = false; @@ -25,6 +26,7 @@ export default class Post { constructor(folder: Folder, platform: Platform, data?: any) { this.folder = folder; this.platform = platform; + this.id = this.folder.id+':'+this.platform.id; if (data) { Object.assign(this, data); this.scheduled = data.scheduled ? new Date(data.scheduled): undefined; @@ -61,7 +63,7 @@ export default class Post { report(): string { Logger.trace('Post','report'); let report = ''; - report += '\nPost: '+this.platform.id+' : '+this.folder.id; + report += '\nPost: '+this.id; report += '\n - valid: '+this.valid; report += '\n - status: '+this.status; report += '\n - scheduled: '+this.scheduled; From 4e52bb6710195799b2405f4498fd2d465dcbb1eb Mon Sep 17 00:00:00 2001 From: pike Date: Sat, 14 Oct 2023 10:13:25 +0200 Subject: [PATCH 21/25] feat: Prettier --- package-lock.json | 22 ++ package.json | 4 +- src/Feed.ts | 567 ++++++++++++++++++---------------- src/Folder.ts | 69 +++-- src/Logger.ts | 6 +- src/Platform.ts | 233 +++++++------- src/Post.ts | 70 ++--- src/platforms/AsFacebook.ts | 61 ++-- src/platforms/AsInstagram.ts | 97 +++--- src/platforms/AsLinkedIn.ts | 77 ++--- src/platforms/AsReddit.ts | 60 ++-- src/platforms/AsTikTok.ts | 44 +-- src/platforms/AsTwitter.ts | 72 ++--- src/platforms/AsYouTube.ts | 61 ++-- src/platforms/Ayrshare.ts | 347 +++++++++++---------- src/platforms/Facebook.ts | 583 ++++++++++++++++++----------------- src/platforms/index.ts | 20 +- 17 files changed, 1243 insertions(+), 1150 deletions(-) diff --git a/package-lock.json b/package-lock.json index b12eccb..65bb917 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "sharp": "0.31.1" }, "devDependencies": { + "prettier": "^3.0.3", "ts-node": "^10.9.1", "typescript": "^5.0.4" } @@ -513,6 +514,21 @@ "node": ">=10" } }, + "node_modules/prettier": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", + "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -1199,6 +1215,12 @@ "tunnel-agent": "^0.6.0" } }, + "prettier": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", + "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", + "dev": true + }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", diff --git a/package.json b/package.json index 010dc77..a89566a 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,8 @@ "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "compile": "tsc" + "compile": "tsc", + "prettier": "prettier --config .prettierrc 'src/**/*.ts' --write" }, "keywords": [ "ayrshare", @@ -20,6 +21,7 @@ "sharp": "0.31.1" }, "devDependencies": { + "prettier": "^3.0.3", "ts-node": "^10.9.1", "typescript": "^5.0.4" } diff --git a/src/Feed.ts b/src/Feed.ts index 06290c6..b3e68f6 100644 --- a/src/Feed.ts +++ b/src/Feed.ts @@ -1,322 +1,351 @@ - -import * as fs from 'fs'; -import * as path from 'path'; -import * as dotenv from 'dotenv'; -import Logger from './Logger'; +import * as fs from "fs"; +import * as path from "path"; +import * as dotenv from "dotenv"; +import Logger from "./Logger"; import Platform from "./Platform"; import Folder from "./Folder"; import Post from "./Post"; import { PostStatus } from "./Post"; -import * as platforms from './platforms'; -import { PlatformId } from './platforms'; +import * as platforms from "./platforms"; +import { PlatformId } from "./platforms"; export default class Feed { + id: string = ""; + path: string = ""; + platforms: { + [id in PlatformId]?: Platform; + } = {}; + folders: Folder[] = []; + interval: number; - id: string = ''; - path: string = ''; - platforms: { - [id in PlatformId]? : Platform; - } = {}; - folders: Folder[] = []; - interval: number; + constructor(configPath?: string) { + if (configPath) { + const configPathResolved = path.resolve( + __dirname + "/../../" + configPath, + ); + dotenv.config({ path: configPathResolved }); + } else { + dotenv.config(); + } + if (process.env.FAIRPOST_FEED_PATH) { + this.path = process.env.FAIRPOST_FEED_PATH; + this.id = this.path; + } else { + throw new Error("Problem reading .env config file"); + } + this.interval = Number(process.env.FAIRPOST_FEED_INTERVAL ?? 7); - constructor(configPath?: string) { - if (configPath) { - const configPathResolved = path.resolve(__dirname+'/../../'+configPath); - dotenv.config({ path:configPathResolved }); - } else { - dotenv.config(); + const activePlatformIds = process.env.FAIRPOST_FEED_PLATFORMS.split(","); + const platformClasses = fs.readdirSync( + path.resolve(__dirname + "/platforms"), + ); + platformClasses.forEach((file) => { + const constructor = file.replace(".ts", "").replace(".js", ""); + // nb import * as platforms loaded the constructors + if (platforms[constructor] !== undefined) { + const platform = new platforms[constructor](); + platform.active = activePlatformIds.includes(platform.id); + if (platform.active) { + this.platforms[platform.id] = platform; } - if (process.env.FAIRPOST_FEED_PATH) { - this.path = process.env.FAIRPOST_FEED_PATH; - this.id = this.path; - } else { - throw new Error('Problem reading .env config file'); - } - this.interval = Number(process.env.FAIRPOST_FEED_INTERVAL ?? 7); + } + }); + } - const activePlatformIds = process.env.FAIRPOST_FEED_PLATFORMS.split(','); - const platformClasses = fs.readdirSync(path.resolve(__dirname+'/platforms')); - platformClasses.forEach(file=> { - const constructor = file.replace('.ts','').replace('.js',''); - // nb import * as platforms loaded the constructors - if (platforms[constructor] !== undefined) { - const platform = new platforms[constructor](); - platform.active = activePlatformIds.includes(platform.id); - if (platform.active) { - this.platforms[platform.id] = platform; - } - } - }); - } + getPlatform(platformId: PlatformId): Platform { + Logger.trace("Feed", "getPlatform", platformId); + return this.getPlatforms([platformId])[0]; + } - getPlatform(platformId:PlatformId): Platform { - Logger.trace('Feed','getPlatform',platformId); - return this.getPlatforms([platformId])[0]; - } + getPlatforms(platformIds?: PlatformId[]): Platform[] { + Logger.trace("Feed", "getPlatforms", platformIds); + return ( + platformIds?.map((platformId) => this.platforms[platformId]) ?? + Object.values(this.platforms) + ); + } - getPlatforms(platformIds?:PlatformId[]): Platform[] { - Logger.trace('Feed','getPlatforms',platformIds); - return platformIds?.map(platformId=>this.platforms[platformId]) ?? Object.values(this.platforms); - } + async testPlatform(platformId: PlatformId): Promise<{}> { + Logger.trace("Feed", "testPlatform", platformId); + const results = await this.testPlatforms([platformId]); + return results[platformId]; + } - async testPlatform(platformId:PlatformId): Promise<{}> { - Logger.trace('Feed','testPlatform',platformId); - const results = await this.testPlatforms([platformId]); - return results[platformId]; + async testPlatforms( + platformsIds?: PlatformId[], + ): Promise<{ [id: string]: {} }> { + Logger.trace("Feed", "testPlatforms", platformsIds); + const results = {}; + for (const platform of this.getPlatforms(platformsIds)) { + results[platform.id] = await platform.test(); } + return results; + } - async testPlatforms(platformsIds?:PlatformId[]): Promise<{ [id:string] : {}}> { - Logger.trace('Feed','testPlatforms',platformsIds); - const results = {}; - for (const platform of this.getPlatforms(platformsIds)) { - results[platform.id] = await platform.test(); - } - return results; + getAllFolders(): Folder[] { + Logger.trace("Feed", "getAllFolders"); + if (this.folders.length) { + return this.folders; } - - getAllFolders(): Folder[] { - Logger.trace('Feed','getAllFolders'); - if (this.folders.length) { - return this.folders; - } - if (!fs.existsSync(this.path)) { - fs.mkdirSync(this.path); - } - const paths = fs.readdirSync(this.path).filter(path => { - return fs.statSync(this.path+'/'+path).isDirectory() && - !path.startsWith('_') && !path.startsWith('.'); - }); - if (paths) { - this.folders = paths.map(path => new Folder(this.path+'/'+path)); - } - return this.folders; + if (!fs.existsSync(this.path)) { + fs.mkdirSync(this.path); } - - getFolder(path: string): Folder | undefined { - Logger.trace('Feed','getFolder',path); - return this.getFolders([path])[0]; + const paths = fs.readdirSync(this.path).filter((path) => { + return ( + fs.statSync(this.path + "/" + path).isDirectory() && + !path.startsWith("_") && + !path.startsWith(".") + ); + }); + if (paths) { + this.folders = paths.map((path) => new Folder(this.path + "/" + path)); } + return this.folders; + } - getFolders(paths?: string[]): Folder[] { - Logger.trace('Feed','getFolders',paths); - return paths?.map(path=>new Folder(this.path+'/'+path)) ?? this.getAllFolders(); - } + getFolder(path: string): Folder | undefined { + Logger.trace("Feed", "getFolder", path); + return this.getFolders([path])[0]; + } - getPost(path: string, platformId: PlatformId): Post | undefined { - Logger.trace('Feed','getPost'); - return this.getPosts({folders:[path],platforms:[platformId]})[0]; - } + getFolders(paths?: string[]): Folder[] { + Logger.trace("Feed", "getFolders", paths); + return ( + paths?.map((path) => new Folder(this.path + "/" + path)) ?? + this.getAllFolders() + ); + } + + getPost(path: string, platformId: PlatformId): Post | undefined { + Logger.trace("Feed", "getPost"); + return this.getPosts({ folders: [path], platforms: [platformId] })[0]; + } - getPosts(filters?: { - folders?:string[] - platforms?:PlatformId[], - status?:PostStatus - }): Post[] { - Logger.trace('Feed','getPosts'); - const posts: Post[] = []; - const platforms = this.getPlatforms(filters?.platforms); - const folders = this.getFolders(filters?.folders); - for (const folder of folders) { - for (const platform of platforms) { - const post = platform.getPost(folder); - if (post && (!filters?.status || filters.status.includes(post.status))) { - posts.push(post); - } - } + getPosts(filters?: { + folders?: string[]; + platforms?: PlatformId[]; + status?: PostStatus; + }): Post[] { + Logger.trace("Feed", "getPosts"); + const posts: Post[] = []; + const platforms = this.getPlatforms(filters?.platforms); + const folders = this.getFolders(filters?.folders); + for (const folder of folders) { + for (const platform of platforms) { + const post = platform.getPost(folder); + if ( + post && + (!filters?.status || filters.status.includes(post.status)) + ) { + posts.push(post); } - return posts; + } } + return posts; + } - async preparePost(path: string, platformId: PlatformId): Promise { - Logger.trace('Feed','preparePost',path,platformId); - return (await this.preparePosts({folders:[path],platforms:[platformId]}))[0]; - } + async preparePost( + path: string, + platformId: PlatformId, + ): Promise { + Logger.trace("Feed", "preparePost", path, platformId); + return ( + await this.preparePosts({ folders: [path], platforms: [platformId] }) + )[0]; + } - async preparePosts(filters?: { - folders?:string[] - platforms?:PlatformId[] - }): Promise { - Logger.trace('Feed','preparePosts',filters); - const posts: Post[] = []; - const platforms = this.getPlatforms(filters?.platforms); - const folders = this.getFolders(filters?.folders); - for (const folder of folders) { - for (const platform of platforms) { - const post = platform.getPost(folder); - if (post?.status!==PostStatus.PUBLISHED) { - const newPost = await platform.preparePost(folder); - if (newPost) { - posts.push(newPost); - } - } - } + async preparePosts(filters?: { + folders?: string[]; + platforms?: PlatformId[]; + }): Promise { + Logger.trace("Feed", "preparePosts", filters); + const posts: Post[] = []; + const platforms = this.getPlatforms(filters?.platforms); + const folders = this.getFolders(filters?.folders); + for (const folder of folders) { + for (const platform of platforms) { + const post = platform.getPost(folder); + if (post?.status !== PostStatus.PUBLISHED) { + const newPost = await platform.preparePost(folder); + if (newPost) { + posts.push(newPost); + } } - return posts; + } } + return posts; + } - schedulePost(path: string, platformId: PlatformId, date: Date): Post { - Logger.trace('Feed','schedulePost',path,platformId,date); - const post = this.getPost(path,platformId); + schedulePost(path: string, platformId: PlatformId, date: Date): Post { + Logger.trace("Feed", "schedulePost", path, platformId, date); + const post = this.getPost(path, platformId); + if (!post.valid) { + throw new Error("Post is not valid"); + } + if (post.status !== PostStatus.UNSCHEDULED) { + throw new Error("Post is not unscheduled"); + } + post.schedule(date); + return post; + } + + schedulePosts( + filters: { + folders?: string[]; + platforms?: PlatformId[]; + }, + date: Date, + ): Post[] { + Logger.trace("Feed", "schedulePosts", filters, date); + const posts: Post[] = []; + const platforms = this.getPlatforms(filters?.platforms); + const folders = this.getFolders(filters?.folders); + for (const platform of platforms) { + for (const folder of folders) { + const post = platform.getPost(folder); if (!post.valid) { - throw new Error('Post is not valid'); + throw new Error("Post is not valid"); } - if (post.status!==PostStatus.UNSCHEDULED) { - throw new Error('Post is not unscheduled'); + if (post.status !== PostStatus.UNSCHEDULED) { + throw new Error("Post is not unscheduled"); } post.schedule(date); - return post; + posts.push(post); + } } + return posts; + } - schedulePosts(filters: { - folders?:string[] - platforms?:PlatformId[] - }, date: Date): Post[] { - Logger.trace('Feed','schedulePosts',filters,date); - const posts: Post[] = []; - const platforms = this.getPlatforms(filters?.platforms); - const folders = this.getFolders(filters?.folders); - for (const platform of platforms) { - for (const folder of folders) { - const post = platform.getPost(folder); - if (!post.valid) { - throw new Error('Post is not valid'); - } - if (post.status!==PostStatus.UNSCHEDULED) { - throw new Error('Post is not unscheduled'); - } - post.schedule(date); - posts.push(post); - } - } - return posts; + async publishPost( + path: string, + platformId: PlatformId, + dryrun: boolean = false, + ): Promise { + Logger.trace("Feed", "publishPost", path, platformId, dryrun); + const now = new Date(); + const platform = this.getPlatform(platformId); + const folder = this.getFolder(path); + const post = platform.getPost(folder); + if (post.valid) { + post.schedule(now); + Logger.info("Posting", platformId, path); + await platform.publishPost(post, dryrun); + } else { + throw new Error("Post is not valid"); } + return post; + } - async publishPost( - path:string, - platformId:PlatformId, - dryrun:boolean = false - ): Promise { - Logger.trace('Feed','publishPost',path,platformId,dryrun); - const now = new Date(); - const platform = this.getPlatform(platformId); - const folder = this.getFolder(path); + async publishPosts( + filters?: { + folders?: string[]; + platforms?: PlatformId[]; + }, + dryrun: boolean = false, + ): Promise { + Logger.trace("Feed", "publishPosts", filters, dryrun); + const now = new Date(); + const posts: Post[] = []; + const platforms = this.getPlatforms(filters?.platforms); + const folders = this.getFolders(filters?.folders); + for (const platform of platforms) { + for (const folder of folders) { const post = platform.getPost(folder); if (post.valid) { - post.schedule(now); - Logger.info('Posting',platformId,path); - await platform.publishPost(post,dryrun); + post.schedule(now); + Logger.trace("Posting", platform.id, folder.id); + await platform.publishPost(post, dryrun); + posts.push(post); } else { - throw new Error('Post is not valid'); - } - return post; - } - - async publishPosts(filters?: { - folders?:string[] - platforms?:PlatformId[] - }, dryrun:boolean = false): Promise { - Logger.trace('Feed','publishPosts',filters,dryrun); - const now = new Date(); - const posts: Post[] = []; - const platforms = this.getPlatforms(filters?.platforms); - const folders = this.getFolders(filters?.folders); - for (const platform of platforms) { - for (const folder of folders) { - const post = platform.getPost(folder); - if (post.valid) { - post.schedule(now); - Logger.trace('Posting',platform.id,folder.id); - await platform.publishPost(post,dryrun); - posts.push(post); - } else { - Logger.warn('Skipping invalid post',platform.id,folder.id); - } - } + Logger.warn("Skipping invalid post", platform.id, folder.id); } - return posts; + } } + return posts; + } - /* + /* feed planning */ - getLastPost(platformId:PlatformId): Post | void { - Logger.trace('Feed','getLastPost'); - let lastPost: Post = undefined; - const posts = this.getPosts({ - platforms: [platformId], - status: PostStatus.PUBLISHED - }); - for (const post of posts) { - if (!lastPost || post.published >= lastPost.published) { - lastPost = post; - } - } - return lastPost; + getLastPost(platformId: PlatformId): Post | void { + Logger.trace("Feed", "getLastPost"); + let lastPost: Post = undefined; + const posts = this.getPosts({ + platforms: [platformId], + status: PostStatus.PUBLISHED, + }); + for (const post of posts) { + if (!lastPost || post.published >= lastPost.published) { + lastPost = post; + } } - - - getNextPostDate(platformId:PlatformId): Date { - Logger.trace('Feed','getNextPostDate'); - let nextDate = null; - const lastPost = this.getLastPost(platformId); - if (lastPost) { - nextDate = new Date(lastPost.published); - nextDate.setDate(nextDate.getDate()+this.interval); - } else { - nextDate = new Date(); - } - return nextDate; + return lastPost; + } + + getNextPostDate(platformId: PlatformId): Date { + Logger.trace("Feed", "getNextPostDate"); + let nextDate = null; + const lastPost = this.getLastPost(platformId); + if (lastPost) { + nextDate = new Date(lastPost.published); + nextDate.setDate(nextDate.getDate() + this.interval); + } else { + nextDate = new Date(); } + return nextDate; + } - scheduleNextPosts(date?: Date, filters?: { - folders?:string[] - platforms?:PlatformId[] - }): Post[] { - Logger.trace('Feed','scheduleNextPosts'); - const posts: Post[] = []; - const platforms = this.getPlatforms(filters?.platforms); - const folders = this.getFolders(filters?.folders); - for (const platform of platforms) { - const nextDate = date?date:this.getNextPostDate(platform.id); - for (const folder of folders) { - const post = platform.getPost(folder); - if (post.valid && post?.status===PostStatus.UNSCHEDULED) { - post.schedule(nextDate); - posts.push(post); - break; - } - - } + scheduleNextPosts( + date?: Date, + filters?: { + folders?: string[]; + platforms?: PlatformId[]; + }, + ): Post[] { + Logger.trace("Feed", "scheduleNextPosts"); + const posts: Post[] = []; + const platforms = this.getPlatforms(filters?.platforms); + const folders = this.getFolders(filters?.folders); + for (const platform of platforms) { + const nextDate = date ? date : this.getNextPostDate(platform.id); + for (const folder of folders) { + const post = platform.getPost(folder); + if (post.valid && post?.status === PostStatus.UNSCHEDULED) { + post.schedule(nextDate); + posts.push(post); + break; } - return posts; + } } + return posts; + } - async publishDuePosts(filters?: { - folders?:string[] - platforms?:PlatformId[] - }, dryrun:boolean = false): Promise { - Logger.trace('Feed','publishDuePosts'); - const now = new Date(); - const posts: Post[] = []; - const platforms = this.getPlatforms(filters?.platforms); - const folders = this.getFolders(filters?.folders); - for (const platform of platforms) { - for (const folder of folders) { - const post = platform.getPost(folder); - if (post?.status===PostStatus.SCHEDULED) { - if (post.scheduled <= now) { - console.log('Posting',platform.id,folder.id); - await platform.publishPost(post,dryrun); - posts.push(post); - break; - } - } - } + async publishDuePosts( + filters?: { + folders?: string[]; + platforms?: PlatformId[]; + }, + dryrun: boolean = false, + ): Promise { + Logger.trace("Feed", "publishDuePosts"); + const now = new Date(); + const posts: Post[] = []; + const platforms = this.getPlatforms(filters?.platforms); + const folders = this.getFolders(filters?.folders); + for (const platform of platforms) { + for (const folder of folders) { + const post = platform.getPost(folder); + if (post?.status === PostStatus.SCHEDULED) { + if (post.scheduled <= now) { + console.log("Posting", platform.id, folder.id); + await platform.publishPost(post, dryrun); + posts.push(post); + break; + } } - return posts; + } } - - -} \ No newline at end of file + return posts; + } +} diff --git a/src/Folder.ts b/src/Folder.ts index 540b6ec..95d5694 100644 --- a/src/Folder.ts +++ b/src/Folder.ts @@ -1,15 +1,14 @@ -import * as fs from 'fs'; -import Logger from './Logger'; +import * as fs from "fs"; +import Logger from "./Logger"; export default class Folder { - id: string; path: string; files?: { - text: string[], - image: string[], - video: string[], - other: string[] + text: string[]; + image: string[]; + video: string[]; + other: string[]; }; constructor(path: string) { @@ -18,40 +17,48 @@ export default class Folder { } getFiles() { - Logger.trace('Folder','getFiles'); - if (this.files!=undefined) { + Logger.trace("Folder", "getFiles"); + if (this.files != undefined) { return { - text: [ ...this.files.text ], - image: [ ...this.files.image ], - video: [ ...this.files.video ], - other: [ ...this.files.other ] + text: [...this.files.text], + image: [...this.files.image], + video: [...this.files.video], + other: [...this.files.other], }; } - const files = fs.readdirSync(this.path).filter(file => { - return fs.statSync(this.path+'/'+file).isFile() && - !file.startsWith('_') && - !file.startsWith('.'); + const files = fs.readdirSync(this.path).filter((file) => { + return ( + fs.statSync(this.path + "/" + file).isFile() && + !file.startsWith("_") && + !file.startsWith(".") + ); }); this.files = { text: [], image: [], video: [], - other: [] + other: [], }; - this.files.text = files.filter(file=>["txt"].includes(file.split('.')?.pop()??'')); - this.files.image = files.filter(file=>["jpg","jpeg","png"].includes(file.split('.')?.pop()??'')); - this.files.video = files.filter(file=>["mp4"].includes(file.split('.')?.pop()??'')); - this.files.other = files.filter(file=> - !this.files.text?.includes(file) - && !this.files.image?.includes(file) - && !this.files.video?.includes(file) + this.files.text = files.filter((file) => + ["txt"].includes(file.split(".")?.pop() ?? ""), + ); + this.files.image = files.filter((file) => + ["jpg", "jpeg", "png"].includes(file.split(".")?.pop() ?? ""), + ); + this.files.video = files.filter((file) => + ["mp4"].includes(file.split(".")?.pop() ?? ""), + ); + this.files.other = files.filter( + (file) => + !this.files.text?.includes(file) && + !this.files.image?.includes(file) && + !this.files.video?.includes(file), ); return { - text: [ ...this.files.text ], - image: [ ...this.files.image ], - video: [ ...this.files.video ], - other: [ ...this.files.other ] + text: [...this.files.text], + image: [...this.files.image], + video: [...this.files.video], + other: [...this.files.other], }; } - -} \ No newline at end of file +} diff --git a/src/Logger.ts b/src/Logger.ts index 920b96f..9b2f1bf 100644 --- a/src/Logger.ts +++ b/src/Logger.ts @@ -1,4 +1,4 @@ import * as log4js from "log4js"; -import * as path from 'path'; -log4js.configure(path.resolve(__dirname+'/../../log4js.json')); -export default log4js.getLogger('default'); +import * as path from "path"; +log4js.configure(path.resolve(__dirname + "/../../log4js.json")); +export default log4js.getLogger("default"); diff --git a/src/Platform.ts b/src/Platform.ts index 474f12c..43ad5a1 100644 --- a/src/Platform.ts +++ b/src/Platform.ts @@ -1,138 +1,133 @@ -import * as fs from 'fs'; -import Logger from './Logger'; +import * as fs from "fs"; +import Logger from "./Logger"; import Folder from "./Folder"; import Post from "./Post"; import { PostStatus } from "./Post"; import { PlatformId } from "./platforms"; export default class Platform { - - active: boolean = false; - id: PlatformId = PlatformId.UNKNOWN; - defaultBody: string = "Fairpost feed"; - - /* - * getPostFileName - * - * Return the intended name for a post of this - * platform to be saved in this folder. - */ - getPostFileName() { - return '_'+this.id+'.json'; + active: boolean = false; + id: PlatformId = PlatformId.UNKNOWN; + defaultBody: string = "Fairpost feed"; + + /* + * getPostFileName + * + * Return the intended name for a post of this + * platform to be saved in this folder. + */ + getPostFileName() { + return "_" + this.id + ".json"; + } + + /* + * getPost + * + * Return the post for this platform for the + * given folder, if it exists. + */ + + getPost(folder: Folder): Post | undefined { + Logger.trace("Platform", "getPost"); + + if (fs.existsSync(folder.path + "/" + this.getPostFileName())) { + const data = JSON.parse( + fs.readFileSync(folder.path + "/" + this.getPostFileName(), "utf8"), + ); + if (data) { + return new Post(folder, this, data); + } + } + return; + } + + /* + * preparePost + * + * Prepare the post for this platform for the + * given folder, and save it. Optionally create + * derivates of media and save those, too. + * + * If the post exists and is published, ignore. + * If the post exists and is failed, set it back to + * unscheduled. + */ + async preparePost(folder: Folder): Promise { + Logger.trace("Platform", "preparePost"); + + const post = this.getPost(folder) ?? new Post(folder, this); + if (post.status === PostStatus.PUBLISHED) { + return; + } + if (post.status === PostStatus.FAILED) { + post.status = PostStatus.UNSCHEDULED; } - /* - * getPost - * - * Return the post for this platform for the - * given folder, if it exists. - */ - - getPost(folder: Folder): Post | undefined { + // some default logic. override this + // in your own platform if you need. - Logger.trace('Platform','getPost'); + post.files = folder.getFiles(); - if (fs.existsSync(folder.path+'/'+this.getPostFileName())) { - const data = JSON.parse(fs.readFileSync(folder.path+'/'+this.getPostFileName(), 'utf8')); - if (data) { - return new Post(folder,this,data); - } - } - return; + if (post.files.text?.includes("body.txt")) { + post.body = fs.readFileSync(post.folder.path + "/body.txt", "utf8"); + } else if (post.files.text.length === 1) { + const bodyFile = post.files.text[0]; + post.body = fs.readFileSync(post.folder.path + "/" + bodyFile, "utf8"); + } else { + post.body = this.defaultBody; } - /* - * preparePost - * - * Prepare the post for this platform for the - * given folder, and save it. Optionally create - * derivates of media and save those, too. - * - * If the post exists and is published, ignore. - * If the post exists and is failed, set it back to - * unscheduled. - */ - async preparePost(folder: Folder): Promise { - - Logger.trace('Platform','preparePost'); - - const post = this.getPost(folder) ?? new Post(folder,this); - if (post.status===PostStatus.PUBLISHED) { - return; - } - if (post.status===PostStatus.FAILED) { - post.status=PostStatus.UNSCHEDULED; - } - - - // some default logic. override this - // in your own platform if you need. - - post.files = folder.getFiles(); - - if (post.files.text?.includes('body.txt')) { - post.body = fs.readFileSync(post.folder.path+'/body.txt','utf8'); - } else if (post.files.text.length === 1 ) { - const bodyFile = post.files.text[0]; - post.body = fs.readFileSync(post.folder.path+'/'+bodyFile,'utf8'); - } else { - post.body = this.defaultBody; - } - - if (post.files.text?.includes('title.txt')) { - post.title = fs.readFileSync(post.folder.path+'/title.txt','utf8'); - } else { - post.title = post.body.split('\n', 1)[0]; - } - - if (post.files.text?.includes('tags.txt')) { - post.tags = fs.readFileSync(post.folder.path+'/tags.txt','utf8'); - } - - if (post.title) { - post.valid = true; - } - - if (post.status===PostStatus.UNKNOWN) { - post.status=PostStatus.UNSCHEDULED; - } - - post.save(); - return post; + if (post.files.text?.includes("title.txt")) { + post.title = fs.readFileSync(post.folder.path + "/title.txt", "utf8"); + } else { + post.title = post.body.split("\n", 1)[0]; } - /* - * publishPost - * - * publish the post for this platform, sync. - * set the posted date to now. - * add the result to post.results - * on success, set the status to published and return true, - * else set the status to failed and return false - */ - - async publishPost(post: Post, dryrun:boolean = false): Promise { - - Logger.trace('Platform','publishPost'); - post.results.push({ - error: 'publishing not implemented for '+this.id - }); - post.published = undefined; - post.status = PostStatus.FAILED; - post.save(); - return false; + if (post.files.text?.includes("tags.txt")) { + post.tags = fs.readFileSync(post.folder.path + "/tags.txt", "utf8"); } - /* - * test - * - * Test the platform installation. This should not post - * anything, but test access tokens et al. It can return - * anything. - */ - async test(): Promise { - return 'No tests'; + if (post.title) { + post.valid = true; } -} + if (post.status === PostStatus.UNKNOWN) { + post.status = PostStatus.UNSCHEDULED; + } + post.save(); + return post; + } + + /* + * publishPost + * + * publish the post for this platform, sync. + * set the posted date to now. + * add the result to post.results + * on success, set the status to published and return true, + * else set the status to failed and return false + */ + + async publishPost(post: Post, dryrun: boolean = false): Promise { + Logger.trace("Platform", "publishPost"); + post.results.push({ + error: "publishing not implemented for " + this.id, + }); + post.published = undefined; + post.status = PostStatus.FAILED; + post.save(); + return false; + } + + /* + * test + * + * Test the platform installation. This should not post + * anything, but test access tokens et al. It can return + * anything. + */ + async test(): Promise { + return "No tests"; + } +} diff --git a/src/Post.ts b/src/Post.ts index 556863e..6327d4b 100644 --- a/src/Post.ts +++ b/src/Post.ts @@ -1,6 +1,5 @@ - -import * as fs from 'fs'; -import Logger from './Logger'; +import * as fs from "fs"; +import Logger from "./Logger"; import Folder from "./Folder"; import Platform from "./Platform"; @@ -13,71 +12,68 @@ export default class Post { scheduled?: Date; published?: Date; results: {}[] = []; - title: string = ''; + title: string = ""; body?: string; tags?: string; files?: { - text: string[], - image: string[], - video: string[], - other: string[] + text: string[]; + image: string[]; + video: string[]; + other: string[]; }; constructor(folder: Folder, platform: Platform, data?: any) { this.folder = folder; this.platform = platform; - this.id = this.folder.id+':'+this.platform.id; + this.id = this.folder.id + ":" + this.platform.id; if (data) { Object.assign(this, data); - this.scheduled = data.scheduled ? new Date(data.scheduled): undefined; - this.published = data.published ? new Date(data.published): undefined; + this.scheduled = data.scheduled ? new Date(data.scheduled) : undefined; + this.published = data.published ? new Date(data.published) : undefined; } } - /* - * save - * - * Save this post for this platform for the - * given folder. - */ + * save + * + * Save this post for this platform for the + * given folder. + */ save(): void { - Logger.trace('Post','save'); - const data = { ...this}; + Logger.trace("Post", "save"); + const data = { ...this }; delete data.folder; delete data.platform; fs.writeFileSync( - this.folder.path+'/'+this.platform.getPostFileName(), - JSON.stringify(data,null,"\t") + this.folder.path + "/" + this.platform.getPostFileName(), + JSON.stringify(data, null, "\t"), ); } - schedule(date:Date): void { - Logger.trace('Post','schedule'); + schedule(date: Date): void { + Logger.trace("Post", "schedule"); this.scheduled = date; this.status = PostStatus.SCHEDULED; this.save(); } report(): string { - Logger.trace('Post','report'); - let report = ''; - report += '\nPost: '+this.id; - report += '\n - valid: '+this.valid; - report += '\n - status: '+this.status; - report += '\n - scheduled: '+this.scheduled; - report += '\n - published: '+this.published; + Logger.trace("Post", "report"); + let report = ""; + report += "\nPost: " + this.id; + report += "\n - valid: " + this.valid; + report += "\n - status: " + this.status; + report += "\n - scheduled: " + this.scheduled; + report += "\n - published: " + this.published; return report; } - } export enum PostStatus { - UNKNOWN = "unknown", - UNSCHEDULED = "unscheduled", - SCHEDULED = "scheduled", - PUBLISHED = "published", - FAILED = "failed" + UNKNOWN = "unknown", + UNSCHEDULED = "unscheduled", + SCHEDULED = "scheduled", + PUBLISHED = "published", + FAILED = "failed", } - diff --git a/src/platforms/AsFacebook.ts b/src/platforms/AsFacebook.ts index b43e80f..6d768a9 100644 --- a/src/platforms/AsFacebook.ts +++ b/src/platforms/AsFacebook.ts @@ -1,41 +1,42 @@ - -import Logger from '../Logger'; +import Logger from "../Logger"; import Ayrshare from "./Ayrshare"; import { PlatformId } from "."; import Folder from "../Folder"; import Post from "../Post"; -import * as fs from 'fs'; -import * as sharp from 'sharp'; +import * as fs from "fs"; +import * as sharp from "sharp"; export default class AsFacebook extends Ayrshare { - id: PlatformId = PlatformId.ASFACEBOOK; + id: PlatformId = PlatformId.ASFACEBOOK; - constructor() { - super(); - } + constructor() { + super(); + } - async preparePost(folder: Folder): Promise { - const post = await super.preparePost(folder); - if (post) { - // facebook : max 10mb images - for (const image of post.files.image) { - var size = fs.statSync(post.folder.path+'/'+image).size / (1024*1024); - if (size>=10) { - console.log('Resizing '+image+' for facebook ..'); - await sharp(post.folder.path+'/'+image).resize({ - width: 1200 - }).toFile(post.folder.path+'/_facebook-'+image); - post.files.image.push('_facebook-'+image); - post.files.image = post.files.image.filter(file => file !== image); - } - } - post.save(); + async preparePost(folder: Folder): Promise { + const post = await super.preparePost(folder); + if (post) { + // facebook : max 10mb images + for (const image of post.files.image) { + var size = + fs.statSync(post.folder.path + "/" + image).size / (1024 * 1024); + if (size >= 10) { + console.log("Resizing " + image + " for facebook .."); + await sharp(post.folder.path + "/" + image) + .resize({ + width: 1200, + }) + .toFile(post.folder.path + "/_facebook-" + image); + post.files.image.push("_facebook-" + image); + post.files.image = post.files.image.filter((file) => file !== image); } - return post; - } - - async publishPost(post: Post, dryrun:boolean = false): Promise { - return super.publishPost(post,{},dryrun); + } + post.save(); } + return post; + } -} \ No newline at end of file + async publishPost(post: Post, dryrun: boolean = false): Promise { + return super.publishPost(post, {}, dryrun); + } +} diff --git a/src/platforms/AsInstagram.ts b/src/platforms/AsInstagram.ts index 3cd4211..262498c 100644 --- a/src/platforms/AsInstagram.ts +++ b/src/platforms/AsInstagram.ts @@ -1,57 +1,62 @@ - -import Logger from '../Logger'; +import Logger from "../Logger"; import Ayrshare from "./Ayrshare"; import { PlatformId } from "."; import Folder from "../Folder"; import Post from "../Post"; -import * as sharp from 'sharp'; +import * as sharp from "sharp"; export default class AsInstagram extends Ayrshare { - id = PlatformId.ASINSTAGRAM; + id = PlatformId.ASINSTAGRAM; - constructor() { - super(); - } + constructor() { + super(); + } - async preparePost(folder: Folder): Promise { - const post = await super.preparePost(folder); - if (post) { - // instagram: 1 video for reel - if (post.files.video.length) { - Logger.trace('Removing images for instagram reel..'); - post.files.image = []; - if (post.files.video.length > 1) { - Logger.trace('Using first video for instagram reel..'); - post.files.video = [post.files.video[0]]; - } - } - // instagram : scale images - for (const image of post.files.image) { - const metadata = await sharp(post.folder.path+'/'+image).metadata(); - if (metadata.width > 1440) { - Logger.trace('Resizing '+image+' for instagram ..'); - await sharp(post.folder.path+'/'+image).resize({ - width: 1440 - }).toFile(post.folder.path+'/_instagram-'+image); - post.files.image.push('_instagram-'+image); - post.files.image = post.files.image.filter(file => file !== image); - } - } - // instagram: require media - if (post.files.image.length+post.files.video.length === 0) { - post.valid = false; - } - post.save(); + async preparePost(folder: Folder): Promise { + const post = await super.preparePost(folder); + if (post) { + // instagram: 1 video for reel + if (post.files.video.length) { + Logger.trace("Removing images for instagram reel.."); + post.files.image = []; + if (post.files.video.length > 1) { + Logger.trace("Using first video for instagram reel.."); + post.files.video = [post.files.video[0]]; } - return post; + } + // instagram : scale images + for (const image of post.files.image) { + const metadata = await sharp(post.folder.path + "/" + image).metadata(); + if (metadata.width > 1440) { + Logger.trace("Resizing " + image + " for instagram .."); + await sharp(post.folder.path + "/" + image) + .resize({ + width: 1440, + }) + .toFile(post.folder.path + "/_instagram-" + image); + post.files.image.push("_instagram-" + image); + post.files.image = post.files.image.filter((file) => file !== image); + } + } + // instagram: require media + if (post.files.image.length + post.files.video.length === 0) { + post.valid = false; + } + post.save(); } + return post; + } - async publishPost(post: Post, dryrun:boolean = false): Promise { - return super.publishPost(post,{ - isVideo: post.files.video.length!==0, - instagramOptions: { - // "autoResize": true -- only enterprise plans - } - },dryrun); - } -} \ No newline at end of file + async publishPost(post: Post, dryrun: boolean = false): Promise { + return super.publishPost( + post, + { + isVideo: post.files.video.length !== 0, + instagramOptions: { + // "autoResize": true -- only enterprise plans + }, + }, + dryrun, + ); + } +} diff --git a/src/platforms/AsLinkedIn.ts b/src/platforms/AsLinkedIn.ts index 166bf4c..9c42834 100644 --- a/src/platforms/AsLinkedIn.ts +++ b/src/platforms/AsLinkedIn.ts @@ -1,47 +1,52 @@ - -import Logger from '../Logger'; +import Logger from "../Logger"; import Ayrshare from "./Ayrshare"; import { PlatformId } from "."; import Folder from "../Folder"; import Post from "../Post"; -import * as fs from 'fs'; -import * as sharp from 'sharp'; +import * as fs from "fs"; +import * as sharp from "sharp"; export default class AsLinkedIn extends Ayrshare { - id = PlatformId.ASLINKEDIN; + id = PlatformId.ASLINKEDIN; - constructor() { - super(); - } + constructor() { + super(); + } - async preparePost(folder: Folder): Promise { - const post = await super.preparePost(folder); - if (post) { - // linkedin: max 9 media - if (post.files.video.length > 9) { - post.files.video.length = 9; - } - if (post.files.image.length + post.files.video.length > 9 ) { - post.files.image.length = Math.max(0,post.files.image.length - post.files.video.length); - } - // linkedin: max 5mb images - for (const image of post.files.image) { - var size = fs.statSync(post.folder.path+'/'+image).size / (1024*1024); - if (size>=5) { - Logger.trace('Resizing '+image+' for linkedin ..'); - await sharp(post.folder.path+'/'+image).resize({ - width: 1200 - }).toFile(post.folder.path+'/_linkedin-'+image); - post.files.image.push('_linkedin-'+image); - post.files.image = post.files.image.filter(file => file !== image); - } - } - post.save(); + async preparePost(folder: Folder): Promise { + const post = await super.preparePost(folder); + if (post) { + // linkedin: max 9 media + if (post.files.video.length > 9) { + post.files.video.length = 9; + } + if (post.files.image.length + post.files.video.length > 9) { + post.files.image.length = Math.max( + 0, + post.files.image.length - post.files.video.length, + ); + } + // linkedin: max 5mb images + for (const image of post.files.image) { + var size = + fs.statSync(post.folder.path + "/" + image).size / (1024 * 1024); + if (size >= 5) { + Logger.trace("Resizing " + image + " for linkedin .."); + await sharp(post.folder.path + "/" + image) + .resize({ + width: 1200, + }) + .toFile(post.folder.path + "/_linkedin-" + image); + post.files.image.push("_linkedin-" + image); + post.files.image = post.files.image.filter((file) => file !== image); } - return post; + } + post.save(); } + return post; + } - async publishPost(post: Post, dryrun:boolean = false): Promise { - return super.publishPost(post,{},dryrun); - } -} \ No newline at end of file + async publishPost(post: Post, dryrun: boolean = false): Promise { + return super.publishPost(post, {}, dryrun); + } +} diff --git a/src/platforms/AsReddit.ts b/src/platforms/AsReddit.ts index b65886e..bc0a4bd 100644 --- a/src/platforms/AsReddit.ts +++ b/src/platforms/AsReddit.ts @@ -1,39 +1,41 @@ -import Logger from '../Logger'; +import Logger from "../Logger"; import Ayrshare from "./Ayrshare"; import { PlatformId } from "."; import Folder from "../Folder"; import Post from "../Post"; export default class AsReddit extends Ayrshare { - id = PlatformId.ASREDDIT; - SUBREDDIT: string; + id = PlatformId.ASREDDIT; + SUBREDDIT: string; - constructor() { - super(); - this.SUBREDDIT = process.env.FAIRPOST_REDDIT_SUBREDDIT; - } - - - async preparePost(folder: Folder): Promise { - const post = await super.preparePost(folder); - if (post) { - // reddit: max 1 image, no video - post.files.video = []; - if (post.files.image.length > 1 ) { - post.files.image.length = 1; - } - post.save(); - } - return post; - } + constructor() { + super(); + this.SUBREDDIT = process.env.FAIRPOST_REDDIT_SUBREDDIT; + } - async publishPost(post: Post, dryrun:boolean = false): Promise { - return super.publishPost(post,{ - redditOptions: { - title: post.title, // required - subreddit: this.SUBREDDIT, // required (no "/r/" needed) - } - },dryrun); + async preparePost(folder: Folder): Promise { + const post = await super.preparePost(folder); + if (post) { + // reddit: max 1 image, no video + post.files.video = []; + if (post.files.image.length > 1) { + post.files.image.length = 1; + } + post.save(); } + return post; + } -} \ No newline at end of file + async publishPost(post: Post, dryrun: boolean = false): Promise { + return super.publishPost( + post, + { + redditOptions: { + title: post.title, // required + subreddit: this.SUBREDDIT, // required (no "/r/" needed) + }, + }, + dryrun, + ); + } +} diff --git a/src/platforms/AsTikTok.ts b/src/platforms/AsTikTok.ts index 93a11bf..8c8b93f 100644 --- a/src/platforms/AsTikTok.ts +++ b/src/platforms/AsTikTok.ts @@ -1,32 +1,32 @@ -import Logger from '../Logger'; +import Logger from "../Logger"; import Ayrshare from "./Ayrshare"; import { PlatformId } from "."; import Folder from "../Folder"; import Post from "../Post"; export default class AsTikTok extends Ayrshare { - id = PlatformId.ASTIKTOK; + id = PlatformId.ASTIKTOK; - constructor() { - super(); - } + constructor() { + super(); + } - async preparePost(folder: Folder): Promise { - const post = await super.preparePost(folder); - if (post) { - // tiktok: one video - post.files.image = []; - if (!post.files.video.length) { - post.valid = false; - } else { - post.files.video.length = 1; - } - post.save(); - } - return post; + async preparePost(folder: Folder): Promise { + const post = await super.preparePost(folder); + if (post) { + // tiktok: one video + post.files.image = []; + if (!post.files.video.length) { + post.valid = false; + } else { + post.files.video.length = 1; + } + post.save(); } + return post; + } - async publishPost(post: Post, dryrun:boolean = false): Promise { - return super.publishPost(post,{},dryrun); - } -} \ No newline at end of file + async publishPost(post: Post, dryrun: boolean = false): Promise { + return super.publishPost(post, {}, dryrun); + } +} diff --git a/src/platforms/AsTwitter.ts b/src/platforms/AsTwitter.ts index 6b32d2d..948d3d3 100644 --- a/src/platforms/AsTwitter.ts +++ b/src/platforms/AsTwitter.ts @@ -1,46 +1,48 @@ -import Logger from '../Logger'; +import Logger from "../Logger"; import Ayrshare from "./Ayrshare"; import { PlatformId } from "."; import Folder from "../Folder"; import Post from "../Post"; -import * as fs from 'fs'; -import * as sharp from 'sharp'; +import * as fs from "fs"; +import * as sharp from "sharp"; export default class AsTwitter extends Ayrshare { - id = PlatformId.ASTWITTER; + id = PlatformId.ASTWITTER; - constructor() { - super(); - } + constructor() { + super(); + } - async preparePost(folder: Folder): Promise { - const post = await super.preparePost(folder); - if (post) { - // twitter: no video - post.files.video = []; - // twitter: max 4 images - if (post.files.image.length>4) { - post.files.image.length=4; - } - // twitter: max 5mb images - for (const image of post.files.image) { - var size = fs.statSync(post.folder.path+'/'+image).size / (1024*1024); - if (size>=5) { - Logger.trace('Resizing '+image+' for twitter ..'); - await sharp(post.folder.path+'/'+image).resize({ - width: 1200 - }).toFile(post.folder.path+'/_twitter-'+image); - post.files.image.push('_twitter-'+image); - post.files.image = post.files.image.filter(file => file !== image); - } - } - post.save(); + async preparePost(folder: Folder): Promise { + const post = await super.preparePost(folder); + if (post) { + // twitter: no video + post.files.video = []; + // twitter: max 4 images + if (post.files.image.length > 4) { + post.files.image.length = 4; + } + // twitter: max 5mb images + for (const image of post.files.image) { + var size = + fs.statSync(post.folder.path + "/" + image).size / (1024 * 1024); + if (size >= 5) { + Logger.trace("Resizing " + image + " for twitter .."); + await sharp(post.folder.path + "/" + image) + .resize({ + width: 1200, + }) + .toFile(post.folder.path + "/_twitter-" + image); + post.files.image.push("_twitter-" + image); + post.files.image = post.files.image.filter((file) => file !== image); } - return post; - } - - async publishPost(post: Post, dryrun:boolean = false): Promise { - return super.publishPost(post,{},dryrun); + } + post.save(); } + return post; + } -} \ No newline at end of file + async publishPost(post: Post, dryrun: boolean = false): Promise { + return super.publishPost(post, {}, dryrun); + } +} diff --git a/src/platforms/AsYouTube.ts b/src/platforms/AsYouTube.ts index defbca9..efd0de6 100644 --- a/src/platforms/AsYouTube.ts +++ b/src/platforms/AsYouTube.ts @@ -1,39 +1,42 @@ -import Logger from '../Logger'; +import Logger from "../Logger"; import Ayrshare from "./Ayrshare"; import { PlatformId } from "."; import Folder from "../Folder"; import Post from "../Post"; export default class AsYouTube extends Ayrshare { - id = PlatformId.ASYOUTUBE; + id = PlatformId.ASYOUTUBE; - constructor() { - super(); - } - - async preparePost(folder: Folder): Promise { - const post = await super.preparePost(folder); - if (post) { - // youtube: only 1 video - post.files.image = []; - if (post.files.video.length>1) { - post.files.video.length=1; - } - if (!post.files.video.length) { - post.valid = false; - } - post.save(); - } - return post; - } + constructor() { + super(); + } - async publishPost(post: Post, dryrun:boolean = false): Promise { - return super.publishPost(post,{ - youTubeOptions: { - title: post.title, // required max 100 - visibility: "public" // optional def private - }, - },dryrun); + async preparePost(folder: Folder): Promise { + const post = await super.preparePost(folder); + if (post) { + // youtube: only 1 video + post.files.image = []; + if (post.files.video.length > 1) { + post.files.video.length = 1; + } + if (!post.files.video.length) { + post.valid = false; + } + post.save(); } + return post; + } -} \ No newline at end of file + async publishPost(post: Post, dryrun: boolean = false): Promise { + return super.publishPost( + post, + { + youTubeOptions: { + title: post.title, // required max 100 + visibility: "public", // optional def private + }, + }, + dryrun, + ); + } +} diff --git a/src/platforms/Ayrshare.ts b/src/platforms/Ayrshare.ts index d09dc1d..3f4f3ce 100644 --- a/src/platforms/Ayrshare.ts +++ b/src/platforms/Ayrshare.ts @@ -1,8 +1,7 @@ - -import Logger from '../Logger'; -import * as fs from 'fs'; -import * as path from 'path'; -import { randomUUID } from 'crypto'; +import Logger from "../Logger"; +import * as fs from "fs"; +import * as path from "path"; +import { randomUUID } from "crypto"; import { PlatformId } from "."; import Platform from "../Platform"; import Folder from "../Folder"; @@ -10,140 +9,152 @@ import Post from "../Post"; import { PostStatus } from "../Post"; interface AyrshareResult { - success: boolean; - error?: Error; - response: {} + success: boolean; + error?: Error; + response: {}; } export default abstract class Ayrshare extends Platform { - - - requiresApproval: boolean = false; - - // map fairpost platforms to ayrshare platforms - platforms: { - [platformId in PlatformId]?: string - } = { - [PlatformId.ASYOUTUBE]: "youtube", - [PlatformId.ASINSTAGRAM]: "instagram", - [PlatformId.ASFACEBOOK]: "facebook", - [PlatformId.ASTWITTER]: "twitter", - [PlatformId.ASTIKTOK]: "tiktok", - [PlatformId.ASLINKEDIN]: "linkedin", - [PlatformId.ASREDDIT]: "reddit" - }; - - constructor() { - super(); + requiresApproval: boolean = false; + + // map fairpost platforms to ayrshare platforms + platforms: { + [platformId in PlatformId]?: string; + } = { + [PlatformId.ASYOUTUBE]: "youtube", + [PlatformId.ASINSTAGRAM]: "instagram", + [PlatformId.ASFACEBOOK]: "facebook", + [PlatformId.ASTWITTER]: "twitter", + [PlatformId.ASTIKTOK]: "tiktok", + [PlatformId.ASLINKEDIN]: "linkedin", + [PlatformId.ASREDDIT]: "reddit", + }; + + constructor() { + super(); + } + + async preparePost(folder: Folder): Promise { + return super.preparePost(folder); + } + + async publishPost( + post: Post, + platformOptions: {}, + dryrun: boolean = false, + ): Promise { + const media = [...post.files.image, ...post.files.video].map( + (f) => post.folder.path + "/" + f, + ); + const uploads = media.length ? await this.uploadMedia(media) : []; + if (dryrun) { + post.results.push({ + date: new Date(), + dryrun: true, + uploads: uploads, + success: true, + response: {}, + }); + post.save(); + return true; } - - async preparePost(folder: Folder): Promise { - return super.preparePost(folder); + const result = await this.publishAyrshare(post, platformOptions, uploads); + post.results.push(result); + if (result.success) { + post.status = PostStatus.PUBLISHED; + post.published = new Date(); } + post.save(); - async publishPost(post: Post, platformOptions: {}, dryrun:boolean = false): Promise { - const media = [ - ...post.files.image, - ...post.files.video - ].map(f=>post.folder.path+'/'+f); - const uploads = media.length ? await this.uploadMedia(media) : []; - if (dryrun) { - post.results.push({ - date: new Date(), - dryrun: true, - uploads: uploads, - success: true, - response: {} - }); - post.save(); - return true; - } - - const result = await this.publishAyrshare(post,platformOptions, uploads); - post.results.push(result); - if (result.success) { - post.status = PostStatus.PUBLISHED; - post.published = new Date(); - } - post.save(); - - if (!result.success) { - console.error(result.error); - } - return result.success ?? false; + if (!result.success) { + console.error(result.error); } - - async uploadMedia(media: string[]): Promise { - const APIKEY = process.env.FAIRPOST_AYRSHARE_API_KEY; - const urls= [] as string[]; - for (const file of media) { - const buffer = fs.readFileSync(file); - const ext = path.extname(file); - const basename = path.basename(file, ext); - const uname = basename+'-'+randomUUID()+ext; - Logger.trace('fetching uploadid...',file); - const res1 = await fetch("https://app.ayrshare.com/api/media/uploadUrl?fileName="+uname+"&contentType="+ext.substring(1), { - method: "GET", - headers: { - "Authorization": `Bearer ${APIKEY}` - } - }); - - if (!res1) { - return []; - } - - const data = await res1.json(); - //console.log(data); - Logger.trace('uploading..',uname); - const uploadUrl = data.uploadUrl; - const contentType = data.contentType; - const accessUrl = data.accessUrl; - - const res2 = await fetch(uploadUrl, { - method: "PUT", - headers: { - "Content-Type": contentType, - "Authorization": `Bearer ${APIKEY}` - }, - body: buffer, - }); - - if (!res2) { - return []; - } - - urls.push(accessUrl.replace(/ /g, '%20')); - - } - return urls; + return result.success ?? false; + } + + async uploadMedia(media: string[]): Promise { + const APIKEY = process.env.FAIRPOST_AYRSHARE_API_KEY; + const urls = [] as string[]; + for (const file of media) { + const buffer = fs.readFileSync(file); + const ext = path.extname(file); + const basename = path.basename(file, ext); + const uname = basename + "-" + randomUUID() + ext; + Logger.trace("fetching uploadid...", file); + const res1 = await fetch( + "https://app.ayrshare.com/api/media/uploadUrl?fileName=" + + uname + + "&contentType=" + + ext.substring(1), + { + method: "GET", + headers: { + Authorization: `Bearer ${APIKEY}`, + }, + }, + ); + + if (!res1) { + return []; + } + + const data = await res1.json(); + //console.log(data); + Logger.trace("uploading..", uname); + const uploadUrl = data.uploadUrl; + const contentType = data.contentType; + const accessUrl = data.accessUrl; + + const res2 = await fetch(uploadUrl, { + method: "PUT", + headers: { + "Content-Type": contentType, + Authorization: `Bearer ${APIKEY}`, + }, + body: buffer, + }); + + if (!res2) { + return []; + } + + urls.push(accessUrl.replace(/ /g, "%20")); } - - async publishAyrshare(post: Post, platformOptions: {}, uploads: string[]): Promise { - - const APIKEY = process.env.FAIRPOST_AYRSHARE_API_KEY; - const scheduleDate = post.scheduled; - //scheduleDate.setDate(scheduleDate.getDate()+100); - - const result = { - success: false, - error: undefined, - response: {} - } as AyrshareResult; - - const postPlatform = this.platforms[this.id]; - if (!postPlatform) { - result.error = new Error('No ayrshare platform associated with platform '+this.id); - return result; - } - const body = JSON.stringify(uploads.length?{ + return urls; + } + + async publishAyrshare( + post: Post, + platformOptions: {}, + uploads: string[], + ): Promise { + const APIKEY = process.env.FAIRPOST_AYRSHARE_API_KEY; + const scheduleDate = post.scheduled; + //scheduleDate.setDate(scheduleDate.getDate()+100); + + const result = { + success: false, + error: undefined, + response: {}, + } as AyrshareResult; + + const postPlatform = this.platforms[this.id]; + if (!postPlatform) { + result.error = new Error( + "No ayrshare platform associated with platform " + this.id, + ); + return result; + } + const body = JSON.stringify( + uploads.length + ? { post: post.body, // required platforms: [postPlatform], // required - mediaUrls: uploads, + mediaUrls: uploads, scheduleDate: scheduleDate, requiresApproval: this.requiresApproval, - ...platformOptions + ...platformOptions, /* youTubeOptions: { title: post.title, // required max 100 @@ -157,48 +168,54 @@ export default abstract class Ayrshare extends Platform { title: this.data.title, // required subreddit: REDDIT_SUBREDDIT, // required (no "/r/" needed) }*/ - - }:{ + } + : { post: post.body, // required platforms: [postPlatform], // required scheduleDate: scheduleDate, - requiresApproval: this.requiresApproval - }); - Logger.trace('scheduling...',postPlatform); - //console.log(body); - const res = await fetch("https://app.ayrshare.com/api/post", { - method: "POST", - headers: { - "Content-Type": "application/json", - "Authorization": `Bearer ${APIKEY}` - }, - body: body - }).catch(e=> { - result.error = e; - }); - - if (res) { - if (res.ok) { - //console.log(res.json()); - result.response = await res.json() as unknown as { - status?: string - }; - if (result.response['status']!=='success' && result.response['status']!=='scheduled') { - console.error('* Failed.'); - result.error = new Error('Bad result status: '+result.response['status']); - } else { - console.error(' .. Published.'); - result.success = true; - } - return result; - } - const response = await res.json(); - console.error('* Failed.'); - result.error = new Error(JSON.stringify(response)); - return result; + requiresApproval: this.requiresApproval, + }, + ); + Logger.trace("scheduling...", postPlatform); + //console.log(body); + const res = await fetch("https://app.ayrshare.com/api/post", { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${APIKEY}`, + }, + body: body, + }).catch((e) => { + result.error = e; + }); + + if (res) { + if (res.ok) { + //console.log(res.json()); + result.response = (await res.json()) as unknown as { + status?: string; + }; + if ( + result.response["status"] !== "success" && + result.response["status"] !== "scheduled" + ) { + console.error("* Failed."); + result.error = new Error( + "Bad result status: " + result.response["status"], + ); + } else { + console.error(" .. Published."); + result.success = true; } - console.error('* Failed.'); - result.error = new Error('no result'); return result; + } + const response = await res.json(); + console.error("* Failed."); + result.error = new Error(JSON.stringify(response)); + return result; } -} \ No newline at end of file + console.error("* Failed."); + result.error = new Error("no result"); + return result; + } +} diff --git a/src/platforms/Facebook.ts b/src/platforms/Facebook.ts index 5706d4c..4a7f741 100644 --- a/src/platforms/Facebook.ts +++ b/src/platforms/Facebook.ts @@ -1,310 +1,317 @@ - import Logger from "../Logger"; import Platform from "../Platform"; import { PlatformId } from "."; import Folder from "../Folder"; import Post from "../Post"; import { PostStatus } from "../Post"; -import * as fs from 'fs'; -import * as path from 'path'; -import * as sharp from 'sharp'; +import * as fs from "fs"; +import * as path from "path"; +import * as sharp from "sharp"; export default class Facebook extends Platform { - id: PlatformId = PlatformId.FACEBOOK; - GRAPH_API_VERSION: string = 'v18.0'; - - constructor() { - super(); - } - - async preparePost(folder: Folder): Promise { - const post = await super.preparePost(folder); - if (post && post.files) { - // facebook: video post can only contain 1 video - if (post.files.video.length) { - post.files.video.length = 1; - post.files.image = []; - } - // facebook : max 4mb images - for (const image of post.files.image) { - var size = fs.statSync(post.folder.path+'/'+image).size / (1024*1024); - if (size>=4) { - Logger.trace('Resizing '+image+' for facebook ..'); - await sharp(post.folder.path+'/'+image).resize({ - width: 1200 - }).toFile(post.folder.path+'/_facebook-'+image); - post.files.image.push('_facebook-'+image); - post.files.image = post.files.image.filter(file => file !== image); - } - } - post.save(); + id: PlatformId = PlatformId.FACEBOOK; + GRAPH_API_VERSION: string = "v18.0"; + + constructor() { + super(); + } + + async preparePost(folder: Folder): Promise { + const post = await super.preparePost(folder); + if (post && post.files) { + // facebook: video post can only contain 1 video + if (post.files.video.length) { + post.files.video.length = 1; + post.files.image = []; + } + // facebook : max 4mb images + for (const image of post.files.image) { + var size = + fs.statSync(post.folder.path + "/" + image).size / (1024 * 1024); + if (size >= 4) { + Logger.trace("Resizing " + image + " for facebook .."); + await sharp(post.folder.path + "/" + image) + .resize({ + width: 1200, + }) + .toFile(post.folder.path + "/_facebook-" + image); + post.files.image.push("_facebook-" + image); + post.files.image = post.files.image.filter((file) => file !== image); } - return post; + } + post.save(); } - - async publishPost(post: Post, dryrun:boolean = false): Promise { - - let result = dryrun ? { id: '-99' } : {} as {id: string}; - - if (post.files.video.length) { - if (!dryrun) { - result = await this.publishVideo(post.files.video[0],post.title,post.body); - } - } else { - const attachments = []; - if (post.files.image.length) { - for (const image of post.files.image) { - attachments.push({"media_fbid": await this.uploadPhoto(post.folder.path+'/'+image)}); - } - } - if (!dryrun) { - result = await this.post( - 'feed', - { - "message":post.body, - "published":process.env.FAIRPOST_FACEBOOK_PUBLISH_POSTS, - //"scheduled_publish_time":"tomorrow", - "attached_media": attachments - } - ); - } - } - - post.results.push(result); - if (result.id) { - if (!dryrun) { - post.status = PostStatus.PUBLISHED; - post.published = new Date(); - } - } else { - console.error(this.id,"No id returned in post",result); + return post; + } + + async publishPost(post: Post, dryrun: boolean = false): Promise { + let result = dryrun ? { id: "-99" } : ({} as { id: string }); + + if (post.files.video.length) { + if (!dryrun) { + result = await this.publishVideo( + post.files.video[0], + post.title, + post.body, + ); + } + } else { + const attachments = []; + if (post.files.image.length) { + for (const image of post.files.image) { + attachments.push({ + media_fbid: await this.uploadPhoto(post.folder.path + "/" + image), + }); } - post.save(); - return !!result.id; - + } + if (!dryrun) { + result = await this.post("feed", { + message: post.body, + published: process.env.FAIRPOST_FACEBOOK_PUBLISH_POSTS, + //"scheduled_publish_time":"tomorrow", + attached_media: attachments, + }); + } } - async test() { - return this.get(); + post.results.push(result); + if (result.id) { + if (!dryrun) { + post.status = PostStatus.PUBLISHED; + post.published = new Date(); + } + } else { + console.error(this.id, "No id returned in post", result); } - - - /* - * Do a GET request on the page. - * - * arguments: - * endpoint: the path to call - * query: query string as object - */ - - private async get( - endpoint: string = '', - query: { [key:string]: string } = {} - ) { - const url = new URL('https://graph.facebook.com'); - url.pathname = this.GRAPH_API_VERSION + "/" + process.env.FAIRPOST_FACEBOOK_PAGE_ID; - url.pathname += "/" + endpoint, - url.search = new URLSearchParams(query).toString(); - Logger.trace('GET',url.href); - const res = await fetch(url,{ - method: 'GET', - headers: process.env.FAIRPOST_FACEBOOK_PAGE_ACCESS_TOKEN ? { - 'Accept': 'application/json', - 'Authorization': 'Bearer '+process.env.FAIRPOST_FACEBOOK_PAGE_ACCESS_TOKEN - }: { - 'Accept': 'application/json' - } - }); - const result = await res.json(); - return result; + post.save(); + return !!result.id; + } + + async test() { + return this.get(); + } + + /* + * Do a GET request on the page. + * + * arguments: + * endpoint: the path to call + * query: query string as object + */ + + private async get( + endpoint: string = "", + query: { [key: string]: string } = {}, + ) { + const url = new URL("https://graph.facebook.com"); + url.pathname = + this.GRAPH_API_VERSION + "/" + process.env.FAIRPOST_FACEBOOK_PAGE_ID; + (url.pathname += "/" + endpoint), + (url.search = new URLSearchParams(query).toString()); + Logger.trace("GET", url.href); + const res = await fetch(url, { + method: "GET", + headers: process.env.FAIRPOST_FACEBOOK_PAGE_ACCESS_TOKEN + ? { + Accept: "application/json", + Authorization: + "Bearer " + process.env.FAIRPOST_FACEBOOK_PAGE_ACCESS_TOKEN, + } + : { + Accept: "application/json", + }, + }); + const result = await res.json(); + return result; + } + + /* + * Do a POST request on the page. + * + * arguments: + * endpoint: the path to call + * body: body as object + */ + + private async post(endpoint: string = "", body = {}) { + const url = new URL("https://graph.facebook.com"); + url.pathname = + this.GRAPH_API_VERSION + "/" + process.env.FAIRPOST_FACEBOOK_PAGE_ID; + (url.pathname += "/" + endpoint), Logger.trace("POST", url.href); + const res = await fetch(url, { + method: "POST", + headers: { + Accept: "application/json", + "Content-Type": "application/json", + Authorization: + "Bearer " + process.env.FAIRPOST_FACEBOOK_PAGE_ACCESS_TOKEN, + }, + body: JSON.stringify(body), + }); + const result = await res.json(); + return result; + } + + /* + * POST an image to the /photos endpoint using multipart/form-data + * + * arguments: + * file: path to the file to post + * + * returns: + * id of the uploaded photo to use in post attachments + */ + private async uploadPhoto( + file: string = "", + published = false, + ): Promise { + Logger.trace("Reading file", file); + const rawData = fs.readFileSync(file); + const blob = new Blob([rawData]); + + const url = new URL("https://graph.facebook.com"); + url.pathname = + this.GRAPH_API_VERSION + "/" + process.env.FAIRPOST_FACEBOOK_PAGE_ID; + url.pathname += "/photos"; + + const body = new FormData(); + body.set("published", published ? "true" : "false"); + body.set("source", blob, path.basename(file)); + + Logger.trace("POST", url.href); + const res = await fetch(url, { + method: "POST", + headers: { + Accept: "application/json", + Authorization: + "Bearer " + process.env.FAIRPOST_FACEBOOK_PAGE_ACCESS_TOKEN, + }, + body, + }); + + const result = await res.json(); + if (!result["id"]) { + console.error(result); + throw new Error("No id returned when uploading photo"); } - - /* - * Do a POST request on the page. - * - * arguments: - * endpoint: the path to call - * body: body as object - */ - - private async post( - endpoint: string = '', - body = {} - ) { - const url = new URL('https://graph.facebook.com'); - url.pathname = this.GRAPH_API_VERSION + "/" + process.env.FAIRPOST_FACEBOOK_PAGE_ID; - url.pathname += "/" + endpoint, - Logger.trace('POST',url.href); - const res = await fetch(url,{ - method: 'POST', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': 'Bearer '+process.env.FAIRPOST_FACEBOOK_PAGE_ACCESS_TOKEN - }, - body: JSON.stringify(body) - }); - const result = await res.json(); - return result; - + return result["id"]; + } + + /* + * POST a video to the page using multipart/form-data + * + * arguments: + * file: path to the video to post + * published: wether to publish it or not + * + * returns: + * { id: string } + */ + private async publishVideo( + file: string, + title: string, + description: string, + ): Promise<{ id: string }> { + Logger.trace("Reading file", file); + const rawData = fs.readFileSync(file); + const blob = new Blob([rawData]); + + const url = new URL("https://graph.facebook.com"); + url.pathname = + this.GRAPH_API_VERSION + "/" + process.env.FAIRPOST_FACEBOOK_PAGE_ID; + url.pathname += "/videos"; + + const body = new FormData(); + body.set("title", title); + body.set("description", description); + body.set("published", process.env.FAIRPOST_FACEBOOK_PUBLISH_POSTS); + body.set("source", blob, path.basename(file)); + + Logger.trace("POST", url.href); + const res = await fetch(url, { + method: "POST", + headers: { + Accept: "application/json", + Authorization: + "Bearer " + process.env.FAIRPOST_FACEBOOK_PAGE_ACCESS_TOKEN, + }, + body, + }); + + const result = await res.json(); + if (!result["id"]) { + console.error(result); + throw new Error("No id returned when uploading video"); } - - /* - * POST an image to the /photos endpoint using multipart/form-data - * - * arguments: - * file: path to the file to post - * - * returns: - * id of the uploaded photo to use in post attachments - */ - private async uploadPhoto( - file: string = '', - published = false - ): Promise { - - Logger.trace('Reading file',file); - const rawData = fs.readFileSync(file); - const blob = new Blob([rawData]); - - const url = new URL('https://graph.facebook.com'); - url.pathname = this.GRAPH_API_VERSION + "/" + process.env.FAIRPOST_FACEBOOK_PAGE_ID; - url.pathname += "/photos"; - - const body = new FormData(); - body.set("published", published? "true":"false"); - body.set("source", blob, path.basename(file)); - - Logger.trace('POST',url.href); - const res = await fetch(url,{ - method: 'POST', - headers: { - 'Accept': 'application/json', - 'Authorization': 'Bearer '+process.env.FAIRPOST_FACEBOOK_PAGE_ACCESS_TOKEN - }, - body - }); - - const result = await res.json(); - if (!result['id']) { - console.error(result); - throw new Error('No id returned when uploading photo'); - } - return result['id']; - + return result; + } + + /* + * Return a long lived page access token. + * + * UserAccessToken: a shortlived user access token + */ + async getPageToken( + appUserId: string, + userAccessToken: string, + ): Promise { + // get a long lived UserAccessToken + + const url = new URL("https://graph.facebook.com"); + url.pathname = this.GRAPH_API_VERSION + "/oauth/access_token"; + const query = { + grant_type: "fb_exchange_token", + client_id: process.env.FAIRPOST_FACEBOOK_APP_ID, + client_secret: process.env.FAIRPOST_FACEBOOK_APP_SECRET, + fb_exchange_token: userAccessToken, + }; + url.search = new URLSearchParams(query).toString(); + + Logger.trace("fetching", url.href); + const res1 = await fetch(url, { + method: "GET", + headers: { Accept: "application/json" }, + }); + const data1 = await res1.json(); + const llUserAccessToken = data1["access_token"]; + + if (!llUserAccessToken) { + console.error(data1); + throw new Error("No llUserAccessToken access_token in response."); } - /* - * POST a video to the page using multipart/form-data - * - * arguments: - * file: path to the video to post - * published: wether to publish it or not - * - * returns: - * { id: string } - */ - private async publishVideo( - file: string, - title: string, - description: string - ): Promise<{ id: string }> { - - Logger.trace('Reading file',file); - const rawData = fs.readFileSync(file); - const blob = new Blob([rawData]); - - const url = new URL('https://graph.facebook.com'); - url.pathname = this.GRAPH_API_VERSION + "/" + process.env.FAIRPOST_FACEBOOK_PAGE_ID; - url.pathname += "/videos"; - - const body = new FormData(); - body.set("title", title); - body.set("description",description); - body.set("published", process.env.FAIRPOST_FACEBOOK_PUBLISH_POSTS); - body.set("source", blob, path.basename(file)); - - Logger.trace('POST',url.href); - const res = await fetch(url,{ - method: 'POST', - headers: { - 'Accept': 'application/json', - 'Authorization': 'Bearer '+process.env.FAIRPOST_FACEBOOK_PAGE_ACCESS_TOKEN - }, - body - }); - - const result = await res.json(); - if (!result['id']) { - console.error(result); - throw new Error('No id returned when uploading video'); + // get a long lived PageAccessToken + + const url2 = new URL("https://graph.facebook.com"); + url2.pathname = appUserId + "/accounts"; + const query2 = { + access_token: llUserAccessToken, + }; + url2.search = new URLSearchParams(query2).toString(); + Logger.trace("fetching", url.href); + const res2 = await fetch(url2, { + method: "GET", + headers: { Accept: "application/json" }, + }); + const data2 = await res2.json(); + + let llPageAccessToken = ""; + if (data2.data) { + data2.data.forEach((page) => { + if (page.id === process.env.FAIRPOST_FACEBOOK_PAGE_ID) { + llPageAccessToken = page.access_token; } - return result; - + }); } - - /* - * Return a long lived page access token. - * - * UserAccessToken: a shortlived user access token - */ - async getPageToken(appUserId: string, userAccessToken :string): Promise { - - // get a long lived UserAccessToken - - const url = new URL('https://graph.facebook.com'); - url.pathname = this.GRAPH_API_VERSION + "/oauth/access_token"; - const query = { - grant_type : "fb_exchange_token", - client_id : process.env.FAIRPOST_FACEBOOK_APP_ID, - client_secret : process.env.FAIRPOST_FACEBOOK_APP_SECRET, - fb_exchange_token : userAccessToken - }; - url.search = new URLSearchParams(query).toString(); - - Logger.trace('fetching',url.href); - const res1 = await fetch(url,{ - method: 'GET', - headers: { 'Accept': 'application/json'}, - }); - const data1 = await res1.json(); - const llUserAccessToken = data1['access_token']; - - if (!llUserAccessToken) { - console.error(data1); - throw new Error('No llUserAccessToken access_token in response.'); - } - - // get a long lived PageAccessToken - - const url2 = new URL('https://graph.facebook.com'); - url2.pathname = appUserId + "/accounts"; - const query2 = { - access_token : llUserAccessToken - }; - url2.search = new URLSearchParams(query2).toString(); - Logger.trace('fetching',url.href); - const res2 = await fetch(url2,{ - method: 'GET', - headers: { 'Accept': 'application/json'}, - }); - const data2 = await res2.json(); - - let llPageAccessToken = ''; - if (data2.data) { - data2.data.forEach(page=> { - if (page.id===process.env.FAIRPOST_FACEBOOK_PAGE_ID) { - llPageAccessToken = page.access_token; - } - }) - } - if (!llPageAccessToken) { - console.error(data2); - throw new Error('No llPageAccessToken for page '+process.env.FAIRPOST_FACEBOOK_PAGE_ID+' in response.'); - } - - return llPageAccessToken; + if (!llPageAccessToken) { + console.error(data2); + throw new Error( + "No llPageAccessToken for page " + + process.env.FAIRPOST_FACEBOOK_PAGE_ID + + " in response.", + ); } - -} \ No newline at end of file + return llPageAccessToken; + } +} diff --git a/src/platforms/index.ts b/src/platforms/index.ts index 9c6366b..07be3a3 100644 --- a/src/platforms/index.ts +++ b/src/platforms/index.ts @@ -8,13 +8,13 @@ export { default as AsReddit } from "./AsReddit"; export { default as Facebook } from "./Facebook"; export enum PlatformId { - UNKNOWN = "unknown", - ASYOUTUBE = "asyoutube", - ASINSTAGRAM = "asinstagram", - ASFACEBOOK = "asfacebook", - ASTWITTER = "astwitter", - ASTIKTOK = "astiktok", - ASLINKEDIN = "aslinkedin", - ASREDDIT = "asreddit", - FACEBOOK = "facebook" -} \ No newline at end of file + UNKNOWN = "unknown", + ASYOUTUBE = "asyoutube", + ASINSTAGRAM = "asinstagram", + ASFACEBOOK = "asfacebook", + ASTWITTER = "astwitter", + ASTIKTOK = "astiktok", + ASLINKEDIN = "aslinkedin", + ASREDDIT = "asreddit", + FACEBOOK = "facebook", +} From 0c7e0b0a3e6c85e04db36a9a7ca1337a132749f3 Mon Sep 17 00:00:00 2001 From: pike Date: Sat, 14 Oct 2023 11:04:00 +0200 Subject: [PATCH 22/25] feat: lint:fix --- index.ts | 438 +++-- package-lock.json | 3404 +++++++++++++++++++++++++++++++++- package.json | 9 +- src/Feed.ts | 4 +- src/Platform.ts | 4 +- src/Post.ts | 8 +- src/platforms/AsFacebook.ts | 5 +- src/platforms/AsInstagram.ts | 2 +- src/platforms/AsLinkedIn.ts | 4 +- src/platforms/AsReddit.ts | 3 +- src/platforms/AsTikTok.ts | 3 +- src/platforms/AsTwitter.ts | 4 +- src/platforms/AsYouTube.ts | 3 +- src/platforms/Ayrshare.ts | 12 +- src/platforms/Facebook.ts | 2 +- 15 files changed, 3593 insertions(+), 312 deletions(-) diff --git a/index.ts b/index.ts index 2af175b..cf1cb08 100644 --- a/index.ts +++ b/index.ts @@ -3,223 +3,249 @@ Fairpost cli handler */ -import * as path from 'path'; -import Logger from './src/Logger'; -import Feed from './src/Feed'; -import { PostStatus } from './src/Post'; -import { PlatformId } from './src/platforms'; -import Facebook from './src/platforms/Facebook'; +import * as path from "path"; +import Logger from "./src/Logger"; +import Feed from "./src/Feed"; +import { PostStatus } from "./src/Post"; +import { PlatformId } from "./src/platforms"; +import Facebook from "./src/platforms/Facebook"; -// arguments -const COMMAND = process.argv[2] ?? 'help' +// arguments +const COMMAND = process.argv[2] ?? "help"; // options -const CONFIG = (getOption('config') as string ) ?? '.env'; -const DRY_RUN = !!getOption('dry-run') ?? false; -const REPORT = (getOption('report') as string ) ?? 'text'; -const PLATFORMS = (getOption('platforms') as string)?.split(',') as PlatformId[] ?? undefined; -const PLATFORM = (getOption('platform') as string) as PlatformId ?? undefined; -const FOLDERS = (getOption('folders') as string)?.split(',') ?? undefined; -const FOLDER = (getOption('folder') as string) ?? undefined; -const DATE = (getOption('date') as string) ?? undefined; -const STATUS = (getOption('status') as PostStatus) ?? undefined; - +const CONFIG = (getOption("config") as string) ?? ".env"; +const DRY_RUN = !!getOption("dry-run") ?? false; +const REPORT = (getOption("report") as string) ?? "text"; +const PLATFORMS = + ((getOption("platforms") as string)?.split(",") as PlatformId[]) ?? undefined; +const PLATFORM = (getOption("platform") as string as PlatformId) ?? undefined; +const FOLDERS = (getOption("folders") as string)?.split(",") ?? undefined; +const FOLDER = (getOption("folder") as string) ?? undefined; +const DATE = (getOption("date") as string) ?? undefined; +const STATUS = (getOption("status") as PostStatus) ?? undefined; // utilities -function getOption(key:string):boolean|string|null { - if ( process.argv.includes( `--${ key }` ) ) return true; - const value = process.argv.find( element => element.startsWith( `--${ key }=` ) ); - if ( !value ) return null; - return value.replace( `--${ key }=` , '' ); +function getOption(key: string): boolean | string | null { + if (process.argv.includes(`--${key}`)) return true; + const value = process.argv.find((element) => element.startsWith(`--${key}=`)); + if (!value) return null; + return value.replace(`--${key}=`, ""); } - /* main */ async function main() { - - let result: any; - let report = ''; - - const feed = new Feed(CONFIG); - Logger.trace('Fairpost '+feed.id+' '+COMMAND,DRY_RUN?' dry-run':''); - - - try { - switch(COMMAND) { - case 'get-feed': - result = feed; - report = 'Feed: '+feed.id; - break; - case 'get-platform': - const platform = feed.getPlatform(PLATFORM); - report += 'Platform: '+platform.id+'\n'; - result = platform; - break; - case 'get-platforms': - const platforms = feed.getPlatforms(PLATFORMS); - platforms.forEach(platform => { - report += 'Platform: '+platform.id+'\n'; - }); - result = platforms; - break; - case 'test-platform': - result = await feed.testPlatform(PLATFORM); - report = "Result: \n"+ JSON.stringify(result,null,'\t'); - break; - case 'test-platforms': - result = await feed.testPlatforms(PLATFORMS); - report = "Result: \n"+ JSON.stringify(result,null,'\t'); - break; - case 'get-folder': - const folder = feed.getFolder(FOLDER); - report += 'Folder: '+folder.id+'\n'; - result = folder; - break; - case 'get-folders': - const folders = feed.getFolders(FOLDERS); - folders.forEach(folder => { - report += 'Folder: '+folder.id+'\n'; - }); - result = folders; - break; - case 'get-post': - const post = feed.getPost(FOLDER, PLATFORM); - report += post.report(); - result = post; - break; - case 'get-posts': - const allposts = feed.getPosts({ - folders:FOLDERS, - platforms:PLATFORMS, - status: STATUS - }); - allposts.forEach(post => { - report += post.report(); - }); - result = allposts; - break; - case 'prepare-post': - const preppost = await feed.preparePost(FOLDER,PLATFORM); - report += preppost.report(); - result = preppost; - break; - case 'prepare-posts': - const prepposts = await feed.preparePosts({ - folders:FOLDERS, - platforms:PLATFORMS - }); - prepposts.forEach(post => { - report += post.report(); - }); - result = prepposts; - break; - case 'schedule-post': - const schedpost = feed.schedulePost( - FOLDER,PLATFORM, new Date(DATE), - ); - report += schedpost.report(); - result = schedpost; - break; - case 'schedule-posts': - const schedposts = feed.schedulePosts({ - folders: FOLDERS, - platforms: PLATFORMS - }, new Date(DATE)); - schedposts.forEach(post => { - report += post.report(); - }); - result = schedposts; - break; - case 'publish-post': - const pubpost = await feed.publishPost(FOLDER,PLATFORM, DRY_RUN); - report += pubpost.report(); - result = pubpost; - break; - - case 'publish-posts': - const pubposts = await feed.publishPosts({ - folders:FOLDERS, - platforms:PLATFORMS - }, DRY_RUN); - pubposts.forEach(post => { - report += post.report(); - }); - result = pubposts; - break; - - /* feed planning */ - case 'schedule-next-posts': - const nextposts = feed.scheduleNextPosts(DATE ? new Date(DATE): undefined,{ - folders:FOLDERS, - platforms:PLATFORMS - }); - nextposts.forEach(post => { - report += post.report(); - }); - result = nextposts; - break; - case 'publish-due-posts': - const dueposts = await feed.publishDuePosts({ - folders:FOLDERS, - platforms:PLATFORMS - }, DRY_RUN); - pubposts.forEach(post => { - report += post.report(); - }); - result = dueposts; - break; - - /* platform specific tools */ - case 'facebook-get-page-token': - const userToken = (getOption('user-token') as string ); - const appUserId = (getOption('app-user-id') as string ); - const facebook = new Facebook(); - result = await facebook.getPageToken(appUserId, userToken); - report = 'Page Token: '+result; - break; - - default: - const cmd = path.basename(process.argv[1]); - result = [ - '# basic commands:', - `${cmd} help`, - `${cmd} get-feed [--config=xxx]`, - `${cmd} test-platform --platform=xxx`, - `${cmd} test-platforms [--platforms=xxx,xxx]`, - `${cmd} get-platform --platform=xxx`, - `${cmd} get-platforms [--platforms=xxx,xxx]`, - `${cmd} get-folder --folder=xxx`, - `${cmd} get-folders [--folders=xxx,xxx]`, - `${cmd} get-post --folder=xxx --platform=xxx`, - `${cmd} get-posts [--status=xxx] [--folders=xxx,xxx] [--platforms=xxx,xxx] `, - `${cmd} prepare-post --folder=xxx --platform=xxx`, - `${cmd} schedule-post --folder=xxx --platform=xxx --date=xxxx-xx-xx `, - `${cmd} schedule-posts [--folders=xxx,xxx] [--platforms=xxx,xxx] --date=xxxx-xx-xx`, - `${cmd} publish-post --folders=xxx --platforms=xxx [--dry-run]`, - `${cmd} publish-posts [--folders=xxx,xxx] [--platforms=xxx,xxx]`, - '\n# feed planning:', - `${cmd} prepare-posts [--folders=xxx,xxx] [--platforms=xxx,xxx]`, - `${cmd} schedule-next-post [--date=xxxx-xx-xx] [--folders=xxx,xxx] [--platforms=xxx,xxx] `, - `${cmd} publish-due-posts [--folders=xxx,xxx] [--platforms=xxx,xxx] [--dry-run]`, - '\n# platform tools:', - `${cmd} facebook-get-page-token --app-user-id=xxx --user-token=xxx` - ]; - result.forEach(line => report += '\n'+line); - } - } catch (e) { - console.error(e.message); + let result: unknown; + let report = ""; + + const feed = new Feed(CONFIG); + Logger.trace( + "Fairpost " + feed.id + " " + COMMAND, + DRY_RUN ? " dry-run" : "", + ); + + try { + switch (COMMAND) { + case "get-feed": { + result = feed; + report = "Feed: " + feed.id; + break; + } + case "get-platform": { + const platform = feed.getPlatform(PLATFORM); + report += "Platform: " + platform.id + "\n"; + result = platform; + break; + } + case "get-platforms": { + const platforms = feed.getPlatforms(PLATFORMS); + platforms.forEach((platform) => { + report += "Platform: " + platform.id + "\n"; + }); + result = platforms; + break; + } + case "test-platform": { + result = await feed.testPlatform(PLATFORM); + report = "Result: \n" + JSON.stringify(result, null, "\t"); + break; + } + case "test-platforms": { + result = await feed.testPlatforms(PLATFORMS); + report = "Result: \n" + JSON.stringify(result, null, "\t"); + break; + } + case "get-folder": { + const folder = feed.getFolder(FOLDER); + report += "Folder: " + folder.id + "\n"; + result = folder; + break; + } + case "get-folders": { + const folders = feed.getFolders(FOLDERS); + folders.forEach((folder) => { + report += "Folder: " + folder.id + "\n"; + }); + result = folders; + break; + } + case "get-post": { + const post = feed.getPost(FOLDER, PLATFORM); + report += post.report(); + result = post; + break; + } + case "get-posts": { + const allposts = feed.getPosts({ + folders: FOLDERS, + platforms: PLATFORMS, + status: STATUS, + }); + allposts.forEach((post) => { + report += post.report(); + }); + result = allposts; + break; + } + case "prepare-post": { + const preppost = await feed.preparePost(FOLDER, PLATFORM); + report += preppost.report(); + result = preppost; + break; + } + case "prepare-posts": { + const prepposts = await feed.preparePosts({ + folders: FOLDERS, + platforms: PLATFORMS, + }); + prepposts.forEach((post) => { + report += post.report(); + }); + result = prepposts; + break; + } + case "schedule-post": { + const schedpost = feed.schedulePost(FOLDER, PLATFORM, new Date(DATE)); + report += schedpost.report(); + result = schedpost; + break; + } + case "schedule-posts": { + const schedposts = feed.schedulePosts( + { + folders: FOLDERS, + platforms: PLATFORMS, + }, + new Date(DATE), + ); + schedposts.forEach((post) => { + report += post.report(); + }); + result = schedposts; + break; + } + case "publish-post": { + const pubpost = await feed.publishPost(FOLDER, PLATFORM, DRY_RUN); + report += pubpost.report(); + result = pubpost; + break; + } + case "publish-posts": { + const pubposts = await feed.publishPosts( + { + folders: FOLDERS, + platforms: PLATFORMS, + }, + DRY_RUN, + ); + pubposts.forEach((post) => { + report += post.report(); + }); + result = pubposts; + break; + } + + /* feed planning */ + case "schedule-next-posts": { + const nextposts = feed.scheduleNextPosts( + DATE ? new Date(DATE) : undefined, + { + folders: FOLDERS, + platforms: PLATFORMS, + }, + ); + nextposts.forEach((post) => { + report += post.report(); + }); + result = nextposts; + break; + } + case "publish-due-posts": { + const dueposts = await feed.publishDuePosts( + { + folders: FOLDERS, + platforms: PLATFORMS, + }, + DRY_RUN, + ); + dueposts.forEach((post) => { + report += post.report(); + }); + result = dueposts; + break; + } + /* platform specific tools */ + case "facebook-get-page-token": { + const userToken = getOption("user-token") as string; + const appUserId = getOption("app-user-id") as string; + const facebook = new Facebook(); + result = await facebook.getPageToken(appUserId, userToken); + report = "Page Token: " + result; + break; + } + default: { + const cmd = path.basename(process.argv[1]); + result = [ + "# basic commands:", + `${cmd} help`, + `${cmd} get-feed [--config=xxx]`, + `${cmd} test-platform --platform=xxx`, + `${cmd} test-platforms [--platforms=xxx,xxx]`, + `${cmd} get-platform --platform=xxx`, + `${cmd} get-platforms [--platforms=xxx,xxx]`, + `${cmd} get-folder --folder=xxx`, + `${cmd} get-folders [--folders=xxx,xxx]`, + `${cmd} get-post --folder=xxx --platform=xxx`, + `${cmd} get-posts [--status=xxx] [--folders=xxx,xxx] [--platforms=xxx,xxx] `, + `${cmd} prepare-post --folder=xxx --platform=xxx`, + `${cmd} schedule-post --folder=xxx --platform=xxx --date=xxxx-xx-xx `, + `${cmd} schedule-posts [--folders=xxx,xxx] [--platforms=xxx,xxx] --date=xxxx-xx-xx`, + `${cmd} publish-post --folders=xxx --platforms=xxx [--dry-run]`, + `${cmd} publish-posts [--folders=xxx,xxx] [--platforms=xxx,xxx]`, + "\n# feed planning:", + `${cmd} prepare-posts [--folders=xxx,xxx] [--platforms=xxx,xxx]`, + `${cmd} schedule-next-post [--date=xxxx-xx-xx] [--folders=xxx,xxx] [--platforms=xxx,xxx] `, + `${cmd} publish-due-posts [--folders=xxx,xxx] [--platforms=xxx,xxx] [--dry-run]`, + "\n# platform tools:", + `${cmd} facebook-get-page-token --app-user-id=xxx --user-token=xxx`, + ]; + (result as string[]).forEach((line) => (report += "\n" + line)); + } } - - switch(REPORT) { - case 'json': - console.log(JSON.stringify(result,null,'\t')); - break; - default: - console.log(report); + } catch (e) { + console.error(e.message); + } + + switch (REPORT) { + case "json": { + console.log(JSON.stringify(result, null, "\t")); + break; } - - + default: { + console.log(report); + } + } } -main(); \ No newline at end of file +main(); diff --git a/package-lock.json b/package-lock.json index 65bb917..83f35fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,11 +15,25 @@ "sharp": "0.31.1" }, "devDependencies": { + "@typescript-eslint/eslint-plugin": "^6.7.5", + "@typescript-eslint/parser": "^6.7.5", + "eslint": "^8.51.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-prettier": "^5.0.1", "prettier": "^3.0.3", "ts-node": "^10.9.1", "typescript": "^5.0.4" } }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -32,6 +46,107 @@ "node": ">=12" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.9.1.tgz", + "integrity": "sha512-Y27x+MBLjXa+0JWDhykM3+JE+il3kHKAEqabfEWq3SDhZjLYb6/BHL/JKFnH3fe207JaXkyDo685Oc2Glt6ifA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", + "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.51.0.tgz", + "integrity": "sha512-HxjQ8Qn+4SI3/AFv6sOrDB+g6PpUTDwSJiQqOrnneEk8L71161srI9gjzzZvYVbzHiVg/BvcH95+cK/zfIt4pg==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", + "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", @@ -57,6 +172,61 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgr/utils": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.2.tgz", + "integrity": "sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "fast-glob": "^3.3.0", + "is-glob": "^4.0.3", + "open": "^9.1.0", + "picocolors": "^1.0.0", + "tslib": "^2.6.0" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", @@ -81,6 +251,12 @@ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", "dev": true }, + "node_modules/@types/json-schema": { + "version": "7.0.13", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.13.tgz", + "integrity": "sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==", + "dev": true + }, "node_modules/@types/node": { "version": "20.7.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.7.2.tgz", @@ -88,6 +264,201 @@ "dev": true, "peer": true }, + "node_modules/@types/semver": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.5.tgz", + "integrity": "sha512-JhtAwTRhOUcP96D0Y6KYnwig/MRQbOoLGXTON2+LlyB/N35SP9j1boai2zzwXb7ypKELXMx3DVk9UTaEq1vHEw==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.7.5", + "@typescript-eslint/type-utils": "6.7.5", + "@typescript-eslint/utils": "6.7.5", + "@typescript-eslint/visitor-keys": "6.7.5", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.7.5.tgz", + "integrity": "sha512-bIZVSGx2UME/lmhLcjdVc7ePBwn7CLqKarUBL4me1C5feOd663liTGjMBGVcGr+BhnSLeP4SgwdvNnnkbIdkCw==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "6.7.5", + "@typescript-eslint/types": "6.7.5", + "@typescript-eslint/typescript-estree": "6.7.5", + "@typescript-eslint/visitor-keys": "6.7.5", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.7.5.tgz", + "integrity": "sha512-GAlk3eQIwWOJeb9F7MKQ6Jbah/vx1zETSDw8likab/eFcqkjSD7BI75SDAeC5N2L0MmConMoPvTsmkrg71+B1A==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.7.5", + "@typescript-eslint/visitor-keys": "6.7.5" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.7.5.tgz", + "integrity": "sha512-Gs0qos5wqxnQrvpYv+pf3XfcRXW6jiAn9zE/K+DlmYf6FcpxeNYN0AIETaPR7rHO4K2UY+D0CIbDP9Ut0U4m1g==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "6.7.5", + "@typescript-eslint/utils": "6.7.5", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.7.5.tgz", + "integrity": "sha512-WboQBlOXtdj1tDFPyIthpKrUb+kZf2VroLZhxKa/VlwLlLyqv/PwUNgL30BlTVZV1Wu4Asu2mMYPqarSO4L5ZQ==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.5.tgz", + "integrity": "sha512-NhJiJ4KdtwBIxrKl0BqG1Ur+uw7FiOnOThcYx9DpOGJ/Abc9z2xNzLeirCG02Ig3vkvrc2qFLmYSSsaITbKjlg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.7.5", + "@typescript-eslint/visitor-keys": "6.7.5", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.7.5.tgz", + "integrity": "sha512-pfRRrH20thJbzPPlPc4j0UNGvH1PjPlhlCMq4Yx7EGjV7lvEeGX0U6MJYe8+SyFutWgSHsdbJ3BXzZccYggezA==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.7.5", + "@typescript-eslint/types": "6.7.5", + "@typescript-eslint/typescript-estree": "6.7.5", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.5.tgz", + "integrity": "sha512-3MaWdDZtLlsexZzDSdQWsFQ9l9nL8B80Z4fImSpyllFC/KLqWQRdEcB+gGGO+N3Q2uL40EsG66wZLsohPxNXvg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.7.5", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/acorn": { "version": "8.10.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", @@ -100,6 +471,15 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/acorn-walk": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", @@ -109,12 +489,73 @@ "node": ">=0.4.0" } }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", "dev": true }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -134,6 +575,15 @@ } ] }, + "node_modules/big-integer": { + "version": "1.6.51", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", + "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, "node_modules/bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", @@ -144,6 +594,40 @@ "readable-stream": "^3.4.0" } }, + "node_modules/bplist-parser": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz", + "integrity": "sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==", + "dev": true, + "dependencies": { + "big-integer": "^1.6.44" + }, + "engines": { + "node": ">= 5.10.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -167,6 +651,46 @@ "ieee754": "^1.1.13" } }, + "node_modules/bundle-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz", + "integrity": "sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==", + "dev": true, + "dependencies": { + "run-applescript": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/chownr": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", @@ -209,12 +733,32 @@ "simple-swizzle": "^0.2.2" } }, - "node_modules/create-require": { + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/date-format": { "version": "4.0.14", "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", @@ -261,6 +805,58 @@ "node": ">=4.0.0" } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/default-browser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-4.0.0.tgz", + "integrity": "sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==", + "dev": true, + "dependencies": { + "bundle-name": "^3.0.0", + "default-browser-id": "^3.0.0", + "execa": "^7.1.1", + "titleize": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-3.0.0.tgz", + "integrity": "sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==", + "dev": true, + "dependencies": { + "bplist-parser": "^0.2.0", + "untildify": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/detect-libc": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", @@ -278,99 +874,755 @@ "node": ">=0.3.1" } }, - "node_modules/dotenv": { - "version": "16.3.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", - "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dotenv": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.51.0.tgz", + "integrity": "sha512-2WuxRZBrlwnXi+/vFSJyjMqrNjtJqiasMzehF0shoLaW7DzS3/9Yvrmq5JiT66+pNjiX4UBnLDiKHcWAr/OInA==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.2", + "@eslint/js": "8.51.0", + "@humanwhocodes/config-array": "^0.11.11", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz", + "integrity": "sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.1.tgz", + "integrity": "sha512-m3u5RnR56asrwV/lDC4GHorlW75DsFfmUcjfCYylTUs85dBRnB7VM6xG8eCMJdeDRnppzmxZVf1GEPJvl1JmNg==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.8.5" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", + "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.1", + "human-signals": "^4.3.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^3.0.7", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": "^14.18.0 || ^16.14.0 || >=18.0.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.1.tgz", + "integrity": "sha512-/qM2b3LUIaIgviBQovTLvijfyOQXPtSRnRK26ksj2J7rzPIecePUIpJsZ4T02Qg+xiAEKIs5K8dsHEd+VaKa/Q==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==" + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/human-signals": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", + "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", + "dev": true, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, "engines": { - "node": ">=12" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { - "url": "https://github.com/motdotla/dotenv?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, "dependencies": { - "once": "^1.4.0" + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "node_modules/is-wsl/node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, "engines": { - "node": ">=6" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/flatted": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", - "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==" - }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true }, - "node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "argparse": "^2.0.1" }, - "engines": { - "node": ">=6 <7 || >=8" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/github-from-package": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true }, - "node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true }, "node_modules/jsonfile": { "version": "4.0.0", @@ -380,6 +1632,49 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, "node_modules/log4js": { "version": "6.9.1", "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz", @@ -412,6 +1707,46 @@ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/mimic-response": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", @@ -423,6 +1758,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", @@ -446,6 +1793,12 @@ "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, "node_modules/node-abi": { "version": "3.47.0", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.47.0.tgz", @@ -481,6 +1834,33 @@ } } }, + "node_modules/npm-run-path": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", + "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -489,6 +1869,152 @@ "wrappy": "1" } }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/open/-/open-9.1.0.tgz", + "integrity": "sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==", + "dev": true, + "dependencies": { + "default-browser": "^4.0.0", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/prebuild-install": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", @@ -514,6 +2040,15 @@ "node": ">=10" } }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/prettier": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", @@ -529,6 +2064,18 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -538,6 +2085,35 @@ "once": "^1.3.1" } }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -565,11 +2141,172 @@ "node": ">= 6" } }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, "node_modules/rfdc": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==" }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-applescript": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz", + "integrity": "sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==", + "dev": true, + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-applescript/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/run-applescript/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/run-applescript/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-applescript/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/run-applescript/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/run-applescript/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-applescript/node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -625,6 +2362,33 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, "node_modules/simple-concat": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", @@ -676,6 +2440,15 @@ "is-arrayish": "^0.3.1" } }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/streamroller": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", @@ -697,6 +2470,30 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", @@ -705,6 +2502,34 @@ "node": ">=0.10.0" } }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/synckit": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz", + "integrity": "sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==", + "dev": true, + "dependencies": { + "@pkgr/utils": "^2.3.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/tar-fs": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", @@ -731,11 +2556,53 @@ "node": ">=6" } }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/titleize": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz", + "integrity": "sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, + "node_modules/ts-api-utils": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", + "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "dev": true, + "engines": { + "node": ">=16.13.0" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, "node_modules/ts-node": { "version": "10.9.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", @@ -779,6 +2646,12 @@ } } }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -790,6 +2663,30 @@ "node": "*" } }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/typescript": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", @@ -811,6 +2708,24 @@ "node": ">= 4.0.0" } }, + "node_modules/untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -836,6 +2751,21 @@ "webidl-conversions": "^3.0.0" } }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -854,9 +2784,27 @@ "engines": { "node": ">=6" } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } }, "dependencies": { + "@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true + }, "@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -866,6 +2814,75 @@ "@jridgewell/trace-mapping": "0.3.9" } }, + "@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^3.3.0" + } + }, + "@eslint-community/regexpp": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.9.1.tgz", + "integrity": "sha512-Y27x+MBLjXa+0JWDhykM3+JE+il3kHKAEqabfEWq3SDhZjLYb6/BHL/JKFnH3fe207JaXkyDo685Oc2Glt6ifA==", + "dev": true + }, + "@eslint/eslintrc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", + "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + } + } + }, + "@eslint/js": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.51.0.tgz", + "integrity": "sha512-HxjQ8Qn+4SI3/AFv6sOrDB+g6PpUTDwSJiQqOrnneEk8L71161srI9gjzzZvYVbzHiVg/BvcH95+cK/zfIt4pg==", + "dev": true + }, + "@humanwhocodes/config-array": { + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", + "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + } + }, + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true + }, + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, "@jridgewell/resolve-uri": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", @@ -888,6 +2905,46 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@pkgr/utils": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.2.tgz", + "integrity": "sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "fast-glob": "^3.3.0", + "is-glob": "^4.0.3", + "open": "^9.1.0", + "picocolors": "^1.0.0", + "tslib": "^2.6.0" + } + }, "@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", @@ -912,6 +2969,12 @@ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", "dev": true }, + "@types/json-schema": { + "version": "7.0.13", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.13.tgz", + "integrity": "sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==", + "dev": true + }, "@types/node": { "version": "20.7.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.7.2.tgz", @@ -919,29 +2982,193 @@ "dev": true, "peer": true }, + "@types/semver": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw==", + "dev": true + }, + "@typescript-eslint/eslint-plugin": { + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.5.tgz", + "integrity": "sha512-JhtAwTRhOUcP96D0Y6KYnwig/MRQbOoLGXTON2+LlyB/N35SP9j1boai2zzwXb7ypKELXMx3DVk9UTaEq1vHEw==", + "dev": true, + "requires": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.7.5", + "@typescript-eslint/type-utils": "6.7.5", + "@typescript-eslint/utils": "6.7.5", + "@typescript-eslint/visitor-keys": "6.7.5", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + } + }, + "@typescript-eslint/parser": { + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.7.5.tgz", + "integrity": "sha512-bIZVSGx2UME/lmhLcjdVc7ePBwn7CLqKarUBL4me1C5feOd663liTGjMBGVcGr+BhnSLeP4SgwdvNnnkbIdkCw==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "6.7.5", + "@typescript-eslint/types": "6.7.5", + "@typescript-eslint/typescript-estree": "6.7.5", + "@typescript-eslint/visitor-keys": "6.7.5", + "debug": "^4.3.4" + } + }, + "@typescript-eslint/scope-manager": { + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.7.5.tgz", + "integrity": "sha512-GAlk3eQIwWOJeb9F7MKQ6Jbah/vx1zETSDw8likab/eFcqkjSD7BI75SDAeC5N2L0MmConMoPvTsmkrg71+B1A==", + "dev": true, + "requires": { + "@typescript-eslint/types": "6.7.5", + "@typescript-eslint/visitor-keys": "6.7.5" + } + }, + "@typescript-eslint/type-utils": { + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.7.5.tgz", + "integrity": "sha512-Gs0qos5wqxnQrvpYv+pf3XfcRXW6jiAn9zE/K+DlmYf6FcpxeNYN0AIETaPR7rHO4K2UY+D0CIbDP9Ut0U4m1g==", + "dev": true, + "requires": { + "@typescript-eslint/typescript-estree": "6.7.5", + "@typescript-eslint/utils": "6.7.5", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + } + }, + "@typescript-eslint/types": { + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.7.5.tgz", + "integrity": "sha512-WboQBlOXtdj1tDFPyIthpKrUb+kZf2VroLZhxKa/VlwLlLyqv/PwUNgL30BlTVZV1Wu4Asu2mMYPqarSO4L5ZQ==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.5.tgz", + "integrity": "sha512-NhJiJ4KdtwBIxrKl0BqG1Ur+uw7FiOnOThcYx9DpOGJ/Abc9z2xNzLeirCG02Ig3vkvrc2qFLmYSSsaITbKjlg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "6.7.5", + "@typescript-eslint/visitor-keys": "6.7.5", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + } + }, + "@typescript-eslint/utils": { + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.7.5.tgz", + "integrity": "sha512-pfRRrH20thJbzPPlPc4j0UNGvH1PjPlhlCMq4Yx7EGjV7lvEeGX0U6MJYe8+SyFutWgSHsdbJ3BXzZccYggezA==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.7.5", + "@typescript-eslint/types": "6.7.5", + "@typescript-eslint/typescript-estree": "6.7.5", + "semver": "^7.5.4" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.5.tgz", + "integrity": "sha512-3MaWdDZtLlsexZzDSdQWsFQ9l9nL8B80Z4fImSpyllFC/KLqWQRdEcB+gGGO+N3Q2uL40EsG66wZLsohPxNXvg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "6.7.5", + "eslint-visitor-keys": "^3.4.1" + } + }, "acorn": { "version": "8.10.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", "dev": true }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "requires": {} + }, "acorn-walk": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", "dev": true }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, "arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", "dev": true }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, + "big-integer": { + "version": "1.6.51", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", + "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", + "dev": true + }, "bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", @@ -952,6 +3179,34 @@ "readable-stream": "^3.4.0" } }, + "bplist-parser": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz", + "integrity": "sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==", + "dev": true, + "requires": { + "big-integer": "^1.6.44" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, "buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -961,6 +3216,31 @@ "ieee754": "^1.1.13" } }, + "bundle-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz", + "integrity": "sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==", + "dev": true, + "requires": { + "run-applescript": "^5.0.0" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, "chownr": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", @@ -997,12 +3277,29 @@ "simple-swizzle": "^0.2.2" } }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, "create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, "date-format": { "version": "4.0.14", "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", @@ -1029,6 +3326,40 @@ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "default-browser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-4.0.0.tgz", + "integrity": "sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==", + "dev": true, + "requires": { + "bundle-name": "^3.0.0", + "default-browser-id": "^3.0.0", + "execa": "^7.1.1", + "titleize": "^3.0.0" + } + }, + "default-browser-id": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-3.0.0.tgz", + "integrity": "sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==", + "dev": true, + "requires": { + "bplist-parser": "^0.2.0", + "untildify": "^4.0.0" + } + }, + "define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true + }, "detect-libc": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", @@ -1040,6 +3371,24 @@ "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, "dotenv": { "version": "16.3.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", @@ -1053,11 +3402,249 @@ "once": "^1.4.0" } }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "eslint": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.51.0.tgz", + "integrity": "sha512-2WuxRZBrlwnXi+/vFSJyjMqrNjtJqiasMzehF0shoLaW7DzS3/9Yvrmq5JiT66+pNjiX4UBnLDiKHcWAr/OInA==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.2", + "@eslint/js": "8.51.0", + "@humanwhocodes/config-array": "^0.11.11", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + } + }, + "eslint-config-prettier": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz", + "integrity": "sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==", + "dev": true, + "requires": {} + }, + "eslint-plugin-prettier": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.1.tgz", + "integrity": "sha512-m3u5RnR56asrwV/lDC4GHorlW75DsFfmUcjfCYylTUs85dBRnB7VM6xG8eCMJdeDRnppzmxZVf1GEPJvl1JmNg==", + "dev": true, + "requires": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.8.5" + } + }, + "eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true + }, + "espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "requires": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + } + }, + "esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "execa": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", + "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.1", + "human-signals": "^4.3.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^3.0.7", + "strip-final-newline": "^3.0.0" + } + }, "expand-template": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==" }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, + "fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat-cache": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.1.tgz", + "integrity": "sha512-/qM2b3LUIaIgviBQovTLvijfyOQXPtSRnRK26ksj2J7rzPIecePUIpJsZ4T02Qg+xiAEKIs5K8dsHEd+VaKa/Q==", + "dev": true, + "requires": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + } + }, "flatted": { "version": "3.2.9", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", @@ -1078,21 +3665,129 @@ "universalify": "^0.1.0" } }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, "github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "globals": { + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } + }, "graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, + "graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "human-signals": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", + "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", + "dev": true + }, "ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" }, + "ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -1108,6 +3803,104 @@ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" }, + "is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "requires": { + "is-docker": "^3.0.0" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true + }, + "is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true + }, + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "requires": { + "is-docker": "^2.0.0" + }, + "dependencies": { + "is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true + } + } + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, "jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", @@ -1116,6 +3909,40 @@ "graceful-fs": "^4.1.6" } }, + "keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "requires": { + "json-buffer": "3.0.1" + } + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, "log4js": { "version": "6.9.1", "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz", @@ -1142,11 +3969,48 @@ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true + }, "mimic-response": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, "minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", @@ -1167,6 +4031,12 @@ "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, "node-abi": { "version": "3.47.0", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.47.0.tgz", @@ -1188,6 +4058,23 @@ "whatwg-url": "^5.0.0" } }, + "npm-run-path": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", + "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", + "dev": true, + "requires": { + "path-key": "^4.0.0" + }, + "dependencies": { + "path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true + } + } + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -1196,6 +4083,104 @@ "wrappy": "1" } }, + "onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "requires": { + "mimic-fn": "^4.0.0" + } + }, + "open": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/open/-/open-9.1.0.tgz", + "integrity": "sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==", + "dev": true, + "requires": { + "default-browser": "^4.0.0", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^2.2.0" + } + }, + "optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "requires": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, "prebuild-install": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", @@ -1215,12 +4200,27 @@ "tunnel-agent": "^0.6.0" } }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, "prettier": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", "dev": true }, + "prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "requires": { + "fast-diff": "^1.1.2" + } + }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -1230,6 +4230,18 @@ "once": "^1.3.1" } }, + "punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, "rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -1251,11 +4263,111 @@ "util-deprecate": "^1.0.1" } }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, "rfdc": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==" }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "run-applescript": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz", + "integrity": "sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==", + "dev": true, + "requires": { + "execa": "^5.0.0" + }, + "dependencies": { + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + } + }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + } + } + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -1284,6 +4396,27 @@ "tunnel-agent": "^0.6.0" } }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, "simple-concat": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", @@ -1307,6 +4440,12 @@ "is-arrayish": "^0.3.1" } }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, "streamroller": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", @@ -1325,11 +4464,45 @@ "safe-buffer": "~5.2.0" } }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true + }, "strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==" }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "synckit": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz", + "integrity": "sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==", + "dev": true, + "requires": { + "@pkgr/utils": "^2.3.1", + "tslib": "^2.5.0" + } + }, "tar-fs": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", @@ -1353,11 +4526,39 @@ "readable-stream": "^3.1.1" } }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "titleize": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz", + "integrity": "sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, "tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, + "ts-api-utils": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", + "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "dev": true, + "requires": {} + }, "ts-node": { "version": "10.9.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", @@ -1379,6 +4580,12 @@ "yn": "3.1.1" } }, + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -1387,6 +4594,21 @@ "safe-buffer": "^5.0.1" } }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + }, "typescript": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", @@ -1398,6 +4620,21 @@ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" }, + "untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "dev": true + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -1423,6 +4660,15 @@ "webidl-conversions": "^3.0.0" } }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -1438,6 +4684,12 @@ "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "dev": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true } } } diff --git a/package.json b/package.json index a89566a..83b25ca 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,9 @@ "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "compile": "tsc", - "prettier": "prettier --config .prettierrc 'src/**/*.ts' --write" + "prettier": "prettier --config .prettierrc 'src/**/*.ts' --write", + "lint": "eslint . --ext .ts", + "lint:fix": "eslint . --ext .ts --fix" }, "keywords": [ "ayrshare", @@ -21,6 +23,11 @@ "sharp": "0.31.1" }, "devDependencies": { + "@typescript-eslint/eslint-plugin": "^6.7.5", + "@typescript-eslint/parser": "^6.7.5", + "eslint": "^8.51.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-prettier": "^5.0.1", "prettier": "^3.0.3", "ts-node": "^10.9.1", "typescript": "^5.0.4" diff --git a/src/Feed.ts b/src/Feed.ts index b3e68f6..e212a61 100644 --- a/src/Feed.ts +++ b/src/Feed.ts @@ -65,7 +65,7 @@ export default class Feed { ); } - async testPlatform(platformId: PlatformId): Promise<{}> { + async testPlatform(platformId: PlatformId): Promise { Logger.trace("Feed", "testPlatform", platformId); const results = await this.testPlatforms([platformId]); return results[platformId]; @@ -73,7 +73,7 @@ export default class Feed { async testPlatforms( platformsIds?: PlatformId[], - ): Promise<{ [id: string]: {} }> { + ): Promise<{ [id: string]: unknown }> { Logger.trace("Feed", "testPlatforms", platformsIds); const results = {}; for (const platform of this.getPlatforms(platformsIds)) { diff --git a/src/Platform.ts b/src/Platform.ts index 43ad5a1..c2f3fcf 100644 --- a/src/Platform.ts +++ b/src/Platform.ts @@ -110,7 +110,7 @@ export default class Platform { */ async publishPost(post: Post, dryrun: boolean = false): Promise { - Logger.trace("Platform", "publishPost"); + Logger.trace("Platform", "publishPost", post.id, dryrun); post.results.push({ error: "publishing not implemented for " + this.id, }); @@ -127,7 +127,7 @@ export default class Platform { * anything, but test access tokens et al. It can return * anything. */ - async test(): Promise { + async test(): Promise { return "No tests"; } } diff --git a/src/Post.ts b/src/Post.ts index 6327d4b..8fc6597 100644 --- a/src/Post.ts +++ b/src/Post.ts @@ -11,7 +11,7 @@ export default class Post { status: PostStatus = PostStatus.UNKNOWN; scheduled?: Date; published?: Date; - results: {}[] = []; + results: object[] = []; title: string = ""; body?: string; tags?: string; @@ -22,14 +22,14 @@ export default class Post { other: string[]; }; - constructor(folder: Folder, platform: Platform, data?: any) { + constructor(folder: Folder, platform: Platform, data?: object) { this.folder = folder; this.platform = platform; this.id = this.folder.id + ":" + this.platform.id; if (data) { Object.assign(this, data); - this.scheduled = data.scheduled ? new Date(data.scheduled) : undefined; - this.published = data.published ? new Date(data.published) : undefined; + this.scheduled = this.scheduled ? new Date(this.scheduled) : undefined; + this.published = this.published ? new Date(this.published) : undefined; } } diff --git a/src/platforms/AsFacebook.ts b/src/platforms/AsFacebook.ts index 6d768a9..92f41eb 100644 --- a/src/platforms/AsFacebook.ts +++ b/src/platforms/AsFacebook.ts @@ -1,4 +1,3 @@ -import Logger from "../Logger"; import Ayrshare from "./Ayrshare"; import { PlatformId } from "."; import Folder from "../Folder"; @@ -18,7 +17,7 @@ export default class AsFacebook extends Ayrshare { if (post) { // facebook : max 10mb images for (const image of post.files.image) { - var size = + const size = fs.statSync(post.folder.path + "/" + image).size / (1024 * 1024); if (size >= 10) { console.log("Resizing " + image + " for facebook .."); @@ -37,6 +36,6 @@ export default class AsFacebook extends Ayrshare { } async publishPost(post: Post, dryrun: boolean = false): Promise { - return super.publishPost(post, {}, dryrun); + return super.publishAyrshare(post, {}, dryrun); } } diff --git a/src/platforms/AsInstagram.ts b/src/platforms/AsInstagram.ts index 262498c..4da9a4b 100644 --- a/src/platforms/AsInstagram.ts +++ b/src/platforms/AsInstagram.ts @@ -48,7 +48,7 @@ export default class AsInstagram extends Ayrshare { } async publishPost(post: Post, dryrun: boolean = false): Promise { - return super.publishPost( + return super.publishAyrshare( post, { isVideo: post.files.video.length !== 0, diff --git a/src/platforms/AsLinkedIn.ts b/src/platforms/AsLinkedIn.ts index 9c42834..1447316 100644 --- a/src/platforms/AsLinkedIn.ts +++ b/src/platforms/AsLinkedIn.ts @@ -28,7 +28,7 @@ export default class AsLinkedIn extends Ayrshare { } // linkedin: max 5mb images for (const image of post.files.image) { - var size = + const size = fs.statSync(post.folder.path + "/" + image).size / (1024 * 1024); if (size >= 5) { Logger.trace("Resizing " + image + " for linkedin .."); @@ -47,6 +47,6 @@ export default class AsLinkedIn extends Ayrshare { } async publishPost(post: Post, dryrun: boolean = false): Promise { - return super.publishPost(post, {}, dryrun); + return super.publishAyrshare(post, {}, dryrun); } } diff --git a/src/platforms/AsReddit.ts b/src/platforms/AsReddit.ts index bc0a4bd..dd34398 100644 --- a/src/platforms/AsReddit.ts +++ b/src/platforms/AsReddit.ts @@ -1,4 +1,3 @@ -import Logger from "../Logger"; import Ayrshare from "./Ayrshare"; import { PlatformId } from "."; import Folder from "../Folder"; @@ -27,7 +26,7 @@ export default class AsReddit extends Ayrshare { } async publishPost(post: Post, dryrun: boolean = false): Promise { - return super.publishPost( + return super.publishAyrshare( post, { redditOptions: { diff --git a/src/platforms/AsTikTok.ts b/src/platforms/AsTikTok.ts index 8c8b93f..e64cc61 100644 --- a/src/platforms/AsTikTok.ts +++ b/src/platforms/AsTikTok.ts @@ -1,4 +1,3 @@ -import Logger from "../Logger"; import Ayrshare from "./Ayrshare"; import { PlatformId } from "."; import Folder from "../Folder"; @@ -27,6 +26,6 @@ export default class AsTikTok extends Ayrshare { } async publishPost(post: Post, dryrun: boolean = false): Promise { - return super.publishPost(post, {}, dryrun); + return super.publishAyrshare(post, {}, dryrun); } } diff --git a/src/platforms/AsTwitter.ts b/src/platforms/AsTwitter.ts index 948d3d3..2e0d476 100644 --- a/src/platforms/AsTwitter.ts +++ b/src/platforms/AsTwitter.ts @@ -24,7 +24,7 @@ export default class AsTwitter extends Ayrshare { } // twitter: max 5mb images for (const image of post.files.image) { - var size = + const size = fs.statSync(post.folder.path + "/" + image).size / (1024 * 1024); if (size >= 5) { Logger.trace("Resizing " + image + " for twitter .."); @@ -43,6 +43,6 @@ export default class AsTwitter extends Ayrshare { } async publishPost(post: Post, dryrun: boolean = false): Promise { - return super.publishPost(post, {}, dryrun); + return super.publishAyrshare(post, {}, dryrun); } } diff --git a/src/platforms/AsYouTube.ts b/src/platforms/AsYouTube.ts index efd0de6..7fcb460 100644 --- a/src/platforms/AsYouTube.ts +++ b/src/platforms/AsYouTube.ts @@ -1,4 +1,3 @@ -import Logger from "../Logger"; import Ayrshare from "./Ayrshare"; import { PlatformId } from "."; import Folder from "../Folder"; @@ -28,7 +27,7 @@ export default class AsYouTube extends Ayrshare { } async publishPost(post: Post, dryrun: boolean = false): Promise { - return super.publishPost( + return super.publishAyrshare( post, { youTubeOptions: { diff --git a/src/platforms/Ayrshare.ts b/src/platforms/Ayrshare.ts index 3f4f3ce..6d78222 100644 --- a/src/platforms/Ayrshare.ts +++ b/src/platforms/Ayrshare.ts @@ -11,7 +11,7 @@ import { PostStatus } from "../Post"; interface AyrshareResult { success: boolean; error?: Error; - response: {}; + response: object; } export default abstract class Ayrshare extends Platform { @@ -38,9 +38,9 @@ export default abstract class Ayrshare extends Platform { return super.preparePost(folder); } - async publishPost( + async publishAyrshare( post: Post, - platformOptions: {}, + platformOptions: object, dryrun: boolean = false, ): Promise { const media = [...post.files.image, ...post.files.video].map( @@ -59,7 +59,7 @@ export default abstract class Ayrshare extends Platform { return true; } - const result = await this.publishAyrshare(post, platformOptions, uploads); + const result = await this.postAyrshare(post, platformOptions, uploads); post.results.push(result); if (result.success) { post.status = PostStatus.PUBLISHED; @@ -124,9 +124,9 @@ export default abstract class Ayrshare extends Platform { return urls; } - async publishAyrshare( + async postAyrshare( post: Post, - platformOptions: {}, + platformOptions: object, uploads: string[], ): Promise { const APIKEY = process.env.FAIRPOST_AYRSHARE_API_KEY; diff --git a/src/platforms/Facebook.ts b/src/platforms/Facebook.ts index 4a7f741..ed917a2 100644 --- a/src/platforms/Facebook.ts +++ b/src/platforms/Facebook.ts @@ -26,7 +26,7 @@ export default class Facebook extends Platform { } // facebook : max 4mb images for (const image of post.files.image) { - var size = + const size = fs.statSync(post.folder.path + "/" + image).size / (1024 * 1024); if (size >= 4) { Logger.trace("Resizing " + image + " for facebook .."); From 6ebfb64393a431ca60e194841c19ec96cddbdc72 Mon Sep 17 00:00:00 2001 From: pike Date: Sat, 14 Oct 2023 11:04:25 +0200 Subject: [PATCH 23/25] feat: Add prettier lint conf --- .eslintignore | 3 +++ .eslintrc | 17 +++++++++++++++++ .prettierrc | 3 +++ 3 files changed, 23 insertions(+) create mode 100644 .eslintignore create mode 100644 .eslintrc create mode 100644 .prettierrc diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..cee9d57 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,3 @@ +node_modules +feed +dist \ No newline at end of file diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..dd152ed --- /dev/null +++ b/.eslintrc @@ -0,0 +1,17 @@ +{ + "root": true, + "parser": "@typescript-eslint/parser", + "plugins": [ + "@typescript-eslint", + "prettier" + ], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended", + "prettier" + ], + "rules": { + "prettier/prettier": 2 // Means error + } +} \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..544b7b4 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,3 @@ +{ + +} \ No newline at end of file From b6e53027b332e1b12f5028305abc00a1f7705c0f Mon Sep 17 00:00:00 2001 From: pike Date: Sat, 14 Oct 2023 12:47:17 +0200 Subject: [PATCH 24/25] Create qa.yml --- .github/workflows/qa.yml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 .github/workflows/qa.yml diff --git a/.github/workflows/qa.yml b/.github/workflows/qa.yml new file mode 100644 index 0000000..3a28bc1 --- /dev/null +++ b/.github/workflows/qa.yml @@ -0,0 +1,29 @@ +# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs + +name: QA + +on: + pull_request: + branches: [ "develop", "master" ] + +jobs: + build: + + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [14.x, 16.x, 18.x] + # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ + + steps: + - uses: actions/checkout@v3 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + - run: npm ci + - run: npm run build + - run: npm test From 6d80cd62effccbb0e93dca900e67acdd2da0f427 Mon Sep 17 00:00:00 2001 From: pike Date: Sat, 14 Oct 2023 12:49:28 +0200 Subject: [PATCH 25/25] feat: Add workflow scripts --- README.md | 2 +- package.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index dc56310..80e8da8 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ register the app to post on your behalf. npm install # compile typescript code -tsc +npm run build # copy and edit config file cp .env.dist .env && nano .env diff --git a/package.json b/package.json index 83b25ca..63978b0 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,8 @@ "description": "Feeds data to ayrshare based on folders in feed dir; marks folder that are done.", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", - "compile": "tsc", + "build": "tsc", + "test": "npm run lint", "prettier": "prettier --config .prettierrc 'src/**/*.ts' --write", "lint": "eslint . --ext .ts", "lint:fix": "eslint . --ext .ts --fix"