@financial-times/o-ads
Advanced tools
Comparing version 14.2.0 to 14.2.1
@@ -14,4 +14,2 @@ "use strict"; | ||
var _url = require("../utils/url"); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
@@ -24,7 +22,7 @@ | ||
/** | ||
* @fileOverview | ||
* ad server modules for o-ads implementing Google publisher tags ad requests. | ||
* | ||
* @author Robin Marr, robin.marr@ft.com | ||
*/ | ||
* @fileOverview | ||
* ad server modules for o-ads implementing Google publisher tags ad requests. | ||
* | ||
* @author Robin Marr, robin.marr@ft.com | ||
*/ | ||
var DEFAULT_COLLAPSE_MODE = 'never'; | ||
@@ -39,4 +37,4 @@ var breakpoints = false; | ||
/* | ||
* Initialise Google publisher tags functionality | ||
*/ | ||
* Initialise Google publisher tags functionality | ||
*/ | ||
@@ -59,4 +57,4 @@ function init() { | ||
/* | ||
* initalise the googletag global namespace and add the google publish tags library to the page | ||
*/ | ||
* initalise the googletag global namespace and add the google publish tags library to the page | ||
*/ | ||
@@ -87,6 +85,6 @@ | ||
/* | ||
* Configure the GPT library for the current page | ||
* this method is pushed onto the googletag command queue and run | ||
* when the library is available | ||
*/ | ||
* Configure the GPT library for the current page | ||
* this method is pushed onto the googletag command queue and run | ||
* when the library is available | ||
*/ | ||
@@ -101,10 +99,4 @@ | ||
setPageCollapseEmpty(); | ||
var url = (0, _url.stripUrlParams)({ | ||
href: window.location.href, | ||
filters: { | ||
root: _url.SEARCH_PARAMS | ||
} | ||
}); | ||
googletag.enableServices(); | ||
googletag.pubads().setTargeting('url', url); | ||
googletag.pubads().setTargeting('url', window.location.href); | ||
googletag.pubads().setRequestNonPersonalizedAds(nonPersonalized); | ||
@@ -114,5 +106,5 @@ return true; | ||
/* | ||
* set the gpt rendering mode to either sync or async | ||
* default is async | ||
*/ | ||
* set the gpt rendering mode to either sync or async | ||
* default is async | ||
*/ | ||
@@ -132,7 +124,7 @@ | ||
/** | ||
* Adds page targeting to GPT ad calls | ||
* @name setPageTargeting | ||
* @memberof GPT | ||
* @lends GPT | ||
*/ | ||
* Adds page targeting to GPT ad calls | ||
* @name setPageTargeting | ||
* @memberof GPT | ||
* @lends GPT | ||
*/ | ||
@@ -157,4 +149,4 @@ | ||
/** | ||
* Removes page targeting for a specified key from GPT ad calls | ||
*/ | ||
* Removes page targeting for a specified key from GPT ad calls | ||
*/ | ||
@@ -180,7 +172,7 @@ | ||
/** | ||
* Sets behaviour of empty slots can be 'after', 'before' or 'never' | ||
* * 'after' collapse slots that return an empty ad | ||
* * 'before' collapses all slots and only displays them when an ad is found | ||
* * 'never' does not collapse any empty slot, the collapseEmptyDivs method is not called in that case | ||
*/ | ||
* Sets behaviour of empty slots can be 'after', 'before' or 'never' | ||
* * 'after' collapse slots that return an empty ad | ||
* * 'before' collapses all slots and only displays them when an ad is found | ||
* * 'never' does not collapse any empty slot, the collapseEmptyDivs method is not called in that case | ||
*/ | ||
@@ -200,5 +192,5 @@ | ||
/** | ||
* When companions are enabled we delay the rendering of ad slots until | ||
* either a master is returned or all slots are returned without a master | ||
*/ | ||
* When companions are enabled we delay the rendering of ad slots until | ||
* either a master is returned or all slots are returned without a master | ||
*/ | ||
@@ -219,4 +211,4 @@ | ||
/* | ||
* Event handler for when a slot is ready for an ad to rendered | ||
*/ | ||
* Event handler for when a slot is ready for an ad to rendered | ||
*/ | ||
@@ -244,4 +236,4 @@ | ||
/* | ||
* Render is called when a slot is not rendered when the ready event fires | ||
*/ | ||
* Render is called when a slot is not rendered when the ready event fires | ||
*/ | ||
@@ -258,4 +250,4 @@ | ||
/* | ||
* refresh is called when a slot requests the ad be flipped | ||
*/ | ||
* refresh is called when a slot requests the ad be flipped | ||
*/ | ||
@@ -285,4 +277,4 @@ | ||
/* | ||
* function passed to the gpt library that is run when an ad completes rendering | ||
*/ | ||
* function passed to the gpt library that is run when an ad completes rendering | ||
*/ | ||
@@ -338,4 +330,4 @@ | ||
/** | ||
* define a GPT slot | ||
*/ | ||
* define a GPT slot | ||
*/ | ||
defineSlot: function defineSlot() { | ||
@@ -399,4 +391,4 @@ window.googletag.cmd.push(() => { | ||
/** | ||
* Tell gpt to destroy the slot and its metadata | ||
*/ | ||
* Tell gpt to destroy the slot and its metadata | ||
*/ | ||
destroySlot: function destroySlot(gptSlot) { | ||
@@ -412,4 +404,4 @@ if (window.googletag.pubadsReady && window.googletag.pubads) { | ||
/* | ||
* Tell gpt to request an ad | ||
*/ | ||
* Tell gpt to request an ad | ||
*/ | ||
display: function display() { | ||
@@ -424,4 +416,4 @@ window.googletag.cmd.push(() => { | ||
/** | ||
* Set the DFP unit name for the slot. | ||
*/ | ||
* Set the DFP unit name for the slot. | ||
*/ | ||
setUnitName: function setUnitName() { | ||
@@ -452,4 +444,4 @@ window.googletag.cmd.push(() => { | ||
/** | ||
* Add the slot to the pub ads service and add a companion service if configured | ||
*/ | ||
* Add the slot to the pub ads service and add a companion service if configured | ||
*/ | ||
addServices: function addServices(gptSlot) { | ||
@@ -469,5 +461,5 @@ window.googletag.cmd.push(() => { | ||
/** | ||
* Sets the GPT collapse empty mode for a given slot | ||
* values can be 'after', 'before', 'never' | ||
*/ | ||
* Sets the GPT collapse empty mode for a given slot | ||
* values can be 'after', 'before', 'never' | ||
*/ | ||
setCollapseEmpty: function setCollapseEmpty() { | ||
@@ -518,5 +510,5 @@ window.googletag.cmd.push(() => { | ||
/** | ||
* Sets page url to be sent to google | ||
* prevents later url changes via javascript from breaking the ads | ||
*/ | ||
* Sets page url to be sent to google | ||
* prevents later url changes via javascript from breaking the ads | ||
*/ | ||
setURL: function setURL(gptSlot) { | ||
@@ -532,4 +524,4 @@ window.googletag.cmd.push(() => { | ||
/** | ||
* Adds key values from a given object to the slot targeting | ||
*/ | ||
* Adds key values from a given object to the slot targeting | ||
*/ | ||
setTargeting: function setTargeting(gptSlot) { | ||
@@ -556,6 +548,6 @@ window.googletag.cmd.push(() => { | ||
/** | ||
* The correlator is a random number added to ad calls. | ||
* It is used by the ad server to determine which impressions where served to the same page | ||
* Updating is used to tell the ad server to treat subsequent ad calls as being on a new page | ||
*/ | ||
* The correlator is a random number added to ad calls. | ||
* It is used by the ad server to determine which impressions where served to the same page | ||
* Updating is used to tell the ad server to treat subsequent ad calls as being on a new page | ||
*/ | ||
@@ -610,3 +602,2 @@ function updateCorrelator() { | ||
init, | ||
setup, | ||
updateCorrelator, | ||
@@ -613,0 +604,0 @@ updatePageTargeting, |
"use strict"; | ||
// generated by genversion | ||
module.exports = '14.2.0'; | ||
module.exports = '14.1.0'; |
@@ -27,3 +27,3 @@ // For a detailed explanation regarding each configuration property, visit: | ||
// The directory where Jest should output its coverage files | ||
coverageDirectory: 'coverage', | ||
coverageDirectory: "coverage", | ||
@@ -65,3 +65,6 @@ // An array of regexp pattern strings used to skip coverage collection | ||
// An array of directory names to be searched recursively up from the requiring module's location | ||
moduleDirectories: ['node_modules', 'bower_components'], | ||
moduleDirectories: [ | ||
"node_modules", | ||
"bower_components" | ||
], | ||
@@ -132,3 +135,3 @@ // An array of file extensions your modules use | ||
// The test environment that will be used for testing | ||
testEnvironment: 'jest-environment-jsdom-global', | ||
// testEnvironment: "node", | ||
@@ -148,6 +151,10 @@ // Options that will be passed to the testEnvironment | ||
// The glob patterns Jest uses to detect test files | ||
testMatch: ['**/test/jest/**/*.[jt]s?(x)'], | ||
testMatch: [ | ||
"**/test/jest/**/*.[jt]s?(x)", | ||
], | ||
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped | ||
testPathIgnorePatterns: ['/node_modules/', '/jest/testUtils'], | ||
testPathIgnorePatterns: [ | ||
"/node_modules/", "/jest/testUtils" | ||
], | ||
@@ -171,5 +178,5 @@ // The regexp pattern or array of patterns that Jest uses to detect test files | ||
// transform: null, | ||
transform: { | ||
'^.+\\.jsx?$': 'babel-jest' | ||
} | ||
"transform": { | ||
"^.+\\.jsx?$": "babel-jest" | ||
}, | ||
@@ -176,0 +183,0 @@ // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation |
@@ -28,3 +28,3 @@ { | ||
"name": "@financial-times/o-ads", | ||
"version": "14.2.0", | ||
"version": "14.2.1", | ||
"dependencies": { | ||
@@ -46,4 +46,3 @@ "ftdomdelegate": "^4.0.0", | ||
"test": "npm run test-unit", | ||
"test-coverage": "karma start karma.conf.js --single-run", | ||
"test-unit": "jest", | ||
"test-unit": "karma start karma.conf.js --single-run && jest", | ||
"test-cy:run": "cypress run -r spec --record false", | ||
@@ -75,5 +74,2 @@ "test-cy:open": "cypress open", | ||
"@testing-library/jest-dom": "^4.1.0", | ||
"@types/doubleclick-gpt": "2019041801.0.2", | ||
"@types/jest": "25.1.3", | ||
"@types/jsdom": "16.1.0", | ||
"babel-jest": "^24.8.0", | ||
@@ -96,4 +92,2 @@ "babel-plugin-rewire": "^1.2.0", | ||
"jest": "^24.8.0", | ||
"jest-environment-jsdom": "25.1.0", | ||
"jest-environment-jsdom-global": "1.2.1", | ||
"jest-puppeteer": "^4.3.0", | ||
@@ -100,0 +94,0 @@ "karma": "^4.1.0", |
@@ -118,2 +118,2 @@ # oAds [![CircleCI Status](https://circleci.com/gh/Financial-Times/o-ads.svg?style=shield&circle-token=36a37c6ca27a08408c2575c7834f5f6f5c5c9d21)](https://circleci.com/gh/Financial-Times/o-ads/tree/master) | ||
- Breaking change: If you use the destroy method on a slots instance, this will now properly destroy the given slots rather than just clear them | ||
- New feature: util event 'off' - remove an event listener | ||
- New feature: util event 'off' - remove an event listener |
@@ -42,7 +42,5 @@ module.exports = { | ||
'bcbe1a6d-fa90-4db5-b4dc-424c69802310', | ||
'6790def915d959f61f56fb7005cba2cc', | ||
'37b1e62e-93ff-4991-aa83-c1ec974d4802', | ||
'0667615f-499e-4fa6-8130-f3430450228d' | ||
'6790def915d959f61f56fb7005cba2cc' | ||
] | ||
} | ||
}; |
@@ -5,12 +5,10 @@ /*globals googletag: true */ | ||
/** | ||
* @fileOverview | ||
* ad server modules for o-ads implementing Google publisher tags ad requests. | ||
* | ||
* @author Robin Marr, robin.marr@ft.com | ||
*/ | ||
* @fileOverview | ||
* ad server modules for o-ads implementing Google publisher tags ad requests. | ||
* | ||
* @author Robin Marr, robin.marr@ft.com | ||
*/ | ||
import config from '../config'; | ||
import utils from '../utils'; | ||
import targeting from '../targeting'; | ||
import { stripUrlParams, SEARCH_PARAMS } from '../utils/url'; | ||
const DEFAULT_COLLAPSE_MODE = 'never'; | ||
@@ -25,4 +23,4 @@ let breakpoints = false; | ||
/* | ||
* Initialise Google publisher tags functionality | ||
*/ | ||
* Initialise Google publisher tags functionality | ||
*/ | ||
function init() { | ||
@@ -40,4 +38,4 @@ const gptConfig = config('gpt') || {}; | ||
/* | ||
* initalise the googletag global namespace and add the google publish tags library to the page | ||
*/ | ||
* initalise the googletag global namespace and add the google publish tags library to the page | ||
*/ | ||
function loadGPT() { | ||
@@ -54,11 +52,5 @@ if (!window.googletag) { | ||
utils.attach( | ||
'//securepubads.g.doubleclick.net/tag/js/gpt.js', | ||
true, | ||
() => { | ||
utils.broadcast('serverScriptLoaded'); | ||
}, | ||
err => { | ||
utils.broadcast('adServerLoadError', err); | ||
} | ||
utils.attach('//securepubads.g.doubleclick.net/tag/js/gpt.js', true, | ||
() => { utils.broadcast('serverScriptLoaded'); }, | ||
(err) => { utils.broadcast('adServerLoadError', err); } | ||
); | ||
@@ -74,6 +66,6 @@ } | ||
/* | ||
* Configure the GPT library for the current page | ||
* this method is pushed onto the googletag command queue and run | ||
* when the library is available | ||
*/ | ||
* Configure the GPT library for the current page | ||
* this method is pushed onto the googletag command queue and run | ||
* when the library is available | ||
*/ | ||
function setup(gptConfig) { | ||
@@ -86,12 +78,5 @@ const nonPersonalized = config('nonPersonalized') ? 1 : 0; | ||
setPageCollapseEmpty(); | ||
const url = stripUrlParams({ | ||
href: window.location.href, | ||
filters: { root: SEARCH_PARAMS } | ||
}); | ||
googletag.enableServices(); | ||
googletag.pubads().setTargeting('url', url); | ||
googletag.pubads().setTargeting('url', window.location.href); | ||
googletag.pubads().setRequestNonPersonalizedAds(nonPersonalized); | ||
return true; | ||
@@ -101,5 +86,5 @@ } | ||
/* | ||
* set the gpt rendering mode to either sync or async | ||
* default is async | ||
*/ | ||
* set the gpt rendering mode to either sync or async | ||
* default is async | ||
*/ | ||
@@ -118,7 +103,7 @@ function setRenderingMode(gptConfig) { | ||
/** | ||
* Adds page targeting to GPT ad calls | ||
* @name setPageTargeting | ||
* @memberof GPT | ||
* @lends GPT | ||
*/ | ||
* Adds page targeting to GPT ad calls | ||
* @name setPageTargeting | ||
* @memberof GPT | ||
* @lends GPT | ||
*/ | ||
function setPageTargeting(targetingData) { | ||
@@ -142,4 +127,4 @@ if (utils.isPlainObject(targetingData)) { | ||
/** | ||
* Removes page targeting for a specified key from GPT ad calls | ||
*/ | ||
* Removes page targeting for a specified key from GPT ad calls | ||
*/ | ||
function clearPageTargetingForKey(key) { | ||
@@ -160,7 +145,7 @@ if (!window.googletag) { | ||
/** | ||
* Sets behaviour of empty slots can be 'after', 'before' or 'never' | ||
* * 'after' collapse slots that return an empty ad | ||
* * 'before' collapses all slots and only displays them when an ad is found | ||
* * 'never' does not collapse any empty slot, the collapseEmptyDivs method is not called in that case | ||
*/ | ||
* Sets behaviour of empty slots can be 'after', 'before' or 'never' | ||
* * 'after' collapse slots that return an empty ad | ||
* * 'before' collapses all slots and only displays them when an ad is found | ||
* * 'never' does not collapse any empty slot, the collapseEmptyDivs method is not called in that case | ||
*/ | ||
function setPageCollapseEmpty() { | ||
@@ -179,5 +164,5 @@ const mode = config('collapseEmpty'); | ||
/** | ||
* When companions are enabled we delay the rendering of ad slots until | ||
* either a master is returned or all slots are returned without a master | ||
*/ | ||
* When companions are enabled we delay the rendering of ad slots until | ||
* either a master is returned or all slots are returned without a master | ||
*/ | ||
function enableCompanions(gptConfig) { | ||
@@ -197,4 +182,4 @@ if (gptConfig.companions) { | ||
/* | ||
* Event handler for when a slot is ready for an ad to rendered | ||
*/ | ||
* Event handler for when a slot is ready for an ad to rendered | ||
*/ | ||
function onReady(slotMethods, event) { | ||
@@ -211,4 +196,3 @@ const slot = event.detail.slot; | ||
googletag.cmd.push(() => { | ||
slot | ||
.defineSlot() | ||
slot.defineSlot() | ||
.addServices() | ||
@@ -226,4 +210,4 @@ .setCollapseEmpty() | ||
/* | ||
* Render is called when a slot is not rendered when the ready event fires | ||
*/ | ||
* Render is called when a slot is not rendered when the ready event fires | ||
*/ | ||
function onRender(event) { | ||
@@ -238,4 +222,4 @@ const slot = event.detail.slot; | ||
/* | ||
* refresh is called when a slot requests the ad be flipped | ||
*/ | ||
* refresh is called when a slot requests the ad be flipped | ||
*/ | ||
function onRefresh(event) { | ||
@@ -262,4 +246,4 @@ window.googletag.cmd.push(() => { | ||
/* | ||
* function passed to the gpt library that is run when an ad completes rendering | ||
*/ | ||
* function passed to the gpt library that is run when an ad completes rendering | ||
*/ | ||
function onRenderEnded(event) { | ||
@@ -312,4 +296,4 @@ const data = { | ||
/** | ||
* define a GPT slot | ||
*/ | ||
* define a GPT slot | ||
*/ | ||
defineSlot: function() { | ||
@@ -327,3 +311,4 @@ window.googletag.cmd.push(() => { | ||
} | ||
} else { | ||
} | ||
else { | ||
this.gpt.slot = googletag.defineOutOfPageSlot(this.gpt.unitName, this.gpt.id); | ||
@@ -344,21 +329,17 @@ } | ||
window.googletag.cmd.push(() => { | ||
utils.on( | ||
'breakpoint', | ||
event => { | ||
const slot = event.detail.slot; | ||
const screensize = event.detail.screensize; | ||
utils.on('breakpoint', (event) => { | ||
const slot = event.detail.slot; | ||
const screensize = event.detail.screensize; | ||
updatePageTargeting({ res: screensize }); | ||
updatePageTargeting({ res: screensize }); | ||
if (slot.hasValidSize(screensize)) { | ||
/* istanbul ignore else */ | ||
if (slot.gpt.iframe) { | ||
slot.fire('refresh'); | ||
} else if (!this.defer) { | ||
slot.display(); | ||
} | ||
if (slot.hasValidSize(screensize)) { | ||
/* istanbul ignore else */ | ||
if (slot.gpt.iframe) { | ||
slot.fire('refresh'); | ||
} else if (!this.defer) { | ||
slot.display(); | ||
} | ||
}, | ||
this.container | ||
); | ||
} | ||
}, this.container); | ||
@@ -378,4 +359,4 @@ const mapping = googletag.sizeMapping(); | ||
/** | ||
* Tell gpt to destroy the slot and its metadata | ||
*/ | ||
* Tell gpt to destroy the slot and its metadata | ||
*/ | ||
destroySlot: function(gptSlot) { | ||
@@ -390,4 +371,4 @@ if (window.googletag.pubadsReady && window.googletag.pubads) { | ||
/* | ||
* Tell gpt to request an ad | ||
*/ | ||
* Tell gpt to request an ad | ||
*/ | ||
display: function() { | ||
@@ -401,4 +382,4 @@ window.googletag.cmd.push(() => { | ||
/** | ||
* Set the DFP unit name for the slot. | ||
*/ | ||
* Set the DFP unit name for the slot. | ||
*/ | ||
setUnitName: function() { | ||
@@ -427,4 +408,4 @@ window.googletag.cmd.push(() => { | ||
/** | ||
* Add the slot to the pub ads service and add a companion service if configured | ||
*/ | ||
* Add the slot to the pub ads service and add a companion service if configured | ||
*/ | ||
addServices: function(gptSlot) { | ||
@@ -443,5 +424,5 @@ window.googletag.cmd.push(() => { | ||
/** | ||
* Sets the GPT collapse empty mode for a given slot | ||
* values can be 'after', 'before', 'never' | ||
*/ | ||
* Sets the GPT collapse empty mode for a given slot | ||
* values can be 'after', 'before', 'never' | ||
*/ | ||
setCollapseEmpty: function() { | ||
@@ -462,3 +443,3 @@ window.googletag.cmd.push(() => { | ||
}, | ||
submitGptImpression: function() { | ||
submitGptImpression : function() { | ||
/* istanbul ignore next */ | ||
@@ -475,8 +456,7 @@ function getImpressionURL(iframe) { | ||
if (this.outOfPage && this.gpt.iframe) { | ||
const impressionURL = getImpressionURL(this.gpt.iframe); | ||
/* istanbul ignore next */ | ||
if (impressionURL) { | ||
utils.attach( | ||
impressionURL, | ||
true, | ||
if(impressionURL){ | ||
utils.attach(impressionURL, true, | ||
() => { | ||
@@ -491,3 +471,4 @@ utils.log.info('Impression Url requested'); | ||
} | ||
} else { | ||
} | ||
else { | ||
utils.log.warn('Attempting to call submitImpression on a non-oop slot'); | ||
@@ -497,5 +478,5 @@ } | ||
/** | ||
* Sets page url to be sent to google | ||
* prevents later url changes via javascript from breaking the ads | ||
*/ | ||
* Sets page url to be sent to google | ||
* prevents later url changes via javascript from breaking the ads | ||
*/ | ||
setURL: function(gptSlot) { | ||
@@ -511,4 +492,4 @@ window.googletag.cmd.push(() => { | ||
/** | ||
* Adds key values from a given object to the slot targeting | ||
*/ | ||
* Adds key values from a given object to the slot targeting | ||
*/ | ||
setTargeting: function(gptSlot) { | ||
@@ -525,3 +506,3 @@ window.googletag.cmd.push(() => { | ||
return this; | ||
} | ||
}, | ||
}; | ||
@@ -536,10 +517,8 @@ | ||
/** | ||
* The correlator is a random number added to ad calls. | ||
* It is used by the ad server to determine which impressions where served to the same page | ||
* Updating is used to tell the ad server to treat subsequent ad calls as being on a new page | ||
*/ | ||
* The correlator is a random number added to ad calls. | ||
* It is used by the ad server to determine which impressions where served to the same page | ||
* Updating is used to tell the ad server to treat subsequent ad calls as being on a new page | ||
*/ | ||
function updateCorrelator() { | ||
utils.log.warn( | ||
'[DEPRECATED]: Updatecorrelator is being phased out by google and removed from o-ads in future releases.' | ||
); | ||
utils.log.warn('[DEPRECATED]: Updatecorrelator is being phased out by google and removed from o-ads in future releases.'); | ||
@@ -560,3 +539,4 @@ googletag.cmd.push(() => { | ||
setPageTargeting(params); | ||
} else { | ||
} | ||
else { | ||
utils.log.warn('Attempting to set page targeting before the GPT library has initialized'); | ||
@@ -566,3 +546,3 @@ } | ||
//https://developers.google.com/doubleclick-gpt/common_implementation_mistakes#scenario-2:-checking-the-googletag-object-to-know-whether-gpt-is-ready | ||
function hasGPTLoaded() { | ||
function hasGPTLoaded () { | ||
if (window.googletag && window.googletag.apiReady) { | ||
@@ -578,3 +558,3 @@ return true; | ||
const conf = config('gpt'); | ||
if (!conf) { | ||
if(!conf){ | ||
return; | ||
@@ -587,6 +567,4 @@ } | ||
} | ||
export default { | ||
init, | ||
setup, | ||
updateCorrelator, | ||
@@ -593,0 +571,0 @@ updatePageTargeting, |
// generated by genversion | ||
module.exports = '14.2.0'; | ||
module.exports = '14.1.0'; |
40
204811
45
5787
118