├── .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 |
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 |
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":"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"}
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------