Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Removing stuff for pkg.js since it has moved out to its own project.

  • Loading branch information...
commit c203037667e006c58be4e6ee8654a96e0c0923d9 1 parent 903a5b0
James Burke authored
248  docs/design/packages.md
Source Rendered
... ...
@@ -1,249 +1,3 @@
1 1
 # Packages
2 2
 
3  
-# Background
4  
-
5  
-I am considering supporting CommonJS packages in RequireJS. Packages are laid out differently than what is expected now from RequireJS. It is also a concern for Dojo, since both RequireJS and Dojo use the same path resolution logic.
6  
-
7  
-Packages can contain more than one module, and the way paths are resolved are different. Here is the standard layout for a CommonJS package, or what I have understood them to be so far (and note that "CommonJS packages" does not imply a hard spec, they are still experimenting, trying to find consensus via implementation):
8  
-
9  
-* packageName/
10  
-    * package.json
11  
-    * lib/
12  
-    * lib/main.js
13  
-    * lib/util.js
14  
-
15  
-There can be other directories inside the packageName directory, notably a tests/ directory, and perhaps a resources/ directory, for resources. In CommonJS pathing land, doing a require('packageName'), it gets mapped to path/to/packageName/lib/main.js, require('packageName/util') is mapped to path/to/packageName/lib/util.js.
16  
-
17  
-Actually, the choice of main.js as the module to use for require('packageName') is configurable in package.json with the "main" property, and so is the lib directory. I think the most recent update of the package spec/proposal forces the package.json to specify at least "main" or "lib" properties to be valid. I want a reasonable default, and would assume lib/main.js if no package.json config is found, and access to the lib directory is always allowed.
18  
-
19  
-For Dojo modules, this would be an improvement over what is used now, particularly for dojox: having dojox/foo.js, dojox/foo/, dojox/foo/_base.js with dojox/foo.js just requiring the _base file. Packages can be easily zipped up and distributed, and all the contents are within a folder.
20  
-
21  
-The downside is the mappings are a little bit weird, and it requires more explicit config to load the package's modules in the browser. The explicit config can be minified particularly if modules follow convention of lib/main.js, but it is something to consider. The existing path mappings in Dojo and RequireJS are a bit more straightforward, but it does make it more difficult to share discrete functionality -- you cannot just zip up a directory, you need to zip up a directory and one file that is a sibling to that directory, and it gets weird to unzip many of these bundles in a common directory.
22  
-
23  
-I would actually like to have one solution that has the best of both worlds, but I cannot see it yet. Feel free to suggest something. In the absense of something else, I will explore the CommonJS package loading.
24  
-
25  
-I still want to support the simpler path mappings we have in Dojo/RequireJS (the default behavior), but also support the package construct. It does not makes sense to force packages to be the default path behavior, but I am open to counter-arguments. As seen below, to support nested packages with nested dependencies, an explicit package config is probably necessary anyway.
26  
-
27  
-Some useful links:
28  
-
29  
-* [CommonJS Packages/1.0](http://wiki.commonjs.org/wiki/Packages/1.0)
30  
-* [CommonJS Packages/1.1](http://wiki.commonjs.org/wiki/Packages/1.1)
31  
-* [CommonJS Mappings/C](http://wiki.commonjs.org/wiki/Packages/Mappings/C)
32  
-
33  
-I believe Mappings will be needed to complete a generic config that does not depend on a server-based package repository. It should be possible to use a package repository but it is not adequate for all use cases. A server-based repository can be seen as just a directory that has the final mapping. However, I hope to leverage some existing repositories, like the one used by npm, to bootstrap something that can reuse existing CommonJS packages.
34  
-
35  
-When the package manager for RequireJS downloads packages, it will convert the modules inside them to RequireJS syntax if they are in the traditional CommonJS module syntax.
36  
-
37  
-# Guiding principles
38  
-
39  
-* Package configuration should be done at development time, not at runtime. The developer will run a tool to introduce a new package dependency into a project. The tool would download the package, and set up the code's configuration to allow the browser to correctly find the package's modules.
40  
-* It should be easy to exclude downloaded packages from the source control for the project.
41  
-* There is no global or assumed packages space for the whole system, and there cannot be a path lookup sequence since this work is to support loading in the browser, which needs to be fast and avoid 404s. Only one path can be configured per package version, and the configuration will be app/package specific.
42  
-* It should be possible to configure loading a package's modules from another domain. CDN loading for example. However, the developer's package tool would contact the CDN location, parse the package.json, then set up the local webapp's configuration.
43  
-* Package config that is run in the browser needs to be reasonably small.
44  
-* While a tool can be used to automate the package config, it should be possible to manually add additional config.
45  
-
46  
-Something to call out in this approach: RequireJS will not try to parse package.json files on the fly in the browser. A developer tool will do that work, and set up an optimized config block that will be injected into top-level application modules. This will avoid extraneous requests, provide more consistent load behavior, and work for server-side RequireJS-based projects.
47  
-
48  
-# User experience
49  
-
50  
-* Download the package manager. For illustration purposes (not the final name), call it pkg.js. Only one file is downloaded. It is a JS file that can be executed in a JS environment that works from the command line, mostly likely Node and/or Rhino. Sample commands:
51  
-
52  
-* pkg.js createApp appName
53  
-    * Generates dir structure, stub an application-level package.json file.
54  
-    * puts in require.js and r.js (the RequireJS/Node adapter) too.
55  
-* pkg.js add packageName version (or an URL)
56  
-    * pkg.js adds the package dependency info to app's package.json
57  
-    * fetches the dependency and any of its dependencies.
58  
-* pkg.js create packageName : creates a new package that is subject to version control.
59  
-    * Different from createApp in that it is assumed not to have require.js included, and it will not need to update config objects in the top-level app modules.
60  
-* pkg.js update : updates the app config to be the latest based on sub packages.
61  
-* pkg.js addApp name: adds a new entry to package.json's app property, updates
62  
-  that named module to have the automated require() config injection. Used to specify
63  
-  what top-level app modules are used in the webapp. App modules are the top level
64  
-  module that is loaded by a particular web page.
65  
-
66  
-I am hoping the pkg.js can be limited to just one JS file to download, but minification tools may mean downloading a couple files. But really try for just one file (for distribution -- in the package manager source it can be many files that are built into one).
67  
-
68  
-# Implementation
69  
-
70  
-## Creating a package-aware project
71  
-
72  
-To create a web app called foo, run
73  
-
74  
-    pkg.js createApp foo
75  
-
76  
-A directory structure is created that looks like this:
77  
-
78  
-* foo
79  
-    * index.html: main app HTML file, loads main.js via RequireJS.
80  
-    * package.json: the config file that stores the persistent info about the project.
81  
-    * lib
82  
-        * .packages: directory to house downloaded 3rd party packages
83  
-        * main.js: the app module loaded by default by index.html
84  
-        * require.js: and/or r.js for node, maybe this is an optional arg to createApp
85  
-
86  
-
87  
-**index.html** would look like the following:
88  
-
89  
-    <!DOCTYPE html>
90  
-    <html>
91  
-    <head>
92  
-        <script data-main="main" src="lib/require.js"></script>
93  
-    </head>
94  
-    <body>
95  
-    </body>
96  
-    </html>
97  
-
98  
-The **data-main** attribute on the RequireJS script tag is something new for RequireJS (not yet implemented), it will tell RequireJS what module to load for the web page. Only one module can be loaded in this way, to support a specific project layout that takes advantage of the optimization tool. The optimization tool in RequireJS can combine all of the page's modules into main.js so only one file needs to be loaded once the developer wants to deploy the code.
99  
-
100  
-**main.js** would look like this by default:
101  
-
102  
-    require(['require'], function (require) {
103  
-
104  
-        //Use require.ready to register callbacks with the DOM is ready for access
105  
-        require.ready(function () {
106  
-            document.getElementsByTagName('body').innerHTML = '<h1>Hello World</h1>';
107  
-        });
108  
-    });
109  
-
110  
-**package.json**:
111  
-
112  
-    {
113  
-        "name": "foo",
114  
-        "version": "0.0.1",
115  
-        "requirejs": {
116  
-            "app" : ["main"]
117  
-        }
118  
-    }
119  
-
120  
-The **requirejs** property is used to store info specific to RequireJS implementation. In this case, it has a property called "app" which is an array listing of all the top-level modules used by web pages. If the user created another page, called secondary.html and its top-level module was secondary.js, then the app array would change to **["main", "secondary"]**.
121  
-
122  
-## Add package to project
123  
-
124  
-To add the "bar" package, run either
125  
-
126  
-    pkg.js add bar 0.4 
127  
-
128  
-or
129  
-    pkg.js add bar http://some.domain.com/packages/bar/0.4.zip
130  
-
131  
-In the first example, a package repository or index would be consulted to find the URL to the 0.4 bar package, and basically convert the call to something like the second one, where an URL would be used to fetch the module. The module could be in source form (like a Git repository URL) or in zip form (details still to be worked out).
132  
-
133  
-bar would be downloaded to foo/.packages/bar, and the following files would be changed by pkg.js to the following:
134  
-
135  
-**package.json**:
136  
-
137  
-    {
138  
-        "name": "foo",
139  
-        "version": "0.0.1",
140  
-        "requirejs": {
141  
-            "app" : ["main"]
142  
-        },
143  
-        mappings: {
144  
-            "bar": {
145  
-                version: "0.4",
146  
-                location: "http://some.domain.com/packages/bar/0.4.zip"
147  
-            }
148  
-        }
149  
-    }
150  
-
151  
-The dependencies and mappings of those dependencies is stored in the package.json file, so that the .packages directory could be missing, but it still would be able to regenerate the correct directory layout. This is important since the .packages directory would likely not be committed to source control (but everything will still work if .packages is committed to source control).
152  
-
153  
-**main.js**:
154  
-
155  
-    //Start automatic config, do not alter by hand
156  
-    require({
157  
-        packagePaths: {
158  
-            ".packages": ["bar"]
159  
-        }
160  
-    });
161  
-    //End automatic config
162  
-
163  
-    require(['require'], function (require) {
164  
-
165  
-        //Use require.ready to register callbacks with the DOM is ready for access
166  
-        require.ready(function () {
167  
-            document.getElementsByTagName('body').innerHTML = '<h1>Hello World</h1>';
168  
-        });
169  
-    });
170  
-
171  
-main.js is modified to add the configuration of the packages and their paths. The config object's "packagePaths" property would be a new feature to RequireJS, it is not supported today.
172  
-
173  
-While it is unfortunate to have a tool modify a source file that will contain other manually edited code by the user, the goal of avoiding extra network requests to get this configuration is more important. It also means those top level app modules could be used directly in a server side environment like Node, via the r.js RequireJS adapter (assuimg the app module was coded specifically for a Node server environment).
174  
-
175  
-In the above example, it has:
176  
-    packagePaths: {
177  
-        ".packages": ["bar]
178  
-    }
179  
-
180  
-This means, "the bar package is located in the .packages basePath", so require("bar") would be translated to ".packages/bar/lib/main.js".
181  
-
182  
-An example of a more complex packagePaths configuration that loads packages "four" and "five" from a remote host on the fly, in the browser. They could be packages hosted on a CDN:
183  
-
184  
-    require({
185  
-        packagePaths: {
186  
-            '.packages': ['one', 'two', {name: 'three', lib: 'scripts'}],
187  
-            'http://something.com/packages': ['four', 'five'],
188  
-            'packages': ['six', ...],
189  
-            '../': []
190  
-        }
191  
-    });
192  
-
193  
-Each property of the packagePaths object is a base path to find some packages. The array values list the packages that are available under that base path. If the package has a different "lib" or "main" property, then instead of using a simple string for the package, an object is used to specify those extra properties, as shown for the "three" package above.
194  
-
195  
-This sort of representation is meant to minimize the size of the configuration object that is needed in the browser. If it turns out it adds a bunch of awkward code to RequireJS, then it may become more verbose, as in:
196  
-
197  
-    require({
198  
-        packages: {
199  
-            "one": ".packages",
200  
-            "two": ".packages",
201  
-            "three": {
202  
-                lib: "scripts"
203  
-            },
204  
-            "bar": {
205  
-                "location": ".packages/bar"
206  
-                //If "main" or "lib" were different they could be listed here
207  
-            },
208  
-            ...
209  
-        }
210  
-    });
211  
-
212  
-If there is specific package config per package, then a "packages" property could be used. This would allow, for instance, having packagePaths that are specific to a package (assuming packagePaths works out in the implementation):
213  
-
214  
-    require({
215  
-        packages: {
216  
-            "bar": {
217  
-                packagePaths: {
218  
-                    //Similar to above, but only applies to packages used by bar
219  
-                },
220  
-                //Conceptually, could allow for a "packages" property here, if
221  
-                //there were specific package properties that only applied to
222  
-                //packages loaded by the "bar" package.
223  
-            }
224  
-        }
225  
-    });
226  
-
227  
-This would allow very specific configuration for each package and its sub-packages. While RequireJS does not support that model today, it could in the future. Right now RequireJS supports multiple versions of a module being loaded in a page, but it means creating top-level "contexts", and not something as granular as a per-package context. However, it may make sense to move to per-package contexts, and I can see a path to supporting it.
228  
-
229  
-# Summary
230  
-
231  
-Hopefully this outline gives a path to supporting packages that work with RequireJS in the browser directly, without a bunch of run-time configuration, but still allow fine-grained debugging support.
232  
-
233  
-Of course, the optimization tool in RequireJS could be used to combine some of the package modules together to make it even faster in dev, and just exclude the specific modules you want to debug. However, it still may make sense to load some packages directly off a third party server, like a CDN, so this approach still allows that.
234  
-
235  
-The optimization tool may need to change to allow inlining package modules, or at least making sure each module included in the optimized layer is mapped correctly to its package. However, that work will be outlined separately, and only if the above makes sense.
236  
-
237  
-# Low level implementation Notes
238  
-
239  
-Mostly notes for me on implementation so I do not forget:
240  
-
241  
-For the data-main="main" attribute on script tags:
242  
-Means that built require.js that includes app needs to not fetch extra scripts
243  
-until after whole file is parsed? Use setTimeout? But that means behavior
244  
-is different vs. command line, but its ok, won't be that different, just order
245  
-of scripts. Hmm, could affect order! plugins, but that is just used in browser
246  
-anyway. But still confusing vs something that defines require = {} before
247  
-the script. Maybe just needs docs, and go with setTimeout if it is available.
248  
-
249  
-Consider using/reusing/adapting Kris Zyp's [Nodules](http://github.com/kriszyp/nodules) as a basis for implementation for pkg.js. It is Dojo Foundation CLA-approved code.
  3
+Moved to [pkg project](http://github.com/jrburke/pkg/blob/master/docs/design/packages.md)
40  pkg/README.md
Source Rendered
... ...
@@ -1,40 +0,0 @@
1  
-# pkg.js
2  
-
3  
-This is a package manager for projects that user RequireJS. It knows how to update the paths for your project so it can effectively load modules from packages. pkg.js can also download packages from remote locations so that they load locally, and can be used as part of the RequireJS optimization tool.
4  
-
5  
-It can use packages that have its modules written in the CommonJS module format. For those modules it will download the package to your project and convert the modules to RequireJS syntax.
6  
-
7  
-## What are packages?
8  
-
9  
-TODO: explain
10  
-
11  
-## Usage
12  
-
13  
-### Prerequisites
14  
-
15  
-Need Node or Java 1.5 or later installed.
16  
-
17  
-TODO: explain how to use it.
18  
-
19  
-Projects conform to this layout:
20  
-xxxx
21  
-
22  
-* pkg.js createApp appName
23  
-    * Generates dir structure, stub an application-level package.json file.
24  
-    * puts in require.js and r.js (the RequireJS/Node adapter) too.
25  
-* pkg.js add packageName version (or an URL)
26  
-    * pkg.js adds the package dependency info to app's package.json
27  
-    * fetches the dependency and any of its dependencies.
28  
-* pkg.js create packageName : creates a new package that is subject to version control.
29  
-    * Different from createApp in that it is assumed not to have require.js included, and it will not need to update config objects in the top-level app modules.
30  
-* pkg.js update : updates the app config to be the latest based on sub packages.
31  
-* pkg.js addApp name: adds a new entry to package.json's app property, updates
32  
-  that named module to have the automated require() config injection. Used to specify
33  
-  what top-level app modules are used in the webapp. App modules are the top level
34  
-  module that is loaded by a particular web page.
35  
-
36  
-
37  
-
38  
-## What does pkg.js do?
39  
-
40  
-TODO: explain the steps that pkg.js does under the covers.
124  pkg/jslib/pkg.js
... ...
@@ -1,124 +0,0 @@
1  
-/**
2  
- * @license Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved.
3  
- * Available via the MIT, GPL or new BSD license.
4  
- * see: http://github.com/jrburke/requirejs for details
5  
- */
6  
-
7  
-/*jslint  */
8  
-/*global print: false, load: false, fileUtil: false */
9  
-
10  
-"use strict";
11  
-
12  
-var pkg;
13  
-(function () {
14  
-
15  
-    function printError(action, err) {
16  
-        print(action + ': ' + err);
17  
-    }
18  
-
19  
-    function usage() {
20  
-        var actions = pkg.actions, prop;
21  
-
22  
-        print('\npkg.js, a package tool for RequireJS. Allowed commands:\n');
23  
-
24  
-        for (prop in actions) {
25  
-            if (actions.hasOwnProperty(prop)) {
26  
-                print(prop + ': ' + actions[prop].doc + '\n');
27  
-            }
28  
-        }
29  
-        return new Error('Unsupported command.');
30  
-    }
31  
-
32  
-    /**
33  
-     * Runs the package tool.
34  
-     * @param {Array} args the array of command line arguments.
35  
-     */
36  
-    pkg = function (args) {
37  
-        var request = {
38  
-                pkgHome: args[0],
39  
-                action: args[1],
40  
-                target: args[2],
41  
-                option: args[3]
42  
-            },
43  
-            pkgHome = request.pkgHome,
44  
-            actionObj = pkg.actions[request.action],
45  
-            error;
46  
-
47  
-        //Load helper libs
48  
-        if (pkgHome.charAt(pkgHome.length - 1) !== "/") {
49  
-            pkgHome += "/";
50  
-            request.pkgHome = pkgHome;
51  
-        }   
52  
-        ["../../build/jslib/logger", "../../build/jslib/fileUtil"].forEach(function (path) {
53  
-            load(pkgHome + "jslib/" + path + ".js");
54  
-        });
55  
-
56  
-        if (!actionObj) {
57  
-            error = usage();
58  
-        } else {
59  
-            error = actionObj.validate(request);
60  
-        }
61  
-
62  
-        if (!error) {
63  
-            error = actionObj.run(request);
64  
-        }
65  
-
66  
-        if (error) {
67  
-            printError(request.action, error);
68  
-        }
69  
-
70  
-        return error;
71  
-    };
72  
-
73  
-    pkg.actions = {
74  
-        'createApp': {
75  
-            doc: 'Creates a new application. Pass it the name of the application to create.',
76  
-            validate: function (request) {
77  
-                if (!request.target || !(/^[A-Za-z\d\-]+$/.test(request.target))) {
78  
-                    return new Error('Application name can only contain alphanumeric and dash characters.');
79  
-                }
80  
-                return undefined;
81  
-            },
82  
-            run: function (request) {
83  
-                var contents,
84  
-                    packageFile = request.target + '/package.json';
85  
-
86  
-                //Copy over the template of files.
87  
-                fileUtil.copyDir(request.pkgHome + 'templates/createApp', request.target);
88  
-                
89  
-                //Update the name of the app in package.json
90  
-                contents = fileUtil.readFile(packageFile);
91  
-                contents = contents.replace(/\%APPNAME\%/g, request.target);
92  
-                fileUtil.saveFile(packageFile, contents);
93  
-            
94  
-                //Copy RequireJS to the lib directory
95  
-                fileUtil.copyFile(request.pkgHome + '../require.js', request.target + '/lib/require.js');
96  
-                fileUtil.copyDir(request.pkgHome + '../require', request.target + '/lib/require');
97  
-            }
98  
-        },
99  
-
100  
-        'add': {
101  
-            doc: 'Add a third party package to your project.',
102  
-            validate: function (request) {
103  
-                if (!request.target) {
104  
-                    return new Error('Please specify a package name.');
105  
-                }
106  
-                return undefined;
107  
-            },
108  
-            run: function (request) {
109  
-                var packageName = request.target,
110  
-                    version = request.option,
111  
-                    url;
112  
-
113  
-                //Package may just be an URL
114  
-                if (packageName.indexOf(':')) {
115  
-                    url = packageName;
116  
-                    packageName = null;
117  
-                }
118  
-            }
119  
-        }
120  
-pkg.js add packageName version
121  
-
122  
-    };
123  
-
124  
-}());
2  pkg/pkg.bat
... ...
@@ -1,2 +0,0 @@
1  
-set MYDIR=%~dp0
2  
-java -classpath %MYDIR%/../build/lib/rhino/js.jar;%MYDIR%/../build/lib/closure/compiler.jar org.mozilla.javascript.tools.shell.Main %MYDIR%/pkg.js %MYDIR% %*
20  pkg/pkg.js
... ...
@@ -1,20 +0,0 @@
1  
-/**
2  
- * @license Copyright (c) 2010, The Dojo Foundation All Rights Reserved.
3  
- * Available via the MIT, GPL or new BSD license.
4  
- * see: http://github.com/jrburke/requirejs for details
5  
- */
6  
-
7  
-/*jslint regexp: false, nomen: false, plusplus: false */
8  
-/*global  */
9  
-
10  
-"use strict";
11  
-
12  
-(function (args) {
13  
-    var requireBuildPath = args[0];
14  
-    if (requireBuildPath.charAt(requireBuildPath.length - 1) !== "/") {
15  
-        requireBuildPath += "/";
16  
-    }
17  
-    load(requireBuildPath + "jslib/pkg.js");
18  
-    pkg(args);
19  
-
20  
-}(Array.prototype.slice.call(arguments)));
4  pkg/pkg.sh
... ...
@@ -1,4 +0,0 @@
1  
-#!/bin/sh
2  
-
3  
-MYDIR=$(cd $(dirname "$0"); pwd)
4  
-java -classpath $MYDIR/../build/lib/rhino/js.jar:$MYDIR/../build/lib/closure/compiler.jar org.mozilla.javascript.tools.shell.Main $MYDIR/pkg.js $MYDIR "$@"
4  pkg/pkgdebug.sh
... ...
@@ -1,4 +0,0 @@
1  
-#!/bin/sh
2  
-
3  
-MYDIR=$(cd $(dirname "$0"); pwd)
4  
-java -classpath $MYDIR/../build/lib/rhino/js.jar:$MYDIR/../build/lib/closure/compiler.jar org.mozilla.javascript.tools.debugger.Main $MYDIR/pkg.js $MYDIR "$@"
8  pkg/templates/createApp/index.html
... ...
@@ -1,8 +0,0 @@
1  
-<!DOCTYPE html>
2  
-<html>
3  
-<head>
4  
-    <script data-main="main" src="lib/require.js"></script>
5  
-</head>
6  
-<body>
7  
-</body>
8  
-</html>
7  pkg/templates/createApp/lib/main.js
... ...
@@ -1,7 +0,0 @@
1  
-require(['require'], function (require) {
2  
-
3  
-    //Use require.ready to register callbacks with the DOM is ready for access
4  
-    require.ready(function () {
5  
-        document.getElementsByTagName('body').innerHTML = '<h1>Hello World</h1>';
6  
-    });
7  
-});
7  pkg/templates/createApp/package.json
... ...
@@ -1,7 +0,0 @@
1  
-{
2  
-    "name": "%APPNAME%",
3  
-    "version": "0.0.1",
4  
-    "requirejs": {
5  
-        "app" : ["main"]
6  
-    }
7  
-}
5  tasks.txt
... ...
@@ -1,5 +1,3 @@
1  
-- get plugins to work in node?
2  
-
3 1
 - jQuery 1.4.3:
4 2
   - Open ticket about just registering via require.def with something like:
5 3
   if( typeof require !== "undefined" && require.def )
@@ -19,6 +17,9 @@
19 17
 Need to figure out why. Function.toString() seems to work.
20 18
 
21 19
 - Build: do not require the user to be in the same directory as build file?
  20
+- Build: if optimize the build profile file, Closure compiler complains. If put parens
  21
+  around the object, then it is OK. Decide if want to go that route or pure JSON route,
  22
+  (which still may fail?) or have build profile skip build files?
22 23
 
23 24
 - Move to using module.basePath instead of require.nameToUrl inside methods? basePath
24 25
   would basically call nameToUrl but use no suffix. Hmm, nice thing about nameToUrl is it

0 notes on commit c203037

Please sign in to comment.
Something went wrong with that request. Please try again.