teamdigitale/digital-citizenship-functions

View on GitHub
gulpfile.js

Summary

Maintainability
D
1 day
Test Coverage
/**
 * This file contains gulp (https://gulpjs.com/) tasks to run at build time.
 * See also npm tasks in package.json.
 */

const fs = require("fs");

const argv = require("yargs").argv;
const path = require("path");
const runSequence = require("run-sequence");

const gulp = require("gulp");
const textSimple = require("gulp-text-simple");
const rename = require("gulp-rename");
const git = require("gulp-git");
const run = require("gulp-run");
const jsonEditor = require("gulp-json-editor");

const semver = require("semver");
const mjml2html = require("mjml");
const prettier = require('gulp-prettier');

const TYPESCRIPT_SOURCE_DIR = "lib";

const TEMPLATES_SOURCE_DIR = "templates/mjml";
const TEMPLATES_SOURCE = `${TEMPLATES_SOURCE_DIR}/*.mjml`;
const TEMPLATES_OUTPUT_DIR = `${TYPESCRIPT_SOURCE_DIR}/templates/html`;

const GIT_RELEASE_BRANCH = "master";
const GIT_ORIGIN = "origin";

// resolve the root directory of this project
const rootDir = path.resolve(argv.rootDir || "./") + "/";

// path to the root `package.json`
const packageJsonPath = path.join(rootDir, "package.json");

// the content of the root `package.json`
const currentPackageJson = JSON.parse(fs.readFileSync(packageJsonPath));

// the following values are calculated for every task but are only used during
// the release process, not super efficient but it's the easiest way to share
// them across tasks
const currentVersion = semver.parse(currentPackageJson.version);
const releaseVersionValue = `${currentVersion.major}.${currentVersion.minor}.${
  currentVersion.patch
  }`;

if (
  process.env.RELEASE &&
  !["major", "minor", "patch"].includes(process.env.RELEASE)
) {
  throw new Error("RELEASE must be one between 'major', 'minor' or 'patch'");
}

const nextVersionValue = `${semver.inc(
  releaseVersionValue,
  // RELEASE can be "major", "minor" (default) or "patch"
  process.env.RELEASE || "minor"
)}-SNAPSHOT`;
const funcpackBranchPrefix = "funcpack-release";
const releaseVersionFuncpackBranchName = `${funcpackBranchPrefix}-v${releaseVersionValue}`;

/**
 * Transform a mjml template to a Typescript function outputting HTML.
 *
 * @param content the content of mjml template as string
 * @param options options object (see gulp-text-simple plugin https://www.npmjs.com/package/gulp-text-simple)
 */
const toMjml = (content, options) => {
  const name = path.basename(options.sourcePath);
  return [
    `// DO NOT EDIT THIS FILE`,
    `// this file was auto generated from '${name}' by gulp generate:templates`,
    `// tslint:disable-next-line:parameters-max-number`,
    `export default function(`,
    `  title: string,`,
    `  headlineText: string,`,
    `  senderOrganizationName: string,`,
    `  senderServiceName: string,`,
    `  organizationFiscalCode: string,`,
    `  titleText: string,`,
    `  contentHtml: string,`,
    `  footerHtml: string`,
    `): string {`,
    `  return \``,
    `${mjml2html(content, { minify: true }).html}\`;`,
    `}`,
    ""
  ].join("\n");
};

/**
 * Generate Typescript template files from mjml (https://mjml.io/).
 */
gulp.task("generate:templates", () => {
  return gulp
    .src(TEMPLATES_SOURCE)
    .pipe(textSimple(toMjml)())
    .pipe(prettier())
    .pipe(rename(filepath => (filepath.extname = ".ts")))
    .pipe(gulp.dest(TEMPLATES_OUTPUT_DIR));
});

/**
 * Run the build task
 */
gulp.task("yarn:build", () => {
  return gulp.src(TYPESCRIPT_SOURCE_DIR).pipe(run(`yarn build`));
});

/**
 * Run the lint task
 */
gulp.task("yarn:lint", () => {
  return gulp.src(TYPESCRIPT_SOURCE_DIR).pipe(run(`yarn run lint`));
});

/**
 * Run unit tests
 */
gulp.task("unit:test", () => {
  return (
    gulp
      .src(TYPESCRIPT_SOURCE_DIR)
      // for some unknow reason jest tests won't exit on win32 machine
      // when run through gulp (running the tests directly through the jest CLI just works)
      // that's the reason of the `forceExit` flag
      .pipe(run("jest --coverage --runInBand"))
  );
});

/**
 * Run the test task
 */
gulp.task("test", cb => runSequence("yarn:lint", "unit:test", cb));

/**
 * Package Azure Functions code and dependencines in a single file
 */
gulp.task("yarn:funcpack", () => {
  return gulp
    .src(TYPESCRIPT_SOURCE_DIR)
    .pipe(run("yarn run funcpack pack -e webpack.config.js ./"));
});

/**
 * Checks out the release branch
 */
gulp.task("git:checkout:release", cb => {
  return git.checkout(GIT_RELEASE_BRANCH, {}, cb);
});

/**
 * Fails if repository has untracked files
 */
gulp.task("git:check:untracked", cb => {
  return git.exec(
    {
      args: "ls-files --other --exclude-standard"
    },
    (err, stdout) => {
      if (err) {
        throw err;
      }
      if (stdout.trim().length > 0) {
        return cb("Repository must not have untracked files");
      }
      cb();
    }
  );
});

/**
 * Fails if repository has modified files
 */
