diff options
author | Sebastian <sebasjm@gmail.com> | 2021-08-23 16:46:06 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2021-08-23 16:48:30 -0300 |
commit | 38acabfa6089ab8ac469c12b5f55022fb96935e5 (patch) | |
tree | 453dbf70000cc5e338b06201af1eaca8343f8f73 /date-fns/scripts/build | |
parent | f26125e039143b92dc0d84e7775f508ab0cdcaa8 (diff) | |
download | node-vendor-master.tar.gz node-vendor-master.tar.bz2 node-vendor-master.zip |
Diffstat (limited to 'date-fns/scripts/build')
22 files changed, 2161 insertions, 0 deletions
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) |