<file_sep>const path = require('path');

const slash = require('slash');

/**
 * @typedef {Object<string, string|number|Array|Object>} BannerData
 */

const stringify = JSON.stringify;

class RuntimeGenerator {
  static loadBanner(banner, contextPath) {
    const modulePath = slash(path.resolve(contextPath, banner.entry));
    return `function() {
      return new Promise(function(resolve) {
        require.ensure([], function(require) {
          resolve(require("${modulePath}"));
        });
      });
    }`;
  }

  /**
   * @param {Array<BannerData>} banners
   * @param {string} contextPath
   * @return {string}
   */
  static banners(banners, contextPath) {
    const runtime = banners
      .map(banner => RuntimeGenerator.banner(banner, contextPath))
      .join(',');
    return `[${runtime}]`;
  }

  /**
   * @param {BannerData} banner
   * @param {string} [contextPath]
   * @return {string}
   */
  static banner(banner, contextPath) {
    const props = Object.keys(banner).reduce((acc, prop) => {
      if (prop !== 'entry') {
        acc[prop] = stringify(banner[prop]);
      }
      return acc;
    }, {});

    props.load = RuntimeGenerator.loadBanner(banner, contextPath);

    const runtime = RuntimeGenerator.props(props);
    return `{${runtime}}`;
  }

  /**
   * @param {Object<string, string>} props
   * @return {string}
   */
  static props(props) {
    return Object.keys(props)
      .map(name => RuntimeGenerator.prop(name, props[name]))
      .join(',');
  }

  /**
   * @param {string} name
   * @param {string|number|Object|Array} value
   * @return {string}
   */
  static prop(name, value) {
    const val = typeof value !== 'string' ? stringify(value) : value;
    return `"${name}":${val}`;
  }
}

module.exports = RuntimeGenerator;
<file_sep># Change Log

All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.

<a name="0.3.0"></a>
# [0.3.0](https://github.com/kisenka/banner-rotator-webpack-plugin/compare/v0.2.1...v0.3.0) (2018-09-17)


### Features

