├── dist
└── .gitkeep
├── .npmignore
├── .gitignore
├── node-tests
├── helpers
│ └── expect.js
├── fixtures
│ ├── splash.svg
│ ├── icon.svg
│ └── config.xml
│ │ ├── no-platform-nodes.xml
│ │ ├── ios-platform-node.xml
│ │ ├── no-and-ios-platform-node-expected.xml
│ │ ├── android-platform-node.xml
│ │ └── android-platform-node-expected.xml
└── unit
│ ├── utils
│ ├── validate-platforms-test.js
│ ├── make-dir-test.js
│ ├── serialize-splash-test.js
│ ├── write-images-test.js
│ ├── serialize-icon-test.js
│ └── update-config-test.js
│ ├── icon-task-test.js
│ └── splash-task-test.js
├── src
├── utils
│ ├── abort-task.js
│ ├── get-platform-sizes.js
│ ├── validate-platforms.js
│ ├── make-dir.js
│ ├── serialize-splash.js
│ ├── serialize-icon.js
│ ├── write-images.js
│ ├── svg2png.js
│ ├── svg2png-converter.js
│ └── update-config.js
├── platform-splash-sizes.js
├── splash-task.js
├── icon-task.js
└── platform-icon-sizes.js
├── .travis.yml
├── bin
├── splicon-icons.js
└── splicon-splashes.js
├── LICENSE.md
├── package.json
└── README.md
/dist/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | dist/*
3 | !dist/.gitkeep
4 |
--------------------------------------------------------------------------------
/node-tests/helpers/expect.js:
--------------------------------------------------------------------------------
1 | const chai = require('chai');
2 | const chaiAsPromised = require('chai-as-promised');
3 |
4 | chai.use(chaiAsPromised);
5 |
6 | module.exports = chai.expect;
--------------------------------------------------------------------------------
/src/utils/abort-task.js:
--------------------------------------------------------------------------------
1 | /* jshint node:true, esversion: 6 */
2 | 'use strict';
3 |
4 | const chalk = require('chalk');
5 |
6 | module.exports = function(msg) {
7 | console.log(chalk.red(`${msg}. Aborting`));
8 | process.exit();
9 | }
10 |
--------------------------------------------------------------------------------
/src/utils/get-platform-sizes.js:
--------------------------------------------------------------------------------
1 | /* jshint node:true, esversion: 6 */
2 | 'use strict';
3 |
4 | module.exports = function(platformSizes, platforms) {
5 | let _platformSizes = [];
6 |
7 | platforms.forEach((platform) => {
8 | _platformSizes[platform] = platformSizes[platform];
9 | });
10 |
11 | return _platformSizes;
12 | };
13 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "7"
4 | - "6"
5 |
6 | sudo: false
7 |
8 | cache:
9 | directories:
10 | - node_modules
11 |
12 | matrix:
13 | fast_finish: true
14 |
15 | before_install:
16 | - npm config set spin false
17 | - npm install -g bower phantomjs-prebuilt
18 |
19 | install:
20 | - npm install
21 |
22 | script:
23 | - npm test
24 |
--------------------------------------------------------------------------------
/src/utils/validate-platforms.js:
--------------------------------------------------------------------------------
1 | /* jshint node:true, esversion: 6 */
2 | const _filter = require('lodash').filter;
3 | const ALLOWED_PLATFORMS = [
4 | 'all',
5 | 'ios',
6 | 'android',
7 | 'blackberry',
8 | 'windows'
9 | ];
10 |
11 | module.exports = function(platforms) {
12 | const invalidPlatforms = _filter(platforms, (item) => {
13 | return ALLOWED_PLATFORMS.indexOf(item) === -1;
14 | });
15 |
16 | return invalidPlatforms.length === 0;
17 | };
18 |
--------------------------------------------------------------------------------
/src/utils/make-dir.js:
--------------------------------------------------------------------------------
1 | /* jshint node:true, esversion: 6 */
2 | const path = require("path");
3 | const fs = require("fs");
4 |
5 | module.exports = function (base, destPath) {
6 | destPath = destPath.split("/");
7 | destPath.forEach((segment) => {
8 | if (segment) {
9 | base = path.join(base, segment);
10 | try {
11 | fs.mkdirSync(base);
12 | } catch (e) {
13 | if (e.code !== "EEXIST") {
14 | throw e;
15 | }
16 | }
17 | }
18 | });
19 | };
20 |
--------------------------------------------------------------------------------
/src/utils/serialize-splash.js:
--------------------------------------------------------------------------------
1 | /* jshint node:true, esversion: 6 */
2 | 'use strict';
3 |
4 | const _get = require('lodash').get;
5 |
6 | module.exports = function (platform, projectPath, iconData) {
7 | let props = {
8 | id: iconData.id,
9 | src: iconData.path
10 | };
11 |
12 | if (platform === 'ios') {
13 | props.width = iconData.width.toString();
14 | props.height = iconData.height.toString();
15 | } else if (platform === 'android') {
16 | props.density = iconData.id;
17 | }
18 |
19 | return props;
20 | };
21 |
--------------------------------------------------------------------------------
/bin/splicon-icons.js:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env node
2 |
3 | /* jshint node:true, esversion: 6 */
4 | 'use strict';
5 |
6 | //TODO - better opts parsing, allow source/dest
7 |
8 | const IconTask = require('../src/icon-task');
9 | const chalk = require('chalk');
10 |
11 | (function() {
12 | const args = process.argv.slice(2);
13 | console.log(chalk.green(
14 | `Generating cordova icons for ${args}`
15 | ));
16 |
17 | IconTask({platforms: args}).then(() => {
18 | console.log(chalk.green(
19 | "Done!"
20 | ));
21 | });
22 | })();
23 |
--------------------------------------------------------------------------------
/bin/splicon-splashes.js:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env node
2 |
3 | /* jshint node:true, esversion: 6 */
4 | 'use strict';
5 |
6 | //TODO - better opts parsing, allow source/dest
7 |
8 | const SplashTask = require('../src/splash-task');
9 | const chalk = require('chalk');
10 |
11 | (function() {
12 | const args = process.argv.slice(2);
13 | console.log(chalk.green(
14 | `Generating cordova splashes for ${args}`
15 | ));
16 |
17 | SplashTask({platforms: args}).then(() => {
18 | console.log(chalk.green(
19 | "Done!"
20 | ));
21 | });
22 | })();
23 |
--------------------------------------------------------------------------------
/node-tests/fixtures/splash.svg:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/src/utils/serialize-icon.js:
--------------------------------------------------------------------------------
1 | /* jshint node:true, esversion: 6 */
2 | 'use strict';
3 |
4 | const _get = require('lodash').get;
5 |
6 | module.exports = function (platform, projectPath, iconData) {
7 | let props = {
8 | id: iconData.id,
9 | src: iconData.path
10 | };
11 |
12 | if (platform === 'ios') {
13 | props.height = iconData.size.toString();
14 | props.width = iconData.size.toString();
15 | } else if (platform === 'android') {
16 | props.density = iconData.id;
17 | } else if (platform === 'windows') {
18 | const target = _get(iconData, 'attrs.target', iconData.id);
19 | props.target = target;
20 | }
21 |
22 | return props;
23 | };
24 |
25 |
--------------------------------------------------------------------------------
/node-tests/fixtures/icon.svg:
--------------------------------------------------------------------------------
1 |
17 |
--------------------------------------------------------------------------------
/node-tests/unit/utils/validate-platforms-test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const expect = require('../../helpers/expect');
4 |
5 | const ValidatePlatforms = require('../../../src/utils/validate-platforms');
6 |
7 | describe('ValidatePlatforms', () => {
8 | context('when platforms contain valid platforms', () => {
9 | const platforms = ['all', 'ios', 'android', 'blackberry', 'windows'];
10 |
11 | it('returns true', () => {
12 | expect(ValidatePlatforms(platforms)).to.equal(true);
13 | });
14 | });
15 |
16 | context('when platforms contain invalid platforms', () => {
17 | const platforms = ['symbian'];
18 |
19 | it('returns false', () => {
20 | expect(ValidatePlatforms(platforms)).to.equal(false);
21 | });
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/node-tests/unit/utils/make-dir-test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const td = require('testdouble');
4 |
5 | const MakeDir = require('../../../src/utils/make-dir');
6 |
7 | const fs = require('fs');
8 | const path = require('path');
9 |
10 | describe('MakeDir', () => {
11 | context('when base and destPath', () => {
12 | let mkdirSync;
13 |
14 | beforeEach(() => {
15 | mkdirSync = td.replace(fs, 'mkdirSync');
16 | });
17 |
18 | afterEach(() => {
19 | td.reset();
20 | });
21 |
22 | it('makes the directory', () => {
23 | let base = './';
24 | const destPath = 'foo/bar';
25 |
26 | MakeDir(base, destPath);
27 |
28 | // Verify replaced property was invoked.
29 | destPath.split('/').forEach((segment) => {
30 | base = path.join(base, segment);
31 |
32 | td.verify(mkdirSync(base));
33 | });
34 | });
35 | });
36 | });
37 |
--------------------------------------------------------------------------------
/node-tests/fixtures/config.xml/no-platform-nodes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | emberCordovaExample
4 |
5 | A sample Apache Cordova application that responds to the deviceready event.
6 |
7 |
8 | Apache Cordova Team
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/node-tests/fixtures/config.xml/ios-platform-node.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | emberCordovaExample
4 |
5 | A sample Apache Cordova application that responds to the deviceready event.
6 |
7 |
8 | Apache Cordova Team
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016 Isle of Code Inc
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 |
--------------------------------------------------------------------------------
/node-tests/fixtures/config.xml/no-and-ios-platform-node-expected.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | emberCordovaExample
4 |
5 | A sample Apache Cordova application that responds to the deviceready event.
6 |
7 |
8 | Apache Cordova Team
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/node-tests/fixtures/config.xml/android-platform-node.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | emberCordovaExample
4 |
5 | A sample Apache Cordova application that responds to the deviceready event.
6 |
7 |
8 | Apache Cordova Team
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/node-tests/fixtures/config.xml/android-platform-node-expected.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | emberCordovaExample
4 |
5 | A sample Apache Cordova application that responds to the deviceready event.
6 |
7 |
8 | Apache Cordova Team
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/node-tests/unit/utils/serialize-splash-test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const expect = require('../../helpers/expect');
4 |
5 | const SerializeSplash = require('../../../src/utils/serialize-splash');
6 |
7 | describe('SerializeSplash', () => {
8 | context('when platform is iOS', () => {
9 | it('returns an object with id, path, width, and height', () => {
10 | const platform = 'ios';
11 | const projectPath = '';
12 | const iconData = {
13 | path: 'foo',
14 | width: 1536,
15 | height: 2048,
16 | id: '1536-2048'
17 | };
18 |
19 | const props = SerializeSplash(platform, projectPath, iconData);
20 |
21 | expect(props.id).to.equal(iconData.id);
22 | expect(props.src).to.equal(iconData.path);
23 | expect(props.width).to.equal(iconData.width.toString());
24 | expect(props.height).to.equal(iconData.height.toString());
25 | });
26 | });
27 |
28 | context('when platform is Android', () => {
29 | it('returns an object with id, path, and density', () => {
30 | const platform = 'android';
31 | const projectPath = '';
32 | const iconData = {
33 | path: 'foo',
34 | width: 1920,
35 | height: 1280,
36 | id: 'land-xxxhdpi'
37 | };
38 |
39 | const props = SerializeSplash(platform, projectPath, iconData);
40 |
41 | expect(props.id).to.equal(iconData.id);
42 | expect(props.src).to.equal(iconData.path);
43 | expect(props.density).to.equal(iconData.id);
44 | });
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/src/utils/write-images.js:
--------------------------------------------------------------------------------
1 | /* jshint node:true, esversion: 6 */
2 | 'use strict';
3 |
4 | const MakeDir = require('./make-dir');
5 | const svg2png = require('./svg2png');
6 | const fs = require('fs');
7 | const RSVP = require('rsvp');
8 | const normalizePath = require('path').normalize;
9 | const _forOwn = require('lodash').forOwn;
10 | const _union = require('lodash').union;
11 |
12 | module.exports = function (opts) {
13 | return new RSVP.Promise((resolve) => {
14 | let buffer = fs.readFileSync(opts.source);
15 | let rasterizeTasks = [];
16 | let rasterizeQueue = [];
17 |
18 | _forOwn(opts.platformSizes, (icons, platform) => {
19 | MakeDir('./', `${opts.projectPath}/${opts.dest}/${platform}`);
20 |
21 | icons.sizes.map((size) => {
22 | size.path = `${opts.dest}/${platform}/${size.id}.png`;
23 | });
24 |
25 | rasterizeQueue = _union(rasterizeQueue, icons.sizes);
26 | });
27 |
28 | rasterizeQueue.forEach((rasterize) => {
29 | let width, height;
30 |
31 | if (rasterize.size) {
32 | width = rasterize.size;
33 | height = rasterize.size;
34 | } else {
35 | width = rasterize.width;
36 | height = rasterize.height;
37 | }
38 |
39 | let rasterizeTask = svg2png(buffer, { width: width, height: height })
40 | .then((pngBuffer) => {
41 | const writePath = `${opts.projectPath}/${rasterize.path}`;
42 | fs.writeFileSync(normalizePath(writePath), pngBuffer);
43 | })
44 |
45 | rasterizeTasks.push(rasterizeTask);
46 | });
47 |
48 | RSVP.all(rasterizeTasks).then(() => resolve(opts.platformSizes));
49 | });
50 | };
51 |
--------------------------------------------------------------------------------
/src/utils/svg2png.js:
--------------------------------------------------------------------------------
1 | /* jshint node:true, esversion: 6 */
2 | 'use strict';
3 |
4 | const path = require('path');
5 | const childProcess = require('pn/child_process');
6 |
7 | module.exports = (sourceBuffer, resize) => {
8 | const phantomjsCmd = require('phantomjs-prebuilt').path;
9 | const converterFileName = path.resolve(__dirname, './svg2png-converter.js');
10 |
11 | const cp = childProcess.execFile(
12 | phantomjsCmd,
13 | [converterFileName, JSON.stringify(resize)],
14 | { maxBuffer: Infinity }
15 | );
16 |
17 | writeBufferInChunks(cp.stdin, sourceBuffer);
18 |
19 | return cp.promise.then(processResult);
20 | };
21 |
22 | function writeBufferInChunks(writableStream, buffer) {
23 | const asString = buffer.toString('base64');
24 | const chunkSize = 1024;
25 |
26 | writableStream.cork();
27 |
28 | for (let offset = 0; offset < asString.length; offset += chunkSize) {
29 | writableStream.write(asString.substring(offset, offset + chunkSize));
30 | }
31 |
32 | writableStream.end("\n"); // so that the PhantomJS side can use readLine()
33 | }
34 |
35 | function processResult(result) {
36 | const prefix = 'data:image/png;base64,';
37 | const stdout = result.stdout.toString();
38 |
39 | if (stdout.startsWith(prefix)) {
40 | return new Buffer(stdout.substring(prefix.length), 'base64');
41 | }
42 |
43 | if (stdout.length > 0) {
44 | // PhantomJS always outputs to stdout.
45 | throw new Error(stdout.replace(/\r/g, '').trim());
46 | }
47 |
48 | const stderr = result.stderr.toString();
49 |
50 | if (stderr.length > 0) {
51 | // But hey something else might get to stderr.
52 | throw new Error(stderr.replace(/\r/g, '').trim());
53 | }
54 |
55 | throw new Error('No data received from the PhantomJS child process');
56 | }
57 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "splicon",
3 | "version": "0.0.15",
4 | "description": "Generate cordova/splash files from a single svg, and update config.xml",
5 | "homepage": "https://github.com/isleofcode/splicon",
6 | "main": "src/icon-task.js",
7 | "scripts": {
8 | "test": "./node_modules/.bin/mocha 'node-tests/**/*-test.js'",
9 | "compile": "./node_modules/babel-cli/bin/babel.js --presets babel-preset-es2015 --browserify -d dist/ src/",
10 | "prepublish": "npm run compile"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "https://github.com/isleofcode/splicon.git"
15 | },
16 | "bin": {
17 | "splicon-icons": "bin/splicon-icons.js",
18 | "splicon-splashes": "bin/splicon-splashes.js"
19 | },
20 | "keywords": [
21 | "cordova",
22 | "phonegap",
23 | "splash",
24 | "icon"
25 | ],
26 | "author": {
27 | "name": "Alex Blom",
28 | "email": "alex@isleofcode.com",
29 | "url": "https://isleofcode.com"
30 | },
31 | "contributors": [
32 | {
33 | "name": "Chris Thoburn",
34 | "email": "chris@isleofcode.com",
35 | "url": "https://isleofcode.com"
36 | },
37 | {
38 | "name": "Jordan Yee",
39 | "email": "jordan@isleofcode.com",
40 | "url": "https://isleofcode.com"
41 | }
42 | ],
43 | "license": "MIT",
44 | "dependencies": {
45 | "chalk": "^0.4.0",
46 | "lodash": "^4.13.1",
47 | "phantomjs-prebuilt": "^2.1.12",
48 | "pn": "^1.0.0",
49 | "rsvp": "^3.2.1",
50 | "xml2js": "^0.4.16"
51 | },
52 | "devDependencies": {
53 | "babel-cli": "^6.14.0",
54 | "babel-preset-es2015": "^6.14.0",
55 | "babelify": "^7.3.0",
56 | "chai": "^3.5.0",
57 | "chai-as-promised": "^5.3.0",
58 | "image-size": "0.3.5",
59 | "mocha": "^2.5.3",
60 | "testdouble": "^1.4.3"
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/platform-splash-sizes.js:
--------------------------------------------------------------------------------
1 | /* jshint node:true, esversion: 6 */
2 |
3 | module.exports = {
4 | ios: {
5 | sizes: [
6 | { width: 640, height: 960, id: '640-960' },
7 | { width: 960, height: 640, id: '960-640' },
8 |
9 | { width: 640, height: 1136, id: '640-1136' },
10 | { width: 1136, height: 640, id: '1136-640' },
11 |
12 | { width: 750, height: 1334, id: '750-1334' },
13 | { width: 1334, height: 750, id: '1334-750' },
14 |
15 | { width: 1242, height: 2208, id: '1242-2208' },
16 | { width: 2208, height: 1242, id: '2208-1242' },
17 |
18 | { width: 768, height: 1024, id: '768-1024' },
19 | { width: 1024, height: 768, id: '1024-768' },
20 |
21 | { width: 1536, height: 2048, id: '1536-2048' },
22 | { width: 2048, height: 1536, id: '2048-1536' },
23 |
24 | { width: 2048, height: 2732, id: '2048-2732' },
25 | { width: 2732, height: 2048, id: '2732-2048' },
26 |
27 | { width: 1125, height: 2436, id: '1125-2436' },
28 | { width: 2436, height: 1125, id: '2436-1125' }
29 | ]
30 | },
31 |
32 | android: {
33 | sizes: [
34 | { width: 200, height: 320, id: 'port-ldpi' },
35 | { width: 320, height: 200, id: 'land-ldpi' },
36 |
37 | { width: 320, height: 480, id: 'port-mdpi' },
38 | { width: 480, height: 320, id: 'land-mdpi' },
39 |
40 | { width: 480, height: 800, id: 'port-hdpi' },
41 | { width: 800, height: 480, id: 'land-hdpi' },
42 |
43 | { width: 720, height: 1280, id: 'port-xhdpi' },
44 | { width: 1280, height: 720, id: 'land-xhdpi' },
45 |
46 | { width: 960, height: 1600, id: 'port-xxhdpi' },
47 | { width: 1600, height: 960, id: 'land-xxhdpi' },
48 |
49 | { width: 1280, height: 1920, id: 'port-xxxhdpi' },
50 | { width: 1920, height: 1280, id: 'land-xxxhdpi' }
51 | ]
52 | }
53 | };
54 |
--------------------------------------------------------------------------------
/src/splash-task.js:
--------------------------------------------------------------------------------
1 | /* jshint node:true, esversion: 6 */
2 | 'use strict';
3 |
4 | const AbortTask = require('./utils/abort-task');
5 | const GetPlatSizes = require('./utils/get-platform-sizes');
6 | const PlatformSizes = require('./platform-splash-sizes');
7 | const SerialSplash = require('./utils/serialize-splash');
8 | const UpdateConfig = require('./utils/update-config');
9 | const ValidPlatform = require('./utils/validate-platforms');
10 | const WriteImages = require('./utils/write-images');
11 |
12 | const RSVP = require('rsvp');
13 | const existsSync = require('fs').existsSync;
14 | const _defaults = require('lodash').defaults;
15 |
16 | module.exports = function(opts) {
17 | return new RSVP.Promise((resolve) => {
18 | if (opts === undefined) opts = {};
19 |
20 | _defaults(opts, {
21 | source: 'splash.svg',
22 | dest: 'res/screen',
23 | projectPath: './cordova',
24 | platforms: ['all']
25 | });
26 |
27 | if (!existsSync(opts.source)) {
28 | AbortTask(`Source splash ${opts.source} does not exist`);
29 | }
30 |
31 | if (!ValidPlatform(opts.platforms)) {
32 | AbortTask(`Platforms ${opts.platforms} are not all valid`);
33 | }
34 |
35 | if (opts.platforms.length === 0 || opts.platforms.indexOf('all') > -1) {
36 | opts.platforms = ['ios', 'android'];
37 | }
38 |
39 | const platformSizes = GetPlatSizes(PlatformSizes, opts.platforms);
40 |
41 | WriteImages({
42 | source: opts.source,
43 | projectPath: opts.projectPath,
44 | dest: opts.dest,
45 | platformSizes: platformSizes
46 | })
47 | .then((updatedPlatformSizes) => {
48 | UpdateConfig({
49 | projectPath: opts.projectPath,
50 | desiredNodes: updatedPlatformSizes,
51 | keyName: 'splash',
52 | serializeFn: SerialSplash
53 | })
54 | })
55 | .then(resolve);
56 | });
57 | };
58 |
--------------------------------------------------------------------------------
/src/icon-task.js:
--------------------------------------------------------------------------------
1 | /* jshint node:true, esversion: 6 */
2 | 'use strict';
3 |
4 | const AbortTask = require('./utils/abort-task');
5 | const GetPlatSizes = require('./utils/get-platform-sizes');
6 | const PlatformSizes = require('./platform-icon-sizes');
7 | const SerializeIcon = require('./utils/serialize-icon');
8 | const UpdateConfig = require('./utils/update-config');
9 | const ValidPlatform = require('./utils/validate-platforms');
10 | const WriteImages = require('./utils/write-images');
11 |
12 | const RSVP = require('rsvp');
13 | const existsSync = require('fs').existsSync;
14 | const _defaults = require('lodash').defaults;
15 |
16 | module.exports = function(opts) {
17 | return new RSVP.Promise((resolve) => {
18 | if (opts === undefined) opts = {};
19 |
20 | _defaults(opts, {
21 | source: 'icon.svg',
22 | dest: 'res/icon',
23 | projectPath: './cordova',
24 | platforms: ['all']
25 | });
26 |
27 | if (!existsSync(opts.source)) {
28 | AbortTask(`Source icon ${opts.source} does not exist`);
29 | }
30 |
31 | if (!ValidPlatform(opts.platforms)) {
32 | AbortTask(`Platforms ${opts.platforms} are not all valid`);
33 | }
34 |
35 | if (opts.platforms.length === 0 || opts.platforms.indexOf('all') > -1) {
36 | opts.platforms = ['ios', 'android', 'windows', 'blackberry'];
37 | }
38 |
39 | const platformSizes = GetPlatSizes(PlatformSizes, opts.platforms);
40 |
41 | WriteImages({
42 | source: opts.source,
43 | projectPath: opts.projectPath,
44 | dest: opts.dest,
45 | platformSizes: platformSizes
46 | })
47 | .then((updatedPlatformSizes) => {
48 | UpdateConfig({
49 | projectPath: opts.projectPath,
50 | desiredNodes: updatedPlatformSizes,
51 | keyName: 'icon',
52 | serializeFn: SerializeIcon
53 | })
54 | })
55 | .then(resolve);
56 | });
57 | };
58 |
--------------------------------------------------------------------------------
/src/platform-icon-sizes.js:
--------------------------------------------------------------------------------
1 | /* jshint node:true, esversion: 6 */
2 |
3 | module.exports = {
4 | ios: {
5 | sizes: [
6 | { size: 40, id: 'icon-40' },
7 | { size: 57, id: 'icon' },
8 | { size: 80, id: 'icon-40@2x' },
9 | { size: 120, id: 'icon-40@3x' },
10 | { size: 60, id: 'icon-60' },
11 | { size: 120, id: 'icon-60@2x' },
12 | { size: 180, id: 'icon-60@3x' },
13 | { size: 114, id: 'icon@2x' },
14 | { size: 29, id: 'icon-small' },
15 | { size: 58, id: 'icon-small@2x' },
16 | { size: 87, id: 'icon-small@3x' },
17 | { size: 152, id: 'icon-76@2x' },
18 | { size: 76, id: 'icon-76' },
19 | { size: 72, id: 'icon-72' },
20 | { size: 144, id: 'icon-72@2x' },
21 | { size: 50, id: 'icon-50' },
22 | { size: 100, id: 'icon-50@2x' },
23 | { size: 167, id: 'icon-83.5@2x' },
24 | { size: 120, id: 'icon-120' },
25 | { size: 240, id: 'icon-120@2x' },
26 | { size: 152, id: 'icon-152' },
27 | { size: 304, id: 'icon-152@2x' },
28 | { size: 20, id: 'icon-20' },
29 | { size: 48, id: 'icon-24@2x' },
30 | { size: 55, id: 'icon-27.5@2x' },
31 | { size: 88, id: 'icon-44@2x' },
32 | { size: 172, id: 'icon-86@2x' },
33 | { size: 196, id: 'icon-98@2x' },
34 | { size: 1024, id: 'icon-1024' }
35 | ]
36 | },
37 |
38 | android: {
39 | sizes: [
40 | { size: 36, id: 'ldpi' },
41 | { size: 48, id: 'mdpi' },
42 | { size: 72, id: 'hdpi' },
43 | { size: 96, id: 'xhdpi' },
44 | { size: 144, id: 'xxhdpi' },
45 | { size: 192, id: 'xxxhdpi' }
46 | ]
47 | },
48 |
49 | blackberry: {
50 | sizes: [
51 | { size: 86, id: 'icon-86' },
52 | { size: 150, id: 'icon-150' }
53 | ]
54 | },
55 |
56 | windows: {
57 | sizes: [
58 | { size: 50, id: 'StoreLogo' },
59 | { size: 30, id: 'smalllogo', attrs: { target: 'Square30x30Logo' }},
60 | { size: 44, id: 'Square44x44Logo' },
61 | { size: 70, id: 'Square70x70Logo' },
62 | { size: 71, id: 'Square71x71Logo' },
63 | { size: 150, id: 'Square150x150Logo' },
64 | { size: 310, id: 'Square310x310Logo' }
65 | ]
66 | }
67 | };
68 |
--------------------------------------------------------------------------------
/src/utils/svg2png-converter.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /* global phantom: true */
4 |
5 | var webpage = require('webpage');
6 | var system = require('system');
7 |
8 | if (system.args.length !== 2) {
9 | console.error('Usage: converter.js resize');
10 | phantom.exit();
11 | } else {
12 | convert(system.args[1]);
13 | }
14 |
15 | function convert(resize) {
16 | var page = webpage.create();
17 | var sourceBase64 = system.stdin.readLine();
18 |
19 | page.open('data:image/svg+xml;base64,' + sourceBase64, function (status) {
20 | if (status !== 'success') {
21 | console.error('Unable to load the source file.');
22 | phantom.exit();
23 | return;
24 | }
25 |
26 | try {
27 | resize = JSON.parse(resize);
28 |
29 | var cropRequired = resize.width !== resize.height;
30 |
31 | if (cropRequired) {
32 | var crop = { width: resize.width, height: resize.height };
33 | var maxDimension = crop.width > crop.height ? 'width' : 'height';
34 | var maxDimensionSize = crop[maxDimension];
35 | resize.width = maxDimensionSize;
36 | resize.height = maxDimensionSize;
37 | }
38 |
39 | setSVGDimensions(page, resize.width, resize.height);
40 |
41 | page.viewportSize = {
42 | width: resize.width,
43 | height: resize.height
44 | };
45 |
46 | if (cropRequired) {
47 | var top, left;
48 |
49 | if (maxDimension == 'width') {
50 | top = (resize.height - crop.height) / 2;
51 | left = 0;
52 | } else {
53 | top = 0;
54 | left = (resize.width - crop.width) / 2;
55 | }
56 |
57 | page.clipRect = {
58 | top: top,
59 | left: left,
60 | width: crop.width,
61 | height: crop.height
62 | };
63 | }
64 | } catch (e) {
65 | console.error('Unable to set dimensions.');
66 | console.error(e);
67 | phantom.exit();
68 | return;
69 | }
70 |
71 | var result = 'data:image/png;base64,' + page.renderBase64('PNG');
72 | system.stdout.write(result);
73 | phantom.exit();
74 | });
75 | }
76 |
77 | function setSVGDimensions(page, width, height) {
78 | return page.evaluate(function (width, height) {
79 | /* global document: true */
80 | var el = document.documentElement;
81 | el.setAttribute('width', width + 'px');
82 | el.setAttribute('height', height + 'px');
83 | }, width, height);
84 | }
85 |
--------------------------------------------------------------------------------
/node-tests/unit/utils/write-images-test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const expect = require('../../helpers/expect');
4 |
5 | const WriteImages = require('../../../src/utils/write-images');
6 |
7 | const fs = require('fs');
8 | const sizeOf = require('image-size');
9 | const _forOwn = require('lodash').forOwn;
10 |
11 | describe('WriteImages', function() {
12 | // Hitting the file system is slow
13 | this.timeout(0);
14 |
15 | before(() => {
16 | if (!fs.existsSync('tmp')) fs.mkdirSync('tmp');
17 | });
18 |
19 | context('when source, projectPath, dest, and platformSizes', () => {
20 | const source = 'node-tests/fixtures/icon.svg';
21 | const projectPath = 'tmp';
22 | const dest = 'icons';
23 | const platformSizes = {
24 | ios: {
25 | sizeKey: 'width',
26 | sizes: [
27 | {
28 | size: 57,
29 | id: 'icon'
30 | }
31 | ]
32 | }
33 | };
34 | let subject;
35 |
36 | before(() => {
37 | subject = WriteImages({
38 | source: source,
39 | projectPath: projectPath,
40 | dest: dest,
41 | platformSizes: platformSizes
42 | });
43 | });
44 |
45 | after(() => {
46 | platformSizes['ios'].sizes.forEach((rasterize) => {
47 | fs.unlinkSync(`${projectPath}/${rasterize.path}`);
48 | });
49 | });
50 |
51 | it('resolves to platform sizes updated with paths', (done) => {
52 | subject.then((updatedPlatformSizes) => {
53 | try {
54 | _forOwn(updatedPlatformSizes, (icons, platform) => {
55 | icons.sizes.map((size) => {
56 | const path = `${dest}/${platform}/${size.id}.png`;
57 | expect(size.path).to.equal(path);
58 | });
59 | });
60 | done();
61 | } catch(e) {
62 | done(e);
63 | }
64 | });
65 | });
66 |
67 | it('writes the files to rasterize at the right size', (done) => {
68 | subject.then((updatedPlatformSizes) => {
69 | try {
70 | updatedPlatformSizes['ios'].sizes.forEach((rasterize) => {
71 | const writePath = `${projectPath}/${rasterize.path}`;
72 | expect(fs.existsSync(writePath)).to.equal(true);
73 | expect(sizeOf(writePath).width).to.equal(rasterize.size);
74 | expect(sizeOf(writePath).height).to.equal(rasterize.size);
75 | });
76 | done();
77 | } catch(e) {
78 | done(e);
79 | }
80 | });
81 | });
82 | });
83 | });
84 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Splicon (beta)
2 | --------------
3 | [](https://travis-ci.org/isleofcode/splicon)
5 |
6 | Splicon is a command-line tool and library for generating icons and splash
7 | screens for Cordova projects. It generates the images for each platform's
8 | required sizes using a single source SVG and updates the project's config.xml.
9 |
10 | It was originally built for use in [ember-cordova](https://github.com/isleofcode/ember-cordova).
11 |
12 | It is built for Node 0.12+ but may work on earlier versions.
13 |
14 | ## Icons
15 |
16 | For an integration example, see the [ember-cordova `make-icon` command](https://github.com/isleofcode/ember-cordova/tree/master/lib/commands/make-icons.js).
17 |
18 | Using the CLI, from your Cordova project, run:
19 |
20 | ```
21 | splicon-icons
22 | ```
23 |
24 | This command will:
25 |
26 | 1. Look for a file called 'icon.svg';
27 | 2. Resize the SVG for each required platform/icon combination;
28 | 3. Move the icons to res/icons/platformName (and create the dir if it does not
29 | exist);
30 | 4. Update your config.xml to represent the new icons & paths;
31 | 5. Ensure there are no duplicate icon nodes in config.xml;
32 |
33 | By default, images for all platforms will be generated. To generate images for
34 | specific platforms you can pass the platforms as arguments:
35 |
36 | ```
37 | splicon-icons ios android windows
38 | ```
39 |
40 | For more granular control (such as setting the destination path), you
41 | will need to require src/icon-task and run the function yourself.
42 |
43 | There is a TODO to enhance CLI flag, but in most cases this is handled in
44 | [ember-cordova](https://github.com/isleofcode/ember-cordova).
45 |
46 | ## Splash Screens
47 |
48 | ```
49 | splicon-splashes
50 | ```
51 |
52 | Like `splicon-icons`, by default images for all platforms will be generated. To
53 | generate images for specific platforms you can pass the platforms as arguments:
54 |
55 | ```
56 | splicon-splashes ios
57 | ```
58 |
59 | ## Testing
60 |
61 | ```
62 | npm test
63 | ```
64 |
65 | ## Contributing
66 |
67 | PRs are very welcome. You can read our style guides
68 | [here](https://github.com/isleofcode/style-guide).
69 |
70 | If you are unsure about your contribution idea, please feel free to open an
71 | issue for feedback.
72 |
73 | ## Credits
74 |
75 | [ember-cordova](https://github.com/isleofcode/ember-cordova) is maintained by
76 | [Isle of Code](https://isleofcode.com) and was written by Alex Blom and Jordan
77 | Yee based on work by Chris Thoburn and Alex Blom.
78 |
--------------------------------------------------------------------------------
/node-tests/unit/utils/serialize-icon-test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const expect = require('../../helpers/expect');
4 |
5 | const SerializeIcon = require('../../../src/utils/serialize-icon');
6 |
7 | describe('SerializeIcon', () => {
8 | context('when platform is iOS', () => {
9 | it('returns an object with id, path, width, and height', () => {
10 | const platform = 'ios';
11 | const projectPath = '';
12 | const iconData = { path: 'foo', size: 180, id: 'foo' };
13 |
14 | const props = SerializeIcon(platform, projectPath, iconData);
15 |
16 | expect(props.id).to.equal(iconData.id);
17 | expect(props.src).to.equal(iconData.path);
18 | expect(props.width).to.equal(iconData.size.toString());
19 | expect(props.height).to.equal(iconData.size.toString());
20 | });
21 | });
22 |
23 | context('when platform is Android', () => {
24 | it('returns an object with id, path, and density', () => {
25 | const platform = 'android';
26 | const projectPath = '';
27 | const iconData = { path: 'foo', id: 'xxxhdpi' };
28 |
29 | const props = SerializeIcon(platform, projectPath, iconData);
30 |
31 | expect(props.id).to.equal(iconData.id);
32 | expect(props.src).to.equal(iconData.path);
33 | expect(props.density).to.equal(iconData.id);
34 | });
35 | });
36 |
37 | context('when platform is Windows', () => {
38 | const platform = 'windows';
39 | const projectPath = '';
40 |
41 | context('when icon data includes id and target', () => {
42 | it('returns an object with id, path, and target as target', () => {
43 | const iconData = {
44 | path: 'foo',
45 | id: 'smalllogo',
46 | attrs: {
47 | target: 'Square30x30Logo'
48 | }
49 | };
50 |
51 | const props = SerializeIcon(platform, projectPath, iconData);
52 |
53 | expect(props.id).to.equal(iconData.id);
54 | expect(props.src).to.equal(iconData.path);
55 | expect(props.target).to.equal(iconData.attrs.target);
56 | });
57 | })
58 |
59 | context('when icon data includes id and not target', () => {
60 | it('returns an object with id, path, and id as target', () => {
61 | const iconData = { path: 'foo', id: 'smalllogo' };
62 |
63 | const props = SerializeIcon(platform, projectPath, iconData);
64 |
65 | expect(props.id).to.equal(iconData.id);
66 | expect(props.src).to.equal(iconData.path);
67 | expect(props.target).to.equal(iconData.id);
68 | });
69 | })
70 | });
71 | });
72 |
--------------------------------------------------------------------------------
/src/utils/update-config.js:
--------------------------------------------------------------------------------
1 | /* globals String */
2 |
3 | /* jshint node:true, esversion: 6 */
4 | 'use strict';
5 |
6 | const xml2js = require('xml2js');
7 | const fs = require('fs');
8 | const RSVP = require('rsvp');
9 | const chalk = require('chalk');
10 | const _findIndex = require('lodash').findIndex;
11 | const _forOwn = require('lodash').forOwn;
12 | const _filter = require('lodash').filter;
13 | const _remove = require('lodash').remove;
14 | const _pick = require('lodash').pick;
15 | const _pullAt = require('lodash').pullAt;
16 |
17 | const parseXML = function(xmlPath) {
18 | return new RSVP.Promise((resolve, reject) => {
19 | const contents = fs.readFileSync(xmlPath, 'utf8');
20 | const parser = new xml2js.Parser();
21 |
22 | if (contents === '') reject('File is empty');
23 |
24 | parser.parseString(contents, function (err, result) {
25 | if (err) reject(err);
26 | if (result) resolve(result);
27 | });
28 | });
29 | };
30 |
31 | const saveXML = function(json, xmlPath) {
32 | const builder = new xml2js.Builder({
33 | renderOpts: {
34 | 'pretty': true,
35 | 'indent': ' ',
36 | 'newline': '\n'
37 | }
38 | });
39 | const xml = builder.buildObject(json);
40 |
41 | // Add missing trailing newline
42 | fs.writeFileSync(xmlPath, xml + '\n');
43 | };
44 |
45 | const addNodes = function(json, opts) {
46 | _forOwn(opts.desiredNodes, (nodeData, platformName) => {
47 |
48 | //Cordova wont always have a platforms: []
49 | if (!json.widget.platform) json.widget.platform = [];
50 |
51 | //See if platform already exists
52 | let platformNode;
53 | let platformNodePos = _findIndex(json.widget.platform, {$: { name: platformName } });
54 |
55 | if (platformNodePos > -1) {
56 | //the platform existed, assign to platform Node & temp rm from JSOn
57 | platformNode = json.widget.platform[platformNodePos];
58 | _pullAt(json.widget.platform, platformNodePos);
59 | } else {
60 | platformNode = {$: { name: platformName } };
61 | }
62 |
63 | let targetNodes = platformNode[opts.keyName];
64 | if (targetNodes === undefined) {
65 | targetNodes = [];
66 | }
67 |
68 | // Replace existing nodes.
69 | nodeData.sizes.forEach((node) => {
70 | let newAttrs = opts.serializeFn(platformName, opts.projectPath, node);
71 |
72 | _filter(targetNodes, (item) => {
73 | if (!item) return;
74 |
75 | let existingAttrs = item.$;
76 |
77 | if (newAttrs.id === existingAttrs.id) {
78 | _remove(targetNodes, item);
79 | }
80 | });
81 |
82 | targetNodes.push( {$: newAttrs} );
83 | });
84 |
85 | platformNode[opts.keyName] = targetNodes;
86 | json.widget.platform.push(platformNode);
87 | });
88 |
89 | return json;
90 | };
91 |
92 | /*
93 | Required opts:
94 |
95 | desiredNodes Array:
96 | {ios: [], android: [], blackberry: []}
97 | See src/platform-icon-sizes for an example
98 |
99 | keyName: String
100 | `icon` or `splash`
101 |
102 | serializeFn: Function
103 | Given a single node from desiredNodes, serialize to config.xml format
104 | */
105 | module.exports = function(opts) {
106 | const configPath = `${opts.projectPath}/config.xml`;
107 |
108 | return new RSVP.Promise((resolve, reject) => {
109 | if (!opts.projectPath ||
110 | !opts.desiredNodes ||
111 | !opts.keyName ||
112 | !opts.serializeFn) {
113 |
114 | reject(
115 | 'Missing required opts: projectPath, desiredNodes, keyName, serializeFn'
116 | );
117 | }
118 |
119 | parseXML(configPath).then((json) => {
120 | json = addNodes(json, opts);
121 | saveXML(json, configPath);
122 | resolve();
123 | }).catch((err) => {
124 | console.log(chalk.red(
125 | `Error reading XML: ${err}`
126 | ));
127 |
128 | reject(new Error(err));
129 | });
130 | });
131 | };
132 |
--------------------------------------------------------------------------------
/node-tests/unit/icon-task-test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const expect = require('../helpers/expect');
4 |
5 | const IconTask = require('../../src/icon-task');
6 |
7 | const fs = require('fs');
8 | const sizeOf = require('image-size');
9 | const parseString = require('xml2js').parseString;
10 | const _find = require('lodash').find;
11 | const PlatformSizes = require('../../src/platform-icon-sizes');
12 |
13 | describe('IconTask', function() {
14 | // Hitting the file system is slow
15 | this.timeout(0);
16 |
17 | const configFixtureDir = 'node-tests/fixtures/config.xml';
18 | const tmpConfigPath = 'tmp/config.xml';
19 |
20 | const svgPath = 'node-tests/fixtures/icon.svg';
21 | const pngPath = 'icons';
22 | const projectPath = 'tmp';
23 |
24 | before((done) => {
25 | if (!fs.existsSync('tmp')) fs.mkdirSync('tmp');
26 |
27 | const fixturePath = `${configFixtureDir}/no-platform-nodes.xml`;
28 | const fixtureStream = fs.createReadStream(fixturePath);
29 | const tmpConfigStream = fs.createWriteStream(tmpConfigPath);
30 | fixtureStream.pipe(tmpConfigStream);
31 | tmpConfigStream.on('finish', () => done());
32 | });
33 |
34 | after(() => {
35 | fs.unlinkSync(tmpConfigPath);
36 | });
37 |
38 | context('when platforms', () => {
39 | context('when ios', () => {
40 | const platform = 'ios';
41 | const platformSizes = PlatformSizes[platform];
42 | let task;
43 |
44 | before(() => {
45 | task = IconTask({
46 | source: svgPath,
47 | dest: pngPath,
48 | projectPath: projectPath,
49 | platforms: [platform]
50 | })
51 | });
52 |
53 | after(() => {
54 | platformSizes.sizes.forEach((size) => {
55 | const path =
56 | `${projectPath}/${pngPath}/${platform}/${size.id}.png`;
57 | fs.unlinkSync(path);
58 | });
59 | fs.rmdirSync(`${projectPath}/${pngPath}/${platform}`);
60 | fs.rmdirSync(`${projectPath}/${pngPath}`);
61 | });
62 |
63 | it('writes the icons', (done) => {
64 | task.then(() => {
65 | try {
66 | platformSizes.sizes.forEach((size) => {
67 | const path =
68 | `${projectPath}/${pngPath}/${platform}/${size.id}.png`;
69 |
70 | expect(fs.existsSync(path)).to.equal(true);
71 | expect(sizeOf(path).width).to.equal(size.size);
72 | expect(sizeOf(path).height).to.equal(size.size);
73 | });
74 | done();
75 | } catch(e) {
76 | done(e);
77 | }
78 | });
79 | });
80 |
81 | it('updates config.xml', (done) => {
82 | task.then(() => {
83 | const configFile = fs.readFileSync(tmpConfigPath, 'utf8');
84 |
85 | try {
86 | parseString(configFile, (err, config) => {
87 | if (err) done(err);
88 |
89 | const platformNode = _find(config.widget.platform, (node) => {
90 | return node.$.name == platform;
91 | });
92 |
93 | expect(platformNode).to.exist;
94 |
95 | const iconsAttrs = platformNode.icon.map((iconNode) => {
96 | return iconNode.$;
97 | });
98 |
99 | platformSizes.sizes.forEach((size) => {
100 | const attrs = {
101 | id: size.id,
102 | src: `${pngPath}/${platform}/${size.id}.png`,
103 | height: size.size.toString(),
104 | width: size.size.toString()
105 | }
106 |
107 | expect(iconsAttrs).to.include(attrs);
108 | });
109 | });
110 | done();
111 | } catch(e) {
112 | done(e);
113 | }
114 | });
115 | });
116 |
117 | it('returns a promise', (done) => {
118 | expect(task).to.be.fulfilled.notify(done);
119 | });
120 | });
121 | });
122 | });
123 |
--------------------------------------------------------------------------------
/node-tests/unit/splash-task-test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const expect = require('../helpers/expect');
4 |
5 | const SplashTask = require('../../src/splash-task');
6 |
7 | const fs = require('fs');
8 | const sizeOf = require('image-size');
9 | const parseString = require('xml2js').parseString;
10 | const _find = require('lodash').find;
11 | const PlatformSizes = require('../../src/platform-splash-sizes');
12 |
13 | describe('SplashTask', function() {
14 | // Hitting the file system is slow
15 | this.timeout(0);
16 |
17 | const configFixtureDir = 'node-tests/fixtures/config.xml';
18 | const tmpConfigPath = 'tmp/config.xml';
19 |
20 | const svgPath = 'node-tests/fixtures/splash.svg';
21 | const pngPath = 'splashes';
22 | const projectPath = 'tmp';
23 |
24 | before((done) => {
25 | if (!fs.existsSync('tmp')) fs.mkdirSync('tmp');
26 |
27 | const fixturePath = `${configFixtureDir}/no-platform-nodes.xml`;
28 | const fixtureStream = fs.createReadStream(fixturePath);
29 | const tmpConfigStream = fs.createWriteStream(tmpConfigPath);
30 | fixtureStream.pipe(tmpConfigStream);
31 | tmpConfigStream.on('finish', () => done());
32 | });
33 |
34 | after(() => {
35 | fs.unlinkSync(tmpConfigPath);
36 | });
37 |
38 | context('when platforms', () => {
39 | context('when ios', () => {
40 | const platform = 'ios';
41 | const platformSizes = PlatformSizes[platform];
42 | let task;
43 |
44 | before(() => {
45 | task = SplashTask({
46 | source: svgPath,
47 | dest: pngPath,
48 | projectPath: projectPath,
49 | platforms: [platform]
50 | })
51 | });
52 |
53 | after(() => {
54 | platformSizes.sizes.forEach((size) => {
55 | const path =
56 | `${projectPath}/${pngPath}/${platform}/${size.id}.png`;
57 | fs.unlinkSync(path);
58 | });
59 | fs.rmdirSync(`${projectPath}/${pngPath}/${platform}`);
60 | fs.rmdirSync(`${projectPath}/${pngPath}`);
61 | });
62 |
63 | it('writes the splashes', (done) => {
64 | task.then(() => {
65 | try {
66 | platformSizes.sizes.forEach((size) => {
67 | const path =
68 | `${projectPath}/${pngPath}/${platform}/${size.id}.png`;
69 |
70 | expect(fs.existsSync(path)).to.equal(true);
71 | expect(sizeOf(path).width).to.equal(size.width);
72 | expect(sizeOf(path).height).to.equal(size.height);
73 | });
74 | done();
75 | } catch(e) {
76 | done(e);
77 | }
78 | });
79 | });
80 |
81 | it('updates config.xml', (done) => {
82 | task.then(() => {
83 | const configFile = fs.readFileSync(tmpConfigPath, 'utf8');
84 |
85 | try {
86 | parseString(configFile, (err, config) => {
87 | if (err) done(err);
88 |
89 | const platformNode = _find(config.widget.platform, (node) => {
90 | return node.$.name == platform;
91 | });
92 |
93 | expect(platformNode).to.exist;
94 |
95 | const splashesAttrs = platformNode.splash.map((splashNode) => {
96 | return splashNode.$;
97 | });
98 |
99 | platformSizes.sizes.forEach((size) => {
100 | const attrs = {
101 | id: size.id,
102 | src: `${pngPath}/${platform}/${size.id}.png`,
103 | height: size.height.toString(),
104 | width: size.width.toString()
105 | }
106 |
107 | expect(splashesAttrs).to.include(attrs);
108 | });
109 | });
110 | done();
111 | } catch(e) {
112 | done(e);
113 | }
114 | });
115 | });
116 |
117 | it('returns a promise', (done) => {
118 | expect(task).to.be.fulfilled.notify(done);
119 | });
120 | });
121 | });
122 | });
123 |
--------------------------------------------------------------------------------
/node-tests/unit/utils/update-config-test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const expect = require('../../helpers/expect');
4 |
5 | const UpdateConfig = require('../../../src/utils/update-config');
6 |
7 | const fs = require('fs');
8 | const SerializeIcon = require('../../../src/utils/serialize-icon');
9 |
10 | describe('UpdateConfig', function() {
11 | const configFixtureDir = 'node-tests/fixtures/config.xml';
12 | const tmpConfigPath = 'tmp/config.xml';
13 |
14 | before(() => {
15 | if (!fs.existsSync('tmp')) fs.mkdirSync('tmp');
16 | });
17 |
18 | afterEach(() => {
19 | fs.unlinkSync(tmpConfigPath);
20 | });
21 |
22 | context('when projectPath, desiredNodes, keyName, and serializeFn', () => {
23 | const projectPath = 'tmp';
24 | const desiredNodes = {
25 | ios: {
26 | sizes: [
27 | { size: 57, id: 'icon', path: 'res/icon/ios/icon.png' }
28 | ]
29 | }
30 | };
31 | const keyName = 'icon';
32 | const serializeFn = SerializeIcon;
33 | const args = {
34 | projectPath: projectPath,
35 | desiredNodes: desiredNodes,
36 | keyName: keyName,
37 | serializeFn: serializeFn
38 | };
39 |
40 | context('when config.xml has no platform nodes', () => {
41 | before((done) => {
42 | const fixturePath = `${configFixtureDir}/no-platform-nodes.xml`;
43 | const fixtureStream = fs.createReadStream(fixturePath);
44 | const tmpConfigStream = fs.createWriteStream(tmpConfigPath);
45 | fixtureStream.pipe(tmpConfigStream);
46 | tmpConfigStream.on('finish', () => { done(); });
47 | });
48 |
49 | it('it adds the platform node with icon nodes', (done) => {
50 | UpdateConfig(args).then(() => {
51 | const tmpConfig = fs.readFileSync(tmpConfigPath, 'utf8');
52 | const expectedConfigPath =
53 | `${configFixtureDir}/no-and-ios-platform-node-expected.xml`;
54 | const expectedConfig = fs.readFileSync(expectedConfigPath, 'utf8');
55 |
56 | try {
57 | expect(tmpConfig).to.equal(expectedConfig);
58 | done();
59 | } catch(e) {
60 | done(e);
61 | }
62 | }).catch((e) => {
63 | done(e);
64 | });
65 | });
66 | });
67 |
68 | context('when config.xml has desiredNodes platform with no icons', () => {
69 | before((done) => {
70 | const fixturePath = `${configFixtureDir}/ios-platform-node.xml`;
71 | const fixtureStream = fs.createReadStream(fixturePath);
72 | const tmpConfigStream = fs.createWriteStream(tmpConfigPath);
73 | fixtureStream.pipe(tmpConfigStream);
74 | tmpConfigStream.on('finish', () => { done(); });
75 | });
76 |
77 | it('it adds the icon nodes', (done) => {
78 | UpdateConfig(args).then(() => {
79 | const tmpConfig = fs.readFileSync(tmpConfigPath, 'utf8');
80 | const expectedConfigPath =
81 | `${configFixtureDir}/no-and-ios-platform-node-expected.xml`;
82 | const expectedConfig = fs.readFileSync(expectedConfigPath, 'utf8');
83 |
84 | try {
85 | expect(tmpConfig).to.equal(expectedConfig);
86 | done();
87 | } catch(e) {
88 | done(e);
89 | }
90 | }).catch((e) => {
91 | done(e);
92 | });
93 | });
94 | });
95 |
96 | context('when config.xml has desiredNodes platform with icons', () => {
97 | before((done) => {
98 | const fixturePath =
99 | `${configFixtureDir}/no-and-ios-platform-node-expected.xml`;
100 | const fixtureStream = fs.createReadStream(fixturePath);
101 | const tmpConfigStream = fs.createWriteStream(tmpConfigPath);
102 | fixtureStream.pipe(tmpConfigStream);
103 | tmpConfigStream.on('finish', () => { done(); });
104 | });
105 |
106 | it('it replaces the icon nodes', (done) => {
107 | UpdateConfig(args).then(() => {
108 | const tmpConfig = fs.readFileSync(tmpConfigPath, 'utf8');
109 | const expectedConfigPath =
110 | `${configFixtureDir}/no-and-ios-platform-node-expected.xml`;
111 | const expectedConfig = fs.readFileSync(expectedConfigPath, 'utf8');
112 |
113 | try {
114 | expect(tmpConfig).to.equal(expectedConfig);
115 | done();
116 | } catch(e) {
117 | done(e);
118 | }
119 | }).catch((e) => {
120 | done(e);
121 | });
122 | });
123 | });
124 |
125 | context('when config.xml does not have desiredNodes platform', () => {
126 | before((done) => {
127 | const fixturePath = `${configFixtureDir}/android-platform-node.xml`;
128 | const fixtureStream = fs.createReadStream(fixturePath);
129 | const tmpConfigStream = fs.createWriteStream(tmpConfigPath);
130 | fixtureStream.pipe(tmpConfigStream);
131 | tmpConfigStream.on('finish', () => { done(); });
132 | });
133 |
134 | it('it adds the platform node with icon nodes', (done) => {
135 | UpdateConfig(args).then(() => {
136 | const tmpConfig = fs.readFileSync(tmpConfigPath, 'utf8');
137 | const expectedConfigPath =
138 | `${configFixtureDir}/android-platform-node-expected.xml`;
139 | const expectedConfig = fs.readFileSync(expectedConfigPath, 'utf8');
140 |
141 | try {
142 | expect(tmpConfig).to.equal(expectedConfig);
143 | done();
144 | } catch(e) {
145 | done(e);
146 | }
147 | }).catch((e) => {
148 | done(e);
149 | });
150 | });
151 | });
152 | });
153 | });
154 |
--------------------------------------------------------------------------------