Compare commits
14 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
4bbde6097f | ||
![]() |
8e60dcb97b | ||
![]() |
e62b2c3fbc | ||
![]() |
ea95438786 | ||
![]() |
b2bee87c02 | ||
![]() |
0ac018073c | ||
![]() |
d3546313a3 | ||
![]() |
1be82e0451 | ||
![]() |
2c02f62569 | ||
![]() |
0713337744 | ||
![]() |
ab46e608ea | ||
![]() |
03b16a432c | ||
![]() |
a163917428 | ||
![]() |
0391559000 |
10
.gitignore
vendored
10
.gitignore
vendored
@ -75,22 +75,12 @@ typings/
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and *not* Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
|
198
dist/index.js
vendored
Normal file
198
dist/index.js
vendored
Normal file
@ -0,0 +1,198 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.ImageGenerator = void 0;
|
||||
//Modules
|
||||
// const AdmZip = require('adm-zip');
|
||||
const adm_zip_1 = __importDefault(require("adm-zip"));
|
||||
const fs_extra_1 = require("fs-extra");
|
||||
const path_1 = __importDefault(require("path"));
|
||||
const pcb_stackup_1 = __importDefault(require("pcb-stackup"));
|
||||
const sharp_1 = __importDefault(require("sharp"));
|
||||
const node_stream_1 = require("node:stream");
|
||||
const node_fs_1 = require("node:fs");
|
||||
//Class definition
|
||||
class ImageGenerator {
|
||||
constructor(folderConfig, imgConfig, layerNames) {
|
||||
this.folderConfig = folderConfig;
|
||||
this.imgConfig = imgConfig;
|
||||
this.layerNames = layerNames;
|
||||
//Ensure folders exist
|
||||
if (!(0, node_fs_1.existsSync)(folderConfig.tmpDir))
|
||||
throw new Error('Temp dir does not exist');
|
||||
if (!(0, node_fs_1.existsSync)(folderConfig.imgDir))
|
||||
throw new Error('Image dir does not exist');
|
||||
//Check folder permissions
|
||||
(0, node_fs_1.accessSync)(folderConfig.tmpDir, node_fs_1.constants.R_OK | node_fs_1.constants.W_OK);
|
||||
(0, node_fs_1.accessSync)(folderConfig.imgDir, node_fs_1.constants.R_OK | node_fs_1.constants.W_OK);
|
||||
}
|
||||
/**
|
||||
* Extracts the passed in zip file
|
||||
|
||||
*/
|
||||
extractArchive(fileName, tmpDir) {
|
||||
// Check archive exists
|
||||
if (!(0, node_fs_1.existsSync)(fileName)) {
|
||||
throw Error('Archive does not exist.');
|
||||
}
|
||||
//Check temp folder exists
|
||||
if (!(0, node_fs_1.existsSync)(tmpDir)) {
|
||||
throw Error('Temporary folder does not exist.');
|
||||
}
|
||||
const zip = new adm_zip_1.default(fileName);
|
||||
zip.extractAllTo(path_1.default.join(tmpDir, 'archive'));
|
||||
return zip.getEntries().length;
|
||||
}
|
||||
//Test archive
|
||||
testArchive(fileName, tmpDir) {
|
||||
// Check archive exists
|
||||
try {
|
||||
if (!(0, node_fs_1.existsSync)(fileName)) {
|
||||
throw Error('Archive does not exist.');
|
||||
}
|
||||
if (!(0, node_fs_1.existsSync)(tmpDir)) {
|
||||
throw Error('Temporary folder does not exist.');
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
const zip = new adm_zip_1.default(fileName);
|
||||
return zip.getEntries().length;
|
||||
}
|
||||
//Layer promise
|
||||
getLayers(dir, layerNames) {
|
||||
//Check correct number of layers and folder exists
|
||||
layerNames.forEach((layerName) => {
|
||||
if (!(0, node_fs_1.existsSync)(path_1.default.join(dir, layerName))) {
|
||||
throw `Missing layer: ${layerName}`;
|
||||
}
|
||||
});
|
||||
if (!(0, node_fs_1.existsSync)(dir)) {
|
||||
throw new Error('Folder not there');
|
||||
}
|
||||
//Return layer promise
|
||||
const layersPromise = new Promise(function (resolve, reject) {
|
||||
const layers = layerNames.map((layerName) => ({
|
||||
filename: layerName,
|
||||
gerber: (0, node_fs_1.createReadStream)(path_1.default.join(dir, layerName)),
|
||||
}));
|
||||
if (layers.length === layerNames.length) {
|
||||
resolve(layers);
|
||||
}
|
||||
else {
|
||||
reject('Invalid layer count');
|
||||
}
|
||||
});
|
||||
return layersPromise;
|
||||
}
|
||||
//Clean up the archive folder in the specified directory
|
||||
static cleanupFiles(dir) {
|
||||
try {
|
||||
const folder = path_1.default.join(dir, 'archive');
|
||||
(0, fs_extra_1.emptyDirSync)(folder);
|
||||
}
|
||||
catch (error) {
|
||||
if (error instanceof Error) {
|
||||
console.error(error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
// * Take an archive containing gerber files, config object, temporary dir
|
||||
// * and output dir and create a PNG image from the gerber in the output dir
|
||||
// * @param {string} gerber Path to an archive file containing gerber
|
||||
// * @returns {Promise.<string>} Promise to return path to image
|
||||
gerberToImage(gerber) {
|
||||
// Create output dir if it doesn't exist
|
||||
try {
|
||||
(0, fs_extra_1.ensureDirSync)(this.folderConfig.imgDir);
|
||||
}
|
||||
catch (error) {
|
||||
if (error instanceof Error) {
|
||||
console.error(error.message);
|
||||
}
|
||||
}
|
||||
// Check temp and output dirs exist
|
||||
if (!(0, node_fs_1.existsSync)(gerber)) {
|
||||
throw Error('Archive does not exist.');
|
||||
}
|
||||
if (!(0, node_fs_1.existsSync)(this.folderConfig.tmpDir)) {
|
||||
throw Error('Temporary folder does not exist.');
|
||||
}
|
||||
if (!(0, node_fs_1.existsSync)(this.folderConfig.imgDir)) {
|
||||
throw Error('Output folder does not exist.');
|
||||
}
|
||||
// Set filenames
|
||||
//Use the filename of the gerber zip to determine the output png filename
|
||||
const imageName = path_1.default.basename(gerber, '.zip');
|
||||
const destFile = `${path_1.default.join(this.folderConfig.imgDir, imageName)}.png`;
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.layerNames) {
|
||||
throw new Error('You must supply an array of layer names.');
|
||||
}
|
||||
this.extractArchive(gerber, this.folderConfig.tmpDir);
|
||||
this.getLayers(path_1.default.join(this.folderConfig.tmpDir, 'archive'), this.layerNames)
|
||||
.then(pcb_stackup_1.default)
|
||||
.then((stackup) => {
|
||||
(0, sharp_1.default)(Buffer.from(stackup.top.svg), {
|
||||
density: this.imgConfig.density,
|
||||
})
|
||||
.resize({ width: this.imgConfig.resizeWidth })
|
||||
.png({ compressionLevel: this.imgConfig.compLevel })
|
||||
.toFile(destFile);
|
||||
})
|
||||
.then(() => {
|
||||
ImageGenerator.cleanupFiles(this.folderConfig.tmpDir);
|
||||
resolve(destFile);
|
||||
})
|
||||
.catch((e) => {
|
||||
ImageGenerator.cleanupFiles(this.folderConfig.tmpDir);
|
||||
reject(new Error(e));
|
||||
});
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Take an archive containing gerber files and return a stream containing
|
||||
* a PNG image from the gerber */
|
||||
gerberToStream(gerber) {
|
||||
// Check temp and output dirs exist
|
||||
if (!(0, node_fs_1.existsSync)(gerber)) {
|
||||
throw Error('Archive does not exist.');
|
||||
}
|
||||
if (!(0, node_fs_1.existsSync)(this.folderConfig.tmpDir)) {
|
||||
throw Error('Temporary folder does not exist.');
|
||||
}
|
||||
if (!(0, node_fs_1.existsSync)(this.folderConfig.imgDir)) {
|
||||
throw Error('Output folder does not exist.');
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
this.extractArchive(gerber, this.folderConfig.tmpDir);
|
||||
if (!this.layerNames)
|
||||
throw new Error('No layers provided');
|
||||
this.getLayers(path_1.default.join(this.folderConfig.tmpDir, 'archive'), this.layerNames)
|
||||
.then(pcb_stackup_1.default)
|
||||
.then((stackup) => {
|
||||
(0, sharp_1.default)(Buffer.from(stackup.top.svg), {
|
||||
density: this.imgConfig.density,
|
||||
})
|
||||
.resize({ width: this.imgConfig.resizeWidth })
|
||||
.png({ compressionLevel: this.imgConfig.compLevel })
|
||||
.toBuffer()
|
||||
.then((buffer) => {
|
||||
ImageGenerator.cleanupFiles(this.folderConfig.tmpDir);
|
||||
const stream = new node_stream_1.Readable();
|
||||
stream.push(buffer);
|
||||
stream.push(null);
|
||||
resolve(stream);
|
||||
});
|
||||
})
|
||||
.catch((e) => {
|
||||
ImageGenerator.cleanupFiles(this.folderConfig.tmpDir);
|
||||
reject(new Error(e));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
exports.ImageGenerator = ImageGenerator;
|
@ -1,17 +1,18 @@
|
||||
import js from '@eslint/js';
|
||||
import globals from 'globals';
|
||||
import { defineConfig, globalIgnores } from 'eslint/config';
|
||||
// @ts-check
|
||||
|
||||
export default defineConfig([
|
||||
globalIgnores(['test/**/*', 'node_modules/**/*']),
|
||||
import eslint from '@eslint/js';
|
||||
import tseslint from 'typescript-eslint';
|
||||
|
||||
export default tseslint.config(
|
||||
eslint.configs.recommended,
|
||||
tseslint.configs.recommended,
|
||||
{
|
||||
files: ['**/*.{js,mjs,cjs}'],
|
||||
plugins: { js },
|
||||
extends: ['js/recommended'],
|
||||
ignores: [
|
||||
'dist/**',
|
||||
'test/**',
|
||||
'node_modules/**',
|
||||
'webpack.dev.js',
|
||||
'webpack.prod.js',
|
||||
],
|
||||
},
|
||||
{ files: ['**/*.js'], languageOptions: { sourceType: 'commonjs' } },
|
||||
{
|
||||
files: ['**/*.{js,mjs,cjs}'],
|
||||
languageOptions: { globals: globals.browser },
|
||||
},
|
||||
]);
|
||||
);
|
||||
|
256
index.js
256
index.js
@ -1,256 +0,0 @@
|
||||
//Modules
|
||||
const AdmZip = require('adm-zip');
|
||||
const { emptyDirSync } = require('fs-extra');
|
||||
const path = require('path');
|
||||
const pcbStackup = require('pcb-stackup');
|
||||
const sharp = require('sharp');
|
||||
const { Readable } = require('node:stream');
|
||||
const { Buffer } = require('node:buffer');
|
||||
const {
|
||||
existsSync,
|
||||
accessSync,
|
||||
createReadStream,
|
||||
mkdirSync,
|
||||
chmodSync,
|
||||
constants,
|
||||
} = require('node:fs');
|
||||
|
||||
//ensureDirSync method
|
||||
function ensureDirSync(directory) {
|
||||
if (!existsSync(directory)) {
|
||||
mkdirSync(directory, { recursive: true });
|
||||
chmodSync(directory, 0o644);
|
||||
}
|
||||
}
|
||||
|
||||
//Class definition
|
||||
class ImageGenerator {
|
||||
constructor(folderConfig, imgConfig, layerNames) {
|
||||
this.tmpDir = folderConfig.tmpDir;
|
||||
this.imgDir = folderConfig.imgDir;
|
||||
this.imgConfig = imgConfig;
|
||||
this.layerNames = layerNames;
|
||||
|
||||
//Ensure folders exist
|
||||
try {
|
||||
if (!existsSync(this.tmpDir)) throw 'Temp dir does not exist';
|
||||
if (!existsSync(this.imgDir)) throw 'Image dir does not exist';
|
||||
} catch (error) {
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
//Check folder permissions
|
||||
try {
|
||||
accessSync(this.tmpDir, constants.R_OK | constants.W_OK);
|
||||
accessSync(this.imgDir, constants.R_OK | constants.W_OK);
|
||||
} catch (error) {
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the passed in zip file
|
||||
* @param {string} fileName Name of the file to be extracted
|
||||
* @param {string} tmpDir Temporary directory to extract to
|
||||
* @returns {number} Number of objects contained in the archive
|
||||
*/
|
||||
static extractArchive(fileName, tmpDir) {
|
||||
// Check archive exists
|
||||
try {
|
||||
if (!existsSync(fileName)) {
|
||||
throw Error('Archive does not exist.');
|
||||
}
|
||||
if (!existsSync(tmpDir)) {
|
||||
throw Error('Temporary folder does not exist.');
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
|
||||
const zip = new AdmZip(fileName);
|
||||
zip.extractAllTo(path.join(tmpDir, 'archive'));
|
||||
|
||||
return zip.getEntries().length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporary test method zip file
|
||||
* @param {string} fileName Name of the file to be extracted
|
||||
* @param {string} tmpDir Temporary directory to extract to
|
||||
* @returns {number} Number of objects contained in the archive
|
||||
*/
|
||||
static testArchive(fileName, tmpDir) {
|
||||
// Check archive exists
|
||||
try {
|
||||
if (!existsSync(fileName)) {
|
||||
throw Error('Archive does not exist.');
|
||||
}
|
||||
if (!existsSync(tmpDir)) {
|
||||
throw Error('Temporary folder does not exist.');
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
try {
|
||||
const zip = new AdmZip(fileName);
|
||||
return zip.getEntries().length;
|
||||
} catch (error) {
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Take in a directory of layer files and return an array of the layers files
|
||||
* @param {string} dir Directory containing layer files
|
||||
* @param {Array} layerNames Array of filenames for the desired layers
|
||||
* @returns {Array} Array of paths to the layers files
|
||||
*/
|
||||
static getLayers(dir, layerNames) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Make sure the directory exists
|
||||
if (!existsSync(dir)) {
|
||||
return reject(new Error('Layers folder does not exist.'));
|
||||
}
|
||||
// Check that the required layer files exist in source dir
|
||||
let layersValid = true;
|
||||
layerNames.forEach((layer) => {
|
||||
if (!existsSync(path.join(dir, layer))) layersValid = false;
|
||||
});
|
||||
if (!layersValid) return reject(new Error('Layer not found.'));
|
||||
// Construct array of layers that match the supplied filenames array
|
||||
const layers = layerNames.map((layerName) => ({
|
||||
filename: layerName,
|
||||
gerber: createReadStream(path.join(dir, layerName)),
|
||||
}));
|
||||
return resolve(layers);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up the archive folder in the specified directory
|
||||
* @param {string} dir Path to a directory to clean up
|
||||
*/
|
||||
static cleanupFiles(dir) {
|
||||
try {
|
||||
const folder = path.join(dir, 'archive');
|
||||
emptyDirSync(folder);
|
||||
} catch (err) {
|
||||
throw new Error(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Take an archive containing gerber files, config object, temporary dir
|
||||
* and output dir and create a PNG image from the gerber in the output dir
|
||||
* @param {string} gerber Path to an archive file containing gerber
|
||||
* @returns {Promise.<string>} Promise to return path to image
|
||||
*/
|
||||
gerberToImage(gerber) {
|
||||
// Create output dir if it doesn't exist
|
||||
try {
|
||||
// fs.ensureDirSync(this.imgDir, 0o644);
|
||||
ensureDirSync(this.imgDir);
|
||||
} catch (e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
|
||||
// Check temp and output dirs exist
|
||||
try {
|
||||
if (!existsSync(gerber)) {
|
||||
throw Error('Archive does not exist.');
|
||||
}
|
||||
if (!existsSync(this.tmpDir)) {
|
||||
throw Error('Temporary folder does not exist.');
|
||||
}
|
||||
if (!existsSync(this.imgDir)) {
|
||||
throw Error('Output folder does not exist.');
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
|
||||
// Set filenames
|
||||
const imageName = path.basename(gerber, '.zip');
|
||||
const destFile = `${path.join(this.imgDir, imageName)}.png`;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
ImageGenerator.extractArchive(gerber, this.tmpDir);
|
||||
ImageGenerator.getLayers(
|
||||
path.join(this.tmpDir, 'archive'),
|
||||
this.layerNames,
|
||||
)
|
||||
.then(pcbStackup)
|
||||
.then((stackup) => {
|
||||
sharp(Buffer.from(stackup.top.svg), {
|
||||
density: this.imgConfig.density,
|
||||
})
|
||||
.resize({ width: this.imgConfig.resizeWidth })
|
||||
.png({ compressionLevel: this.imgConfig.compLevel })
|
||||
.toFile(destFile);
|
||||
})
|
||||
.then(() => {
|
||||
ImageGenerator.cleanupFiles(this.tmpDir);
|
||||
resolve(destFile);
|
||||
})
|
||||
.catch((e) => {
|
||||
ImageGenerator.cleanupFiles(this.tmpDir);
|
||||
reject(new Error(e));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Take an archive containing gerber files and return a stream containing
|
||||
* a PNG image from the gerber
|
||||
* @param {string} gerber Path to an archive file containing gerber
|
||||
* @returns {Promise.<stream.Readable>} Promise that resolves to a PNG stream
|
||||
*/
|
||||
gerberToStream(gerber) {
|
||||
// Check temp and output dirs exist
|
||||
try {
|
||||
if (!existsSync(gerber)) {
|
||||
throw Error('Archive does not exist.');
|
||||
}
|
||||
if (!existsSync(this.tmpDir)) {
|
||||
throw Error('Temporary folder does not exist.');
|
||||
}
|
||||
if (!existsSync(this.imgDir)) {
|
||||
throw Error('Output folder does not exist.');
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
ImageGenerator.extractArchive(gerber, this.tmpDir);
|
||||
ImageGenerator.getLayers(
|
||||
path.join(this.tmpDir, 'archive'),
|
||||
this.layerNames,
|
||||
)
|
||||
.then(pcbStackup)
|
||||
.then((stackup) => {
|
||||
sharp(Buffer.from(stackup.top.svg), {
|
||||
density: this.imgConfig.density,
|
||||
})
|
||||
.resize({ width: this.imgConfig.resizeWidth })
|
||||
.png({ compressionLevel: this.imgConfig.compLevel })
|
||||
.toBuffer()
|
||||
.then((buffer) => {
|
||||
ImageGenerator.cleanupFiles(this.tmpDir);
|
||||
const stream = new Readable();
|
||||
stream.push(buffer);
|
||||
stream.push(null);
|
||||
resolve(stream);
|
||||
});
|
||||
})
|
||||
.catch((e) => {
|
||||
ImageGenerator.cleanupFiles(this.tmpDir);
|
||||
reject(new Error(e));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
ImageGenerator,
|
||||
};
|
1093
package-lock.json
generated
1093
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
45
package.json
45
package.json
@ -1,10 +1,13 @@
|
||||
{
|
||||
"name": "@nplayfair/npe_gerber",
|
||||
"version": "0.2.0",
|
||||
"version": "1.0.2",
|
||||
"description": "Create a PCB image from gerber files",
|
||||
"main": "index.js",
|
||||
"main": "dist/index.js",
|
||||
"types": "types/npe_gerber.d.ts",
|
||||
"scripts": {
|
||||
"test": "jest",
|
||||
"build": "tsc --build",
|
||||
"test": "NODE_ENV=test PORT=7788 jest",
|
||||
"test:watch": "npm run test -- --watchAll",
|
||||
"lint": "eslint ."
|
||||
},
|
||||
"repository": {
|
||||
@ -22,18 +25,48 @@
|
||||
"image",
|
||||
"gerber"
|
||||
],
|
||||
"jest": {
|
||||
"verbose": true,
|
||||
"modulePathIgnorePatterns": [
|
||||
"<rootDir>/node_modules"
|
||||
],
|
||||
"roots": [
|
||||
"<rootDir>/test"
|
||||
],
|
||||
"transform": {
|
||||
"^.+\\.tsx?$": "ts-jest"
|
||||
},
|
||||
"testEnvironment": "node",
|
||||
"testPathIgnorePatterns": [
|
||||
"/node_modules"
|
||||
],
|
||||
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$",
|
||||
"moduleFileExtensions": [
|
||||
"ts",
|
||||
"tsx",
|
||||
"js",
|
||||
"jsx",
|
||||
"json",
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"adm-zip": "^0.5.16",
|
||||
"fs-extra": "^11.3.0",
|
||||
"jszip": "^3.10.1",
|
||||
"pcb-stackup": "^4.2.8",
|
||||
"sharp": "^0.34.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.29.0",
|
||||
"@types/adm-zip": "^0.5.7",
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@types/jest": "^29.5.14",
|
||||
"eslint": "^9.29.0",
|
||||
"globals": "^16.2.0",
|
||||
"jest": "^30.0.0",
|
||||
"prettier": "^3.5.3"
|
||||
"prettier": "^3.5.3",
|
||||
"ts-jest": "^29.4.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript-eslint": "^8.34.0"
|
||||
}
|
||||
}
|
||||
|
210
src/index.ts
Normal file
210
src/index.ts
Normal file
@ -0,0 +1,210 @@
|
||||
//Modules
|
||||
// const AdmZip = require('adm-zip');
|
||||
import AdmZip from 'adm-zip';
|
||||
import { emptyDirSync, ensureDirSync } from 'fs-extra';
|
||||
import path from 'path';
|
||||
import pcbStackup from 'pcb-stackup';
|
||||
import sharp from 'sharp';
|
||||
import { Readable } from 'node:stream';
|
||||
import { existsSync, accessSync, createReadStream, constants } from 'node:fs';
|
||||
|
||||
//Class definition
|
||||
export class ImageGenerator implements ZipExtractor, LayerGenerator {
|
||||
constructor(
|
||||
public folderConfig: FolderConfig,
|
||||
public imgConfig: ImageConfig,
|
||||
public layerNames?: string[],
|
||||
) {
|
||||
//Ensure folders exist
|
||||
if (!existsSync(folderConfig.tmpDir))
|
||||
throw new Error('Temp dir does not exist');
|
||||
|
||||
if (!existsSync(folderConfig.imgDir))
|
||||
throw new Error('Image dir does not exist');
|
||||
|
||||
//Check folder permissions
|
||||
accessSync(folderConfig.tmpDir, constants.R_OK | constants.W_OK);
|
||||
accessSync(folderConfig.imgDir, constants.R_OK | constants.W_OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the passed in zip file
|
||||
|
||||
*/
|
||||
public extractArchive(fileName: string, tmpDir: string): number {
|
||||
// Check archive exists
|
||||
if (!existsSync(fileName)) {
|
||||
throw Error('Archive does not exist.');
|
||||
}
|
||||
//Check temp folder exists
|
||||
if (!existsSync(tmpDir)) {
|
||||
throw Error('Temporary folder does not exist.');
|
||||
}
|
||||
|
||||
const zip = new AdmZip(fileName);
|
||||
zip.extractAllTo(path.join(tmpDir, 'archive'));
|
||||
|
||||
return zip.getEntries().length;
|
||||
}
|
||||
|
||||
//Test archive
|
||||
public testArchive(fileName: string, tmpDir: string): number {
|
||||
// Check archive exists
|
||||
try {
|
||||
if (!existsSync(fileName)) {
|
||||
throw Error('Archive does not exist.');
|
||||
}
|
||||
if (!existsSync(tmpDir)) {
|
||||
throw Error('Temporary folder does not exist.');
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
console.error(e);
|
||||
}
|
||||
const zip = new AdmZip(fileName);
|
||||
return zip.getEntries().length;
|
||||
}
|
||||
|
||||
//Layer promise
|
||||
public getLayers(dir: string, layerNames: string[]): Promise<Layers[]> {
|
||||
//Check correct number of layers and folder exists
|
||||
layerNames.forEach((layerName) => {
|
||||
if (!existsSync(path.join(dir, layerName))) {
|
||||
throw `Missing layer: ${layerName}`;
|
||||
}
|
||||
});
|
||||
if (!existsSync(dir)) {
|
||||
throw new Error('Folder not there');
|
||||
}
|
||||
//Return layer promise
|
||||
const layersPromise = new Promise<Layers[]>(function (resolve, reject) {
|
||||
const layers: Layers[] = layerNames.map((layerName: string) => ({
|
||||
filename: layerName,
|
||||
gerber: createReadStream(path.join(dir, layerName)),
|
||||
}));
|
||||
if (layers.length === layerNames.length) {
|
||||
resolve(layers);
|
||||
} else {
|
||||
reject('Invalid layer count');
|
||||
}
|
||||
});
|
||||
return layersPromise;
|
||||
}
|
||||
|
||||
//Clean up the archive folder in the specified directory
|
||||
public static cleanupFiles(dir: string): void {
|
||||
try {
|
||||
const folder = path.join(dir, 'archive');
|
||||
emptyDirSync(folder);
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
console.error(error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// * Take an archive containing gerber files, config object, temporary dir
|
||||
// * and output dir and create a PNG image from the gerber in the output dir
|
||||
// * @param {string} gerber Path to an archive file containing gerber
|
||||
// * @returns {Promise.<string>} Promise to return path to image
|
||||
|
||||
public gerberToImage(gerber: string) {
|
||||
// Create output dir if it doesn't exist
|
||||
try {
|
||||
ensureDirSync(this.folderConfig.imgDir);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
console.error(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Check temp and output dirs exist
|
||||
if (!existsSync(gerber)) {
|
||||
throw Error('Archive does not exist.');
|
||||
}
|
||||
if (!existsSync(this.folderConfig.tmpDir)) {
|
||||
throw Error('Temporary folder does not exist.');
|
||||
}
|
||||
if (!existsSync(this.folderConfig.imgDir)) {
|
||||
throw Error('Output folder does not exist.');
|
||||
}
|
||||
|
||||
// Set filenames
|
||||
//Use the filename of the gerber zip to determine the output png filename
|
||||
const imageName = path.basename(gerber, '.zip');
|
||||
const destFile = `${path.join(this.folderConfig.imgDir, imageName)}.png`;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.layerNames) {
|
||||
throw new Error('You must supply an array of layer names.');
|
||||
}
|
||||
this.extractArchive(gerber, this.folderConfig.tmpDir);
|
||||
this.getLayers(
|
||||
path.join(this.folderConfig.tmpDir, 'archive'),
|
||||
this.layerNames,
|
||||
)
|
||||
.then(pcbStackup)
|
||||
.then((stackup) => {
|
||||
sharp(Buffer.from(stackup.top.svg as ArrayLike<number>), {
|
||||
density: this.imgConfig.density,
|
||||
})
|
||||
.resize({ width: this.imgConfig.resizeWidth })
|
||||
.png({ compressionLevel: this.imgConfig.compLevel })
|
||||
.toFile(destFile);
|
||||
})
|
||||
.then(() => {
|
||||
ImageGenerator.cleanupFiles(this.folderConfig.tmpDir);
|
||||
resolve(destFile);
|
||||
})
|
||||
.catch((e) => {
|
||||
ImageGenerator.cleanupFiles(this.folderConfig.tmpDir);
|
||||
reject(new Error(e));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Take an archive containing gerber files and return a stream containing
|
||||
* a PNG image from the gerber */
|
||||
|
||||
gerberToStream(gerber: string) {
|
||||
// Check temp and output dirs exist
|
||||
if (!existsSync(gerber)) {
|
||||
throw Error('Archive does not exist.');
|
||||
}
|
||||
if (!existsSync(this.folderConfig.tmpDir)) {
|
||||
throw Error('Temporary folder does not exist.');
|
||||
}
|
||||
if (!existsSync(this.folderConfig.imgDir)) {
|
||||
throw Error('Output folder does not exist.');
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.extractArchive(gerber, this.folderConfig.tmpDir);
|
||||
if (!this.layerNames) throw new Error('No layers provided');
|
||||
this.getLayers(
|
||||
path.join(this.folderConfig.tmpDir, 'archive'),
|
||||
this.layerNames,
|
||||
)
|
||||
.then(pcbStackup)
|
||||
.then((stackup) => {
|
||||
sharp(Buffer.from(stackup.top.svg as ArrayLike<number>), {
|
||||
density: this.imgConfig.density,
|
||||
})
|
||||
.resize({ width: this.imgConfig.resizeWidth })
|
||||
.png({ compressionLevel: this.imgConfig.compLevel })
|
||||
.toBuffer()
|
||||
.then((buffer) => {
|
||||
ImageGenerator.cleanupFiles(this.folderConfig.tmpDir);
|
||||
const stream = new Readable();
|
||||
stream.push(buffer);
|
||||
stream.push(null);
|
||||
resolve(stream);
|
||||
});
|
||||
})
|
||||
.catch((e) => {
|
||||
ImageGenerator.cleanupFiles(this.folderConfig.tmpDir);
|
||||
reject(new Error(e));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
@ -1,8 +1,9 @@
|
||||
const path = require('path');
|
||||
const { readdirSync, ReadStream } = require('node:fs');
|
||||
const { Readable } = require('node:stream');
|
||||
const { ImageGenerator } = require('../index.js');
|
||||
require('../index.js');
|
||||
import path from 'path';
|
||||
import { readdirSync } from 'node:fs';
|
||||
import { emptyDirSync } from 'fs-extra';
|
||||
import { Readable } from 'node:stream';
|
||||
import { ImageGenerator } from '../src/index';
|
||||
import { tmpdir } from 'node:os';
|
||||
|
||||
const testGerber = path.join(__dirname, 'Arduino-Pro-Mini.zip');
|
||||
const incompleteGerber = path.join(__dirname, 'incomplete.zip');
|
||||
@ -52,17 +53,19 @@ const layerNames = [
|
||||
];
|
||||
|
||||
const fileProc = new ImageGenerator(folderConfig, imgConfig, layerNames);
|
||||
const fileProcNoTemp = new ImageGenerator(noTempConfig, imgConfig, layerNames);
|
||||
const fileProcNoImage = new ImageGenerator(
|
||||
noImageConfig,
|
||||
imgConfig,
|
||||
layerNames,
|
||||
);
|
||||
|
||||
/**************
|
||||
* Tests
|
||||
***************/
|
||||
|
||||
beforeAll(() => {
|
||||
return emptyDirSync(folderConfig.tmpDir);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
return emptyDirSync(emptyFolder);
|
||||
});
|
||||
|
||||
// Test constructor
|
||||
describe('Creating an ImageGenerator object', () => {
|
||||
const imgGen = new ImageGenerator(folderConfig, imgConfig);
|
||||
@ -80,8 +83,11 @@ describe('Creating an ImageGenerator object', () => {
|
||||
expect(imgGen.imgConfig.compLevel).toBe(1);
|
||||
});
|
||||
test('folders should be the ones specified in the folder config parameter', () => {
|
||||
expect(imgGen.tmpDir).toBe(path.join(__dirname, 'tmp'));
|
||||
expect(imgGen.imgDir).toBe(path.join(__dirname, 'tmp'));
|
||||
expect(imgGen.folderConfig.tmpDir).toBe(path.join(__dirname, 'tmp'));
|
||||
expect(imgGen.folderConfig.imgDir).toBe(path.join(__dirname, 'tmp'));
|
||||
});
|
||||
afterAll(() => {
|
||||
return emptyDirSync(folderConfig.tmpDir);
|
||||
});
|
||||
});
|
||||
|
||||
@ -109,61 +115,50 @@ describe('Passing in', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// Testing static methods
|
||||
//Layer methods
|
||||
describe('Getting layers', () => {
|
||||
test('should return a promise of array layers', () => {
|
||||
expect.assertions(1);
|
||||
return ImageGenerator.getLayers(testLayers, layerNames).then((data) => {
|
||||
expect(data).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
filename: expect.any(String),
|
||||
gerber: expect.any(ReadStream),
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
const imgGen = new ImageGenerator(folderConfig, imgConfig);
|
||||
test('should return a promise of array of layers', () => {
|
||||
expect(imgGen.getLayers(testLayers, layerNames)).resolves.toBeInstanceOf(
|
||||
Array,
|
||||
);
|
||||
});
|
||||
test('should reject promise with error if the layers folder is not valid', () => {
|
||||
expect.assertions(1);
|
||||
return expect(
|
||||
ImageGenerator.getLayers('./invalid_folder', layerNames),
|
||||
).rejects.toThrow(new Error('Layers folder does not exist.'));
|
||||
|
||||
test('should throw error if the layers folder is not valid', () => {
|
||||
expect(() => {
|
||||
imgGen.getLayers('some_invalid_folder', layerNames);
|
||||
}).toThrow();
|
||||
});
|
||||
test('should reject promise with error if there is not the correct number of layers', () => {
|
||||
expect.assertions(1);
|
||||
return expect(
|
||||
ImageGenerator.getLayers(emptyFolder, layerNames),
|
||||
).rejects.toThrow(new Error('Layer not found.'));
|
||||
|
||||
test('should throw error if incorrect number of layers supplied', () => {
|
||||
expect(() => {
|
||||
imgGen.getLayers(emptyFolder, layerNames);
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
//Archive methods
|
||||
describe('When extracting an archive', () => {
|
||||
const imgGen = new ImageGenerator(folderConfig, imgConfig);
|
||||
test('a non-existent archive should throw an error', () => {
|
||||
expect(() =>
|
||||
ImageGenerator.extractArchive('invalid.zip', folderConfig.tmpDir),
|
||||
imgGen.extractArchive('invalid.zip', folderConfig.tmpDir),
|
||||
).toThrow();
|
||||
});
|
||||
test('if the temp dir does not exist it should throw an error', () => {
|
||||
expect(() =>
|
||||
ImageGenerator.extractArchive(testGerber, './invalid_dir'),
|
||||
).toThrow(Error);
|
||||
expect(() => imgGen.extractArchive(testGerber, 'some_invalid_dir')).toThrow(
|
||||
Error,
|
||||
);
|
||||
});
|
||||
test('it should load the archive and return the number of files extracted', () => {
|
||||
expect(() => {
|
||||
ImageGenerator.testArchive(testGerber, archiveTestFolder);
|
||||
imgGen.testArchive(testGerber, archiveTestFolder);
|
||||
}).not.toThrow();
|
||||
expect(ImageGenerator.testArchive(testGerber, archiveTestFolder)).toEqual(
|
||||
12,
|
||||
);
|
||||
expect(imgGen.testArchive(testGerber, archiveTestFolder)).toEqual(12);
|
||||
});
|
||||
test('it should extract archive and all files should be present', () => {
|
||||
expect(ImageGenerator.testArchive(testGerber, archiveTestFolder)).toEqual(
|
||||
12,
|
||||
);
|
||||
ImageGenerator.extractArchive(testGerber, archiveTestFolder);
|
||||
expect(imgGen.testArchive(testGerber, archiveTestFolder)).toEqual(12);
|
||||
imgGen.extractArchive(testGerber, archiveTestFolder);
|
||||
const dirents = readdirSync(archiveTestFolder, {
|
||||
recursive: true,
|
||||
withFileTypes: true,
|
||||
@ -171,34 +166,27 @@ describe('When extracting an archive', () => {
|
||||
const numOutputFiles = dirents.filter((dirent) => dirent.isFile());
|
||||
expect(numOutputFiles).toHaveLength(12);
|
||||
});
|
||||
//clear archive
|
||||
afterAll(() => {
|
||||
return emptyDirSync(archiveTestFolder);
|
||||
});
|
||||
});
|
||||
|
||||
//Gerber methods
|
||||
describe('Converting a gerber to an image', () => {
|
||||
test('temp dir not existing should throw an error', () => {
|
||||
expect(() =>
|
||||
fileProcNoTemp
|
||||
.gerberToImage(testGerber)
|
||||
.toThrow(new Error('Temporary folder does not exist.')),
|
||||
);
|
||||
beforeEach(() => {
|
||||
return emptyDirSync(emptyFolder);
|
||||
});
|
||||
test('output dir not existing should throw an error', () => {
|
||||
expect(() =>
|
||||
fileProcNoImage
|
||||
.gerberToImage(testGerber)
|
||||
.toThrow(new Error('Output folder does not exist.')),
|
||||
);
|
||||
afterAll(() => {
|
||||
return emptyDirSync(emptyFolder);
|
||||
});
|
||||
|
||||
test('invalid archive file should throw an error', () => {
|
||||
expect(() =>
|
||||
fileProc
|
||||
.gerberToImage('invalid.zip')
|
||||
.toThrow(new Error('Archive does not exist.')),
|
||||
);
|
||||
});
|
||||
test('an archive with incomplete set of layers should throw an error', () => {
|
||||
expect(() => fileProc.gerberToImage(incompleteGerber).toThrow(Error));
|
||||
expect(() => fileProc.gerberToImage('invalid.zip')).toThrow();
|
||||
});
|
||||
// test('an archive with incomplete set of layers should throw an error', () => {
|
||||
// expect(() => fileProc.gerberToImage(incompleteGerber)).toThrow();
|
||||
// });
|
||||
test('gerber archive should resolve promise and return a filename of an image', () => {
|
||||
expect.assertions(1);
|
||||
return expect(fileProc.gerberToImage(testGerber)).resolves.toEqual(
|
115
tsconfig.json
Normal file
115
tsconfig.json
Normal file
@ -0,0 +1,115 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||
|
||||
/* Projects */
|
||||
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
|
||||
/* Language and Environment */
|
||||
"target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
|
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||
// "libReplacement": true, /* Enable lib replacement. */
|
||||
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||
|
||||
/* Modules */
|
||||
"module": "commonjs" /* Specify what module code is generated. */,
|
||||
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
|
||||
// "rewriteRelativeImportExtensions": true, /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */
|
||||
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
|
||||
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
|
||||
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
|
||||
// "noUncheckedSideEffectImports": true, /* Check side effect imports. */
|
||||
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
|
||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||
|
||||
/* JavaScript Support */
|
||||
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||
|
||||
/* Emit */
|
||||
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||
"outDir": "./dist" /* Specify an output folder for all emitted files. */,
|
||||
// "removeComments": true, /* Disable emitting comments. */
|
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||
|
||||
/* Interop Constraints */
|
||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
|
||||
// "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */
|
||||
// "erasableSyntaxOnly": true, /* Do not allow runtime constructs that are not part of ECMAScript. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
|
||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
|
||||
|
||||
/* Type Checking */
|
||||
"strict": true /* Enable all strict type-checking options. */,
|
||||
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||
// "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */
|
||||
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
},
|
||||
"include": ["src/**/*", "types/npe_gerber.d.ts"],
|
||||
"exclude": ["node_modules", "dist/**/*", "test/**/*"]
|
||||
}
|
23
types/npe_gerber.d.ts
vendored
Normal file
23
types/npe_gerber.d.ts
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
interface ImageConfig {
|
||||
resizeWidth: number;
|
||||
density: number;
|
||||
compLevel: number;
|
||||
}
|
||||
|
||||
interface FolderConfig {
|
||||
tmpDir: string;
|
||||
imgDir: string;
|
||||
}
|
||||
|
||||
interface ZipExtractor {
|
||||
extractArchive(fileName: string, tmpDir: string): number;
|
||||
}
|
||||
|
||||
interface Layers {
|
||||
filename: string;
|
||||
gerber: ReadStream;
|
||||
}
|
||||
|
||||
interface LayerGenerator {
|
||||
getLayers(dir: string, layerNames: string[]): Promise<Layers[]>;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user