patternpack
Version:
Build and Document Your Interface. Then Share the Code.
240 lines (199 loc) • 8.8 kB
JavaScript
module.exports = function (grunt) {
"use strict";
var log = require("../gruntLogHelper.js")(grunt);
var fs = require("fs");
var path = require("path");
var _ = require("lodash");
_.defaultsDeep = require("merge-defaults"); // Add deep defaults capabilities to lodash
var packagePath = "./" + path.relative(process.cwd(), path.dirname(__dirname)); // "./node_modules/patternpack"
var packageName = path.basename(packagePath); // "patternpack"
var tasksValues = ["default", "build", "integrate", "release", "release-patch", "release-minor", "release-major", "build-styles", "build-pages", "build-patterns", "", undefined];
var cssPreprocessorValues = ["less", "sass", "none", "", undefined];
var gruntTaskName = "patternpack";
var gruntTaskDescription = "Creates a pattern library from structured markdown and styles.";
var optionsOverrideFileName = ".patternpackrc";
var optionDefaults = {
// Paths for input and output
release: "./dist",
build: "./html",
src: "./src",
assets: "./src/assets",
theme: "patternpack-example-theme",
data: "./data",
logo: "/theme-assets/images/logo.svg",
// Operation to run (default|build|release)
// TODO: consider using a flag for the "MODE" of operation (dev|build|release)
// task: "build",
// Configure our CSS
css: {
preprocessor: "sass", // which preprocessor we should use (sass|less|none)
fileName: "patterns", // the name for our final CSS file that will import everything
autoprefixer: {
browsers: ["last 2 versions"]
}
},
// Configures the ability to only publish certain resources (css|library|patterns)
publish: {
library: true,
patterns: false
},
// Configures the pattern hierarchy.
patternStructure: [
{ name: "Atoms", path: "atoms" },
{ name: "Molecules", path: "molecules" },
{ name: "Pages", path: "pages" }
],
server: {
port: 8888
}
};
function getPathOrPackagePath(path) {
var packagePath;
// Attempt to find the configured location
if (fs.existsSync(path)) {
packagePath = path;
}
// If not found, look for the path as a node package
if (!packagePath) {
packagePath = getPackagePath(path);
}
if (!packagePath) {
throw new Error("Could not find package: " + path);
}
return packagePath;
}
function getPackagePath(name) {
var currentPath = "./" + path.relative(process.cwd(), path.dirname(__dirname));
var dependencyPaths = [
"./node_modules/",
"../node_modules/",
currentPath + "/node_modules/"
];
// Look for the package in any of the node_modules locations
var packageDependencyPath = _.find(dependencyPaths, function(path) {
return fs.existsSync(path + name);
});
// Return the validated location of the package
// Otherwise return undefined
return packageDependencyPath ? packageDependencyPath + name : undefined;
}
function applyOverrides(value, overrideValue) {
return _.defaultsDeep(_.cloneDeep(overrideValue), value);
}
function getOptions(context) {
var options = {};
var optionOverrides = context.options();
var optionOverridesFile = grunt.file.exists(optionsOverrideFileName) ? grunt.file.readJSON(optionsOverrideFileName) : {};
// If the task is allowed then use it as the default value.
// Otherwise leave the task blank, which will result in "default" being called
if (_.contains(tasksValues, context.target)) {
optionDefaults.task = context.target;
}
// Override the defaults with any user specified options
// Apply overrides from the .patternpackrc file
// then apply the overrries from the gruntfile options
options = applyOverrides(optionDefaults, optionOverrides);
options = applyOverrides(options, optionOverridesFile);
// Resolve the theme path either from a path or from a package name
log.verbose("Theme paths");
log.verbose("Default: " + optionDefaults.theme);
log.verbose("Override: " + optionOverrides.theme);
options.theme = optionOverrides.theme ? optionOverrides.theme : optionDefaults.theme;
options.theme = getPathOrPackagePath(options.theme);
options.theme = path.relative(packagePath, options.theme)
log.verbose("Resolved: " + options.theme);
return options;
}
function transformOptions(options) {
// Add the relative path to the root of the calling pattern library
options.root = path.relative(packagePath, "");
// Massage any paths to be relative to the child process
options.release = path.relative(packagePath, options.release);
options.build = path.relative(packagePath, options.build);
options.src = path.relative(packagePath, options.src);
options.assets = path.relative(packagePath, options.assets);
options.data = path.relative(packagePath, options.data);
// Resolve the application integration path if the user has provided it
if (options.integrate) {
options.integrate = path.relative(packagePath, options.integrate);
}
return options;
}
function saveOptions(options) {
var file = packagePath + "/gruntfileConfig.json";
var contents = JSON.stringify(options);
grunt.file.write(file, contents);
}
function ensureOptions(options, name, allowedValues, allowFalsey) {
var value = options[name];
if (!_.contains(allowedValues, value)) {
log.log("Allowed values for " + name + "");
log.log(allowedValues);
throw new Error("The option " + name + " was set to an unacceptable value: " + value);
}
}
function ensureFilesExist(options) {
// TODO: File generation should be enhanced to use templates rather than
// the current implementation that generates the files inline.
var mkdirp = require("mkdirp");
// Create core css file if it does not exist
var coreCssExtension = options.css.preprocessor === "sass" ? "scss" : options.css.preprocessor;
var coreCssFileName = options.css.fileName + "." + coreCssExtension;
var coreCssDirectory = options.assets + "/" + options.css.preprocessor;
var coreCssPath = coreCssDirectory + "/" + coreCssFileName;
if (!fs.existsSync(coreCssPath)) {
log.verbose("Generating the core CSS file: " + coreCssPath);
mkdirp.sync(coreCssDirectory);
fs.writeFileSync(coreCssPath, '@import "_patternpack-patterns";'); // TODO: Make this a template based on Sass/LESS - LESS requires the underscore
}
// Create the dirctories for the pattern structure if they do not exist
_.each(options.patternStructure, function(pattern) {
if (!fs.existsSync(options.src + "/" + pattern.path)) {
log.verbose("Generating the pattern directory: " + options.src + "/" + pattern.path);
fs.mkdir(options.src + "/" + pattern.path);
}
});
// Generate a placeholder index.md file
var indexFile = options.src + "/index.md";
if (!fs.existsSync(indexFile)) {
log.verbose("Generating the index file: " + coreCssPath);
fs.writeFileSync(indexFile, '# Welcome to PatternPack');
}
}
function gruntPatternPackTask() {
var done = this.async();
// Ensure that the packagePath exists.
// TODO: Figure out how to abstract this path creation. It is also used in the gruntRunner.js
if (!fs.existsSync(packagePath)) {
throw new Error("The path to the pattern pack dependency does not exists at: " + packagePath);
}
// Get the options
var options = getOptions(this);
// Ensure option values are set to acceptable values
// and files/directories are present
ensureOptions(options, "task", tasksValues);
ensureOptions(options.css, "preprocessor", cssPreprocessorValues);
ensureFilesExist(options);
// Change the options to be relative to the patternpackage package
options = transformOptions(options);
log.verbose("PatternPack options:");
log.verbose(options);
// Save the options
// Since I haven"t figured out how to pass the options from the command
// save the options to a file that can then be used by the child grunt task.
saveOptions(options);
// TODO: FIX THIS. Pass the options to the command!
// Create the options required to run the child grunt process
var gruntRunner = require("../gruntRunner.js")(grunt);
var gruntRunnerOptions = {
name: packageName,
tasks: options.task,
flags: grunt.option.flags()
};
log.verbose("PatternPack configured to run:");
log.verbose(gruntRunnerOptions);
log.verbose("PatternPack running...");
gruntRunner.run(gruntRunnerOptions, done);
}
grunt.registerMultiTask(gruntTaskName, gruntTaskDescription, gruntPatternPackTask);
};