diff --git a/local-cli/cli.json b/local-cli/cli.json index 32a04d6..1c9c03a 100644 --- a/local-cli/cli.json +++ b/local-cli/cli.json @@ -25,33 +25,45 @@ "description": "Bundle javascript and copy assets." }, "bundle": { - "description": "Bundle javascript code only." + "description": "Bundle javascript code only.", + "options": { + "dev": { + "default": false + }, + "platform": { + "hasValue": true + }, + "entryFile": { + "default": "index.${platform}.js", + "hasValue": true + }, + "intermediaDir": { + "default": "build/intermedia/${platform}", + "hasValue": true + }, + "output": { + "default": "build/output/${platform}.${hash}.ppk", + "hasValue": true + }, + "verbose": { + + } + } }, "release": { "description": "Push builded file to server." + }, + "diff": { + "description": "Create diff patch", + "options": { + "output": { + "default": "build/output/diff", + "hasValue": true + } + } } }, "globalOptions":{ - "dev": { - "default": false - }, - "platform": { - "hasValue": true - }, - "entryFile": { - "default": "index.${platform}.js", - "hasValue": true - }, - "intermediaDir": { - "default": "build/intermedia/${platform}", - "hasValue": true - }, - "output": { - "default": "build/output/${platform}.${hash}.ppk", - "hasValue": true - }, - "verbose": { - } } } diff --git a/local-cli/src/bundle.js b/local-cli/src/bundle.js index 22bf3b0..adda502 100644 --- a/local-cli/src/bundle.js +++ b/local-cli/src/bundle.js @@ -10,6 +10,7 @@ import { } from './utils'; import * as fs from 'fs'; import {ZipFile} from 'yazl'; +import {open as openZipFile} from 'yauzl'; import crypto from 'crypto'; @@ -54,8 +55,9 @@ async function calcMd5ForDirectory(dir) { } async function pack(dir, output){ - await mkdir(path.dirname(output)) const hash = await calcMd5ForDirectory(dir); + const realOutput = output.replace(/\$\{hash\}/g, hash); + await mkdir(path.dirname(realOutput)) await new Promise((resolve, reject) => { var zipfile = new ZipFile(); @@ -83,7 +85,7 @@ async function pack(dir, output){ addDirectory(dir, ''); zipfile.outputStream.on('error', err => reject(err)); - zipfile.outputStream.pipe(fs.createWriteStream(output.replace(/\$\{hash\}/g, hash))) + zipfile.outputStream.pipe(fs.createWriteStream(realOutput)) .on("close", function() { resolve(); }); @@ -92,6 +94,27 @@ async function pack(dir, output){ console.log('Bundled with hash: ' + hash); } +function enumZipEntries(zipFn, callback) { + return new Promise((resolve, reject) => { + openZipFile(zipFn, {lazyEntries:true}, (err, zipfile) => { + if (err) { + return reject(err); + } + zipfile.on('end', resolve); + zipfile.on('error', reject); + zipfile.on('entry', entry => { + const result = callback(entry, zipfile); + if (result && typeof(result.then) === 'function') { + result.then(() => zipfile.readEntry()); + } else { + zipfile.readEntry(); + } + }); + zipfile.readEntry(); + }); + }); +} + export const commands = { bundle: async function({options}){ const { @@ -142,5 +165,79 @@ export const commands = { console.log('Packing'); await pack(intermediaDir, output); + }, + + async diff({args, options}) { + const [origin, next] = args; + const {output} = options; + + if (!origin || !next) { + console.error('pushy diff '); + process.exit(1); + } + + //Read crc32 map from origin + const originMap = {}; + const originEntries = {}; + + await enumZipEntries(origin, entry => { + if (!/\/$/.test(entry.fileName)) { + // isFile + originEntries[entry.fileName] = entry.crc32; + originMap[entry.crc32] = entry.fileName; + } + }); + + 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 same file. + if (originEntries[entry.fileName] === entry.crc32) { + console.log('keep:', entry.fileName); + copies[entry.fileName] = 1; + return; + } + // If moved from other place + if (originMap[entry.crc32]){ + console.log('move:' + originMap[entry.crc32] + '->' + entry.fileName); + copies[entry.fileName] = originMap[entry.crc32]; + return; + } + + // TODO: test diff + console.log('add:' + entry.fileName); + + 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(); + }); + }); + }) + } + }); + + zipfile.addBuffer(new Buffer(JSON.stringify({copies})), '__diff.json'); + zipfile.end(); + await writePromise; } }; diff --git a/package.json b/package.json index c215d86..1633d1e 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "description": "react-native hot update", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Error: no test specified\" && exit 1", + "prepublish": "node_modules/.bin/babel local-cli/src --out-dir local-cli/lib" }, "repository": { "type": "git", @@ -31,6 +32,7 @@ "isomorphic-fetch": "^2.2.1", "mkdir-recursive": "^0.2.1", "read": "^1.0.7", + "yauzl": "^2.4.1", "yazl": "^2.3.0" }, "devDependencies": {