diff --git a/local-cli/cli.json b/local-cli/cli.json index d7c7df4..de2e1be 100644 --- a/local-cli/cli.json +++ b/local-cli/cli.json @@ -61,6 +61,15 @@ "hasValue": true } } + }, + "diffWithApk": { + "description": "Create diff patch from a Android package(.apk)", + "options": { + "output": { + "default": "build/output/diff-${time}.apk-patch", + "hasValue": true + } + } } }, "globalOptions":{ diff --git a/local-cli/src/bundle.js b/local-cli/src/bundle.js index a556e3f..f77c7fb 100644 --- a/local-cli/src/bundle.js +++ b/local-cli/src/bundle.js @@ -11,6 +11,7 @@ import { import * as fs from 'fs'; import {ZipFile} from 'yazl'; import {open as openZipFile} from 'yauzl'; +import {diff} from 'node-bsdiff'; import crypto from 'crypto'; @@ -65,6 +66,105 @@ async function pack(dir, output){ console.log('Bundled saved to: ' + realOutput); } +function readEntire(entry, zipFile) { + const buffers = []; + return new Promise((resolve, reject) => { + zipFile.openReadStream(entry, (err, stream) => { + stream.pipe({ + write(chunk) { + buffers.push(chunk); + }, + end() { + resolve(Buffer.concat(buffers)); + }, + on() { + }, + once() { + }, + emit() { + }, + }) + }); + }) +} + +async function diffWithApk(origin, next, output) { + const originEntries = {}; + const originMap = {}; + + let originSource; + + await enumZipEntries(origin, (entry, zipFile) => { + if (!/\/$/.test(entry.fileName)) { + // isFile + originEntries[entry.fileName] = entry.crc32; + originMap[entry.crc32] = entry.fileName; + + if (entry.fileName === 'assets/index.android.bundle') { + // This is source. + return readEntire(entry, zipFile).then(v=>originSource = v); + } + } + }); + + originSource = originSource || new Buffer(0); + + const copies = {}; + + var zipfile = new ZipFile(); + + const writePromise = new Promise((resolve, reject)=>{ + zipfile.outputStream.on('error', err => {throw err;}); + zipfile.outputStream.pipe(fs.createWriteStream(output)) + .on("close", function() { + resolve(); + }); + }); + + await enumZipEntries(next, (entry, nextZipfile) => { + if (/\/$/.test(entry.fileName)) { + // Directory + zipfile.addEmptyDirectory(entry.fileName); + } else if (entry.fileName === 'index.bundlejs') { + console.log('Found bundle'); + return readEntire(entry, nextZipfile).then( newSource => { + console.log('Begin diff'); + zipfile.addBuffer(diff(originSource, newSource), 'index.bundlejs.patch'); + console.log('End diff'); + }); + } else { + // If same file. + if (originEntries[entry.fileName] === entry.crc32) { + copies[entry.fileName] = 1; + return; + } + // If moved from other place + if (originMap[entry.crc32]){ + copies[entry.fileName] = originMap[entry.crc32]; + return; + } + + return new Promise((resolve, reject)=>{ + nextZipfile.openReadStream(entry, function(err, readStream) { + if (err){ + return reject(err); + } + zipfile.addReadStream(readStream, entry.fileName); + readStream.on('end', () => { + console.log('add finished'); + resolve(); + }); + }); + }) + } + }); + + console.log(copies); + zipfile.addBuffer(new Buffer(JSON.stringify({copies})), '__diff.json'); + zipfile.end(); + await writePromise; +} + function enumZipEntries(zipFn, callback) { return new Promise((resolve, reject) => { openZipFile(zipFn, {lazyEntries:true}, (err, zipfile) => { @@ -205,5 +305,20 @@ export const commands = { zipfile.addBuffer(new Buffer(JSON.stringify({copies})), '__diff.json'); zipfile.end(); await writePromise; - } + }, + + async diffWithApk({args, options}) { + const [origin, next] = args; + const {output} = options; + + const realOutput = output.replace(/\$\{time\}/g, '' + Date.now()); + + if (!origin || !next) { + console.error('pushy diffWithApk '); + process.exit(1); + } + + await diffWithApk(origin, next, realOutput); + + }, }; diff --git a/package.json b/package.json index 65e571a..ceaa677 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "fs-promise": "^0.4.1", "isomorphic-fetch": "^2.2.1", "mkdir-recursive": "^0.2.1", - "node-bsdiff": "^0.1.1", + "node-bsdiff": "^0.1.2", "read": "^1.0.7", "yauzl": "^2.4.1", "yazl": "^2.3.0"