Skip to content

Commit e1aeee3

Browse files
add ability to retreieve environment variables from an env file
1 parent 40f1d1e commit e1aeee3

File tree

9 files changed

+5492
-5313
lines changed

9 files changed

+5492
-5313
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@
9898
"colors": "1.4.0",
9999
"cross-spawn": "7.0.3",
100100
"dependency-graph": "0.11.0",
101+
"dotenv": "16.0.1",
101102
"errorhandler": "1.5.1",
102103
"express": "4.17.1",
103104
"form-data": "4.0.0",
@@ -135,4 +136,4 @@
135136
"universalify": "^2.0.0",
136137
"yargs": "17.3.0"
137138
}
138-
}
139+
}

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: 146 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

@@ -61,6 +61,22 @@ export default function getComponent(conf: Config, repository: Repository) {
6161
refreshInterval: conf.refreshInterval
6262
});
6363

64+
const getEnv = async (
65+
component: Component
66+
): Promise<Record<string, string>> => {
67+
const cacheKey = `${component.name}/${component.version}/.env`;
68+
const cached = cache.get('file-contents', cacheKey);
69+
70+
if (cached) return cached;
71+
72+
const env = component.oc.files.env
73+
? await repository.getEnv(component.name, component.version)
74+
: {};
75+
cache.set('file-contents', cacheKey, env);
76+
77+
return env;
78+
};
79+
6480
const renderer = function (
6581
options: RendererOptions,
6682
cb: (result: GetComponentResult) => void
@@ -411,136 +427,152 @@ export default function getComponent(conf: Config, repository: Repository) {
411427
if (!component.oc.files.dataProvider) {
412428
returnComponent(null, {});
413429
} 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);
430+
fromPromise(getEnv)(component, (err, env) => {
431+
if (err) {
432+
componentCallbackDone = true;
433+
434+
return callback({
435+
status: 502,
436+
response: {
437+
code: 'ENV_RESOLVING_ERROR',
438+
error: strings.errors.registry.RESOLVING_ERROR
439+
}
440+
});
455441
}
456-
};
457442

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

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-
});
470+
if (header && value) {
471+
responseHeaders = responseHeaders || {};
472+
responseHeaders[header.toLowerCase()] = value;
484473
}
474+
},
475+
templates: repository.getTemplatesInfo()
476+
};
485477

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 : { log: _.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') {
478+
const setCallbackTimeout = () => {
479+
const executionTimeout = conf.executionTimeout;
480+
if (executionTimeout) {
481+
setTimeout(() => {
482+
const message = `timeout (${executionTimeout * 1000}ms)`;
483+
returnComponent({ message }, undefined);
484+
domain.exit();
485+
}, executionTimeout * 1000);
486+
}
487+
};
488+
489+
if (!!cached && !conf.hotReloading) {
490+
domain.on('error', returnComponent);
491+
492+
try {
493+
domain.run(() => {
494+
cached(contextObj, returnComponent);
495+
setCallbackTimeout();
496+
});
497+
} catch (e) {
498+
return returnComponent(e, undefined);
499+
}
500+
} else {
501+
fromPromise(repository.getDataProvider)(
502+
component.name,
503+
component.version,
504+
(err, dataProvider) => {
505+
if (err) {
504506
componentCallbackDone = true;
505507

506508
return callback({
507-
status: 501,
509+
status: 502,
508510
response: {
509-
code: err.code,
510-
error: strings.errors.registry.DEPENDENCY_NOT_FOUND(
511-
err.missing.join(', ')
512-
),
513-
missingDependencies: err.missing
511+
code: 'DATA_RESOLVING_ERROR',
512+
error: strings.errors.registry.RESOLVING_ERROR
514513
}
515514
});
516515
}
517516

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

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

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

src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ interface OcConfiguration {
7878
type: string;
7979
version: string;
8080
};
81+
env?: string;
8182
};
8283
packaged: boolean;
8384
parameters: Record<string, OcParameter>;
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
'use strict';
2+
3+
module.exports = {
4+
package: {
5+
name: 'env-component',
6+
version: '1.0.0',
7+
oc: {
8+
container: false,
9+
renderInfo: false,
10+
files: {
11+
template: {
12+
type: 'jade',
13+
hashKey: '8c1fbd954f2b0d8cd5cf11c885fed4805225749f',
14+
src: 'template.js'
15+
},
16+
dataProvider: {
17+
type: 'node.js',
18+
hashKey: '123456',
19+
src: 'server.js'
20+
},
21+
env: '.env'
22+
}
23+
}
24+
},
25+
data: '"use strict";module.exports.data=function (ctx, cb){cb(null,{ mySecret:ctx.env.secret});};',
26+
view:
27+
'var oc=oc||{};oc.components=oc.components||{},oc.components["8c1fbd954f2b0d8cd5cf11c885fed4805225749f"]' +
28+
'=function(){var o=[];return o.push("<div>hello</div>"),o.join("")};'
29+
};

0 commit comments

Comments
 (0)