├── .github └── workflows │ └── release.yml ├── .gitignore ├── .nvmrc ├── LICENSE.md ├── README.md ├── icon.svg ├── lib ├── SiteInfoExtender.js ├── SiteInfoExtender.js.map ├── conf │ ├── site.conf.hbs │ └── wordpress-multi.conf.hbs ├── main.js ├── main.js.map ├── renderer.js └── renderer.js.map ├── package-lock.json ├── package.json ├── src ├── SiteInfoExtender.jsx ├── main.ts └── renderer.jsx ├── style.css └── tsconfig.json /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout repository 13 | uses: actions/checkout@v3 14 | 15 | - name: Setup node.js 16 | uses: actions/setup-node@v3 17 | with: 18 | node-version-file: '.nvmrc' 19 | cache: npm 20 | 21 | - name: Install Dependencies 22 | run: npm ci 23 | 24 | - name: Build JS 25 | run: npm run build 26 | 27 | - name: Tar files 28 | run: tar -czf release.tar.gz * 29 | 30 | - name: Release 31 | uses: softprops/action-gh-release@v1 32 | if: startsWith(github.ref, 'refs/tags/') 33 | with: 34 | generate_release_notes: true 35 | files: | 36 | release.tar.gz 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 16 -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # Copyright (c) Dekode 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Project Base LocalWP Addon. 2 | A helper addon for LocalWP. 3 | 4 | image 5 | 6 | 7 | ## Features 8 | 9 | ### Multisite Config fixer. 10 | Updates the nginx config to support our project base multisite setup. 11 | 12 | ### Convert to multisite 13 | Adds a button to convert site to multiste sub directory setup, you should be able to setup subdomains with this setup also. 14 | 15 | ### Root Path. 16 | Adds a input field to change the projects root path. Often legacy projects requires you to have a root path like this: "{{root}}/web" etc. 17 | 18 | Absolute path should also work. like this: "/Users/username/Sites/project-name/web" 19 | 20 | ### Remote Images through NGINX config. 21 | This allows you to type in the production url and it will use that url to fetch images. 22 | 23 | ## Installation 24 | 25 | * Download the .tar.gz file from the releases page. 26 | * Go to LocalWP and press the "Addons" tab in the sidebar. 27 | * Press the "Installed" tab. 28 | * Then click the "Install from disk" button at the top right. 29 | * Activate and relaunch localWP. 30 | 31 | ## Development 32 | 33 | * Use Node v16 34 | * `npm install` installs dependencies. 35 | * `npm start` Starts the renderer process. 36 | * `npm start:main` Starts the main.ts process. 37 | 38 | ### Useful Links 39 | 40 | - @getflywheel/local provides type definitions for Local's Add-on API. 41 | - Node Module: https://www.npmjs.com/package/@getflywheel/local-components 42 | - GitHub Repo: https://github.com/getflywheel/local-components 43 | 44 | - @getflywheel/local-components provides reusable React components to use in your Local add-on. 45 | - Node Module: https://www.npmjs.com/package/@getflywheel/local 46 | - GitHub Repo: https://github.com/getflywheel/local-addon-api 47 | - Style Guide: https://getflywheel.github.io/local-components 48 | -------------------------------------------------------------------------------- /icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /lib/SiteInfoExtender.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | var desc = Object.getOwnPropertyDescriptor(m, k); 5 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { 6 | desc = { enumerable: true, get: function() { return m[k]; } }; 7 | } 8 | Object.defineProperty(o, k2, desc); 9 | }) : (function(o, m, k, k2) { 10 | if (k2 === undefined) k2 = k; 11 | o[k2] = m[k]; 12 | })); 13 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { 14 | Object.defineProperty(o, "default", { enumerable: true, value: v }); 15 | }) : function(o, v) { 16 | o["default"] = v; 17 | }); 18 | var __importStar = (this && this.__importStar) || function (mod) { 19 | if (mod && mod.__esModule) return mod; 20 | var result = {}; 21 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); 22 | __setModuleDefault(result, mod); 23 | return result; 24 | }; 25 | Object.defineProperty(exports, "__esModule", { value: true }); 26 | const local_components_1 = require("@getflywheel/local-components"); 27 | const react_1 = __importStar(require("react")); 28 | const electron_1 = require("electron"); 29 | const SiteInfoExtender = ({ site }) => { 30 | const [state, setState] = (0, react_1.useReducer)((s, a) => (Object.assign(Object.assign({}, s), a)), { 31 | rootPath: '{{root}}', 32 | imageURL: `http://${site.domain}`, 33 | updating: false, 34 | }); 35 | const { rootPath, imageURL, updating } = state; 36 | (0, react_1.useEffect)(() => { 37 | if (site.id) { 38 | setState({ 39 | rootPath: (site === null || site === void 0 ? void 0 : site.rootPath) || '{{root}}', 40 | imageURL: (site === null || site === void 0 ? void 0 : site.imageURL) || `http://${site.domain}`, 41 | updating: false, 42 | }); 43 | } 44 | }, [site.id]); 45 | const saveCustomConfig = () => { 46 | setState({ updating: true }); 47 | electron_1.ipcRenderer.send('update-custom-config', site.id, site.path, rootPath, imageURL); 48 | const timeout = setTimeout(() => { 49 | setState({ updating: false }); 50 | }, 2000); 51 | }; 52 | const saveMultiSiteConfig = () => { 53 | setState({ updating: true }); 54 | electron_1.ipcRenderer.send('update-multisite-config', site.id, site.path); 55 | const timeout = setTimeout(() => { 56 | setState({ updating: false }); 57 | }, 2000); 58 | }; 59 | const convertToMultiSite = () => { 60 | setState({ updating: true }); 61 | electron_1.ipcRenderer.send('update-multisite-config', site.id, site.path); 62 | electron_1.ipcRenderer.send('convert-to-multisite', site.id); 63 | const timeout = setTimeout(() => { 64 | setState({ updating: false }); 65 | }, 2000); 66 | }; 67 | console.log(site); 68 | return (react_1.default.createElement(react_1.default.Fragment, null, 69 | site.multiSite && (react_1.default.createElement(local_components_1.TableListRow, { key: 'fixmultisite', label: ' ' }, 70 | react_1.default.createElement(local_components_1.TextButton, { disabled: updating ? 'true' : '', style: { paddingLeft: 0, float: 'right' }, onClick: saveMultiSiteConfig }, "Fix multisite config"))), 71 | !(site === null || site === void 0 ? void 0 : site.multiSite) && (react_1.default.createElement(local_components_1.TableListRow, { key: 'convertMultisite', label: ' ' }, 72 | react_1.default.createElement(local_components_1.TextButton, { disabled: updating ? 'true' : '', style: { paddingLeft: 0, float: 'right' }, onClick: convertToMultiSite }, "Convert to multisite (subdir)"))), 73 | react_1.default.createElement(local_components_1.TableListRow, { key: 'rootpath', label: 'Root Path' }, 74 | react_1.default.createElement(local_components_1.BasicInput, { value: rootPath, className: 'inputText', onChange: event => { 75 | setState({ rootPath: event.target.value }); 76 | } })), 77 | react_1.default.createElement(local_components_1.TableListRow, { key: 'remote-images', label: 'Remote Image URL' }, 78 | react_1.default.createElement(local_components_1.BasicInput, { value: imageURL, className: 'inputText', onChange: event => { 79 | setState({ imageURL: event.target.value }); 80 | } }), 81 | react_1.default.createElement(local_components_1.Text, { size: 'caption' }, "Note: This will throw a 502 error if the url is invalid.")), 82 | react_1.default.createElement(local_components_1.TableListRow, { key: 'save', label: ' ' }, 83 | react_1.default.createElement(local_components_1.TextButton, { disabled: updating ? 'true' : '', style: { paddingLeft: 0, float: 'right' }, onClick: saveCustomConfig }, updating ? 'Applied' : 'Apply')))); 84 | }; 85 | exports.default = SiteInfoExtender; 86 | //# sourceMappingURL=SiteInfoExtender.js.map -------------------------------------------------------------------------------- /lib/SiteInfoExtender.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"SiteInfoExtender.js","sourceRoot":"","sources":["../src/SiteInfoExtender.jsx"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAAA,oEAKuC;AAEvC,+CAA+D;AAC/D,uCAAuC;AAEvC,MAAM,gBAAgB,GAAG,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE;IACrC,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,IAAA,kBAAU,EAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,iCAAM,CAAC,GAAK,CAAC,EAAG,EAAE;QAChE,QAAQ,EAAE,UAAU;QACpB,QAAQ,EAAE,UAAU,IAAI,CAAC,MAAM,EAAE;QACjC,QAAQ,EAAE,KAAK;KACf,CAAC,CAAC;IAEH,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IAE/C,IAAA,iBAAS,EAAC,GAAG,EAAE;QACd,IAAI,IAAI,CAAC,EAAE,EAAE;YACZ,QAAQ,CAAC;gBACR,QAAQ,EAAE,CAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,QAAQ,KAAI,UAAU;gBACtC,QAAQ,EAAE,CAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,QAAQ,KAAI,UAAU,IAAI,CAAC,MAAM,EAAE;gBACnD,QAAQ,EAAE,KAAK;aACf,CAAC,CAAC;SACH;IACF,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;IAEd,MAAM,gBAAgB,GAAG,GAAG,EAAE;QAC7B,QAAQ,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;QAE7B,sBAAW,CAAC,IAAI,CACf,sBAAsB,EACtB,IAAI,CAAC,EAAE,EACP,IAAI,CAAC,IAAI,EACT,QAAQ,EACR,QAAQ,CACR,CAAC;QAEF,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE;YAC/B,QAAQ,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;QAC/B,CAAC,EAAE,IAAI,CAAC,CAAC;IACV,CAAC,CAAC;IAEF,MAAM,mBAAmB,GAAG,GAAG,EAAE;QAChC,QAAQ,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;QAE7B,sBAAW,CAAC,IAAI,CAAC,yBAAyB,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QAEhE,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE;YAC/B,QAAQ,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;QAC/B,CAAC,EAAE,IAAI,CAAC,CAAC;IACV,CAAC,CAAC;IAEF,MAAM,kBAAkB,GAAG,GAAG,EAAE;QAC/B,QAAQ,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;QAE7B,sBAAW,CAAC,IAAI,CAAC,yBAAyB,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QAEhE,sBAAW,CAAC,IAAI,CAAC,sBAAsB,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;QAElD,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE;YAC/B,QAAQ,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;QAC/B,CAAC,EAAE,IAAI,CAAC,CAAC;IACV,CAAC,CAAC;IAEF,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAElB,OAAO,CACN;QACE,IAAI,CAAC,SAAS,IAAI,CAClB,8BAAC,+BAAY,IAAC,GAAG,EAAC,cAAc,EAAC,KAAK,EAAC,GAAG;YACzC,8BAAC,6BAAU,IACV,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAChC,KAAK,EAAE,EAAE,WAAW,EAAE,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,EACzC,OAAO,EAAE,mBAAmB,2BAGhB,CACC,CACf;QACA,CAAC,CAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,SAAS,CAAA,IAAI,CACpB,8BAAC,+BAAY,IAAC,GAAG,EAAC,kBAAkB,EAAC,KAAK,EAAC,GAAG;YAC7C,8BAAC,6BAAU,IACV,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAChC,KAAK,EAAE,EAAE,WAAW,EAAE,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,EACzC,OAAO,EAAE,kBAAkB,oCAGf,CACC,CACf;QACD,8BAAC,+BAAY,IAAC,GAAG,EAAC,UAAU,EAAC,KAAK,EAAC,WAAW;YAC7C,8BAAC,6BAAU,IACV,KAAK,EAAE,QAAQ,EACf,SAAS,EAAC,WAAW,EACrB,QAAQ,EAAE,KAAK,CAAC,EAAE;oBACjB,QAAQ,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;gBAC5C,CAAC,GACA,CACY;QACf,8BAAC,+BAAY,IAAC,GAAG,EAAC,eAAe,EAAC,KAAK,EAAC,kBAAkB;YACzD,8BAAC,6BAAU,IACV,KAAK,EAAE,QAAQ,EACf,SAAS,EAAC,WAAW,EACrB,QAAQ,EAAE,KAAK,CAAC,EAAE;oBACjB,QAAQ,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;gBAC5C,CAAC,GACA;YACF,8BAAC,uBAAI,IAAC,IAAI,EAAC,SAAS,+DAEb,CACO;QACf,8BAAC,+BAAY,IAAC,GAAG,EAAC,MAAM,EAAC,KAAK,EAAC,GAAG;YACjC,8BAAC,6BAAU,IACV,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAChC,KAAK,EAAE,EAAE,WAAW,EAAE,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,EACzC,OAAO,EAAE,gBAAgB,IAExB,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CACnB,CACC,CACb,CACH,CAAC;AACH,CAAC,CAAC;AAEF,kBAAe,gBAAgB,CAAC"} -------------------------------------------------------------------------------- /lib/conf/site.conf.hbs: -------------------------------------------------------------------------------- 1 | upstream php { 2 | {{#each fastcgi_servers}} 3 | server {{this}}; 4 | {{/each}} 5 | } 6 | 7 | server { 8 | listen {{port}}; 9 | root "{{root}}"; 10 | 11 | index index.php index.html index.htm; 12 | 13 | # 14 | # Generic restrictions for things like PHP files in uploads 15 | # 16 | include includes/restrictions.conf; 17 | 18 | # 19 | # Gzip rules 20 | # 21 | include includes/gzip.conf; 22 | 23 | # 24 | # WordPress Rules 25 | # 26 | 27 | include includes/wordpress-multi.conf; 28 | 29 | 30 | # 31 | # Forward 404's to WordPress 32 | # 33 | error_page 404 = @wperror; 34 | location @wperror { 35 | rewrite ^/(.*)$ /index.php?q=$1 last; 36 | } 37 | 38 | # 39 | # Static file rules 40 | # 41 | location ~* \.(?:css|js)$ { 42 | access_log off; 43 | log_not_found off; 44 | add_header Cache-Control "no-cache, public, must-revalidate, proxy-revalidate"; 45 | } 46 | 47 | location ~* ^.+\.(svg|svgz|jpg|jpeg|gif|png|ico|bmp)$ { 48 | try_files $uri @image_fallback; 49 | } 50 | 51 | location @image_fallback { 52 | proxy_pass {{image_url}}; 53 | } 54 | 55 | location ~* \.(?:eot|woff|woff2|ttf|svg|otf) { 56 | access_log off; 57 | log_not_found off; 58 | 59 | expires 5m; 60 | add_header Cache-Control "public"; 61 | 62 | # allow CORS requests 63 | add_header Access-Control-Allow-Origin *; 64 | } 65 | 66 | # 67 | # PHP-FPM 68 | # 69 | location ~ \.php$ { 70 | try_files $uri =404; 71 | 72 | fastcgi_split_path_info ^(.+\.php)(/.+)$; 73 | 74 | fastcgi_param QUERY_STRING $query_string; 75 | fastcgi_param REQUEST_METHOD $request_method; 76 | fastcgi_param CONTENT_TYPE $content_type; 77 | fastcgi_param CONTENT_LENGTH $content_length; 78 | 79 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 80 | fastcgi_param SCRIPT_NAME $fastcgi_script_name; 81 | fastcgi_param PATH_INFO $fastcgi_path_info; 82 | fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info; 83 | fastcgi_param REQUEST_URI $request_uri; 84 | fastcgi_param DOCUMENT_URI $document_uri; 85 | fastcgi_param DOCUMENT_ROOT $document_root; 86 | fastcgi_param SERVER_PROTOCOL $server_protocol; 87 | 88 | fastcgi_param GATEWAY_INTERFACE CGI/1.1; 89 | fastcgi_param SERVER_SOFTWARE nginx/$nginx_version; 90 | 91 | fastcgi_param REMOTE_ADDR $remote_addr; 92 | fastcgi_param REMOTE_PORT $remote_port; 93 | fastcgi_param SERVER_ADDR $server_addr; 94 | fastcgi_param SERVER_PORT $server_port; 95 | fastcgi_param SERVER_NAME $host; 96 | 97 | fastcgi_param HTTPS $fastcgi_https; 98 | 99 | fastcgi_param REDIRECT_STATUS 200; 100 | 101 | fastcgi_index index.php; 102 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 103 | 104 | fastcgi_pass php; 105 | fastcgi_buffer_size 64k; 106 | fastcgi_buffers 32 32k; 107 | fastcgi_read_timeout 1200s; 108 | 109 | proxy_buffer_size 64k; 110 | proxy_buffers 32 32k; 111 | proxy_busy_buffers_size 256k; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /lib/conf/wordpress-multi.conf.hbs: -------------------------------------------------------------------------------- 1 | # Deny access to any files with a .php extension in the files directory 2 | # Works in sub-directory installs and also in multisite network 3 | # Keep logging the requests to parse later (or to pass to firewall utilities such as fail2ban) 4 | location ~* /(?:files)/.*\.php$ { 5 | deny all; 6 | } 7 | 8 | location ~ ^/([^/]+/)?files/(.+) { 9 | try_files /wp-content/blogs.dir/0/files/$2 /wp-includes/ms-files.php?file=$2; 10 | access_log off; 11 | log_not_found off; 12 | expires 5m; 13 | } 14 | 15 | if (!-e $request_filename) { 16 | rewrite /wp-admin$ $resolved_scheme://$host$uri/ permanent; 17 | rewrite ^(/[^/]+)?(/wp-.*) /wp$2 last; 18 | rewrite ^(/[^/]+)?(/.*\.php) /wp$2 last; 19 | } 20 | 21 | location / { 22 | try_files $uri $uri/ /index.php$is_args$args; 23 | } 24 | 25 | # Add trailing slash to */wp-admin requests. 26 | rewrite /wp-admin$ $resolved_scheme://$host$uri/ permanent; 27 | -------------------------------------------------------------------------------- /lib/main.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | // https://getflywheel.github.io/local-addon-api/modules/_local_main_.html 13 | const main_1 = require("@getflywheel/local/main"); 14 | const local_1 = require("@getflywheel/local"); 15 | const ServiceContainer = (0, main_1.getServiceContainer)(); 16 | function default_1(context) { 17 | const { electron, fileSystem } = context; 18 | const { siteProcessManager } = ServiceContainer.cradle; 19 | const { ipcMain } = electron; 20 | ipcMain.on('update-multisite-config', (event, siteId, sitePath) => __awaiter(this, void 0, void 0, function* () { 21 | const config = __dirname + '/conf/wordpress-multi.conf.hbs'; 22 | let confPath = sitePath + '/conf/nginx/includes/wordpress-multi.conf.hbs'; 23 | confPath = confPath.replace('~', process.env.HOME); 24 | try { 25 | // Check if the file exists 26 | try { 27 | yield fileSystem.access(confPath, fileSystem.constants.F_OK); 28 | } 29 | catch (error) { 30 | // If the file doesn't exist, create it 31 | yield fileSystem.writeFile(confPath, ''); // Initialize with an empty string or some default content 32 | } 33 | // Then copy your config file over 34 | yield fileSystem.copyFile(config, confPath); 35 | siteProcessManager.restart(main_1.SiteData.getSite(siteId)); 36 | } 37 | catch (error) { 38 | context.notifier.notify({ 39 | title: 'Nginx multisite config failed', 40 | message: String(error), 41 | }); 42 | ServiceContainer.cradle.localLogger.log('info', `Error moving wordpress-multi.conf ${siteId}: ${error}`); 43 | } 44 | })); 45 | ipcMain.on('convert-to-multisite', (event, siteId) => __awaiter(this, void 0, void 0, function* () { 46 | main_1.SiteData.updateSite(siteId, { 47 | id: siteId, 48 | multiSite: local_1.MultiSite.Subdir, 49 | }); 50 | })); 51 | ipcMain.on('update-custom-config', (event, siteId, sitePath, rootPath, imageURL) => __awaiter(this, void 0, void 0, function* () { 52 | const config = __dirname + '/conf/site.conf.hbs'; 53 | let confPath = sitePath + '/conf/nginx/site.conf.hbs'; 54 | confPath = confPath.replace('~', process.env.HOME); 55 | try { 56 | fileSystem.readFile(config, 'utf8', (error, data) => { 57 | if (error) 58 | throw error; 59 | // Replace the root path and image URL. 60 | data = data.replace('{{root}}', rootPath); 61 | data = data.replace('{{image_url}}', imageURL); 62 | fileSystem.writeFile(confPath, data, error => { 63 | if (error) 64 | throw error; 65 | ServiceContainer.cradle.localLogger.log('info', `site.conf updated for ${siteId}`); 66 | // TODO: Update from deprecated method. 67 | main_1.SiteData.updateSite(siteId, { 68 | id: siteId, 69 | // @ts-ignore 70 | rootPath: rootPath, 71 | imageURL: imageURL, 72 | }); 73 | // Restart site on save. 74 | siteProcessManager.restart(main_1.SiteData.getSite(siteId)); 75 | }); 76 | }); 77 | } 78 | catch (error) { 79 | context.notifier.notify({ 80 | title: 'Nginx config failed', 81 | message: String(error), 82 | }); 83 | ServiceContainer.cradle.localLogger.log('info', `Error moving site.conf ${siteId}: ${error}`); 84 | } 85 | })); 86 | } 87 | exports.default = default_1; 88 | //# sourceMappingURL=main.js.map -------------------------------------------------------------------------------- /lib/main.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /lib/renderer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const fs_extra_1 = __importDefault(require("fs-extra")); 7 | const path_1 = __importDefault(require("path")); 8 | const SiteInfoExtender_1 = __importDefault(require("./SiteInfoExtender")); 9 | const packageJSON = fs_extra_1.default.readJsonSync(path_1.default.join(__dirname, '../package.json')); 10 | const addonName = packageJSON['productName']; 11 | const addonID = packageJSON['slug']; 12 | function default_1(context) { 13 | const { React, hooks } = context; 14 | /** 15 | * Stylesheets. 16 | */ 17 | const stylesheetPath = path_1.default.resolve(__dirname, '../style.css'); 18 | hooks.addContent('stylesheets', () => React.createElement("link", { rel: "stylesheet", key: "dekode-styleesheet", href: stylesheetPath })); 19 | /** 20 | * Add fields to the Overview tab 21 | */ 22 | hooks.addContent('SiteInfoOverview_TableList', (site) => React.createElement(SiteInfoExtender_1.default, { key: "siteinfo", site: site })); 23 | } 24 | exports.default = default_1; 25 | //# sourceMappingURL=renderer.js.map -------------------------------------------------------------------------------- /lib/renderer.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"renderer.js","sourceRoot":"","sources":["../src/renderer.jsx"],"names":[],"mappings":";;;;;AAAA,wDAA0B;AAC1B,gDAAwB;AACxB,0EAAkD;AAGlD,MAAM,WAAW,GAAG,kBAAE,CAAC,YAAY,CAAC,cAAI,CAAC,IAAI,CAAC,SAAS,EAAE,iBAAiB,CAAC,CAAC,CAAC;AAC7E,MAAM,SAAS,GAAG,WAAW,CAAC,aAAa,CAAC,CAAC;AAC7C,MAAM,OAAO,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;AAEpC,mBAAyB,OAAO;IAC/B,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC;IAEjC;;OAEG;IACH,MAAM,cAAc,GAAG,cAAI,CAAC,OAAO,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;IAC/D,KAAK,CAAC,UAAU,CAAC,aAAa,EAAE,GAAG,EAAE,CAAC,8BAAM,GAAG,EAAC,YAAY,EAAC,GAAG,EAAC,oBAAoB,EAAC,IAAI,EAAE,cAAc,GAAI,CAAC,CAAC;IAEhH;;OAEG;IACH,KAAK,CAAC,UAAU,CAAC,4BAA4B,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,oBAAC,0BAAgB,IAAC,GAAG,EAAC,UAAU,EAAC,IAAI,EAAE,IAAI,GAAI,CAAC,CAAC;AAC3G,CAAC;AAbD,4BAaC"} -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "project-base-localwp-addon", 3 | "productName": "Project Base LocalWP Addon", 4 | "version": "1.0.0", 5 | "author": "Jørgen Stenshaugen", 6 | "keywords": [ 7 | "local-addon" 8 | ], 9 | "bgColor": "#51bb7b", 10 | "icon": "icon.svg", 11 | "slug": "project-base-localwp-addon", 12 | "description": "Helper features for project base setup", 13 | "renderer": "lib/renderer.js", 14 | "main": "lib/main.js", 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/DekodeInteraktiv/localwp-projectbase-addon" 18 | }, 19 | "license": "MIT", 20 | "scripts": { 21 | "start": "tsc --watch", 22 | "build": "tsc" 23 | }, 24 | "devDependencies": { 25 | "@babel/preset-react": "^7.12.13", 26 | "@getflywheel/eslint-config-local": "^1.0.4", 27 | "@getflywheel/local": "^9", 28 | "@graphql-tools/utils": "^10.8.6", 29 | "@types/classnames": "^2.2.11", 30 | "@types/dateformat": "^3.0.1", 31 | "@types/node": "^18.11.9", 32 | "@types/node-fetch": "^2.6.2", 33 | "@types/react": "^18.0.25", 34 | "eslint": "^5.0.0", 35 | "eslint-plugin-import": "^2.14.0", 36 | "eslint-plugin-react": "^7.11.1", 37 | "typescript": "^4" 38 | }, 39 | "peerDependencies": { 40 | "react": ">= 16.4.0", 41 | "react-dom": ">= 16.4.0", 42 | "react-router-dom": "^4.3.1" 43 | }, 44 | "dependencies": { 45 | "@getflywheel/local-components": "^17.8.0", 46 | "apollo-boost": "^0.4.9", 47 | "classnames": "^2.2.6", 48 | "cross-fetch": "^3.1.5", 49 | "dateformat": "^3.0.3", 50 | "fs-extra": "^9.0.1", 51 | "prop-types": "^15.6.2", 52 | "react": "^16.14.0", 53 | "react-dom": "^16.14.0", 54 | "react-redux": "^7.2.2", 55 | "react-router-dom": "^5.1.2" 56 | }, 57 | "bundledDependencies": [ 58 | "@getflywheel/local-components", 59 | "classnames", 60 | "dateformat", 61 | "prop-types", 62 | "@electron/remote" 63 | ], 64 | "engines": { 65 | "local-by-flywheel": ">=6.7.0" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/SiteInfoExtender.jsx: -------------------------------------------------------------------------------- 1 | import { 2 | TableListRow, 3 | TextButton, 4 | BasicInput, 5 | Text, 6 | } from '@getflywheel/local-components'; 7 | 8 | import React, { useEffect, useState, useReducer } from 'react'; 9 | import { ipcRenderer } from 'electron'; 10 | 11 | const SiteInfoExtender = ({ site }) => { 12 | const [state, setState] = useReducer((s, a) => ({ ...s, ...a }), { 13 | rootPath: '{{root}}', 14 | imageURL: `http://${site.domain}`, 15 | updating: false, 16 | }); 17 | 18 | const { rootPath, imageURL, updating } = state; 19 | 20 | useEffect(() => { 21 | if (site.id) { 22 | setState({ 23 | rootPath: site?.rootPath || '{{root}}', 24 | imageURL: site?.imageURL || `http://${site.domain}`, 25 | updating: false, 26 | }); 27 | } 28 | }, [site.id]); 29 | 30 | const saveCustomConfig = () => { 31 | setState({ updating: true }); 32 | 33 | ipcRenderer.send( 34 | 'update-custom-config', 35 | site.id, 36 | site.path, 37 | rootPath, 38 | imageURL 39 | ); 40 | 41 | const timeout = setTimeout(() => { 42 | setState({ updating: false }); 43 | }, 2000); 44 | }; 45 | 46 | const saveMultiSiteConfig = () => { 47 | setState({ updating: true }); 48 | 49 | ipcRenderer.send('update-multisite-config', site.id, site.path); 50 | 51 | const timeout = setTimeout(() => { 52 | setState({ updating: false }); 53 | }, 2000); 54 | }; 55 | 56 | const convertToMultiSite = () => { 57 | setState({ updating: true }); 58 | 59 | ipcRenderer.send('update-multisite-config', site.id, site.path); 60 | 61 | ipcRenderer.send('convert-to-multisite', site.id); 62 | 63 | const timeout = setTimeout(() => { 64 | setState({ updating: false }); 65 | }, 2000); 66 | }; 67 | 68 | return ( 69 | <> 70 | {site.multiSite && ( 71 | 72 | 77 | Fix multisite config 78 | 79 | 80 | )} 81 | {!site?.multiSite && ( 82 | 83 | 88 | Convert to multisite (subdir) 89 | 90 | 91 | )} 92 | 93 | { 97 | setState({ rootPath: event.target.value }); 98 | }} 99 | /> 100 | 101 | 102 | { 106 | setState({ imageURL: event.target.value }); 107 | }} 108 | /> 109 | 110 | Note: This will throw a 502 error if the url is invalid. 111 | 112 | 113 | 114 | 119 | {updating ? 'Applied' : 'Apply'} 120 | 121 | 122 | 123 | ); 124 | }; 125 | 126 | export default SiteInfoExtender; 127 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | // https://getflywheel.github.io/local-addon-api/modules/_local_main_.html 2 | import { getServiceContainer, SiteData } from '@getflywheel/local/main'; 3 | 4 | import { MultiSite } from '@getflywheel/local'; 5 | 6 | const ServiceContainer = getServiceContainer(); 7 | 8 | export default function (context) { 9 | const { electron, fileSystem } = context; 10 | const { siteProcessManager } = ServiceContainer.cradle; 11 | const { ipcMain } = electron; 12 | 13 | ipcMain.on('update-multisite-config', async (event, siteId, sitePath) => { 14 | const config = __dirname + '/conf/wordpress-multi.conf.hbs'; 15 | 16 | let confPath = 17 | sitePath + '/conf/nginx/includes/wordpress-multi.conf.hbs'; 18 | confPath = confPath.replace('~', process.env.HOME); 19 | 20 | try { 21 | // Check if the file exists 22 | try { 23 | await fileSystem.access(confPath, fileSystem.constants.F_OK); 24 | } catch (error) { 25 | // If the file doesn't exist, create it 26 | await fileSystem.writeFile(confPath, ''); // Initialize with an empty string or some default content 27 | } 28 | 29 | // Then copy your config file over 30 | await fileSystem.copyFile(config, confPath); 31 | 32 | siteProcessManager.restart(SiteData.getSite(siteId)); 33 | } catch (error) { 34 | context.notifier.notify({ 35 | title: 'Nginx multisite config failed', 36 | message: String(error), 37 | }); 38 | 39 | ServiceContainer.cradle.localLogger.log( 40 | 'info', 41 | `Error moving wordpress-multi.conf ${siteId}: ${error}` 42 | ); 43 | } 44 | }); 45 | 46 | ipcMain.on('convert-to-multisite', async (event, siteId) => { 47 | SiteData.updateSite(siteId, { 48 | id: siteId, 49 | multiSite: MultiSite.Subdir, 50 | }); 51 | }); 52 | 53 | ipcMain.on( 54 | 'update-custom-config', 55 | async (event, siteId, sitePath, rootPath, imageURL) => { 56 | const config = __dirname + '/conf/site.conf.hbs'; 57 | 58 | let confPath = sitePath + '/conf/nginx/site.conf.hbs'; 59 | confPath = confPath.replace('~', process.env.HOME); 60 | 61 | try { 62 | fileSystem.readFile(config, 'utf8', (error, data) => { 63 | if (error) throw error; 64 | 65 | // Replace the root path and image URL. 66 | data = data.replace('{{root}}', rootPath); 67 | data = data.replace('{{image_url}}', imageURL); 68 | 69 | fileSystem.writeFile(confPath, data, error => { 70 | if (error) throw error; 71 | 72 | ServiceContainer.cradle.localLogger.log( 73 | 'info', 74 | `site.conf updated for ${siteId}` 75 | ); 76 | 77 | // TODO: Update from deprecated method. 78 | SiteData.updateSite(siteId, { 79 | id: siteId, 80 | // @ts-ignore 81 | rootPath: rootPath, 82 | imageURL: imageURL, 83 | }); 84 | 85 | // Restart site on save. 86 | siteProcessManager.restart(SiteData.getSite(siteId)); 87 | }); 88 | }); 89 | } catch (error) { 90 | context.notifier.notify({ 91 | title: 'Nginx config failed', 92 | message: String(error), 93 | }); 94 | 95 | ServiceContainer.cradle.localLogger.log( 96 | 'info', 97 | `Error moving site.conf ${siteId}: ${error}` 98 | ); 99 | } 100 | } 101 | ); 102 | } 103 | -------------------------------------------------------------------------------- /src/renderer.jsx: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import path from 'path'; 3 | import SiteInfoExtender from './SiteInfoExtender'; 4 | 5 | 6 | const packageJSON = fs.readJsonSync(path.join(__dirname, '../package.json')); 7 | const addonName = packageJSON['productName']; 8 | const addonID = packageJSON['slug']; 9 | 10 | export default function (context) { 11 | const { React, hooks } = context; 12 | 13 | /** 14 | * Stylesheets. 15 | */ 16 | const stylesheetPath = path.resolve(__dirname, '../style.css'); 17 | hooks.addContent('stylesheets', () => ); 18 | 19 | /** 20 | * Add fields to the Overview tab 21 | */ 22 | hooks.addContent('SiteInfoOverview_TableList', (site) => ); 23 | } 24 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | .TableList .TableListRow .BasicInput.inputText input[type="text"] { 2 | padding: 0 10px; 3 | min-height: 30px; 4 | } 5 | 6 | .BasicInput { 7 | margin: 0; 8 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "checkJs": false, 5 | "declaration": false, 6 | "esModuleInterop": true, 7 | "experimentalDecorators": true, 8 | "jsx": "react", 9 | "lib": ["es2017", "esnext.asynciterable", "dom"], 10 | "module": "commonjs", 11 | "noImplicitReturns": true, 12 | "noImplicitAny": false, 13 | "outDir": "./lib", 14 | "resolveJsonModule": true, 15 | "rootDir": "./src", 16 | "sourceMap": true, 17 | "strict": false, 18 | "target": "es2015", 19 | "typeRoots": [ 20 | "./node_modules/@types" 21 | ], 22 | "skipLibCheck": true, 23 | "moduleResolution": "node", 24 | "allowSyntheticDefaultImports": true 25 | }, 26 | "exclude": [ 27 | "node_modules", 28 | "lib", 29 | "**/*.test.ts", 30 | "**/*.test.tsx", 31 | "**/*.spec.ts", 32 | "**/*.spec.tsx" 33 | ], 34 | "include": [ 35 | "src/**/*" 36 | ] 37 | } 38 | --------------------------------------------------------------------------------