mirror of
https://github.com/marcogll/passkit-generator.git
synced 2026-03-16 00:25:30 +00:00
Moved to async/await approach for generate()
This commit is contained in:
317
src/pass.js
317
src/pass.js
@@ -56,190 +56,182 @@ class Pass {
|
|||||||
* @return {Promise<Stream>} A Promise containing the stream of the generated pass.
|
* @return {Promise<Stream>} A Promise containing the stream of the generated pass.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
generate() {
|
async generate() {
|
||||||
let archive = archiver("zip");
|
try {
|
||||||
|
// Reading the model
|
||||||
|
const modelFilesList = await readdir(this.model);
|
||||||
|
|
||||||
return readCertificates(this.Certificates)
|
/**
|
||||||
.then((certs) => Object.assign(this.Certificates, certs))
|
* Getting the buffers for remote files
|
||||||
.then(() => readdir(this.model))
|
*/
|
||||||
.catch((err) => {
|
|
||||||
// May have not used this catch but ENOENT error is not enough self-explanatory
|
const buffersPromise = this._remoteResources.reduce(async (acc, current) => {
|
||||||
// in the case of internal usage ()
|
try {
|
||||||
if (err.code && err.code === "ENOENT") {
|
const response = await got(current[0], { encoding: null });
|
||||||
throw new Error(formatMessage("MODEL_NOT_FOUND", this.model));
|
loadDebug(formatMessage("LOAD_MIME", response.headers["content-type"]));
|
||||||
|
|
||||||
|
if (!Buffer.isBuffer(response.body)) {
|
||||||
|
throw "LOADED_RESOURCE_NOT_A_BUFFER";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.headers["content-type"].includes("image/")) {
|
||||||
|
throw "LOADED_RESOURCE_NOT_A_PICTURE";
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...acc, response.body];
|
||||||
|
} catch (err) {
|
||||||
|
loadDebug(formatMessage("LOAD_NORES", current[1], err));
|
||||||
|
return acc;
|
||||||
}
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
throw new Error(err);
|
const remoteFilesList = buffersPromise.length ? this._remoteResources.map(r => r[1]): [];
|
||||||
})
|
|
||||||
.then(filesList => {
|
|
||||||
if (!this._remoteResources.length) {
|
|
||||||
return [filesList, [], []];
|
|
||||||
}
|
|
||||||
|
|
||||||
let buffersPromise = this._remoteResources.map((r) => {
|
// list without dynamic components like manifest, signature or pass files (will be added later in the flow) and hidden files.
|
||||||
return got(r[0], { encoding: null })
|
const noDynList = removeHidden(modelFilesList).filter(f => !/(manifest|signature|pass)/i.test(f));
|
||||||
.then(response => {
|
const hasAssets = noDynList.length || remoteFilesList.length;
|
||||||
loadDebug(formatMessage("LOAD_MIME", response.headers["content-type"]));
|
|
||||||
|
|
||||||
if (!Buffer.isBuffer(response.body)) {
|
if (!hasAssets || ![...noDynList, ...remoteFilesList].some(f => f.toLowerCase().includes("icon"))) {
|
||||||
throw "LOADED_RESOURCE_NOT_A_BUFFER";
|
let eMessage = formatMessage("MODEL_UNINITIALIZED", path.parse(this.model).name);
|
||||||
}
|
throw new Error(eMessage);
|
||||||
|
}
|
||||||
|
|
||||||
if (!response.headers["content-type"].includes("image/")) {
|
// list without localization files (they will be added later in the flow)
|
||||||
throw "LOADED_RESOURCE_NOT_A_PICTURE";
|
const bundle = noDynList.filter(f => !f.includes(".lproj"));
|
||||||
}
|
|
||||||
|
|
||||||
return response.body;
|
// Localization folders only
|
||||||
})
|
const L10N = noDynList.filter(f => f.includes(".lproj") && Object.keys(this.l10n).includes(path.parse(f).name));
|
||||||
.catch(e => {
|
|
||||||
loadDebug(formatMessage("LOAD_NORES", r[1], e));
|
|
||||||
// here we are adding undefined values, that will be removed later.
|
|
||||||
return undefined;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// forwarding model files list, remote files list and remote buffers.
|
/**
|
||||||
return [
|
* Reads pass.json file and apply patches on it
|
||||||
filesList,
|
* @function
|
||||||
buffersPromise.length ? this._remoteResources.map(r => r[1]) : [],
|
* @name passExtractor
|
||||||
buffersPromise
|
* @return {Promise<Buffer>} The patched pass.json buffer
|
||||||
];
|
*/
|
||||||
})
|
|
||||||
.then(([modelFileList, remoteFilesList, remoteBuffers]) => {
|
|
||||||
// list without dynamic components like manifest, signature or pass files (will be added later in the flow) and hidden files.
|
|
||||||
let noDynList = removeHidden(modelFileList).filter(f => !/(manifest|signature|pass)/i.test(f));
|
|
||||||
const hasAssets = noDynList.length || remoteFilesList.length;
|
|
||||||
|
|
||||||
if (!hasAssets || ![...noDynList, ...remoteFilesList].some(f => f.toLowerCase().includes("icon"))) {
|
const passExtractor = async () => {
|
||||||
let eMessage = formatMessage("MODEL_UNINITIALIZED", path.parse(this.model).name);
|
const passStructBuffer = await readFile(path.resolve(this.model, "pass.json"))
|
||||||
|
|
||||||
|
if (!this._validateType(passStructBuffer)) {
|
||||||
|
const eMessage = formatMessage("PASSFILE_VALIDATION_FAILED");
|
||||||
throw new Error(eMessage);
|
throw new Error(eMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
// list without localization files (they will be added later in the flow)
|
bundle.push("pass.json");
|
||||||
let bundle = noDynList.filter(f => !f.includes(".lproj"));
|
|
||||||
|
|
||||||
// Localization folders only
|
return this._patch(passStructBuffer);
|
||||||
const L10N = noDynList.filter(f => f.includes(".lproj") && Object.keys(this.l10n).includes(path.parse(f).name));
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Reading all the localization selected folders and removing hidden files (the ones that starts with ".")
|
||||||
|
* from the list.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const listByFolder = await Promise.all(
|
||||||
|
L10N.map(async f =>
|
||||||
|
removeHidden(await readdir(
|
||||||
|
path.join(this.model, f)
|
||||||
|
))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Pushing into the bundle the composed paths for localization files
|
||||||
|
|
||||||
|
listByFolder.forEach((folder, index) =>
|
||||||
|
bundle.push(
|
||||||
|
...folder.map(f => path.join(L10N[index], f))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Getting all bundle file buffers, pass.json included, and appending path */
|
||||||
|
|
||||||
|
if (remoteFilesList.length) {
|
||||||
|
// Removing files in bundle that also exist in remoteFilesList
|
||||||
|
// I'm giving priority to downloaded files
|
||||||
|
bundle = bundle.filter(file => !remoteFilesList.includes(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
const bundleBuffers = bundle.map(f => readFile(path.resolve(this.model, f)));
|
||||||
|
const passBuffer = passExtractor();
|
||||||
|
|
||||||
|
const buffers = await Promise.all([...bundleBuffers, passBuffer, ...buffersPromise]);
|
||||||
|
|
||||||
|
Object.keys(this.l10n).forEach(l => {
|
||||||
|
const strings = generateStringFile(this.l10n[l]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads pass.json file and apply patches on it
|
* if .string file buffer is empty, no translations were added
|
||||||
* @function
|
* but still wanted to include the language
|
||||||
* @name passExtractor
|
|
||||||
* @return {Promise<Buffer>} The patched pass.json buffer
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
let passExtractor = (() => {
|
if (!strings.length) {
|
||||||
return readFile(path.resolve(this.model, "pass.json"))
|
return;
|
||||||
.then(passStructBuffer => {
|
}
|
||||||
if (!this._validateType(passStructBuffer)) {
|
|
||||||
let eMessage = formatMessage("PASSFILE_VALIDATION_FAILED");
|
|
||||||
throw new Error(eMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
bundle.push("pass.json");
|
/**
|
||||||
|
* if there's already a buffer of the same folder and called
|
||||||
return this._patch(passStructBuffer);
|
* `pass.strings`, we'll merge the two buffers. We'll create
|
||||||
});
|
* it otherwise.
|
||||||
});
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Reading all the localization selected folders and removing hidden files (the ones that starts with ".")
|
|
||||||
* from the list. Returning a Promise containing all those files
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
return Promise.all(L10N.map(f => readdir(path.join(this.model, f)).then(removeHidden)))
|
const stringFilePath = path.join(`${l}.lproj`, "pass.strings");
|
||||||
.then(listByFolder => {
|
const stringFileIndex = bundle.findIndex(file => file === stringFilePath);
|
||||||
/* Each localization file name is joined with its own path and pushed to the bundle files array. */
|
|
||||||
|
|
||||||
listByFolder.forEach((folder, index) => bundle.push(...folder.map(f => path.join(L10N[index], f))));
|
if (stringFileIndex > -1) {
|
||||||
|
buffers[stringFileIndex] = Buffer.concat([
|
||||||
/* Getting all bundle file buffers, pass.json included, and appending path */
|
buffers[stringFileIndex],
|
||||||
|
strings
|
||||||
if (remoteFilesList.length) {
|
]);
|
||||||
// Removing files in bundle that also exist in remoteFilesList
|
} else {
|
||||||
// I'm giving priority to downloaded files
|
buffers.push(strings);
|
||||||
bundle = bundle.filter(file => !remoteFilesList.includes(file));
|
bundle.push(stringFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
let bundleBuffers = bundle.map(f => readFile(path.resolve(this.model, f)));
|
|
||||||
let passBuffer = passExtractor();
|
|
||||||
|
|
||||||
// Resolving all the buffers promises
|
|
||||||
return Promise.all([...bundleBuffers, passBuffer, ...remoteBuffers])
|
|
||||||
.then(buffers => {
|
|
||||||
Object.keys(this.l10n).forEach(l => {
|
|
||||||
const strings = generateStringFile(this.l10n[l]);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* if .string file buffer is empty, no translations were added
|
|
||||||
* but still wanted to include the language
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (!strings.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* if there's already a buffer of the same folder and called
|
|
||||||
* `pass.strings`, we'll merge the two buffers. We'll create
|
|
||||||
* it otherwise.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const stringFilePath = path.join(`${l}.lproj`, "pass.strings");
|
|
||||||
const stringFileIndex = bundle.findIndex(file => file === stringFilePath);
|
|
||||||
|
|
||||||
if (stringFileIndex > -1) {
|
|
||||||
buffers[stringFileIndex] = Buffer.concat([
|
|
||||||
buffers[stringFileIndex],
|
|
||||||
Buffer.from(EOL),
|
|
||||||
strings
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
buffers.push(strings);
|
|
||||||
bundle.push(stringFilePath);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return [
|
|
||||||
// removing undefined values
|
|
||||||
buffers.filter(b => !!b),
|
|
||||||
[...bundle, ...remoteFilesList]
|
|
||||||
];
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.then(([buffers, bundle]) => {
|
|
||||||
/*
|
|
||||||
* Parsing the buffers, pushing them into the archive
|
|
||||||
* and returning the compiled manifest
|
|
||||||
*/
|
|
||||||
|
|
||||||
return buffers.reduce((acc, current, index) => {
|
|
||||||
let filename = bundle[index];
|
|
||||||
let hashFlow = forge.md.sha1.create();
|
|
||||||
|
|
||||||
hashFlow.update(current.toString("binary"));
|
|
||||||
archive.append(current, { name: filename });
|
|
||||||
|
|
||||||
acc[filename] = hashFlow.digest().toHex();
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
})
|
|
||||||
.then((manifest) => {
|
|
||||||
let signatureBuffer = this._sign(manifest);
|
|
||||||
|
|
||||||
archive.append(signatureBuffer, { name: "signature" });
|
|
||||||
archive.append(JSON.stringify(manifest), { name: "manifest.json" });
|
|
||||||
|
|
||||||
let passStream = new stream.PassThrough();
|
|
||||||
|
|
||||||
archive.pipe(passStream);
|
|
||||||
|
|
||||||
FieldsArray.emptyUnique();
|
|
||||||
|
|
||||||
return archive.finalize().then(() => passStream);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Pushing the remote files into the bundle
|
||||||
|
bundle.push(...remoteFilesList);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Parsing the buffers, pushing them into the archive
|
||||||
|
* and returning the compiled manifest
|
||||||
|
*/
|
||||||
|
const archive = archiver("zip");
|
||||||
|
const manifest = buffers.reduce((acc, current, index) => {
|
||||||
|
let filename = bundle[index];
|
||||||
|
let hashFlow = forge.md.sha1.create();
|
||||||
|
|
||||||
|
hashFlow.update(current.toString("binary"));
|
||||||
|
archive.append(current, { name: filename });
|
||||||
|
|
||||||
|
acc[filename] = hashFlow.digest().toHex();
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
// Reading the certificates,
|
||||||
|
// signing the manifest, appending signature an manifest to the archive
|
||||||
|
// and returning the generated pass stream.
|
||||||
|
|
||||||
|
Object.assign(this.Certificates, await readCertificates(this.Certificates));
|
||||||
|
|
||||||
|
const signatureBuffer = this._sign(manifest);
|
||||||
|
|
||||||
|
archive.append(signatureBuffer, { name: "signature" });
|
||||||
|
archive.append(JSON.stringify(manifest), { name: "manifest.json" });
|
||||||
|
|
||||||
|
const passStream = new stream.PassThrough();
|
||||||
|
|
||||||
|
archive.pipe(passStream);
|
||||||
|
|
||||||
|
FieldsArray.emptyUnique();
|
||||||
|
|
||||||
|
return archive.finalize().then(() => passStream);
|
||||||
|
} catch (err) {
|
||||||
|
if (err.code && err.code === "ENOENT") {
|
||||||
|
// No model available at this path - renaming the error
|
||||||
|
throw new Error(formatMessage("MODEL_NOT_FOUND", this.model));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -763,7 +755,6 @@ function readCertificates(certificates) {
|
|||||||
);
|
);
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
if (!err.path) {
|
if (!err.path) {
|
||||||
// Catching error from '.then()';
|
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user