@data-fair/dev-server 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -1
- package/src/app.js +117 -2
- package/src/index.js +0 -0
- package/ui/dist/assets/{index-B1O0xIkd.js → index-C0yX4XkL.js} +13 -13
- package/ui/dist/index.html +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@data-fair/dev-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "A development server for optimal development experience of data-fair applications.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
},
|
|
39
39
|
"homepage": "https://github.com/koumoul-dev/df-dev-server#readme",
|
|
40
40
|
"devDependencies": {
|
|
41
|
+
"@data-fair/lib-common-types": "^1.21.0",
|
|
41
42
|
"@data-fair/lib-types-builder": "^1.14.0",
|
|
42
43
|
"@types/config": "^4.4.0",
|
|
43
44
|
"@types/cors": "^2.8.19",
|
package/src/app.js
CHANGED
|
@@ -77,6 +77,9 @@ server.on('upgrade', function upgrade(req, socket, head) {
|
|
|
77
77
|
});
|
|
78
78
|
app.use(cors());
|
|
79
79
|
app.use(express.json());
|
|
80
|
+
// URL of the app under development, used by the proxy and by the remote configuration import
|
|
81
|
+
const appUrl = new URL(config.app.url);
|
|
82
|
+
const appPrefix = appUrl.pathname.endsWith('/') ? appUrl.pathname.substring(0, appUrl.pathname.length - 1) : appUrl.pathname;
|
|
80
83
|
// very basic CRUD of config
|
|
81
84
|
app.get('/config', (req, res, next) => {
|
|
82
85
|
const devConfig = existsSync('.dev-config.json') ? JSON.parse(readFileSync('.dev-config.json', 'utf8')) : {};
|
|
@@ -95,9 +98,88 @@ app.post('/config/error', (req, res) => {
|
|
|
95
98
|
}
|
|
96
99
|
res.send();
|
|
97
100
|
});
|
|
101
|
+
// Identification of the app under development: its name is read from the
|
|
102
|
+
// "application-name" meta tag of the local index.html and its version from the
|
|
103
|
+
// package.json of the current directory.
|
|
104
|
+
const localAppInfo = async () => {
|
|
105
|
+
let name;
|
|
106
|
+
try {
|
|
107
|
+
const res = await fetch(appUrl.origin + appPrefix + '/index.html');
|
|
108
|
+
const html = await res.text();
|
|
109
|
+
const document = parse5.parse(html);
|
|
110
|
+
const htmlNode = document.childNodes?.find((c) => c.tagName === 'html');
|
|
111
|
+
const headNode = htmlNode?.childNodes?.find((c) => c.tagName === 'head');
|
|
112
|
+
const metaNode = (headNode?.childNodes ?? []).find((c) => c.tagName === 'meta' && c.attrs?.find((a) => a.name === 'name')?.value === 'application-name');
|
|
113
|
+
name = metaNode?.attrs?.find((a) => a.name === 'content')?.value;
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
debug('failed to fetch local app index.html', err);
|
|
117
|
+
}
|
|
118
|
+
let version;
|
|
119
|
+
try {
|
|
120
|
+
version = JSON.parse(readFileSync('package.json', 'utf8')).version;
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
debug('failed to read local package.json', err);
|
|
124
|
+
}
|
|
125
|
+
return { name, version };
|
|
126
|
+
};
|
|
127
|
+
// Fetch a resource from the remote data-fair api
|
|
128
|
+
const remoteFetch = async (path) => {
|
|
129
|
+
const res = await fetch(config.dataFair.url + '/api/v1' + path, {
|
|
130
|
+
headers: config.dataFair.apiKey ? { 'x-apiKey': config.dataFair.apiKey } : {}
|
|
131
|
+
});
|
|
132
|
+
if (!res.ok)
|
|
133
|
+
throw new Error(`error ${res.status} on remote data-fair: ${await res.text()}`);
|
|
134
|
+
return res.json();
|
|
135
|
+
};
|
|
136
|
+
// extract the major.minor part of a version, versions on remote base apps look like "1.3"
|
|
137
|
+
const minorVersion = (version) => version.split('.').slice(0, 2).join('.');
|
|
138
|
+
// list configurations of the app under development that exist on the remote data-fair
|
|
139
|
+
// with the same minor version, useful to test quickly for regressions
|
|
140
|
+
app.get('/configurations', async (req, res) => {
|
|
141
|
+
const { name, version } = await localAppInfo();
|
|
142
|
+
const response = { appName: name, localVersion: version, remoteUrl: config.dataFair.url };
|
|
143
|
+
if (!name) {
|
|
144
|
+
res.status(400).send({ ...response, error: 'The "application-name" meta tag was not found in the local app index.html' });
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (!version) {
|
|
148
|
+
res.status(400).send({ ...response, error: 'The version could not be read from the local package.json' });
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
response.minorVersion = minorVersion(version);
|
|
152
|
+
try {
|
|
153
|
+
const baseApps = await remoteFetch('/base-applications?applicationName=' + encodeURIComponent(name) + '&size=1000&count=false');
|
|
154
|
+
const matchingBaseApps = (baseApps.results ?? []).filter((b) => typeof b.version === 'string' && minorVersion(b.version) === response.minorVersion);
|
|
155
|
+
response.results = [];
|
|
156
|
+
for (const baseApp of matchingBaseApps) {
|
|
157
|
+
const applications = await remoteFetch('/applications?base-application=' + encodeURIComponent(baseApp.url) + '&size=1000&count=false&select=id,title,owner');
|
|
158
|
+
for (const application of applications.results ?? []) {
|
|
159
|
+
response.results.push({ id: application.id, title: application.title, owner: application.owner, baseAppVersion: baseApp.version });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
response.results.sort((a, b) => (a.title || '').localeCompare(b.title || ''));
|
|
163
|
+
res.send(response);
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
res.status(500).send({ ...response, error: err.message });
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
// copy a configuration from the remote data-fair, replacing all references to the
|
|
170
|
+
// remote origin by the local one so that data goes through the local proxies
|
|
171
|
+
app.get('/configurations/:id', async (req, res) => {
|
|
172
|
+
try {
|
|
173
|
+
const configuration = await remoteFetch('/applications/' + encodeURIComponent(req.params.id) + '/configuration');
|
|
174
|
+
const remoteOrigin = new URL(config.dataFair.url).origin;
|
|
175
|
+
const localOrigin = `http://localhost:${config.port}`;
|
|
176
|
+
res.send(JSON.parse(JSON.stringify(configuration).replaceAll(remoteOrigin, localOrigin)));
|
|
177
|
+
}
|
|
178
|
+
catch (err) {
|
|
179
|
+
res.status(500).send({ error: err.message });
|
|
180
|
+
}
|
|
181
|
+
});
|
|
98
182
|
// re-expose the application performing similar modifications to the body as data-fair
|
|
99
|
-
const appUrl = new URL(config.app.url);
|
|
100
|
-
const appPrefix = appUrl.pathname.endsWith('/') ? appUrl.pathname.substring(0, appUrl.pathname.length - 1) : appUrl.pathname;
|
|
101
183
|
app.use('/app', createProxyMiddleware({
|
|
102
184
|
target: appUrl.origin,
|
|
103
185
|
secure: false,
|
|
@@ -258,6 +340,39 @@ app.use('/data-fair', createProxyMiddleware({
|
|
|
258
340
|
}
|
|
259
341
|
}
|
|
260
342
|
}));
|
|
343
|
+
// re-expose a tileserver to serve base map styles, tiles, sprites and fonts.
|
|
344
|
+
// derived from the data-fair origin (each site exposes its tileserver on <origin>/tileserver).
|
|
345
|
+
const tileserverUrl = new URL(config.dataFair.url).origin + '/tileserver';
|
|
346
|
+
app.use('/tileserver', createProxyMiddleware({
|
|
347
|
+
target: new URL(config.dataFair.url).origin,
|
|
348
|
+
secure: false,
|
|
349
|
+
changeOrigin: true,
|
|
350
|
+
selfHandleResponse: true, // so that the proxyRes handler takes care of sending the response
|
|
351
|
+
on: {
|
|
352
|
+
proxyReq(proxyReq, req, res) {
|
|
353
|
+
proxyReq.path = '/tileserver' + proxyReq.path;
|
|
354
|
+
// no gzip so that we can process the content
|
|
355
|
+
proxyReq.setHeader('accept-encoding', 'identity');
|
|
356
|
+
},
|
|
357
|
+
proxyRes(proxyRes, req, res) {
|
|
358
|
+
if (proxyRes.headers['content-type'] && proxyRes.headers['content-type'].startsWith('application/json')) {
|
|
359
|
+
let body = '';
|
|
360
|
+
proxyRes.on('data', (data) => { body += data.toString(); });
|
|
361
|
+
proxyRes.on('end', () => {
|
|
362
|
+
// make all references to the tileserver url point to the local proxy
|
|
363
|
+
const output = body.replace(new RegExp(escapeStringRegexp(tileserverUrl), 'g'), `http://localhost:${config.port}/tileserver`);
|
|
364
|
+
delete proxyRes.headers['content-length'];
|
|
365
|
+
res.writeHead(proxyRes.statusCode ?? 200, proxyRes.headers);
|
|
366
|
+
res.end(output);
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
res.writeHead(proxyRes.statusCode ?? 200, proxyRes.headers);
|
|
371
|
+
proxyRes.pipe(res);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}));
|
|
261
376
|
// also re-expose the simple-directory instance matching data-fair
|
|
262
377
|
app.use('/simple-directory', createProxyMiddleware({
|
|
263
378
|
target: dfUrl.origin,
|
package/src/index.js
CHANGED
|
File without changes
|