Skip to content

Commit e450e3c

Browse files
authored
Improve community site local dev workflow (fixes watch & hot-reloading) (#1674)
* add qol fixes * fix live reload on ract components * Revert package-lock.json churn * limit rebuilds to dev * respond to Copilot PR comments * respond to Copilot PR comments
1 parent 79a20d8 commit e450e3c

4 files changed

Lines changed: 151 additions & 25 deletions

File tree

app/services/LibraryDb.js

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,26 @@
11
"use strict";
22

3+
import fs from "fs";
4+
import path from "path";
35
import _ from "underscore";
46

5-
import StepTemplates from "./step-templates.json";
6-
77
class LibraryDb {
88
constructor() {
9-
this._items = _.chain(StepTemplates.items)
9+
this._items = null;
10+
this._all = null;
11+
}
12+
13+
isDevelopment() {
14+
return process.env.NODE_ENV === "development";
15+
}
16+
17+
readTemplatesFromDisk() {
18+
const templatePath = path.join(__dirname, "step-templates.json");
19+
return JSON.parse(fs.readFileSync(templatePath, "utf8"));
20+
}
21+
22+
hydrateTemplates(stepTemplates) {
23+
const items = _.chain(stepTemplates.items)
1024
.map(function (t) {
1125
if (t.Properties) {
1226
var script = t.Properties["Octopus.Action.Script.ScriptBody"];
@@ -42,15 +56,39 @@ class LibraryDb {
4256
})
4357
.value();
4458

45-
this._all = _.indexBy(this._items, "Id");
59+
return {
60+
items,
61+
all: _.indexBy(items, "Id"),
62+
};
63+
}
64+
65+
loadTemplates() {
66+
return this.hydrateTemplates(this.readTemplatesFromDisk());
67+
}
68+
69+
getTemplates() {
70+
if (this.isDevelopment()) {
71+
return this.loadTemplates();
72+
}
73+
74+
if (!this._items || !this._all) {
75+
const templates = this.loadTemplates();
76+
this._items = templates.items;
77+
this._all = templates.all;
78+
}
79+
80+
return {
81+
items: this._items,
82+
all: this._all,
83+
};
4684
}
4785

4886
list(cb) {
49-
cb(null, this._items);
87+
cb(null, this.getTemplates().items);
5088
}
5189

5290
get(id, cb) {
53-
var item = this._all[id];
91+
var item = this.getTemplates().all[id];
5492
cb(null, item);
5593
}
5694
}

gulpfile.babel.js

Lines changed: 87 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,11 @@ import jasmineReporters from "jasmine-reporters";
3030
import jasmineTerminalReporter from "jasmine-terminal-reporter";
3131
import eventStream from "event-stream";
3232
import fs from "fs";
33+
import http from "http";
34+
import https from "https";
3335
import jsonlint from "gulp-jsonlint";
3436
import path from "path";
37+
import { spawn } from "child_process";
3538

3639
const sass = gulpSass(dartSass);
3740
const clientDir = "app";
@@ -49,6 +52,54 @@ const $ = gulpLoadPlugins({
4952
const reload = browserSync.reload;
5053
const argv = yargs.argv;
5154

55+
function openBrowser(url) {
56+
if (process.env.CI) {
57+
return;
58+
}
59+
60+
if (process.platform === "darwin") {
61+
spawn("open", [url], { detached: true, stdio: "ignore" }).unref();
62+
return;
63+
}
64+
65+
if (process.platform === "win32") {
66+
spawn("cmd", ["/c", "start", "", url], { detached: true, stdio: "ignore" }).unref();
67+
return;
68+
}
69+
70+
spawn("xdg-open", [url], { detached: true, stdio: "ignore" }).unref();
71+
}
72+
73+
function waitForServer(url, { timeoutMs = 10000, pollIntervalMs = 200 } = {}) {
74+
const parsedUrl = new URL(url);
75+
const client = parsedUrl.protocol === "https:" ? https : http;
76+
const startedAt = Date.now();
77+
78+
return new Promise((resolve) => {
79+
function tryConnect() {
80+
const request = client.get(url, (response) => {
81+
response.resume();
82+
resolve(true);
83+
});
84+
85+
request.on("error", () => {
86+
if (Date.now() - startedAt >= timeoutMs) {
87+
resolve(false);
88+
return;
89+
}
90+
91+
setTimeout(tryConnect, pollIntervalMs);
92+
});
93+
94+
request.setTimeout(pollIntervalMs, () => {
95+
request.destroy(new Error("timeout"));
96+
});
97+
}
98+
99+
tryConnect();
100+
});
101+
}
102+
52103
const vendorStyles = [
53104
"node_modules/font-awesome/css/font-awesome.min.css",
54105
"node_modules/font-awesome/css/font-awesome.css.map",
@@ -64,7 +115,6 @@ function lint(files, options = {}) {
64115
return () => {
65116
return gulp
66117
.src(files)
67-
.pipe(reload({ stream: true, once: true }))
68118
.pipe($.eslint(options))
69119
.pipe($.eslint.format("compact"))
70120
.pipe($.if(!browserSync.active, $.eslint.failOnError()));
@@ -323,17 +373,16 @@ function provideMissingData() {
323373
});
324374
}
325375

326-
gulp.task(
327-
"step-templates",
328-
gulp.series("tests", () => {
329-
return gulp
330-
.src("./step-templates/*.json")
331-
.pipe(provideMissingData())
332-
.pipe(concat("step-templates.json", { newLine: "," }))
333-
.pipe(insert.wrap('{"items": [', "]}"))
334-
.pipe(argv.production ? gulp.dest(`${publishDir}/app/services`) : gulp.dest(`${buildDir}/app/services`));
335-
})
336-
);
376+
gulp.task("step-templates:data", () => {
377+
return gulp
378+
.src("./step-templates/*.json")
379+
.pipe(provideMissingData())
380+
.pipe(concat("step-templates.json", { newLine: "," }))
381+
.pipe(insert.wrap('{"items": [', "]}"))
382+
.pipe(argv.production ? gulp.dest(`${publishDir}/app/services`) : gulp.dest(`${buildDir}/app/services`));
383+
});
384+
385+
gulp.task("step-templates", gulp.series("tests", "step-templates:data"));
337386

338387
gulp.task("styles:vendor", () => {
339388
return gulp.src(vendorStyles, { base: "node_modules/" }).pipe(argv.production ? gulp.dest(`${publishDir}/public/styles/vendor`) : gulp.dest(`${buildDir}/public/styles/vendor`));
@@ -426,14 +475,35 @@ gulp.task(
426475
server.start();
427476
process.chdir(`../`);
428477

429-
browserSync.init(null, {
430-
proxy: "http://localhost:9000",
431-
});
478+
browserSync.init(
479+
null,
480+
{
481+
proxy: "http://localhost:9000",
482+
open: false,
483+
},
484+
() => {
485+
waitForServer("http://localhost:9000").then((isReady) => {
486+
if (isReady) {
487+
openBrowser("http://localhost:9000");
488+
return;
489+
}
490+
491+
log.warn("Timed out waiting for http://localhost:9000, skipping automatic browser launch.");
492+
});
493+
}
494+
);
495+
496+
function reloadServer(done) {
497+
process.chdir(`${buildDir}`);
498+
server.start();
499+
process.chdir(`../`);
500+
done();
501+
}
432502

433503
gulp.watch(`${clientDir}/**/*.jade`, gulp.series("build:client"));
434-
gulp.watch(`${clientDir}/**/*.jsx`, gulp.series("scripts", "copy:app"));
504+
gulp.watch(`${clientDir}/**/*.jsx`, gulp.series("scripts", "copy:app", reloadServer));
435505
gulp.watch(`${clientDir}/content/styles/**/*.scss`, gulp.series("styles:client"));
436-
gulp.watch("step-templates/*.json", gulp.series("step-templates"));
506+
gulp.watch("step-templates/*.json", gulp.series("step-templates:data"));
437507

438508
gulp.watch(`${buildDir}/**/*.*`).on("change", reload);
439509
})

server/server.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,22 @@ import LibraryStorageService from "./app/services/LibraryDb.js";
1818
import LibraryActions from "./app/actions/LibraryActions";
1919

2020
let app = express();
21+
const isDevelopment = process.env.NODE_ENV === "development";
22+
const staticAssetOptions = isDevelopment
23+
? {
24+
maxAge: 0,
25+
etag: false,
26+
lastModified: false,
27+
setHeaders: (res) => {
28+
res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate");
29+
res.setHeader("Pragma", "no-cache");
30+
res.setHeader("Expires", "0");
31+
},
32+
}
33+
: { maxAge: "1d" };
2134

22-
app.use(express.static(path.join(__dirname, "public"), { maxage: "1d" }));
23-
app.use(express.static(path.join(__dirname, "views"), { maxage: "1d" }));
35+
app.use(express.static(path.join(__dirname, "public"), staticAssetOptions));
36+
app.use(express.static(path.join(__dirname, "views"), staticAssetOptions));
2437
app.use("/.well-known", express.static(".well-known"));
2538

2639
app.set("views", path.join(__dirname, "views"));
@@ -74,11 +87,13 @@ app.get("*", (req, res) => {
7487

7588
LibraryActions.sendTemplates(data, () => {
7689
var libraryAppHtml = ReactDOMServer.renderToStaticMarkup(<RouterContext {...renderProps} />);
90+
const browserSyncClientUrl = process.env.NODE_ENV === "development" ? `${req.protocol}://${req.hostname}:3000/browser-sync/browser-sync-client.js` : null;
7791
res.render("index", {
7892
siteKeywords: config.keywords.join(),
7993
siteDescription: config.description,
8094
reactOutput: libraryAppHtml,
8195
stepTemplates: JSON.stringify(data),
96+
browserSyncClientUrl,
8297
});
8398
});
8499
});

server/views/index.pug

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,8 @@ html
4646

4747
ga('create', 'UA-24461753-5', 'octopusdeploy.com');
4848

49+
if browserSyncClientUrl
50+
script(type='text/javascript', async=true, src=browserSyncClientUrl)
51+
4952
//- inject:js
5053
//- endinject

0 commit comments

Comments
 (0)