Skip to content

Commit c1d3b15

Browse files
Merge pull request #1303 from opencomponents/env
add ability to retreieve environment variables from an env file
2 parents 5699dfe + de91c67 commit c1d3b15

File tree

10 files changed

+1761
-1528
lines changed

10 files changed

+1761
-1528
lines changed

.github/workflows/node.js.yml

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,12 @@ name: Node.js CI
55

66
on:
77
push:
8-
branches: [ master ]
8+
branches: [master]
99
pull_request:
10-
branches: [ master ]
10+
branches: [master]
1111

1212
jobs:
1313
build:
14-
1514
runs-on: ubuntu-latest
1615

1716
strategy:
@@ -20,11 +19,11 @@ jobs:
2019
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
2120

2221
steps:
23-
- uses: actions/checkout@v2
24-
- name: Use Node.js ${{ matrix.node-version }}
25-
uses: actions/setup-node@v2
26-
with:
27-
node-version: ${{ matrix.node-version }}
28-
cache: 'npm'
29-
- run: npm install
30-
- run: npm test
22+
- uses: actions/checkout@v2
23+
- name: Use Node.js ${{ matrix.node-version }}
24+
uses: actions/setup-node@v2
25+
with:
26+
node-version: ${{ matrix.node-version }}
27+
cache: 'npm'
28+
- run: npm install
29+
- run: npm test

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@
9292
"colors": "1.4.0",
9393
"cross-spawn": "7.0.3",
9494
"dependency-graph": "0.11.0",
95+
"dotenv": "16.0.1",
9596
"errorhandler": "1.5.1",
9697
"express": "4.18.2",
9798
"form-data": "4.0.0",

src/registry/domain/repository.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import fs from 'fs-extra';
22
import getUnixUtcTimestamp from 'oc-get-unix-utc-timestamp';
33
import path from 'path';
4+
import dotenv from 'dotenv';
45

56
import ComponentsCache from './components-cache';
67
import getComponentsDetails from './components-details';
@@ -114,6 +115,14 @@ export default function repository(conf: Config) {
114115
content: fs.readFileSync(filePath).toString(),
115116
filePath
116117
};
118+
},
119+
getEnv(componentName: string): Record<string, string> {
120+
const pkg: Component = fs.readJsonSync(
121+
path.join(conf.path, `${componentName}/package.json`)
122+
);
123+
const filePath = path.join(conf.path, componentName, pkg.oc.files.env!);
124+
125+
return dotenv.parse(fs.readFileSync(filePath).toString());
117126
}
118127
};
119128

