Skip to content

Commit cf53588

Browse files
authored
Allow project to be developed on Windows and non-Raspbian Linux
* Support for Windows development. * Support for non-Raspbian Linux development. * Improved build process, including optional automatic deployment of code on Raspberry Pi. * Fix Firefox SVG problems. * Add ability to proxy temperature/humidity sensor data.
1 parent 0701227 commit cf53588

21 files changed

Lines changed: 450 additions & 241 deletions

README.md

Lines changed: 47 additions & 32 deletions
Large diffs are not rendered by default.

build.ts

Lines changed: 117 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import * as chalk from 'chalk';
2-
import { ChildProcess, spawn } from 'child_process';
1+
import * as Chalk from 'chalk';
2+
import { ChildProcess, spawn as nodeSpawn } from 'child_process';
33
import * as copyfiles from 'copyfiles';
44
import * as fs from 'fs';
55
import { processMillis } from './server/src/util';
@@ -13,70 +13,144 @@ const SPIN_DELAY = 100;
1313
const MAX_SPIN_DELAY = 100;
1414
const NO_OP = () => {};
1515

16+
const isWindows = (process.platform === 'win32');
17+
1618
let spinStep = 0;
1719
let lastSpin = 0;
1820
let npmInitDone = false;
1921
let doAcu = false;
2022
let doDht = false;
23+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
2124
let doGps = false;
22-
let doWwvb = false;
2325
let doI2c = false;
26+
let doStdDeploy = false;
27+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
28+
let doWwvb = false;
29+
let isRaspberryPi = false;
30+
31+
const chalk = new Chalk.Instance();
32+
let canSpin = true;
33+
let backspace = '\x08';
34+
let trailingSpace = ' '; // Two spaces
35+
36+
if (process.platform === 'linux') {
37+
try {
38+
if (fs.existsSync('/proc/cpuinfo')) {
39+
const lines = fs.readFileSync('/proc/cpuinfo').toString().split('\n');
40+
41+
for (const line of lines) {
42+
if (/\bModel\s*:\s*Raspberry Pi\b/i.test(line)) {
43+
isRaspberryPi = true;
44+
break;
45+
}
46+
}
47+
}
48+
}
49+
catch (err) {
50+
console.error(chalk.red('Raspberry Pi check failed'));
51+
}
52+
}
2453

2554
// Remove extraneous command line args, if present.
26-
if (/[/\\]ts-node(?:\.cmd)?$/.test(process.argv[0] ?? ''))
55+
if (/\bts-node\b/.test(process.argv[0] ?? ''))
2756
process.argv.splice(0, 1);
2857

29-
if (/[/\\]build\.ts$/.test(process.argv[0] ?? ''))
58+
if (/\bbuild\.ts\b/.test(process.argv[0] ?? ''))
3059
process.argv.splice(0, 1);
3160

61+
if (process.argv.length === 0 && isRaspberryPi) {
62+
console.warn(chalk.yellow('Warning: no build options specified.'));
63+
console.warn(chalk.yellow('This could be OK, or this could mean you forgot the leading ') +
64+
chalk.white('--') + chalk.yellow(' before your options.'));
65+
}
66+
3267
process.argv.forEach(arg => {
3368
if (arg === '--acu')
3469
doAcu = true;
3570
else if (arg === '--dht')
3671
doDht = true;
3772
else if (arg === '--gps')
3873
doGps = doI2c = true;
74+
else if (arg === '--pt') {
75+
canSpin = false;
76+
chalk.level = 0;
77+
backspace = '';
78+
trailingSpace = ' ';
79+
}
80+
else if (arg === '--sd')
81+
doStdDeploy = true;
3982
else if (arg === '--wwvb')
4083
doWwvb = doI2c = true;
4184
else {
4285
if (arg !== '--help')
4386
console.error('Unrecognized option "' + chalk.red(arg) + '"');
4487

45-
console.log('Usage: npm run build [-- [--acu] [--dht] [--gps] [--help] [--wwvb]]');
88+
console.log('Usage: npm run build [-- [--acu] [--dht] [--help] [--pt] [--sd]]');
4689
process.exit(0);
4790
}
4891
});
4992

