Skip to content

Commit b5fc0cd

Browse files
authored
Merge pull request #4 from tonyjunkes/develop
#2 break out environment loading for non-fw1 apps
2 parents c4ea7cd + b8643fd commit b5fc0cd

File tree

7 files changed

+258
-84
lines changed

7 files changed

+258
-84
lines changed

README.md

Lines changed: 134 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,137 @@
33

44
An FW/1 subsystem for loading external configurations into an application accessible bean object.
55

6-
## How to Use
7-
8-
1. Include `fw1dotenv` in your FW/1 application's designated `subsystems` directory. If you do not already have a `subsystems` directory, create the directory wherever the rest of your core framework structure lives. You may refer to the [FW/1 Documentation](http://framework-one.github.io/documentation/developing-applications.html#basic-application-structure) for a rundown of a typical structure.
9-
1. Configure your FW/1 settings to include the location of your settings file and the subsystem's load listener for integrating the settings as a bean.
10-
```cfc
11-
variables.framework = {
12-
// ... other framework settings
13-
dotenv: {
14-
fileName: "/path/to/settings/file/.env"
15-
}
16-
subsystems: {
17-
fw1dotenv: {
18-
diLocations: "model",
19-
diConfig: {
20-
omitDirectoryAliases: true,
21-
loadListener: "DotenvListener"
22-
}
23-
}
24-
}
25-
}
26-
```
27-
28-
> Note: The settings file may be any type of readable extension (.env, txt, json, properties etc.) and contain variables stored in either JSON ({ "key": "value" }) or Properties (key=value) formats.
29-
30-
1. To access the newly wired settings in your application, you can inject the `SystemSettings` bean into your other DI/1 aware beans via `property SystemSettings;` or manually call the bean from the default bean factory with `getBeanFactory().getBean("SystemSettings");`.
6+
## Requirements
7+
8+
* Lucee 5.x+ or Adobe ColdFusion 2016+
9+
* FW/1 4.x+
10+
11+
> Note: FW/1 Dotenv may also be used outside of FW/1 applications but with some obvious limitations. See the `Usage` section for more info.
12+
13+
## Installation
14+
15+
### CommandBox
16+
17+
Installation via ForgeBox/CommandBox coming soon...
18+
19+
### Manual
20+
21+
#### FW/1
22+
23+
Include `fw1dotenv` in your FW/1 application's designated `subsystems` directory. If you do not already have a `subsystems` directory, create the directory wherever the rest of your core framework structure lives. You may refer to the [FW/1 Documentation](http://framework-one.github.io/documentation/developing-applications.html#basic-application-structure) for a rundown of a typical structure.
24+
25+
## Configuration
26+
27+
### Default Setup
28+
29+
Configure your FW/1 settings variable to include the location of your settings file and the subsystem's load listener for integrating the settings as a bean.
30+
31+
> Note: The settings file may be any type of readable extension (i.e: .env, .txt, .json, .properties etc.) and contain variables stored in either JSON ({ "key": "value" }) or Properties (key=value) formats.
32+
33+
```cfc
34+
variables.framework = {
35+
// ... other framework settings
36+
dotenv: {
37+
fileName: "/path/to/settings/file/.env"
38+
}
39+
subsystems: {
40+
fw1dotenv: {
41+
diLocations: "model",
42+
diConfig: {
43+
omitDirectoryAliases: true,
44+
loadListener: "DotenvListener"
45+
}
46+
}
47+
}
48+
}
49+
```
50+
51+
### Creating a Bean Alias
52+
53+
To assign an alternative naming convention to the settings bean, you can include a `beanAlias` key with a name value in the `dotenv` struct.
54+
55+
```cfc
56+
variables.framework = {
57+
// ... other framework settings
58+
dotenv: {
59+
beanAlias: "MyCustomBeanName",
60+
fileName: "/path/to/settings/file/.env"
61+
}
62+
// ... other framework settings
63+
}
64+
```
65+
66+
## Usage
67+
68+
### Default Convention
69+
70+
To access the newly wired settings in your application, you can call the bean from the `fw1dotenv` subsystem bean factory using `getBeanFactory( "fw1dotenv" ).getBean( "SystemSettings" );`.
71+
72+
### Adding to the Parent Bean Factory
73+
74+
If you would like to make the settings bean available to your main/parent bean factory, you can declare it as a new bean in `setupApplication()` or the parent bean factory's `load listener`.
75+
76+
#### setupApplication()
77+
78+
```cfc
79+
setupApplication() {
80+
// ... some other code
81+
getBeanFactory().declare( "SystemSettings" ).asValue( getBeanFactory( "fw1dotenv" ).getBean( "SystemSettings" ) );
82+
}
83+
```
84+
85+
#### Load Listener via variables.framework
86+
87+
```cfc
88+
variables.framework = {
89+
// ... other framework settings
90+
diEngine: "di1",
91+
diLocations: [ "/path/to/beans" ],
92+
diConfig: {
93+
loadListener: function(di1) {
94+
di1.declare( "SystemSettings" ).asValue( getBeanFactory( "fw1dotenv" ).getBean( "SystemSettings" ) );
95+
}
96+
}
97+
// ... other framework settings
98+
}
99+
```
100+
101+
#### Load Listener via CFC
102+
103+
```cfc
104+
// Application.cfc (or your alternative config location)
105+
variables.framework = {
106+
// ... other framework settings
107+
diEngine: "di1",
108+
diLocations: [ "/path/to/beans" ],
109+
diConfig: { loadListener: "MyLoadListener" }
110+
// ... other framework settings
111+
}
112+
113+
// MyLoadListener.cfc
114+
component {
115+
public function onLoad( di1 ) {
116+
var settings = arguments.di1.getBean( "fw" ).getBeanFactory( "fw1dotenv" ).getBean( "SystemSettings" );
117+
arguments.di1.declare( "SystemSettings" ).asValue( settings );
118+
}
119+
}
120+
```
121+
122+
### Non-FW/1 Apps
123+
124+
FW/1 Dotenv can be used without FW/1 by instantiating `DotenvService.cfc` and calling `loadSettings()`.
125+
126+
```cfc
127+
dotenvService = new fw1dotenv.model.services.DotenvService();
128+
settings = dotenvService.loadSettings( filePath = "path/to/settings/file/.env" );
129+
```
130+
131+
## Development
132+
133+
### Running Tests With CommandBox & TestBox
134+
135+
From the project root, start CommandBox in your preferred terminal and point to the `/test-harness` directory (`cd test-harness`). Include the test dependencies (FW/1 & TestBox) by running `install`. Start the server by executing `server start`. The server instance will be located at `http://127.0.0.1:8520`.
136+
137+
> Note: By default, the test harness server will use Lucee 5. To use a specific engine/version, either update the `server.json` or run `server start cfengine=[engine]@[version]`.
138+
139+
Once the server has started, you can run `testbox run` in the terminal to execute the tests. To view the test results in the browser, you can navigate to `http://127.0.0.1:8520/tests/runner.cfm`.

