packages feed

duplo (empty) → 1.6.0

raw patch · 5 files changed

+607/−0 lines, 5 filesdep +MissingHdep +aesondep +aeson-prettysetup-changed

Dependencies added: MissingH, aeson, aeson-pretty, ansi-terminal, base, base64-bytestring, bytestring, containers, directory, executable-path, filepath, filepather, fsnotify, http-types, language-javascript, lens, mtl, process, regex-compat, scotty, shake, system-fileio, system-filepath, text, text-format, transformers, unordered-containers, utf8-string, wai, warp

Files

+ LICENSE view
@@ -0,0 +1,20 @@+The MIT License (MIT)+Copyright (c) 2014 Pixbi++Permission is hereby granted, free of charge, to any person obtaining a copy of+this software and associated documentation files (the "Software"), to deal in+the Software without restriction, including without limitation the rights to+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies+of the Software, and to permit persons to whom the Software is furnished to do+so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+SOFTWARE.
+ README.md view
@@ -0,0 +1,318 @@+# duplo++A opinionated, framework-less build tool for web applications+++## Installation++1. Get a [Mac](http://www.apple.com/mac/)+2. Install [Homebrew](http://brew.sh/)+3. Get [npm](http://npmjs.org/): `brew install npm`+4. Get [GMP](https://gmplib.org/): `brew install gmp`+5. Get pre-1.x.x [Component](https://github.com/componentjs): `npm install -g+   component@0.19.9`+6. Get [duplo](https://github.com/pixbi/duplo): `npm install -g duplo`+++## Usage++* `duplo help` displays all commands.+* `duplo info` displays the version for this duplo installation.+* `duplo init <user> <repo>` scaffolds a new duplo repo in the current+  directory.+* `duplo build` builds the project. `DUPLO_ENV` defaults to `dev`.+* `duplo dev`: starts a webserver, watches for file changes, and builds in+  development environment.+* `duplo test`: build test cases and you should run it in browser by yourself.+* `duplo production`: like `duplo dev` but builds in production environment+* `duplo patch` bumps the patch version.+* `duplo minor` bumps the minor version.+* `duplo major` bumps the major version.+++## Guiding Principle++This is a build tool, not an application framework. It simply compiles and+builds your codebase for you and does not inject a runtime into or impose a+structure on your application.++However, it does have opinions, specifically:++* [Jade](http://jade-lang.com/) over HTML+* [Stylus](http://learnboost.github.io/stylus/) over CSS+* [GitHub](http://github.com/) and by extension [git](http://git-scm.com/) for+  source code management+* [Heroku](https://www.heroku.com/) for application deployment+* [Selenium](http://docs.seleniumhq.org/) for automated browser testing+* [CircleCI](https://circleci.com/) for continuous integration++The idea is to manage and deploy your code exclusively with git and have+CircleCI deals with deployment for you. However, duplo is a build tool; it+doesn't care about the exact structure of your application. This means that all+scripts are dumped into one single file, and so are the stylesheets and the+markup.+++## File Structure++    app/            --> Application code+    app/index.jade  --> Entry point for markups. Only this file is compiled.+                        Use Jade's include system to pull in other markups.+    app/index.js    --> Application entry point. Only the top-level+                        application's `index.js` is included and run. Its+                        dependencies' are ignored.+    app/assets/     --> Asset files are copied as-is to build's top-level+                        directory+    app/styl/       --> It contains "special" stylesheets that get loaded+                        before any other stylesheets.+    app/modules/    --> All other application code not listed above must be+                        placed here. All files at the `app/` level are not+                        included by default.+    components/     --> Other repos imported via Component.IO+    component.json  --> The Component.IO manifest+    dev/            --> Files here are included only when building in+                        development mode.+    dev/assets/     --> Copied as-is just like `app/assets/`. Files here would+                        replace those with the same name under `app/assets/`.+    dev/modules/    --> Works just like `app/modules/`.+    public/         --> Built files when developing. Not committed to source+    test/           --> Test files go here+++## Development++During development, everything in the `dev/assets/` directory is copied over+as-is *at the end* of the build process. This means that files in the directory+would replace whatever that has been built (or copied over from `app/assets/`)+at their respective locations.++Anything under `dev/modules/` would be treated just like those under+`app/modules/`, that they would be concatenated/compiled into the respective+output files (i.e. `index.html`, `index.css`, or `index.js`).+++## Environment++duplo injects the `DUPLO_ENV` global variable with the value from the+environment variable of the same name when building. There is no default value.+++## Entry Point++Every application has a main entry point. In a duplo application, it is+`app/index.js`. Each repo may contain its own `app/index.js` but only the repo+on which duplo is run does duplo execute `app/index.js`. Note that+`app/index.js` is excluded when duplo commits via Component.IO so that the+consuming application does not see library index files when building the+project.++Note that duplo only inspects the *top-level* `define()`. If you use+`require()`, your program may not execute as duplo is not aware of anything+other `define()` declarations. The proper way to declare an entry point in+`app/index.js` is:++```js+define('main', [/* ... dependencies ... */], function (/* ... dependencies ... */) {+  // Code here ...+});+```+++## Application Parameterization++If you need some build-time customization of the app, such as customizing each+build with a JSON object of unique IDs and metadata, you can pass any string+as the environment variable `DUPLO_IN`. The string is then turned into a+JavaScript string and stored into a global variable.++To avoid special characters, `DUPLO_IN` must be base-64 encoded.++For example, say you need to pass in a random ID for each build, you would+invoke:++```sh+// Content decoded as: `{"id":"someId"}`+$ env DUPLO_IN="eyJpZCI6InNvbWVJZCJ9" duplo+```++Then in `app/index.js`:++```js+var someId = DUPLO_IN.id;+```++Note that all newline characters are removed before the string is wrapped into+a JavaScript string.+++## JavaScript Concatenation Order++JavaScript files are not concatenated in any particular order. You must wrap+code inside an AMD module and declaring its dependencies. For code that needs+to be executed at initialization, utilize the environment's initialization+event such as `document.addEventListener("DOMContentLoaded")` to bootstrap the+rest of the script.+++## CSS/Stylus Concatenation Order++Unlike script files, where you place your CSS files within `app/` is+significant. Stylus files will be concatenated in this order:++    app/styl/variables.styl --> An optional variable file that gets injected+                                into every Stylus file+    app/styl/keyframes.styl --> Keyframes+    app/styl/fonts.styl     --> Font declarations+    app/styl/reset.styl     --> Resetting existing CSS in the target+                                environment+    app/styl/main.styl      --> Application CSS that goes before any module+                                CSS+    app/**/*.styl           --> All other CSS files++There is no particular concatenation order between different dependencies.+++## HTML/Jade Concatenation Order++Jade files are concatenated in no particular order as the Jade include system+is used for explicit ordering.+++## Automatic rewriting for Jade++duplo does not and cannot peek into Jade's include system. However, it does+automatically expand paths in include statements to make the inclusion process+easier. Take this example:++```jade+include index.jade+include menu/index.jade+include pixbi-helper/index.jade+```++In the absence of a Component repo string (i.e. `<user>-<repo>`), the path is+assumed to be pointing to a file under the `modules` directory in the current+repo. With a component repo string, it is assumed to also be pointing to a file+under the `modules` directory, but in the corresponding component's repo.++The above is effectively rewritten into these paths, relative to the top-level+repo's directory.++```jade+include app/modules/index.jade+include app/modules/menu/index.jade+include components/pixbi-helper/app/modules/index.jade+```+++## A note on the `modules` directory++By now, it should be obvious that there are really two "modes" for any duplo+repo: an application mode and a library mode. In application mode, duplo acts+as the top-level program, including other duplo repos via Component as+libraries. In this scenario, `app/index.js` and `app/index.jade` are included+into the build. Contrast this to the library mode, where only those in the+"second" level (e.g. `modules/`, `assets/`, `styl/`) are included into the+build.+++## Component Versions++Each component's version is recorded in the `DUPLO_VERSIONS` global variable,+in the form similar to:++```json+{+  "pixbi-main": "4.1.9",+  "pixbi-launcher": "0.1.4"+}+```+++## Dependency Selection++Some cases require the repo to be polymorphic in the sense that we could+generate different forms of the same codebase. For example, you may need to+build the repo in an embeddable form which would exclude certain dependencies+that are required in its standalone form.++In this case you would include a `modes` attribute in the `component.json`+manifest file. The attribute would contain an `embeddable` and a `standalone`+attributes, each of which would then contain an array of dependencies as+specified in the `dependencies` attribute to include.++Running duplo with the environment variable `DUPLO_MODE` set to `embeddable`+would build with the dependencies specified under `embeddable` while setting+`MODE` to `standalone` would do the same with those specified under the+`standalone` attribute. Otherwise duplo would just build with all dependencies.++Note that dependency selection applies at the dependency level but not at the+file level within the components.++Also note that duplo caches between builds. When you switch dependency+selection, remember to `duplo clean` your repo first.++Putting it all together, an example of a `component.json`:++```json+{+  "dependencies": {+    "pixbi/sdk": "1.1.1",+    "pixbi/embeddable": "2.2.2",+    "pixbi/standalone": "3.3.3"+  },+  "modes": {+    "embeddable": [+      "pixbi/standalone"+    ],+    "standalone": [+      "pixbi/embeddable"+    ]+  }+}+```+++## Duplo Log++Note that tasks are run in parallel so the display log may look scrambled from+line to line. This is normal.+++## Testing++Follow these steps:++1. Create the directory `test` in your project root directory, alongside with+   `dev` and `app`.+2. Create your unit test cases in the directory `test/modules`.+3. Put the following in the file `test/modules/index.jade`:++```jade+html+  head+    title Testing Result+    link(rel="stylesheet", href="vender/mocha.css")+    script(src="vender/mocha.js").+    script.+      mocha.setup('bdd');++  body+    div#mocha+      p+        a(href=".") Index+    div#messages+    div#fixtures++    script(src="./index.js").+    script.+      mocha.run()+```++Then run `duplo test`. Open `public/index.html` in your browser for the test+results.++## Copyright and License++Code and documentation copyright 2014 Pixbi. Code released under the MIT+license. Docs released under Creative Commons.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ duplo.cabal view
@@ -0,0 +1,52 @@+name:                  duplo+version:               1.6.0+synopsis:              Frontend development build tool+description:           Intuitive, simple building blocks for building composable, completely self-managed web applications+license:               MIT+license-file:          LICENSE+author:                Kenneth Kan+maintainer:            ken@pixbi.com+category:              Web+build-type:            Simple+extra-source-files:    README.md+cabal-version:         >=1.10++source-repository head+  type:                git+  location:            https://github.com/pixbi/duplo.git++executable duplo+  main-is            : Duplo.hs+  build-depends      : base                 == 4.7.*+                     , MissingH             == 1.3.*+                     , aeson                == 0.8.*+                     , aeson-pretty         == 0.7.*+                     , base64-bytestring    == 1.0.*+                     , bytestring           == 0.10.*+                     , containers           == 0.5.*+                     , directory            == 1.2.*+                     , executable-path      == 0.0.*+                     , filepath             == 1.3.*+                     , filepather           == 0.3.*+                     , fsnotify             == 0.1.*+                     , http-types           == 0.8.*+                     , language-javascript  == 0.5.*+                     , lens                 == 4.5.*+                     , mtl                  == 2.2.*+                     , process              == 1.2.*+                     , regex-compat         == 0.95.*+                     , scotty               == 0.9.*+                     , shake                == 0.14.*+                     , system-fileio        == 0.3.*+                     , system-filepath      == 0.4.*+                     , text                 == 1.1.*+                     , transformers         == 0.4.*+                     , unordered-containers == 0.2.*+                     , utf8-string          == 0.3.*+                     , wai                  == 3.0.*+                     , warp                 == 3.0.*+                     , ansi-terminal        == 0.5.*+                     , text-format          == 0.3.*+  hs-source-dirs     : src/+  default-language   : Haskell2010+  default-extensions : OverloadedStrings
+ src/Duplo.hs view
@@ -0,0 +1,215 @@+{-# LANGUAGE ScopedTypeVariables #-}++import Control.Applicative ((<$>))+import Control.Exception (catch, handle, throw, Exception)+import Control.Lens.Operators+import Control.Monad (void, when, unless)+import Control.Monad.Trans.Maybe (MaybeT(..), runMaybeT)+import Data.ByteString.Base64 (decode)+import Data.ByteString.Char8 (pack, unpack)+import Data.Maybe (fromMaybe)+import Data.String.Utils (replace)+import Development.Duplo.Server (serve)+import Development.Duplo.Shake (shakeMain)+import Development.Duplo.Utilities (logStatus, errorPrintSetter)+import Development.Duplo.Watcher (watch)+import Development.Shake (cmd, ShakeException(..))+import Development.Shake.FilePath ((</>))+import GHC.Conc (forkIO)+import System.Console.GetOpt (getOpt, OptDescr(..), ArgDescr(..), ArgOrder(..))+import System.Directory (getCurrentDirectory, createDirectoryIfMissing)+import System.Environment (lookupEnv, getArgs, getExecutablePath)+import System.FilePath.Posix (takeDirectory)+import System.Process (proc, createProcess, waitForProcess)+import qualified Control.Lens+import qualified Development.Duplo.Component as CM+import qualified Development.Duplo.Types.AppInfo as AI+import qualified Development.Duplo.Types.Builder as TB+import qualified Development.Duplo.Types.Config as TC+import qualified Development.Duplo.Types.Options as OP+import qualified Filesystem.Path+import qualified GHC.IO++main = do+  -- Command-line arguments+  args <- getArgs++  let (cmdName:cmdArgs) =+        if   (length args > 0)+        -- Normal case (with command name)+        then args+        -- Edge cases (with nothing provided)+        else ("":[])++  -- Deal with options+  let (actions, nonOptions, errors) = getOpt Permute OP.options args+  options <- foldl (>>=) (return OP.defaultOptions) actions++  -- Port for running dev server+  port       <- fmap read $ fromMaybe "8888" <$> lookupEnv "PORT"+  -- Environment - e.g. dev, staging, production+  duploEnvMB <- lookupEnv "DUPLO_ENV"+  -- Build mode, for dependency selection+  duploMode  <- fromMaybe "" <$> lookupEnv "DUPLO_MODE"+  -- Application parameter+  duploIn    <- fromMaybe "" <$> lookupEnv "DUPLO_IN"+  -- Current working directory+  cwd        <- getCurrentDirectory+  -- Duplo directory, assuming this is a build cabal executable (i.e.+  -- `./dist/build/duplo/duplo`)+  duploPath  <- fmap ((</> "../../../") . takeDirectory) getExecutablePath++  -- Base64 decode+  let duploInDecoded = case (decode $ pack $ duploIn) of+                         Left _      -> ""+                         Right input -> unpack input++  -- Paths to various relevant directories+  let nodeModulesPath = duploPath </> "node_modules/.bin/"+  let utilPath        = duploPath </> "util/"+  let distPath        = duploPath </> "dist/build/"+  let miscPath        = duploPath </> "etc/"+  let defaultsPath    = miscPath </> "static/"+  let appPath         = cwd </> "app/"+  let devPath         = cwd </> "dev/"+  let testPath        = cwd </> "test/"+  let assetsPath      = appPath </> "assets/"+  let depsPath        = cwd </> "components/"+  let targetPath      = cwd </> "public/"++  -- Extract environment+  let duploEnv' = case cmdName of+                    -- `build` is a special case. It takes `production` as the+                    -- default.+                    "build" -> maybe "production" id duploEnvMB+                    -- By default, `dev` is the default.+                    _       -> maybe "dev" id duploEnvMB++  -- Internal command translation+  let (cmdNameTranslated, bumpLevel, duploEnv, toWatch) =+        case cmdName of+          "info"       -> ("version", "", duploEnv', False)+          "ver"        -> ("version", "", duploEnv', False)+          "new"        -> ("init", "", duploEnv', False)+          "bump"       -> ("bump", "patch", duploEnv', False)+          "release"    -> ("bump", "patch", duploEnv', False)+          "patch"      -> ("bump", "patch", duploEnv', False)+          "minor"      -> ("bump", "minor", duploEnv', False)+          "major"      -> ("bump", "major", duploEnv', False)+          "dev"        -> ("build", "", "dev", True)+          "live"       -> ("build", "", "production", True)+          "production" -> ("build", "", "production", True)+          "build"      -> ("build", "", duploEnv', False)+          "test"       -> ("build", "", "test", False)+          _            -> (cmdName, "", duploEnv', False)++  -- Certain flags turn into commands.+  let cmdNameWithFlags = if   (OP.optVersion options)+                         then "version"+                         else cmdNameTranslated++  -- Display version either via command or option. We need to do this+  -- before any `readManifest` as it throws an error when there isn't one,+  -- as it should.+  when (cmdNameWithFlags == "version") $ do+    let command = utilPath </> "display-version.sh"+    let process = createProcess $ proc command [duploPath]++    -- Run the command.+    (_, _, _, handle) <- process+    -- Remember to do it synchronously.+    void $ waitForProcess handle++  -- We only care about exceptions thrown by the builder.+  let ignoreManifestError' (e :: TB.BuilderException) = case e of+        -- Only when missing manifest+        TB.MissingManifestException _ -> return ""+        -- Re-throw other builder exceptions.+        _ -> throw e+  -- Helper function to ignore exceptions, only for this stage, before+  -- Shake is run.+  let ignoreManifestError io = catch io ignoreManifestError'++  -- Gather information about this project+  appName    <- ignoreManifestError $ fmap AI.name CM.readManifest+  appVersion <- ignoreManifestError $ fmap AI.version CM.readManifest+  appId      <- ignoreManifestError $ fmap CM.appId CM.readManifest++  -- We may need custom builds with mode+  let depManifestPath = cwd </> "component.json"+  dependencies <- CM.getDependencies $ case duploMode of+                                         "" -> Nothing+                                         a  -> Just a+  let depIds = fmap (replace "/" "-") dependencies++  -- Display additional information when verbose.+  when (OP.optVerbose options) $+    -- More info about where we are+    putStr $ "\n"+          ++ ">> Current Directory\n"+          ++ "Application name                : "+          ++ appName ++ "\n"+          ++ "Application version             : "+          ++ appVersion ++ "\n"+          ++ "Component.IO repo ID            : "+          ++ appId ++ "\n"+          ++ "Current working directory       : "+          ++ cwd ++ "\n"+          ++ "duplo is installed at           : "+          ++ duploPath ++ "\n"+    -- Report back what's given for confirmation.+          ++ "\n"+          ++ ">> Environment Variables\n"+          ++ "DUPLO_ENV (runtime environment) : "+          ++ duploEnv ++ "\n"+          ++ "DUPLO_MODE (build mode)         : "+          ++ duploMode ++ "\n"+          ++ "DUPLO_IN (app parameters)       : "+          ++ duploInDecoded ++ "\n"++  -- Construct environment+  let buildConfig = TC.BuildConfig { TC._appName      = appName+                                   , TC._appVersion   = appVersion+                                   , TC._appId        = appId+                                   , TC._cwd          = cwd+                                   , TC._duploPath    = duploPath+                                   , TC._env          = duploEnv+                                   , TC._mode         = duploMode+                                   , TC._nodejsPath   = nodeModulesPath+                                   , TC._dist         = distPath+                                   , TC._input        = duploIn+                                   , TC._utilPath     = utilPath+                                   , TC._miscPath     = miscPath+                                   , TC._defaultsPath = defaultsPath+                                   , TC._appPath      = appPath+                                   , TC._devPath      = devPath+                                   , TC._testPath     = testPath+                                   , TC._assetsPath   = assetsPath+                                   , TC._depsPath     = depsPath+                                   , TC._targetPath   = targetPath+                                   , TC._bumpLevel    = bumpLevel+                                   , TC._port         = port+                                   , TC._dependencies = depIds+                                   }++  -- Construct the Shake command+  let shake' = shakeMain cmdNameWithFlags cmdArgs buildConfig options+  let shake  = shake' `catch` handleExc++  -- Watch or just build+  unless toWatch $ shake+  when toWatch $ do+    -- Start a local server+    _ <- forkIO $ serve port++    let targetDirs = [devPath, appPath, depsPath]+    -- Make sure we have these directories to watch+    mapM_ (createDirectoryIfMissing True) targetDirs+    -- Watch for file changes+    watch shake targetDirs++-- | Handle all errors+handleExc (e :: ShakeException) = do+    putStr $ show e+    logStatus errorPrintSetter "Build failed"+    putStrLn ""