├── .github └── main.workflow ├── .gitignore ├── .npmrc ├── README.md ├── bin └── aem-front ├── package-lock.json ├── package.json └── src ├── aem-sync ├── index.js ├── mapArgs.js └── mapArgs.test.js ├── browser-sync └── index.js └── index.js /.github/main.workflow: -------------------------------------------------------------------------------- 1 | workflow "NPM Install && Test" { 2 | resolves = ["Test"] 3 | on = "pull_request" 4 | } 5 | 6 | action "Install" { 7 | uses = "actions/npm@master" 8 | args = "install" 9 | } 10 | 11 | action "Test" { 12 | needs = "Install" 13 | uses = "actions/npm@master" 14 | args = "test" 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AEM Front 2 | 3 | AEM Front speeds up development in AEM significantly. It combines AEMSync with BrowserSync, and also works together with the AEM Front Extension for Google Chrome so you don't have to manually inject the BrowserSync script into your code base. 4 | 5 | ## Installation 6 | 7 | ```bash 8 | npm install aem-front -g 9 | ``` 10 | 11 | ## Usage 12 | 13 | ```bash 14 | aem-front 15 | ``` 16 | 17 | You can also specify one or multiple of the following options: 18 | 19 | ```bash 20 | -w: Folder to watch; default is current (`CWD`). 21 | -t: URL to AEM instance; multiple can be set. Default is http://admin:admin@localhost:4502. 22 | -e: Anymatch exclude filter; any file matching the pattern will be skipped. Default is `**/jcr_root/*, **/@(.git|.svn|.hg|target), **/@(.git|.svn|.hg|target)/**` 23 | -o: Browser page to be opened after successful launch; default is "false". 24 | -b: Browser where page should be opened in; this parameter is platform dependent; for example, Chrome is "google chrome" on OS X, "google-chrome" on Linux and "chrome" on Windows; default is "google chrome". 25 | -i: Update interval in milliseconds; default is 100. 26 | -v: Display version of AEM Front. 27 | ``` 28 | 29 | Examples: 30 | 31 | ```bash 32 | aem-front -w "./../sibling/my_project" -e "**/webpack.module/**" -i "500" 33 | 34 | aem-front -t http://admin:admin@localhost:4502 -t http://admin:admin@localhost:4503 -w ~/workspace/my_project 35 | ``` 36 | 37 | ## Requirements 38 | 39 | - NodeJS and NPM 40 | - Required if you want to use the corresponding Chrome extension: Chrome Browser 41 | 42 | ## Step-by-step guide 43 | 44 | 1. If you do not have Node and NPM installed, in command line run: 45 | 46 | ```bash 47 | sudo brew install node 48 | sudo npm install npm -g 49 | ``` 50 | 51 | 2. Install AEM Front by running: 52 | 53 | ```bash 54 | npm install aem-front -g 55 | ``` 56 | 57 | 3. Run the command listed in the "Usage" section in your terminal from a folder where you want to watch file changes. But you can basically run it from anywhere as long as you pass the correct path with the `-w` option. 58 | 59 | 4. After successfully starting this script, you can/should install the corresponding Chrome extension. The script injects the required BrowserSync script automatically into your website and comes with a few handy configuration options. But you can also past the BrowserSync script into your website manually. You can install the extension directly from the Chrome app store: https://chrome.google.com/webstore/detail/cmpbphecgagbhhociicpakhddeagjlih 60 | 61 | ## Development 62 | 63 | If you clone this project (aem-front) to your local machine and modify it, you can test changes without having to push and publish them to the NPM repository. Do this by linking aem-front globally. In your terminal, run: 64 | 65 | ``` 66 | npm link 67 | ``` 68 | 69 | Then, in your project where you want to test the local aem-front instead of the one hosted at npmjs.com, run: 70 | 71 | ``` 72 | npm link aem-front 73 | ``` 74 | 75 | Thanks to the [BrowserSync](https://www.npmjs.com/package/browser-sync) team and to [gavoja](https://github.com/gavoja) for [aemsync](https://www.npmjs.com/package/aemsync). 76 | -------------------------------------------------------------------------------- /bin/aem-front: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const aemFront = require("../src"); 3 | aemFront(); 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aem-front", 3 | "version": "0.3.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node bin/aem-front", 8 | "postversion": "git push && git push --tags", 9 | "test": "jest", 10 | "watch:test": "npm run test -- --watch" 11 | }, 12 | "author": "Kevin Weber", 13 | "keywords": [ 14 | "AEM", 15 | "aem", 16 | "BrowserSync", 17 | "browsersync", 18 | "LiveReload", 19 | "livereload", 20 | "aemsync", 21 | "aem-sync", 22 | "adobe", 23 | "frontend", 24 | "front-end", 25 | "kevinweber", 26 | "kevin-weber", 27 | "Kevin Weber" 28 | ], 29 | "repository": { 30 | "type": "git", 31 | "url": "https://github.com/kevinweber/aem-front.git" 32 | }, 33 | "bugs": { 34 | "url": "https://github.com/kevinweber/aem-front/issues" 35 | }, 36 | "license": "GPL-3.0-or-later", 37 | "dependencies": { 38 | "aemsync": "^4.0.0-rc1", 39 | "browser-sync": "^2.26.7", 40 | "graceful-fs": "^4.1.15", 41 | "minimist": "^1.2.0", 42 | "opn": "^5.5.0", 43 | "path": "^0.12.7" 44 | }, 45 | "bin": { 46 | "aem-front": "./bin/aem-front" 47 | }, 48 | "devDependencies": { 49 | "jest": "^24.8.0" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/aem-sync/index.js: -------------------------------------------------------------------------------- 1 | const fs = require("graceful-fs"); 2 | const opn = require("opn"); 3 | const minimist = require("minimist"); 4 | 5 | const packageInfo = require("../../package.json"); 6 | const mapArgs = require("./mapArgs"); 7 | 8 | const browserSync = require("../browser-sync"); 9 | 10 | const aemsync = require("aemsync"); 11 | 12 | const Watcher = aemsync.Watcher; 13 | const Pipeline = aemsync.Pipeline; 14 | 15 | const ANSI_COLOR_CYAN = "\x1b[36m"; 16 | const ANSI_COLOR_MAGENTA = "\x1b[35m"; 17 | const ANSI_COLOR_RESET = "\x1b[0m"; 18 | 19 | const consoleSeparator = () => { 20 | console.log("---------------------------------------"); 21 | }; 22 | 23 | // Command line options 24 | const MSG_HELP = `Usage: aem-front [OPTIONS] 25 | Options: 26 | -t targets Default is http://admin:admin@localhost:4502 27 | -w path_to_watch Default is current 28 | -e exclude_filter Anymach exclude filter; disabled by default 29 | -i sync_interval Update interval in milliseconds; default is 100 30 | -u packmgr_path Package manager path; default is /crx/packmgr/service.jsp 31 | -o open_page Browser page to be opened after successful launch; default is "false". 32 | -b browser Browser where page should be opened in; this parameter is platform dependent; for example, Chrome is "google chrome" on OS X, "google-chrome" on Linux and "chrome" on Windows; default is "google chrome" 33 | -h Displays this screen 34 | -v Displays version of this package`; 35 | 36 | const reloadBrowser = browserSyncTarget => (err, host) => { 37 | if (err) { 38 | return console.error(`Error when pushing package to ${host}`, err); 39 | } 40 | 41 | if (browserSyncTarget.includes(host)) { 42 | browserSync.reload({ 43 | name: "aem-sync" 44 | }); 45 | } 46 | }; 47 | 48 | const init = () => { 49 | "use strict"; 50 | 51 | const args = minimist(process.argv.slice(2)); 52 | 53 | // Show help 54 | if (args.h) { 55 | console.log(MSG_HELP); 56 | return; 57 | } 58 | 59 | // Show version 60 | if (args.v) { 61 | console.log(packageInfo.version); 62 | return; 63 | } 64 | 65 | const { 66 | workingDir, 67 | targets, 68 | browserSyncTarget, 69 | interval, 70 | packmgrPath, 71 | exclude, 72 | startPage, 73 | startBrowser 74 | } = mapArgs(args); 75 | 76 | if (targets.length > 1) { 77 | consoleSeparator(); 78 | console.log( 79 | `You've provided multiple targets. BrowserSync only uses the first one: ${browserSyncTarget}` 80 | ); 81 | } 82 | 83 | browserSync.create({ 84 | name: "aem-sync", 85 | proxy: browserSyncTarget 86 | }); 87 | 88 | // Overview of ANSI color codes: http://www.lihaoyi.com/post/BuildyourownCommandLinewithANSIescapecodes.html 89 | if (!fs.existsSync(workingDir)) { 90 | consoleSeparator(); 91 | console.log( 92 | `Invalid path: ${ANSI_COLOR_CYAN}${workingDir}${ANSI_COLOR_RESET}` 93 | ); 94 | return; 95 | } 96 | 97 | consoleSeparator(); 98 | console.log(` 99 | Watch over: ${ANSI_COLOR_MAGENTA}${workingDir}${ANSI_COLOR_RESET} 100 | Targets: ${ANSI_COLOR_MAGENTA}${targets}${ANSI_COLOR_RESET} 101 | Excludes: ${ANSI_COLOR_MAGENTA}${exclude}${ANSI_COLOR_RESET} 102 | Interval: ${ANSI_COLOR_MAGENTA}${interval}${ANSI_COLOR_RESET} 103 | `); 104 | 105 | // Reload browser once files are pushed to AEM instance 106 | const onPushEnd = reloadBrowser(browserSyncTarget); 107 | 108 | aemsync(workingDir, { 109 | workingDir, 110 | targets, 111 | interval, 112 | exclude, 113 | packmgrPath, 114 | onPushEnd 115 | }); 116 | 117 | if (startPage !== false && startPage !== "false") { 118 | opn(startPage, { 119 | app: startBrowser 120 | }); 121 | } 122 | 123 | consoleSeparator(); 124 | console.log( 125 | `NOTE: It's recommended to use AEM Front together with the corresponding Chrome browser extension. Get it from the official app store: ${ANSI_COLOR_CYAN}https://chrome.google.com/webstore/detail/cmpbphecgagbhhociicpakhddeagjlih${ANSI_COLOR_RESET}. Alternatively, you can use AEM Front without extension by adding a BrowserSync code into your AEM site manually.` 126 | ); 127 | consoleSeparator(); 128 | console.log("\n"); 129 | }; 130 | 131 | module.exports = { 132 | init 133 | }; 134 | -------------------------------------------------------------------------------- /src/aem-sync/mapArgs.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const aemsyncDefaults = require("aemsync/src/defaults"); 3 | 4 | const mapArgs = (args = {}) => { 5 | const targets = args.t 6 | ? typeof args.t === "string" 7 | ? [...args.t.split(",")] 8 | : args.t 9 | : aemsyncDefaults.targets; 10 | 11 | const exclude = args.e 12 | ? typeof args.e === "string" 13 | ? [...args.e.split(",")] 14 | : args.e 15 | : aemsyncDefaults.exclude; 16 | 17 | return { 18 | workingDir: path.resolve(args.w || aemsyncDefaults.workingDir), 19 | targets, 20 | browserSyncTarget: targets[0], 21 | interval: args.i || 100, 22 | packmgrPath: args.u || aemsyncDefaults.packmgrPath, 23 | exclude, 24 | startPage: args.o || false, 25 | startBrowser: args.browser || "google chrome" 26 | }; 27 | }; 28 | 29 | module.exports = mapArgs; 30 | -------------------------------------------------------------------------------- /src/aem-sync/mapArgs.test.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const mapArgs = require("./mapArgs"); 3 | 4 | jest.mock("path"); 5 | path.resolve.mockImplementation(pathString => pathString); 6 | 7 | const defaultConfig = { 8 | browserSyncTarget: "http://admin:admin@localhost:4502", 9 | exclude: [ 10 | "**/jcr_root/*", 11 | "**/@(.git|.svn|.hg|target)", 12 | "**/@(.git|.svn|.hg|target)/**" 13 | ], 14 | interval: 100, 15 | packmgrPath: "/crx/packmgr/service.jsp", 16 | startBrowser: "google chrome", 17 | startPage: false, 18 | targets: ["http://admin:admin@localhost:4502"], 19 | workingDir: "." 20 | }; 21 | 22 | describe("mapArgs", () => { 23 | it("returns expected default values", () => { 24 | expect(mapArgs()).toStrictEqual(defaultConfig); 25 | }); 26 | 27 | it("supports multiple `-t` arguments", () => { 28 | expect( 29 | mapArgs({ 30 | t: ["targetA", "targetB"] 31 | }) 32 | ).toHaveProperty("targets", ["targetA", "targetB"]); 33 | }); 34 | 35 | it("supports one `-t` argument with comma-separated values", () => { 36 | expect( 37 | mapArgs({ 38 | t: "targetA,targetB" 39 | }) 40 | ).toHaveProperty("targets", ["targetA", "targetB"]); 41 | }); 42 | 43 | it("supports multiple `-e` arguments", () => { 44 | expect( 45 | mapArgs({ 46 | e: ["excludeA", "excludeB"] 47 | }) 48 | ).toHaveProperty("exclude", ["excludeA", "excludeB"]); 49 | }); 50 | 51 | it("supports one `-e` argument with comma-separated values", () => { 52 | expect( 53 | mapArgs({ 54 | e: "excludeA,excludeB" 55 | }) 56 | ).toHaveProperty("exclude", ["excludeA", "excludeB"]); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /src/browser-sync/index.js: -------------------------------------------------------------------------------- 1 | const browserSync = require("browser-sync"); 2 | 3 | const create = args => { 4 | // Documentation: https://www.browsersync.io/docs/options 5 | const options = { 6 | notify: false, 7 | open: false, 8 | proxy: args.proxy 9 | }; 10 | 11 | browserSync.create(args.name); 12 | browserSync.init(options, function(unknown, data) { 13 | // Callback: 14 | // console.log(data.options.get("urls").get("ui")); 15 | // console.log(data.options.get("urls").get("ui-external")); 16 | }); 17 | }; 18 | 19 | const reload = args => { 20 | console.log("Reloading browsers."); 21 | // Documentation: https://www.browsersync.io/docs/api#api-reload 22 | browserSync.reload(args.name); 23 | }; 24 | 25 | module.exports = { 26 | create, 27 | reload 28 | }; 29 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const aemSync = require("./aem-sync"); 2 | module.exports = aemSync.init; 3 | --------------------------------------------------------------------------------