box.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name":"FW/1 Dotenv",
33
"slug":"fw1dotenv",
4-
"version":"1.0.0",
4+
"version":"1.2.0",
55
"author":"@tonyjunkes",
66
"location":"tonyjunkes/fw1dotenv#develop",
77
"createPackageDirectory":true,

model/DotenvListener.cfc

Lines changed: 12 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,39 +2,20 @@ component
22
accessors=true
33
output=false
44
{
5-
property fw;
5+
property DotenvService;
66

77
public function onLoad( di1 ) {
8-
// Get configuration file from framework subsystem settings
9-
var fw1Config = variables.fw.getConfig();
10-
var envFilePath = expandPath( fw1Config?.dotenv?.fileName );
11-
if ( fileExists( envFilePath ) ) {
12-
var envFile = fileRead( envFilePath );
13-
// If JSON, deserialize natively and include as bean
14-
if ( isJSON( envFile ) ) {
15-
// Load the bean into the parent bean factory
16-
variables.fw.getBeanFactory().declare( "SystemSettings" ).asValue( deserializeJSON( envFile ) );
17-
} else {
18-
// Otherwise load as properties format and include as bean
19-
var FileInputStream = createObject( "java", "java.io.FileInputStream" );
20-
var Properties = createObject( "java", "java.util.Properties" ).init();
21-
Properties.load( FileInputStream.init( envFilePath ) );
22-
// To avoid case sensitivity issues when accessing key/value pairs
23-
var envVars = {};
24-
Properties.each(function( prop ) {
25-
envVars[ arguments.prop ] = Properties[ arguments.prop ];
26-
});
27-
// Load the bean into the parent bean factory
28-
variables.fw.getBeanFactory().declare( "SystemSettings" ).asValue( envVars );
29-
}
30-
// Setup alias if config exists in framework settings
31-
if ( len( fw1Config?.dotenv?.beanAlias ) ) {
32-
variables.fw.getBeanFactory().declare( fw1Config.dotenv.beanAlias ).aliasFor( "SystemSettings" );
33-
}
34-
} else {
35-
// Print to console if no file can be found
36-
var System = createObject( "java", "java.lang.System" );
37-
System.out.println( "[fw1dotenv]: Cannot find environment file." );
8+
// Get configuration file from framework settings
9+
var fw1Config = arguments.di1.getBean( "fw" ).getConfig();
10+
// Load env settings
11+
var envPath = expandPath( fw1Config?.dotenv?.fileName );
12+
var settings = variables.DotenvService.loadSettings( filePath = envPath );
13+
// Load the bean into the parent bean factory
14+
arguments.di1.declare( "SystemSettings" ).asValue( settings );
15+
// Setup alias if config exists in framework settings
16+
var beanAlias = fw1Config?.dotenv?.beanAlias ?: "";
17+
if ( beanAlias.len() ) {
18+
arguments.di1.declare( beanAlias ).aliasFor( "SystemSettings" );
3819
}
3920
}
4021
}

model/services/DotenvService.cfc

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
component
2+
output=false
3+
{
4+
public DotenvService function init() {
5+
return this;
6+
}
7+
8+
public function loadSettings( required string filePath ) {
9+
var envVars = {};
10+
if ( fileExists( arguments.filePath ) ) {
11+
var envFile = fileRead( arguments.filePath );
12+
// If JSON, deserialize natively
13+
if ( isJSON( envFile ) ) {
14+
envVars = deserializeJSON( envFile );
15+
} else {
16+
// Otherwise load as properties format and convert to CFML struct
17+
var FileInputStream = createObject( "java", "java.io.FileInputStream" );
18+
var Properties = createObject( "java", "java.util.Properties" ).init();
19+
Properties.load( FileInputStream.init( arguments.filePath ) );
20+
Properties.each(function( prop ) {
21+
envVars[ arguments.prop ] = Properties[ arguments.prop ];
22+
});
23+
}
24+
} else {
25+
// Throw error if no file can be found
26+
throw( "[fw1dotenv]: Could not find environment file." );
27+
}
28+
29+
return envVars;
30+
}
31+
}

test-harness/tests/Application.cfc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ component {
66
"/resources": variables.testsPath & "./resources",
77
"/testbox": variables.testsPath & "../testbox",
88
"/framework": variables.testsPath & "../framework",
9-
"/model": variables.testsPath & "../../model"
9+
"/model": variables.testsPath & "../../model",
10+
// This is to fake the subsystem location
11+
"/tests/subsystems/fw1dotenv/model": variables.testsPath & "../../model"
1012
};
1113
}
Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,39 @@
11
component extends="testbox.system.BaseSpec" {
22

3+
function __config() {
4+
return variables.framework;
5+
}
6+
37
function run() {
4-
describe( "Tests FW/1 Dotenv", function() {
8+
describe( "FW/1 Dotenv load listener", function() {
59
beforeEach(function( currentSpec ) {
610
// Reset the framework instance before each spec is run
711
request.delete( "_fw1" );
812
variables.fw = new framework.one();
913
variables.fw.__config = __config;
1014
variables.fw.__config().append({
11-
diLocations: "/model",
12-
diConfig: {
13-
omitDirectoryAliases: true,
14-
loadListener: "DotenvListener"
15+
subsystems: {
16+
fw1dotenv: {
17+
diLocations: "/model",
18+
diConfig: {
19+
omitDirectoryAliases: true,
20+
loadListener: "DotenvListener"
21+
}
22+
}
1523
}
1624
});
1725
});
1826

19-
it( "Tests load listener is discovered", function() {
27+
it( "Tests exception when file not found", function() {
28+
variables.fw.__config().append({
29+
dotenv: {
30+
fileName: "/resources/.notafile.env"
31+
}
32+
});
2033
variables.fw.onApplicationStart();
2134

22-
// Load listener is loaded
23-
var moduleExists = variables.fw.getBeanFactory().containsBean( "DotenvListener" );
24-
expect( moduleExists ).toBeTrue();
35+
expect( function() { variables.fw.getBeanFactory().getBean( "SystemSettings" ); } )
36+
.toThrow( message = "[fw1dotenv]: Could not find environment file." );
2537
});
2638

2739
it( "Tests SystemSettings bean is created from a .env properties file", function() {
@@ -32,14 +44,13 @@ component extends="testbox.system.BaseSpec" {
3244
});
3345
variables.fw.onApplicationStart();
3446

35-
// Bean is created
36-
var beanExists = variables.fw.getBeanFactory().containsBean( "SystemSettings" );
47+
var beanExists = variables.fw.getBeanFactory( "fw1dotenv" ).containsBean( "SystemSettings" );
3748
expect( beanExists ).toBeTrue();
3849

39-
var beanType = variables.fw.getBeanFactory().getBean( "SystemSettings" );
50+
var beanType = variables.fw.getBeanFactory( "fw1dotenv" ).getBean( "SystemSettings" );
4051
expect( beanType ).toBeTypeOf( "struct" );
4152

42-
var beanVal = variables.fw.getBeanFactory().getBean( "SystemSettings" ).testkey;
53+
var beanVal = variables.fw.getBeanFactory( "fw1dotenv" ).getBean( "SystemSettings" ).testkey;
4354
expect( beanVal ).toBe( "test_env_value" );
4455
});
4556

@@ -51,13 +62,13 @@ component extends="testbox.system.BaseSpec" {
5162
});
5263
variables.fw.onApplicationStart();
5364

54-
var beanExists = variables.fw.getBeanFactory().containsBean( "SystemSettings" );
65+
var beanExists = variables.fw.getBeanFactory( "fw1dotenv" ).containsBean( "SystemSettings" );
5566
expect( beanExists ).toBeTrue();
5667

57-
var beanType = variables.fw.getBeanFactory().getBean( "SystemSettings" );
68+
var beanType = variables.fw.getBeanFactory( "fw1dotenv" ).getBean( "SystemSettings" );
5869
expect( beanType ).toBeTypeOf( "struct" );
5970

60-
var beanVal = variables.fw.getBeanFactory().getBean( "SystemSettings" ).testkey;
71+
var beanVal = variables.fw.getBeanFactory( "fw1dotenv" ).getBean( "SystemSettings" ).testkey;
6172
expect( beanVal ).toBe( "test_properties_value" );
6273
});
6374

@@ -69,13 +80,13 @@ component extends="testbox.system.BaseSpec" {
6980
});
7081
variables.fw.onApplicationStart();
7182

72-
var beanExists = variables.fw.getBeanFactory().containsBean( "SystemSettings" );
83+
var beanExists = variables.fw.getBeanFactory( "fw1dotenv" ).containsBean( "SystemSettings" );
7384
expect( beanExists ).toBeTrue();
7485

75-
var beanType = variables.fw.getBeanFactory().getBean( "SystemSettings" );
86+
var beanType = variables.fw.getBeanFactory( "fw1dotenv" ).getBean( "SystemSettings" );
7687
expect( beanType ).toBeTypeOf( "struct" );
7788

78-
var beanVal = variables.fw.getBeanFactory().getBean( "SystemSettings" ).testkey;
89+
var beanVal = variables.fw.getBeanFactory( "fw1dotenv" ).getBean( "SystemSettings" ).testkey;
7990
expect( beanVal ).toBe( "test_json_value" );
8091
});
8192

@@ -88,20 +99,16 @@ component extends="testbox.system.BaseSpec" {
8899
});
89100
variables.fw.onApplicationStart();
90101

91-
var beanExists = variables.fw.getBeanFactory().containsBean( "CustomNameSettings" );
102+
var beanExists = variables.fw.getBeanFactory( "fw1dotenv" ).containsBean( "CustomNameSettings" );
92103
expect( beanExists ).toBeTrue();
93104

94-
var beanType = variables.fw.getBeanFactory().getBean( "CustomNameSettings" );
105+
var beanType = variables.fw.getBeanFactory( "fw1dotenv" ).getBean( "CustomNameSettings" );
95106
expect( beanType ).toBeTypeOf( "struct" );
96107

97-
var beanVal = variables.fw.getBeanFactory().getBean( "CustomNameSettings" ).testkey;
108+
var beanVal = variables.fw.getBeanFactory( "fw1dotenv" ).getBean( "CustomNameSettings" ).testkey;
98109
expect( beanVal ).toBe( "test_env_value" );
99110
});
100111
});
101112
}
102113

103-
function __config() {
104-
return variables.framework;
105-
}
106-
107114
}

0 commit comments

Comments
 (0)