refactor: replace build dependencies with native Node.js APIs

Replace fs-extra/rimraf with native node:fs, lodash with
structuredClone/spread, ts-node with tsx, minimist with node:util
parseArgs, winston with console logger, async-promise-queue with
native Promise concurrency. Use promisify for child_process.exec.
Normalize all imports to use node: protocol. Extract Jest config to
jest.config.ts and replace ts-jest with @swc/jest.
This commit is contained in:
Daniel Sogl
2026-03-21 15:11:03 -07:00
parent f103f8bdae
commit 120e0f6d23
17 changed files with 197 additions and 281 deletions
+12
View File
@@ -0,0 +1,12 @@
import type { Config } from 'jest';
const config: Config = {
testEnvironment: 'jsdom',
transform: {
'^.+\\.tsx?$': '@swc/jest',
},
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(js?|ts?)$',
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
};
export default config;
+12 -13
View File
@@ -1,11 +1,13 @@
import { readdirSync } from 'fs-extra';
import { camelCase, clone } from 'lodash';
import { join, resolve } from 'path';
import { readdirSync } from 'node:fs';
import { readFileSync } from 'node:fs';
import { join, resolve } from 'node:path';
import {
ArrayLiteralExpression,
canHaveDecorators,
Decorator,
Expression,
factory,
getDecorators as tsGetDecorators,
Node,
ObjectLiteralElementLike,
ObjectLiteralExpression,
@@ -15,24 +17,21 @@ import {
import { Logger } from '../logger';
export const ROOT = resolve(__dirname, '../../');
// tslint:disable-next-line:no-var-requires
export const TS_CONFIG = clone(require(resolve(ROOT, 'tsconfig.json')));
export const TS_CONFIG = JSON.parse(readFileSync(resolve(ROOT, 'tsconfig.json'), 'utf-8'));
export const COMPILER_OPTIONS = TS_CONFIG.compilerOptions;
export const PLUGINS_ROOT = join(ROOT, 'src/@awesome-cordova-plugins/plugins/');
export const PLUGIN_PATHS = readdirSync(PLUGINS_ROOT).map((d) => join(PLUGINS_ROOT, d, 'index.ts'));
export function getDecorator(node: Node, index = 0): Decorator {
if (node.decorators && node.decorators[index]) {
return node.decorators[index];
const decorators = canHaveDecorators(node) ? tsGetDecorators(node) : undefined;
if (decorators && decorators[index]) {
return decorators[index];
}
}
export function hasDecorator(decoratorName: string, node: Node): boolean {
return (
node.decorators &&
node.decorators.length &&
node.decorators.findIndex((d) => getDecoratorName(d) === decoratorName) > -1
);
const decorators = canHaveDecorators(node) ? tsGetDecorators(node) : undefined;
return decorators && decorators.length > 0 && decorators.findIndex((d) => getDecoratorName(d) === decoratorName) > -1;
}
export function getDecoratorName(decorator: any) {
@@ -148,5 +147,5 @@ export function getMethodsForDecorator(decoratorName: string) {
return ['instanceAvailability'];
}
return [camelCase(decoratorName)];
return [decoratorName.charAt(0).toLowerCase() + decoratorName.slice(1)];
}
+12 -13
View File
@@ -1,8 +1,6 @@
import { CompilerHost, CompilerOptions, createCompilerHost, createProgram, EmitFlags } from '@angular/compiler-cli';
import { copyFileSync, mkdirpSync, readJSONSync, writeJSONSync } from 'fs-extra';
import { clone } from 'lodash';
import { dirname, join, resolve } from 'path';
import { sync } from 'rimraf';
import { copyFileSync, mkdirSync, rmSync } from 'node:fs';
import { dirname, join, resolve } from 'node:path';
import { rollup } from 'rollup';
import { ModuleKind, ModuleResolutionKind, ScriptTarget } from 'typescript';
@@ -12,16 +10,15 @@ import { pluginClassTransformer } from './transformers/plugin-class';
import { generateDeclarations } from './transpile';
export function getProgram(rootNames: string[] = createSourceFiles()) {
const options: CompilerOptions = clone(COMPILER_OPTIONS);
const options: CompilerOptions = structuredClone(COMPILER_OPTIONS);
options.basePath = ROOT;
options.moduleResolution = ModuleResolutionKind.NodeJs;
options.module = ModuleKind.ES2015;
options.target = ScriptTarget.ES5;
options.lib = ['dom', 'es2017'];
options.moduleResolution = ModuleResolutionKind.Node16;
options.module = ModuleKind.ES2022;
options.target = ScriptTarget.ES2022;
options.lib = ['dom', 'es2022'];
options.inlineSourceMap = true;
options.importHelpers = true;
options.inlineSources = true;
options.enableIvy = true;
options.compilationMode = 'partial';
delete options.baseUrl;
@@ -84,8 +81,8 @@ function createSourceFiles(): string[] {
newPath = resolve(ngxPath, 'index.ts');
// delete directory
sync(ngxPath);
mkdirpSync(ngxPath);
rmSync(ngxPath, { recursive: true, force: true });
mkdirSync(ngxPath, { recursive: true });
copyFileSync(indexPath, newPath);
return newPath;
@@ -93,5 +90,7 @@ function createSourceFiles(): string[] {
}
export function cleanupNgx() {
PLUGIN_PATHS.forEach((indexPath: string) => sync(indexPath.replace('index.ts', 'ngx')));
PLUGIN_PATHS.forEach((indexPath: string) =>
rmSync(indexPath.replace('index.ts', 'ngx'), { recursive: true, force: true })
);
}
-2
View File
@@ -1,2 +0,0 @@
// removes the __extends method that is added automatically by typescript
module.exports = (source) => source.replace(/var\s__extends\s=\s\(this\s&&[\sa-z\._\(\)\|{}=:\[\]&,;?]+}\)\(\);/i, '');
@@ -1,5 +1,5 @@
import { unlinkSync, writeJSONSync } from 'fs-extra';
import { resolve } from 'path';
import { unlinkSync, writeFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { ClassDeclaration, SyntaxKind, TransformationContext, visitEachChild } from 'typescript';
import { hasDecorator, ROOT } from '../helpers';
@@ -51,7 +51,7 @@ export function extractInjectables() {
}
export function emitInjectableClasses() {
writeJSONSync(EMIT_PATH, injectableClasses);
writeFileSync(EMIT_PATH, JSON.stringify(injectableClasses, null, 2));
}
export function cleanEmittedData() {
+4 -3
View File
@@ -1,4 +1,4 @@
import { ClassDeclaration, factory, SyntaxKind } from 'typescript';
import { canHaveDecorators, ClassDeclaration, factory, getDecorators as tsGetDecorators, SyntaxKind } from 'typescript';
import { transformMethod } from './methods';
import { transformProperty } from './properties';
@@ -8,7 +8,8 @@ export function transformMembers(cls: ClassDeclaration) {
const members = cls.members.map((member: any, index: number) => {
// only process decorated members
if (!member.decorators || !member.decorators.length) return member;
const memberDecorators = canHaveDecorators(member) ? tsGetDecorators(member) : undefined;
if (!memberDecorators || !memberDecorators.length) return member;
switch (member.kind) {
case SyntaxKind.MethodDeclaration:
@@ -17,7 +18,7 @@ export function transformMembers(cls: ClassDeclaration) {
propertyIndices.push(index);
return member;
case SyntaxKind.Constructor:
return factory.createConstructorDeclaration(undefined, undefined, member.parameters, member.body);
return factory.createConstructorDeclaration(undefined, member.parameters, member.body);
default:
return member; // in case anything gets here by accident...
}
-2
View File
@@ -18,7 +18,6 @@ export function transformMethod(method: MethodDeclaration) {
try {
return factory.createMethodDeclaration(
undefined,
undefined,
undefined,
method.name,
@@ -40,7 +39,6 @@ function getMethodBlock(method: MethodDeclaration, decoratorName: string, decora
switch (decoratorName) {
case 'CordovaCheck':
case 'InstanceCheck':
// TODO remove function wrapper
return factory.createImmediatelyInvokedArrowFunction([
factory.createIfStatement(
factory.createBinaryExpression(
+12 -6
View File
@@ -1,6 +1,10 @@
import {
canHaveDecorators,
canHaveModifiers,
Decorator,
factory,
getDecorators as tsGetDecorators,
getModifiers as tsGetModifiers,
SourceFile,
SyntaxKind,
TransformationContext,
@@ -25,7 +29,6 @@ function transformClass(cls: any, ngcBuild?: boolean) {
for (const prop in pluginDecoratorArgs) {
pluginStatics.push(
factory.createPropertyDeclaration(
undefined,
[factory.createToken(SyntaxKind.StaticKeyword)],
factory.createIdentifier(prop),
undefined,
@@ -36,11 +39,14 @@ function transformClass(cls: any, ngcBuild?: boolean) {
}
}
const clsDecorators = canHaveDecorators(cls) ? tsGetDecorators(cls) : undefined;
const keepDecorators =
ngcBuild && clsDecorators && clsDecorators.length
? clsDecorators.filter((d: Decorator) => getDecoratorName(d) === 'Injectable')
: [];
cls = factory.createClassDeclaration(
ngcBuild && cls.decorators && cls.decorators.length
? cls.decorators.filter((d) => getDecoratorName(d) === 'Injectable')
: undefined, // remove Plugin and Injectable decorators
[factory.createToken(SyntaxKind.ExportKeyword)],
[...keepDecorators, factory.createToken(SyntaxKind.ExportKeyword)],
cls.name,
cls.typeParameters,
cls.heritageClauses,
@@ -58,7 +64,7 @@ function transformClasses(file: SourceFile, ctx: TransformationContext, ngcBuild
(node) => {
if (
node.kind !== SyntaxKind.ClassDeclaration ||
(node.modifiers && node.modifiers.find((v) => v.kind === SyntaxKind.DeclareKeyword))
(canHaveModifiers(node) ? tsGetModifiers(node) : undefined)?.find((v) => v.kind === SyntaxKind.DeclareKeyword)
) {
return node;
}
+2 -4
View File
@@ -23,10 +23,9 @@ export function transformProperty(members: any[], index: number) {
}
const getter = factory.createGetAccessorDeclaration(
undefined,
undefined,
property.name,
undefined,
[],
property.type,
factory.createBlock([
factory.createReturnStatement(
@@ -39,10 +38,9 @@ export function transformProperty(members: any[], index: number) {
);
const setter = factory.createSetAccessorDeclaration(
undefined,
undefined,
property.name,
[factory.createParameterDeclaration(undefined, undefined, undefined, 'value', undefined, property.type)],
[factory.createParameterDeclaration(undefined, undefined, 'value', undefined, property.type)],
factory.createBlock([
factory.createExpressionStatement(
factory.createCallExpression(factory.createIdentifier(type + 'PropertySet'), undefined, [
+5 -6
View File
@@ -1,4 +1,3 @@
import { clone } from 'lodash';
import {
CompilerHost,
CompilerOptions,
@@ -22,14 +21,14 @@ export function getCompilerHost() {
}
export function getProgram(declaration = false, pluginPaths: string[] = PLUGIN_PATHS) {
const compilerOptions: CompilerOptions = clone(COMPILER_OPTIONS);
const compilerOptions: CompilerOptions = structuredClone(COMPILER_OPTIONS);
compilerOptions.declaration = declaration;
compilerOptions.moduleResolution = ModuleResolutionKind.NodeJs;
compilerOptions.target = ScriptTarget.ES5;
compilerOptions.module = ModuleKind.ES2015;
compilerOptions.moduleResolution = ModuleResolutionKind.Node16;
compilerOptions.target = ScriptTarget.ES2022;
compilerOptions.module = ModuleKind.ES2022;
compilerOptions.inlineSourceMap = true;
compilerOptions.inlineSources = true;
compilerOptions.lib = ['lib.dom.d.ts', 'lib.es5.d.ts', 'lib.es2015.d.ts', 'lib.es2016.d.ts', 'lib.es2017.d.ts'];
compilerOptions.lib = ['lib.dom.d.ts', 'lib.es2022.d.ts'];
return createProgram(pluginPaths, compilerOptions, getCompilerHost());
}
+29 -10
View File
@@ -1,11 +1,30 @@
import { createLogger, format, transports } from 'winston';
const profiles = new Map<string, number>();
const { combine, colorize, simple } = format;
const LOG_LEVEL = 'verbose';
export const Logger = createLogger({
level: LOG_LEVEL,
format: combine(colorize(), simple()),
transports: [new transports.Console({ level: LOG_LEVEL })],
});
export const Logger = {
error(...args: unknown[]) {
console.error('[error]', ...args);
},
info(...args: unknown[]) {
console.info('[info]', ...args);
},
verbose(...args: unknown[]) {
console.log('[verbose]', ...args);
},
debug(...args: unknown[]) {
console.debug('[debug]', ...args);
},
silly(...args: unknown[]) {
console.debug('[silly]', ...args);
},
profile(label: string) {
const now = performance.now();
const start = profiles.get(label);
if (start !== undefined) {
const durationMs = Math.round(now - start);
console.info(`[info] ${label} {"durationMs":${durationMs}}`);
profiles.delete(label);
} else {
profiles.set(label, now);
}
},
};
-91
View File
@@ -1,91 +0,0 @@
import { readJSONSync, writeFileSync } from 'fs-extra';
import { resolve } from 'path';
import * as TerserPlugin from 'terser-webpack-plugin';
import * as unminifiedPlugin from 'unminified-webpack-plugin';
import { Configuration, DefinePlugin, ProvidePlugin, webpack } from 'webpack';
import { ROOT } from '../build/helpers';
import { cleanEmittedData, EMIT_PATH, InjectableClassEntry } from '../build/transformers/extract-injectables';
import { Logger } from '../logger';
const DIST = resolve(ROOT, 'dist');
const INDEX_PATH = resolve(DIST, 'index.js');
const INJECTABLE_CLASSES = readJSONSync(EMIT_PATH).map((item: InjectableClassEntry) => {
item.file =
'./' +
item.file
.split(/[\/\\]+/)
.slice(-4, -1)
.join('/');
return item;
});
const webpackConfig: Configuration = {
mode: 'production',
entry: INDEX_PATH,
devtool: 'source-map',
target: 'web',
output: {
path: DIST,
filename: 'awesome-cordova-plugins.min.js',
},
resolve: {
modules: ['node_modules'],
extensions: ['.js'],
alias: {
'@awesome-cordova-plugins/core': resolve(DIST, '@awesome-cordova-plugins/core/index.js'),
},
},
module: {
rules: [
{
test: /\.js$/,
use: resolve(ROOT, 'scripts/build/remove-tslib-helpers.js'),
},
],
},
plugins: [
new ProvidePlugin({
__extends: ['tslib', '__extends'],
}),
new DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production'),
}),
new unminifiedPlugin(),
],
optimization: {
minimize: true,
minimizer: [new TerserPlugin()],
},
};
function getPluginImport(entry: InjectableClassEntry) {
return `import { ${entry.className} } from '${entry.file}';`;
}
function createIndexFile() {
let fileContent = '';
fileContent += INJECTABLE_CLASSES.map(getPluginImport).join('\n');
fileContent += `\nwindow.IonicNative = {\n`;
fileContent += INJECTABLE_CLASSES.map((e) => e.className).join(',\n');
fileContent += '\n};\n';
fileContent += `require('./@awesome-cordova-plugins/core/bootstrap').checkReady();\n`;
fileContent += `require('./@awesome-cordova-plugins/core/ng1').initAngular1(window.IonicNative);`;
writeFileSync(INDEX_PATH, fileContent, { encoding: 'utf-8' });
}
function compile() {
Logger.profile('build-es5');
webpack(webpackConfig, (err, stats) => {
Logger.profile('build-es5');
if (err) Logger.error('Error occurred while compiling with Webpack', err);
else {
Logger.info('Compiled ES5 file with Webpack successfully.');
}
cleanEmittedData();
});
}
createIndexFile();
compile();
+3 -3
View File
@@ -1,5 +1,5 @@
import { readFileSync, readJSONSync, writeFileSync } from 'fs-extra';
import { join } from 'path';
import { readFileSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
import { PLUGIN_PATHS, ROOT } from '../build/helpers';
import { EMIT_PATH } from '../build/transformers/extract-injectables';
@@ -9,7 +9,7 @@ generateDeclarations();
transpile();
const outDirs = PLUGIN_PATHS.map((p) => p.replace(join(ROOT, 'src'), join(ROOT, 'dist')).replace(/[\\/]index.ts/, ''));
const injectableClasses = readJSONSync(EMIT_PATH);
const injectableClasses = JSON.parse(readFileSync(EMIT_PATH, 'utf-8'));
outDirs.forEach((dir) => {
const classes = injectableClasses.filter((entry) => entry.dirName === dir.split(/[\\/]+/).pop());
-55
View File
@@ -1,55 +0,0 @@
import { writeFileSync } from 'fs-extra';
import { resolve } from 'path';
import { PLUGIN_PATHS, ROOT } from '../build/helpers';
// Base configuration for release-please
const baseConfig = {
$schema: 'https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json',
'release-type': 'node',
'bump-minor-pre-major': false,
'bump-patch-for-minor-pre-major': false,
draft: false,
prerelease: false,
'changelog-sections': [
{ type: 'feat', section: 'Features' },
{ type: 'fix', section: 'Bug Fixes' },
{ type: 'chore', section: 'Miscellaneous Chores', hidden: false },
{ type: 'docs', section: 'Documentation' },
{ type: 'style', section: 'Styles', hidden: true },
{ type: 'refactor', section: 'Code Refactoring' },
{ type: 'perf', section: 'Performance Improvements' },
{ type: 'test', section: 'Tests', hidden: true },
{ type: 'build', section: 'Build System', hidden: true },
{ type: 'ci', section: 'Continuous Integration', hidden: true },
],
packages: {},
};
function generateReleaseConfig() {
const config = { ...baseConfig };
// Add core package
config.packages['src/@awesome-cordova-plugins/core'] = {
'package-name': '@awesome-cordova-plugins/core',
};
// Add all plugin packages dynamically
PLUGIN_PATHS.forEach((pluginPath: string) => {
const pluginName = pluginPath.split(/[\/\\]+/).slice(-2)[0];
const packagePath = `src/@awesome-cordova-plugins/plugins/${pluginName}`;
config.packages[packagePath] = {
'package-name': `@awesome-cordova-plugins/${pluginName}`,
};
});
// Write the configuration file
const configPath = resolve(ROOT, 'release-please-config.json');
writeFileSync(configPath, JSON.stringify(config, null, 2));
console.log(`Generated release-please-config.json with ${Object.keys(config.packages).length} packages`);
console.log(`- 1 core package`);
console.log(`- ${Object.keys(config.packages).length - 1} plugin packages`);
}
generateReleaseConfig();
@@ -1,32 +0,0 @@
import { writeFileSync } from 'fs-extra';
import { resolve } from 'path';
import { PLUGIN_PATHS, ROOT } from '../build/helpers';
// Get the current version from package.json
const MAIN_PACKAGE_JSON = require('../../package.json');
const VERSION = MAIN_PACKAGE_JSON.version;
function generateReleaseManifest() {
const manifest = {};
// Add core package with current version
manifest['src/@awesome-cordova-plugins/core'] = VERSION;
// Add all plugin packages with current version
PLUGIN_PATHS.forEach((pluginPath: string) => {
const pluginName = pluginPath.split(/[\/\\]+/).slice(-2)[0];
const packagePath = `src/@awesome-cordova-plugins/plugins/${pluginName}`;
manifest[packagePath] = VERSION;
});
// Write the manifest file
const manifestPath = resolve(ROOT, '.release-please-manifest.json');
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
console.log(
`Generated .release-please-manifest.json with version ${VERSION} for ${Object.keys(manifest).length} packages`
);
}
generateReleaseManifest();
+46
View File
@@ -0,0 +1,46 @@
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import { join, resolve } from 'node:path';
import { parseArgs } from 'node:util';
const ROOT = resolve(__dirname, '../..');
const PLUGINS_DIR = join(ROOT, 'src/@awesome-cordova-plugins/plugins');
const TEMPLATES_DIR = join(ROOT, 'scripts/templates');
const { values } = parseArgs({
args: process.argv.slice(2),
options: {
n: { type: 'string', short: 'n' },
m: { type: 'boolean', short: 'm', default: false },
},
});
if (!values.n) {
console.log('Usage: tsx scripts/tasks/plugin-create -n PluginName [-m]');
process.exit(1);
}
const pluginName: string = values.n;
const useMinimal: boolean = !!values.m;
// Convert PascalCase to variants
const spaced = pluginName.replace(/(?!^)([A-Z])/g, ' $1');
const kebabCase = pluginName
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
.replace(/([A-Z])([A-Z][a-z])/g, '$1-$2')
.toLowerCase();
const camelCase = pluginName.charAt(0).toLowerCase() + pluginName.slice(1);
const templateFile = useMinimal ? 'wrap-min.tmpl' : 'wrap.tmpl';
const templatePath = join(TEMPLATES_DIR, templateFile);
let content = readFileSync(templatePath, 'utf-8');
content = content.replace(/\{\{ PluginName \}\}/g, pluginName);
content = content.replace(/\{\{ Plugin_Name \}\}/g, spaced);
content = content.replace(/\{\{ pluginName \}\}/g, camelCase);
content = content.replace(/\{\{ plugin-name \}\}/g, kebabCase);
const outDir = join(PLUGINS_DIR, kebabCase);
mkdirSync(outDir, { recursive: true });
writeFileSync(join(outDir, 'index.ts'), content, 'utf-8');
console.log(`Plugin created at: src/@awesome-cordova-plugins/plugins/${kebabCase}/index.ts`);
+57 -38
View File
@@ -1,24 +1,38 @@
import * as Queue from 'async-promise-queue';
import { exec } from 'child_process';
import { writeJSONSync } from 'fs-extra';
import { merge } from 'lodash';
import { cpus } from 'os';
import { join, resolve } from 'path';
import { exec as execCb } from 'node:child_process';
import { readFileSync, writeFileSync } from 'node:fs';
import { availableParallelism } from 'node:os';
import { join, resolve } from 'node:path';
import { promisify } from 'node:util';
const exec = promisify(execCb);
import { PLUGIN_PATHS, ROOT } from '../build/helpers';
import { Logger } from '../logger';
// tslint:disable-next-line:no-var-requires
const MAIN_PACKAGE_JSON = require('../../package.json');
const MAIN_PACKAGE_JSON = JSON.parse(readFileSync(resolve(__dirname, '../../package.json'), 'utf-8'));
const VERSION = MAIN_PACKAGE_JSON.version;
const FLAGS = '--access public';
const PACKAGE_JSON_BASE = {
description: 'Awesome Cordova Plugins - Native plugins for ionic apps',
main: 'index.js',
module: 'index.js',
typings: 'index.d.ts',
author: 'ionic',
type: 'module',
main: './index.js',
module: './index.js',
types: './index.d.ts',
exports: {
'.': {
types: './index.d.ts',
import: './index.js',
default: './index.js',
},
'./ngx': {
types: './ngx/index.d.ts',
import: './ngx/index.js',
default: './ngx/index.js',
},
},
sideEffects: false,
author: 'Daniel Sogl',
license: 'MIT',
repository: {
type: 'git',
@@ -39,22 +53,30 @@ const PLUGIN_PEER_DEPENDENCIES = {
};
function getPackageJsonContent(name: string, peerDependencies = {}, dependencies = {}) {
return merge(PACKAGE_JSON_BASE, {
const pkg = {
...structuredClone(PACKAGE_JSON_BASE),
name: '@awesome-cordova-plugins/' + name,
dependencies,
peerDependencies,
version: VERSION,
});
};
// Core package has no ngx subfolder
if (name === 'core') {
delete pkg.exports['./ngx'];
}
return pkg;
}
function writePackageJson(data: any, dir: string) {
const filePath = resolve(dir, 'package.json');
writeJSONSync(filePath, data);
writeFileSync(filePath, JSON.stringify(data, null, 2));
PACKAGES.push(dir);
}
function writeNGXPackageJson(data: any, dir: string) {
const filePath = resolve(dir, 'package.json');
writeJSONSync(filePath, data);
writeFileSync(filePath, JSON.stringify(data, null, 2));
}
function prepare() {
// write @awesome-cordova-plugins/core package.json
@@ -74,32 +96,29 @@ function prepare() {
});
}
async function publishPackage(pkg: string, ignoreErrors: boolean): Promise<void> {
try {
const { stdout } = await exec(`npm publish ${pkg} ${FLAGS}`);
if (stdout) Logger.verbose(stdout.trim());
} catch (err: any) {
if (err.message?.includes('You cannot publish over the previously published version')) {
Logger.verbose('Ignoring duplicate version error.');
return;
}
if (!ignoreErrors) throw err;
}
}
async function publish(ignoreErrors = false) {
Logger.profile('Publishing');
// upload 1 package per CPU thread at a time
const worker = Queue.async.asyncify(
(pkg: any) =>
new Promise<string | void>((resolve, reject) => {
exec(`npm publish ${pkg} ${FLAGS}`, (err, stdout) => {
if (stdout) {
Logger.verbose(stdout.trim());
resolve(stdout);
}
if (err) {
if (!ignoreErrors) {
if (err.message.includes('You cannot publish over the previously published version')) {
Logger.verbose('Ignoring duplicate version error.');
return resolve();
}
reject(err);
}
}
});
})
);
const concurrency = availableParallelism();
const queue = [...PACKAGES];
try {
await Queue(worker, PACKAGES, cpus().length);
while (queue.length > 0) {
const batch = queue.splice(0, concurrency);
await Promise.all(batch.map((pkg) => publishPackage(pkg, ignoreErrors)));
}
Logger.info('Done publishing!');
} catch (e) {
Logger.error('Error publishing!');