Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(client): client/src/cordova/dev action #2111

Merged
merged 13 commits into from
Aug 12, 2024
23 changes: 23 additions & 0 deletions client/build/make_replacements.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright 2024 The Outline Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import replace from 'replace-in-file';

export async function makeReplacements(replacements) {
let results = [];

for (const replacement of replacements) {
results = [...results, ...(await replace(replacement))];
}
}
1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
"eslint-plugin-import": "^2.26.0",
"esm": "^3.2.25",
"file-loader": "^6.2.0",
"folder-hash": "^4.0.4",
"html-webpack-plugin": "^5.1.0",
"husky": "^1.3.1",
"i18n-strings-files": "^2.0.0",
Expand Down
142 changes: 142 additions & 0 deletions client/src/cordova/dev.action.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// Copyright 2022 The Outline Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import os from 'os';
import path from 'path';
import url from 'url';

import {createReloadServer} from '@outline/infrastructure/build/create_reload_server.mjs';
import {getRootDir} from '@outline/infrastructure/build/get_root_dir.mjs';
import {runAction} from '@outline/infrastructure/build/run_action.mjs';
import {spawnStream} from '@outline/infrastructure/build/spawn_stream.mjs';
import {hashElement} from 'folder-hash';
import * as fs from 'fs-extra';
import minimist from 'minimist';

import {makeReplacements} from '../../build/make_replacements.mjs';

const OUTPUT_PATH = 'output/build/client/macos';
const OUTLINE_APP_PATH = 'Debug/Outline.app';
const OUTLINE_APP_WWW_PATH = 'Contents/Resources/www';
const RELOAD_SERVER_PORT = 35729;

const getUIHash = async () => {
const hashResult = await hashElement(
path.join(getRootDir(), 'client/src/www'),
{
files: {include: ['**/*.ts', '**/*.html', '**/*.css', '**/*.js']},
}
);

return hashResult.hash;
};

/**
* @description Runs the cordova app in development mode.
*
* @param {string[]} parameters
*/
export async function main(...givenParameters) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, thanks. This is better.

const {
_: [platform = 'macos'],
buildMode = 'release',
sentryDsn = 'https://public@sentry.example.com/1',
versionName = '0.0.0-dev',
} = minimist(givenParameters);

if (platform !== 'macos') {
throw new Error('This action currently only works for the MacOS platform.');
}

if (buildMode !== 'release') {
throw new Error('MacOS only renders properly in the release build mode.');
}

if (os.platform() !== 'darwin') {
throw new Error('You must be on MacOS to develop for MacOS.');
}

const parameters = [
'macos',
'--buildMode=release',
`--sentryDsn=${sentryDsn}`,
`--versionName=${versionName}`,
];

await runAction('client/src/www/build', ...parameters);
await runAction('client/go/build', ...parameters);
await runAction('client/src/cordova/setup', ...parameters);

await makeReplacements([
{
files: path.join(
getRootDir(),
'client/platforms/osx/**/index_cordova.html'
),
from: '<app-root></app-root>',
to: `<app-root></app-root>

<script>
try {
const reloadSocket = new WebSocket("ws://localhost:35729");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need to hard-code the server? Can this be a dynamic port of a unix socket instead?

You start the reload server, so you might as well use a dynamic port to avoid port conflicts.


reloadSocket.onopen = () => console.log("LiveReload connected~");
reloadSocket.onmessage = ({ data }) => data === "reload" && location.reload();
} catch (e) {
// nevermind
}
</script>`,
},
]);

await spawnStream(
'xcodebuild',
'-scheme',
'Outline',
'-workspace',
path.join(getRootDir(), 'client/src/cordova/apple/macos.xcworkspace'),
`SYMROOT=${path.join(getRootDir(), OUTPUT_PATH)}`
);

await spawnStream(
'open',
path.join(getRootDir(), OUTPUT_PATH, OUTLINE_APP_PATH)
);

let previousUIHashResult = await getUIHash();

