Advanced Archive File Manipulation In SuiteScript

Author: Luke Pirtle, Director of IP Development

Overview:

This guide is technical in nature and details how to successfully unzip any archive file or folder inside of NetSuite and store the results in the file cabinet without the use of a middleware. While the compress module exists it is not sufficient for heavy lifting and this solution can resolve many use cases not supported by the official module.

Background:

When it comes to unzipping files most languages such as C++, Python, .NET, it’s mostly an afterthought. Even JavaScript can unzip files as numerous libraries exist but it’s not always straight forward to use most Node.js libraries inside the NetSuite ecosystem. The challenge is getting a library that works in the SuiteScript AMD module loader with minimal polyfill functions that doesn’t use ECMA script exceeding your current SuiteScript standard.

Due to these limitations the best solution is to add a middleware or redesign things completely. However, I recently ran into an unavoidable use case and neither was a satisfactory path. After extensive research and testing I’ve found a lightweight no hassle solution to work with archived files / folders. At some point NetSuite might release updates to the compress rendering this unnecessary but until then this should address these issues

Solution:

he solution is simply the impressive JSZip library but getting the library to work in NetSuite is not simple
https://stuk.github.io/jszip/

Version:

This is the key and most important piece. The current version of the JSZip library is 3.X and utilizes some impressive asynchronous functions and patterns for better performance. Despite trying every syntax and asynchronous method this new version is incompatible with the ECMAScript standard and NetSuite configuration in SuiteScript. Every permutation will eventually break right before reading the file contents.

However, the 2.7 version works and doesn’t include the asynchronous components. This stripped down version has all the capabilities without the unnecessary advanced asynchronous features and is fully compatible with the 2.1 SuiteScript ECMA script configuration.

Loading the module in a Suitelet:

NetSuite uses an AMD (Asynchronous Module Definition) loader. This is the biggest hindrance in getting Node.js modules to work. Fortunately, JSZip has a minified singular file that can be dropped into NetSuite. Below I detail how I load the module but I’ve also included the author’s notes on how it’s loaded in the system to give a full picture of what is happening.

Working with 2.7

I have the minified JSZip file in my project library folder and below demonstrate how to load it.

This allows the creation of new JSZip Instances to work with archives. With this configuration, the issue is much more straight forward now as we only have to write code. Sample code is detailed further down and documentation on the older version can be seen in their upgrade guide linked below
https://stuk.github.io/jszip/documentation/upgrade_guide.html

Sample Code

This solution below loads an archive file which is a folder with ~numerous files of differing types and is an excellent example. The code reads the archive file using the N/file module, then it iterates through each file and then creates a corresponding file in the file cabinet.

In my code snippet above you’ll notice the references to base64, this is because NetSuite uses base64 for essentially everything non-plaintext and files are no exception. Fortunately JSZip can handle this. Grab the contents of your archive file via N/file use the “getContents()” method to return the base64 content of the archive file. Drop that into the JSZip initialization (line 64) with the base64 argument set and now you have a valid JSZip Archive instance object loaded via SuiteScript. To create files you’ll simply need to iterate through the files subobject and then run the JSZip “file” method to read the contents. When creating files in NetSuite you’ll need to once again consider base64. Text is needed for text files and Base64 for anything else. To determine what is needed, look at the file extension. Once you have encoded your file simply save it and you now have a file committed to the file cabinet. Below I have a text reference for copying and utilizing in your own solutions.

Plaintext Reference for (Ctrl + C):

let extensionMapping = {
    'XML': file.Type.XMLDOC,
    'html': file.Type.HTMLDOC,
    'PDF': file.Type.PDF,
    'JPG': file.Type.JPGIMAGE,
    'BMP': file.Type.BMPIMAGE,
    'xlsx': file.Type.EXCEL,
    'txt': file.Type.PLAINTEXT,
    'json': file.Type.JSON
};

let plainTextTypes = [
    file.Type.CSV,
    file.Type.XMLDOC,
    file.Type.HTMLDOC,
    file.Type.PLAINTEXT,
    file.Type.JSON
];

let archiveFile = 57163;
let archiveFolder = 56852;
let xactimateTestArchiveTargetFolder = 2510;

let zipFileObj = file.load(archiveFolder);
let bin = zipFileObj.getContents();

var ZipInstance = new JSZip(bin , {base64: true});
for (let fullPathFileName in ZipInstance.files){
    if (fullPathFileName.indexOf('.' !== -1)){
        let fileName = fullPathFileName.split('/').pop();
        let fileExt = fileName.substring(fileName.lastIndexOf('.') + 1).toUpperCase();
        let netsuiteExt = extensionMapping[fileExt];
        log.debug({title:`File to ${ netsuiteExt ? 'Create' : 'Ignore' }`, details:`FileName: ${fullPathFileName}, fileType:${fileExt}, NetSuiteType: ${netsuiteExt}`});
        if (netsuiteExt){
            let data =ZipInstance.file(fullPathFileName).asBinary();
            if (plainTextTypes.includes(netsuiteExt) === false){
                data = JSZip.base64.encode(data);
            }

            let zippedFile = file.create({
                name:fileName,
                folder:xactimateTestArchiveTargetFolder,
                contents:data,
                fileType:netsuiteExt
            });
            zippedFile.save();
        }

    }

}

Fill out this form and one of our team members will be in touch shortly!