summaryrefslogtreecommitdiff
path: root/date-fns/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'date-fns/scripts')
-rw-r--r--date-fns/scripts/.eslintrc.js5
-rw-r--r--date-fns/scripts/_lib/getConstants.js14
-rw-r--r--date-fns/scripts/_lib/listFPFns.js17
-rw-r--r--date-fns/scripts/_lib/listFns.js38
-rw-r--r--date-fns/scripts/_lib/listLocales.js24
-rw-r--r--date-fns/scripts/build/_lib/addDenoExtensions.ts79
-rw-r--r--date-fns/scripts/build/_lib/prettier.js6
-rw-r--r--date-fns/scripts/build/_lib/typings/common.js92
-rw-r--r--date-fns/scripts/build/_lib/typings/flow.js190
-rw-r--r--date-fns/scripts/build/_lib/typings/formatBlock.js149
-rw-r--r--date-fns/scripts/build/_lib/typings/typeScript.js460
-rwxr-xr-xdate-fns/scripts/build/build.sh12
-rwxr-xr-xdate-fns/scripts/build/deno.sh7
-rwxr-xr-xdate-fns/scripts/build/docs.js380
-rwxr-xr-xdate-fns/scripts/build/fp.js51
-rwxr-xr-xdate-fns/scripts/build/indices.js61
-rw-r--r--date-fns/scripts/build/localeSnapshots/_lib/distanceDates.js59
-rwxr-xr-xdate-fns/scripts/build/localeSnapshots/index.js69
-rw-r--r--date-fns/scripts/build/localeSnapshots/renderFormatDistance/index.js26
-rw-r--r--date-fns/scripts/build/localeSnapshots/renderFormatDistanceStrict/index.js26
-rw-r--r--date-fns/scripts/build/localeSnapshots/renderFormatParse/formatParseTokens.js218
-rw-r--r--date-fns/scripts/build/localeSnapshots/renderFormatParse/index.js59
-rw-r--r--date-fns/scripts/build/localeSnapshots/renderFormatRelative/index.js23
-rwxr-xr-xdate-fns/scripts/build/package.sh51
-rwxr-xr-xdate-fns/scripts/build/packages.js92
-rwxr-xr-xdate-fns/scripts/build/removeOutdatedLocales.js20
-rwxr-xr-xdate-fns/scripts/build/typings.js31
-rw-r--r--date-fns/scripts/release/buildChangelog.ts289
-rwxr-xr-xdate-fns/scripts/release/release.sh45
-rwxr-xr-xdate-fns/scripts/release/tweet.js36
-rwxr-xr-xdate-fns/scripts/release/updateFirebase.js116
-rwxr-xr-xdate-fns/scripts/release/writeVersion.js35
-rwxr-xr-xdate-fns/scripts/test/ci.sh50
-rwxr-xr-xdate-fns/scripts/test/countTests.sh14
-rwxr-xr-xdate-fns/scripts/test/coverageReport.sh12
-rwxr-xr-xdate-fns/scripts/test/dst.sh18
-rwxr-xr-xdate-fns/scripts/test/formatISO.sh12
-rwxr-xr-xdate-fns/scripts/test/formatRFC3339.sh17
-rwxr-xr-xdate-fns/scripts/test/node.sh19
-rwxr-xr-xdate-fns/scripts/test/smoke.sh23
-rwxr-xr-xdate-fns/scripts/test/tz.sh20
-rwxr-xr-xdate-fns/scripts/test/tzExtended.sh368
42 files changed, 3333 insertions, 0 deletions
diff --git a/date-fns/scripts/.eslintrc.js b/date-fns/scripts/.eslintrc.js
new file mode 100644
index 0000000..a40aaa8
--- /dev/null
+++ b/date-fns/scripts/.eslintrc.js
@@ -0,0 +1,5 @@
+module.exports = {
+ rules: {
+ 'no-console': 'off'
+ }
+}
diff --git a/date-fns/scripts/_lib/getConstants.js b/date-fns/scripts/_lib/getConstants.js
new file mode 100644
index 0000000..56387fd
--- /dev/null
+++ b/date-fns/scripts/_lib/getConstants.js
@@ -0,0 +1,14 @@
+const path = require('path')
+const jsDocParser = require('jsdoc-to-markdown')
+
+module.exports = getConstants
+
+function getConstants() {
+ return jsDocParser
+ .getJsdocDataSync({
+ files: path.resolve(process.cwd(), 'src/constants/index.ts'),
+ 'no-cache': true,
+ configure: path.resolve(process.cwd(), 'jsdoc2md.json'),
+ })
+ .filter((c) => c.kind === 'constant' && !c.undocumented)
+}
diff --git a/date-fns/scripts/_lib/listFPFns.js b/date-fns/scripts/_lib/listFPFns.js
new file mode 100644
index 0000000..d33180e
--- /dev/null
+++ b/date-fns/scripts/_lib/listFPFns.js
@@ -0,0 +1,17 @@
+const path = require('path')
+const fs = require('fs')
+
+module.exports = listFPFns
+
+const ignoredFiles = ['index.js', 'test.js', 'index.js.flow', 'package.json']
+
+function listFPFns() {
+ const files = fs.readdirSync(path.join(process.cwd(), 'src', 'fp'))
+ return files
+ .filter(file => /^[^._]/.test(file) && !ignoredFiles.includes(file))
+ .map(file => ({
+ name: file,
+ path: `./${file}`,
+ fullPath: `./src/fp/${file}/index.js`
+ }))
+}
diff --git a/date-fns/scripts/_lib/listFns.js b/date-fns/scripts/_lib/listFns.js
new file mode 100644
index 0000000..7ed93a4
--- /dev/null
+++ b/date-fns/scripts/_lib/listFns.js
@@ -0,0 +1,38 @@
+const path = require('path')
+const fs = require('fs')
+const { promisify } = require('util')
+
+const exists = promisify(fs.exists)
+const readDir = promisify(fs.readdir)
+
+module.exports = listFns
+
+const ignoredFiles = [
+ 'locale',
+ 'esm',
+ 'fp',
+ 'constants',
+ 'index.js',
+ 'test.js',
+ 'index.js.flow',
+ 'package.json',
+ 'types.ts'
+]
+
+async function listFns() {
+ const srcPath = path.join(process.cwd(), 'src')
+ const files = await readDir(srcPath)
+
+ return Promise.all(
+ files
+ .filter(file => /^[^._]/.test(file) && !ignoredFiles.includes(file))
+ .map(async file => {
+ const isTs = await exists(path.join(srcPath, file, 'index.ts'))
+ return {
+ name: file,
+ path: `./${file}`,
+ fullPath: `./src/${file}/index.${isTs ? 'ts' : 'js'}`
+ }
+ })
+ )
+}
diff --git a/date-fns/scripts/_lib/listLocales.js b/date-fns/scripts/_lib/listLocales.js
new file mode 100644
index 0000000..81c8cc8
--- /dev/null
+++ b/date-fns/scripts/_lib/listLocales.js
@@ -0,0 +1,24 @@
+const path = require('path')
+const fs = require('fs')
+
+const ignoredFiles = [
+ 'index.js',
+ 'test.js',
+ 'index.js.flow',
+ 'package.json',
+ 'types.ts',
+]
+
+module.exports = listLocales
+
+function listLocales() {
+ const locales = fs.readdirSync(path.join(process.cwd(), 'src', 'locale'))
+ return locales
+ .filter((file) => /^[^._]/.test(file) && !ignoredFiles.includes(file))
+ .map((locale) => ({
+ name: locale.replace(/-/g, ''),
+ code: locale,
+ path: `./${locale}`,
+ fullPath: `./src/locale/${locale}/index.js`,
+ }))
+}
diff --git a/date-fns/scripts/build/_lib/addDenoExtensions.ts b/date-fns/scripts/build/_lib/addDenoExtensions.ts
new file mode 100644
index 0000000..f0e43d6
--- /dev/null
+++ b/date-fns/scripts/build/_lib/addDenoExtensions.ts
@@ -0,0 +1,79 @@
+import { resolve, dirname } from 'path'
+import fs from 'fs'
+import globby from 'globby'
+import ts, {
+ ExportDeclaration,
+ ImportDeclaration,
+ StringLiteral,
+} from 'typescript'
+
+const { readFile, writeFile, stat } = fs.promises
+
+const pattern = /\.(ts|js)$/
+const ignore = [/\.d\.ts$/]
+
+const resolvedExtensions: Record<string, string> = {}
+
+globby('deno')
+ .then((files) =>
+ files.filter(
+ (file) => pattern.test(file) && !ignore.find((p) => p.test(file))
+ )
+ )
+ .then((files) =>
+ Promise.all(
+ files.map((file) =>
+ readFile(file, 'utf8').then(async (content) => {
+ const source = ts.createSourceFile(
+ file,
+ content,
+ ts.ScriptTarget.Latest
+ )
+ const imports: string[] = []
+
+ source.forEachChild((node) => {
+ if (
+ [
+ ts.SyntaxKind.ImportDeclaration,
+ ts.SyntaxKind.ExportDeclaration,
+ ].includes(node.kind)
+ ) {
+ const importNode = node as ImportDeclaration | ExportDeclaration
+ const specifier = importNode.moduleSpecifier as StringLiteral
+ const importPath = specifier.text
+ const isLocal = /\.\/.+/
+ if (isLocal) imports.push(importPath)
+ }
+ })
+
+ await Promise.all(
+ imports.map(async (importPath) => {
+ if (resolvedExtensions[importPath]) return
+ const fullPath = resolveFullPath(file, importPath)
+ let isTs = false
+ try {
+ await stat(fullPath + '.ts')
+ isTs = true
+ } catch (_) {}
+ resolvedExtensions[fullPath] = isTs ? '.ts' : '.js'
+ })
+ )
+
+ return writeFile(
+ file,
+ imports.reduce((acc, importPath) => {
+ const fullPath = resolveFullPath(file, importPath)
+ return acc.replace(
+ new RegExp(importPath, 'g'),
+ importPath + resolvedExtensions[fullPath]
+ )
+ }, content)
+ )
+ })
+ )
+ )
+ )
+
+function resolveFullPath(file: string, importPath: string) {
+ return resolve(dirname(file), importPath)
+}
diff --git a/date-fns/scripts/build/_lib/prettier.js b/date-fns/scripts/build/_lib/prettier.js
new file mode 100644
index 0000000..a8fb388
--- /dev/null
+++ b/date-fns/scripts/build/_lib/prettier.js
@@ -0,0 +1,6 @@
+const prettier = require('prettier')
+const config = require('../../../.prettierrc')
+
+module.exports = (code, parser = 'babel') => {
+ return prettier.format(code, Object.assign(config, { parser }))
+}
diff --git a/date-fns/scripts/build/_lib/typings/common.js b/date-fns/scripts/build/_lib/typings/common.js
new file mode 100644
index 0000000..cd56267
--- /dev/null
+++ b/date-fns/scripts/build/_lib/typings/common.js
@@ -0,0 +1,92 @@
+const { addSeparator, formatBlock } = require('./formatBlock')
+
+const lowerCaseTypes = ['String', 'Number', 'Boolean']
+
+function correctTypeCase(type) {
+ if (lowerCaseTypes.includes(type)) {
+ return type.toLowerCase()
+ }
+ return type
+}
+
+function getParams(params, { leftBorder = '{', rightBorder = '}' } = {}) {
+ if (!params || params.length === 0) {
+ return leftBorder + rightBorder
+ }
+
+ const formattedParams = addSeparator(
+ params.map(param => {
+ const {
+ name,
+ props,
+ optional,
+ variable,
+ type: { names: typeNames }
+ } = param
+ const type = getType(typeNames, { props, forceArray: variable })
+ return `${variable ? '...' : ''}${name}${optional ? '?' : ''}: ${type}`
+ }),
+ ','
+ )
+
+ return formatBlock`
+ ${leftBorder}
+ ${formattedParams}
+ ${rightBorder}
+ `
+}
+
+function getType(types, { props = [], forceArray = false } = {}) {
+ const typeStrings = types.map(type => {
+ if (type === '*') {
+ return 'any'
+ }
+
+ if (type === 'function') {
+ return '(...args: Array<any>) => any'
+ }
+
+ if (type.startsWith('Array.')) {
+ const [, arrayType] = type.match(/^Array\.<(\w+)>$/i)
+ return `${correctTypeCase(arrayType)}[]`
+ }
+
+ if (type === 'Object' && props.length > 0) {
+ return getParams(props)
+ }
+
+ const caseCorrectedType = correctTypeCase(type)
+ if (forceArray) {
+ return `${caseCorrectedType}[]`
+ }
+
+ return caseCorrectedType
+ })
+
+ const allArrayTypes =
+ typeStrings.length > 1 && typeStrings.every(type => type.endsWith('[]'))
+ if (allArrayTypes) {
+ return `(${typeStrings.map(type => type.replace('[]', '')).join(' | ')})[]`
+ }
+
+ return typeStrings.join(' | ')
+}
+
+function getFPFnType(params, returns) {
+ const fpParamTypes = params.map(param =>
+ getType(param.type.names, { props: param.props })
+ )
+
+ const arity = fpParamTypes.length
+
+ fpParamTypes.push(getType(returns))
+
+ return `CurriedFn${arity}<${fpParamTypes.join(', ')}>`
+}
+
+module.exports = {
+ correctTypeCase,
+ getParams,
+ getType,
+ getFPFnType
+}
diff --git a/date-fns/scripts/build/_lib/typings/flow.js b/date-fns/scripts/build/_lib/typings/flow.js
new file mode 100644
index 0000000..8eca3b7
--- /dev/null
+++ b/date-fns/scripts/build/_lib/typings/flow.js
@@ -0,0 +1,190 @@
+const fs = require('fs')
+const path = require('path')
+const prettier = require('../prettier')
+
+const { getParams, getType, getFPFnType } = require('./common')
+
+const { addSeparator, formatBlock, formatFlowFile } = require('./formatBlock')
+
+/**
+ * Return curried function type aliases for a specific FP function arity.
+ * @param {Number} [arity=4]
+ */
+const getFlowFPTypeAliases = (arity = 4) =>
+ [
+ 'type CurriedFn1<A, R> = <A>(a: A) => R',
+
+ formatBlock`
+ type CurriedFn2<A, B, R> = <A>(a: A) => CurriedFn1<B, R>
+ | <A, B>(a: A, b: B) => R
+ `,
+
+ formatBlock`
+ type CurriedFn3<A, B, C, R> = <A>(a: A) => CurriedFn2<B, C, R>
+ | <A,B>(a: A, b: B) => CurriedFn1<C, R>
+ | <A,B,C>(a: A, b: B, c: C) => R
+ `,
+
+ formatBlock`
+ type CurriedFn4<A, B, C, D, R> = <A>(a: A) => CurriedFn3<B, C, D, R>
+ | <A,B>(a: A, b: B) => CurriedFn2<C, D, R>
+ | <A,B,C>(a: A, b: B, c: C) => CurriedFn1<D, R>
+ | <A,B,C,D>(a: A, b: B, c: C, d: D) => R
+ `,
+ ].slice(0, arity)
+
+function getFlowTypeAlias(type) {
+ const { title, properties, content } = type
+ return `export type ${title} = ${
+ properties ? getParams(properties) : content.type.names.join(' | ')
+ }`
+}
+
+function generateFlowFnTyping(fn, aliasDeclarations) {
+ const { title, args, content } = fn
+
+ const params = getParams(args, { leftBorder: '(', rightBorder: ')' })
+ const returns = getType(content.returns[0].type.names)
+
+ const moduleDeclaration = `declare module.exports: ${params} => ${returns}`
+
+ const typingFile = formatFlowFile`
+ ${addSeparator(aliasDeclarations, '\n')}
+
+ ${moduleDeclaration}
+ `
+
+ writeFile(`src/${title}/index.js.flow`, typingFile)
+}
+
+function generateFlowFnIndexTyping(fns, aliasDeclarations, constants) {
+ const fnsDeclarations = fns.map(({ title, args, content }) => {
+ const params = getParams(args, { leftBorder: '(', rightBorder: ')' })
+ const returns = getType(content.returns[0].type.names)
+ return `${title}: ${params} => ${returns}`
+ })
+
+ const typingFile = formatFlowFile`
+ ${addSeparator(aliasDeclarations, '\n')}
+
+ declare module.exports: {
+ ${addSeparator(
+ fnsDeclarations.concat(generateConstantsDeclarations(constants)),
+ ',\n'
+ )}
+ }
+ `
+
+ writeFile(`src/index.js.flow`, typingFile)
+}
+
+function generateFlowFPFnTyping(fn, aliasDeclarations) {
+ const { title, args, content } = fn
+
+ const type = getFPFnType(args, content.returns[0].type.names)
+
+ const typingFile = formatFlowFile`
+ ${addSeparator(aliasDeclarations, '\n')}
+
+ ${addSeparator(getFlowFPTypeAliases(args.length), '\n')}
+
+ declare module.exports: ${type}
+ `
+
+ writeFile(`src/fp/${title}/index.js.flow`, typingFile)
+}
+
+function generateFlowFPFnIndexTyping(fns, aliasDeclarations, constants) {
+ const fnsDeclarations = fns.map(
+ ({ title, args, content }) =>
+ `${title}: ${getFPFnType(args, content.returns[0].type.names)}`
+ )
+
+ const typingFile = formatFlowFile`
+ ${addSeparator(aliasDeclarations, '\n')}
+
+ ${addSeparator(getFlowFPTypeAliases(), '\n')}
+
+ declare module.exports: {
+ ${addSeparator(
+ fnsDeclarations.concat(generateConstantsDeclarations(constants)),
+ ','
+ )}
+ }
+ `
+
+ writeFile(`src/fp/index.js.flow`, typingFile)
+}
+
+function generateFlowLocaleTyping(locale, localeAliasDeclaration) {
+ const { fullPath } = locale
+
+ const typingFile = formatFlowFile`
+ ${localeAliasDeclaration}
+
+ declare module.exports: Locale
+ `
+
+ writeFile(`${fullPath}.flow`, typingFile)
+}
+
+function generateFlowLocaleIndexTyping(locales, localeAliasDeclaration) {
+ const typingFile = formatFlowFile`
+ ${localeAliasDeclaration}
+
+ declare module.exports: {
+ ${addSeparator(
+ locales.map(({ name }) => `${name}: Locale`),
+ ','
+ )}
+ }
+ `
+
+ writeFile('src/locale/index.js.flow', typingFile)
+}
+
+function generateFlowTypings(fns, aliases, locales, constants) {
+ const aliasDeclarations = aliases.map(getFlowTypeAlias)
+ const localeAliasDeclaration = getFlowTypeAlias(
+ aliases.find((alias) => alias.title === 'Locale')
+ )
+
+ fns.forEach((fn) => {
+ if (fn.isFPFn) {
+ generateFlowFPFnTyping(fn, aliasDeclarations)
+ } else {
+ generateFlowFnTyping(fn, aliasDeclarations)
+ }
+ })
+
+ locales.forEach((locale) => {
+ generateFlowLocaleTyping(locale, localeAliasDeclaration)
+ })
+
+ generateFlowFnIndexTyping(
+ fns.filter(({ isFPFn }) => !isFPFn),
+ aliasDeclarations,
+ constants
+ )
+ generateFlowFPFnIndexTyping(
+ fns.filter(({ isFPFn }) => isFPFn),
+ aliasDeclarations,
+ constants
+ )
+ generateFlowLocaleIndexTyping(locales, localeAliasDeclaration)
+}
+
+function generateConstantsDeclarations(constants) {
+ return constants.map((c) => `${c.name}: ${c.type.names.join(' | ')}`)
+}
+
+function writeFile(relativePath, content) {
+ return fs.writeFileSync(
+ path.resolve(process.cwd(), relativePath),
+ prettier(content, 'flow')
+ )
+}
+
+module.exports = {
+ generateFlowTypings,
+}
diff --git a/date-fns/scripts/build/_lib/typings/formatBlock.js b/date-fns/scripts/build/_lib/typings/formatBlock.js
new file mode 100644
index 0000000..4e1aa32
--- /dev/null
+++ b/date-fns/scripts/build/_lib/typings/formatBlock.js
@@ -0,0 +1,149 @@
+const flowHeader = '// @flow'
+const generatedAutomaticallyMessage = "// This file is generated automatically by `scripts/build/typings.js`. Please, don't change it."
+
+const id = x => x
+
+const trimLeft = string =>
+ string.replace(/^\s*/, '')
+
+const trimRight = string =>
+ string.replace(/\s*$/, '')
+
+const removeIndent = (string, indent) => {
+ return trimLeft(string.slice(0, indent)) + string.slice(indent)
+}
+
+const addIndent = (string, indent) =>
+ string.length > 0
+ ? ' '.repeat(indent) + string
+ : string
+
+const detectIndent = (string) => {
+ const matchResult = string.match(/^\n*(\s+)/)
+ const indent = matchResult
+ ? matchResult[1].length
+ : 0
+ return indent
+}
+
+const addIndentToMultilineString = (string, indent, ignoreFirstLine) =>
+ string
+ .split('\n')
+ .map((line, index) =>
+ (ignoreFirstLine && index === 0)
+ ? line
+ : addIndent(line, indent)
+ )
+ .join('\n')
+
+const addIndentToArray = (array, indent, ignoreFirstElement) =>
+ array
+ .map((element, index) =>
+ addIndentToMultilineString(element, indent, ignoreFirstElement && index === 0)
+ )
+
+const removeIndentFromArray = (array, indent, ignoreFirstElement) =>
+ array
+ .map((element, index) =>
+ (ignoreFirstElement && index === 0)
+ ? element
+ : removeIndent(element, indent)
+ )
+
+/**
+ * Add a specified separator to the end of every string in the array, except the last one
+ * @param {String[]} stringsArray
+ * @returns {String[]} stringsArray with added separators
+ */
+const addSeparator = (stringsArray, separator) =>
+ stringsArray.map((string, index) =>
+ index === stringsArray.length - 1
+ ? string
+ : string + separator
+ )
+
+/**
+ * Tag function that formats a code block by putting correct indentation to it
+ * @param {String[]} rawStrings
+ * @param {...String} substitutions
+ * @returns {String} formatted code block
+ *
+ * @example
+ * const result = formatBlock`
+ * while (true) {
+ * ${addSeparator(
+ * ['Hello', 'world', '!'].map(s => `console.log('${s}')`),
+ * ';'
+ * )}
+ * }
+ * `
+ * console.log(result)
+ * //=>
+ * while (true) {
+ * console.log('Hello');
+ * console.log('world');
+ * console.log('!')
+ * }
+ */
+const formatBlock = (rawStrings, ...substitutions) => {
+ const firstLineIndent = detectIndent(rawStrings[0])
+ let lastLineIndent = 0
+
+ const result = [...substitutions, ''].map((substitution, index) => {
+ const rawString = rawStrings[index]
+
+ // Trim left if it is the first string, and right if it is the last string
+ const maybeTrimLeft = index === 0 ? trimLeft : id
+ const maybeTrimRight = index === substitutions.length ? trimRight : id
+ const string = maybeTrimLeft(maybeTrimRight(rawString))
+
+ const lines = removeIndentFromArray(string.split('\n'), firstLineIndent, true)
+
+ if (lines.length > 1) {
+ const lastLine = lines[lines.length - 1]
+ lastLineIndent = detectIndent(lastLine)
+ }
+
+ const indentedSubstitution =
+ Array.isArray(substitution)
+ ? addIndentToArray(substitution, lastLineIndent, true).join('\n')
+ : addIndentToMultilineString(substitution, lastLineIndent, true)
+
+ return lines.join('\n') + indentedSubstitution
+ }).join('')
+
+ return result
+}
+
+/**
+ * Tag function that formats a Flow file by putting the correct indentation, header and footer to it
+ * @param {String[]} rawStrings
+ * @param {...String} substitutions
+ * @returns {String} formatted file content
+ */
+const formatFlowFile = (...args) =>
+ flowHeader +
+ '\n' +
+ generatedAutomaticallyMessage +
+ '\n\n' +
+ formatBlock(...args) +
+ '\n'
+
+/**
+ * Tag function that formats a TypeScript file by putting the correct indentation, header and footer to it
+ * @param {String[]} rawStrings
+ * @param {...String} substitutions
+ * @returns {String} formatted file content
+ */
+const formatTypeScriptFile = (...args) =>
+ generatedAutomaticallyMessage +
+ '\n\n' +
+ formatBlock(...args) +
+ '\n'
+
+module.exports = {
+ addSeparator,
+ formatBlock,
+ formatFlowFile,
+ formatTypeScriptFile
+}
diff --git a/date-fns/scripts/build/_lib/typings/typeScript.js b/date-fns/scripts/build/_lib/typings/typeScript.js
new file mode 100644
index 0000000..4e93155
--- /dev/null
+++ b/date-fns/scripts/build/_lib/typings/typeScript.js
@@ -0,0 +1,460 @@
+const fs = require('fs')
+const path = require('path')
+const prettier = require('../prettier')
+
+const { getParams, getType, getFPFnType } = require('./common')
+
+const {
+ addSeparator,
+ formatBlock,
+ formatTypeScriptFile,
+} = require('./formatBlock')
+
+/**
+ * Return curried function interfaces for a specific FP function arity.
+ * @param {Number} [arity=4]
+ * @returns {String[arity]} an array of code blocks
+ */
+const getTypeScriptFPInterfaces = (arity = 4) =>
+ [
+ formatBlock`
+ interface CurriedFn1<A, R> {
+ (a: A): R
+ }
+ `,
+
+ formatBlock`
+ interface CurriedFn2<A, B, R> {
+ (a: A): CurriedFn1<B, R>
+ (a: A, b: B): R
+ }
+ `,
+
+ formatBlock`
+ interface CurriedFn3<A, B, C, R> {
+ (a: A): CurriedFn2<B, C, R>
+ (a: A, b: B): CurriedFn1<C, R>
+ (a: A, b: B, c: C): R
+ }
+ `,
+
+ formatBlock`
+ interface CurriedFn4<A, B, C, D, R> {
+ (a: A): CurriedFn3<B, C, D, R>
+ (a: A, b: B): CurriedFn2<C, D, R>
+ (a: A, b: B, c: C): CurriedFn1<D, R>
+ (a: A, b: B, c: C, d: D): R
+ }
+ `,
+ ].slice(0, arity)
+
+function getTypeScriptTypeAlias(type) {
+ const { title, properties, content } = type
+
+ return formatBlock`
+ type ${title} = ${
+ properties ? getParams(properties) : content.type.names.join(' | ')
+ }
+ type ${title}Aliased = ${title}
+ `
+}
+
+function getExportedTypeScriptTypeAlias(type) {
+ const { title } = type
+
+ return formatBlock`
+ export type ${title} = ${title}Aliased
+ `
+}
+
+function getExportedTypeScriptTypeAliases(aliases) {
+ return formatBlock`
+ declare module 'date-fns' {
+ ${addSeparator(aliases.map(getExportedTypeScriptTypeAlias), '\n')}
+ }
+ `
+}
+
+function getTypeScriptDateFnsModuleDefinition(
+ submodule,
+ fns,
+ constantsDefinitions
+) {
+ const moduleName = `date-fns${submodule}`
+
+ const definition = formatBlock`
+ declare module '${moduleName}' {
+ ${addSeparator(
+ fns.map(getTypeScriptFnDefinition).concat(constantsDefinitions),
+ '\n'
+ )}
+ }
+ `
+
+ return {
+ name: moduleName,
+ definition,
+ }
+}
+
+function getTypeScriptDateFnsFPModuleDefinition(
+ submodule,
+ fns,
+ constantsDefinitions
+) {
+ const moduleName = `date-fns${submodule}/fp`
+
+ const fnDefinitions = fns.map(getTypeScriptFPFnDefinition)
+
+ const definition = formatBlock`
+ declare module '${moduleName}' {
+ ${addSeparator(fnDefinitions.concat(constantsDefinitions), '\n')}
+ }
+ `
+
+ return {
+ name: moduleName,
+ definition,
+ }
+}
+
+function getTypeScriptFnModuleDefinition(submodule, fnSuffix, fn) {
+ const name = fn.content.name
+ const moduleName = `date-fns${submodule}/${name}${fnSuffix}`
+
+ const definition = formatBlock`
+ declare module '${moduleName}' {
+ import {${name}} from 'date-fns${submodule}'
+ export default ${name}
+ }
+ `
+
+ return {
+ name: moduleName,
+ definition,
+ }
+}
+
+function getTypeScriptFnDefinition(fn) {
+ const { title, args, content } = fn
+
+ const params = getParams(args, { leftBorder: '(', rightBorder: ')' })
+ const returns = getType(content.returns[0].type.names)
+
+ return formatBlock`
+ function ${title} ${params}: ${returns}
+ namespace ${title} {}
+ `
+}
+
+function getTypeScriptFPFnDefinition(fn) {
+ const { title, args, content } = fn
+
+ const type = getFPFnType(args, content.returns[0].type.names)
+
+ return formatBlock`
+ const ${title}: ${type}
+ namespace ${title} {}
+ `
+}
+
+function getTypeScriptFPFnModuleDefinition(submodule, fnSuffix, isDefault, fn) {
+ const { title } = fn
+ const moduleName = `date-fns${submodule}/fp/${title}${fnSuffix}`
+
+ const definition = formatBlock`
+ declare module '${moduleName}' {
+ import {${title}} from 'date-fns${submodule}/fp'
+ export default ${title}
+ }
+ `
+
+ return {
+ name: moduleName,
+ definition,
+ }
+}
+
+function getTypeScriptLocaleIndexModuleDefinition(submodule, locales) {
+ const moduleName = `date-fns${submodule}/locale`
+
+ const localesDefinitions = locales.map(getTypeScriptLocaleDefinition)
+
+ const definition = formatBlock`
+ declare module '${moduleName}' {
+ ${addSeparator(localesDefinitions, '\n')}
+ }
+ `
+
+ return {
+ name: moduleName,
+ definition,
+ }
+}
+
+function getTypeScriptLocaleDefinition(locale) {
+ const { name } = locale
+
+ return formatBlock`
+ const ${name}: Locale
+ namespace ${name} {}
+ `
+}
+
+function getTypeScriptLocaleModuleDefinition(
+ submodule,
+ localeSuffix,
+ isDefault,
+ locale
+) {
+ const code = locale.code
+ const moduleName = `date-fns${submodule}/locale/${code}${localeSuffix}`
+ const { name } = locale
+
+ const definition = formatBlock`
+ declare module '${moduleName}' {
+ import {${name}} from 'date-fns${submodule}/locale'
+ export default ${name}
+ }
+ `
+
+ return {
+ name: moduleName,
+ definition,
+ }
+}
+
+function getTypeScriptInterfaceDefinition(fn) {
+ const { title, args, content } = fn
+ const params = getParams(args, { leftBorder: '(', rightBorder: ')' })
+ const returns = getType(content.returns[0].type.names)
+
+ return `${title}${params}: ${returns}`
+}
+
+function generateTypescriptFnTyping(fn) {
+ const typingFile = formatTypeScriptFile`
+ import {${fn.title}} from 'date-fns'
+ export default ${fn.title}
+ `
+ writeFile(`./src/${fn.title}/index.d.ts`, typingFile)
+}
+
+function generateTypescriptFPFnTyping(fn) {
+ const typingFile = formatTypeScriptFile`
+ import {${fn.title}} from 'date-fns/fp'
+ export default ${fn.title}
+ `
+ writeFile(`./src/fp/${fn.title}/index.d.ts`, typingFile)
+}
+
+function generateTypescriptLocaleTyping(locale) {
+ const typingFile = formatTypeScriptFile`
+ import {${locale.name}} from 'date-fns/locale'
+ export default ${locale.name}
+ `
+ writeFile(`src/locale/${locale.code}/index.d.ts`, typingFile)
+}
+
+function generateTypeScriptTypings(fns, aliases, locales, constants) {
+ const nonFPFns = fns.filter((fn) => !fn.isFPFn)
+ const fpFns = fns.filter((fn) => fn.isFPFn)
+ const constantsDefinitions = constants.map(
+ (c) => `const ${c.name}: ${c.type.names.join(' | ')}`
+ )
+
+ const moduleDefinitions = [
+ getTypeScriptDateFnsModuleDefinition('', nonFPFns, constantsDefinitions),
+ ]
+ .concat(nonFPFns.map(getTypeScriptFnModuleDefinition.bind(null, '', '')))
+ .concat(
+ nonFPFns.map(getTypeScriptFnModuleDefinition.bind(null, '', '/index'))
+ )
+ .concat(
+ nonFPFns.map(getTypeScriptFnModuleDefinition.bind(null, '', '/index.js'))
+ )
+ .map((module) => module.definition)
+
+ const fpModuleDefinitions = [
+ getTypeScriptDateFnsFPModuleDefinition('', fpFns, constantsDefinitions),
+ ]
+ .concat(
+ fpFns.map(getTypeScriptFPFnModuleDefinition.bind(null, '', '', false))
+ )
+ .concat(
+ fpFns.map(
+ getTypeScriptFPFnModuleDefinition.bind(null, '', '/index', false)
+ )
+ )
+ .concat(
+ fpFns.map(
+ getTypeScriptFPFnModuleDefinition.bind(null, '', '/index.js', false)
+ )
+ )
+ .map((module) => module.definition)
+
+ const esmModuleDefinitions = [
+ getTypeScriptDateFnsModuleDefinition(
+ '/esm',
+ nonFPFns,
+ constantsDefinitions
+ ),
+ ]
+ .concat(
+ nonFPFns.map(getTypeScriptFnModuleDefinition.bind(null, '/esm', ''))
+ )
+ .concat(
+ nonFPFns.map(getTypeScriptFnModuleDefinition.bind(null, '/esm', '/index'))
+ )
+ .concat(
+ nonFPFns.map(
+ getTypeScriptFnModuleDefinition.bind(null, '/esm', '/index.js')
+ )
+ )
+ .map((module) => module.definition)
+
+ const esmFPModuleDefinitions = [
+ getTypeScriptDateFnsFPModuleDefinition('/esm', fpFns, constantsDefinitions),
+ ]
+ .concat(
+ fpFns.map(getTypeScriptFPFnModuleDefinition.bind(null, '/esm', '', true))
+ )
+ .concat(
+ fpFns.map(
+ getTypeScriptFPFnModuleDefinition.bind(null, '/esm', '/index', true)
+ )
+ )
+ .concat(
+ fpFns.map(
+ getTypeScriptFPFnModuleDefinition.bind(null, '/esm', '/index.js', true)
+ )
+ )
+ .map((module) => module.definition)
+
+ const aliasDefinitions = aliases.map(getTypeScriptTypeAlias)
+
+ const exportedAliasDefinitions = [getExportedTypeScriptTypeAliases(aliases)]
+
+ const localeModuleDefinitions = [
+ getTypeScriptLocaleIndexModuleDefinition('', locales),
+ ]
+ .concat(
+ locales.map(getTypeScriptLocaleModuleDefinition.bind(null, '', '', false))
+ )
+ .concat(
+ locales.map(
+ getTypeScriptLocaleModuleDefinition.bind(null, '', '/index', false)
+ )
+ )
+ .concat(
+ locales.map(
+ getTypeScriptLocaleModuleDefinition.bind(null, '', '/index.js', false)
+ )
+ )
+ .map((module) => module.definition)
+
+ const esmLocaleModuleDefinitions = [
+ getTypeScriptLocaleIndexModuleDefinition('/esm', locales),
+ ]
+ .concat(
+ locales.map(
+ getTypeScriptLocaleModuleDefinition.bind(null, '/esm', '', true)
+ )
+ )
+ .concat(
+ locales.map(
+ getTypeScriptLocaleModuleDefinition.bind(null, '/esm', '/index', true)
+ )
+ )
+ .concat(
+ locales.map(
+ getTypeScriptLocaleModuleDefinition.bind(
+ null,
+ '/esm',
+ '/index.js',
+ true
+ )
+ )
+ )
+ .map((module) => module.definition)
+
+ const globalInterfaceDefinition = formatBlock`
+ interface dateFns {
+ ${addSeparator(
+ nonFPFns
+ .map(getTypeScriptInterfaceDefinition)
+ .concat(
+ constants.map((c) => `${c.name}: ${c.type.names.join(' | ')}`)
+ ),
+ '\n'
+ )}
+ }
+ `
+
+ const typingFile = formatTypeScriptFile`
+ // FP Interfaces
+
+ ${addSeparator(getTypeScriptFPInterfaces(), '\n')}
+
+ // Type Aliases
+
+ ${addSeparator(aliasDefinitions, '\n')}
+
+ // Exported Type Aliases
+
+ ${addSeparator(exportedAliasDefinitions, '\n')}
+
+ // Regular Functions
+
+ ${addSeparator(moduleDefinitions, '\n')}
+
+ // FP Functions
+
+ ${addSeparator(fpModuleDefinitions, '\n')}
+
+ // ECMAScript Module Functions
+
+ ${addSeparator(esmModuleDefinitions, '\n')}
+
+ // ECMAScript Module FP Functions
+
+ ${addSeparator(esmFPModuleDefinitions, '\n')}
+
+ // Regular Locales
+
+ ${addSeparator(localeModuleDefinitions, '\n')}
+
+ // ECMAScript Module Locales
+
+ ${addSeparator(esmLocaleModuleDefinitions, '\n')}
+
+ // dateFns Global Interface
+
+ ${globalInterfaceDefinition}
+ `
+
+ writeFile('typings.d.ts', typingFile)
+
+ fns.forEach((fn) => {
+ if (fn.isFPFn) {
+ generateTypescriptFPFnTyping(fn)
+ } else {
+ generateTypescriptFnTyping(fn)
+ }
+ })
+
+ locales.forEach((locale) => {
+ generateTypescriptLocaleTyping(locale)
+ })
+}
+
+function writeFile(relativePath, content) {
+ return fs.writeFileSync(
+ path.resolve(process.cwd(), relativePath),
+ prettier(content, 'typescript')
+ )
+}
+
+module.exports = {
+ generateTypeScriptTypings,
+}
diff --git a/date-fns/scripts/build/build.sh b/date-fns/scripts/build/build.sh
new file mode 100755
index 0000000..d4b3726
--- /dev/null
+++ b/date-fns/scripts/build/build.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+# The script unifies the build scripts.
+#
+# It's the entry point for the build process.
+
+set -ex
+
+./scripts/build/docs.js
+./scripts/build/fp.js
+./scripts/build/typings.js
+./scripts/build/indices.js
diff --git a/date-fns/scripts/build/deno.sh b/date-fns/scripts/build/deno.sh
new file mode 100755
index 0000000..f76b52d
--- /dev/null
+++ b/date-fns/scripts/build/deno.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+# The script builds the Deno package.
+
+rsync --archive --prune-empty-dirs --relative --exclude={'*.flow','benchmark.*','test.*','snapshot.md'} src/./ deno
+cp {CHANGELOG.md,LICENSE.md,typings.d.ts} deno
+yarn ts-node scripts/build/_lib/addDenoExtensions.ts \ No newline at end of file
diff --git a/date-fns/scripts/build/docs.js b/date-fns/scripts/build/docs.js
new file mode 100755
index 0000000..85c99eb
--- /dev/null
+++ b/date-fns/scripts/build/docs.js
@@ -0,0 +1,380 @@
+#!/usr/bin/env node
+
+/**
+ * @file
+ * The script generates docs.json used as the source of truth
+ * for the source code generators (FP, typings, etc.).
+ *
+ * It's a part of the build process.
+ */
+
+const os = require('os')
+const pLimit = require('p-limit')
+const fs = require('fs/promises')
+const path = require('path')
+const cloneDeep = require('lodash.clonedeep')
+const jsDocParser = require('jsdoc-to-markdown')
+const listFns = require('../_lib/listFns')
+const docsConfig = require('../../docs/index.js')
+
+const docsPath = path.resolve(process.cwd(), 'tmp/docs.json')
+
+generateDocsFromSource()
+ .then(generatedDocsObj)
+ .then(injectStaticDocsToDocsObj)
+ .then(injectSharedDocsToDocsObj)
+ .then(writeDocsFile)
+ .catch(reportErrors)
+
+/**
+ * Generates docs object from a list of functions using extended JSDoc format.
+ */
+async function generateDocsFromSource() {
+ const fns = await listFns()
+
+ const limit = pLimit(os.cpus().length)
+
+ const configFile = path.resolve(process.cwd(), 'jsdoc2md.json')
+
+ const jobs = fns.map((fn) => {
+ return limit(() =>
+ jsDocParser
+ .getTemplateData({
+ files: fn.fullPath,
+ 'no-cache': true,
+ configure: configFile,
+ })
+ .then((result) => result[0])
+ )
+ })
+
+ const docsResult = await Promise.all(jobs)
+
+ return docsResult
+ .map((doc) => {
+ const pureTag =
+ doc.customTags && doc.customTags.find((t) => t.tag === 'pure')
+ const pure = (pureTag && pureTag.value) !== 'false'
+ return {
+ type: 'jsdoc',
+ kind: 'function',
+ urlId: doc.name,
+ category: doc.category,
+ title: doc.name,
+ description: doc.summary,
+ content: doc,
+ pure,
+ }
+ })
+ .reduce(
+ (array, doc) =>
+ array
+ .concat(generateFnDoc(doc))
+ .concat(
+ doc.pure
+ ? [generateFPFnDoc(doc)].concat(
+ generateFPFnWithOptionsDoc(doc) || []
+ )
+ : []
+ ),
+ []
+ )
+}
+
+/**
+ * Generates docs object.
+ */
+function generatedDocsObj(docs) {
+ return groupDocs(docs, docsConfig.groups)
+}
+
+/**
+ * Injects static docs (markdown documents specified in the config file)
+ * to docs object.
+ */
+function injectStaticDocsToDocsObj(docsFileObj) {
+ return getListOfStaticDocs()
+ .then((staticDocs) => {
+ staticDocs.forEach((staticDoc) => {
+ docsFileObj[staticDoc.category].push(staticDoc)
+ })
+ return docsFileObj
+ })
+ .catch(reportErrors)
+}
+
+/**
+ * Injects shared docs to docs object.
+ */
+function injectSharedDocsToDocsObj(docsFileObj) {
+ return generateSharedDocs()
+ .then((sharedDocs) => {
+ sharedDocs.forEach((sharedDoc) => {
+ docsFileObj[sharedDoc.category].push(sharedDoc)
+ })
+ return docsFileObj
+ })
+ .catch(reportErrors)
+}
+
+/**
+ * Prints an error and exits the process with 1 status code.
+ */
+function reportErrors(err) {
+ console.error(err.stack)
+ process.exit(1)
+}
+
+/**
+ * Writes docs file.
+ */
+function writeDocsFile(docsFileObj) {
+ return fs.writeFile(docsPath, JSON.stringify(docsFileObj))
+}
+
+/**
+ * Groups passed docs list.
+ */
+function groupDocs(docs, groups) {
+ return docs.reduce((acc, doc) => {
+ ;(acc[doc.category] = acc[doc.category] || []).push(doc)
+ return acc
+ }, buildGroupsTemplate(groups))
+}
+
+/**
+ * Builds an object where the key is a group name and the value is
+ * an empty array. Pre-generated docs object allows to preserve the desired
+ * groups order.
+ */
+function buildGroupsTemplate(groups) {
+ return groups.reduce((acc, group) => {
+ acc[group] = []
+ return acc
+ }, {})
+}
+
+/**
+ * Returns promise to list of static docs with its contents.
+ */
+function getListOfStaticDocs() {
+ return Promise.all(
+ docsConfig.staticDocs.map((staticDoc) => {
+ return fs
+ .readFile(staticDoc.path)
+ .then((docContent) => docContent.toString())
+ .then((content) => Object.assign({ content }, staticDoc))
+ .catch(reportErrors)
+ })
+ )
+}
+
+/**
+ * Returns promise to list of shared docs with its contents.
+ */
+function generateSharedDocs() {
+ const docs = docsConfig.sharedDocs
+ .map(
+ (fn) =>
+ jsDocParser.getTemplateDataSync({
+ files: fn.fullPath,
+ 'no-cache': true,
+ })[0]
+ )
+ .map((doc) => ({
+ type: 'jsdoc',
+ kind: 'typedef',
+ urlId: doc.name,
+ category: doc.category,
+ title: doc.name,
+ description: doc.summary,
+ content: doc,
+ properties: paramsToTree(doc.properties),
+ }))
+
+ return Promise.resolve(docs)
+}
+
+function generateFnDoc(dirtyDoc) {
+ const doc = cloneDeep(dirtyDoc)
+
+ const isFPFn = false
+ const { urlId, title } = doc
+ const args = paramsToTree(doc.content.params)
+
+ return Object.assign(doc, {
+ isFPFn,
+ args,
+ relatedDocs: Object.assign(
+ { default: urlId, fp: `fp/${urlId}` },
+ withOptions(args) ? { fpWithOptions: `fp/${urlId}WithOptions` } : {}
+ ),
+ usage: generateUsage(title, isFPFn),
+ usageTabs: generateUsageTabs(isFPFn),
+ syntax: generateSyntaxString(title, args, isFPFn),
+ })
+}
+
+function generateFPFnDoc(dirtyDoc) {
+ const doc = cloneDeep(dirtyDoc)
+
+ const isFPFn = true
+ const { urlId, title } = doc
+ const exceptions = doc.content.exceptions.filter(
+ (exception) => !exception.description.includes('options.')
+ )
+ const params = doc.content.params
+ .filter((param) => !param.name.startsWith('options'))
+ .reverse()
+ const args = paramsToTree(params)
+
+ return Object.assign(doc, {
+ isFPFn,
+ args,
+ generatedFrom: title,
+ urlId: `fp/${urlId}`,
+ relatedDocs: Object.assign(
+ { default: urlId, fp: `fp/${urlId}` },
+ withOptions(args) ? { fpWithOptions: `fp/${urlId}WithOptions` } : {}
+ ),
+ usage: generateUsage(title, isFPFn),
+ usageTabs: generateUsageTabs(isFPFn),
+ syntax: generateSyntaxString(title, args, isFPFn),
+
+ content: Object.assign(doc.content, {
+ exceptions,
+ params,
+ examples:
+ 'See [FP Guide](https://date-fns.org/docs/FP-Guide) for more information',
+ }),
+ })
+}
+
+function generateFPFnWithOptionsDoc(dirtyDoc) {
+ const doc = cloneDeep(dirtyDoc)
+
+ const isFPFn = true
+ const { urlId, title } = doc
+ const params = doc.content.params
+ .map((param) => {
+ if (!param.name.includes('.')) {
+ param.optional = false
+ }
+ return param
+ })
+ .reverse()
+ const args = paramsToTree(params)
+
+ if (!withOptions(args)) return
+
+ return Object.assign(doc, {
+ isFPFn,
+ args,
+ generatedFrom: title,
+ title: `${title}WithOptions`,
+ urlId: `fp/${urlId}WithOptions`,
+ relatedDocs: {
+ default: urlId,
+ fp: `fp/${urlId}`,
+ fpWithOptions: `fp/${urlId}WithOptions`,
+ },
+ usage: generateUsage(title, isFPFn),
+ usageTabs: generateUsageTabs(isFPFn),
+ syntax: generateSyntaxString(title, args, isFPFn),
+
+ content: Object.assign(doc.content, {
+ params,
+ id: `${doc.content.id}WithOptions`,
+ longname: `${doc.content.longname}WithOptions`,
+ name: `${doc.content.name}WithOptions`,
+ examples:
+ 'See [FP Guide](https://date-fns.org/docs/FP-Guide) for more information',
+ }),
+ })
+}
+
+function withOptions(args) {
+ return args && args[0].name === 'options'
+}
+
+function generateUsageTabs(isFPFn) {
+ return isFPFn
+ ? ['commonjs', 'es2015', 'esm']
+ : ['commonjs', 'umd', 'es2015', 'esm']
+}
+
+function generateUsage(name, isFPFn) {
+ const submodule = isFPFn ? '/fp' : ''
+
+ let usage = {
+ commonjs: {
+ title: 'CommonJS',
+ code: `var ${name} = require('date-fns${submodule}/${name}')`,
+ },
+
+ es2015: {
+ title: 'ES 2015',
+ code: `import ${name} from 'date-fns${submodule}/${name}'`,
+ },
+
+ esm: {
+ title: 'ESM',
+ code: `import { ${name} } from 'date-fns${
+ submodule && `/esm/${submodule}`
+ }'`,
+ text:
+ 'See [ECMAScript Modules guide](https://date-fns.org/docs/ECMAScript-Modules) for more information',
+ },
+ }
+
+ return usage
+}
+
+function paramsToTree(dirtyParams) {
+ if (!dirtyParams) {
+ return null
+ }
+
+ const params = cloneDeep(dirtyParams)
+
+ const paramIndices = params.reduce((result, { name }, index) => {
+ result[name] = index
+ return result
+ }, {})
+
+ return params
+ .map((param) => {
+ const { name, isProperty } = param
+
+ const indexOfDot = name.indexOf('.')
+
+ if (indexOfDot >= 0 && !isProperty) {
+ const parentIndex = paramIndices[name.substring(0, indexOfDot)]
+ const parent = params[parentIndex]
+
+ param.name = name.substring(indexOfDot + 1)
+ param.isProperty = true
+ if (!parent.props) {
+ parent.props = [param]
+ } else {
+ parent.props.push(param)
+ }
+ }
+
+ return param
+ })
+ .filter((param) => !param.isProperty)
+}
+
+function generateSyntaxString(name, args, isFPFn) {
+ if (!args) {
+ return undefined
+ } else if (isFPFn) {
+ return args.reduce((acc, arg) => acc.concat(`(${arg.name})`), name)
+ } else {
+ const argsString = args
+ .map((arg) => (arg.optional ? `[${arg.name}]` : arg.name))
+ .join(', ')
+ return `${name}(${argsString})`
+ }
+}
diff --git a/date-fns/scripts/build/fp.js b/date-fns/scripts/build/fp.js
new file mode 100755
index 0000000..f3e5a5a
--- /dev/null
+++ b/date-fns/scripts/build/fp.js
@@ -0,0 +1,51 @@
+#!/usr/bin/env node
+
+/**
+ * @file
+ * The script generates the FP functions using the docs JSON file.
+ *
+ * It's a part of the build process.
+ */
+
+const fs = require('fs')
+const path = require('path')
+const prettier = require('./_lib/prettier')
+const jsDocs = require(path.resolve(process.cwd(), 'tmp/docs.json'))
+
+const generatedAutomaticallyMessage =
+ "// This file is generated automatically by `scripts/build/fp.js`. Please, don't change it."
+const FP_DIR = './src/fp'
+
+const fpFns = Object.keys(jsDocs)
+ .map((category) => jsDocs[category])
+ .reduce((previousValue, newValue) => [...previousValue, ...newValue], [])
+ .filter((doc) => doc.kind === 'function' && doc.isFPFn)
+
+buildFP(fpFns)
+
+function getFPFn(resultFnName, initialFnName, arity) {
+ return [generatedAutomaticallyMessage]
+ .concat('')
+ .concat(`import fn from '../../${initialFnName}/index'`)
+ .concat(`import convertToFP from '../_lib/convertToFP/index'`)
+ .concat('')
+ .concat(`var ${resultFnName} = convertToFP(fn, ${arity})`)
+ .concat('')
+ .concat(`export default ${resultFnName}`)
+ .concat('')
+ .join('\n')
+}
+
+function buildFPFn({ title, generatedFrom, args: { length } }) {
+ const fpFnLines = getFPFn(title, generatedFrom, length)
+ const fpFnDir = `${FP_DIR}/${title}`
+
+ if (!fs.existsSync(fpFnDir)) {
+ fs.mkdirSync(fpFnDir)
+ }
+ fs.writeFileSync(`${fpFnDir}/index.js`, prettier(fpFnLines))
+}
+
+function buildFP(fns) {
+ fns.forEach(buildFPFn)
+}
diff --git a/date-fns/scripts/build/indices.js b/date-fns/scripts/build/indices.js
new file mode 100755
index 0000000..45767fb
--- /dev/null
+++ b/date-fns/scripts/build/indices.js
@@ -0,0 +1,61 @@
+#!/usr/bin/env node
+
+/**
+ * @file
+ * The script generates index files for submodules.
+ *
+ * It's a part of the build process.
+ */
+
+const fs = require('fs')
+const path = require('path')
+const prettier = require('./_lib/prettier')
+const listFns = require('../_lib/listFns')
+const listFPFns = require('../_lib/listFPFns')
+const listLocales = require('../_lib/listLocales')
+
+const outdatedLocales = require('../../outdatedLocales.json')
+
+const generatedAutomaticallyMessage =
+ "// This file is generated automatically by `scripts/build/indices.js`. Please, don't change it."
+
+listFns().then((fns) => {
+ const fpFns = listFPFns()
+ const locales = listLocales().filter(
+ ({ code }) => !outdatedLocales.includes(code)
+ )
+
+ writeFile('src/index.js', generateIndex(fns, false, true))
+ writeFile('src/fp/index.js', generateIndex(fpFns, true, true))
+ writeFile('src/locale/index.js', generateIndex(locales, false, false))
+})
+
+function writeFile(relativePath, content) {
+ return fs.writeFileSync(
+ path.resolve(process.cwd(), relativePath),
+ prettier(content)
+ )
+}
+
+function generateIndex(files, isFP, includeConstants) {
+ const fileLines = files
+ .map(
+ (fn) =>
+ `export { default as ${fn.name} } from '${fn.path.replace(
+ /\.js$/,
+ ''
+ )}/index'`
+ )
+ .concat(
+ includeConstants
+ ? `export * from '${isFP ? '..' : '.'}/constants/index'`
+ : []
+ )
+
+ const indexLines = [generatedAutomaticallyMessage]
+ .concat('')
+ .concat(fileLines)
+ .join('\n')
+
+ return `${indexLines}\n`
+}
diff --git a/date-fns/scripts/build/localeSnapshots/_lib/distanceDates.js b/date-fns/scripts/build/localeSnapshots/_lib/distanceDates.js
new file mode 100644
index 0000000..4f104a0
--- /dev/null
+++ b/date-fns/scripts/build/localeSnapshots/_lib/distanceDates.js
@@ -0,0 +1,59 @@
+export const baseDate = new Date(2000, 0, 1)
+
+export const dates = [
+ new Date(2006, 0, 1),
+ new Date(2005, 0, 1),
+ new Date(2004, 0, 1),
+ new Date(2003, 0, 1),
+ new Date(2002, 0, 1),
+ new Date(2001, 5, 1),
+ new Date(2001, 1, 1),
+ new Date(2001, 0, 1),
+ new Date(2000, 5, 1),
+ new Date(2000, 2, 1),
+ new Date(2000, 1, 1),
+ new Date(2000, 0, 15),
+ new Date(2000, 0, 2),
+ new Date(2000, 0, 1, 6),
+ new Date(2000, 0, 1, 1),
+ new Date(2000, 0, 1, 0, 45),
+ new Date(2000, 0, 1, 0, 30),
+ new Date(2000, 0, 1, 0, 15),
+ new Date(2000, 0, 1, 0, 1),
+ new Date(2000, 0, 1, 0, 0, 25),
+ new Date(2000, 0, 1, 0, 0, 15),
+ new Date(2000, 0, 1, 0, 0, 5),
+ baseDate,
+ new Date(1999, 11, 31, 23, 59, 55),
+ new Date(1999, 11, 31, 23, 59, 45),
+ new Date(1999, 11, 31, 23, 59, 35),
+ new Date(1999, 11, 31, 23, 59),
+ new Date(1999, 11, 31, 23, 45),
+ new Date(1999, 11, 31, 23, 30),
+ new Date(1999, 11, 31, 23, 15),
+ new Date(1999, 11, 31, 23),
+ new Date(1999, 11, 31, 18),
+ new Date(1999, 11, 30),
+ new Date(1999, 11, 15),
+ new Date(1999, 11, 1),
+ new Date(1999, 10, 1),
+ new Date(1999, 5, 1),
+ new Date(1999, 0, 1),
+ new Date(1998, 11, 1),
+ new Date(1998, 5, 1),
+ new Date(1998, 0, 1),
+ new Date(1997, 0, 1),
+ new Date(1996, 0, 1),
+ new Date(1995, 0, 1),
+ new Date(1994, 0, 1)
+]
+
+export const relativeDates = [
+ new Date(2000, 0, 10),
+ new Date(2000, 0, 5),
+ new Date(2000, 0, 2),
+ baseDate,
+ new Date(1999, 11, 31),
+ new Date(1999, 11, 27),
+ new Date(1999, 11, 21)
+]
diff --git a/date-fns/scripts/build/localeSnapshots/index.js b/date-fns/scripts/build/localeSnapshots/index.js
new file mode 100755
index 0000000..7581d95
--- /dev/null
+++ b/date-fns/scripts/build/localeSnapshots/index.js
@@ -0,0 +1,69 @@
+#!/usr/bin/env babel-node
+
+/**
+ * @file
+ * The script generates the locale snapshots.
+ *
+ * It's a part of the build process.
+ */
+
+import { readFile, readFileSync, writeFile } from 'mz/fs'
+import path from 'path'
+import listLocales from '../../_lib/listLocales'
+import prettier from '../_lib/prettier'
+import renderFormatDistance from './renderFormatDistance'
+import renderFormatDistanceStrict from './renderFormatDistanceStrict'
+import renderFormatParse from './renderFormatParse'
+import renderFormatRelative from './renderFormatRelative'
+
+const mode = process.argv[2] || 'generate'
+
+if (process.env.TZ.toLowerCase() !== 'utc')
+ throw new Error('The locale snapshots generation must be run with TZ=utc')
+
+const outdatedLocales = JSON.parse(
+ readFileSync(path.join(process.cwd(), 'outdatedLocales.json'), 'utf8')
+)
+const locales = listLocales().filter(
+ ({ code }) => !outdatedLocales.includes(code)
+)
+
+Promise.all(
+ locales.map(localeObj => {
+ const { code, fullPath } = localeObj
+ const locale = require(`../../../src/locale/${code}`)
+ const source = readFileSync(path.join(process.cwd(), fullPath)).toString()
+ const languageName = source.match(/\* @language (.*)/)[1]
+
+ const snapshot = `# ${languageName} (${code}) locale
+
+${renderFormatParse(locale)}
+
+${renderFormatDistance(locale)}
+
+${renderFormatDistanceStrict(locale)}
+
+${renderFormatRelative(locale)}
+`
+
+ const snapshotPath = path.join(
+ path.resolve(process.cwd(), path.dirname(fullPath)),
+ 'snapshot.md'
+ )
+ const formattedSnapshot = prettier(snapshot, 'markdown')
+
+ if (mode === 'test') {
+ return readFile(snapshotPath, 'utf8').then(snapshotFileContent => {
+ if (snapshotFileContent !== formattedSnapshot)
+ throw new Error(
+ `The snapshot on the disk doesn't match the generated snapshot: ${snapshotPath}. Please run yarn locale-snapshots and commit the results.`
+ )
+ })
+ } else {
+ return writeFile(snapshotPath, formattedSnapshot)
+ }
+ })
+).catch(err => {
+ console.error(err.stack)
+ process.exit(1)
+})
diff --git a/date-fns/scripts/build/localeSnapshots/renderFormatDistance/index.js b/date-fns/scripts/build/localeSnapshots/renderFormatDistance/index.js
new file mode 100644
index 0000000..f59d312
--- /dev/null
+++ b/date-fns/scripts/build/localeSnapshots/renderFormatDistance/index.js
@@ -0,0 +1,26 @@
+import formatDistance from '../../../../src/formatDistance'
+import { baseDate, dates } from '../_lib/distanceDates'
+
+export default function renderFormatDistance(locale) {
+ return `## \`formatDistance\`
+
+If now is January 1st, 2000, 00:00.
+
+| Date | Result | \`includeSeconds: true\` | \`addSuffix: true\` |
+|-|-|-|-|
+${dates
+ .map(date => {
+ const dateString = date.toISOString()
+ const result = formatDistance(date, baseDate, { locale })
+ const resultIncludeSeconds = formatDistance(date, baseDate, {
+ locale,
+ includeSeconds: true
+ })
+ const resultAddSuffix = formatDistance(date, baseDate, {
+ locale,
+ addSuffix: true
+ })
+ return `| ${dateString} | ${result} | ${resultIncludeSeconds} | ${resultAddSuffix} |`
+ })
+ .join('\n')}`
+}
diff --git a/date-fns/scripts/build/localeSnapshots/renderFormatDistanceStrict/index.js b/date-fns/scripts/build/localeSnapshots/renderFormatDistanceStrict/index.js
new file mode 100644
index 0000000..3dc03ab
--- /dev/null
+++ b/date-fns/scripts/build/localeSnapshots/renderFormatDistanceStrict/index.js
@@ -0,0 +1,26 @@
+import formatDistanceStrict from '../../../../src/formatDistanceStrict'
+import { baseDate, dates } from '../_lib/distanceDates'
+
+export default function renderFormatDistanceStrict(locale) {
+ return `## \`formatDistanceStrict\`
+
+If now is January 1st, 2000, 00:00.
+
+| Date | Result | \`addSuffix: true\` | With forced unit (i.e. \`hour\`)
+|-|-|-|-|
+${dates
+ .map(date => {
+ const dateString = date.toISOString()
+ const result = formatDistanceStrict(date, baseDate, { locale })
+ const resultAddSuffix = formatDistanceStrict(date, baseDate, {
+ locale,
+ addSuffix: true
+ })
+ const resultForcedUnit = formatDistanceStrict(date, baseDate, {
+ locale,
+ unit: 'hour'
+ })
+ return `| ${dateString} | ${result} | ${resultAddSuffix} | ${resultForcedUnit} |`
+ })
+ .join('\n')}`
+}
diff --git a/date-fns/scripts/build/localeSnapshots/renderFormatParse/formatParseTokens.js b/date-fns/scripts/build/localeSnapshots/renderFormatParse/formatParseTokens.js
new file mode 100644
index 0000000..5631d95
--- /dev/null
+++ b/date-fns/scripts/build/localeSnapshots/renderFormatParse/formatParseTokens.js
@@ -0,0 +1,218 @@
+const yearDates = [
+ new Date(1987, 1, 11, 12, 13, 14, 15),
+ new Date(0, 0, 1, 12, 13, 14, 15).setFullYear(5, 0, 1)
+]
+
+const quarterDates = [
+ new Date(2019, 0, 1, 12, 13, 14, 15),
+ new Date(2019, 3, 1, 12, 13, 14, 15)
+]
+
+const monthDates = [
+ new Date(2019, 1, 11, 12, 13, 14, 15),
+ new Date(2019, 6, 10, 12, 13, 14, 15)
+]
+
+const weekOfYearDates = [
+ new Date(2019, 0, 1, 12, 13, 14, 15),
+ new Date(2019, 11, 1, 12, 13, 14, 15)
+]
+
+const dayOfMonthDates = [
+ new Date(2019, 1, 11, 12, 13, 14, 15),
+ new Date(2019, 1, 28, 12, 13, 14, 15)
+]
+
+const dayOfYearDates = [
+ new Date(2019, 1, 11, 12, 13, 14, 15),
+ new Date(2019, 11, 31, 12, 13, 14, 15)
+]
+
+const dayOfWeekDates = [
+ new Date(2019, 1, 11, 12, 13, 14, 15),
+ new Date(2019, 1, 15, 12, 13, 14, 15)
+]
+
+const timeOfDayDates = [
+ new Date(2019, 1, 11, 11, 13, 14, 15),
+ new Date(2019, 1, 11, 14, 13, 14, 15),
+ new Date(2019, 1, 11, 19, 13, 14, 15),
+ new Date(2019, 1, 11, 2, 13, 14, 15)
+]
+
+const hourDates = [
+ new Date(2019, 1, 11, 11, 13, 14, 15),
+ new Date(2019, 1, 11, 23, 13, 14, 15)
+]
+
+const localizedDates = [
+ new Date(1987, 1, 11, 12, 13, 14, 15),
+ new Date(1453, 4, 29, 23, 59, 59, 999)
+]
+
+const formatParseTokens = [
+ {
+ title: 'Calendar year',
+ tokens: ['yo'],
+ dates: yearDates
+ },
+
+ {
+ title: 'Local week-numbering year',
+ tokens: ['Yo'],
+ dates: yearDates,
+ options: { useAdditionalWeekYearTokens: true }
+ },
+
+ {
+ title: 'Quarter (formatting)',
+ tokens: ['Qo', 'QQQ', 'QQQQ', 'QQQQQ'],
+ dates: quarterDates
+ },
+
+ {
+ title: 'Quarter (stand-alone)',
+ tokens: ['qo', 'qqq', 'qqqq'],
+ dates: quarterDates
+ },
+
+ {
+ title: 'Month (formatting)',
+ tokens: ['Mo', 'MMM', 'MMMM', 'MMMMM'],
+ dates: monthDates
+ },
+
+ {
+ title: 'Month (stand-alone) ',
+ tokens: ['Lo', 'LLL', 'LLLL', 'LLLLL'],
+ dates: monthDates
+ },
+
+ {
+ title: 'Local week of year',
+ tokens: ['wo'],
+ dates: weekOfYearDates
+ },
+
+ {
+ title: 'ISO week of year',
+ tokens: ['Io'],
+ dates: weekOfYearDates
+ },
+
+ {
+ title: 'Day of month',
+ tokens: ['do'],
+ dates: dayOfMonthDates
+ },
+
+ {
+ title: 'Day of year',
+ tokens: ['Do'],
+ dates: dayOfYearDates,
+ options: { useAdditionalDayOfYearTokens: true }
+ },
+
+ {
+ title: 'Day of week (formatting)',
+ tokens: ['E', 'EE', 'EEE', 'EEEE', 'EEEEE', 'EEEEEE'],
+ dates: dayOfWeekDates
+ },
+
+ {
+ title: 'ISO day of week (formatting)',
+ tokens: ['io', 'iii', 'iiii', 'iiiii', 'iiiiii'],
+ dates: dayOfWeekDates
+ },
+
+ {
+ title: 'Local day of week (formatting)',
+ tokens: ['eo', 'eee', 'eeee', 'eeeee', 'eeeeee'],
+ dates: dayOfWeekDates
+ },
+
+ {
+ title: 'Local day of week (stand-alone)',
+ tokens: ['co', 'ccc', 'cccc', 'ccccc', 'cccccc'],
+ dates: dayOfWeekDates
+ },
+
+ {
+ title: 'AM, PM',
+ tokens: ['a', 'aa', 'aaa', 'aaaa', 'aaaaa'],
+ dates: timeOfDayDates
+ },
+
+ {
+ title: 'AM, PM, noon, midnight',
+ tokens: ['b', 'bb', 'bbb', 'bbbb', 'bbbbb'],
+ dates: timeOfDayDates
+ },
+
+ {
+ title: 'Flexible day period',
+ tokens: ['B', 'BB', 'BBB', 'BBBB', 'BBBBB'],
+ dates: timeOfDayDates
+ },
+
+ {
+ title: 'Hour [1-12]',
+ tokens: ['ho'],
+ dates: hourDates
+ },
+
+ {
+ title: 'Hour [0-23]',
+ tokens: ['Ho'],
+ dates: hourDates
+ },
+
+ {
+ title: 'Hour [0-11]',
+ tokens: ['Ko'],
+ dates: hourDates
+ },
+
+ {
+ title: 'Hour [1-24]',
+ tokens: ['ko'],
+ dates: hourDates
+ },
+
+ {
+ title: 'Minute',
+ tokens: ['mo'],
+ dates: [
+ new Date(2019, 0, 1, 12, 1, 14, 15),
+ new Date(2019, 3, 1, 12, 55, 14, 15)
+ ]
+ },
+
+ {
+ title: 'Second',
+ tokens: ['so'],
+ dates: [
+ new Date(2019, 0, 1, 12, 13, 1, 15),
+ new Date(2019, 3, 1, 12, 13, 55, 15)
+ ]
+ },
+
+ {
+ title: 'Long localized date',
+ tokens: ['P', 'PP', 'PPP', 'PPPP'],
+ dates: localizedDates
+ },
+
+ {
+ title: 'Long localized time',
+ tokens: ['p', 'pp', 'ppp', 'pppp'],
+ dates: localizedDates
+ },
+
+ {
+ title: 'Combination of date and time',
+ tokens: ['Pp', 'PPpp', 'PPPppp', 'PPPPpppp'],
+ dates: localizedDates
+ }
+]
+export default formatParseTokens
diff --git a/date-fns/scripts/build/localeSnapshots/renderFormatParse/index.js b/date-fns/scripts/build/localeSnapshots/renderFormatParse/index.js
new file mode 100644
index 0000000..2ddcb10
--- /dev/null
+++ b/date-fns/scripts/build/localeSnapshots/renderFormatParse/index.js
@@ -0,0 +1,59 @@
+import format from '../../../../src/format'
+import isValid from '../../../../src/isValid'
+import parse from '../../../../src/parse'
+import toDate from '../../../../src/toDate'
+import formatParseTokens from './formatParseTokens'
+
+export default function renderFormatParse(locale) {
+ return `## \`format\` and \`parse\`
+
+| Title | Token string | Date | \`format\` result | \`parse\` result |
+|-|-|-|-|-|
+${formatParseTokens
+ .map(({ title, tokens, dates, options = {}, skipParse }) => {
+ return tokens
+ .map((token, tokenIndex) => {
+ return dates
+ .map((date, dateIndex) => {
+ const dateString = toDate(date).toISOString()
+ const formatResult = format(
+ date,
+ token,
+ Object.assign({ locale }, options)
+ )
+ let parsedDate
+ try {
+ parsedDate =
+ !skipParse &&
+ parse(
+ formatResult,
+ token,
+ date,
+ Object.assign({ locale }, options)
+ )
+ } catch (_err) {
+ parsedDate = 'Errored'
+ }
+
+ const parseResult = skipParse
+ ? 'NA'
+ : parsedDate === 'Errored'
+ ? parsedDate
+ : isValid(parsedDate)
+ ? parsedDate.toISOString()
+ : 'Invalid Date'
+
+ if (dateIndex === 0 && tokenIndex === 0) {
+ return `| ${title} | ${token} | ${dateString} | ${formatResult} | ${parseResult} |`
+ } else if (dateIndex === 0) {
+ return `| | ${token} | ${dateString} | ${formatResult} | ${parseResult} |`
+ } else {
+ return `| | | ${dateString} | ${formatResult} | ${parseResult} |`
+ }
+ })
+ .join('\n')
+ })
+ .join('\n')
+ })
+ .join('\n')}`
+}
diff --git a/date-fns/scripts/build/localeSnapshots/renderFormatRelative/index.js b/date-fns/scripts/build/localeSnapshots/renderFormatRelative/index.js
new file mode 100644
index 0000000..68e558e
--- /dev/null
+++ b/date-fns/scripts/build/localeSnapshots/renderFormatRelative/index.js
@@ -0,0 +1,23 @@
+import formatRelative from '../../../../src/formatRelative'
+import { baseDate, relativeDates } from '../_lib/distanceDates'
+
+export default function renderFormatRelative(locale) {
+ return `## \`formatRelative\`
+
+If now is January 1st, 2000, 00:00.
+
+| Date | Result |
+|-|-|
+${relativeDates
+ .map(date => {
+ const dateString = date.toISOString()
+ let result
+ try {
+ result = formatRelative(date, baseDate, { locale })
+ } catch (_err) {
+ result = 'Errored'
+ }
+ return `| ${dateString} | ${result} |`
+ })
+ .join('\n')}`
+}
diff --git a/date-fns/scripts/build/package.sh b/date-fns/scripts/build/package.sh
new file mode 100755
index 0000000..5f7a831
--- /dev/null
+++ b/date-fns/scripts/build/package.sh
@@ -0,0 +1,51 @@
+#!/bin/bash
+
+# The script generates the package in the given directory.
+#
+# It's addition to the build process. The script is used in examples.
+# It also could be used to build date-fns from a git checkout.
+
+set -e
+
+# cd to the root dir
+root="$(pwd)/$(dirname "$0")/../.."
+cd "$root" || exit 1
+
+PATH="$(npm bin):$PATH"
+# XXX: $PACKAGE_OUTPUT_PATH must be an absolute path!
+dir=${PACKAGE_OUTPUT_PATH:-"$root/tmp/package"}
+
+# Clean up output dir
+rm -rf "$dir"
+mkdir -p "$dir"
+
+# Traspile CommonJS versions of files
+env BABEL_ENV='commonjs' babel src --source-root src --out-dir "$dir" --extensions .ts,.js --ignore test.js,benchmark.js,snapshot.md --copy-files --quiet
+
+# Traspile ESM versions of files
+env BABEL_ENV='esm' babel src --source-root src --out-dir "$dir/esm" --extensions .ts,.js --ignore test.js,benchmark.js,snapshot.md,package.json --copy-files --quiet
+
+# Copy basic files
+for pattern in CHANGELOG.md \
+ package.json \
+ docs \
+ LICENSE.md \
+ README.md \
+ typings.d.ts
+do
+ cp -r "$pattern" "$dir"
+done
+
+# Remove clean up code when this issues is resolved:
+# https://github.com/babel/babel/issues/6226
+
+# Clean up dev code
+find "$dir" -type f -name "test.js" -delete
+find "$dir" -type f -name "benchmark.js" -delete
+find "$dir" -type f -name "snapshot.md" -delete
+
+# Clean up package.json pointing to the modules
+find "$dir/esm" -type f -name "package.json" -delete
+
+./scripts/build/packages.js
+./scripts/build/removeOutdatedLocales.js $dir
diff --git a/date-fns/scripts/build/packages.js b/date-fns/scripts/build/packages.js
new file mode 100755
index 0000000..154754c
--- /dev/null
+++ b/date-fns/scripts/build/packages.js
@@ -0,0 +1,92 @@
+#!/usr/bin/env node
+
+/**
+ * @file
+ * The script generates package.json files that points to correct ESM modules
+ * and TypeScript typings.
+ *
+ * It's a part of the build process.
+ */
+
+const { writeFile } = require('mz/fs')
+const path = require('path')
+const listFns = require('../_lib/listFns')
+const listFPFns = require('../_lib/listFPFns')
+const listLocales = require('../_lib/listLocales')
+const rootPath =
+ process.env.PACKAGE_OUTPUT_PATH || path.resolve(process.cwd(), 'tmp/package')
+
+const extraModules = [
+ { fullPath: './src/fp/index.js' },
+ { fullPath: './src/locale/index.js' },
+]
+
+writePackages()
+
+async function writePackages() {
+ const initialPackages = await getInitialPackages()
+ const modules = await listAll()
+ await Promise.all(
+ modules.map(async (module) =>
+ writePackage(module.fullPath, initialPackages[module.fullPath])
+ )
+ )
+ console.log('package.json files are generated')
+}
+
+function writePackage(fullPath, initialPackage) {
+ const dirPath = path.dirname(fullPath)
+ const typingsRelativePath = path.relative(dirPath, `./src/typings.d.ts`)
+ const packagePath = path.resolve(
+ rootPath,
+ `${dirPath.replace('./src/', './')}/package.json`
+ )
+
+ return writeFile(
+ packagePath,
+ JSON.stringify(
+ Object.assign({ sideEffects: false }, initialPackage || {}, {
+ typings: typingsRelativePath,
+ }),
+ null,
+ 2
+ )
+ )
+}
+
+async function getInitialPackages() {
+ const fns = await listFns()
+ return fns
+ .concat(listFPFns())
+ .concat(listLocales())
+ .concat(extraModules)
+ .reduce((acc, module) => {
+ acc[module.fullPath] = getModulePackage(module.fullPath)
+ return acc
+ }, {})
+}
+
+function getModulePackage(fullPath) {
+ const dirPath = path.dirname(fullPath)
+ const subPath = dirPath.match(/^\.\/src\/(.+)$/)[1]
+ const esmRelativePath = path.relative(
+ dirPath,
+ `./src/esm/${subPath}/index.js`
+ )
+ return { module: esmRelativePath }
+}
+
+async function listAll() {
+ const fns = await listFns()
+ return fns
+ .concat(listFPFns())
+ .concat(listLocales())
+ .concat(extraModules)
+ .reduce((acc, module) => {
+ const esmModule = Object.assign({}, module, {
+ fullPath: module.fullPath.replace('./src/', './src/esm/'),
+ })
+ return acc.concat([module, esmModule])
+ }, [])
+ .concat([])
+}
diff --git a/date-fns/scripts/build/removeOutdatedLocales.js b/date-fns/scripts/build/removeOutdatedLocales.js
new file mode 100755
index 0000000..5285f11
--- /dev/null
+++ b/date-fns/scripts/build/removeOutdatedLocales.js
@@ -0,0 +1,20 @@
+#!/usr/bin/env node
+
+/**
+ * @file
+ * The script removes outdated locales from the package.
+ *
+ * It's a part of the build process.
+ */
+
+const path = require('path')
+const rimraf = require('rimraf')
+
+const packageDir = process.argv[2]
+if (!packageDir) throw new Error('Package dir should be passed as an argument')
+
+const locales = require('../../outdatedLocales.json')
+locales.forEach(locale => {
+ rimraf.sync(path.resolve(packageDir, `locale/${locale}`))
+ rimraf.sync(path.resolve(packageDir, `locale/esm/${locale}`))
+})
diff --git a/date-fns/scripts/build/typings.js b/date-fns/scripts/build/typings.js
new file mode 100755
index 0000000..ea0dde5
--- /dev/null
+++ b/date-fns/scripts/build/typings.js
@@ -0,0 +1,31 @@
+#!/usr/bin/env node
+
+/**
+ * @file
+ * The script generates Flow and TypeScript typing files.
+ *
+ * It's a part of the build process.
+ */
+
+const path = require('path')
+const listLocales = require('../_lib/listLocales')
+const getConstants = require('../_lib/getConstants')
+const jsDocs = require(path.resolve(process.cwd(), 'tmp/docs.json'))
+
+const { generateTypeScriptTypings } = require('./_lib/typings/typeScript')
+const { generateFlowTypings } = require('./_lib/typings/flow')
+
+const locales = listLocales()
+
+const fns = Object.keys(jsDocs)
+ .map(category => jsDocs[category])
+ .reduce((previousValue, newValue) => [...previousValue, ...newValue], [])
+ .filter(doc => doc.kind === 'function')
+ .sort((a, b) => a.title.localeCompare(b.title, 'en-US'))
+
+const constants = getConstants()
+
+const aliases = jsDocs['Types']
+
+generateTypeScriptTypings(fns, aliases, locales, constants)
+generateFlowTypings(fns, aliases, locales, constants)
diff --git a/date-fns/scripts/release/buildChangelog.ts b/date-fns/scripts/release/buildChangelog.ts
new file mode 100644
index 0000000..b745951
--- /dev/null
+++ b/date-fns/scripts/release/buildChangelog.ts
@@ -0,0 +1,289 @@
+import { fromEntries, last, sample, uniq } from 'js-fns'
+import sg from 'simple-git'
+import { Octokit } from '@octokit/core'
+import format from '../../src/format'
+
+const git = sg()
+const gh = new Octokit({ auth: process.env.GITHUB_TOKEN })
+
+;(async () => {
+ const changelog = await buildChangelog()
+ console.log(renderChangelog(changelog))
+})()
+
+function renderChangelog(changelog: ChangelogVersion) {
+ let markdown = `## ${renderVersion(changelog.version)} - ${format(
+ Date.now(),
+ 'yyyy-MM-dd'
+ )}
+
+${sample(thanksOptions)!(renderAuthors(changelog.authors))}`
+
+ if (changelog.fixed.length)
+ markdown += `
+
+### Fixed
+
+${changelog.fixed.map(renderItem).join('\n\n')}`
+
+ if (changelog.changed.length)
+ markdown += `
+
+### Changed
+
+${changelog.changed.map(renderItem).join('\n\n')}`
+
+ if (changelog.added.length)
+ markdown += `
+
+### Added
+
+${changelog.added.map(renderItem).join('\n\n')}`
+ return markdown
+}
+
+async function buildChangelog(): Promise<ChangelogVersion> {
+ const lastTag = last((await git.tags()).all)
+ if (!lastTag) throw new Error("Can't find tags")
+
+ const commits = await git.log({ from: lastTag, to: 'HEAD' })
+
+ const authorsMap: { [hash: string]: string } = fromEntries(
+ await Promise.all(
+ commits.all.map((c) =>
+ gh
+ .request('GET /repos/{owner}/{repo}/commits/{ref}', {
+ owner: 'date-fns',
+ repo: 'date-fns',
+ ref: c.hash,
+ })
+ .then(({ data }) => [c.hash, data.author?.login] as [string, string])
+ )
+ )
+ )
+
+ const items: ChangelogItem[] = []
+ const authors: Author[] = []
+
+ commits.all.forEach((commit) => {
+ const author: Author = {
+ login: authorsMap[commit.hash],
+ email: commit.author_email,
+ name: commit.author_name,
+ }
+
+ const prCaptures = commit.message.match(/\(#(\d+)\)/)
+ const pr = prCaptures ? parseInt(prCaptures[1]) : undefined
+
+ let issues: number[] | undefined
+ commit.message.match(new RegExp(closesRegExp, 'g'))?.forEach((str) => {
+ const issueCaptures = str.match(closesRegExp)
+ if (issueCaptures)
+ issues = (issues || []).concat(
+ issueCaptures.slice(1).map((issue) => parseInt(issue))
+ )
+ })
+ if (!issues?.length) issues = undefined
+
+ const commitItems = extractItems(commit.body.trim(), { author, pr, issues })
+
+ if (!authors.find((a) => a.login === author.login)) authors.push(author)
+ items.push(...commitItems)
+ })
+
+ const changed = items.filter((i) => i.type === 'changed')
+ const fixed = items.filter((i) => i.type === 'fixed')
+ const added = items.filter((i) => i.type === 'added')
+
+ const lastVersion = parseVersion(lastTag)
+ let version: Version
+ if (items.find((i) => i.breaking)) {
+ version = { major: lastVersion.major + 1, minor: 0, patch: 0 }
+ } else if (changed.length || added.length) {
+ version = {
+ major: lastVersion.major,
+ minor: lastVersion.minor + 1,
+ patch: 0,
+ }
+ } else {
+ version = {
+ major: lastVersion.major,
+ minor: lastVersion.minor,
+ patch: lastVersion.patch + 1,
+ }
+ }
+
+ return { version, changed, fixed, added, authors }
+}
+
+function parseVersion(tag: string): Version {
+ const captures = tag.match(/v(\d+)\.(\d+).(\d+)/)
+ if (!captures) throw new Error(`Can't parse version from tag "${tag}"`)
+ return {
+ major: parseInt(captures[1]),
+ minor: parseInt(captures[2]),
+ patch: parseInt(captures[3]),
+ }
+}
+
+function extractItems(
+ message: string,
+ {
+ author,
+ pr,
+ issues,
+ }: { author: Author; pr: number | undefined; issues: number[] | undefined }
+): ChangelogItem[] {
+ const item = ({
+ type,
+ message,
+ }: {
+ type: ChangelogType
+ message: string
+ }) => {
+ const issuesCaptures = message.match(issuesRegExp)
+ const messageIssues = issuesCaptures?.reduce<number[]>(
+ (acc, capture) =>
+ acc.concat(
+ (capture.match(/#\d+/g) || []).map((str) => parseInt(str.slice(1)))
+ ),
+ []
+ )
+ const itemIssues = messageIssues?.length
+ ? uniq(messageIssues.concat(issues || []))
+ : issues
+
+ const breaking = /^breaking:\s?/i.test(message)
+
+ return {
+ type,
+ author,
+ message: message.replace(issuesRegExp, ''),
+ pr,
+ issues: itemIssues,
+ breaking,
+ }
+ }
+
+ switch (true) {
+ // Fixed
+ case fixedSentenceRegExp.test(message): {
+ const captures = message.match(fixedSentenceRegExp)!
+ return [item({ type: 'fixed', message: captures[2] })]
+ }
+ case fixedOneLinerRegExp.test(message): {
+ const captures = message.match(fixedOneLinerRegExp)!
+ return [item({ type: 'fixed', message: captures[1] })]
+ }
+
+ // Changed
+ case changedSentenceRegExp.test(message): {
+ const captures = message.match(changedSentenceRegExp)!
+ return [item({ type: 'changed', message: captures[2] })]
+ }
+ case changedOneLinerRegExp.test(message): {
+ const captures = message.match(changedOneLinerRegExp)!
+ return [item({ type: 'changed', message: captures[1] })]
+ }
+
+ // Added
+ case addedSentenceRegExp.test(message): {
+ const captures = message.match(addedSentenceRegExp)!
+ return [item({ type: 'added', message: captures[2] })]
+ }
+ case addedOneLinerRegExp.test(message): {
+ const captures = message.match(addedOneLinerRegExp)!
+ return [item({ type: 'added', message: captures[1] })]
+ }
+
+ default:
+ return []
+ }
+}
+
+function renderVersion({ major, minor, patch }: Version) {
+ return `v${major}.${minor}.${patch}`
+}
+
+function renderAuthors(authors: Author[]) {
+ if (authors.length > 1) {
+ return (
+ authors
+ .slice(0, authors.length - 1)
+ .map(renderAuthor)
+ .join(', ') +
+ ' and ' +
+ renderAuthor(last(authors)!)
+ )
+ } else {
+ return renderAuthor(authors[0])
+ }
+}
+
+function renderAuthor(author: Author) {
+ return `[${author.name}](http://github.com/${author.login})`
+}
+
+function renderItem(item: ChangelogItem) {
+ const message = item.pr
+ ? `[${item.message}](https://github.com/date-fns/date-fns/pull/${item.pr})`
+ : item.message
+ const issues = item.issues
+ ? ` (${item.issues
+ .map((i) => `[#${i}](https://github.com/date-fns/date-fns/issues/${i})`)
+ .join(', ')})`
+ : ''
+
+ return `- ${message}${issues}`
+}
+
+type ChangelogType = 'changed' | 'fixed' | 'added'
+
+interface Version {
+ major: number
+ minor: number
+ patch: number
+}
+
+interface ChangelogItem {
+ type: ChangelogType
+ author: Author
+ message: string
+ pr?: number
+ issues?: number[]
+ breaking: boolean
+}
+
+interface ChangelogVersion {
+ version: Version
+ changed: ChangelogItem[]
+ fixed: ChangelogItem[]
+ added: ChangelogItem[]
+ authors: Author[]
+}
+
+interface Author {
+ login: string
+ name: string
+ email: string
+}
+
+var closesRegExp = /(?:closes|fixes) #(\d+)/
+
+var issuesRegExp = /\s?\(((?:#\d+(?:,\s?)?)+)\)/g
+
+var thanksOptions = [
+ (authors: string) => `Kudos to ${authors} for working on the release.`,
+ (authors: string) => `Thanks to ${authors} for working on the release.`,
+ (authors: string) => `This release is brought to you by ${authors}.`,
+ (authors: string) => `On this release worked ${authors}.`,
+]
+
+var fixedSentenceRegExp = /^(breaking:\s?)?(fixed\s.+)/i
+var fixedOneLinerRegExp = /^fixed:\s(.+)/i
+
+var changedSentenceRegExp = /^(breaking:\s?)?(changed\s.+)/i
+var changedOneLinerRegExp = /^changed:\s(.+)/i
+
+var addedSentenceRegExp = /^(breaking:\s?)?(added\s.+)/i
+var addedOneLinerRegExp = /^added:\s(.+)/i
diff --git a/date-fns/scripts/release/release.sh b/date-fns/scripts/release/release.sh
new file mode 100755
index 0000000..b7e53a7
--- /dev/null
+++ b/date-fns/scripts/release/release.sh
@@ -0,0 +1,45 @@
+#!/bin/bash
+
+# The script builds the package and publishes it to npm.
+#
+# It's the entry point for the release process.
+
+set -e
+
+if [ -z "${APP_ENV+x}" ];
+then
+ echo 'APP_ENV is unset; please set to staging or production'
+ exit 1
+fi
+
+# A pre-release is a version with a label i.e. v2.0.0-alpha.1
+if [[ "$VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+-.+$ ]]
+then
+ IS_PRE_RELEASE=true
+else
+ IS_PRE_RELEASE=false
+fi
+
+PACKAGE_PATH="$(pwd)/../../tmp/package"
+./scripts/release/writeVersion.js
+
+env PACKAGE_OUTPUT_PATH="$PACKAGE_PATH" ./scripts/build/package.sh
+
+# Right now, we do releases manually, but when we move to GitHub Actions we'll need this line:
+# echo "//registry.npmjs.org/:_authToken=$NPM_KEY" > ~/.npmrc
+cd "$PACKAGE_PATH" || exit 1
+if [ "$IS_PRE_RELEASE" = true ]
+then
+ npm publish --tag next
+else
+ npm publish
+fi
+cd - || exit
+
+./scripts/build/docs.js
+./scripts/release/updateFirebase.js
+# TODO: Reanimate it
+# if [ "$IS_PRE_RELEASE" = false ]
+# then
+# ./scripts/release/tweet.js
+# fi
diff --git a/date-fns/scripts/release/tweet.js b/date-fns/scripts/release/tweet.js
new file mode 100755
index 0000000..8947712
--- /dev/null
+++ b/date-fns/scripts/release/tweet.js
@@ -0,0 +1,36 @@
+#!/usr/bin/env node
+
+/**
+ * @file
+ * The script posts a tweet as @date_fns about the release
+ * with a link to the change log entry.
+ *
+ * It's a part of the release process.
+ */
+
+const fetch = require('node-fetch')
+const { execSync } = require('child_process')
+const formatDate = require('../../src/format')
+
+const zapierHookURL = `https://zapier.com/hooks/catch/${process.env.ZAPIER_TWEET_RELEASE_HOOK_ID}/`
+const tag =
+ process.env.VERSION ||
+ execSync('git describe --abbrev=0 --tags').toString().trim()
+const formattedDate = formatDate(new Date(), 'YYYY-MM-DD')
+const changelogUrl = `https://date-fns.org/docs/Change-Log#${tag.replace(
+ /^v/,
+ ''
+)}-${formattedDate}`
+
+console.log('Posting release tweet...')
+
+fetch(zapierHookURL, {
+ method: 'POST',
+ body: JSON.stringify({
+ tweet: `date-fns ${tag} is published! See changelog: ${changelogUrl}.`,
+ }),
+ headers: { 'Content-Type': 'application/json' },
+}).catch((err) => {
+ console.error(err)
+ process.exit(1)
+})
diff --git a/date-fns/scripts/release/updateFirebase.js b/date-fns/scripts/release/updateFirebase.js
new file mode 100755
index 0000000..ee53561
--- /dev/null
+++ b/date-fns/scripts/release/updateFirebase.js
@@ -0,0 +1,116 @@
+#!/usr/bin/env node
+
+/**
+ * @file
+ * The script generates the site data (docs, versions, etc.)
+ * and writes it to Firebase.
+ *
+ * It's a part of the release process.
+ */
+
+const path = require('path')
+const fs = require('fs')
+const childProcess = require('child_process')
+const listLocales = require('../_lib/listLocales')
+const countries = require('world-countries')
+const publishVersion = require('@date-fns/date-fns-scripts').publishVersion
+const { version } = require('../../package.json')
+
+const prereleaseRegExp = /(test|alpha|beta|rc)/
+
+const features = {
+ docs: true,
+ i18n: true,
+ benchmarks: true,
+ camelCase: true,
+ fp: true,
+ esm: true,
+ utc: false,
+}
+
+function generateLocale(tag, locale) {
+ const { code, fullPath } = locale
+ const source = fs.readFileSync(path.join(process.cwd(), fullPath)).toString()
+ const languageName = source.match(/\* @language (.*)/)[1]
+ const iso639dash2 = source.match(/\* @iso-639-2 (.*)/)[1]
+
+ if (iso639dash2) {
+ return {
+ code,
+ url: `https://github.com/date-fns/date-fns/tree/${tag}/src/locale/${code}`,
+ name: languageName,
+ countries: countries.reduce((acc, country) => {
+ if (Object.keys(country.languages).includes(iso639dash2)) {
+ return acc.concat(country.cca2)
+ } else {
+ return acc
+ }
+ }, []),
+ }
+ } else {
+ return null
+ }
+}
+
+function generateVersionData() {
+ const tag = `v${version}`
+
+ const commit = childProcess
+ .execSync('git rev-parse HEAD')
+ .toString()
+ .replace(/[\s\n]/g, '')
+
+ const date =
+ parseInt(
+ childProcess
+ .execSync('git show -s --format=%ct')
+ .toString()
+ .replace(/[\s\n]/g, ''),
+ 10
+ ) * 1000
+
+ const docsJSON = fs
+ .readFileSync(path.resolve(process.cwd(), 'tmp/docs.json'))
+ .toString()
+ const docs = JSON.parse(docsJSON)
+ const docsCategories = Object.keys(docs)
+ const docsPages = docsCategories.reduce(
+ (acc, category) => acc.concat(docs[category]),
+ []
+ )
+ const docsKeys = docsPages.map(
+ ({ urlId, category, title, description }, index) => ({
+ urlId,
+ category,
+ title,
+ description,
+ key: index,
+ })
+ )
+
+ const locales = listLocales().map(generateLocale.bind(null, tag))
+
+ return {
+ tag,
+ date,
+ prerelease: Boolean(prereleaseRegExp.exec(tag)),
+ commit,
+ docsPages,
+ docsKeys,
+ docsCategories,
+ locales,
+ features,
+ }
+}
+
+const data = generateVersionData()
+
+publishVersion(data)
+ .then(() => {
+ console.log('Done!')
+ process.exit(0)
+ })
+ .catch((err) => {
+ console.log(err)
+ process.exit(1)
+ })
diff --git a/date-fns/scripts/release/writeVersion.js b/date-fns/scripts/release/writeVersion.js
new file mode 100755
index 0000000..c796d18
--- /dev/null
+++ b/date-fns/scripts/release/writeVersion.js
@@ -0,0 +1,35 @@
+#!/usr/bin/env node
+
+/**
+ * @file
+ * The script extracts the actual package version from $VERSION
+ * and writes it to package.json
+ *
+ * It's a part of the release process.
+ */
+
+const path = require('path')
+const fs = require('fs')
+const beautify = require('js-beautify')['js_beautify']
+
+// Extract version from VERSION
+let version
+try {
+ ;[, version] = process.env.VERSION.match(/v(.+)/)
+} catch (err) {
+ console.error(`Can not extract version from VERSION (${process.env.VERSION})`)
+ console.error(err)
+ process.exit(1)
+}
+
+console.log(`Version: ${version}`)
+
+console.log('Writing to package.json...')
+// Write package.json with the version equal to the version encoded in the tag name
+const packagePath = path.join(process.cwd(), 'package.json')
+const packageContent = JSON.parse(fs.readFileSync(packagePath).toString())
+Object.assign(packageContent, { version })
+const newPackageContentStr = beautify(JSON.stringify(packageContent), {
+ indent_size: 2,
+})
+fs.writeFileSync(packagePath, `${newPackageContentStr}\n`)
diff --git a/date-fns/scripts/test/ci.sh b/date-fns/scripts/test/ci.sh
new file mode 100755
index 0000000..6ec5bfc
--- /dev/null
+++ b/date-fns/scripts/test/ci.sh
@@ -0,0 +1,50 @@
+#!/bin/bash
+
+# The script unifies the test scripts.
+#
+# It's used as the main CI script.
+
+set -ex
+
+export PATH="$(yarn bin):$PATH"
+
+function prebuild {
+ env BUILD_TESTS=true webpack-cli --config ./config/webpack.js
+}
+
+if [ "$TEST_SUITE" == "main" ]
+then
+ yarn lint
+ yarn lint-types
+ yarn locale-snapshots test
+ ./scripts/test/smoke.sh
+
+ yarn test --single-run
+
+ ./scripts/test/dst.sh
+ ./scripts/test/formatISO.sh
+ ./scripts/test/formatRFC3339.sh
+
+ prebuild
+ ./scripts/test/tz.sh
+
+elif [ "$TEST_SUITE" == "tz" ]
+then
+ prebuild
+ ./scripts/test/tzExtended.sh
+
+elif [ "$TEST_SUITE" == "cross_browser" ] && [ "$SAUCE_USERNAME" != "" ]
+then
+ yarn test-cross-browser
+ env TEST_CROSS_BROWSER=true yarn test --single-run
+
+elif [ "$TEST_SUITE" == "node" ]
+then
+ ./scripts/test/node.sh
+
+else
+ printf "\n\033[0;31m" "UNKNOWN SUITE!" "\033[0m\n"
+ exit 1
+fi
+
+./scripts/test/countTests.sh
diff --git a/date-fns/scripts/test/countTests.sh b/date-fns/scripts/test/countTests.sh
new file mode 100755
index 0000000..3db4daa
--- /dev/null
+++ b/date-fns/scripts/test/countTests.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+# The script prints the total number of the examples in the test process.
+#
+# It's a part of the test process
+#
+# TODO: Write stats to Firebase and display the stats at the site.
+
+count=$( cat tmp/tests_count.txt 2> /dev/null || echo 0 )
+
+printf "\n%bSUITE: %s%b\n" "\x1B[32m" "$TEST_SUITE" "\x1B[0m"
+printf "\n%bTOTAL TESTS COMPLETED: %s%b\n" "\x1B[32m" "$count" "\x1B[0m"
+
+rm -f tmp/tests_count.txt
diff --git a/date-fns/scripts/test/coverageReport.sh b/date-fns/scripts/test/coverageReport.sh
new file mode 100755
index 0000000..0e7e6a3
--- /dev/null
+++ b/date-fns/scripts/test/coverageReport.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+# The script generates coverage report and uploads it to Coveralls.
+#
+# It's a part of the test process
+
+set -ex
+
+export PATH="$(yarn bin):$PATH"
+
+env COVERAGE_REPORT=true yarn test --single-run
+cat ./coverage/lcov.info | coveralls
diff --git a/date-fns/scripts/test/dst.sh b/date-fns/scripts/test/dst.sh
new file mode 100755
index 0000000..5e645b9
--- /dev/null
+++ b/date-fns/scripts/test/dst.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+# The script runs the DST tests.
+#
+# It's a part of the test process.
+
+set -ex
+
+export PATH="$(yarn bin):$PATH"
+export NODE_ENV=test
+
+env TZ=America/Sao_Paulo babel-node --extensions .ts,.js ./test/dst/parseISO/basic.js
+env TZ=Australia/Sydney babel-node --extensions .ts,.js ./test/dst/parseISO/sydney.js
+env TZ=Pacific/Apia babel-node --extensions .ts,.js ./test/dst/parseISO/samoa.js
+env TZ=Asia/Damascus babel-node --extensions .ts,.js ./test/dst/eachDayOfInterval/basic.js
+env TZ=America/Santiago babel-node --extensions .ts,.js ./test/dst/addBusinessDays/basic.js
+env TZ=Australia/Melbourne ts-node ./test/dst/formatDistanceStrict/melbourne.ts
+env TZ=Africa/Cairo ts-node ./test/dst/formatDistanceStrict/cairo.ts \ No newline at end of file
diff --git a/date-fns/scripts/test/formatISO.sh b/date-fns/scripts/test/formatISO.sh
new file mode 100755
index 0000000..28fc09e
--- /dev/null
+++ b/date-fns/scripts/test/formatISO.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+# The script runs formatISO in a non%60 timezone offset
+#
+# It's a part of the test process.
+
+set -ex
+
+export PATH="$(yarn bin):$PATH"
+export NODE_ENV=test
+
+env TZ=Asia/Kolkata babel-node --extensions .ts,.js ./test/formatISO/india.js
diff --git a/date-fns/scripts/test/formatRFC3339.sh b/date-fns/scripts/test/formatRFC3339.sh
new file mode 100755
index 0000000..df8b4cf
--- /dev/null
+++ b/date-fns/scripts/test/formatRFC3339.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+# The script runs formatRFC3339 in a non%60 timezone offset
+#
+# It's a part of the test process.
+
+set -ex
+
+export PATH="$(yarn bin):$PATH"
+export NODE_ENV=test
+
+env TZ=Asia/Kolkata babel-node --extensions .ts,.js ./test/formatRFC3339/india.js
+env TZ=America/St_Johns babel-node --extensions .ts,.js ./test/formatRFC3339/newfoundland.js
+env TZ=Australia/Eucla babel-node --extensions .ts,.js ./test/formatRFC3339/australia.js
+env TZ=Pacific/Chatham babel-node --extensions .ts,.js ./test/formatRFC3339/newzealand.js
+env TZ=Europe/Warsaw babel-node --extensions .ts,.js ./test/formatRFC3339/poland.js
+
diff --git a/date-fns/scripts/test/node.sh b/date-fns/scripts/test/node.sh
new file mode 100755
index 0000000..182b72e
--- /dev/null
+++ b/date-fns/scripts/test/node.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+# The script runs the test suite using Jest in different Node.js versions.
+#
+# It's a part of the test process.
+
+export PATH="$(yarn bin):$PATH"
+
+# Update and source nvm
+curl -o ~/.nvm/nvm.sh https://raw.githubusercontent.com/nvm-sh/nvm/v0.34.0/nvm.sh
+source ~/.nvm/nvm.sh
+
+for version in 8 9 10 11 12
+do
+ echo "Running tests using Node.js $version"
+ nvm install $version
+ npm rebuild
+ jest || exit 1
+done \ No newline at end of file
diff --git a/date-fns/scripts/test/smoke.sh b/date-fns/scripts/test/smoke.sh
new file mode 100755
index 0000000..b718edd
--- /dev/null
+++ b/date-fns/scripts/test/smoke.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+
+# The script runs the smoke test against every supported builder configuration.
+#
+# It's a part of the test process.
+
+dir="$(pwd)/examples"
+
+ok_message="\n\033[0;32m✓ OK!\033[0m\n"
+error_message="\n\033[0;31m✗ Something went wrong!\033[0m\n"
+
+cd "$dir" || exit 1
+
+for example in `ls`
+do
+ printf "\n\033[0;32mTesting $example...\033[0m\n\n"
+ cd "$example" || exit 1
+ yarn
+ yarn build
+ yarn test || (printf "$error_message" && exit 1) || exit 1
+ cd - || exit 1
+ printf "$ok_message"
+done
diff --git a/date-fns/scripts/test/tz.sh b/date-fns/scripts/test/tz.sh
new file mode 100755
index 0000000..90d1bd3
--- /dev/null
+++ b/date-fns/scripts/test/tz.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+# The script runs the test suite against every possible time zone offset.
+#
+# It's a part of the test process.
+
+printf "\n"
+
+for tz in UTC-12:00 UTC-11:00 UTC-10:00 UTC-09:30 UTC-09:00 \
+ UTC-08:00 UTC-07:00 UTC-06:00 UTC-05:00 UTC-04:30 UTC-04:00 \
+ UTC-03:30 UTC-03:00 UTC-02:00 UTC-01:00 UTC UTC+01:00 UTC+02:00 \
+ UTC+03:00 UTC+03:30 UTC+04:00 UTC+04:30 UTC+05:00 UTC+05:30 \
+ UTC+05:45 UTC+06:00 UTC+06:30 UTC+07:00 UTC+08:00 UTC+08:30 \
+ UTC+08:45 UTC+09:00 UTC+09:30 UTC+10:00 UTC+10:30 UTC+11:00 \
+ UTC+11:30 UTC+12:00 UTC+12:45 UTC+13:00 UTC+14:00
+do
+ printf "Run test in time zone $tz\n"
+ env TEST_TZ=true USE_STATIC_TESTS=true TZ=$tz yarn test --single-run \
+ &>tmp/last_test_output.txt || (cat tmp/last_test_output.txt && exit 1) || exit 1
+done
diff --git a/date-fns/scripts/test/tzExtended.sh b/date-fns/scripts/test/tzExtended.sh
new file mode 100755
index 0000000..4a20e63
--- /dev/null
+++ b/date-fns/scripts/test/tzExtended.sh
@@ -0,0 +1,368 @@
+#!/bin/bash
+
+# The script runs the test suite against every time zone
+# in the IANA database.
+#
+# It's a part of the test process.
+
+printf "\n"
+
+tz_array=(
+ "Africa/Abidjan"
+ "Africa/Accra"
+ "Africa/Algiers"
+ "Africa/Bissau"
+ "Africa/Cairo"
+ "Africa/Casablanca"
+ "Africa/Ceuta"
+ "Africa/El_Aaiun"
+ "Africa/Johannesburg"
+ "Africa/Khartoum"
+ "Africa/Lagos"
+ "Africa/Maputo"
+ "Africa/Monrovia"
+ "Africa/Nairobi"
+ "Africa/Ndjamena"
+ "Africa/Tripoli"
+ "Africa/Tunis"
+ "Africa/Windhoek"
+ "America/Adak"
+ "America/Anchorage"
+ "America/Araguaina"
+ "America/Argentina/Buenos_Aires"
+ "America/Argentina/Catamarca"
+ "America/Argentina/Cordoba"
+ "America/Argentina/Jujuy"
+ "America/Argentina/La_Rioja"
+ "America/Argentina/Mendoza"
+ "America/Argentina/Rio_Gallegos"
+ "America/Argentina/Salta"
+ "America/Argentina/San_Juan"
+ "America/Argentina/San_Luis"
+ "America/Argentina/Tucuman"
+ "America/Argentina/Ushuaia"
+ "America/Asuncion"
+ "America/Atikokan"
+ "America/Bahia"
+ "America/Bahia_Banderas"
+ "America/Barbados"
+ "America/Belem"
+ "America/Belize"
+ "America/Blanc-Sablon"
+ "America/Boa_Vista"
+ "America/Bogota"
+ "America/Boise"
+ "America/Cambridge_Bay"
+ "America/Campo_Grande"
+ "America/Cancun"
+ "America/Caracas"
+ "America/Cayenne"
+ "America/Cayman"
+ "America/Chicago"
+ "America/Chihuahua"
+ "America/Costa_Rica"
+ "America/Creston"
+ "America/Cuiaba"
+ "America/Curacao"
+ "America/Danmarkshavn"
+ "America/Dawson"
+ "America/Dawson_Creek"
+ "America/Denver"
+ "America/Detroit"
+ "America/Edmonton"
+ "America/Eirunepe"
+ "America/El_Salvador"
+ "America/Fortaleza"
+ "America/Glace_Bay"
+ "America/Godthab"
+ "America/Goose_Bay"
+ "America/Grand_Turk"
+ "America/Guatemala"
+ "America/Guayaquil"
+ "America/Guyana"
+ "America/Halifax"
+ "America/Havana"
+ "America/Hermosillo"
+ "America/Indiana/Indianapolis"
+ "America/Indiana/Knox"
+ "America/Indiana/Marengo"
+ "America/Indiana/Petersburg"
+ "America/Indiana/Tell_City"
+ "America/Indiana/Vevay"
+ "America/Indiana/Vincennes"
+ "America/Indiana/Winamac"
+ "America/Inuvik"
+ "America/Iqaluit"
+ "America/Jamaica"
+ "America/Juneau"
+ "America/Kentucky/Louisville"
+ "America/Kentucky/Monticello"
+ "America/La_Paz"
+ "America/Lima"
+ "America/Los_Angeles"
+ "America/Maceio"
+ "America/Managua"
+ "America/Manaus"
+ "America/Martinique"
+ "America/Matamoros"
+ "America/Mazatlan"
+ "America/Menominee"
+ "America/Merida"
+ "America/Metlakatla"
+ "America/Mexico_City"
+ "America/Miquelon"
+ "America/Moncton"
+ "America/Monterrey"
+ "America/Montevideo"
+ "America/Nassau"
+ "America/New_York"
+ "America/Nipigon"
+ "America/Nome"
+ "America/Noronha"
+ "America/North_Dakota/Beulah"
+ "America/North_Dakota/Center"
+ "America/North_Dakota/New_Salem"
+ "America/Ojinaga"
+ "America/Panama"
+ "America/Pangnirtung"
+ "America/Paramaribo"
+ "America/Phoenix"
+ "America/Port-au-Prince"
+ "America/Port_of_Spain"
+ "America/Porto_Velho"
+ "America/Puerto_Rico"
+ "America/Rainy_River"
+ "America/Rankin_Inlet"
+ "America/Recife"
+ "America/Regina"
+ "America/Resolute"
+ "America/Rio_Branco"
+ "America/Santa_Isabel"
+ "America/Santarem"
+ "America/Santiago"
+ "America/Santo_Domingo"
+ "America/Sao_Paulo"
+ "America/Scoresbysund"
+ "America/Sitka"
+ "America/St_Johns"
+ "America/Swift_Current"
+ "America/Tegucigalpa"
+ "America/Thule"
+ "America/Thunder_Bay"
+ "America/Tijuana"
+ "America/Toronto"
+ "America/Vancouver"
+ "America/Whitehorse"
+ "America/Winnipeg"
+ "America/Yakutat"
+ "America/Yellowknife"
+ "Antarctica/Casey"
+ "Antarctica/Davis"
+ "Antarctica/DumontDUrville"
+ "Antarctica/Macquarie"
+ "Antarctica/Mawson"
+ "Antarctica/Palmer"
+ "Antarctica/Rothera"
+ "Antarctica/Syowa"
+ "Antarctica/Troll"
+ "Antarctica/Vostok"
+ "Asia/Almaty"
+ "Asia/Amman"
+ "Asia/Anadyr"
+ "Asia/Aqtau"
+ "Asia/Aqtobe"
+ "Asia/Ashgabat"
+ "Asia/Baghdad"
+ "Asia/Baku"
+ "Asia/Bangkok"
+ "Asia/Beirut"
+ "Asia/Bishkek"
+ "Asia/Brunei"
+ "Asia/Chita"
+ "Asia/Choibalsan"
+ "Asia/Colombo"
+ "Asia/Damascus"
+ "Asia/Dhaka"
+ "Asia/Dili"
+ "Asia/Dubai"
+ "Asia/Dushanbe"
+ "Asia/Gaza"
+ "Asia/Hebron"
+ "Asia/Ho_Chi_Minh"
+ "Asia/Hong_Kong"
+ "Asia/Hovd"
+ "Asia/Irkutsk"
+ "Asia/Jakarta"
+ "Asia/Jayapura"
+ "Asia/Jerusalem"
+ "Asia/Kabul"
+ "Asia/Kamchatka"
+ "Asia/Karachi"
+ "Asia/Kathmandu"
+ "Asia/Khandyga"
+ "Asia/Kolkata"
+ "Asia/Krasnoyarsk"
+ "Asia/Kuala_Lumpur"
+ "Asia/Kuching"
+ "Asia/Macau"
+ "Asia/Magadan"
+ "Asia/Makassar"
+ "Asia/Manila"
+ "Asia/Nicosia"
+ "Asia/Novokuznetsk"
+ "Asia/Novosibirsk"
+ "Asia/Omsk"
+ "Asia/Oral"
+ "Asia/Pontianak"
+ "Asia/Pyongyang"
+ "Asia/Qatar"
+ "Asia/Qyzylorda"
+ "Asia/Rangoon"
+ "Asia/Riyadh"
+ "Asia/Sakhalin"
+ "Asia/Samarkand"
+ "Asia/Seoul"
+ "Asia/Shanghai"
+ "Asia/Singapore"
+ "Asia/Srednekolymsk"
+ "Asia/Taipei"
+ "Asia/Tashkent"
+ "Asia/Tbilisi"
+ "Asia/Tehran"
+ "Asia/Thimphu"
+ "Asia/Tokyo"
+ "Asia/Ulaanbaatar"
+ "Asia/Urumqi"
+ "Asia/Ust-Nera"
+ "Asia/Vladivostok"
+ "Asia/Yakutsk"
+ "Asia/Yekaterinburg"
+ "Asia/Yerevan"
+ "Atlantic/Azores"
+ "Atlantic/Bermuda"
+ "Atlantic/Canary"
+ "Atlantic/Cape_Verde"
+ "Atlantic/Faroe"
+ "Atlantic/Madeira"
+ "Atlantic/Reykjavik"
+ "Atlantic/South_Georgia"
+ "Atlantic/Stanley"
+ "Australia/Adelaide"
+ "Australia/Brisbane"
+ "Australia/Broken_Hill"
+ "Australia/Currie"
+ "Australia/Darwin"
+ "Australia/Eucla"
+ "Australia/Hobart"
+ "Australia/Lindeman"
+ "Australia/Lord_Howe"
+ "Australia/Melbourne"
+ "Australia/Perth"
+ "Australia/Sydney"
+ "Europe/Amsterdam"
+ "Europe/Andorra"
+ "Europe/Athens"
+ "Europe/Belgrade"
+ "Europe/Berlin"
+ "Europe/Brussels"
+ "Europe/Bucharest"
+ "Europe/Budapest"
+ "Europe/Chisinau"
+ "Europe/Copenhagen"
+ "Europe/Dublin"
+ "Europe/Gibraltar"
+ "Europe/Helsinki"
+ "Europe/Istanbul"
+ "Europe/Kaliningrad"
+ "Europe/Kiev"
+ "Europe/Lisbon"
+ "Europe/London"
+ "Europe/Luxembourg"
+ "Europe/Madrid"
+ "Europe/Malta"
+ "Europe/Minsk"
+ "Europe/Monaco"
+ "Europe/Moscow"
+ "Europe/Oslo"
+ "Europe/Paris"
+ "Europe/Prague"
+ "Europe/Riga"
+ "Europe/Rome"
+ "Europe/Samara"
+ "Europe/Simferopol"
+ "Europe/Sofia"
+ "Europe/Stockholm"
+ "Europe/Tallinn"
+ "Europe/Tirane"
+ "Europe/Uzhgorod"
+ "Europe/Vienna"
+ "Europe/Vilnius"
+ "Europe/Volgograd"
+ "Europe/Warsaw"
+ "Europe/Zaporozhye"
+ "Europe/Zurich"
+ "Indian/Chagos"
+ "Indian/Christmas"
+ "Indian/Cocos"
+ "Indian/Kerguelen"
+ "Indian/Mahe"
+ "Indian/Maldives"
+ "Indian/Mauritius"
+ "Indian/Reunion"
+ "Pacific/Apia"
+ "Pacific/Auckland"
+ "Pacific/Bougainville"
+ "Pacific/Chatham"
+ "Pacific/Chuuk"
+ "Pacific/Easter"
+ "Pacific/Efate"
+ "Pacific/Enderbury"
+ "Pacific/Fakaofo"
+ "Pacific/Fiji"
+ "Pacific/Funafuti"
+ "Pacific/Galapagos"
+ "Pacific/Gambier"
+ "Pacific/Guadalcanal"
+ "Pacific/Guam"
+ "Pacific/Honolulu"
+ "Pacific/Kiritimati"
+ "Pacific/Kosrae"
+ "Pacific/Kwajalein"
+ "Pacific/Majuro"
+ "Pacific/Marquesas"
+ "Pacific/Nauru"
+ "Pacific/Niue"
+ "Pacific/Norfolk"
+ "Pacific/Noumea"
+ "Pacific/Pago_Pago"
+ "Pacific/Palau"
+ "Pacific/Pitcairn"
+ "Pacific/Pohnpei"
+ "Pacific/Port_Moresby"
+ "Pacific/Rarotonga"
+ "Pacific/Tahiti"
+ "Pacific/Tarawa"
+ "Pacific/Tongatapu"
+ "Pacific/Wake"
+ "Pacific/Wallis"
+)
+
+# Split array by max $TZ_LENGTH elements and take array with index $TZ_INDEX
+if [ "$TZ_LENGTH" != "" ] && [ "$TZ_INDEX" != "" ]
+then
+ tz_array_length="${#tz_array[@]}"
+
+ # divide $tz_array_length by $TZ_LENGTH and round up
+ tz_limit="$((($tz_array_length + $TZ_LENGTH - 1) / $TZ_LENGTH))"
+
+ tz_offset="$(($TZ_INDEX * $tz_limit))"
+
+ tz_array=("${tz_array[@]:$tz_offset:$tz_limit}")
+fi
+
+for tz in "${tz_array[@]}"
+do
+ printf "Run test in time zone $tz\n"
+ env TEST_TZ=true USE_STATIC_TESTS=true TZ=$tz yarn test --single-run \
+ &>tmp/last_test_output.txt || (cat tmp/last_test_output.txt && exit 1) || exit 1
+done