console.log(`Starting reload server @ port ${RELOAD_SERVER_PORT}...`);
createReloadServer(async () => {
const currentUIHashResult = await getUIHash();

if (previousUIHashResult === currentUIHashResult) {
return false;
}

previousUIHashResult = currentUIHashResult;

await runAction('client/src/www/build', ...parameters);

await fs.copy(
path.join(getRootDir(), 'client/www'),
path.join(OUTPUT_PATH, OUTLINE_APP_PATH, OUTLINE_APP_WWW_PATH)
);

return true;
}).listen(RELOAD_SERVER_PORT);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe you are listening on the "any" address here. This is a security issue. Please change this to localhost.

Also, use port zero, so that the OS assign a port for you. Then read the port (really, the full address) to pass it to the websocket url.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the tips - this is great. Since we merged this I'll do it in a follow up PR ASAP.

}

if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
await main(...process.argv.slice(2));
}
101 changes: 74 additions & 27 deletions client/src/cordova/setup.action.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ import {runAction} from '@outline/infrastructure/build/run_action.mjs';
import {spawnStream} from '@outline/infrastructure/build/spawn_stream.mjs';
import chalk from 'chalk';
import cordovaLib from 'cordova-lib';
import replace from 'replace-in-file';
import rmfr from 'rmfr';

import {getBuildParameters} from '../../build/get_build_parameters.mjs';
import {makeReplacements} from '../../build/make_replacements.mjs';

const {cordova} = cordovaLib;
const WORKING_CORDOVA_OSX_COMMIT = '07e62a53aa6a8a828fd988bc9e884c38c3495a67';
Expand All @@ -37,17 +37,20 @@ const WORKING_CORDOVA_OSX_COMMIT = '07e62a53aa6a8a828fd988bc9e884c38c3495a67';
* @param {string[]} parameters
*/
export async function main(...parameters) {
const {platform, buildMode, verbose, buildNumber, versionName} = getBuildParameters(parameters);
const {platform, buildMode, verbose, buildNumber, versionName} =
getBuildParameters(parameters);

await runAction('client/src/www/build', ...parameters);
await runAction('client/go/build', ...parameters);
const CORDOVA_PROJECT_DIR = path.resolve(getRootDir(), 'client');

const CORDOVA_PROJECT_DIR = path.resolve(getRootDir(), 'client');
await rmfr(path.resolve(CORDOVA_PROJECT_DIR, 'platforms'));
await rmfr(path.resolve(CORDOVA_PROJECT_DIR, 'plugins'));

if (verbose) {
cordova.on('verbose', message => console.debug(`[cordova:verbose] ${message}`));
cordova.on('verbose', message =>
console.debug(`[cordova:verbose] ${message}`)
);
}

// this is so cordova doesn't complain about not being in a cordova project
Expand All @@ -57,7 +60,9 @@ export async function main(...parameters) {
case 'android' + 'debug':
return androidDebug(verbose);
case 'android' + 'release':
console.warn('NOTE: You must open the Outline.zip file after building to upload to the Play Store.');
console.warn(
'NOTE: You must open the Outline.zip file after building to upload to the Play Store.'
);
return androidRelease(versionName, buildNumber, verbose);
case 'ios' + 'debug':
case 'maccatalyst' + 'debug':
Expand Down Expand Up @@ -86,23 +91,27 @@ async function androidDebug(verbose) {
});
}

async function makeReplacements(replacements) {
let results = [];

for (const replacement of replacements) {
results = [...results, ...(await replace(replacement))];
}
}

