├── .editorconfig ├── .github └── FUNDING.yml ├── .gitignore ├── .markdownlint.json ├── .vscode └── launch.json ├── LICENSE ├── README.md ├── jest.config.js ├── package-lock.json ├── package.json ├── src ├── GitListDir.ts ├── Interfaces.ts ├── LoadModules.ts ├── bin.ts ├── modules │ ├── Html │ │ ├── ManifestBuilder.ts │ │ ├── ProgressBar.ts │ │ ├── checkForUpdates.ts │ │ ├── createIfNotExist.ts │ │ ├── create_intent_filter.ts │ │ ├── downloadGithubFile.ts │ │ ├── downloadGithubRepo.ts │ │ ├── downloadSDK.ts │ │ ├── getJavaVersion.ts │ │ ├── updateApkVersion.ts │ │ ├── updateAppName.ts │ │ ├── updateAppTheme.ts │ │ ├── updateIcon.ts │ │ ├── updateSdk.ts │ │ └── update_manifest_file.ts │ ├── react.ts │ ├── static_app.ts │ └── webview.ts └── postinstall.ts ├── tsconfig.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: androidjs 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | 4 | coverage/ 5 | dist/ 6 | 7 | !.vscode/ 8 | test/ 9 | dist/ 10 | .idea/ 11 | TEST_FOLDER/ 12 | package-lock.json 13 | 14 | -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "default": true, 3 | "no-bare-urls": true, 4 | "no-alt-text": false, 5 | "heading-increment": false, 6 | "MD002": { 7 | "level": 1 8 | }, 9 | "MD003": { 10 | "style": "atx" 11 | }, 12 | "MD004": { 13 | "style": "sublist" 14 | }, 15 | "MD007": { 16 | "indent": 4 17 | }, 18 | "MD009": { 19 | "br_spaces": 4, 20 | "list_item_empty_lines": true 21 | }, 22 | "MD013": { 23 | "code_blocks": false, 24 | "headings": false 25 | }, 26 | "MD024": { 27 | "siblings_only": true 28 | }, 29 | "MD026": { 30 | "punctuation": ".,;:" 31 | }, 32 | "MD033": { 33 | "allowed_elements": [ 34 | "br" 35 | ] 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "skipFiles": [ 12 | "/**" 13 | ], 14 | "program": "${workspaceFolder}\\dist\\index.js", 15 | "outFiles": [ 16 | "${workspaceFolder}/**/*.js" 17 | ] 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 androidjs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Androidjs-builder 2 | --- 3 | This is official builder for creating [Androidjs](https://android-js.github.io/) apps. Builder provides required commands for creating Androidjs project and to build the project and generate Apk for [android](https://developer.android.com/about) devices. 4 | 5 | ## Prerequisites 6 | A Java Development Kit (JDK) is required, for Ubuntu and Debian-based operating systems, do: 7 | ```shell script 8 | $ sudo apt install default-jdk -y 9 | ``` 10 | 11 | ## Installing from [npm](https://www.npmjs.com/). [![androidjs-builder][androidjs-builder-badge]][androidjs-builder-npm] 12 | ```shell script 13 | $ npm install -g androidjs-builder 14 | ``` 15 | This command installs the builder in globelly and sets the required commands. 16 | 17 | ## Creating Project. 18 | ```shell script 19 | $ androidjs init 20 | ``` 21 | This command is used for generating new project in the current directory. This command prompts for [``project-name``](https://android-js.github.io/docs/configuring_app.html#change-the-name-of-your-app) and [``project-type``](https://android-js.github.io/docs/configuring_app.html#define-project-type). Currently it supports only the HTML type project. 22 | 23 | ## Build project 24 | ```shell script 25 | $ androidjs build 26 | ``` 27 | This command is used to build project and generate Apk file. It can be used to build all type of projects supported by Androidjs. 28 | 29 | ## Update required modules from github 30 | ```shell script 31 | $ androidjs update 32 | ``` 33 | 34 | --- 35 | # Build from source 36 | ## Download from github [![androidjs-builder][androidjs-builder-badge]][androidjs-builder-github] 37 | ```shell script 38 | $ git clone https://github.com/android-js/androidjs-builder.git 39 | ``` 40 | ## Build source 41 | ```shell script 42 | $ cd androidjs-builder 43 | $ npm install 44 | $ npm run build 45 | ``` 46 | ## Install global command 47 | ```shell script 48 | $ npm install -g ./ 49 | ``` 50 | 51 | 52 | [androidjs-builder-badge]: https://img.shields.io/badge/Androidjs-builder-green.svg 53 | [androidjs-builder-npm]: https://www.npmjs.com/package/androidjs-builder 54 | [androidjs-builder-github]: https://github.com/android-js/androidjs-builder 55 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | // For a detailed explanation regarding each configuration property, visit: 2 | // https://jestjs.io/docs/en/configuration.html 3 | 4 | module.exports = { 5 | // All imported modules in your tests should be mocked automatically 6 | // automock: false, 7 | 8 | // Stop running tests after `n` failures 9 | // bail: 0, 10 | 11 | // Respect "browser" field in package.json when resolving modules 12 | // browser: false, 13 | 14 | // The directory where Jest should store its cached dependency information 15 | // cacheDirectory: "C:\\Users\\Smart\\AppData\\Local\\Temp;\\jest", 16 | 17 | // Automatically clear mock calls and instances between every test 18 | clearMocks: true, 19 | 20 | // Indicates whether the coverage information should be collected while executing the test 21 | // collectCoverage: false, 22 | 23 | // An array of glob patterns indicating a set of files for which coverage information should be collected 24 | // collectCoverageFrom: undefined, 25 | 26 | // The directory where Jest should output its coverage files 27 | // coverageDirectory: undefined, 28 | 29 | // An array of regexp pattern strings used to skip coverage collection 30 | // coveragePathIgnorePatterns: [ 31 | // "\\\\node_modules\\\\" 32 | // ], 33 | 34 | // A list of reporter names that Jest uses when writing coverage reports 35 | // coverageReporters: [ 36 | // "json", 37 | // "text", 38 | // "lcov", 39 | // "clover" 40 | // ], 41 | 42 | // An object that configures minimum threshold enforcement for coverage results 43 | // coverageThreshold: undefined, 44 | 45 | // A path to a custom dependency extractor 46 | // dependencyExtractor: undefined, 47 | 48 | // Make calling deprecated APIs throw helpful error messages 49 | // errorOnDeprecated: false, 50 | 51 | // Force coverage collection from ignored files using an array of glob patterns 52 | // forceCoverageMatch: [], 53 | 54 | // A path to a module which exports an async function that is triggered once before all test suites 55 | // globalSetup: undefined, 56 | 57 | // A path to a module which exports an async function that is triggered once after all test suites 58 | // globalTeardown: undefined, 59 | 60 | // A set of global variables that need to be available in all test environments 61 | // globals: {}, 62 | 63 | // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. 64 | // maxWorkers: "50%", 65 | 66 | // An array of directory names to be searched recursively up from the requiring module's location 67 | // moduleDirectories: [ 68 | // "node_modules" 69 | // ], 70 | 71 | // An array of file extensions your modules use 72 | // moduleFileExtensions: [ 73 | // "js", 74 | // "json", 75 | // "jsx", 76 | // "ts", 77 | // "tsx", 78 | // "node" 79 | // ], 80 | 81 | // A map from regular expressions to module names that allow to stub out resources with a single module 82 | // moduleNameMapper: {}, 83 | 84 | // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader 85 | // modulePathIgnorePatterns: [], 86 | 87 | // Activates notifications for test results 88 | // notify: false, 89 | 90 | // An enum that specifies notification mode. Requires { notify: true } 91 | // notifyMode: "failure-change", 92 | 93 | // A preset that is used as a base for Jest's configuration 94 | // preset: undefined, 95 | 96 | // Run tests from one or more projects 97 | // projects: undefined, 98 | 99 | // Use this configuration option to add custom reporters to Jest 100 | // reporters: undefined, 101 | 102 | // Automatically reset mock state between every test 103 | // resetMocks: false, 104 | 105 | // Reset the module registry before running each individual test 106 | // resetModules: false, 107 | 108 | // A path to a custom resolver 109 | // resolver: undefined, 110 | 111 | // Automatically restore mock state between every test 112 | // restoreMocks: false, 113 | 114 | // The root directory that Jest should scan for tests and modules within 115 | // rootDir: undefined, 116 | 117 | // A list of paths to directories that Jest should use to search for files in 118 | // roots: [ 119 | // "" 120 | // ], 121 | 122 | // Allows you to use a custom runner instead of Jest's default test runner 123 | // runner: "jest-runner", 124 | 125 | // The paths to modules that run some code to configure or set up the testing environment before each test 126 | // setupFiles: [], 127 | 128 | // A list of paths to modules that run some code to configure or set up the testing framework before each test 129 | // setupFilesAfterEnv: [], 130 | 131 | // A list of paths to snapshot serializer modules Jest should use for snapshot testing 132 | // snapshotSerializers: [], 133 | 134 | // The test environment that will be used for testing 135 | testEnvironment: "node", 136 | 137 | // Options that will be passed to the testEnvironment 138 | // testEnvironmentOptions: {}, 139 | 140 | // Adds a location field to test results 141 | // testLocationInResults: false, 142 | 143 | // The glob patterns Jest uses to detect test files 144 | // testMatch: [ 145 | // "**/__tests__/**/*.[jt]s?(x)", 146 | // "**/?(*.)+(spec|test).[tj]s?(x)" 147 | // ], 148 | 149 | // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped 150 | // testPathIgnorePatterns: [ 151 | // "\\\\node_modules\\\\" 152 | // ], 153 | 154 | // The regexp pattern or array of patterns that Jest uses to detect test files 155 | // testRegex: [], 156 | 157 | // This option allows the use of a custom results processor 158 | // testResultsProcessor: undefined, 159 | 160 | // This option allows use of a custom test runner 161 | // testRunner: "jasmine2", 162 | 163 | // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href 164 | // testURL: "http://localhost", 165 | 166 | // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout" 167 | // timers: "real", 168 | 169 | // A map from regular expressions to paths to transformers 170 | // transform: undefined, 171 | 172 | // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation 173 | transformIgnorePatterns: [ 174 | "\\\\node_modules\\\\" 175 | ], 176 | 177 | // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them 178 | // unmockedModulePathPatterns: undefined, 179 | 180 | // Indicates whether each individual test should be reported during the run 181 | // verbose: undefined, 182 | 183 | // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode 184 | // watchPathIgnorePatterns: [], 185 | 186 | // Whether to use watchman for file crawling 187 | // watchman: true, 188 | }; 189 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "androidjs-builder", 3 | "description": "", 4 | "version": "2.3.2", 5 | "license": "MIT", 6 | "author": { 7 | "name": "Pankaj Devesh", 8 | "email": "pankajdevesh3@gmail.com" 9 | }, 10 | "scripts": { 11 | "clear": "rm -rf dist/", 12 | "clear:all": "rm -rf node_modules/ && npm run clear", 13 | "test": "jest", 14 | "start": "tsc && node dist/index.js", 15 | "dev": "ts-node src/bin.ts", 16 | "build": "tsc", 17 | "build:watch": "tsc --watch", 18 | "postversion": "git push --tags", 19 | "docs": "./node_modules/.bin/typedoc.cmd --out dist/docs --mode modules ./src/" 20 | }, 21 | "bin": { 22 | "androidjs": "./dist/src/bin.js" 23 | }, 24 | "dependencies": { 25 | "@types/jest": "^25.1.3", 26 | "@types/node": "^12.11.5", 27 | "adm-zip": "^0.4.14", 28 | "chalk": "^3.0.0", 29 | "chokidar": "^3.3.1", 30 | "find-java-home": "^1.1.0", 31 | "fs-extra": "^8.1.0", 32 | "inquirer": "^7.0.5", 33 | "invalidate-module": "^1.0.0", 34 | "js-yaml": "^3.13.1", 35 | "list-github-dir-content": "^2.0.0", 36 | "modify-xml": "^0.2.0", 37 | "ora": "^4.0.2", 38 | "replace-in-files": "^2.0.3", 39 | "request": "^2.88.2", 40 | "semver": "^7.1.3", 41 | "superagent": "^5.2.2", 42 | "ts-node": "^8.4.1", 43 | "typedoc": "^0.16.10", 44 | "typescript": "^3.6.4", 45 | "xml2js": "^0.4.23", 46 | "yargs": "^14.2.0", 47 | "commander": "^6.0.0" 48 | }, 49 | "maintainers": [], 50 | "devDependencies": { 51 | "jest": "^25.1.0" 52 | }, 53 | "repository": { 54 | "type": "git", 55 | "url": "git@github.com:deveshpankaj/androidjs-builder" 56 | }, 57 | "files": [ 58 | "src", 59 | "package.json", 60 | "tsconfig.json" 61 | ], 62 | "keywords": [] 63 | } 64 | -------------------------------------------------------------------------------- /src/GitListDir.ts: -------------------------------------------------------------------------------- 1 | import * as request from 'request'; 2 | import * as path from 'path'; 3 | 4 | export function lsGit(user: string, repo: string, dir: string, callback) { 5 | if(!callback){ 6 | callback = dir; 7 | dir = ''; 8 | } 9 | let options = { 10 | url: `https://api.github.com/repos/${user}/${repo}/contents/${dir}?ref=HEAD`, 11 | headers: { 12 | 'User-Agent': 'request' 13 | } 14 | }; 15 | request.get(options, callback); 16 | } 17 | 18 | 19 | export function getDownloadLink(user: string, repo: string, branch ="master") { 20 | return `https://github.com/${user}/${repo}/archive/${branch}.zip`; 21 | } 22 | 23 | /// TODO: need to update this function 24 | // @ts-ignore 25 | export function getFileDownloadLink(user: string, repo: string, dir: string, file?:string) { 26 | if(file===undefined)return `https://raw.githubusercontent.com/${user}/${repo}/HEAD/${dir}`; 27 | else return `https://raw.githubusercontent.com/${user}/${repo}/HEAD/${dir}/${file}` 28 | } 29 | 30 | // function callback(error, response, body) { 31 | // if (!error && response.statusCode == 200) { 32 | // const ls: Array<{name: string, type: string}> = JSON.parse(body); 33 | // ls.forEach(e => { 34 | // console.log(e.type.toUpperCase(), e.name); 35 | // }) 36 | // } 37 | // } 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/Interfaces.ts: -------------------------------------------------------------------------------- 1 | import {Command} from 'commander'; 2 | 3 | export namespace Interfaces { 4 | export interface IProject { 5 | name: string 6 | dir: string 7 | type: string 8 | package?: IPackage 9 | } 10 | 11 | export interface IBuilder { 12 | dir: string 13 | debug?: boolean 14 | cache: string 15 | commander: Command | any 16 | } 17 | 18 | export interface IEnv { 19 | force: boolean 20 | release: boolean 21 | project: Interfaces.IProject 22 | builder: Interfaces.IBuilder 23 | debuggable?: boolean 24 | } 25 | 26 | export interface IContext { 27 | updateSdk: any; 28 | commander: Command 29 | } 30 | export interface IBuilderModule { 31 | installModule(env: IEnv, context:IContext): number 32 | create(): number 33 | build(): number 34 | _?: IContext 35 | } 36 | 37 | export interface GithubFileLink { 38 | user: string 39 | repo: string 40 | file: string 41 | dir: string 42 | } 43 | 44 | export interface IPackage { 45 | version: string 46 | name: string 47 | type: string 48 | 'package-name': string 49 | 'app-name': string 50 | permission: Array 51 | 'deep-link': Array 52 | } 53 | export interface IDeepLink { 54 | scheme: string, 55 | host: string 56 | } 57 | 58 | export interface IGithubRepoLink { 59 | user: string 60 | repo: string 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/LoadModules.ts: -------------------------------------------------------------------------------- 1 | import { Interfaces } from './Interfaces'; 2 | import {Command} from 'commander'; 3 | import { readdirSync } from 'fs'; 4 | import * as path from 'path'; 5 | 6 | export function getClassName(name) { 7 | return `${name[0].toUpperCase()}${name.slice(1, name.length)}`; 8 | } 9 | 10 | export interface IContext {[key: string]: Interfaces.IBuilderModule} 11 | 12 | export function loadModules(env: Interfaces.IEnv, context:IContext): IContext { 13 | let ls = readdirSync(path.join(__dirname, 'modules')); 14 | 15 | for(const i in ls) { 16 | 17 | try { 18 | let mod = require(path.join(__dirname, 'modules', ls[i])); 19 | if(ls[i].slice(ls[i].length-3) !== '.js' && ls[i].slice(ls[i].length-3) !== '.ts') { 20 | continue; 21 | } 22 | let moduleClassName = getClassName(ls[i].slice(0, ls[i].length-3)); 23 | let mod_instance: Interfaces.IBuilderModule = new mod[moduleClassName](); 24 | 25 | if(env.builder.debug) { 26 | console.log(`loading '${mod_instance.constructor.name}' module ...`); 27 | } 28 | 29 | //@ts-ignore 30 | let newContext:Interfaces.IContext = {commander: new Command()}; 31 | let returnCode = mod_instance.installModule(env, newContext); 32 | 33 | if(returnCode !== 0) { 34 | console.warn(`'${mod_instance.constructor.name}' module exit with code: ${returnCode}`); 35 | }else { 36 | context[mod_instance.constructor.name] = mod_instance; 37 | //@ts-ignore 38 | context[mod_instance.constructor.name]._ = newContext; 39 | } 40 | 41 | } catch (error) { 42 | console.log('failed to load modules', error.message); 43 | process.exit(); 44 | } 45 | } 46 | return context; 47 | } 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /src/bin.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import {Command} from 'commander'; 4 | import {Interfaces} from './Interfaces'; 5 | import * as path from 'path'; 6 | // import {IContext, loadModules, getClassName} from './loadModules'; 7 | import * as fs from 'fs'; 8 | import * as inquirer from "inquirer"; 9 | import {Webview} from './modules/webview'; 10 | import {StaticApp} from './modules/static_app'; 11 | 12 | import {ReactApp} from "./modules/react"; 13 | 14 | import {symlink} from "fs"; 15 | 16 | const pkg = require('./package.json'); 17 | 18 | const project: Interfaces.IProject = { 19 | dir: process.cwd(), 20 | name: 'myapp', 21 | type: 'webview' 22 | }; 23 | 24 | const builder: Interfaces.IBuilder = { 25 | dir: __dirname, 26 | commander: new Command(), 27 | cache: path.join(process.env.HOME || process.env.USERPROFILE || process.env.HOMEPATH, '.androidjs', 'cache'), 28 | debug: false 29 | }; 30 | 31 | const env: Interfaces.IEnv = { 32 | force: false, 33 | release: false, 34 | project, 35 | builder 36 | }; 37 | 38 | // create cache folder 39 | if (!fs.existsSync(path.join(env.builder.cache, '..'))) { 40 | try { 41 | fs.mkdirSync(path.join(env.builder.cache, '..')); 42 | fs.mkdirSync(env.builder.cache); 43 | } catch (e) { 44 | if (env.builder.debug) { 45 | console.warn(e.message); 46 | } 47 | } 48 | } 49 | 50 | export interface IContext { 51 | [key: string]: typeof Webview | typeof ReactApp 52 | } 53 | 54 | const context: IContext = { 55 | webview: Webview, 56 | static: StaticApp, 57 | 'react-native': ReactApp 58 | }; 59 | 60 | let commander = new Command(); 61 | commander.version(pkg.version, '-v, --version') 62 | .description(`Android-Js Builder: ${pkg.version}`, {}); 63 | 64 | 65 | commander 66 | .command('init') 67 | .description('Create new project') 68 | .option('-f, --force', 'Force to download the required modules and examples') 69 | .option('-d, --debug', 'Enable debug') 70 | .action((args) => { 71 | let ans = inquirer.prompt(questions); 72 | ans.then(data=> { 73 | //@ts-ignore 74 | const {APPNAME, APPTYPE} = data; 75 | env.project.name = APPNAME; 76 | env.project.type = APPTYPE; 77 | env.force = args.forceBuild ? true : false; 78 | env.builder.debug = args.debug ? true : false; 79 | 80 | 81 | 82 | 83 | // load module 84 | if(context.hasOwnProperty(APPTYPE)){ 85 | let mod = new context[APPTYPE](); 86 | mod.installModule(env, {}); 87 | mod.create(); 88 | } 89 | }); 90 | }); 91 | 92 | commander 93 | .command('build') 94 | .alias('b') 95 | .option('-f, --force', 'Force to download sdk and build tools') 96 | .option('-d, --debug', 'Enable debug') 97 | .option('--release', 'Generate apk in release mode') 98 | .description('Build project') 99 | .action((args) => { 100 | if(fs.existsSync(path.join(env.project.dir, 'package.json'))){ 101 | let _package = require(path.join(env.project.dir, 'package.json')); 102 | env.force = args.force ? true : false; 103 | env.release = args.release ? true : false; 104 | env.builder.debug = args.debug ? true : false; 105 | // check for the project type 106 | if(context.hasOwnProperty(_package['project-type'])){ 107 | let mod = new context[_package['project-type']](); 108 | mod.installModule(env, {}); 109 | mod.build(); 110 | }else { 111 | console.log("Invalid project type:", _package['project-type']); 112 | } 113 | } 114 | else { 115 | console.log('can not find package.json'); 116 | process.exit(); 117 | } 118 | }); 119 | 120 | commander 121 | .command('update') 122 | .alias('u') 123 | .description('Update module') 124 | .action((args) => { 125 | let mod = new context['webview'](); 126 | mod.installModule(env, {}); 127 | // @ts-ignore 128 | mod.downloadSDK((error)=>{ 129 | if(error){ 130 | console.log("error:", error) 131 | }else { 132 | console.log("Update complete"); 133 | } 134 | }, true); 135 | }); 136 | 137 | 138 | commander.on('command:*', function () { 139 | commander.help(); 140 | }); 141 | 142 | if(process.argv.length == 2){ 143 | commander.help(); 144 | } 145 | 146 | // commander.on('--help', function () { 147 | // for (const key in context) { 148 | // loadModules(env, context); 149 | // context[key]._.commander.help(); 150 | // } 151 | // }); 152 | 153 | const questions = [ 154 | { 155 | name: "APPNAME", 156 | type: "input", 157 | message: "Application name:" 158 | }, 159 | { 160 | type: "list", 161 | name: "APPTYPE", 162 | message: "Project type:", 163 | choices: [ 164 | // chalk.yellow("React"), 165 | // chalk.green("Flutter"), 166 | "webview", 167 | "static", 168 | // "react-native" 169 | ], 170 | // filter: function (val) { 171 | // return val.split(".")[1]; 172 | // } 173 | } 174 | ]; 175 | 176 | 177 | commander.parse(process.argv); 178 | // env.builder.commander.parse(process.argv); 179 | // context.Webview.create(); 180 | // context.Webview.build(); 181 | -------------------------------------------------------------------------------- /src/modules/Html/ManifestBuilder.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import {Interfaces} from "../../Interfaces"; 3 | import IEnv = Interfaces.IEnv; 4 | import IDeepLink = Interfaces.IDeepLink; 5 | 6 | let header = `\n`; 7 | 8 | class Node { 9 | name: string = ''; 10 | keys: {[key:string]: string}; 11 | children: Array = []; 12 | constructor({name, keys={}}) { 13 | this.name = name; 14 | this.keys = {...keys}; 15 | } 16 | render({padding, paddingValue}): string { 17 | let data = ''; 18 | data += ' '.repeat(padding) + `<${this.name} `; 19 | for(const key in this.keys) { 20 | data += `${key}="${this.keys[key]}" `; 21 | } 22 | if(this.children.length === 0) { 23 | data += `/>\n`; 24 | }else{ 25 | data += `>\n`; 26 | } 27 | for(const i in this.children){ 28 | data += this.children[i].render({padding:padding+paddingValue, paddingValue}); 29 | } 30 | if(this.children.length !== 0) { 31 | data += ' '.repeat(padding) + `\n` 32 | } 33 | return data; 34 | } 35 | } 36 | 37 | function createPermission(permission){ 38 | return new Node({ 39 | name: 'uses-permission', 40 | keys: { 41 | 'android:name': permission 42 | } 43 | }); 44 | } 45 | 46 | function createDeepLink(deep_link:IDeepLink): Node { 47 | let filter = new Node({name: 'intent-filter'}); 48 | let action = new Node({ 49 | name: 'action', 50 | keys: { 51 | 'android:name': 'android.intent.action.VIEW' 52 | } 53 | }); 54 | let data = new Node({ 55 | name: 'data', 56 | keys: { 57 | 'android:scheme': deep_link.scheme, 58 | 'android:host': deep_link.host 59 | } 60 | }); 61 | let category1 = new Node({ 62 | name: 'category', 63 | keys: { 64 | 'android:name': 'android.intent.category.DEFAULT' 65 | } 66 | }); 67 | let category2 = new Node({ 68 | name: 'category', 69 | keys: { 70 | 'android:name': 'android.intent.category.BROWSABLE' 71 | } 72 | }); 73 | filter.children.push(action); 74 | filter.children.push(data); 75 | filter.children.push(category1); 76 | filter.children.push(category2); 77 | return filter; 78 | } 79 | 80 | export function getManifest(env: IEnv, args, permissions: Array, deep_links: Array, screenOrientation: String = null): string { 81 | let package_name = env.project.package["package-name"]; 82 | let env_manifist = args.manifist; 83 | 84 | const sdkPath = path.join(env.builder.cache, args.sdk.repo); 85 | 86 | if(!package_name){ 87 | console.log("can not find package-name"); 88 | process.exit(); 89 | } 90 | let manifest = new Node({ 91 | name: 'manifest', 92 | keys: { 93 | 'xmlns:android': "http://schemas.android.com/apk/res/android", 94 | 'package':`com.androidjs.${package_name}`, 95 | platformBuildVersionCode: env_manifist.platformBuildVersionCode, 96 | platformBuildVersionName: env_manifist.platformBuildVersionName 97 | } 98 | }); 99 | 100 | let application = new Node({ 101 | name: 'application', 102 | keys: env_manifist.application 103 | }); 104 | 105 | let activity = new Node({ 106 | name: 'activity', 107 | keys: env_manifist.activity 108 | }); 109 | 110 | // required for Android 12+ 111 | activity.keys['android:exported'] = "true"; 112 | 113 | if(screenOrientation !== null) { 114 | // @ts-ignore 115 | activity.keys['android:screenOrientation'] = screenOrientation; 116 | } 117 | 118 | let intent_filter = new Node({name: 'intent-filter'}); 119 | intent_filter.children.push(new Node({ 120 | name: 'action', 121 | keys: { 122 | 'android:name':"android.intent.action.MAIN" 123 | } 124 | })); 125 | intent_filter.children.push(new Node({ 126 | name: 'category', 127 | keys: { 128 | 'android:name':"android.intent.category.LAUNCHER" 129 | } 130 | })); 131 | 132 | manifest.children.push(application); 133 | application.children.push(activity); 134 | activity.children.push(intent_filter); 135 | 136 | 137 | for(const i in permissions) { 138 | manifest.children.push(createPermission(permissions[i])); 139 | if(env.builder.debug)console.log('Adding:', permissions[i]); 140 | } 141 | 142 | for(const i in deep_links){ 143 | let deepLink = createDeepLink(deep_links[i]); 144 | activity.children.push(deepLink) 145 | } 146 | if(env.builder.debug) { 147 | console.log("Built AndroidManifest.xml") 148 | console.log(" platformBuildVersionCode:", env_manifist.platformBuildVersionCode) 149 | console.log(" platformBuildVersionName:", env_manifist.platformBuildVersionName) 150 | } 151 | return header + manifest.render({padding:0, paddingValue: 4}); 152 | } 153 | -------------------------------------------------------------------------------- /src/modules/Html/ProgressBar.ts: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk'); 2 | 3 | export class LoadingBar{ 4 | isRunning: boolean; 5 | loop: any; 6 | interval: number; 7 | currentIndex: number; 8 | bar_length: number; 9 | fill: string; 10 | empty: string; 11 | flip: number; 12 | success: string; 13 | failed: string; 14 | chunksDownloaded: string; 15 | message: string; 16 | constructor() { 17 | this.interval = 100; 18 | this.currentIndex = 0; 19 | this.fill = '█'; 20 | this.empty = '⣿'; 21 | this.bar_length = 50; 22 | this.loop = null; 23 | this.flip = 1; 24 | this.chunksDownloaded="0"; 25 | this.isRunning = false; 26 | this.success = ' √'; 27 | this.failed = ' X'; 28 | this.message = ''; 29 | } 30 | reset() { 31 | this.constructor(); 32 | } 33 | next() { 34 | this.clear(); 35 | this.currentIndex += this.flip; 36 | if(this.currentIndex === this.bar_length || this.currentIndex === 0){ 37 | this.flip *= -1; 38 | } 39 | let _left = this.currentIndex - 1; 40 | _left = _left < 0 ? 0 : _left; 41 | let _right = this.bar_length - _left - 1; 42 | process.stdout.write(`${this.message}${this.chunksDownloaded} :` + chalk.green(` ${this.empty.repeat(_left)}${this.fill}${this.empty.repeat(_right)}`)); 43 | } 44 | clear(){ 45 | //@ts-ignore 46 | process.stdout.clearLine(); 47 | //@ts-ignore 48 | process.stdout.cursorTo(0); 49 | } 50 | 51 | start(speed?:number) { 52 | this.isRunning = true; 53 | this.loop = setInterval(()=>{ 54 | this.next(); 55 | }, speed || this.interval); 56 | } 57 | stop(error:{[key: string]: string| number}=null) { 58 | if(this.isRunning){ 59 | this.isRunning = false; 60 | if(this.loop !==null) { 61 | clearInterval(this.loop); 62 | this.clear(); 63 | if(error===null) { 64 | console.log(`${this.message}${this.chunksDownloaded} :` + chalk.green(this.fill.repeat(this.bar_length)+this.success)); 65 | } else { 66 | this.clear(); 67 | console.log(chalk.red(error.message)); 68 | } 69 | } 70 | } 71 | } 72 | } 73 | 74 | 75 | 76 | export class ProgressBar { 77 | total: number; 78 | current: number; 79 | bar_length: number; 80 | bar_fill: string = "⣿"; 81 | bar_current: string = "⣦"; 82 | barr_empty: string = "⣀"; 83 | constructor(total?:number) { 84 | this.total = total || null; 85 | this.current = 0; 86 | this.bar_length = process.stdout.columns - 30; 87 | // this.bar_length = 50; 88 | } 89 | next(progress) { 90 | let _left:number = parseInt(((progress * this.bar_length) / this.total).toFixed(0)); 91 | let _right = this.bar_length - _left; 92 | let _p = (progress * 100) / this.total; 93 | let _in_progress_code = _p === 100 ? this.bar_fill : this.bar_current; 94 | 95 | _left = _left > 1 ? _left : 1; 96 | 97 | this.clearLine(); 98 | const _str = ` ${_p}% : ${chalk.green(this.bar_fill.repeat(_left-1)+_in_progress_code)}${ this.barr_empty.repeat(_right)}`; 99 | process.stdout.write(_str); 100 | // console.log(_left, _right) 101 | } 102 | clearLine() { 103 | //@ts-ignore 104 | process.stdout.clearLine(); 105 | //@ts-ignore 106 | process.stdout.cursorTo(0); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/modules/Html/checkForUpdates.ts: -------------------------------------------------------------------------------- 1 | import {Interfaces} from "../../Interfaces"; 2 | 3 | const dns = require('dns'); 4 | import {lsGit, getDownloadLink, getFileDownloadLink} from '../../GitListDir'; 5 | import {downloadGithubFile} from './downloadGithubFile'; 6 | import * as path from 'path'; 7 | import * as fs from 'fs'; 8 | const semver = require('semver'); 9 | 10 | 11 | let isSearchComplete = false; 12 | let isAvailable = false; 13 | let _hostname = ''; 14 | let _service = ''; 15 | 16 | interface IUpdate { 17 | packageName: string 18 | oldVersion: string 19 | newVersion: string 20 | } 21 | 22 | 23 | let msg:string = ''; 24 | 25 | 26 | dns.lookupService('8.8.8.8', 53, function(err, hostname, service){ 27 | isSearchComplete = true; 28 | if(err) { 29 | 30 | }else { 31 | _hostname = hostname; 32 | _service = service; 33 | if(_hostname.length>0 && _service.length>0){ 34 | isAvailable = true; 35 | } 36 | } 37 | // google-public-dns-a.google.com domain 38 | }); 39 | 40 | 41 | function getSdkVersion(env, callback) { 42 | let tempFilePath = path.join(env.builder.cache, 'TEMP-sdk-config.json'); 43 | let sdkConfigFileLink = getFileDownloadLink(env.sdk.user, env.sdk.repo, 'config.json'); 44 | let _sdkConfigFileLink:Interfaces.GithubFileLink = { 45 | user: env.sdk.user, 46 | repo: env.sdk.repo, 47 | file: 'config.json', 48 | dir: '' 49 | }; 50 | 51 | downloadGithubFile(_sdkConfigFileLink, tempFilePath, ()=>{ 52 | try { 53 | let sdkConfig = require(tempFilePath); 54 | callback(sdkConfig.version); 55 | }catch (e) { 56 | ///... 57 | } 58 | }, false); 59 | } 60 | 61 | export function getUpdate(env) { 62 | return new Promise(resolve => { 63 | let loopCount = 0; 64 | let loop = setInterval(()=>{ 65 | // check for 5 seconds for network connectivity 66 | if(++loopCount === 5) { 67 | clearInterval(loop); 68 | } 69 | if(isSearchComplete && (true || clearInterval(loop))) { 70 | if(isAvailable){ 71 | try{ 72 | let oldSdk = require(path.join(env.builder.cache, env.sdk.repo, 'config.json')); 73 | getSdkVersion(env, sdkVersion => { 74 | if(semver.lt(oldSdk.version, sdkVersion)){ 75 | msg = `Androidjs-sdk: ${sdkVersion} available`; 76 | msg += `Update using $androidjs u`; 77 | 78 | } else { 79 | ///... 80 | } 81 | }); 82 | }catch (e) { 83 | ///... 84 | } 85 | } 86 | }else { 87 | /// if search is not complete then do nothing for 5 seconds 88 | } 89 | }, 1000); 90 | 91 | 92 | 93 | }); 94 | } 95 | 96 | export function getUpdateMessage() { 97 | if(msg.length > 1){ 98 | console.log(msg); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/modules/Html/createIfNotExist.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs-extra'; 2 | 3 | export function createIfNotExist(path) { 4 | if(!fs.existsSync(path)){ 5 | fs.mkdirs(path); 6 | } 7 | return path; 8 | } 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/modules/Html/create_intent_filter.ts: -------------------------------------------------------------------------------- 1 | export function getIntentFilter(filter_action: string, filter_category) { 2 | return { 3 | "type": "element", 4 | "name": "intent-filter", 5 | "childNodes": [ 6 | { 7 | "type": "text", 8 | "value": "\n " 9 | }, 10 | { 11 | "type": "element", 12 | "name": "action", 13 | "childNodes": [], 14 | "attributes": { 15 | "android:name": filter_action 16 | }, 17 | "selfClosing": true, 18 | "openTag": "", 19 | "closeTag": "" 20 | }, 21 | { 22 | "type": "text", 23 | "value": "\n " 24 | }, 25 | { 26 | "type": "element", 27 | "name": "category", 28 | "childNodes": [], 29 | "attributes": { 30 | "android:name": filter_category 31 | }, 32 | "selfClosing": true, 33 | "openTag": "", 34 | "closeTag": "" 35 | }, 36 | { 37 | "type": "text", 38 | "value": "\n " 39 | } 40 | ], 41 | "attributes": {}, 42 | "selfClosing": false, 43 | "openTag": "", 44 | "closeTag": "" 45 | }; 46 | } 47 | 48 | -------------------------------------------------------------------------------- /src/modules/Html/downloadGithubFile.ts: -------------------------------------------------------------------------------- 1 | import {LoadingBar} from './ProgressBar'; 2 | import * as request from 'request'; 3 | import {lsGit, getFileDownloadLink} from '../../GitListDir'; 4 | import * as fs from 'fs-extra'; 5 | import {Interfaces} from "../../Interfaces"; 6 | 7 | 8 | 9 | export function downloadGithubFile(githubFileLink: Interfaces.GithubFileLink, file, callback, showProgress?:boolean) { 10 | let downloadLink:string = ''; 11 | if(githubFileLink.dir && githubFileLink.dir.length>0) { 12 | downloadLink = getFileDownloadLink(githubFileLink.user, githubFileLink.repo, githubFileLink.dir, githubFileLink.file); 13 | }else { 14 | downloadLink = getFileDownloadLink(githubFileLink.user, githubFileLink.repo, githubFileLink.file); 15 | } 16 | 17 | let writeStream = fs.createWriteStream(file); 18 | let progress = new LoadingBar(); 19 | request.get(downloadLink) 20 | .on('response', (res) =>{ 21 | if(res.statusCode === 200){ 22 | if(showProgress) { 23 | progress.start(); 24 | } 25 | } 26 | }) 27 | .on('data', (chunk)=> { 28 | progress.chunksDownloaded += chunk.length; 29 | }) 30 | .on('error', (error)=> { 31 | if(showProgress) { 32 | progress.stop(); 33 | } 34 | callback(error); 35 | }) 36 | .on('end', (code)=> { 37 | if(showProgress) { 38 | progress.stop(); 39 | } 40 | callback(); 41 | }) 42 | 43 | .pipe(writeStream); 44 | } 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/modules/Html/downloadGithubRepo.ts: -------------------------------------------------------------------------------- 1 | import {LoadingBar} from './ProgressBar'; 2 | import * as request from 'request'; 3 | import {lsGit, getDownloadLink, getFileDownloadLink} from '../../GitListDir'; 4 | import * as fs from 'fs-extra'; 5 | import {Interfaces} from "../../Interfaces"; 6 | 7 | 8 | export function downloadGithubRepo(githubReplLink: Interfaces.IGithubRepoLink, file, callback) { 9 | let downloadLink = getDownloadLink(githubReplLink.user, githubReplLink.repo); 10 | let writeStream = fs.createWriteStream(file); 11 | let progress = new LoadingBar(); 12 | request.get(downloadLink) 13 | .on('response', (res) =>{ 14 | if(res.statusCode === 200){ 15 | progress.start(); 16 | } 17 | }) 18 | .on('data', (chunk)=> { 19 | progress.chunksDownloaded += chunk.length; 20 | }) 21 | .on('end', (code)=> { 22 | progress.stop(); 23 | callback(); 24 | }) 25 | .on('error', (error)=> { 26 | progress.stop(); 27 | console.log("ERROR:", error); 28 | callback(error); 29 | }) 30 | .pipe(writeStream); 31 | } 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/modules/Html/downloadSDK.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import * as fs from 'fs'; 4 | import * as request from 'request'; 5 | import {LoadingBar} from './ProgressBar'; 6 | 7 | export function downloadSDK(env, url, to, callback: Function) { 8 | // process.stdout.write(`Downloading ${link}`); 9 | let fileStream = fs.createWriteStream(to); 10 | 11 | let loading = new LoadingBar(); 12 | let receivedBytes = 0; 13 | request 14 | .get({url: url, headers: {'User-Agent': 'request'}}) 15 | .on('response', response => { 16 | if(response.statusCode === 200) { 17 | loading.start(); 18 | // bar.total = parseInt(response.headers['content-length']); 19 | // console.log("H:",parseInt(response.headers['content-length'])); 20 | } else { 21 | console.log("error"); 22 | } 23 | }) 24 | .on('data', (chunk) => { 25 | receivedBytes += chunk.length; 26 | }) 27 | .on('end', (code)=>{ 28 | loading.stop(); 29 | }) 30 | .on('error', (err) => { 31 | // @ts-ignore 32 | fs.unlink(local_file); 33 | loading.stop(); 34 | }) 35 | .pipe(fileStream); 36 | // callback(); 37 | } 38 | -------------------------------------------------------------------------------- /src/modules/Html/getJavaVersion.ts: -------------------------------------------------------------------------------- 1 | const findJava = require('find-java-home'); 2 | 3 | 4 | 5 | export function javaVersion(callback) { 6 | findJava(callback); 7 | } 8 | -------------------------------------------------------------------------------- /src/modules/Html/updateApkVersion.ts: -------------------------------------------------------------------------------- 1 | import {Interfaces} from "../../Interfaces"; 2 | import IEnv = Interfaces.IEnv; 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const yaml = require('js-yaml'); 6 | 7 | 8 | var primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199]; 9 | let header = '!!brut.androlib.meta.MetaInfo\n'; 10 | 11 | 12 | function expmod( base, exp, mod ){ 13 | if (exp == 0) return 1; 14 | if (exp % 2 == 0){ 15 | return Math.pow( expmod( base, (exp / 2), mod), 2) % mod; 16 | } 17 | else { 18 | return (base * expmod( base, (exp - 1), mod)) % mod; 19 | } 20 | } 21 | 22 | function gethesh(str: string) { 23 | var ans = 0; 24 | for(var index=0; index < str.length; index++) { 25 | ans = (ans +((str.charCodeAt(index)% 97) * expmod(primes[index], index, 97)) % 97) % 97; 26 | } 27 | return ans.toString(); 28 | } 29 | 30 | function pad(str, len=2) { 31 | var ans = str; 32 | for(var i=str.length; i < len; i++) ans = '0' + ans; 33 | return ans; 34 | } 35 | 36 | export function getAppVersion(env: IEnv, args) { 37 | var versionInfo = { 38 | versionCode: null, 39 | versionName: null 40 | }; 41 | 42 | let pkgVersion = env.project.package.version; 43 | versionInfo.versionName = pkgVersion; 44 | 45 | var arr = pkgVersion.split('-'); 46 | // @ts-ignore 47 | arr[0] = arr[0].split('.'); 48 | let versionCode = pad(arr[0][0], 3); 49 | versionCode += pad(arr[0][1]); 50 | versionCode += pad(arr[0][2]); 51 | versionCode += pad(gethesh(arr[1]||' ')); 52 | versionInfo.versionCode = parseInt(versionCode); 53 | return versionInfo; 54 | } 55 | 56 | export function updateApkVersion(env: IEnv, args) { 57 | console.log("Setting app version ..."); 58 | let ymlFile = path.join(env.builder.cache, args.sdk.repo, 'apktool.yml'); 59 | 60 | try { 61 | let fileContents = fs.readFileSync(ymlFile, 'utf8'); 62 | 63 | let data = yaml.safeLoadAll(fileContents.substring(header.length-1, fileContents.length)); 64 | console.log("Version Code:", data[0].versionInfo.versionCode); 65 | console.log("Version Name:", data[0].versionInfo.versionName); 66 | let version = getAppVersion(env, args); 67 | data[0].versionInfo = version; 68 | 69 | let yamlStr = yaml.safeDump(data[0]); 70 | 71 | fs.writeFileSync(ymlFile, header + yamlStr, 'utf8'); 72 | } catch (e) { 73 | console.log(e); 74 | process.exit(); 75 | } 76 | } 77 | 78 | -------------------------------------------------------------------------------- /src/modules/Html/updateAppName.ts: -------------------------------------------------------------------------------- 1 | import {Interfaces} from "../../Interfaces"; 2 | import IEnv = Interfaces.IEnv; 3 | 4 | const path = require('path'); 5 | const fs = require('fs'); 6 | const chalk = require("chalk"); 7 | const xml2js = require('xml2js'); 8 | 9 | export function updateAppName(env: IEnv, args) { 10 | console.log('updating app name...'); 11 | 12 | const sdkPath = path.join(env.builder.cache, args.sdk.repo); 13 | 14 | try { 15 | 16 | let pkg = env.project.package; 17 | let stringFilePath = path.join(sdkPath, 'res', 'values', 'strings.xml'); 18 | let data = fs.readFileSync(stringFilePath).toString(); 19 | let index = data.indexOf('app_name'); 20 | let startIndex = 0, lastIndex = 0; 21 | while (startIndex == 0) { 22 | index++; 23 | if (data[index] === '>') { 24 | startIndex = index + 1; 25 | } 26 | } 27 | 28 | while (lastIndex == 0) { 29 | index++; 30 | if (data[index] === '<') { 31 | lastIndex = index; 32 | } 33 | } 34 | 35 | data = data.replace('>' + data.slice(startIndex, lastIndex) + '<', `>${pkg["app-name"]}<`); 36 | fs.writeFileSync(stringFilePath, data); 37 | }catch (e) { 38 | console.log(`Failed to update app name`); 39 | process.exit(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/modules/Html/updateAppTheme.ts: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const xml2js = require('xml2js'); 3 | const path = require('path'); 4 | 5 | export function updateTheme(env, callback) { 6 | console.log(`updating theme ...`); 7 | let pkg = require(path.join(env.project.dir, 'package.json')); 8 | let theme = pkg.theme; 9 | if (theme === undefined) { 10 | console.log('theme attribute not found in package.json'); 11 | callback(); 12 | } else { 13 | // open res/values/styles.xml 14 | const stylesXML = path.join(env.builder.cache, env.sdk.repo, 'res', 'values', 'styles.xml'); 15 | // open res/values/colors.xml 16 | const colorsXML = path.join(env.builder.cache, env.sdk.repo, 'res', 'values', 'colors.xml'); 17 | 18 | let parser = new xml2js.Parser(); 19 | let builder = new xml2js.Builder(); 20 | let stylesData = fs.readFileSync(stylesXML); 21 | let colorsData = fs.readFileSync(colorsXML); 22 | // colors.xml parse and modify 23 | parser.parseString(colorsData, function (err, result) { 24 | if (err) { 25 | console.log(`Failed to update theme, colors.xml error code : ${err}`); 26 | process.exit(); 27 | } 28 | try { 29 | let res = result.resources.color; 30 | for (let i = 0; i < res.length; i++) { 31 | if (res[i].$.name == "colorAccent" && pkg.theme.colorAccent) 32 | res[i]._ = pkg.theme.colorAccent; // colorAccent 33 | if (res[i].$.name == "colorPrimary" && pkg.theme.colorPrimary) 34 | res[i]._ = pkg.theme.colorPrimary; // colorPrimary 35 | if (res[i].$.name == "colorPrimaryDark" && pkg.theme.colorPrimaryDark) 36 | res[i]._ = pkg.theme.colorPrimaryDark // colorPrimaryDark 37 | } 38 | let xml_color = builder.buildObject(result); 39 | fs.writeFileSync(colorsXML, xml_color); 40 | if (env.debug) { 41 | console.log('Theme colors.xml Updated'); 42 | } 43 | } 44 | catch (e) { 45 | console.log(`Failed to update theme, colors.xml error code : ${e}`); 46 | } 47 | }); 48 | 49 | // styles.xml parse and modify 50 | parser.parseString(stylesData, function (err, result) { 51 | if (err) { 52 | console.log(`Failed to update theme, styles.xml error code : ${err}`); 53 | process.exit(); 54 | } 55 | try { 56 | // Updating theme 57 | let thm = result.resources.style[6].item; 58 | thm[3] = {_: 'true', '$': {name: 'android:windowNoTitle'}}; 59 | thm[4] = {_: 'true', '$': {name: 'android:windowFullscreen'}}; 60 | // thm[5] = {_: 'false', '$': {name: 'android:windowActionBar'}}; 61 | if (pkg.theme.fullScreen) { 62 | thm[3]._ = true; //{ _: 'true', '$': { name: 'android:windowNoTitle' } }; 63 | thm[4]._ = true; //{ _: 'true', '$': { name: 'android:windowFullscreen' } }; 64 | } else { 65 | thm[3]._ = true; 66 | thm[4]._ = false; 67 | } 68 | let xml_styles = builder.buildObject(result); 69 | fs.writeFileSync(stylesXML, xml_styles); 70 | if (env.debug) { 71 | console.log('Theme styles.xml Updated'); 72 | } 73 | callback(); 74 | } 75 | catch (e) { 76 | console.log(`Failed to update theme, styles.xml error code : ${e}`); 77 | callback(e); 78 | } 79 | }); 80 | } 81 | } 82 | 83 | 84 | -------------------------------------------------------------------------------- /src/modules/Html/updateIcon.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as path from 'path'; 3 | import {Interfaces} from "../../Interfaces"; 4 | 5 | export function updateIcon(env: Interfaces.IEnv, args) { 6 | const sdkPath = path.join(env.builder.cache, args.sdk.repo); 7 | 8 | let iconPath = undefined; 9 | try{ 10 | iconPath = require(path.join(env.project.dir, 'package.json')).icon; 11 | if(!iconPath){ 12 | console.log("can not find icon path in package.json"); 13 | process.exit(); 14 | } 15 | if(iconPath[0] === '.') { 16 | iconPath = path.join(env.project.dir, iconPath); 17 | } 18 | }catch (e) { 19 | console.log('failed to update icon'); 20 | process.exit(); 21 | } 22 | 23 | 24 | let icon_paths = []; 25 | icon_paths.push(path.join(sdkPath, 'res', 'mipmap-hdpi', 'ic_launcher.png')); 26 | icon_paths.push(path.join(sdkPath, 'res', 'mipmap-hdpi', 'ic_launcher_round.png')); 27 | 28 | icon_paths.push(path.join(sdkPath, 'res', 'mipmap-mdpi', 'ic_launcher.png')); 29 | icon_paths.push(path.join(sdkPath, 'res', 'mipmap-mdpi', 'ic_launcher_round.png')); 30 | 31 | icon_paths.push(path.join(sdkPath, 'res', 'mipmap-xhdpi', 'ic_launcher.png')); 32 | icon_paths.push(path.join(sdkPath, 'res', 'mipmap-xhdpi', 'ic_launcher_round.png')); 33 | 34 | icon_paths.push(path.join(sdkPath, 'res', 'mipmap-xxhdpi', 'ic_launcher.png')); 35 | icon_paths.push(path.join(sdkPath, 'res', 'mipmap-xxhdpi', 'ic_launcher_round.png')); 36 | 37 | icon_paths.push(path.join(sdkPath, 'res', 'mipmap-xxxhdpi', 'ic_launcher.png')); 38 | icon_paths.push(path.join(sdkPath, 'res', 'mipmap-xxxhdpi', 'ic_launcher_round.png')); 39 | 40 | for(const i in icon_paths){ 41 | fs.writeFileSync(icon_paths[i], fs.readFileSync(iconPath)); 42 | } 43 | 44 | } 45 | 46 | -------------------------------------------------------------------------------- /src/modules/Html/updateSdk.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | const admZip = require('adm-zip'); 3 | import {getDownloadLink} from "../../GitListDir"; 4 | import {downloadGithubRepo} from "./downloadGithubRepo"; 5 | import * as fs from 'fs-extra'; 6 | 7 | export function updateSdk(env, callback?:Function) { 8 | 9 | const sdkZip = path.join(env.builder.cache, env.sdk.repo + '.zip'); 10 | const sdkFolder = path.join(env.builder.cache); 11 | 12 | console.log("Downloading Androidjs-SDK:", getDownloadLink(env.sdk.user, env.sdk.repo)); 13 | downloadGithubRepo(env.sdk, sdkZip, (error) => { 14 | if (error) { 15 | console.log('Failed to download sdk'); 16 | process.exit(); 17 | } else { 18 | try { 19 | let zip = new admZip(sdkZip); 20 | zip.extractEntryTo(env.sdk.repo + '-master/', sdkFolder, true, true); 21 | fs.removeSync(path.join(sdkFolder, env.sdk.repo)); 22 | fs.renameSync(path.join(sdkFolder, env.sdk.repo + '-master'), path.join(sdkFolder, env.sdk.repo)); 23 | if(callback){ 24 | callback(); 25 | } 26 | } catch (e) { 27 | console.log("Failed to extract sdk"); 28 | process.exit(); 29 | } 30 | } 31 | }); 32 | } 33 | 34 | -------------------------------------------------------------------------------- /src/modules/Html/update_manifest_file.ts: -------------------------------------------------------------------------------- 1 | import {Interfaces} from "../../Interfaces"; 2 | import * as path from "path"; 3 | import * as fs from 'fs'; 4 | import { parse, render } from 'modify-xml'; 5 | 6 | export function update(env: Interfaces.IEnv): number { 7 | try { 8 | 9 | // loading required files 10 | const pkg = require(path.join(env.project.dir, 'package.json')); 11 | const androidManifestPath = path.join(env.builder.cache, 'AndroidManifest.xml'); 12 | 13 | 14 | let app_name = pkg['app-name']; 15 | let package_name = pkg['package-name']; 16 | let permissions: Array = pkg['permission']; 17 | 18 | if (package_name === undefined || app_name === undefined) { 19 | console.warn("'app-name' or 'package-name' not define in package.json"); 20 | process.exit(); 21 | } 22 | /// Debug 23 | if (env.builder.debug) { 24 | console.log(`App name: ${app_name}`); 25 | } 26 | 27 | // let parser = new xml2js.Parser({}); 28 | // let builder = new xml2js.Builder(); 29 | 30 | let androidManifest = fs.readFileSync(androidManifestPath); 31 | // let parsedManifest = parser.parseString(); 32 | 33 | // updating package name 34 | let data = androidManifest.toString().replace('"com.android.js.webview"', `"com.android.js.${package_name}"`); 35 | const document = parse(data); 36 | console.log('Package name:', document['childNodes'][1]['attributes']['package']); 37 | 38 | 39 | // set user permissions 40 | for(const i in permissions) { 41 | let permission = permissions[i]; 42 | if(env.builder.debug){ 43 | console.log(`Adding '${permission.slice(19)}' permission`); 44 | } 45 | document['childNodes'][1]['childNodes'].push({ 46 | "type": "element", 47 | "name": "uses-permission", 48 | "childNodes": [], 49 | "attributes": { 50 | "android:name": permission 51 | }, 52 | "selfClosing": true, 53 | "openTag": ``, 54 | "closeTag": `` 55 | }); 56 | document['childNodes'][1]['childNodes'].push({ 57 | "type": "text", 58 | "value": "\n " 59 | }); 60 | } 61 | 62 | // set intent filter 63 | 64 | 65 | fs.writeFileSync('../../androidManifest.json', JSON.stringify(document, null, 4)); 66 | 67 | 68 | // saving back to sdk folder 69 | const result = render(document, { indent: ' ' }); 70 | fs.writeFileSync(path.join(env.builder.cache, 'sdk', 'AndroidManifest.xml'), result); 71 | } 72 | catch (e) { 73 | console.log(`Failed to update android manifest`, e.message); 74 | process.exit(); 75 | } 76 | return 0; 77 | } 78 | 79 | -------------------------------------------------------------------------------- /src/modules/react.ts: -------------------------------------------------------------------------------- 1 | import {Interfaces} from '../Interfaces'; 2 | 3 | 4 | 5 | export class ReactApp implements Interfaces.IBuilderModule { 6 | constructor() { 7 | console.log("Loading React modules"); 8 | } 9 | 10 | installModule(env: Interfaces.IEnv, context): number { 11 | console.log("Installing React modules"); 12 | return 0; 13 | } 14 | create(): number { 15 | console.log("Creating React project"); 16 | return 0; 17 | } 18 | 19 | build(): number { 20 | console.log("Building React project"); 21 | return 0; 22 | } 23 | } -------------------------------------------------------------------------------- /src/modules/static_app.ts: -------------------------------------------------------------------------------- 1 | import '../Interfaces'; 2 | import {Interfaces} from '../Interfaces'; 3 | import * as path from 'path'; 4 | import {getDownloadLink, getFileDownloadLink} from '../GitListDir'; 5 | 6 | const request = require('request'); 7 | 8 | 9 | import * as fs from 'fs-extra'; 10 | import {spawn} from 'child_process'; 11 | import {getManifest} from './Html/ManifestBuilder'; 12 | import {updateIcon} from './Html/updateIcon'; 13 | import {updateAppName} from './Html/updateAppName'; 14 | import {createIfNotExist} from './Html/createIfNotExist'; 15 | import {downloadGithubFile} from './Html/downloadGithubFile'; 16 | import {downloadGithubRepo} from './Html/downloadGithubRepo'; 17 | import {LoadingBar} from './Html/ProgressBar'; 18 | import {javaVersion} from './Html/getJavaVersion'; 19 | import {getUpdate, getUpdateMessage} from './Html/checkForUpdates'; 20 | import {updateTheme} from "./Html/updateAppTheme"; 21 | import {updateApkVersion} from './Html/updateApkVersion'; 22 | 23 | import {Webview} from './webview'; 24 | 25 | 26 | const chalk = require('chalk'); 27 | 28 | const admZip = require('adm-zip'); 29 | 30 | 31 | /** 32 | * Webview module for creating HTML based Android-Js project 33 | */ 34 | export class StaticApp extends Webview { 35 | env: Interfaces.IEnv; 36 | user: string = 'android-js'; 37 | sdk: Interfaces.IGithubRepoLink = { 38 | user: 'android-js', 39 | repo: 'sdk-static' 40 | }; 41 | example: Interfaces.IGithubRepoLink = { 42 | user: 'android-js', 43 | repo: 'static-app-template' 44 | }; 45 | apk_tool: Interfaces.GithubFileLink = { 46 | user: 'android-js', 47 | repo: 'androidjs-builder', 48 | dir: 'build_tools', 49 | file: 'apktool.jar' 50 | }; 51 | apk_signer: Interfaces.GithubFileLink = { 52 | user: 'android-js', 53 | repo: 'androidjs-builder', 54 | dir: 'build_tools', 55 | file: 'uber-apk-signer-1.0.0.jar' 56 | }; 57 | 58 | // ManifistBuilder args 59 | manifist = { 60 | platformBuildVersionCode : "30", 61 | platformBuildVersionName : "11", 62 | application: { 63 | 'android:allowBackup':"true", 64 | 'android:appComponentFactory':"androidx.core.app.CoreComponentFactory", 65 | 'android:debuggable':"false", 66 | 'android:icon':"@mipmap/ic_launcher", 67 | 'android:label':"@string/app_name", 68 | 'android:roundIcon':"@mipmap/ic_launcher_round", 69 | 'android:supportsRtl':"true", 70 | 'android:theme':"@style/AppTheme", 71 | 'android:usesCleartextTraffic': "true", 72 | 'android:requestLegacyExternalStorage':"true" 73 | }, 74 | activity: { 75 | 'android:configChanges':"keyboard|keyboardHidden|orientation|screenSize", 76 | 'android:name':"com.android.js.staticsdk.MainActivity" 77 | } 78 | } 79 | 80 | // moduld context 81 | context: {}; 82 | 83 | installModule(env: Interfaces.IEnv, context): number { 84 | // init 85 | this.env = env; 86 | this.context = context; 87 | //@ts-ignore 88 | this.context.updateSdk = () => require('./Html/updateSdk').updateSdk(this.env); 89 | //@ts-ignore 90 | env.sdk = this.sdk; 91 | getUpdate(env); 92 | 93 | 94 | console.log("--release=", env.release) 95 | this.manifist.application["android:debuggable"] = (!env.release).toString() 96 | 97 | 98 | // check if sdk folder exist 99 | // if (this.env.builder.debug) { 100 | // try { 101 | // let sdk_config = require(path.join(this.sdk, 'config.json')); 102 | // console.log(`SDK version: ${sdk_config.version}`); 103 | // } catch (e) { 104 | // console.log(`Failed to load: ${path.join(this.sdk, 'config.json')}\n`, e.message); 105 | // } 106 | // } 107 | 108 | // // downloading sdk 109 | // if (!fs.existsSync(this.sdk) || this.env.force) { 110 | // this.downloadSKD(() => { 111 | // //////////////////////////////////////////////// 112 | // // downloading build tools 113 | // // apk-tool 114 | // if (!fs.existsSync(path.join(this.env.builder.cache, this.apk_tool.file))) { 115 | // let apk_tool_download_link = getFileDownloadLink(this.apk_tool.user, this.apk_tool.repo, this.apk_tool.dir, this.apk_tool.file); 116 | // console.log(apk_tool_download_link); 117 | // console.log(`downloading: ${this.apk_tool.file} ...`); 118 | // request.get(apk_tool_download_link).on('error', (e) => { 119 | // console.log(e); 120 | // }).pipe(fs.createWriteStream(path.join(this.env.builder.cache, this.apk_tool.file))); 121 | // } 122 | // 123 | // // apk-signer 124 | // if (!fs.existsSync(path.join(this.env.builder.cache, this.apk_signer.file))) { 125 | // let apk_signer_download_link = getFileDownloadLink(this.apk_signer.user, this.apk_signer.repo, this.apk_signer.dir, this.apk_signer.file); 126 | // console.log(apk_signer_download_link); 127 | // console.log(`downloading: ${this.apk_signer.file} ...`); 128 | // request.get(apk_signer_download_link).on('error', (e) => { 129 | // console.log(e); 130 | // }).pipe(fs.createWriteStream(path.join(this.env.builder.cache, this.apk_signer.file))); 131 | // } 132 | // ////////////////////////////////////// 133 | // }); 134 | // return 0; 135 | // } else { 136 | // // downloading build tools 137 | // // apk-tool 138 | // if (!fs.existsSync(path.join(this.env.builder.cache, this.apk_tool.file))) { 139 | // let apk_tool_download_link = getFileDownloadLink(this.apk_tool.user, this.apk_tool.repo, this.apk_tool.dir, this.apk_tool.file); 140 | // console.log(apk_tool_download_link); 141 | // console.log(`downloading: ${this.apk_tool.file} ...`); 142 | // request.get(apk_tool_download_link).on('error', (e) => { 143 | // console.log(e); 144 | // }).pipe(fs.createWriteStream(path.join(this.env.builder.cache, this.apk_tool.file))); 145 | // } 146 | // 147 | // // apk-signer 148 | // if (!fs.existsSync(path.join(this.env.builder.cache, this.apk_signer.file))) { 149 | // let apk_signer_download_link = getFileDownloadLink(this.apk_signer.user, this.apk_signer.repo, this.apk_signer.dir, this.apk_signer.file); 150 | // console.log(apk_signer_download_link); 151 | // console.log(`downloading: ${this.apk_signer.file} ...`); 152 | // request.get(apk_signer_download_link).on('error', (e) => { 153 | // console.log(e); 154 | // }).pipe(fs.createWriteStream(path.join(this.env.builder.cache, this.apk_signer.file))); 155 | // } 156 | return 0; 157 | // } 158 | } 159 | 160 | /// TODO: 161 | /// - check before update or create new project 162 | /// - update app name 163 | create(): number { 164 | let examplesFolder = createIfNotExist(path.join(this.env.builder.cache, '..', 'examples')); 165 | let exampleFilePath = path.join(examplesFolder, this.example.repo + '.zip'); 166 | let projectPath = path.join(this.env.project.dir, this.env.project.name); 167 | const exampleRepoDownloadLink = getDownloadLink(this.example.user, this.example.repo); 168 | const exampleFolderPath = path.join(examplesFolder, this.example.repo); 169 | 170 | // directory is not empty 171 | if (fs.existsSync(projectPath)) { 172 | console.log(`ERROR: directory is not empty ${projectPath}`); 173 | process.exit(); 174 | } 175 | 176 | // download example 177 | if (!fs.existsSync(exampleFolderPath) || this.env.force) { 178 | console.log('Downloading:', exampleRepoDownloadLink); 179 | 180 | downloadsdk({ 181 | url: getDownloadLink(this.example.user, this.example.repo), 182 | repo: this.example.repo, 183 | targetFolder: path.join( this.env.builder.cache, '..', 'examples'), 184 | targetZip: path.join( this.env.builder.cache, '..', 'examples', this.example.repo + ".zip"), 185 | zipFolder: this.example.repo + '-master/', 186 | retry: 3 187 | }, (error)=>{ 188 | // creating project 189 | 190 | if(error){ 191 | console.log(error); 192 | }else { 193 | if (this.env.builder.debug) 194 | console.log('Creating Project...'); 195 | fs.copySync(exampleFolderPath, projectPath); 196 | this.updatePackageJson(); 197 | console.log(`$cd ${this.env.project.name}`); 198 | console.log(`$npm install`); 199 | console.log(`$npm run start:dev`); 200 | console.log(`$npm run build`); 201 | } 202 | }); 203 | }else { 204 | if (this.env.builder.debug) 205 | console.log('Creating Project...'); 206 | fs.copySync(exampleFolderPath, projectPath); 207 | this.updatePackageJson(); 208 | console.log(chalk.green(` $cd ${this.env.project.name}`)); 209 | console.log(chalk.green(` $npm install`)); 210 | console.log(chalk.green(` $npm run start:dev`)); 211 | console.log(chalk.green(` $npm run build`)); 212 | } 213 | return 0; 214 | } 215 | 216 | 217 | updatePackageJson() { 218 | let projectPath = path.join(this.env.project.dir, this.env.project.name); 219 | 220 | /// generating package.json 221 | const _package = require(path.join(projectPath, 'package.json')); 222 | 223 | if(_package['scripts'] === undefined) _package['scripts'] = {}; 224 | _package.name = this.env.project.name; 225 | _package['app-name'] = this.env.project.name; 226 | _package['project-type'] = this.env.project.type; 227 | _package['project-name'] = this.env.project.name; 228 | _package['scripts']['build'] = 'androidjs build'; 229 | _package["dist-path"] = "./dist"; 230 | // _package["screenOrientation"] = "portrait"; 231 | _package['theme'] = { 232 | "fullScreen": true 233 | }; 234 | fs.writeFileSync(path.join(this.env.project.dir, this.env.project.name, 'package.json'), JSON.stringify(_package, null, 4)); 235 | 236 | } 237 | 238 | downloadSDK(callback, force:boolean=false) { 239 | const sdkZip = path.join(this.env.builder.cache, this.sdk.repo + '.zip'); 240 | const sdkFolder = path.join(this.env.builder.cache, this.sdk.repo); 241 | 242 | 243 | if ( !force && fs.existsSync(sdkFolder)) { 244 | callback(); 245 | } else { 246 | console.log(getDownloadLink(this.sdk.user, this.sdk.repo)) 247 | downloadsdk({ 248 | url: getDownloadLink(this.sdk.user, this.sdk.repo, 'main'), 249 | repo: this.sdk.repo, 250 | targetFolder: this.env.builder.cache, 251 | targetZip: sdkZip, 252 | zipFolder: this.sdk.repo + '-main/', 253 | retry: 4 254 | }, callback); 255 | } 256 | 257 | 258 | // 259 | // if (fs.existsSync(sdkZip)) { 260 | // // checking if the sdk file is extracted or not 261 | // if(!fs.existsSync(path.join(sdkFolder, this.sdk.repo))) { 262 | // try{ 263 | // let zip = new admZip(sdkZip); 264 | // if(fs.existsSync(path.join(sdkFolder, this.sdk.repo + '-master'))){ 265 | // fs.removeSync(path.join(sdkFolder, this.sdk.repo + '-master'), {recursive: true}); 266 | // } 267 | // zip.extractEntryTo(this.sdk.repo + '-master/', sdkFolder, true, true); 268 | // fs.renameSync(path.join(sdkFolder, this.sdk.repo + '-master'), path.join(sdkFolder, this.sdk.repo)); 269 | // } 270 | // catch(e) { 271 | // console.log(chalk.red('Invalid file format'), sdkZip); 272 | // } 273 | // } 274 | // // skip if force command is not enabled 275 | // if (!this.env.force) { 276 | // callback(); 277 | // return; 278 | // } 279 | // } 280 | // 281 | // console.log("Downloading Androidjs-SDK:", getDownloadLink(this.sdk.user, this.sdk.repo)); 282 | // downloadGithubRepo(this.sdk, sdkZip, (error) => { 283 | // if (error) { 284 | // console.log('Failed to download sdk'); 285 | // process.exit(); 286 | // } else { 287 | // try { 288 | // let zip = new admZip(sdkZip); 289 | // zip.extractEntryTo(this.sdk.repo + '-master/', sdkFolder, true, true); 290 | // fs.renameSync(path.join(sdkFolder, this.sdk.repo + '-master'), path.join(sdkFolder, this.sdk.repo)); 291 | // callback(); 292 | // } catch (e) { 293 | // console.log("Failed to extract sdk"); 294 | // console.log(chalk.red("Retry using '--force' command")); 295 | // console.log("$ androidjs build --force"); 296 | // process.exit(); 297 | // } 298 | // } 299 | // }); 300 | } 301 | 302 | updateFiles(callback) { 303 | const sdkZip = path.join(this.env.builder.cache, this.sdk.repo + '.zip'); 304 | const sdkFolder = path.join(this.env.builder.cache, this.sdk.repo); 305 | const assetsFolder = path.join(sdkFolder, 'assets'); 306 | const myappFolder = path.join(assetsFolder, 'myapp'); 307 | 308 | 309 | // reading package.json 310 | try { 311 | this.env.project.package = require(path.join(this.env.project.dir, 'package.json')); 312 | } catch (e) { 313 | console.log(`failed to load package:`, e.message); 314 | process.exit(); 315 | } 316 | 317 | try { 318 | // creating myapp folder 319 | if (!fs.existsSync(assetsFolder)) { 320 | fs.mkdirSync(assetsFolder); 321 | fs.mkdirSync(myappFolder); 322 | } else { 323 | fs.removeSync(myappFolder); 324 | fs.mkdirSync(myappFolder); 325 | } 326 | } catch (e) { 327 | if (!fs.existsSync(sdkFolder)) { 328 | console.log(chalk.red('Can not find ' + sdkFolder)); 329 | console.log("Try using '--force' command"); 330 | console.log("$ androidjs build --force"); 331 | } else { 332 | console.log("Failed to create assets", e); 333 | } 334 | process.exit(); 335 | } 336 | 337 | // copy assets 338 | try { 339 | console.log('copying assets ...'); 340 | fs.copySync(this.env.project.dir, myappFolder); 341 | 342 | } catch (e) { 343 | console.log(`failed to copy assets:`, e); 344 | process.exit(); 345 | } 346 | 347 | // removing dist folder from copied filed if exist. 348 | if(fs.existsSync(path.join(myappFolder, 'dist'))){ 349 | fs.removeSync(path.join(myappFolder, 'dist')) 350 | } 351 | 352 | // adding permissions 353 | let permissions = []; 354 | if (this.env.project.package.permission) { 355 | permissions = [ 356 | ///... add default 357 | ///...................... 358 | ...this.env.project.package.permission 359 | ]; 360 | } 361 | 362 | let deep_links: Array = []; 363 | if (this.env.project.package["deep-link"]) { 364 | deep_links = [ 365 | ///... add default 366 | ///...................... 367 | ...this.env.project.package["deep-link"] 368 | ]; 369 | } 370 | 371 | // Screen Orientation 372 | let screenOrientation = this.env.project.package['screenOrientation'] || null; 373 | 374 | // updating icon 375 | updateIcon(this.env, this); 376 | 377 | // updating app name 378 | updateAppName(this.env, this); 379 | 380 | // updating app version 381 | updateApkVersion(this.env, this); 382 | 383 | // generate Android Manifest file 384 | const manifestFileData = getManifest(this.env, this, permissions, deep_links, screenOrientation); 385 | fs.writeFileSync(path.join(sdkFolder, 'AndroidManifest.xml'), manifestFileData); 386 | 387 | 388 | /** 389 | * Downloading Build Tools 390 | **/ 391 | callback(); 392 | } 393 | 394 | 395 | rebuildApk(callback) { 396 | const progress = new LoadingBar(); 397 | 398 | const apkToolLink = getFileDownloadLink(this.apk_tool.user, this.apk_tool.repo, this.apk_tool.dir, this.apk_tool.file); 399 | const apkToolFilePath = path.join(this.env.builder.dir, 'build_tools', this.apk_tool.file); 400 | const apkSignerLink = getFileDownloadLink(this.apk_signer.user, this.apk_signer.repo, this.apk_signer.dir, this.apk_signer.file); 401 | const apkSignerFilePath = path.join(this.env.builder.cache, this.apk_signer.file); 402 | const sdkFolderPath = path.join(this.env.builder.cache, this.sdk.repo); 403 | const cacheFolderPath = this.env.builder.cache; 404 | const buildApkFilePath = path.join(this.env.builder.cache, this.env.project.package.name + '.apk'); 405 | 406 | let sdkConfig:any = {}; 407 | try { 408 | sdkConfig = require(path.join(sdkFolderPath, 'config.json')); 409 | }catch (e) { 410 | ///.. 411 | } 412 | 413 | // build tools can be used from the builder itself 414 | // if(!fs.existsSync(apkToolFilePath)){ 415 | // fs.copyFileSync(path.join(this.env.builder.dir, '..', 'build_tools', this.apk_tool.file), apkToolFilePath) 416 | // } 417 | 418 | 419 | let args_ = ['-jar', apkToolFilePath, 'b', sdkFolderPath, '-o', buildApkFilePath, '--frame-path', cacheFolderPath]; 420 | const proc = spawn('java', args_, {cwd: cacheFolderPath}); 421 | 422 | let log = ""; 423 | 424 | if(sdkConfig.version){ 425 | console.log(`Using SDK: ${sdkConfig.version}`); 426 | } 427 | console.log("Building Apk ..."); 428 | try { 429 | 430 | proc.stdout.on('data', data => { 431 | if (progress.isRunning === false) { 432 | if (this.env.builder.debug) { 433 | console.log("Build process started:"); 434 | } 435 | progress.message = 'building ...'; 436 | progress.start(); 437 | } else { 438 | progress.chunksDownloaded = (parseInt(progress.chunksDownloaded) + 1).toString(); 439 | } 440 | }); 441 | 442 | proc.stderr.on('data', (data) => { 443 | /// no need to show any error since user will get an error after build failed 444 | // progress.stop({message: `failed to build apk ${data}`}); 445 | // if (data.toString().indexOf('fakeLogOpen(/dev/log_crash) failed') != -1) { 446 | // console.log("Invalid sdk files, try using '--force' command"); 447 | // } else { 448 | // console.log(chalk.red(`${data}`)); 449 | // } 450 | // process.exit(); 451 | log += `${data}`; 452 | }); 453 | 454 | proc.on('close', (code) => { 455 | if (code === 0) { 456 | progress.stop(); 457 | progress.clear(); 458 | callback(); 459 | } else { 460 | progress.stop({message: 'non zero exit code: failed to build apk'}); 461 | console.error(log); 462 | process.exit(); 463 | } 464 | }); 465 | } catch (e) { 466 | console.log(e); 467 | } 468 | } 469 | 470 | /// this function is no longer required 471 | // downloadBuildTools(callback) { 472 | // const apkToolLink = getFileDownloadLink(this.apk_tool.user, this.apk_tool.repo, this.apk_tool.dir, this.apk_tool.file); 473 | // const apkToolFilePath = path.join(this.env.builder.cache, this.apk_tool.file); 474 | // const apkSignerLink = getFileDownloadLink(this.apk_signer.user, this.apk_signer.repo, this.apk_signer.dir, this.apk_signer.file); 475 | // const apkSignerFilePath = path.join(this.env.builder.cache, this.apk_signer.file); 476 | // 477 | // if (fs.existsSync(apkToolFilePath)) { 478 | // if (!this.env.force) { 479 | // callback(); 480 | // return; 481 | // } 482 | // } 483 | // 484 | // console.log("Downloading BuildTools:"); 485 | // 486 | // // download build tools 487 | // if (!fs.existsSync(apkToolFilePath)) { 488 | // console.log(apkToolLink); 489 | // downloadGithubFile(this.apk_tool, apkToolFilePath, (error) => { 490 | // if (error) { 491 | // console.log("Failed to download apk-tool"); 492 | // process.exit(); 493 | // } else { 494 | // console.log(apkSignerLink); 495 | // downloadGithubFile(this.apk_signer, apkSignerFilePath, (error) => { 496 | // if (error) { 497 | // console.log("Failed to download apk-tool"); 498 | // process.exit(); 499 | // } else { 500 | // callback(); 501 | // } 502 | // }); 503 | // } 504 | // }); 505 | // } else { 506 | // console.log(apkSignerLink); 507 | // downloadGithubFile(this.apk_signer, apkSignerFilePath, (error) => { 508 | // if (error) { 509 | // console.log("Failed to download apk-tool"); 510 | // process.exit(); 511 | // } else { 512 | // callback(); 513 | // } 514 | // }); 515 | // } 516 | // } 517 | 518 | build(): number { 519 | // required paths 520 | const sdkZip = path.join(this.env.builder.cache, this.sdk.repo + '.zip'); 521 | const sdkFolder = path.join(this.env.builder.cache, this.sdk.repo); 522 | const assetsFolder = path.join(sdkFolder, 'assets'); 523 | const myappFolder = path.join(assetsFolder, 'myapp'); 524 | const projectDistFolderPath = path.join(this.env.project.dir, 'dist'); 525 | 526 | 527 | // check Java version 528 | javaVersion((error, home) => { 529 | if (error) { 530 | console.log(error.message); 531 | process.exit(); 532 | } else { 533 | this.downloadSDK(() => { 534 | // this.downloadBuildTools(() => { 535 | this.updateFiles((error) => { 536 | updateTheme(this.env, (err) => { 537 | this.rebuildApk((error) => { 538 | this.sign(() => { 539 | try { 540 | // checking if user defined the dist path 541 | if (this.env.project.package['dist-path']) { 542 | let dist = this.env.project.package['dist-path']; 543 | if (dist[0] === '.') { 544 | dist = path.join(this.env.project.dir, dist); 545 | } 546 | if (!fs.existsSync(dist)) fs.mkdirSync(dist); 547 | fs.copyFileSync(path.join(this.env.builder.cache, this.env.project.package.name + '-aligned-debugSigned.apk'), path.join(dist, this.env.project.package.name + '.apk')); 548 | } else { 549 | if (!fs.existsSync(projectDistFolderPath)) fs.mkdirSync(projectDistFolderPath); 550 | fs.copyFileSync(path.join(this.env.builder.cache, this.env.project.package.name + '-aligned-debugSigned.apk'), path.join(projectDistFolderPath, this.env.project.package.name + '.apk')); 551 | } 552 | // check if internet is available 553 | getUpdateMessage(); 554 | } catch (e) { 555 | console.log("failed to move apk to dist folder"); 556 | process.exit(); 557 | } 558 | }); 559 | }); 560 | }); 561 | }); 562 | }, this.env.force||false); 563 | 564 | // }); 565 | } 566 | 567 | }); 568 | return 0; 569 | } 570 | 571 | 572 | /// TODO: check this function 573 | 574 | sign(callback) { 575 | const progress = new LoadingBar(); 576 | 577 | let apksigner_path: string = path.join(this.env.builder.dir, 'build_tools', this.apk_signer.file); 578 | let apk_file_path = path.join(this.env.builder.cache, this.env.project.package.name + '.apk'); 579 | let build_working_dir = path.join(this.env.builder.cache); 580 | 581 | let args_: Array = ['-jar', apksigner_path, '--apks', apk_file_path]; 582 | 583 | if(this.env.release === false) { 584 | args_.push('--debug'); 585 | console.log("Generating apk in debug mode. use '--release' to generate release build"); 586 | } else { 587 | console.log("Generating apk in release mode."); 588 | } 589 | 590 | // @ts-ignore 591 | const proc = spawn('java', args_, {cwd: build_working_dir}); 592 | proc.stdout.on('data', data => { 593 | if (progress.isRunning === false) { 594 | progress.message = 'signing ...'; 595 | progress.start(); 596 | } else { 597 | progress.chunksDownloaded = (parseInt(progress.chunksDownloaded) + 1).toString(); 598 | } 599 | }); 600 | 601 | proc.stderr.on('data', (data) => { 602 | progress.stop({message: 'error on signing apk'}); 603 | if (this.env.builder.debug) { 604 | console.log(`${data}`); 605 | } 606 | process.exit(); 607 | }); 608 | 609 | proc.on('close', (code) => { 610 | if (code === 0) { // process exit successfully 611 | progress.stop(); 612 | if(this.env.release) { 613 | /// TODO: - 614 | console.log(chalk.green(` Resign the apk with your own private keystore.`)); 615 | // console.log(`$ java -jar ${apksigner_path} -a ./dist/${this.env.project.package.name}.apk --ks --ksAlias --allowResign`); 616 | } 617 | callback(); 618 | } else { 619 | progress.stop({message: "failed to sign apk", code}); 620 | process.exit(); // exit process instead of invoking callback 621 | } 622 | 623 | }); 624 | } 625 | 626 | // extractSKD(callback) { 627 | // 628 | // let zip = new admZip(path.join(this.env.builder.cache, this.sdk_repo + '.zip')); 629 | // zip.extractEntryTo(this.sdk_repo + '-master/', this.env.builder.cache); 630 | // 631 | // // removing previous sdk folder before updating new sdk folder 632 | // if (fs.existsSync(this.sdk)) { 633 | // fs.removeSync(this.sdk); 634 | // } 635 | // fs.renameSync(path.join(this.env.builder.cache, this.sdk_repo + '-master'), this.sdk); 636 | // 637 | // } 638 | } 639 | 640 | 641 | interface downloadGithubArgs { 642 | url: string 643 | targetZip: string 644 | repo: string 645 | targetFolder: string 646 | zipFolder: string 647 | retry: number 648 | } 649 | 650 | interface IGithubRepoLink { 651 | user: string 652 | repo: string 653 | } 654 | 655 | function downloadsdk(args: downloadGithubArgs, callback) { 656 | args.retry -= 1; 657 | let state = { 658 | data: 0, 659 | moveCursor: false, 660 | responseCode: null, 661 | fileStream: fs.createWriteStream(args.targetZip), 662 | progress: new LoadingBar() 663 | }; 664 | request 665 | .get({url: args.url, headers: {'User-Agent': 'request'}}) 666 | .on('response', response => { 667 | state.responseCode = response.statusCode; 668 | if (response.statusCode === 200) { 669 | //@ts-ignore 670 | if(args.recursive){ 671 | console.log(`re-trying`); 672 | state.progress.message = "Downloading:"; 673 | state.progress.start(); 674 | }else { 675 | console.log("Downloading:", args.url); 676 | state.progress.message = "Downloading:"; 677 | state.progress.start(); 678 | } 679 | } 680 | }) 681 | .on('data', data => { 682 | state.data += data.length; 683 | state.progress.chunksDownloaded = `${Math.floor(state.data * 0.001)} KB`; 684 | state.progress.message = `Data: `; 685 | }) 686 | .on('error', error => { 687 | if(state.responseCode === null){ 688 | console.log(`Failed to download: ${args.url},\nCheck your internet connection`); 689 | // console.log(chalk.green(`You can manually update Androidjs-sdk by downloading from: \n[${args.url}] \nand extracting it to: [${path.join(args.targetFolder, args.repo)}]`)); 690 | } else { 691 | state.progress.stop({message: `Failed to download: ${args.url}`}); 692 | console.log(error); 693 | } 694 | }) 695 | .on('end', () => { 696 | state.fileStream.close(); 697 | try { 698 | let zip = new admZip(args.targetZip); 699 | if (fs.existsSync(path.join(args.targetFolder, args.zipFolder))) { 700 | fs.removeSync(path.join(args.targetFolder, args.zipFolder)); 701 | } 702 | zip.extractEntryTo(args.zipFolder, args.targetFolder, true, true); 703 | if (fs.existsSync(path.join(args.targetFolder, args.repo))) { 704 | fs.removeSync(path.join(args.targetFolder, args.repo)); 705 | } 706 | fs.renameSync(path.join(args.targetFolder, args.zipFolder), path.join(args.targetFolder, args.repo)); 707 | state.progress.stop(); 708 | callback(); 709 | } catch (e) { 710 | if (args.retry > 0) { 711 | state.progress.stop({message: "failed to download, retrying.."}); 712 | //@ts-ignore 713 | downloadsdk({...args, recursive: true}, callback); 714 | } else { 715 | state.progress.stop({message: "Failed to download"}); 716 | console.error(e); 717 | callback({message: "failed to download"}); 718 | } 719 | } 720 | }) 721 | .pipe(state.fileStream); 722 | } 723 | -------------------------------------------------------------------------------- /src/modules/webview.ts: -------------------------------------------------------------------------------- 1 | import '../Interfaces'; 2 | import {Interfaces} from '../Interfaces'; 3 | import * as path from 'path'; 4 | import {getDownloadLink, getFileDownloadLink} from '../GitListDir'; 5 | 6 | const request = require('request'); 7 | 8 | 9 | import * as fs from 'fs-extra'; 10 | import {spawn} from 'child_process'; 11 | import {getManifest} from './Html/ManifestBuilder'; 12 | import {updateIcon} from './Html/updateIcon'; 13 | import {updateAppName} from './Html/updateAppName'; 14 | import {createIfNotExist} from './Html/createIfNotExist'; 15 | import {downloadGithubFile} from './Html/downloadGithubFile'; 16 | import {downloadGithubRepo} from './Html/downloadGithubRepo'; 17 | import {LoadingBar} from './Html/ProgressBar'; 18 | import {javaVersion} from './Html/getJavaVersion'; 19 | import {getUpdate, getUpdateMessage} from './Html/checkForUpdates'; 20 | import {updateTheme} from "./Html/updateAppTheme"; 21 | import {updateApkVersion} from './Html/updateApkVersion'; 22 | 23 | 24 | 25 | const chalk = require('chalk'); 26 | 27 | const admZip = require('adm-zip'); 28 | 29 | 30 | /** 31 | * Webview module for creating HTML based Android-Js project 32 | */ 33 | export class Webview implements Interfaces.IBuilderModule { 34 | env: Interfaces.IEnv; 35 | user: string = 'android-js'; 36 | sdk: Interfaces.IGithubRepoLink = { 37 | user: 'android-js', 38 | repo: 'androidjs-sdk' 39 | }; 40 | example: Interfaces.IGithubRepoLink = { 41 | user: 'android-js', 42 | repo: 'webview-app-template' 43 | }; 44 | apk_tool: Interfaces.GithubFileLink = { 45 | user: 'android-js', 46 | repo: 'androidjs-builder', 47 | dir: 'build_tools', 48 | file: 'apktool.jar' 49 | }; 50 | apk_signer: Interfaces.GithubFileLink = { 51 | user: 'android-js', 52 | repo: 'androidjs-builder', 53 | dir: 'build_tools', 54 | file: 'uber-apk-signer.jar' 55 | }; 56 | 57 | // ManifistBuilder args 58 | manifist = { 59 | platformBuildVersionCode : "33", 60 | platformBuildVersionName : "13", 61 | application: { 62 | 'android:allowBackup':"true", 63 | 'android:appComponentFactory':"android.support.v4.app.CoreComponentFactory", 64 | 'android:debuggable':"false", 65 | 'android:icon':"@mipmap/ic_launcher", 66 | 'android:label':"@string/app_name", 67 | 'android:roundIcon':"@mipmap/ic_launcher_round", 68 | 'android:supportsRtl':"true", 69 | 'android:theme':"@style/AppTheme", 70 | 'android:usesCleartextTraffic': "true", 71 | 'android:requestLegacyExternalStorage':"true" 72 | }, 73 | activity: { 74 | 'android:configChanges':"keyboard|keyboardHidden|orientation|screenSize", 75 | 'android:name':"com.android.js.webview.MainActivity" 76 | } 77 | } 78 | 79 | // moduld context 80 | context: {}; 81 | 82 | installModule(env: Interfaces.IEnv, context): number { 83 | // init 84 | this.env = env; 85 | this.context = context; 86 | //@ts-ignore 87 | this.context.updateSdk = () => require('./Html/updateSdk').updateSdk(this.env); 88 | //@ts-ignore 89 | env.sdk = this.sdk; 90 | getUpdate(env); 91 | 92 | console.log("--release=", env.release) 93 | this.manifist.application["android:debuggable"] = (!env.release).toString() 94 | 95 | 96 | // check if sdk folder exist 97 | // if (this.env.builder.debug) { 98 | // try { 99 | // let sdk_config = require(path.join(this.sdk, 'config.json')); 100 | // console.log(`SDK version: ${sdk_config.version}`); 101 | // } catch (e) { 102 | // console.log(`Failed to load: ${path.join(this.sdk, 'config.json')}\n`, e.message); 103 | // } 104 | // } 105 | 106 | // // downloading sdk 107 | // if (!fs.existsSync(this.sdk) || this.env.force) { 108 | // this.downloadSKD(() => { 109 | // //////////////////////////////////////////////// 110 | // // downloading build tools 111 | // // apk-tool 112 | // if (!fs.existsSync(path.join(this.env.builder.cache, this.apk_tool.file))) { 113 | // let apk_tool_download_link = getFileDownloadLink(this.apk_tool.user, this.apk_tool.repo, this.apk_tool.dir, this.apk_tool.file); 114 | // console.log(apk_tool_download_link); 115 | // console.log(`downloading: ${this.apk_tool.file} ...`); 116 | // request.get(apk_tool_download_link).on('error', (e) => { 117 | // console.log(e); 118 | // }).pipe(fs.createWriteStream(path.join(this.env.builder.cache, this.apk_tool.file))); 119 | // } 120 | // 121 | // // apk-signer 122 | // if (!fs.existsSync(path.join(this.env.builder.cache, this.apk_signer.file))) { 123 | // let apk_signer_download_link = getFileDownloadLink(this.apk_signer.user, this.apk_signer.repo, this.apk_signer.dir, this.apk_signer.file); 124 | // console.log(apk_signer_download_link); 125 | // console.log(`downloading: ${this.apk_signer.file} ...`); 126 | // request.get(apk_signer_download_link).on('error', (e) => { 127 | // console.log(e); 128 | // }).pipe(fs.createWriteStream(path.join(this.env.builder.cache, this.apk_signer.file))); 129 | // } 130 | // ////////////////////////////////////// 131 | // }); 132 | // return 0; 133 | // } else { 134 | // // downloading build tools 135 | // // apk-tool 136 | // if (!fs.existsSync(path.join(this.env.builder.cache, this.apk_tool.file))) { 137 | // let apk_tool_download_link = getFileDownloadLink(this.apk_tool.user, this.apk_tool.repo, this.apk_tool.dir, this.apk_tool.file); 138 | // console.log(apk_tool_download_link); 139 | // console.log(`downloading: ${this.apk_tool.file} ...`); 140 | // request.get(apk_tool_download_link).on('error', (e) => { 141 | // console.log(e); 142 | // }).pipe(fs.createWriteStream(path.join(this.env.builder.cache, this.apk_tool.file))); 143 | // } 144 | // 145 | // // apk-signer 146 | // if (!fs.existsSync(path.join(this.env.builder.cache, this.apk_signer.file))) { 147 | // let apk_signer_download_link = getFileDownloadLink(this.apk_signer.user, this.apk_signer.repo, this.apk_signer.dir, this.apk_signer.file); 148 | // console.log(apk_signer_download_link); 149 | // console.log(`downloading: ${this.apk_signer.file} ...`); 150 | // request.get(apk_signer_download_link).on('error', (e) => { 151 | // console.log(e); 152 | // }).pipe(fs.createWriteStream(path.join(this.env.builder.cache, this.apk_signer.file))); 153 | // } 154 | return 0; 155 | // } 156 | } 157 | 158 | /// TODO: 159 | /// - check before update or create new project 160 | /// - update app name 161 | create(): number { 162 | let examplesFolder = createIfNotExist(path.join(this.env.builder.cache, '..', 'examples')); 163 | let exampleFilePath = path.join(examplesFolder, this.example.repo + '.zip'); 164 | let projectPath = path.join(this.env.project.dir, this.env.project.name); 165 | const exampleRepoDownloadLink = getDownloadLink(this.example.user, this.example.repo); 166 | const exampleFolderPath = path.join(examplesFolder, this.example.repo); 167 | 168 | // directory is not empty 169 | if (fs.existsSync(projectPath)) { 170 | console.log(`ERROR: directory is not empty ${projectPath}`); 171 | process.exit(); 172 | } 173 | 174 | // download example 175 | if (!fs.existsSync(exampleFolderPath) || this.env.force) { 176 | console.log('Downloading:', exampleRepoDownloadLink); 177 | 178 | downloadsdk({ 179 | url: getDownloadLink(this.example.user, this.example.repo), 180 | repo: this.example.repo, 181 | targetFolder: path.join( this.env.builder.cache, '..', 'examples'), 182 | targetZip: path.join( this.env.builder.cache, '..', 'examples', this.example.repo + ".zip"), 183 | zipFolder: this.example.repo + '-master/', 184 | retry: 3 185 | }, (error)=>{ 186 | // creating project 187 | 188 | if(error){ 189 | console.log(error); 190 | }else { 191 | if (this.env.builder.debug) 192 | console.log('Creating Project...'); 193 | fs.copySync(exampleFolderPath, projectPath); 194 | this.updatePackageJson(); 195 | console.log(`$cd ${this.env.project.name}`); 196 | console.log(`$npm install`); 197 | console.log(`$npm run start:dev`); 198 | console.log(`$npm run build`); 199 | } 200 | }); 201 | }else { 202 | if (this.env.builder.debug) 203 | console.log('Creating Project...'); 204 | fs.copySync(exampleFolderPath, projectPath); 205 | this.updatePackageJson(); 206 | console.log(chalk.green(` $cd ${this.env.project.name}`)); 207 | console.log(chalk.green(` $npm install`)); 208 | console.log(chalk.green(` $npm run start:dev`)); 209 | console.log(chalk.green(` $npm run build`)); 210 | } 211 | return 0; 212 | } 213 | 214 | 215 | updatePackageJson() { 216 | let projectPath = path.join(this.env.project.dir, this.env.project.name); 217 | 218 | /// generating package.json 219 | const _package = require(path.join(projectPath, 'package.json')); 220 | _package.name = this.env.project.name; 221 | _package['app-name'] = this.env.project.name; 222 | _package['project-type'] = this.env.project.type; 223 | _package['project-name'] = this.env.project.name; 224 | _package['scripts']['build'] = 'androidjs build'; 225 | _package["dist-path"] = "./dist"; 226 | // _package["screenOrientation"] = "portrait"; 227 | _package['theme'] = { 228 | "fullScreen": true 229 | }; 230 | fs.writeFileSync(path.join(this.env.project.dir, this.env.project.name, 'package.json'), JSON.stringify(_package, null, 4)); 231 | 232 | } 233 | 234 | downloadSDK(callback, force:boolean=false) { 235 | const sdkZip = path.join(this.env.builder.cache, this.sdk.repo + '.zip'); 236 | const sdkFolder = path.join(this.env.builder.cache, this.sdk.repo); 237 | 238 | 239 | if ( !force && fs.existsSync(sdkFolder)) { 240 | callback(); 241 | } else { 242 | downloadsdk({ 243 | url: getDownloadLink(this.sdk.user, this.sdk.repo), 244 | repo: this.sdk.repo, 245 | targetFolder: this.env.builder.cache, 246 | targetZip: sdkZip, 247 | zipFolder: this.sdk.repo + '-master/', 248 | retry: 4 249 | }, callback); 250 | } 251 | 252 | 253 | // 254 | // if (fs.existsSync(sdkZip)) { 255 | // // checking if the sdk file is extracted or not 256 | // if(!fs.existsSync(path.join(sdkFolder, this.sdk.repo))) { 257 | // try{ 258 | // let zip = new admZip(sdkZip); 259 | // if(fs.existsSync(path.join(sdkFolder, this.sdk.repo + '-master'))){ 260 | // fs.removeSync(path.join(sdkFolder, this.sdk.repo + '-master'), {recursive: true}); 261 | // } 262 | // zip.extractEntryTo(this.sdk.repo + '-master/', sdkFolder, true, true); 263 | // fs.renameSync(path.join(sdkFolder, this.sdk.repo + '-master'), path.join(sdkFolder, this.sdk.repo)); 264 | // } 265 | // catch(e) { 266 | // console.log(chalk.red('Invalid file format'), sdkZip); 267 | // } 268 | // } 269 | // // skip if force command is not enabled 270 | // if (!this.env.force) { 271 | // callback(); 272 | // return; 273 | // } 274 | // } 275 | // 276 | // console.log("Downloading Androidjs-SDK:", getDownloadLink(this.sdk.user, this.sdk.repo)); 277 | // downloadGithubRepo(this.sdk, sdkZip, (error) => { 278 | // if (error) { 279 | // console.log('Failed to download sdk'); 280 | // process.exit(); 281 | // } else { 282 | // try { 283 | // let zip = new admZip(sdkZip); 284 | // zip.extractEntryTo(this.sdk.repo + '-master/', sdkFolder, true, true); 285 | // fs.renameSync(path.join(sdkFolder, this.sdk.repo + '-master'), path.join(sdkFolder, this.sdk.repo)); 286 | // callback(); 287 | // } catch (e) { 288 | // console.log("Failed to extract sdk"); 289 | // console.log(chalk.red("Retry using '--force' command")); 290 | // console.log("$ androidjs build --force"); 291 | // process.exit(); 292 | // } 293 | // } 294 | // }); 295 | } 296 | 297 | updateFiles(callback) { 298 | const sdkZip = path.join(this.env.builder.cache, this.sdk.repo + '.zip'); 299 | const sdkFolder = path.join(this.env.builder.cache, this.sdk.repo); 300 | const assetsFolder = path.join(sdkFolder, 'assets'); 301 | const myappFolder = path.join(assetsFolder, 'myapp'); 302 | 303 | 304 | // reading package.json 305 | try { 306 | this.env.project.package = require(path.join(this.env.project.dir, 'package.json')); 307 | } catch (e) { 308 | console.log(`failed to load package:`, e.message); 309 | process.exit(); 310 | } 311 | 312 | try { 313 | // creating myapp folder 314 | if (!fs.existsSync(assetsFolder)) { 315 | fs.mkdirSync(assetsFolder); 316 | fs.mkdirSync(myappFolder); 317 | } else { 318 | fs.removeSync(myappFolder); 319 | fs.mkdirSync(myappFolder); 320 | } 321 | } catch (e) { 322 | if (!fs.existsSync(sdkFolder)) { 323 | console.log(chalk.red('Can not find ' + sdkFolder)); 324 | console.log("Try using '--force' command"); 325 | console.log("$ androidjs build --force"); 326 | } else { 327 | console.log("Failed to create assets", e); 328 | } 329 | process.exit(); 330 | } 331 | 332 | // copy assets 333 | try { 334 | console.log('copying assets ...'); 335 | fs.copySync(this.env.project.dir, myappFolder); 336 | 337 | } catch (e) { 338 | console.log(`failed to copy assets:`, e); 339 | process.exit(); 340 | } 341 | 342 | // removing dist folder from copied filed if exist. 343 | if(fs.existsSync(path.join(myappFolder, 'dist'))){ 344 | fs.removeSync(path.join(myappFolder, 'dist')) 345 | } 346 | 347 | // adding permissions 348 | let permissions = []; 349 | if (this.env.project.package.permission) { 350 | permissions = [ 351 | ///... add default 352 | ///...................... 353 | ...this.env.project.package.permission 354 | ]; 355 | } 356 | 357 | let deep_links: Array = []; 358 | if (this.env.project.package["deep-link"]) { 359 | deep_links = [ 360 | ///... add default 361 | ///...................... 362 | ...this.env.project.package["deep-link"] 363 | ]; 364 | } 365 | 366 | // Screen Orientation 367 | let screenOrientation = this.env.project.package['screenOrientation'] || null; 368 | 369 | // updating icon 370 | updateIcon(this.env, this); 371 | 372 | // updating app name 373 | updateAppName(this.env, this); 374 | 375 | // updating app version 376 | updateApkVersion(this.env, this); 377 | 378 | // generate Android Manifest file 379 | const manifestFileData = getManifest(this.env, this, permissions, deep_links, screenOrientation); 380 | fs.writeFileSync(path.join(sdkFolder, 'AndroidManifest.xml'), manifestFileData); 381 | 382 | 383 | /** 384 | * Downloading Build Tools 385 | **/ 386 | callback(); 387 | } 388 | 389 | rebuildApk(callback) { 390 | const progress = new LoadingBar(); 391 | 392 | const apkToolLink = getFileDownloadLink(this.apk_tool.user, this.apk_tool.repo, this.apk_tool.dir, this.apk_tool.file); 393 | const apkToolFilePath = path.join(this.env.builder.dir, 'build_tools', this.apk_tool.file); 394 | const apkSignerLink = getFileDownloadLink(this.apk_signer.user, this.apk_signer.repo, this.apk_signer.dir, this.apk_signer.file); 395 | const apkSignerFilePath = path.join(this.env.builder.cache, this.apk_signer.file); 396 | const sdkFolderPath = path.join(this.env.builder.cache, this.sdk.repo); 397 | const cacheFolderPath = this.env.builder.cache; 398 | const buildApkFilePath = path.join(this.env.builder.cache, this.env.project.package.name + '.apk'); 399 | 400 | let sdkConfig:any = {}; 401 | try { 402 | sdkConfig = require(path.join(sdkFolderPath, 'config.json')); 403 | }catch (e) { 404 | ///.. 405 | } 406 | 407 | // build tools can be used from the builder itself 408 | // if(!fs.existsSync(apkToolFilePath)){ 409 | // fs.copyFileSync(path.join(this.env.builder.dir, '..', 'build_tools', this.apk_tool.file), apkToolFilePath) 410 | // } 411 | 412 | 413 | let args_ = ['-jar', apkToolFilePath, 'b', sdkFolderPath, '-o', buildApkFilePath, '--frame-path', cacheFolderPath]; 414 | const proc = spawn('java', args_, {cwd: cacheFolderPath}); 415 | 416 | let log = ""; 417 | 418 | if(sdkConfig.version){ 419 | console.log(`Using SDK: ${sdkConfig.version}`); 420 | } 421 | console.log("Building Apk ..."); 422 | try { 423 | 424 | proc.stdout.on('data', data => { 425 | if (progress.isRunning === false) { 426 | if (this.env.builder.debug) { 427 | console.log("Build process started:"); 428 | } 429 | progress.message = 'building ...'; 430 | progress.start(); 431 | } else { 432 | progress.chunksDownloaded = (parseInt(progress.chunksDownloaded) + 1).toString(); 433 | } 434 | }); 435 | 436 | proc.stderr.on('data', (data) => { 437 | /// no need to show any error since user will get an error after build failed 438 | // progress.stop({message: `failed to build apk ${data}`}); 439 | // if (data.toString().indexOf('fakeLogOpen(/dev/log_crash) failed') != -1) { 440 | // console.log("Invalid sdk files, try using '--force' command"); 441 | // } else { 442 | // console.log(chalk.red(`${data}`)); 443 | // } 444 | // process.exit(); 445 | log += `${data}`; 446 | }); 447 | 448 | proc.on('close', (code) => { 449 | if (code === 0) { 450 | progress.stop(); 451 | progress.clear(); 452 | callback(); 453 | } else { 454 | progress.stop({message: 'non zero exit code: failed to build apk'}); 455 | console.error(log); 456 | process.exit(); 457 | } 458 | }); 459 | } catch (e) { 460 | console.log(e); 461 | } 462 | } 463 | 464 | /// this function is no longer required 465 | // downloadBuildTools(callback) { 466 | // const apkToolLink = getFileDownloadLink(this.apk_tool.user, this.apk_tool.repo, this.apk_tool.dir, this.apk_tool.file); 467 | // const apkToolFilePath = path.join(this.env.builder.cache, this.apk_tool.file); 468 | // const apkSignerLink = getFileDownloadLink(this.apk_signer.user, this.apk_signer.repo, this.apk_signer.dir, this.apk_signer.file); 469 | // const apkSignerFilePath = path.join(this.env.builder.cache, this.apk_signer.file); 470 | // 471 | // if (fs.existsSync(apkToolFilePath)) { 472 | // if (!this.env.force) { 473 | // callback(); 474 | // return; 475 | // } 476 | // } 477 | // 478 | // console.log("Downloading BuildTools:"); 479 | // 480 | // // download build tools 481 | // if (!fs.existsSync(apkToolFilePath)) { 482 | // console.log(apkToolLink); 483 | // downloadGithubFile(this.apk_tool, apkToolFilePath, (error) => { 484 | // if (error) { 485 | // console.log("Failed to download apk-tool"); 486 | // process.exit(); 487 | // } else { 488 | // console.log(apkSignerLink); 489 | // downloadGithubFile(this.apk_signer, apkSignerFilePath, (error) => { 490 | // if (error) { 491 | // console.log("Failed to download apk-tool"); 492 | // process.exit(); 493 | // } else { 494 | // callback(); 495 | // } 496 | // }); 497 | // } 498 | // }); 499 | // } else { 500 | // console.log(apkSignerLink); 501 | // downloadGithubFile(this.apk_signer, apkSignerFilePath, (error) => { 502 | // if (error) { 503 | // console.log("Failed to download apk-tool"); 504 | // process.exit(); 505 | // } else { 506 | // callback(); 507 | // } 508 | // }); 509 | // } 510 | // } 511 | 512 | build(): number { 513 | // required paths 514 | const sdkZip = path.join(this.env.builder.cache, this.sdk.repo + '.zip'); 515 | const sdkFolder = path.join(this.env.builder.cache, this.sdk.repo); 516 | const assetsFolder = path.join(sdkFolder, 'assets'); 517 | const myappFolder = path.join(assetsFolder, 'myapp'); 518 | const projectDistFolderPath = path.join(this.env.project.dir, 'dist'); 519 | 520 | 521 | // check Java version 522 | javaVersion((error, home) => { 523 | if (error) { 524 | console.log(error.message); 525 | process.exit(); 526 | } else { 527 | this.downloadSDK(() => { 528 | // this.downloadBuildTools(() => { 529 | this.updateFiles((error) => { 530 | updateTheme(this.env, (err) => { 531 | this.rebuildApk((error) => { 532 | this.sign(() => { 533 | try { 534 | // checking if user defined the dist path 535 | if (this.env.project.package['dist-path']) { 536 | let dist = this.env.project.package['dist-path']; 537 | if (dist[0] === '.') { 538 | dist = path.join(this.env.project.dir, dist); 539 | } 540 | if (!fs.existsSync(dist)) fs.mkdirSync(dist); 541 | fs.copyFileSync(path.join(this.env.builder.cache, this.env.project.package.name + '-aligned-debugSigned.apk'), path.join(dist, this.env.project.package.name + '.apk')); 542 | } else { 543 | if (!fs.existsSync(projectDistFolderPath)) fs.mkdirSync(projectDistFolderPath); 544 | fs.copyFileSync(path.join(this.env.builder.cache, this.env.project.package.name + '-aligned-debugSigned.apk'), path.join(projectDistFolderPath, this.env.project.package.name + '.apk')); 545 | } 546 | // check if internet is available 547 | getUpdateMessage(); 548 | } catch (e) { 549 | console.log("failed to move apk to dist folder"); 550 | process.exit(); 551 | } 552 | }); 553 | }); 554 | }); 555 | }); 556 | }, this.env.force||false); 557 | 558 | // }); 559 | } 560 | 561 | }); 562 | return 0; 563 | } 564 | 565 | 566 | /// TODO: check this function 567 | 568 | sign(callback) { 569 | const progress = new LoadingBar(); 570 | 571 | let apksigner_path: string = path.join(this.env.builder.dir, 'build_tools', this.apk_signer.file); 572 | let apk_file_path = path.join(this.env.builder.cache, this.env.project.package.name + '.apk'); 573 | let build_working_dir = path.join(this.env.builder.cache); 574 | 575 | let args_: Array = ['-jar', apksigner_path, '--apks', apk_file_path]; 576 | 577 | if(this.env.release === false) { 578 | args_.push('--debug'); 579 | console.log("Generating apk in debug mode. use '--release' to generate release build"); 580 | } else { 581 | console.log("Generating apk in release mode."); 582 | } 583 | 584 | // @ts-ignore 585 | const proc = spawn('java', args_, {cwd: build_working_dir}); 586 | proc.stdout.on('data', data => { 587 | if (progress.isRunning === false) { 588 | progress.message = 'signing ...'; 589 | progress.start(); 590 | } else { 591 | progress.chunksDownloaded = (parseInt(progress.chunksDownloaded) + 1).toString(); 592 | } 593 | }); 594 | 595 | proc.stderr.on('data', (data) => { 596 | progress.stop({message: 'error on signing apk'}); 597 | if (this.env.builder.debug) { 598 | console.log(`${data}`); 599 | } 600 | process.exit(); 601 | }); 602 | 603 | proc.on('close', (code) => { 604 | if (code === 0) { // process exit successfully 605 | progress.stop(); 606 | if(this.env.release) { 607 | /// TODO: - 608 | console.log(chalk.green(` Resign the apk with your own private keystore.`)); 609 | // console.log(`$ java -jar ${apksigner_path} -a ./dist/${this.env.project.package.name}.apk --ks --ksAlias --allowResign`); 610 | } 611 | callback(); 612 | } else { 613 | progress.stop({message: "failed to sign apk", code}); 614 | process.exit(); // exit process instead of invoking callback 615 | } 616 | 617 | }); 618 | } 619 | 620 | // extractSKD(callback) { 621 | // 622 | // let zip = new admZip(path.join(this.env.builder.cache, this.sdk_repo + '.zip')); 623 | // zip.extractEntryTo(this.sdk_repo + '-master/', this.env.builder.cache); 624 | // 625 | // // removing previous sdk folder before updating new sdk folder 626 | // if (fs.existsSync(this.sdk)) { 627 | // fs.removeSync(this.sdk); 628 | // } 629 | // fs.renameSync(path.join(this.env.builder.cache, this.sdk_repo + '-master'), this.sdk); 630 | // 631 | // } 632 | } 633 | 634 | 635 | interface downloadGithubArgs { 636 | url: string 637 | targetZip: string 638 | repo: string 639 | targetFolder: string 640 | zipFolder: string 641 | retry: number 642 | } 643 | 644 | interface IGithubRepoLink { 645 | user: string 646 | repo: string 647 | } 648 | 649 | function downloadsdk(args: downloadGithubArgs, callback) { 650 | args.retry -= 1; 651 | let state = { 652 | data: 0, 653 | moveCursor: false, 654 | responseCode: null, 655 | fileStream: fs.createWriteStream(args.targetZip), 656 | progress: new LoadingBar() 657 | }; 658 | request 659 | .get({url: args.url, headers: {'User-Agent': 'request'}}) 660 | .on('response', response => { 661 | state.responseCode = response.statusCode; 662 | if (response.statusCode === 200) { 663 | //@ts-ignore 664 | if(args.recursive){ 665 | console.log(`re-trying`); 666 | state.progress.message = "Downloading:"; 667 | state.progress.start(); 668 | }else { 669 | console.log("Downloading:", args.url); 670 | state.progress.message = "Downloading:"; 671 | state.progress.start(); 672 | } 673 | } 674 | }) 675 | .on('data', data => { 676 | state.data += data.length; 677 | state.progress.chunksDownloaded = `${Math.floor(state.data * 0.001)} KB`; 678 | state.progress.message = `Data: `; 679 | }) 680 | .on('error', error => { 681 | if(state.responseCode === null){ 682 | console.log(`Failed to download: ${args.url},\nCheck your internet connection`); 683 | // console.log(chalk.green(`You can manually update Androidjs-sdk by downloading from: \n[${args.url}] \nand extracting it to: [${path.join(args.targetFolder, args.repo)}]`)); 684 | } else { 685 | state.progress.stop({message: `Failed to download: ${args.url}`}); 686 | console.log(error); 687 | } 688 | }) 689 | .on('end', () => { 690 | state.fileStream.close(); 691 | try { 692 | let zip = new admZip(args.targetZip); 693 | if (fs.existsSync(path.join(args.targetFolder, args.zipFolder))) { 694 | fs.removeSync(path.join(args.targetFolder, args.zipFolder)); 695 | } 696 | zip.extractEntryTo(args.zipFolder, args.targetFolder, true, true); 697 | if (fs.existsSync(path.join(args.targetFolder, args.repo))) { 698 | fs.removeSync(path.join(args.targetFolder, args.repo)); 699 | } 700 | fs.renameSync(path.join(args.targetFolder, args.zipFolder), path.join(args.targetFolder, args.repo)); 701 | state.progress.stop(); 702 | callback(); 703 | } catch (e) { 704 | if (args.retry > 0) { 705 | state.progress.stop({message: "failed to download, retrying.."}); 706 | //@ts-ignore 707 | downloadsdk({...args, recursive: true}, callback); 708 | } else { 709 | state.progress.stop({message: "failed to download"}); 710 | callback({message: "failed to download"}); 711 | } 712 | } 713 | }) 714 | .pipe(state.fileStream); 715 | } 716 | -------------------------------------------------------------------------------- /src/postinstall.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | // "incremental": true, /* Enable incremental compilation */ 5 | "target": "ES2019", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ 6 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 7 | "lib": [ 8 | "dom", 9 | "es2015" 10 | ], /* Specify library files to be included in the compilation. */ 11 | // "allowJs": true, /* Allow javascript files to be compiled. */ 12 | // "checkJs": true, /* Report errors in .js files. */ 13 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 14 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 15 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 16 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 17 | // "outFile": "./", /* Concatenate and emit output to single file. */ 18 | "outDir": "./dist/", /* Redirect output structure to the directory. */ 19 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 20 | // "composite": true, /* Enable project compilation */ 21 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 22 | // "removeComments": true, /* Do not emit comments to output. */ 23 | // "noEmit": true, /* Do not emit outputs. */ 24 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 25 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 26 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 27 | 28 | /* Strict Type-Checking Options */ 29 | // "strict": true, /* Enable all strict type-checking options. */ 30 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 31 | // "strictNullChecks": true, /* Enable strict null checks. */ 32 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 33 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 34 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 35 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 36 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 37 | 38 | /* Additional Checks */ 39 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 40 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 41 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 42 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 43 | 44 | /* Module Resolution Options */ 45 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 46 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 47 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 48 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 49 | // "typeRoots": [], /* List of folders to include type definitions from. */ 50 | // "types": [], /* Type declaration files to be included in compilation. */ 51 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 52 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 53 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 54 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 55 | 56 | /* Source Map Options */ 57 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 58 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 59 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 60 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 61 | 62 | /* Experimental Options */ 63 | "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 64 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 65 | } 66 | } 67 | --------------------------------------------------------------------------------