50-
process.stdout.write(('' + doAcu + doGps + doWwvb).substr(0, 0));
93+
if (doStdDeploy && !isRaspberryPi) {
94+
console.error(chalk.red('--sd option is only valid on Raspberry Pi'));
95+
process.exit(0);
96+
}
97+
98+
function spawn(command: string, args: string[] = [], options?: any): ChildProcess {
99+
if (isWindows) {
100+
const cmd = process.env.comspec || 'cmd';
101+
102+
return nodeSpawn(cmd, ['/c', command, ...args], options);
103+
}
104+
else
105+
return nodeSpawn(command, args, options);
106+
}
51107

52108
function spin(): void {
53109
const now = processMillis();
54110

55111
if (lastSpin < now - SPIN_DELAY) {
56112
lastSpin = now;
57-
process.stdout.write('\x08' + SPIN_CHARS.charAt(spinStep));
113+
process.stdout.write(backspace + SPIN_CHARS.charAt(spinStep));
58114
spinStep = (spinStep + 1) % 4;
59115
}
60116
}
61117

62118
function monitorProcess(proc: ChildProcess, doSpin = true): Promise<string> {
119+
let errors = '';
63120
let output = '';
64121

122+
doSpin = doSpin && canSpin;
123+
65124
return new Promise<string>((resolve, reject) => {
66125
const slowSpin = setInterval(doSpin ? spin : NO_OP, MAX_SPIN_DELAY);
67126

68-
proc.stderr.on('data', doSpin ? spin : NO_OP);
127+
proc.stderr.on('data', data => {
128+
(doSpin ? spin : NO_OP)();
129+
data = data.toString();
130+
// This gets confusing, because a lot of non-error progress messaging goes to stderr, and the
131+
// webpack process doesn't exit with an error for compilation errors unless you make it do so.
132+
if (/\[webpack.Progress]/.test(data))
133+
return;
134+
135+
errors += data;
136+
});
69137
proc.stdout.on('data', data => {
70-
output += data.toString();
71138
(doSpin ? spin : NO_OP)();
139+
data = data.toString();
140+
output += data;
141+
errors = '';
72142
});
73143
proc.on('error', err => {
74144
clearInterval(slowSpin);
75145
reject(err);
76146
});
77147
proc.on('close', () => {
78148
clearInterval(slowSpin);
79-
resolve(output);
149+
150+
if (errors && (/\b(error|exception)\b/i.test(errors) || /[_0-9a-z](Error|Exception)\b/.test(errors)))
151+
reject(errors);
152+
else
153+
resolve(output);
80154
});
81155
});
82156
}
@@ -108,56 +182,67 @@ async function npmInit(): Promise<void> {
108182
(async () => {
109183
try {
110184
console.log(chalk.cyan('Starting build...'));
111-
112-
process.stdout.write('Updating client ');
185+
process.stdout.write('Updating client' + trailingSpace);
113186
await monitorProcess(spawn('npm', ['--dev', 'update']));
114-
console.log('\x08' + chalk.green(CHECK_MARK));
187+
console.log(backspace + chalk.green(CHECK_MARK));
115188

116-
process.stdout.write('Building client ');
189+
process.stdout.write('Building client' + trailingSpace);
117190
if (fs.existsSync('dist'))
118191
await monitorProcess(spawn('rm', ['-Rf', 'dist']));
119192
let output = await monitorProcess(spawn('webpack'));
120-
console.log('\x08' + chalk.green(CHECK_MARK));
193+
console.log(backspace + chalk.green(CHECK_MARK));
121194
console.log(chalk.hex('#808080')(getWebpackSummary(output)));
122195

123-
process.stdout.write('Updating server ');
196+
process.stdout.write('Updating server' + trailingSpace);
124197
await monitorProcess(spawn('npm', ['--dev', 'update'], { cwd: path.join(__dirname, 'server') }));
125-
console.log('\x08' + chalk.green(CHECK_MARK));
198+
console.log(backspace + chalk.green(CHECK_MARK));
126199

127-
process.stdout.write('Building server ');
200+
process.stdout.write('Building server' + trailingSpace);
128201
if (fs.existsSync('server/dist'))
129202
await monitorProcess(spawn('rm', ['-Rf', 'server/dist']));
130-
output = await monitorProcess(spawn('npm', ['run', 'build'], { cwd: path.join(__dirname, 'server') }));
131-
console.log('\x08' + chalk.green(CHECK_MARK));
203+
output = await monitorProcess(spawn('npm', ['run', isWindows ? 'build-win' : 'build'], { cwd: path.join(__dirname, 'server') }));
204+
console.log(backspace + chalk.green(CHECK_MARK));
132205
console.log(chalk.hex('#808080')(getWebpackSummary(output)));
133206

134207
if (doAcu) {
135-
process.stdout.write('Adding Acu-Rite wireless temperature/humidity sensor support ');
208+
process.stdout.write('Adding Acu-Rite wireless temperature/humidity sensor support' + trailingSpace);
136209
await npmInit();
137-
await monitorProcess(spawn('npm', ['i', 'rpi-acu-rite-temperature'], { cwd: path.join(__dirname, 'server', 'dist') }));
138-
console.log('\x08' + chalk.green(CHECK_MARK));
210+
await monitorProcess(spawn('npm', ['i', 'rpi-acu-rite-temperature@2.x'], { cwd: path.join(__dirname, 'server', 'dist') }));
211+
console.log(backspace + chalk.green(CHECK_MARK));
139212
}
140213

141214
if (doDht) {
142-
process.stdout.write('Adding DHT wired temperature/humidity sensor support ');
215+
process.stdout.write('Adding DHT wired temperature/humidity sensor support' + trailingSpace);
143216
await npmInit();
144-
await monitorProcess(spawn('npm', ['i', 'node-dht-sensor'], { cwd: path.join(__dirname, 'server', 'dist') }));
145-
console.log('\x08' + chalk.green(CHECK_MARK));
217+
await monitorProcess(spawn('npm', ['i', 'node-dht-sensor@0.4.x'], { cwd: path.join(__dirname, 'server', 'dist') }));
218+
console.log(backspace + chalk.green(CHECK_MARK));
146219
}
147220

148221
if (doI2c) {
149-
process.stdout.write('Adding I²C serial bus support ');
222+
process.stdout.write('Adding I²C serial bus support' + trailingSpace);
150223
await npmInit();
151224
await monitorProcess(spawn('npm', ['i', 'i2c-bus'], { cwd: path.join(__dirname, 'server', 'dist') }));
152-
console.log('\x08' + chalk.green(CHECK_MARK));
225+
console.log(backspace + chalk.green(CHECK_MARK));
153226
}
154227

155-
process.stdout.write('Copying server to top-level dist directory ');
228+
process.stdout.write('Copying server to top-level dist directory' + trailingSpace);
156229
await (promisify(copyfiles) as any)(['server/dist/**/*', 'dist/'], { up: 2 });
157-
console.log('\x08' + chalk.green(CHECK_MARK));
230+
console.log(backspace + chalk.green(CHECK_MARK));
231+
232+
if (doStdDeploy) {
233+
process.stdout.write('Moving server to ~/weather directory' + trailingSpace);
234+
235+
if (!fs.existsSync(process.env.HOME + '/weather'))
236+
fs.mkdirSync(process.env.HOME + '/weather');
237+
else
238+
await monitorProcess(spawn('rm', ['-Rf', '~/weather/*'], { shell: true }));
239+
240+
await monitorProcess(spawn('mv', ['dist/*', '~/weather'], { shell: true }));
241+
console.log(backspace + chalk.green(CHECK_MARK));
242+
}
158243
}
159244
catch (err) {
160-
console.log('\x08' + chalk.red(FAIL_MARK));
245+
console.log(backspace + chalk.red(FAIL_MARK));
161246
console.error(err);
162247
}
163248
})();

package-lock.json

Lines changed: 29 additions & 29 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)