* webpack 4 interop ([c10090f](https://github.com/kisenka/banner-rotator-webpack-plugin/commit/c10090f))



<a name="0.2.1"></a>
## [0.2.1](https://github.com/kisenka/banner-rotator-webpack-plugin/compare/v0.2.0...v0.2.1) (2018-08-06)


### Bug Fixes

* fix date parsing ([4016eee](https://github.com/kisenka/banner-rotator-webpack-plugin/commit/4016eee))



<a name="0.2.0"></a>
# [0.2.0](https://github.com/kisenka/banner-rotator-webpack-plugin/compare/v0.1.4...v0.2.0) (2018-07-27)


### Features

* add glob matcher to countries ([16077f7](https://github.com/kisenka/banner-rotator-webpack-plugin/commit/16077f7))



<a name="0.1.4"></a>
## [0.1.4](https://github.com/kisenka/banner-rotator-webpack-plugin/compare/v0.1.3...v0.1.4) (2017-12-07)



<a name="0.1.3"></a>
## [0.1.3](https://github.com/kisenka/banner-rotator-webpack-plugin/compare/v0.1.1...v0.1.3) (2017-12-01)


### Bug Fixes

* build script ([406b00c](https://github.com/kisenka/banner-rotator-webpack-plugin/commit/406b00c))
* **loader:** add banners json file to loader dependencies ([9cadb68](https://github.com/kisenka/banner-rotator-webpack-plugin/commit/9cadb68))



<a name="0.1.2"></a>
## [0.1.2](https://github.com/kisenka/banner-rotator-webpack-plugin/compare/v0.1.1...v0.1.2) (2017-12-01)


### Bug Fixes

* build script ([406b00c](https://github.com/kisenka/banner-rotator-webpack-plugin/commit/406b00c))
* **loader:** add banners json file to loader dependencies ([9cadb68](https://github.com/kisenka/banner-rotator-webpack-plugin/commit/9cadb68))



<a name="0.1.1"></a>
## [0.1.1](https://github.com/kisenka/banner-rotator-webpack-plugin/compare/v0.1.0...v0.1.1) (2017-11-27)


### Bug Fixes

* browser runtime script ([cd59302](https://github.com/kisenka/banner-rotator-webpack-plugin/commit/cd59302))



<a name="0.1.0"></a>
# 0.1.0 (2017-11-27)


### Bug Fixes

* **loader:** pass bannerId prop to processBanners ([a021170](https://github.com/kisenka/banner-rotator-webpack-plugin/commit/a021170))
* **location-matcher:** fix multiple matches ([d05a45f](https://github.com/kisenka/banner-rotator-webpack-plugin/commit/d05a45f))
* **plugin:** banner id generator ([aa20fa3](https://github.com/kisenka/banner-rotator-webpack-plugin/commit/aa20fa3))
* **plugin:** fix wrong import ([9931e8c](https://github.com/kisenka/banner-rotator-webpack-plugin/commit/9931e8c))
* **runtime:** change merging utils ([c071061](https://github.com/kisenka/banner-rotator-webpack-plugin/commit/c071061))
* **runtime:** ewoijgw ([9228b20](https://github.com/kisenka/banner-rotator-webpack-plugin/commit/9228b20))
* **runtime:** kenjrkfbhdjls ([a45c44f](https://github.com/kisenka/banner-rotator-webpack-plugin/commit/a45c44f))
* **runtime:** localStorage values parsing ([9bf1902](https://github.com/kisenka/banner-rotator-webpack-plugin/commit/9bf1902))
* **runtime:** process banners properly ([00e7b1a](https://github.com/kisenka/banner-rotator-webpack-plugin/commit/00e7b1a))
* **runtime:** return processed banners from normalizeBanners ([aaa9a0e](https://github.com/kisenka/banner-rotator-webpack-plugin/commit/aaa9a0e))
* **runtime:** wrong reference ([41259b8](https://github.com/kisenka/banner-rotator-webpack-plugin/commit/41259b8))
* **runtime-generator:** better stringifying ([73f4cd7](https://github.com/kisenka/banner-rotator-webpack-plugin/commit/73f4cd7))
* path resolution on Windows ([ffc43b8](https://github.com/kisenka/banner-rotator-webpack-plugin/commit/ffc43b8))
* **runtime-glob-matcher:** process end of the string properly ([628fa9e](https://github.com/kisenka/banner-rotator-webpack-plugin/commit/628fa9e))
* **runtime/glob-matcher:** process arguments properly ([0c5e61e](https://github.com/kisenka/banner-rotator-webpack-plugin/commit/0c5e61e))
* **tests:** plugin tesst ([1f8712d](https://github.com/kisenka/banner-rotator-webpack-plugin/commit/1f8712d))


### Features

* **plugin:** add `chunkId` option which allows to override banner chunk id ([aea072f](https://github.com/kisenka/banner-rotator-webpack-plugin/commit/aea072f))
* **plugin:** pass banners as external JSON file with ([3e5c80e](https://github.com/kisenka/banner-rotator-webpack-plugin/commit/3e5c80e))
<file_sep>import merge from 'merge-options';

import ClosedStorage from './closed-banners-storage';
import globMatcher from './glob-matcher';
import * as defaultConfig from './config';

/**
 * @typedef {Object} BannerRotatorFilterCriteria
 * @property {Date} date
 * @property {string} location
 * @property {string} countryCode
 */

export default class BannerRotator {
  /**
   * @return {Date}
   */
  static getUTCDate() {
    const now = new Date();
    return new Date(
      now.getUTCFullYear(),
      now.getUTCMonth(),
      now.getUTCDate(),
      now.getUTCHours(),
      now.getUTCMinutes(),
      now.getUTCSeconds(),
      now.getUTCMilliseconds()
    );
  }

  /**
   * @param {Date} [rangeStart]
   * @param {Date} [rangeEnd]
   * @param {Date} date
   * @return {boolean}
   */
  static isRangeContainsDate(rangeStart, rangeEnd, date) {
    const time = date.getTime();
    const startTime = rangeStart && rangeStart.getTime();
    const endTime = rangeEnd && rangeEnd.getTime();
    const greaterThanStart = startTime ? time >= startTime : true;
    const lessThanEnd = endTime ? time <= endTime : true;
    return greaterThanStart && lessThanEnd;
  }

  constructor(config = {}) {
    const cfg = merge(defaultConfig, config);

    this.config = cfg;
    this.closedBannersStorage = cfg.closedBannersStorage || new ClosedStorage();
    this.banners = cfg.banners || __BANNER_ROTATOR_BANNERS_CONFIG__; // eslint-disable-line no-undef

    this.banners.forEach(b => {
      if (typeof b.startDate === 'string') {
        b.startDate = new Date(b.startDate);
      }

      if (typeof b.endDate === 'string') {
        b.endDate = new Date(b.endDate);
      }
    });

    this._handleBannerClose = e => this.closeBanner(e.detail);
    window.addEventListener(cfg.closeEventName, this._handleBannerClose);
  }

  /**
   * @param {BannerRotatorFilterCriteria} [filterCriteria]
   * @return {Promise<Banner[]>}
   */
  run(filterCriteria) {
    const banners = this.filterBanners(filterCriteria);
    const promises = banners.map(banner => banner.load()
      .then(module => (banner.module = module))
      .then(() => banner)
    );
    return Promise.all(promises);
  }

  /**
   * @param {BannerRotatorFilterCriteria} [criteria]
   * @return {Array<Banner>}
   */
  filterBanners(criteria = {}) {
    const {
      date = BannerRotator.getUTCDate(),
      location = window.location.pathname,
      countryCode
    } = criteria;

    return this.banners.filter(banner => {
      const { id, disabled, startDate, endDate, locations, countries } = banner;
      const isClosed = id ? this.isBannerClosed(id) : false;
      const isDisabled = typeof disabled === 'boolean' ? disabled : false;
      const matchDate = BannerRotator.isRangeContainsDate(startDate, endDate, date);
      const matchLocation = locations && location ? globMatcher(locations, location) : true;
      const matchCountry = countries && countryCode ? globMatcher(countries, countryCode) : true;

      return !isClosed && !isDisabled && matchDate && matchLocation && matchCountry;
    });
  }

  /**
   * @param {string} id
   * @return {boolean}
   */
  isBannerClosed(id) {
    return this.closedBannersStorage.has(id);
  }

  /**
   * @param {string} id
   */
  closeBanner(id) {
    if (!this.isBannerClosed(id)) {
      this.closedBannersStorage.add(id);
    }
  }

  reset() {
    this.closedBannersStorage.clear();
  }

  /**
   * Detach close banner event and destroy closed banners storage
   */
  destroy() {
    window.removeEventListener(this.config.closeEventName, this._handleBannerClose);
    this.closedBannersStorage.destroy();
  }
}
<file_sep>import { closeEventName } from './config';

/**
 * @param {string} bannerId
 * @param {string} [eventName]
 */
export default function dispatchCloseEvent(bannerId, eventName = closeEventName) {
  let event;

  if (window.CustomEvent) {
    event = new CustomEvent(eventName, { detail: bannerId });
  } else {
    event = document.createEvent('CustomEvent');
    event.initCustomEvent(eventName, false, false, bannerId);
  }

  window.dispatchEvent(event);
}
<file_sep>const path = require('path');

const glob = require('glob');
const { rollup } = require('rollup');
const resolvePlugin = require('rollup-plugin-node-resolve');
const commonjsPlugin = require('rollup-plugin-commonjs');
const bublePlugin = require('rollup-plugin-buble');

const root = path.resolve(__dirname, '..');
const src = path.resolve(root, 'src/browser');
const dest = path.resolve(root, 'browser');

const promises = glob.sync(path.join(src, '*.js')).map(entryPath => {
  const entryName = path.basename(entryPath, path.extname(entryPath));
  const outputPath = path.resolve(dest, `${entryName}.js`);

  return rollup({
    input: entryPath,
    plugins: [
      resolvePlugin(),
      commonjsPlugin(),
      bublePlugin()
    ]
  })
    .then(bundle => {
      bundle.write({
        file: outputPath,
        format: 'cjs',
        sourcemap: true
      });
    })
    .catch(e => console.log(`Runtime build ERROR: ${e.message}`));
});

Promise.all(promises);
<file_sep>const Generator = require('../../src/lib/runtime-generator');

describe('runtime-generator', () => {
  describe('#prop()', () => {
    it('should work', () => {
      Generator.prop('foo-bar', 'olala').should.be.equal('"foo-bar":olala');
      Generator.prop('foo-bar', '"string"').should.be.equal('"foo-bar":"string"');
      Generator.prop('foo-bar', '\'string\'').should.be.equal('"foo-bar":\'string\'');
      Generator.prop('foo-bar', 1).should.be.equal('"foo-bar":1');
      Generator.prop('foo-bar', 'function(){}').should.be.equal('"foo-bar":function(){}');
      Generator.prop('foo-bar', [1, 2, 3]).should.be.equal('"foo-bar":[1,2,3]');
      Generator.prop('foo-bar', { a: 1 }).should.be.equal('"foo-bar":{"a":1}');
    });
  });

  describe('#props()', () => {
    it('should work', () => {
      const input = { a: 1, b: '"2"' };
      const expected = `"a":1,"b":"2"`; // eslint-disable-line quotes
      Generator.props(input).should.be.equal(expected);
    });
  });
});
<file_sep>const webpackMerge = require('webpack-merge');

const {
  LOADER_PATH,
  plugin: defaultConfig
} = require('../../src/lib/config');

const webpackConfig = require('../../webpack.config');
const Plugin = require('../../src/lib/plugin');
const utils = require('../utils');

const { createCompiler } = utils;

const compile = config => {
  const merged = webpackMerge(webpackConfig, {
    entry: './entry',
    mode: 'development',
    devtool: false
  }, config);
  return utils.compile(merged);
};

function containChunkIdRuntime(source, chunkId, asNumber) {
  const chunkExpr = asNumber ? chunkId : `"${chunkId}"`;
  return source.should.contain(`__webpack_require__.e(/*! require.ensure */ ${chunkExpr}`);
}

describe('plugin', () => {
  describe('prepare()', () => {
    it('should properly add custom loader rules', () => {
      const compiler = createCompiler({ entry: './test-banner' })._compiler;
      const plugin = new Plugin();
      plugin.addLoaderForRuntime(compiler);
      const [rule] = compiler.options.module.rules;
      rule.loader.should.be.equal(LOADER_PATH);
      rule.test.should.be.equal(defaultConfig.runtimeModule);
    });
  });

  describe('apply()', () => {
    const defaultBanners = [{ entry: './banners/test-banner' }];
    const defaultExpectedChunkId = 'banners/0';
    const defaultExpectedChunkFilename = `${defaultExpectedChunkId}.js`;

    it('should replace banners placeholder and create lazy chunk', async () => {
      const expectedChunkId = defaultExpectedChunkId;
      const expectedChunkFilename = defaultExpectedChunkFilename;

      const { assets } = await compile({
        plugins: [
          new Plugin({ banners: defaultBanners })
        ]
      });

      const source = assets['main.js'].source();

      containChunkIdRuntime(source, expectedChunkId);
      source.should.and.not.contain(defaultConfig.runtimePlaceholder);
      assets.should.contain.property(expectedChunkFilename);
    });

    it('should allow to override banner chunk id', async () => {
      const customChunkId = 'qwe/[id]';
      const expectedChunkId = 'qwe/0';
      const expectedChunkFilename = `${expectedChunkId}.js`;

      const { assets } = await compile({
        plugins: [
          new Plugin({ banners: defaultBanners, chunkId: customChunkId })
        ]
      });

      const source = assets['main.js'].source();

      containChunkIdRuntime(source, expectedChunkId);
      assets.should.contain.property(expectedChunkFilename);
    });

    it('should allow to pass falsy value as chunkId and dont\'t touch chunk id then', async () => {
      const chunkId = null;
      const expectedChunkId = '0';
      const expectedChunkFilename = `${expectedChunkId}.js`;

      const { assets } = await compile({
        plugins: [
          new Plugin({ banners: defaultBanners, chunkId })
        ]
      });

      const source = assets['main.js'].source();

      containChunkIdRuntime(source, expectedChunkId, true);
      assets.should.contain.property(expectedChunkFilename);
    });

    it('should allow to pass path to banners json file', async () => {
      const { assets } = await compile({
        plugins: [
          new Plugin({
            banners: './banners.json'
          })
        ]
      });

      const source = assets['main.js'].source();

      containChunkIdRuntime(source, defaultExpectedChunkId);
      assets.should.contain.property(defaultExpectedChunkFilename);
    });

    it('should allow to postprocess banners', async () => {
      const { assets } = await compile({
        plugins: [
          new Plugin({
            banners: defaultBanners,
            process: banners => {
              banners.forEach(b => b.id = 'tralala');
              return banners;
            }
          })
        ]
      });

      assets['main.js'].source().should.contain('tralala');
    });
  });
});
<file_sep>const merge = require('merge-options');

const {
  NAMESPACE,
  LOADER_PATH,
  plugin: defaultConfig
} = require('./config');

/**
 * @typedef {Object} Banner
 * @property {string} id
 */

class BannerRotatorPlugin {
  /**
   * @param {BannerRotatorPluginConfig} [config]
   */
  constructor(config) {
    this.config = merge(defaultConfig, config);
  }

  /**
   * @param {Compiler} compiler
   * @return {Compiler}
   */
  addLoaderForRuntime(compiler) {
    const compilerOptions = compiler.options;
    const runtimeModule = this.config.runtimeModule;

    /* istanbul ignore if */
    if (!compilerOptions.module) {
      compilerOptions.module = {};
    }

    /* istanbul ignore if */
    if (!compilerOptions.module.rules) {
      compilerOptions.module.rules = [];
    }

    compilerOptions.module.rules.push({
      test: runtimeModule,
      loader: LOADER_PATH
    });

    return compiler;
  }

  getRuntimeModule(compilation) {
    return compilation.modules
      .find(m => m.loaders
        .some(({ loader }) => loader === LOADER_PATH)
      );
  }

  /**
   * Return banners chunks
   * @param {Array<Chunk>} chunks
   * @return {Array<Chunk>}
   */
  getBannersChunks(chunks, compilation) {
    const runtimeModule = this.getRuntimeModule(compilation);
    const runtimeModulePath = this.config.runtimeModule;

    if (compilation.hooks) {
      const asyncChunks = chunks
        .map(chunk => Array.from(chunk.getAllAsyncChunks().values()))
        .reduce((acc, chunkChunks) => acc.concat(chunkChunks), []);

      const bannersChunks = asyncChunks.filter(chunk => chunk.getModules()
        .some(m => m.reasons
          .some(reason => reason.module === runtimeModule)
        ));

      return bannersChunks;
    }

    return chunks.filter(chunk => chunk.origins
      .map(origin => origin.module.resource)
      .some(resource => resource === runtimeModulePath));
  }

  apply(compiler) {
    this.addLoaderForRuntime(compiler);

    if (compiler.hooks) {
      compiler.hooks.emit.tapAsync(NAMESPACE, (compilation, done) => {
        done();
      });

      compiler.hooks.thisCompilation.tap(NAMESPACE, compilation => {
        compilation.hooks.normalModuleLoader
          .tap(NAMESPACE, loaderCtx => this.hookNormalModuleLoader(loaderCtx));

        compilation.hooks.afterOptimizeChunkIds
          .tap(NAMESPACE, chunks => {
            this.hookOptimizeChunkIds(chunks, compilation);
          });
      });
    } else {
      compiler.plugin('this-compilation', compilation => {
        compilation.plugin('normal-module-loader', loaderCtx => {
          this.hookNormalModuleLoader(loaderCtx);
        });

        compilation.plugin('optimize-chunk-ids', chunks => {
          this.hookOptimizeChunkIds(chunks, compilation);
        });
      });
    }
  }

  hookNormalModuleLoader(loaderContext) {
    loaderContext[NAMESPACE] = this;
  }

  hookOptimizeChunkIds(chunks, compilation) {
    const config = this.config;

    if (!config.chunkId) {
      return;
    }

    this.getBannersChunks(chunks, compilation).forEach(chunk => {
      // Rename banner chunk id
      const id = compilation.getPath(config.chunkId, { chunk });
      chunk.id = id;
      chunk.ids = [id];
    });
  }
}

module.exports = BannerRotatorPlugin;
<file_sep>#!/usr/bin/env bash

set -e -x

git config user.email "<EMAIL>"
git config user.name "Circle CI"

# Make semantic release
$(npm bin)/standard-version --message 'chore(release): %s [skip ci]' --no-verify

# Publish to NPM
$(npm bin)/ci-publish

# Push to github only if package successfully published to NPM
git push --follow-tags origin master
<file_sep>export const closeEventName = 'banner-rotator-banner-close';
export const closedBannersStorageKey = 'banner-rotator-closed-banners';
<file_sep>import globToRegexp from 'glob-to-regexp';

function isNegative(pattern) {
  return pattern.charAt(0) === '!';
}

/**
 * Supports:
 * - glob star, eg `/path/*`
 * - negation of whole expression, eg `!/path/`
 * - any single-character match, eg `.jsx?`
 * - character ranges [a-z]
 * - brace expansion {*.html, *.js}
 * - combination of all these things in any proportion
 * @see https://github.com/fitzgen/glob-to-regexp#usage
 * @param {string} glob Expression with optional glob pattern.
 * @return {Function<boolean>} Matcher function
 */
export function createMatcher(glob) {
  const negative = isNegative(glob);
  const pattern = negative ? glob.substr(1) : glob;
  const regexp = globToRegexp(pattern, { extended: true });

  /**
   * @param {string} value
   * @return {boolean}
   */
  return function matcher(input) {
    const matched = regexp.test(input);
    return negative ? !matched : matched;
  };
}

/**
 * @param {Array<string>} patterns
 * @return {Function<boolean>}
 */
export function createMultiMatcher(patterns) {
  const normal = patterns.filter(p => !isNegative(p)).map(p => createMatcher(p));
  const negation = patterns.filter(p => isNegative(p)).map(p => createMatcher(p));

  /**
   * @param {string} input
   * @return {boolean}
   */
  return function multiMatcher(input) {
    const satisfyNormal = normal.length > 0
      ? normal.some(normalMatcher => normalMatcher(input))
      : true;

    const satisfyNegation = negation.length > 0
      ? negation.every(negationMatcher => negationMatcher(input))
      : true;

    return satisfyNormal && satisfyNegation;
  };
}

/**
 * @param {string|Array<string>} pattern
 * @param {string} input
 * @return {boolean}
 */
export default function (pattern, input) {
  const multiple = Array.isArray(pattern);
  const matcher = multiple ? createMultiMatcher(pattern) : createMatcher(pattern);
  return matcher(input);
}
<file_sep>/**
 * @see https://github.com/leonardoanalista/cz-customizable#options
 */
module.exports = {
  types: [
    {value: 'feat',     name: 'feat:     A new feature'},
    {value: 'improve',  name: 'improve:  Kind of small feature, but will not trigger a release'},
    {value: 'fix',      name: 'fix:      A bug fix'},
    {value: 'docs',     name: 'docs:     Documentation changes'},
    {value: 'style',    name: 'style:    Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)'},
    {value: 'refactor', name: 'refactor: A code change that neither fixes a bug nor adds a feature'},
    {value: 'perf',     name: 'perf:     A code change that improves performance'},
    {value: 'test',     name: 'test:     Adding missing tests or update existing'},
    {value: 'chore',    name: 'chore:    Changes to the build process or auxiliary tools and libraries such as documentation generation'},
    {value: 'ci',       name: 'ci:       Continuous intergation related changes'},
    {value: 'scripts',  name: 'scripts:  Scripts related changes'},
    {value: 'revert',   name: 'revert:   Revert to a commit'},
    {value: 'WIP',      name: 'WIP:      Work in progress'}
  ],

  /**
   * Specify the scopes for your particular project. Eg.: for some banking system: ["acccounts", "payments"].
   * For another travelling application: ["bookings", "search", "profile"].
   */
  scopes: [],

  /**
   * It needs to match the value for field type.
   * @example
   * scopeOverrides: {
   *   fix: [
   *     {name: 'merge'},
   *     {name: 'style'},
   *     {name: 'e2eTest'},
   *     {name: 'unitTest'}
   *   ]
   * }
   */
  scopeOverrides: {},
  allowCustomScopes: true,
  allowBreakingChanges: ['feat', 'fix'],
  appendBranchNameToCommitMessage: false,
  breakingPrefix: 'BREAKING CHANGE:',
  footerPrefix: 'Closes'
};
<file_sep># DEPRECATED

We understood that with modern Webpack we don't need such kind of plugin.

Client side part with improves for requirements of our product
step by step was transformed to product specific.

Also, the plugin was released on npm as alpha version
and by [stat](https://www.npmjs.com/package/webpack-banner-rotator-plugin)
we see max 8 download per week that looks like only our team usages.

By these reasons we decided to deprecate this project. Thanks 🙏

## Webpack Banner Rotator Plugin
<file_sep>/* nyc ignore */
const crypto = require('crypto');

const slugify = require('url-slug');

/**
 * @param {Array<Banner>} banners
 * @return {Array<Banner>}
 */
function processBanners(banners) {
  const filtered = banners.filter(banner => {
    const hasEntry = 'entry' in banner;
    const isDisabled = typeof banner.disabled === 'boolean' ? banner.disabled : false;
    return hasEntry && !isDisabled;
  });

  filtered.forEach(banner => {
    if (typeof banner.id === 'undefined') {
      banner.id = slugify(banner.entry);
    }
  });

  return filtered;
}

/**
 * @param {Banner} banner
 * @param {Array<string>} fields
 * @return {string}
 */
function generateId(banner, fields = ['entry', 'id', 'startDate', 'endDate']) {
  const data = fields
    .filter(field => typeof banner[field] !== 'undefined')
    .map(field => JSON.stringify(banner[field]))
    .join('_');

  const id = crypto.createHash('md5').update(data).digest('hex');
  return id;
}

module.exports = processBanners;
module.exports.generateId = generateId;
<file_sep>const path = require('path');
const fs = require('fs');

/**
 * @typedef {Object} BannerRotatorPluginConfig
 *
 * @property {Array<Banner>|string} banners Array of banners or absolute path to JSON file with banners
 * @property {string} [chunkId] Use custom chunk id for banner chunks. `[id]` token represents current chunk id.
 * NOTICE: it changes only chunk id, which will be used in `chunkFilename` webpack option (`[id].js` by default).
 * @example `banners/[id]` will produce chunk files like `banners/0.js`
 * @see https://webpack.js.org/configuration/output/#output-chunkfilename
 *
 * @property {Promise<Array<Banner>>} [process] Process banners by custom used defined function
 * @property {string} runtimeModule Path to runtime module. Plugin use it to find modules processed by plugin.
 * @property {string} runtimePlaceholder Placeholder which will be replaced with banners config data with runtime to lazy load banner module.
 */

module.exports = {
  /**
   * Unique filesystem path to identify plugin and share data between plugin and loader.
   * @type {string}
   */
  NAMESPACE: fs.realpathSync(__dirname),

  /**
   * Absolute path to loader. Plugin use it when adding loader rules for runtime rotator module.
   * @type {string}
   */
  LOADER_PATH: path.resolve(__dirname, 'loader.js'),

  /**
   * @type {BannerRotatorPluginConfig}
   */
  plugin: {
    banners: undefined,
    chunkId: 'banners/[id]',
    process: undefined,
    runtimeModule: path.resolve(__dirname, '../../browser/rotator.js'),
    runtimePlaceholder: '__BANNER_ROTATOR_BANNERS_CONFIG__'
  }
};
