mirror of
https://github.com/marcogll/passkit-generator.git
synced 2026-03-15 19:25:23 +00:00
Added _checkReqs method to check model requirements (pass.json, name, non-empty);
Created different type of file lists and renamed the existing ones; Renamed 'removeHiddenFiles' in 'removeHidden'; Changed model name and directory representation as Object; Moved 'generate' main conditions in _checkReqs; Changed L10N representation (list, extractors) as Object; Changed completely the way in which bundle files (except pass.json) get retrieved and managed;
This commit is contained in:
195
index.js
195
index.js
@@ -8,24 +8,32 @@ const settingSchema = require("./schema.js");
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
Apply a filter to arg0 to remove hidden files names (starting with dot)
|
Apply a filter to arg0 to remove hidden files names (starting with dot)
|
||||||
@function removeHiddenFiles
|
@function removeHidden
|
||||||
@params {[String]} from - list of file names
|
@params {[String]} from - list of file names
|
||||||
@return {[String]}
|
@return {[String]}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function removeHiddenFiles(from) {
|
function removeHidden(from) {
|
||||||
return from.filter(e => e.charAt(0) !== ".");
|
return from.filter(e => e.charAt(0) !== ".");
|
||||||
}
|
}
|
||||||
|
|
||||||
class Pass {
|
class Pass {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
|
this.passTypes = ["boardingPass", "eventTicket", "coupon", "generic", "storeCard"];
|
||||||
this.overrides = options.overrides || {};
|
this.overrides = options.overrides || {};
|
||||||
this.Certificates = {};
|
this.Certificates = {};
|
||||||
this.handlers = {};
|
this.handlers = {};
|
||||||
this.modelDirectory = null;
|
this.model = {
|
||||||
|
name: null,
|
||||||
|
dir: null,
|
||||||
|
computed: null,
|
||||||
|
};
|
||||||
|
|
||||||
this._parseSettings(options)
|
this._parseSettings(options)
|
||||||
;// .then(() => console.log("WAT IS", this));
|
.then(() => {
|
||||||
this.passTypes = ["boardingPass", "eventTicket", "coupon", "generic", "storeCard"];
|
this._checkReqs()
|
||||||
|
.catch(e => { throw new Error(e) });
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -37,74 +45,37 @@ class Pass {
|
|||||||
|
|
||||||
generate() {
|
generate() {
|
||||||
return new Promise((success, reject) => {
|
return new Promise((success, reject) => {
|
||||||
if (!this.modelName || typeof this.modelName !== "string") {
|
fs.readdir(this.model.computed, (err, files) => {
|
||||||
return reject({
|
// list without dynamic components like manifest, signature or pass files (will be added later in the flow) and hidden files.
|
||||||
status: false,
|
let noDynList = removeHidden(files).filter(f => !/(manifest|signature|pass)/i.test(f));
|
||||||
error: {
|
|
||||||
message: "A string model name must be provided in order to continue.",
|
|
||||||
ecode: 418
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let computedModelPath = path.resolve(this.modelDirectory, `${this.modelName}.pass`);
|
// list without localization files (they will be added later in the flow)
|
||||||
|
let bundleList = noDynList.filter(f => !f.includes(".lproj"));
|
||||||
|
|
||||||
fs.readdir(computedModelPath, (err, files) => {
|
const L10N = {
|
||||||
if (err) {
|
// localization folders
|
||||||
return reject({
|
list: noDynList.filter(f => f.includes(".lproj"))
|
||||||
status: false,
|
};
|
||||||
error: {
|
|
||||||
message: "Provided model name doesn't match with any model in the folder.",
|
|
||||||
ecode: 418
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Removing hidden files and folders
|
|
||||||
let list = removeHiddenFiles(files).filter(f => !f.includes(".lproj"));
|
|
||||||
|
|
||||||
if (!list.length) {
|
|
||||||
return reject({
|
|
||||||
status: false,
|
|
||||||
error: {
|
|
||||||
message: "Model provided matched but unitialized. Refer to https://apple.co/2IhJr0Q to fill the model correctly.",
|
|
||||||
ecode: 418
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!list.includes("pass.json")) {
|
|
||||||
return reject({
|
|
||||||
status: false,
|
|
||||||
error: {
|
|
||||||
message: "I'm a teapot. How am I supposed to serve you pass without pass.json in the chosen model as tea without water?",
|
|
||||||
ecode: 418
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Getting only folders
|
|
||||||
let folderList = files.filter(f => f.includes(".lproj"));
|
|
||||||
|
|
||||||
// I may have (and I rathered) used async.concat to achieve this but it returns a list of filenames ordered by folder.
|
// I may have (and I rathered) used async.concat to achieve this but it returns a list of filenames ordered by folder.
|
||||||
// The problem rises when I have to understand which is the first file of a folder which is not the first one.
|
// The problem rises when I have to understand which is the first file of a folder which is not the first one.
|
||||||
// By doing this way, I get an Array containing an array of filenames for each folder.
|
// By doing this way, I get an Array containing an array of filenames for each folder.
|
||||||
|
|
||||||
let folderExtractors = folderList.map(f => function(callback) {
|
L10N.extractors = L10N.list.map(f => ((callback) => {
|
||||||
let l10nPath = path.join(computedModelPath, f);
|
let l10nPath = path.join(this.model.computed, f);
|
||||||
|
|
||||||
fs.readdir(l10nPath, function(err, list) {
|
fs.readdir(l10nPath, function(err, list) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return callback(err, null);
|
return callback(err, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
let filteredFiles = removeHiddenFiles(list);
|
let filteredFiles = removeHidden(list);
|
||||||
return callback(null, filteredFiles);
|
return callback(null, filteredFiles);
|
||||||
});
|
});
|
||||||
});
|
}));
|
||||||
|
|
||||||
async.parallel(folderExtractors, (err, listByFolder) => {
|
async.parallel(L10N.extractors, (err, listByFolder) => {
|
||||||
listByFolder.forEach((folder, index) => list.push(...folder.map(f => path.join(folderList[index], f))));
|
listByFolder.forEach((folder, index) => bundleList.push(...folder.map(f => path.join(L10N.list[index], f))));
|
||||||
|
|
||||||
let manifest = {};
|
let manifest = {};
|
||||||
let archive = archiver("zip");
|
let archive = archiver("zip");
|
||||||
@@ -113,13 +84,13 @@ class Pass {
|
|||||||
// Otherwise would had to put everything in editPassStructure's Promise .then().
|
// Otherwise would had to put everything in editPassStructure's Promise .then().
|
||||||
async.parallel([
|
async.parallel([
|
||||||
passCallback => {
|
passCallback => {
|
||||||
fs.readFile(path.resolve(computedModelPath, "pass.json"), {}, (err, passStructBuffer) => {
|
fs.readFile(path.resolve(this.model.computed, "pass.json"), {}, (err, passStructBuffer) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
// Flow should never enter in there since pass.json existence-check is already done above.
|
// Flow should never enter in there since pass.json existence-check is already done above.
|
||||||
return passCallback({
|
return passCallback({
|
||||||
status: false,
|
status: false,
|
||||||
error: {
|
error: {
|
||||||
message: `Unable to read pass.json file @ ${computedModelPath}`
|
message: `Unable to read pass.json file @ ${this.model.computed}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -145,7 +116,7 @@ class Pass {
|
|||||||
return passCallback({
|
return passCallback({
|
||||||
status: false,
|
status: false,
|
||||||
error: {
|
error: {
|
||||||
message: `Unable to read pass.json as buffer @ ${computedModelPath}. Unable to continue.\n${err}`,
|
message: `Unable to read pass.json as buffer @ ${this.model.computed}. Unable to continue.\n${err}`,
|
||||||
ecode: 418
|
ecode: 418
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -154,29 +125,20 @@ class Pass {
|
|||||||
},
|
},
|
||||||
|
|
||||||
bundleCallback => {
|
bundleCallback => {
|
||||||
async.each(list, (file, callback) => {
|
let pathList = bundleList.map(f => path.resolve(this.model.computed, f));
|
||||||
if (/(manifest|signature|pass)/ig.test(file)) {
|
|
||||||
// skipping files
|
|
||||||
return callback();
|
|
||||||
}
|
|
||||||
|
|
||||||
// adding the files to the zip - i'm not using .directory method because it adds also hidden files like .DS_Store on macOS
|
async.concat(pathList, fs.readFile, (err, modelBuffers) => {
|
||||||
archive.file(path.resolve(this.modelDirectory, `${this.modelName}.pass`, file), { name: file });
|
let modelFiles = Object.assign({}, ...modelBuffers.map((buf, index) => ({ [bundleList[index]]: buf })));
|
||||||
|
|
||||||
|
async.eachOf(modelFiles, (fileBuffer, bufferKey, callback) => {
|
||||||
let hashFlow = forge.md.sha1.create();
|
let hashFlow = forge.md.sha1.create();
|
||||||
|
hashFlow.update(fileBuffer.toString("binary"));
|
||||||
|
|
||||||
|
manifest[bufferKey] = hashFlow.digest().toHex().trim();
|
||||||
|
archive.file(path.resolve(this.model.computed, bufferKey), { name: bufferKey });
|
||||||
|
|
||||||
fs.createReadStream(path.resolve(this.modelDirectory, `${this.modelName}.pass`, file))
|
|
||||||
.on("data", function(data) {
|
|
||||||
hashFlow.update(data.toString("binary"));
|
|
||||||
})
|
|
||||||
.on("error", function(e) {
|
|
||||||
return callback(e);
|
|
||||||
})
|
|
||||||
.on("end", function() {
|
|
||||||
manifest[file] = hashFlow.digest().toHex().trim();
|
|
||||||
return callback();
|
return callback();
|
||||||
});
|
}, function(error) {
|
||||||
}, function end(error) {
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return reject({
|
return reject({
|
||||||
status: false,
|
status: false,
|
||||||
@@ -189,6 +151,7 @@ class Pass {
|
|||||||
|
|
||||||
return bundleCallback(null);
|
return bundleCallback(null);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
], (error) => {
|
], (error) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
@@ -214,6 +177,54 @@ class Pass {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Check if the requirements are satisfied
|
||||||
|
|
||||||
|
@method _checkReqs
|
||||||
|
@returns {Promise} - success if requirements are satisfied, reject otherwise
|
||||||
|
*/
|
||||||
|
|
||||||
|
_checkReqs() {
|
||||||
|
return new Promise((success, reject) => {
|
||||||
|
fs.readdir(this.model.computed, function(err, files) {
|
||||||
|
if (err) {
|
||||||
|
return reject({
|
||||||
|
status: false,
|
||||||
|
error: {
|
||||||
|
message: "Provided model name doesn't match with any model in the folder.",
|
||||||
|
ecode: 418
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Removing hidden files and folders
|
||||||
|
let list = removeHidden(files);
|
||||||
|
|
||||||
|
if (!list.length) {
|
||||||
|
return reject({
|
||||||
|
status: false,
|
||||||
|
error: {
|
||||||
|
message: "Model provided matched but unitialized. Refer to https://apple.co/2IhJr0Q to fill the model correctly.",
|
||||||
|
ecode: 418
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!list.includes("pass.json")) {
|
||||||
|
return reject({
|
||||||
|
status: false,
|
||||||
|
error: {
|
||||||
|
message: "I'm a teapot. How am I supposed to serve you pass without pass.json in the chosen model as tea without water?",
|
||||||
|
ecode: 418
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return success();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Checks if pass model type is one of the supported ones
|
Checks if pass model type is one of the supported ones
|
||||||
|
|
||||||
@@ -400,9 +411,21 @@ class Pass {
|
|||||||
throw new Error("The options passed to Pass constructor does not meet the requirements. Refer to the documentation to compile them correctly.");
|
throw new Error("The options passed to Pass constructor does not meet the requirements. Refer to the documentation to compile them correctly.");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.modelDirectory = path.resolve(__dirname, options.modelDir);
|
if (!options.modelName || typeof options.modelName !== "string") {
|
||||||
|
return reject({
|
||||||
|
status: false,
|
||||||
|
error: {
|
||||||
|
message: "A string model name must be provided in order to continue.",
|
||||||
|
ecode: 418
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.model.dir = path.resolve(__dirname, options.modelDir);
|
||||||
|
this.model.name = options.modelName;
|
||||||
|
this.model.computed = path.resolve(this.model.dir, `${this.model.name}.pass`);
|
||||||
|
|
||||||
this.Certificates.dir = options.certificates.dir;
|
this.Certificates.dir = options.certificates.dir;
|
||||||
this.modelName = options.modelName;
|
|
||||||
|
|
||||||
let certPaths = Object.keys(options.certificates)
|
let certPaths = Object.keys(options.certificates)
|
||||||
.filter(v => v !== "dir")
|
.filter(v => v !== "dir")
|
||||||
@@ -414,7 +437,7 @@ class Pass {
|
|||||||
);
|
);
|
||||||
|
|
||||||
async.parallel([
|
async.parallel([
|
||||||
(function __certificatesParser(callback) {
|
__certsParseCallback => {
|
||||||
async.concat(certPaths, fs.readFile, (err, contents) => {
|
async.concat(certPaths, fs.readFile, (err, contents) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
return reject(err);
|
return reject(err);
|
||||||
@@ -429,14 +452,14 @@ class Pass {
|
|||||||
this.Certificates[pem.key] = pem.value;
|
this.Certificates[pem.key] = pem.value;
|
||||||
});
|
});
|
||||||
|
|
||||||
return callback();
|
return __certsParseCallback();
|
||||||
});
|
});
|
||||||
}).bind(this),
|
},
|
||||||
|
|
||||||
(function __handlersAssign(callback) {
|
__handlersAssignCallback => {
|
||||||
this.handlers = options.handlers || {};
|
this.handlers = options.handlers || {};
|
||||||
return callback();
|
return __handlersAssignCallback();
|
||||||
}).bind(this)
|
}
|
||||||
], success);
|
], success);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user