@@ -249,6 +258,19 @@ export default function repository(conf: Config) {
249258

250259
return { content, filePath };
251260
},
261+
async getEnv(
262+
componentName: string,
263+
componentVersion: string
264+
): Promise<Record<string, string>> {
265+
if (conf.local) {
266+
return local.getEnv(componentName);
267+
}
268+
269+
const filePath = getFilePath(componentName, componentVersion, '.env');
270+
const file = await cdn.getFile(filePath);
271+
272+
return dotenv.parse(file);
273+
},
252274
getStaticClientPath: (): string =>
253275
`${options!['path']}${getFilePath(
254276
'oc-client',

src/registry/routes/helpers/get-component.ts

Lines changed: 147 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import strings from '../../../resources';
1919
import * as urlBuilder from '../../domain/url-builder';
2020
import * as validator from '../../domain/validators';
2121
import type { Repository } from '../../domain/repository';
22-
import { Config } from '../../../types';
22+
import { Component, Config } from '../../../types';
2323
import { IncomingHttpHeaders } from 'http';
2424
import { fromPromise } from 'universalify';
2525

@@ -54,13 +54,33 @@ export interface GetComponentResult {
5454
};
5555
}
5656

57+
const noopConsole = Object.fromEntries(
58+
Object.keys(console).map(key => [key, _.noop])
59+
);
60+
5761
export default function getComponent(conf: Config, repository: Repository) {
5862
const client = Client({ templates: conf.templates });
5963
const cache = new Cache({
6064
verbose: !!conf.verbosity,
6165
refreshInterval: conf.refreshInterval
6266
});
6367

68+
const getEnv = async (
69+
component: Component
70+
): Promise<Record<string, string>> => {
71+
const cacheKey = `${component.name}/${component.version}/.env`;
72+
const cached = cache.get('file-contents', cacheKey);
73+
74+
if (cached) return cached;
75+
76+
const env = component.oc.files.env
77+
? await repository.getEnv(component.name, component.version)
78+
: {};
79+
cache.set('file-contents', cacheKey, env);
80+
81+
return env;
82+
};
83+
6484
const renderer = function (
6585
options: RendererOptions,
6686
cb: (result: GetComponentResult) => void
@@ -411,136 +431,149 @@ export default function getComponent(conf: Config, repository: Repository) {
411431
if (!component.oc.files.dataProvider) {
412432
returnComponent(null, {});
413433
} else {
414-
const cacheKey = `${component.name}/${component.version}/server.js`;
415-
const cached = cache.get('file-contents', cacheKey);
416-
const domain = Domain.create();
417-
const setEmptyResponse =
418-
emptyResponseHandler.contextDecorator(returnComponent);
419-
const contextObj = {
420-
acceptLanguage: acceptLanguageParser.parse(acceptLanguage!),
421-
baseUrl: conf.baseUrl,
422-
env: conf.env,
423-
params,
424-
plugins: conf.plugins,
425-
renderComponent: fromPromise(nestedRenderer.renderComponent),
426-
renderComponents: fromPromise(nestedRenderer.renderComponents),
427-
requestHeaders: options.headers,
428-
requestIp: options.ip,
429-
setEmptyResponse,
430-
staticPath: repository
431-
.getStaticFilePath(component.name, component.version, '')
432-
.replace('https:', ''),
433-
setHeader: (header?: string, value?: string) => {
434-
if (!(typeof header === 'string' && typeof value === 'string')) {
435-
throw strings.errors.registry
436-
.COMPONENT_SET_HEADER_PARAMETERS_NOT_VALID;
437-
}
438-
439-
if (header && value) {
440-
responseHeaders = responseHeaders || {};
441-
responseHeaders[header.toLowerCase()] = value;
442-
}
443-
},
444-
templates: repository.getTemplatesInfo()
445-
};
446-
447-
const setCallbackTimeout = () => {
448-
const executionTimeout = conf.executionTimeout;
449-
if (executionTimeout) {
450-
setTimeout(() => {
451-
const message = `timeout (${executionTimeout * 1000}ms)`;
452-
returnComponent({ message }, undefined);
453-
domain.exit();
454-
}, executionTimeout * 1000);
434+
fromPromise(getEnv)(component, (err, env) => {
435+
if (err) {
436+
componentCallbackDone = true;
437+
438+
return callback({
439+
status: 502,
440+
response: {
441+
code: 'ENV_RESOLVING_ERROR',
442+
error: strings.errors.registry.RESOLVING_ERROR
443+
}
444+
});
455445
}
456-
};
457446

458-
if (!!cached && !conf.hotReloading) {
459-
domain.on('error', returnComponent);
447+
const cacheKey = `${component.name}/${component.version}/server.js`;
448+
const cached = cache.get('file-contents', cacheKey);
449+
const domain = Domain.create();
450+
const setEmptyResponse =
451+
emptyResponseHandler.contextDecorator(returnComponent);
452+
const contextObj = {
453+
acceptLanguage: acceptLanguageParser.parse(acceptLanguage!),
454+
baseUrl: conf.baseUrl,
455+
env: { ...conf.env, ...env },
456+
params,
457+
plugins: conf.plugins,
458+
renderComponent: fromPromise(nestedRenderer.renderComponent),
459+
renderComponents: fromPromise(nestedRenderer.renderComponents),
460+
requestHeaders: options.headers,
461+
requestIp: options.ip,
462+
setEmptyResponse,
463+
staticPath: repository
464+
.getStaticFilePath(component.name, component.version, '')
465+
.replace('https:', ''),
466+
setHeader: (header?: string, value?: string) => {
467+
if (
468+
!(typeof header === 'string' && typeof value === 'string')
469+
) {
470+
throw strings.errors.registry
471+
.COMPONENT_SET_HEADER_PARAMETERS_NOT_VALID;
472+
}
460473

461-
try {
462-
domain.run(() => {
463-
cached(contextObj, returnComponent);
464-
setCallbackTimeout();
465-
});
466-
} catch (e) {
467-
return returnComponent(e, undefined);
468-
}
469-
} else {
470-
fromPromise(repository.getDataProvider)(
471-
component.name,
472-
component.version,
473-
(err, dataProvider) => {
474-
if (err) {
475-
componentCallbackDone = true;
476-
477-
return callback({
478-
status: 502,
479-
response: {
480-
code: 'DATA_RESOLVING_ERROR',
481-
error: strings.errors.registry.RESOLVING_ERROR
482-
}
483-
});
474+
if (header && value) {
475+
responseHeaders = responseHeaders || {};
476+
responseHeaders[header.toLowerCase()] = value;
484477
}
478+
},
479+
templates: repository.getTemplatesInfo()
480+
};
485481

486-
const context = {
487-
require: RequireWrapper(conf.dependencies),
488-
module: {
489-
exports: {} as Record<
490-
string,
491-
(...args: unknown[]) => unknown
492-
>
493-
},
494-
console: conf.local ? console : Object.fromEntries(Object.keys(console).map(key => [key, _.noop])),
495-
setTimeout,
496-
Buffer
497-
};
498-
499-
const handleError = (err: {
500-
code: string;
501-
missing: string[];
502-
}) => {
503-
if (err.code === 'DEPENDENCY_MISSING_FROM_REGISTRY') {
482+
const setCallbackTimeout = () => {
483+
const executionTimeout = conf.executionTimeout;
484+
if (executionTimeout) {
485+
setTimeout(() => {
486+
const message = `timeout (${executionTimeout * 1000}ms)`;
487+
returnComponent({ message }, undefined);
488+
domain.exit();
489+
}, executionTimeout * 1000);
490+
}
491+
};
492+
493+
if (!!cached && !conf.hotReloading) {
494+
domain.on('error', returnComponent);
495+
496+
try {
497+
domain.run(() => {
498+
cached(contextObj, returnComponent);
499+
setCallbackTimeout();
500+
});
501+
} catch (e) {
502+
return returnComponent(e, undefined);
503+
}
504+
} else {
505+
fromPromise(repository.getDataProvider)(
506+
component.name,
507+
component.version,
508+
(err, dataProvider) => {
509+
if (err) {
504510
componentCallbackDone = true;
505511

506512
return callback({
507-
status: 501,
513+
status: 502,
508514
response: {
509-
code: err.code,
510-
error: strings.errors.registry.DEPENDENCY_NOT_FOUND(
511-
err.missing.join(', ')
512-
),
513-
missingDependencies: err.missing
515+
code: 'DATA_RESOLVING_ERROR',
516+
error: strings.errors.registry.RESOLVING_ERROR
514517
}
515518
});
516519
}
517520

518-
returnComponent(err, undefined);
519-
};
520-
521-
const options = conf.local
522-
? {
523-
displayErrors: true,
524-
filename: dataProvider.filePath
521+
const context = {
522+
require: RequireWrapper(conf.dependencies),
523+
module: {
524+
exports: {} as Record<string, (...args: any[]) => any>
525+
},
526+
console: conf.local ? console : noopConsole,
527+
setTimeout,
528+
Buffer
529+
};
530+
531+
const handleError = (err: {
532+
code: string;
533+
missing: string[];
534+
}) => {
535+
if (err.code === 'DEPENDENCY_MISSING_FROM_REGISTRY') {
536+
componentCallbackDone = true;
537+
538+
return callback({
539+
status: 501,
540+
response: {
541+
code: err.code,
542+
error: strings.errors.registry.DEPENDENCY_NOT_FOUND(
543+
err.missing.join(', ')
544+
),
545+
missingDependencies: err.missing
546+
}
547+
});
525548
}
526-
: {};
527549

528-
try {
529-
vm.runInNewContext(dataProvider.content, context, options);
530-
const processData = context.module.exports['data'];
531-
cache.set('file-contents', cacheKey, processData);
550+
returnComponent(err, undefined);
551+
};
532552

533-
domain.on('error', handleError);
534-
domain.run(() => {
535-
processData(contextObj, returnComponent);
536-
setCallbackTimeout();
537-
});
538-
} catch (err) {
539-
handleError(err as any);
553+
const options = conf.local
554+
? {
555+
displayErrors: true,
556+
filename: dataProvider.filePath
557+
}
558+
: {};
559+
560+
try {
561+
vm.runInNewContext(dataProvider.content, context, options);
562+
const processData = context.module.exports['data'];
563+
cache.set('file-contents', cacheKey, processData);
564+
565+
domain.on('error', handleError);
566+
domain.run(() => {
567+
processData(contextObj, returnComponent);
568+
setCallbackTimeout();
569+
});
570+
} catch (err) {
571+
handleError(err as any);
572+
}
540573
}
541-
}
542-
);
543-
}
574+
);
575+
}
576+
});
544577
}
545578
}
546579
);

0 commit comments

Comments
 (0)