gulp.task("git:check:modified", cb => {
  return git.exec(
    {
      args: "ls-files --modified --exclude-standard"
    },
    (err, stdout) => {
      if (err) {
        throw err;
      }
      if (stdout.trim().length > 0) {
        return cb("Repository must not have modified files");
      }
      cb();
    }
  );
});

/**
 * Fails if repository is not clean
 */
gulp.task("git:check:clean", ["git:check:untracked", "git:check:modified"]);

/**
 * Fails if current branch is not GIT_RELEASE_BRANCH
 */
gulp.task("git:check:branch", cb => {
  return git.exec(
    {
      args: "symbolic-ref HEAD"
    },
    (err, stdout) => {
      if (err) {
        throw err;
      }
      if (stdout.trim() !== `refs/heads/${GIT_RELEASE_BRANCH}`) {
        return cb(`You must be on the ${GIT_RELEASE_BRANCH} branch`);
      }
      cb();
    }
  );
});

/**
 * Push master to origin
 */
gulp.task("git:push:origin", cb => {
  return git.push(GIT_ORIGIN, GIT_RELEASE_BRANCH, {}, cb);
});

/**
 * Push the tags to origin
 */
gulp.task("git:push:tags", cb => {
  return git.push(
    GIT_ORIGIN,
    GIT_RELEASE_BRANCH,
    // push only annotated tags
    { args: "--follow-tags" },
    cb
  );
});

/**
 * Bump the version to the release version
 */
gulp.task("release:bump:release", () => {
  return gulp
    .src(packageJsonPath)
    .pipe(
      jsonEditor({
        version: releaseVersionValue
      })
    )
    .pipe(gulp.dest(rootDir));
});

/**
 * Commit package.json with updated release version
 */
gulp.task("release:git:commit:release", () => {
  return gulp
    .src(packageJsonPath)
    .pipe(git.add())
    .pipe(git.commit(`Bumped release ${releaseVersionValue}`));
});

/**
 * Tag last commit with the release version
 */
gulp.task("release:git:tag:release", cb => {
  const tag = `v${releaseVersionValue}`;
  return git.tag(tag, `Created tag for version ${tag}`, cb);
});

/**
 * Creates a new branch for storing funcpack assets
 */
gulp.task("release:git:checkout:funcpack", cb => {
  return git.checkout(releaseVersionFuncpackBranchName, { args: "-b" }, cb);
});

/**
 * Adds funcpack assets
 */
gulp.task("release:git:commit:funcpack", cb => {
  return gulp
    .src(["*/function.json", ".funcpack/index.js"])
    .pipe(git.add({ args: "-f" })) // force because .gitignore contains *.js
    .pipe(
      git.commit(`Adds funcpack assets for release ${releaseVersionValue}`)
    );
});

/**
 * Pushes funcpack branch to origin
 */
gulp.task("release:git:push:funcpack", cb => {
  return git.push(
    GIT_ORIGIN,
    releaseVersionFuncpackBranchName,
    { args: "-u" },
    cb
  );
});

/**
 * Updates the remote "latest" funcpack branch to the current funcpack branch
 */
gulp.task("release:git:push:funcpack:latest", cb => {
  return git.push(
    GIT_ORIGIN,
    `${releaseVersionFuncpackBranchName}:${funcpackBranchPrefix}-latest`,
    { args: "-f" },
    cb
  );
});

/**
 * Bump the version to the snapshot version
 */
gulp.task("release:bump:next", () => {
  return gulp
    .src(packageJsonPath)
    .pipe(
      jsonEditor({
        version: nextVersionValue
      })
    )
    .pipe(gulp.dest(rootDir));
});

/**
 * Commit package.json with updated snapshot version
 */
gulp.task("release:git:commit:next", () => {
  return gulp
    .src(packageJsonPath)
    .pipe(git.add())
    .pipe(git.commit(`Bumped release ${nextVersionValue}`));
});

gulp.task("release", function (cb) {
  runSequence(
    // check that the release is running on the GIT_RELEASE_BRANCH branch
    "git:check:branch",
    // check that the working directory is a git repository and
    // the repository has no outstanding changes.
    "git:check:clean",
    // generate models, templates and compile typescript
    "yarn:build",
    // run tests
    "test",
    // bumps the version to the next release version:
    // current version without the qualifier (eg. 1.2-SNAPSHOT -> 1.2)
    "release:bump:release",
    // commit the changes in package.json
    "release:git:commit:release",
    // tag the previous commit with v$version (eg. v1.2, v1.2.3).
    "release:git:tag:release",
    // checkout a new branch funcpack-release-vx.x.x
    "release:git:checkout:funcpack",
    // run funcpack
    "yarn:funcpack",
    // commits and pushes funcpack branch
    "release:git:commit:funcpack",
    "release:git:push:funcpack",
    "release:git:push:funcpack:latest",
    // check out the release branch (master)
    "git:checkout:release",
    // bumps the version to the next snapshot version:
    // increase the minor version segment of the current version and set the
    // qualifier to '-SNAPSHOT' (eg. 1.2.1-SNAPSHOT -> 1.3.0-SNAPSHOT)
    "release:bump:next",
    // commit the changes in package.json
    "release:git:commit:next",
    // push changes to origin
    "git:push:origin",
    "git:push:tags",
    err => {
      if (err) {
        console.log(err.message);
      } else {
        console.log("RELEASE FINISHED SUCCESSFULLY");
      }
      cb(err);
    }
  );
});

gulp.task("default", ["generate:templates"]);