async function androidRelease(versionName, buildNumber, verbose) {
await cordova.prepare({
platforms: ['android'],
save: false,
verbose,
});

const manifestXmlGlob = path.join(getRootDir(), 'platforms', 'android', '**', 'AndroidManifest.xml');
const configXmlGlob = path.join(getRootDir(), 'platforms', 'android', '**', 'config.xml');
const manifestXmlGlob = path.join(
getRootDir(),
'platforms',
'android',
'**',
'AndroidManifest.xml'
);
const configXmlGlob = path.join(
getRootDir(),
'platforms',
'android',
'**',
'config.xml'
);

await makeReplacements([
{
Expand Down Expand Up @@ -130,7 +139,9 @@ async function androidRelease(versionName, buildNumber, verbose) {

async function appleIosDebug(verbose) {
if (os.platform() !== 'darwin') {
throw new Error('Building an Apple binary requires xcodebuild and can only be done on MacOS');
throw new Error(
'Building an Apple binary requires xcodebuild and can only be done on MacOS'
);
}

await cordova.prepare({
Expand All @@ -140,19 +151,32 @@ async function appleIosDebug(verbose) {
});

// TODO(daniellacosse): move this to a cordova hook
await spawnStream('rsync', '-avc', 'src/cordova/apple/xcode/ios/', 'platforms/ios/');
await spawnStream(
'rsync',
'-avc',
'src/cordova/apple/xcode/ios/',
'platforms/ios/'
);
}

async function appleMacOsDebug(verbose) {
if (os.platform() !== 'darwin') {
throw new Error('Building an Apple binary requires xcodebuild and can only be done on MacOS');
throw new Error(
'Building an Apple binary requires xcodebuild and can only be done on MacOS'
);
}

console.warn(
chalk.yellow('Debug mode on the MacOS client is currently broken. Try running with `--buildMode=release` instead.')
chalk.yellow(
'Debug mode on the MacOS client is currently broken. Try running with `--buildMode=release` instead.'
)
);

await cordova.platform('add', [`github:apache/cordova-osx#${WORKING_CORDOVA_OSX_COMMIT}`], {save: false});
await cordova.platform(
'add',
[`github:apache/cordova-osx#${WORKING_CORDOVA_OSX_COMMIT}`],
{save: false}
);

await cordova.prepare({
platforms: ['osx'],
Expand All @@ -161,7 +185,12 @@ async function appleMacOsDebug(verbose) {
});

// TODO(daniellacosse): move this to a cordova hook
await spawnStream('rsync', '-avc', 'src/cordova/apple/xcode/macos/', 'platforms/osx/');
await spawnStream(
'rsync',
'-avc',
'src/cordova/apple/xcode/macos/',
'platforms/osx/'
);
}

async function setAppleVersion(platform, versionName, buildNumber) {
Expand All @@ -181,7 +210,9 @@ async function setAppleVersion(platform, versionName, buildNumber) {

async function appleIosRelease(version, buildNumber, verbose) {
if (os.platform() !== 'darwin') {
throw new Error('Building an Apple binary requires xcodebuild and can only be done on MacOS');
throw new Error(
'Building an Apple binary requires xcodebuild and can only be done on MacOS'
);
}

await cordova.prepare({
Expand All @@ -191,17 +222,28 @@ async function appleIosRelease(version, buildNumber, verbose) {
});

// TODO(daniellacosse): move this to a cordova hook
await spawnStream('rsync', '-avc', 'src/cordova/apple/xcode/ios/', 'platforms/ios/');
await spawnStream(
'rsync',
'-avc',
'src/cordova/apple/xcode/ios/',
'platforms/ios/'
);

await setAppleVersion('ios', version, buildNumber);
}

async function appleMacOsRelease(version, buildNumber, verbose) {
if (os.platform() !== 'darwin') {
throw new Error('Building an Apple binary requires xcodebuild and can only be done on MacOS');
throw new Error(
'Building an Apple binary requires xcodebuild and can only be done on MacOS'
);
}

await cordova.platform('add', [`github:apache/cordova-osx#${WORKING_CORDOVA_OSX_COMMIT}`], {save: false});
await cordova.platform(
'add',
[`github:apache/cordova-osx#${WORKING_CORDOVA_OSX_COMMIT}`],
{save: false}
);

await cordova.prepare({
platforms: ['osx'],
Expand All @@ -210,7 +252,12 @@ async function appleMacOsRelease(version, buildNumber, verbose) {
});

// TODO(daniellacosse): move this to a cordova hook
await spawnStream('rsync', '-avc', 'src/cordova/apple/xcode/macos/', 'platforms/osx/');
await spawnStream(
'rsync',
'-avc',
'src/cordova/apple/xcode/macos/',
'platforms/osx/'
);

await setAppleVersion('osx', version, buildNumber);
}
Expand Down
1 change: 0 additions & 1 deletion client/src/www/index_cordova.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,4 @@
</head>
<body unresolved>
<app-root></app-root>
</body>
</html>
Loading
Loading