Skip to content

Instantly share code, notes, and snippets.

@unicornware
Last active September 4, 2023 08:21
Show Gist options
  • Save unicornware/1ecc41e7407de9d8c9e90e088cd88d6d to your computer and use it in GitHub Desktop.
Save unicornware/1ecc41e7407de9d8c9e90e088cd88d6d to your computer and use it in GitHub Desktop.
Repo Configuration Files
{
"extends": "@flex-development"
}
# EDITORCONFIG
# https://editorconfig.org
# https://github.com/editorconfig/editorconfig/wiki/EditorConfig-Properties
# indicate top-most editorconfig file
root = true
# universal rules
[*]
charset = utf-8
continuation_indent_size = 2
curly_bracket_next_line = false
end_of_line = lf
indent_brace_style = BSD
indent_size = 2
indent_style = space
insert_final_newline = true
max_line_length = 80
quote_type = single
spaces_around_brackets = inside
spaces_around_operators = true
tab_width = 2
trim_trailing_whitespace = true
# handlebars
[*.hbs]
max_line_length = 100
# markdown
[*.md]
max_line_length = 120
# shellscript
[*.sh]
max_line_length = 100
# snapshots
[*.snap]
max_line_length = 130
# xml
[*.xml]
max_line_length = 100
# yaml
[*.yml]
max_line_length = 100
# ESLINT IGNORE
# https://eslint.org/docs/user-guide/configuring/ignoring-code#the-eslintignore-file
# DIRECTORIES & FILES
**/*.patch
**/*.snap
**/*config.*.timestamp*
**/.DS_Store
**/.temp/
**/__tests__/report.json
**/coverage/
**/dist/
**/node_modules/
**/tsconfig*temp.json
yarn.lock
# NEGATED PATTERNS
!**/.eslintrc*
!**/__fixtures__/**/dist/
!**/__fixtures__/**/node_modules/
!**/typings/**/dist/
!.attw.json
!.codecov.yml
!.commitlintrc.*
!.cspell.json
!.github/
!.graphqlrc.yml
!.lintstagedrc.json
!.markdownlint.jsonc
!.prettierrc.json
!.vscode/
!.yarnrc.yml
/**
* @file ESLint Configuration - Base
* @module config/eslint/base
* @see https://eslint.org/docs/user-guide/configuring
*/
/**
* @type {typeof import('node:fs')}
* @const fs
*/
const fs = require('node:fs')
/**
* @type {typeof import('./tsconfig.json')}
* @const tsconfig - Tsconfig object
*/
const tsconfig = require('./tsconfig.json')
/**
* @type {boolean}
* @const jsx - JSX check
*/
const jsx = Boolean(tsconfig.compilerOptions.jsx)
/**
* @type {import('eslint').Linter.Config}
* @const config - ESLint configuration object
*/
const config = {
env: {
[require('./tsconfig.build.json').compilerOptions.target]: true,
node: true
},
extends: ['plugin:prettier/recommended'],
overrides: [
{
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
files: '**/*.+(cjs|cts|js|jsx|mjs|mts|ts|tsx)',
globals: {
BufferEncoding: 'readonly',
Chai: 'readonly',
Console: 'readonly',
JSX: jsx ? 'readonly' : false,
NodeJS: 'readonly'
},
parser: '@typescript-eslint/parser',
parserOptions: {
extraFileExtensions: [],
project: ['./tsconfig.json', './tsconfig.cjs.json'],
sourceType: require('./package.json').type,
tsconfigRootDir: process.cwd(),
warnOnUnsupportedTypeScriptVersion: true
},
plugins: [
'@typescript-eslint',
'import',
'jsdoc',
'node',
'prettier',
'promise',
'unicorn'
],
rules: {
'@typescript-eslint/adjacent-overload-signatures': 2,
'@typescript-eslint/array-type': [
2,
{
default: 'array',
readonly: 'array'
}
],
'@typescript-eslint/await-thenable': 2,
'@typescript-eslint/ban-ts-comment': [
2,
{
minimumDescriptionLength: 3,
'ts-expect-error': 'allow-with-description',
'ts-ignore': 'allow-with-description',
'ts-nocheck': 'allow-with-description'
}
],
'@typescript-eslint/ban-tslint-comment': 2,
'@typescript-eslint/ban-types': [
2,
{
extendDefaults: true,
types: {}
}
],
'@typescript-eslint/camelcase': 0,
'@typescript-eslint/class-literal-property-style': [2, 'getters'],
'@typescript-eslint/consistent-indexed-object-style': [2, 'record'],
'@typescript-eslint/consistent-type-assertions': [
2,
{
assertionStyle: 'as',
objectLiteralTypeAssertions: 'allow'
}
],
'@typescript-eslint/consistent-type-definitions': 0,
'@typescript-eslint/consistent-type-exports': [
2,
{
fixMixedExportsWithInlineTypeSpecifier: true
}
],
'@typescript-eslint/consistent-type-imports': 0,
'@typescript-eslint/default-param-last': 2,
'@typescript-eslint/dot-notation': [
2,
{
allowIndexSignaturePropertyAccess: false,
allowKeywords: true,
allowPattern: undefined,
allowPrivateClassPropertyAccess: false,
allowProtectedClassPropertyAccess: false
}
],
'@typescript-eslint/explicit-function-return-type': 0,
'@typescript-eslint/explicit-member-accessibility': [
2,
{
accessibility: 'explicit',
overrides: {
constructors: 'no-public'
}
}
],
'@typescript-eslint/init-declarations': 0,
'@typescript-eslint/lines-between-class-members': [
2,
'always',
{
exceptAfterOverload: true,
exceptAfterSingleLine: false
}
],
'@typescript-eslint/member-ordering': [
2,
{
default: {
memberTypes: [
'static-field',
'instance-field',
'constructor',
'signature',
'static-get',
'static-set',
'static-method',
'instance-get',
'instance-set',
'instance-method'
],
order: 'alphabetically'
}
}
],
'@typescript-eslint/method-signature-style': [2, 'method'],
'@typescript-eslint/no-array-constructor': 2,
'@typescript-eslint/no-base-to-string': [
2,
{
ignoredTypeNames: ['Error', 'RegExp', 'URL', 'URLSearchParams']
}
],
'@typescript-eslint/no-confusing-non-null-assertion': 0,
'@typescript-eslint/no-confusing-void-expression': [
2,
{
ignoreArrowShorthand: true,
ignoreVoidOperator: true
}
],
'@typescript-eslint/no-dupe-class-members': 2,
'@typescript-eslint/no-dynamic-delete': 2,
'@typescript-eslint/no-empty-function': [
2,
{
allow: ['constructors', 'decoratedFunctions']
}
],
'@typescript-eslint/no-empty-interface': 0,
'@typescript-eslint/no-explicit-any': 0,
'@typescript-eslint/no-extra-non-null-assertion': 2,
'@typescript-eslint/no-extra-parens': 0,
'@typescript-eslint/no-extra-semi': 0,
'@typescript-eslint/no-extraneous-class': [
2,
{
allowConstructorOnly: false,
allowEmpty: true,
allowStaticOnly: true,
allowWithDecorator: true
}
],
'@typescript-eslint/no-floating-promises': [
2,
{
ignoreIIFE: false,
ignoreVoid: true
}
],
'@typescript-eslint/no-for-in-array': 2,
'@typescript-eslint/no-implied-eval': 2,
'@typescript-eslint/no-inferrable-types': 0,
'@typescript-eslint/no-invalid-this': [2, { capIsConstructor: true }],
'@typescript-eslint/no-invalid-void-type': [
2,
{
allowAsThisParameter: true,
allowInGenericTypeArguments: true
}
],
'@typescript-eslint/no-loop-func': 2,
'@typescript-eslint/no-loss-of-precision': 2,
'@typescript-eslint/no-magic-numbers': 0,
'@typescript-eslint/no-meaningless-void-operator': 0,
'@typescript-eslint/no-misused-new': 2,
'@typescript-eslint/no-misused-promises': [
2,
{
checksConditionals: true,
checksVoidReturn: true
}
],
'@typescript-eslint/no-namespace': 0,
'@typescript-eslint/no-non-null-asserted-nullish-coalescing': 2,
'@typescript-eslint/no-non-null-asserted-optional-chain': 0,
'@typescript-eslint/no-non-null-assertion': 0,
'@typescript-eslint/no-parameter-properties': 0,
'@typescript-eslint/no-redeclare': [
2,
{
builtinGlobals: true,
ignoreDeclarationMerge: true
}
],
'@typescript-eslint/no-redundant-type-constituents': 2,
'@typescript-eslint/no-require-imports': 2,
'@typescript-eslint/no-restricted-imports': 0,
'@typescript-eslint/no-shadow': 0,
'@typescript-eslint/no-this-alias': [
2,
{
allowDestructuring: false,
allowedNames: ['self']
}
],
'@typescript-eslint/no-throw-literal': 0,
'@typescript-eslint/no-type-alias': 0,
'@typescript-eslint/no-unnecessary-boolean-literal-compare': [
2,
{
allowComparingNullableBooleansToFalse: true,
allowComparingNullableBooleansToTrue: true
}
],
'@typescript-eslint/no-unnecessary-condition': [
2,
{
allowConstantLoopConditions: true,
allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing: false
}
],
'@typescript-eslint/no-unnecessary-qualifier': 2,
'@typescript-eslint/no-unnecessary-type-arguments': 2,
'@typescript-eslint/no-unnecessary-type-assertion': [
2,
{
typesToIgnore: []
}
],
'@typescript-eslint/no-unnecessary-type-constraint': 2,
'@typescript-eslint/no-unsafe-argument': 2,
'@typescript-eslint/no-unsafe-assignment': 0,
'@typescript-eslint/no-unsafe-call': 2,
'@typescript-eslint/no-unsafe-member-access': 2,
'@typescript-eslint/no-unsafe-return': 2,
'@typescript-eslint/no-unused-expressions': [
2,
{
allowShortCircuit: true,
allowTaggedTemplates: true,
allowTernary: true,
enforceForJSX: true
}
],
'@typescript-eslint/no-unused-vars': [
2,
{
args: 'after-used',
caughtErrors: 'all',
ignoreRestSiblings: false,
vars: 'all'
}
],
'@typescript-eslint/no-use-before-define': [
2,
{
classes: true,
enums: true,
functions: true,
ignoreTypeReferences: true,
typedefs: true,
variables: true
}
],
'@typescript-eslint/no-useless-constructor': 2,
'@typescript-eslint/no-useless-empty-export': 2,
'@typescript-eslint/no-var-requires': 2,
'@typescript-eslint/non-nullable-type-assertion-style': 2,
'@typescript-eslint/padding-line-between-statements': 0,
'@typescript-eslint/prefer-as-const': 2,
'@typescript-eslint/prefer-enum-initializers': 0,
'@typescript-eslint/prefer-for-of': 2,
'@typescript-eslint/prefer-function-type': 2,
'@typescript-eslint/prefer-includes': 0,
'@typescript-eslint/prefer-literal-enum-member': [
2,
{
allowBitwiseExpressions: true
}
],
'@typescript-eslint/prefer-namespace-keyword': 2,
'@typescript-eslint/prefer-nullish-coalescing': [
2,
{
ignoreConditionalTests: true,
ignoreMixedLogicalExpressions: true,
ignorePrimitives: { string: true }
}
],
'@typescript-eslint/prefer-optional-chain': 2,
'@typescript-eslint/prefer-readonly': 2,
'@typescript-eslint/prefer-readonly-parameter-types': 0,
'@typescript-eslint/prefer-reduce-type-parameter': 2,
'@typescript-eslint/prefer-regexp-exec': 2,
'@typescript-eslint/prefer-return-this-type': 0,
'@typescript-eslint/prefer-string-starts-ends-with': 2,
'@typescript-eslint/prefer-ts-expect-error': 2,
'@typescript-eslint/promise-function-async': 2,
'@typescript-eslint/quotes': [
2,
'single',
{
allowTemplateLiterals: true,
avoidEscape: true
}
],
'@typescript-eslint/require-array-sort-compare': 2,
'@typescript-eslint/require-await': 2,
'@typescript-eslint/restrict-plus-operands': [
2,
{
allowAny: false,
allowBoolean: false,
allowNullish: false,
allowNumberAndString: true,
allowRegExp: false,
skipCompoundAssignments: true
}
],
'@typescript-eslint/restrict-template-expressions': [
2,
{
allowAny: false,
allowBoolean: true,
allowNullish: true,
allowNumber: true,
allowRegExp: true
}
],
'@typescript-eslint/return-await': [2, 'in-try-catch'],
'@typescript-eslint/sort-type-constituents': 2,
'@typescript-eslint/strict-boolean-expressions': [
2,
{
allowAny: false,
allowNullableBoolean: true,
allowNullableNumber: true,
allowNullableObject: true,
allowNullableString: true,
allowNumber: true,
allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing: false,
allowString: true
}
],
'@typescript-eslint/switch-exhaustiveness-check': 2,
'@typescript-eslint/triple-slash-reference': [
2,
{
lib: 'never',
path: 'never',
types: 'prefer-import'
}
],
'@typescript-eslint/typedef': 0,
'@typescript-eslint/unbound-method': [2, { ignoreStatic: true }],
'@typescript-eslint/unified-signatures': 2,
'default-param-last': 0,
eqeqeq: 1,
'import/no-duplicates': [
2,
{
considerQueryString: true,
'prefer-inline': true
}
],
'init-declarations': 0,
'jsdoc/check-access': 1,
'jsdoc/check-alignment': 1,
'jsdoc/check-examples': 0,
// https://github.com/gajus/eslint-plugin-jsdoc/issues/541
'jsdoc/check-indentation': 0,
'jsdoc/check-line-alignment': 1,
'jsdoc/check-param-names': [
1,
{
allowExtraTrailingParamDocs: false,
checkDestructured: true,
checkRestProperty: true,
disableExtraPropertyReporting: true,
enableFixer: true
}
],
'jsdoc/check-property-names': [1, { enableFixer: true }],
'jsdoc/check-syntax': 1,
'jsdoc/check-tag-names': [
1,
{
definedTags: [
'decorator',
'experimental',
'maximum',
'minimum',
'next',
'packageManager',
'visibleName'
],
jsxTags: false
}
],
'jsdoc/check-types': [1, { unifyParentAndChildTypeChecks: true }],
'jsdoc/check-values': 1,
'jsdoc/empty-tags': 1,
'jsdoc/implements-on-classes': 1,
'jsdoc/match-description': 0,
'jsdoc/match-name': 0,
'jsdoc/multiline-blocks': 1,
'jsdoc/no-bad-blocks': [1, { preventAllMultiAsteriskBlocks: true }],
'jsdoc/no-defaults': 0,
'jsdoc/no-missing-syntax': 0,
'jsdoc/no-multi-asterisks': [
1,
{
allowWhitespace: true,
preventAtEnd: true,
preventAtMiddleLines: true
}
],
'jsdoc/no-restricted-syntax': 0,
'jsdoc/no-types': 0,
'jsdoc/no-undefined-types': [1, { definedTypes: ['never', 'unknown'] }],
'jsdoc/require-asterisk-prefix': [1, 'always'],
'jsdoc/require-description': [
1,
{
checkConstructors: true,
checkGetters: true,
checkSetters: true,
descriptionStyle: 'body'
}
],
'jsdoc/require-description-complete-sentence': 0,
'jsdoc/require-example': 0,
'jsdoc/require-file-overview': [
1,
{
tags: {
file: {
initialCommentsOnly: true,
mustExist: true,
preventDuplicates: true
},
module: {
initialCommentsOnly: true,
mustExist: true,
preventDuplicates: true
}
}
}
],
'jsdoc/require-hyphen-before-param-description': 1,
'jsdoc/require-jsdoc': [
1,
{
checkConstructors: true,
checkGetters: true,
checkSetters: true,
enableFixer: true,
exemptEmptyConstructors: true,
exemptEmptyFunctions: false
}
],
'jsdoc/require-param': [
1,
{
autoIncrementBase: 0,
checkConstructors: true,
checkDestructured: true,
checkDestructuredRoots: true,
checkGetters: true,
checkRestProperty: true,
checkSetters: true,
enableFixer: true,
enableRestElementFixer: true,
enableRootFixer: true,
exemptedBy: ['inheritdoc', 'this'],
unnamedRootBase: ['param'],
useDefaultObjectProperties: true
}
],
'jsdoc/require-param-description': 1,
'jsdoc/require-param-name': 1,
'jsdoc/require-param-type': 1,
'jsdoc/require-property': 1,
'jsdoc/require-property-description': 1,
'jsdoc/require-property-name': 1,
'jsdoc/require-property-type': 1,
'jsdoc/require-returns': [
1,
{
checkConstructors: false,
checkGetters: true,
forceRequireReturn: true,
forceReturnsWithAsync: true
}
],
'jsdoc/require-returns-check': [
1,
{
exemptAsync: false,
exemptGenerators: true,
reportMissingReturnForUndefinedTypes: true
}
],
'jsdoc/require-returns-description': 1,
'jsdoc/require-returns-type': 1,
'jsdoc/require-throws': 1,
'jsdoc/require-yields': [
1,
{
forceRequireNext: true,
forceRequireYields: true,
next: true,
nextWithGeneratorTag: true,
withGeneratorTag: true
}
],
'jsdoc/require-yields-check': [
1,
{
checkGeneratorsOnly: true,
next: true
}
],
'jsdoc/sort-tags': 0,
'jsdoc/tag-lines': [
1,
'any',
{
applyToEndTag: true,
count: 1,
endLines: 0,
startLines: 1,
tags: {}
}
],
'jsdoc/valid-types': [1, { allowEmptyNamepaths: true }],
'lines-between-class-members': 0,
'no-array-constructor': 0,
'no-case-declarations': 0,
'no-duplicate-imports': 0,
'no-empty': [2, { allowEmptyCatch: true }],
'no-empty-function': 0,
'no-ex-assign': 0,
'no-extra-parens': 0,
'no-extra-semi': 0,
'no-implied-eval': 0,
'no-invalid-this': 0,
'no-loop-func': 0,
'no-loss-of-precision': 0,
'no-magic-numbers': 0,
'no-restricted-imports': 0,
'no-return-await': 0,
'no-shadow': 0,
'no-sparse-arrays': 0,
'no-throw-literal': 0,
'no-unused-expressions': 0,
'no-unused-vars': 0,
'no-use-before-define': 0,
'no-useless-constructor': 0,
'no-warning-comments': 0,
'node/callback-return': [2, ['callback', 'cb', 'done', 'next']],
'node/exports-style': [2, 'module.exports', { allowBatchAssign: true }],
'node/file-extension-in-import': 0,
'node/global-require': 0,
'node/handle-callback-err': [2, '^(err|error)$'],
'node/no-callback-literal': 2,
'node/no-deprecated-api': 2,
'node/no-exports-assign': 2,
'node/no-extraneous-import': 0,
'node/no-extraneous-require': 0,
'node/no-missing-import': 0,
'node/no-new-require': 2,
'node/no-path-concat': 2,
'node/no-process-env': 0,
'node/no-process-exit': 0,
'node/no-unpublished-bin': 0,
'node/no-unpublished-import': 0,
'node/no-unpublished-require': 0,
'node/no-unsupported-features/es-builtins': 2,
'node/no-unsupported-features/es-syntax': 0,
'node/no-unsupported-features/node-builtins': 2,
'node/prefer-global/buffer': 2,
'node/prefer-global/console': 2,
'node/prefer-global/process': 2,
'node/prefer-global/text-decoder': 2,
'node/prefer-global/text-encoder': 2,
'node/prefer-global/url': 2,
'node/prefer-global/url-search-params': 2,
'node/prefer-promises/dns': 2,
'node/prefer-promises/fs': 2,
'node/process-exit-as-throw': 2,
'node/shebang': 0,
'padding-line-between-statements': 0,
'prefer-arrow-callback': 0,
'promise/always-return': 2,
'promise/avoid-new': 0,
'promise/catch-or-return': [2, { allowFinally: true, allowThen: true }],
'promise/no-callback-in-promise': 2,
'promise/no-native': 0,
'promise/no-nesting': 2,
'promise/no-new-statics': 2,
'promise/no-promise-in-callback': 2,
'promise/no-return-in-finally': 2,
'promise/no-return-wrap': [2, { allowReject: false }],
'promise/param-names': 2,
'promise/prefer-await-to-callbacks': 1,
'promise/prefer-await-to-then': 2,
'promise/valid-params': 2,
quotes: 0,
'require-await': 0,
'sort-keys': [
2,
'asc',
{
caseSensitive: true,
minKeys: 2,
natural: true
}
],
'unicorn/better-regex': [2, { sortCharacterClasses: true }],
'unicorn/catch-error-name': [2, { name: 'e' }],
'unicorn/consistent-destructuring': 2,
'unicorn/custom-error-definition': 2,
'unicorn/empty-brace-spaces': 2,
'unicorn/error-message': 2,
'unicorn/escape-case': 2,
'unicorn/expiring-todo-comments': [
2,
{
allowWarningComments: true,
ignore: [],
ignoreDatesOnPullRequests: true,
terms: ['@fixme', '@todo']
}
],
'unicorn/explicit-length-check': 0,
'unicorn/filename-case': [
2,
{
cases: { kebabCase: true },
ignore: []
}
],
'unicorn/import-index': 2,
'unicorn/import-style': [
2,
{
styles: {
shelljs: { default: true }
}
}
],
'unicorn/new-for-builtins': 2,
'unicorn/no-abusive-eslint-disable': 2,
'unicorn/no-array-callback-reference': 0,
'unicorn/no-array-for-each': 2,
'unicorn/no-array-method-this-argument': 2,
'unicorn/no-array-push-push': 2,
'unicorn/no-array-reduce': 0,
'unicorn/no-await-expression-member': 0,
'unicorn/no-console-spaces': 2,
'unicorn/no-empty-file': 2,
'unicorn/no-for-loop': 0,
'unicorn/no-hex-escape': 2,
'unicorn/no-instanceof-array': 2,
'unicorn/no-keyword-prefix': [
2,
{
checkProperties: false,
disallowedPrefixes: ['class', 'new'],
onlyCamelCase: true
}
],
'unicorn/no-lonely-if': 0,
'unicorn/no-nested-ternary': 0,
'unicorn/no-new-array': 2,
'unicorn/no-new-buffer': 2,
'unicorn/no-null': 0,
'unicorn/no-object-as-default-parameter': 2,
'unicorn/no-process-exit': 2,
'unicorn/no-static-only-class': 0,
'unicorn/no-thenable': 2,
'unicorn/no-this-assignment': 2,
'unicorn/no-unreadable-array-destructuring': 0,
'unicorn/no-unsafe-regex': 0,
'unicorn/no-unused-properties': 2,
'unicorn/no-useless-fallback-in-spread': 2,
'unicorn/no-useless-length-check': 2,
'unicorn/no-useless-promise-resolve-reject': 2,
'unicorn/no-useless-spread': 2,
'unicorn/no-useless-undefined': 2,
'unicorn/no-zero-fractions': 2,
'unicorn/number-literal-case': 0,
// https://github.com/sindresorhus/eslint-plugin-unicorn/issues/2003
'unicorn/numeric-separators-style': 0,
'unicorn/prefer-add-event-listener': 2,
'unicorn/prefer-array-find': 2,
'unicorn/prefer-array-flat': [2, { functions: [] }],
'unicorn/prefer-array-flat-map': 2,
'unicorn/prefer-array-index-of': 2,
'unicorn/prefer-array-some': 2,
'unicorn/prefer-at': 0,
'unicorn/prefer-code-point': 2,
'unicorn/prefer-date-now': 2,
'unicorn/prefer-default-parameters': 2,
'unicorn/prefer-export-from': [2, { ignoreUsedVariables: true }],
'unicorn/prefer-includes': 2,
'unicorn/prefer-json-parse-buffer': 0,
'unicorn/prefer-math-trunc': 2,
'unicorn/prefer-module': 2,
'unicorn/prefer-negative-index': 2,
'unicorn/prefer-node-protocol': 2,
'unicorn/prefer-number-properties': 2,
'unicorn/prefer-object-from-entries': [2, { functions: [] }],
'unicorn/prefer-optional-catch-binding': 2,
'unicorn/prefer-prototype-methods': 2,
'unicorn/prefer-reflect-apply': 2,
'unicorn/prefer-regexp-test': 0,
'unicorn/prefer-set-has': 2,
'unicorn/prefer-spread': 2,
'unicorn/prefer-string-replace-all': 0,
'unicorn/prefer-string-slice': 2,
'unicorn/prefer-string-starts-ends-with': 2,
'unicorn/prefer-string-trim-start-end': 2,
'unicorn/prefer-switch': 2,
'unicorn/prefer-ternary': 2,
'unicorn/prefer-top-level-await': 0,
'unicorn/prefer-type-error': 2,
'unicorn/prevent-abbreviations': 0,
'unicorn/relative-url-style': [2, 'never'],
'unicorn/require-array-join-separator': 2,
'unicorn/require-number-to-fixed-digits-argument': 2,
'unicorn/string-content': [2, { patterns: {} }],
'unicorn/template-indent': [2, { indent: 2 }],
'unicorn/text-encoding-identifier-case': 2,
'unicorn/throw-new-error': 2
}
},
{
files: '**/*.+(cts|mts|ts)',
rules: {
'@typescript-eslint/explicit-module-boundary-types': [
2,
{
allowArgumentsExplicitlyTypedAsAny: true,
allowDirectConstAssertionInArrowFunctions: true,
allowHigherOrderFunctions: false,
allowTypedFunctionExpressions: false,
allowedNames: []
}
],
'no-undef': 0
}
},
{
files: '**/*.d.+(cts|mts|ts)',
rules: {
'@typescript-eslint/ban-types': 0,
'@typescript-eslint/triple-slash-reference': 0,
'jsdoc/no-undefined-types': 0,
'jsdoc/require-file-overview': 0,
'no-var': 0,
'unicorn/filename-case': 0,
'unicorn/no-keyword-prefix': 0
}
},
{
files: '**/*.+(cjs|cts)',
rules: {
'@typescript-eslint/no-require-imports': 0,
'@typescript-eslint/no-var-requires': 0,
'unicorn/prefer-module': 0
}
},
{
files: '**/__mocks__/**/*.ts',
rules: {
'@typescript-eslint/no-unused-vars': 0,
'@typescript-eslint/require-await': 0
}
},
{
files: '**/__tests__/*.+(spec|spec-d).ts',
globals: {
afterAll: true,
afterEach: true,
assert: true,
beforeAll: true,
beforeEach: true,
chai: true,
describe: true,
expect: true,
faker: fs.existsSync('node_modules/@faker-js/faker'),
it: true,
pf: fs.existsSync('node_modules/pretty-format'),
suite: true,
test: true,
vi: true,
vitest: true
},
plugins: ['chai-expect', 'jest-formatting'],
rules: {
'@typescript-eslint/class-literal-property-style': 0,
'@typescript-eslint/consistent-indexed-object-style': 0,
'@typescript-eslint/no-base-to-string': 0,
'@typescript-eslint/no-empty-function': 0,
'@typescript-eslint/no-unused-expressions': 0,
'@typescript-eslint/prefer-as-const': 0,
'@typescript-eslint/prefer-ts-expect-error': 0,
'@typescript-eslint/require-await': 0,
'@typescript-eslint/restrict-template-expressions': 0,
'@typescript-eslint/unbound-method': 0,
'chai-expect/missing-assertion': 2,
'chai-expect/no-inner-compare': 2,
'chai-expect/no-inner-literal': 2,
'chai-expect/terminating-properties': [2, { properties: [] }],
'jest-formatting/padding-around-after-all-blocks': 1,
'jest-formatting/padding-around-after-each-blocks': 1,
'jest-formatting/padding-around-before-all-blocks': 1,
'jest-formatting/padding-around-before-each-blocks': 1,
'jest-formatting/padding-around-describe-blocks': 1,
'jest-formatting/padding-around-expect-groups': 1,
'jest-formatting/padding-around-test-blocks': 1,
'promise/prefer-await-to-callbacks': 0,
'promise/valid-params': 0,
'unicorn/consistent-destructuring': 0,
'unicorn/error-message': 0,
'unicorn/no-array-for-each': 0,
'unicorn/no-hex-escape': 0,
'unicorn/no-useless-undefined': 0,
'unicorn/prefer-at': 0,
'unicorn/prefer-dom-node-append': 0,
'unicorn/string-content': 0
}
},
{
files: '**/__tests__/*.spec-d.ts',
globals: {
assertType: true,
expectTypeOf: true
},
rules: {
'@typescript-eslint/ban-types': 0,
'@typescript-eslint/no-redundant-type-constituents': 0
}
},
{
files: ['**/decorators/*.constraint.ts', '**/*.decorator.ts'],
rules: {
'@typescript-eslint/ban-types': 0,
'@typescript-eslint/no-invalid-void-type': 0
}
},
{
files: ['**/enums/*.ts', '**/interfaces/*.ts', '**/types/*.ts'],
rules: {
'jsdoc/check-indentation': 0,
'unicorn/no-keyword-prefix': 0
}
},
{
extends: ['plugin:@graphql-eslint/operations-all'],
files: '**/*.gql',
rules: {
'@graphql-eslint/no-anonymous-operations': 0,
'@graphql-eslint/require-id-when-available': 0
}
},
{
files: '**/*.+(json|json5|jsonc)',
parser: 'jsonc-eslint-parser',
plugins: ['jsonc'],
rules: {
'jsonc/no-bigint-literals': 2,
'jsonc/no-binary-expression': 2,
'jsonc/no-binary-numeric-literals': 2,
'jsonc/no-escape-sequence-in-identifier': 2,
'jsonc/no-hexadecimal-numeric-literals': 2,
'jsonc/no-infinity': 2,
'jsonc/no-multi-str': 2,
'jsonc/no-nan': 2,
'jsonc/no-number-props': 2,
'jsonc/no-numeric-separators': 2,
'jsonc/no-octal': 0,
'jsonc/no-octal-escape': 2,
'jsonc/no-octal-numeric-literals': 2,
'jsonc/no-parenthesized': 2,
'jsonc/no-plus-sign': 2,
'jsonc/no-regexp-literals': 2,
'jsonc/no-sparse-arrays': 2,
'jsonc/no-template-literals': 2,
'jsonc/no-undefined-value': 2,
'jsonc/no-unicode-codepoint-escapes': 2,
'jsonc/no-useless-escape': 2,
'jsonc/sort-array-values': [
2,
{
order: { caseSensitive: true, type: 'asc' },
pathPattern: '^$'
}
],
'jsonc/sort-keys': [
2,
{
order: { caseSensitive: true, type: 'asc' },
pathPattern: '^$'
}
],
'jsonc/valid-json-number': 2,
'jsonc/vue-custom-block/no-parsing-error': 2
}
},
{
files: ['**/*.+(json5|jsonc)', 'tsconfig*.json'],
rules: {
'jsonc/no-comments': 0
}
},
{
files: ['**/package.json'],
rules: {
'jsonc/sort-keys': [
2,
{
order: [
'name',
'description',
'version',
'keywords',
'license',
'homepage',
'repository',
'bugs',
'author',
'contributors',
'publishConfig',
'type',
'files',
'exports',
'main',
'module',
'types',
'scripts',
'dependencies',
'devDependencies',
'optionalDependencies',
'peerDependencies',
'resolutions',
'engines',
'packageManager',
'readme'
],
pathPattern: '^$'
}
]
}
},
{
files: '**/*.md',
parser: 'eslint-plugin-markdownlint/parser',
plugins: ['markdown', 'markdownlint'],
processor: 'markdown/markdown'
},
{
files: '**/*.md/*.+(cjs|cts|js|jsx|mjs|mts|ts|tsx)',
parserOptions: { project: false },
rules: {
'@typescript-eslint/await-thenable': 0,
'@typescript-eslint/consistent-type-exports': 0,
'@typescript-eslint/dot-notation': 0,
'@typescript-eslint/naming-convention': 0,
'@typescript-eslint/no-base-to-string': 0,
'@typescript-eslint/no-confusing-void-expression': 0,
'@typescript-eslint/no-duplicate-type-constituents': 0,
'@typescript-eslint/no-floating-promises': 0,
'@typescript-eslint/no-for-in-array': 0,
'@typescript-eslint/no-implied-eval': 0,
'@typescript-eslint/no-meaningless-void-operator': 0,
'@typescript-eslint/no-misused-promises': 0,
'@typescript-eslint/no-mixed-enums': 0,
'@typescript-eslint/no-redundant-type-constituents': 0,
'@typescript-eslint/no-throw-literal': 0,
'@typescript-eslint/no-unnecessary-boolean-literal-compare': 0,
'@typescript-eslint/no-unnecessary-condition': 0,
'@typescript-eslint/no-unnecessary-qualifier': 0,
'@typescript-eslint/no-unnecessary-type-arguments': 0,
'@typescript-eslint/no-unnecessary-type-assertion': 0,
'@typescript-eslint/no-unsafe-argument': 0,
'@typescript-eslint/no-unsafe-assignment': 0,
'@typescript-eslint/no-unsafe-call': 0,
'@typescript-eslint/no-unsafe-enum-comparison': 0,
'@typescript-eslint/no-unsafe-member-access': 0,
'@typescript-eslint/no-unsafe-return': 0,
'@typescript-eslint/no-unused-expressions': 0,
'@typescript-eslint/non-nullable-type-assertion-style': 0,
'@typescript-eslint/prefer-includes': 0,
'@typescript-eslint/prefer-nullish-coalescing': 0,
'@typescript-eslint/prefer-optional-chain': 0,
'@typescript-eslint/prefer-readonly': 0,
'@typescript-eslint/prefer-readonly-parameter-types': 0,
'@typescript-eslint/prefer-reduce-type-parameter': 0,
'@typescript-eslint/prefer-regexp-exec': 0,
'@typescript-eslint/prefer-return-this-type': 0,
'@typescript-eslint/prefer-string-starts-ends-with': 0,
'@typescript-eslint/promise-function-async': 0,
'@typescript-eslint/require-array-sort-compare': 0,
'@typescript-eslint/require-await': 0,
'@typescript-eslint/restrict-plus-operands': 0,
'@typescript-eslint/restrict-template-expressions': 0,
'@typescript-eslint/return-await': 0,
'@typescript-eslint/strict-boolean-expressions': 0,
'@typescript-eslint/switch-exhaustiveness-check': 0,
'@typescript-eslint/unbound-method': 0,
'jsdoc/require-file-overview': 0,
'unicorn/filename-case': 0
}
},
{
files: '**/*.+(yaml|yml)',
parser: 'yaml-eslint-parser',
plugins: ['yml'],
rules: {
'prettier/prettier': 0,
'yml/block-mapping': 2,
'yml/block-mapping-question-indicator-newline': [2, 'never'],
'yml/block-sequence': 2,
'yml/block-sequence-hyphen-indicator-newline': [2, 'never'],
'yml/flow-mapping-curly-newline': 2,
'yml/flow-mapping-curly-spacing': [
2,
'always',
{
arraysInObjects: false,
objectsInObjects: false
}
],
'yml/flow-sequence-bracket-newline': 2,
'yml/flow-sequence-bracket-spacing': 2,
'yml/indent': [2, 2],
'yml/key-name-casing': [
2,
{
SCREAMING_SNAKE_CASE: true,
camelCase: false,
'kebab-case': true,
snake_case: true
}
],
'yml/key-spacing': 2,
'yml/no-empty-document': 0,
'yml/no-empty-key': 2,
'yml/no-empty-mapping-value': 0,
'yml/no-empty-sequence-entry': 2,
'yml/no-irregular-whitespace': 2,
'yml/no-multiple-empty-lines': [2, { max: 2, maxBOF: 0, maxEOF: 1 }],
'yml/no-tab-indent': 2,
'yml/plain-scalar': [2, 'always'],
'yml/quotes': [2, { avoidEscape: true, prefer: 'single' }],
'yml/require-string-key': 2,
'yml/sort-keys': [
2,
{
order: { caseSensitive: true, type: 'asc' },
pathPattern: '^$'
}
],
'yml/sort-sequence-values': [
2,
{
order: { caseSensitive: true, type: 'asc' },
pathPattern: '^$'
}
],
'yml/spaced-comment': 2
}
},
{
files: '.eslintrc.*',
rules: {
'@typescript-eslint/no-unsafe-member-access': 0,
'unicorn/string-content': 0
}
},
{
files: '.github/ISSUE_TEMPLATE/*.yml',
rules: {
'yml/sort-keys': [
2,
{
order: [
'name',
'description',
'title',
'assignees',
'labels',
'body'
],
pathPattern: '^$'
}
]
}
},
{
files: '.github/ISSUE_TEMPLATE/config.yml',
rules: {
'yml/sort-keys': [
2,
{
order: { caseSensitive: true, type: 'asc' },
pathPattern: '^$'
}
]
}
},
{
files: [
'.github/dependabot.yml',
'.github/workflows/*.yml',
'action.yml',
'docker*.yml'
],
rules: {
'yml/sort-keys': 0
}
},
{
files: ['.github/workflows/*.yml', '.yarnrc.yml', 'docker*.yml'],
rules: {
'yml/key-name-casing': 0
}
},
{
files: ['.vscode/launch.json'],
rules: {
'jsonc/sort-keys': 0
}
}
],
plugins: ['prettier'],
reportUnusedDisableDirectives: true,
rules: {
'prettier/prettier': [2, {}, { usePrettierrc: true }]
},
settings: {
'import/parsers': {
'@typescript-eslint/parser': ['.cts', '.mts', '.ts', '.tsx']
},
'import/resolver': {
node: true,
typescript: true
},
jsdoc: {
augmentsExtendsReplacesDocs: true,
ignoreInternal: false,
ignorePrivate: false,
implementsReplacesDocs: true,
overrideReplacesDocs: true,
preferredTypes: {
'*': false,
'.<>': false,
'Array<>': { replacement: '[]' },
Object: { replacement: 'object' },
'Object<>': { replacement: 'Record<>' },
object: 'object'
},
structuredTags: {
const: {
name: 'namepath-defining',
required: ['name']
},
decorator: {
name: 'none'
},
enum: {
name: 'namepath-defining',
required: ['name', 'type']
},
experimental: {
name: 'none'
},
extends: {
name: 'namepath-defining',
required: ['type']
},
implements: {
name: 'namepath-defining',
required: ['type']
},
maximum: {
name: 'text',
required: ['name']
},
member: {
name: 'namepath-defining',
required: ['name', 'type']
},
minimum: {
name: 'text',
required: ['name']
},
next: {
name: 'namepath-defining',
required: ['type']
},
packageManager: {
name: 'text',
required: ['name']
},
param: {
name: 'namepath-defining',
required: ['name', 'type']
},
return: {
name: 'namepath-defining',
required: ['type']
},
throws: {
name: 'namepath-defining',
required: ['type']
},
type: {
name: 'namepath-defining',
required: ['type']
},
var: {
name: 'namepath-defining',
required: ['name']
},
visibleName: {
required: ['name']
},
yield: {
name: 'namepath-defining',
required: ['type']
}
},
tagNamePreference: {
augments: 'extends',
constant: 'const',
fileoverview: 'file',
member: 'member',
returns: 'return',
var: 'var',
yields: 'yield'
}
}
}
}
module.exports = config
# GITATTRIBUTES
# https://github.com/alexkaratarakis/gitattributes/blob/master/Web.gitattributes
#
# details per file setting:
# binary: these files are binary and should be left untouched
# text: these files should be normalized (i.e. convert crlf to lf)
#
# note that binary is a macro for -text -diff.
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# AUTO DETECT
# handle line endings automatically for files detected as text and leave files
# detected as binary untouched. this will catch all files not defined below.
# ------------------------------------------------------------------------------
* text=auto
# ------------------------------------------------------------------------------
# TEXT FILE ATTRIBUTES
# ------------------------------------------------------------------------------
*.cjs text eol=lf
*.cts text eol=lf
*.gql text eol=lf
*.hbs text eol=lf
*.js text eol=lf
*.json text eol=lf
*.json5 text eol=lf
*.jsonc text eol=lf
*.jsx text eol=lf
*.md text eol=lf diff=markdown
*.mjs text eol=lf
*.mts text eol=lf
*.sh text eol=lf
*.snap text eol=lf
*.ts text eol=lf
*.tsx text eol=lf
*.txt text eol=lf
*.yml text eol=lf
# configuration files
# ------------------------------------------------------------------------------
*.*ignore text eol=lf
*.*rc text eol=lf
.editorconfig text eol=lf
.env* text eol=lf
.gitattributes text eol=lf
.gitconfig text eol=lf
package.json text eol=lf
yarn.lock text -diff
# GIT CONFIGURATION
# http://michaelwales.com/articles/make-gitconfig-work-for-you
# aliases - basic helpers
[alias]
# add and commit
ac = "!f() { git add . && git cm \"$@\"; }; f"
# add new remote
ar = "!f() { git remote add \"$0\" \"$1\"; }; f"
# delete branch locally
bdel = "!f() { git branch -d $@; }; f"
# delete branch remotely
bdelr = "!f() { git push origin --no-verify --delete $@; }; f"
# rename branch
bren = "!f() { git branch -m $@ && git pou $@; }; f"
# checkout and push new branch to origin
chb = "!f() { git checkout -b \"$@\" && git pou \"$@\"; }; f"
# checkout branch and pull latest version
chp = "!f() { git checkout $@ && git pull; }; f"
# commit with message
cm = "!f() { git commit -s -m \"$@\"; }; f"
# tell git to start tracking branch and push to origin
pou = "!f() { git push origin --no-verify -u $@; }; f"
# remove local .git directory
restart = "!f() { rm -rf .git; echo \"removed .git directory.\"; }; f"
# create new local repo and perform initial commit
setup = "!f() { git init && git config branch.autosetuprebase always && git config core.ignorecase false && git config pull.rebase true && git config rebase.autoStash true && git ac \"initial commit\"; }; f"
# undo last commit
ulc = "!f() { git reset head~1 --soft; }; f"
# aliases - branch naming conventions
[alias]
# create new feature branch and push upstream
chbfeat = "!f() { git chb feat/$@; }; f"
# create new bugfix (feature) branch and push upstream
chbfix = "!f() { git chb feat/fix/$@; }; f"
# create new hotfix branch and push upstream
chbhotfix = "!f() { git chb hotfix/$@; }; f"
# create new release branch and push upstream
chbrelease = "!f() { git chb release/$@; }; f"
# aliases - husky
[alias]
# force push commits without running `pre-push` hook
fpnv = "!f() { git pnv --force ; }; f"
# push commits without running `pre-push` hook
pnv = "!f() { git push --no-verify $@; }; f"
[branch]
autosetuprebase = always
[checkout]
defaultRemote = origin
[color]
ui = true
[commit]
gpgsign = true
[core]
autocrlf = false
editor = code-insiders --wait
ignorecase = false
safecrlf = false
[diff]
tool = vscode
[difftool "vscode"]
cmd = code-insiders --wait --diff $LOCAL $REMOTE
[gitflow "prefix"]
feature = feat/
hotfix = hotfix/
release = release/
support = feat/fix/
[gpg]
program = gpg2
[init]
defaultBranch = main
[merge]
tool = vscode
[mergetool "vscode"]
cmd = code-insiders --wait --merge $REMOTE $LOCAL $BASE $MERGED
[pull]
rebase = true
[rebase]
autoStash = true
[tag]
forceSignAnnotated = true
[url "git@github.com:"]
insteadOf = gh:
[url "https://gist.github.com/"]
insteadOf = gist:
# macOS
# ------------------------------------------------------------------------------
**/._*
**/.DS_*
# Node
# ------------------------------------------------------------------------------
**/node_modules/
*.log
*.pid
*.pid.lock
*.seed
*.tgz
*.zip
.npm
.yarn-integrity
logs
npm-debug.log*
pids
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
yarn-debug.log*
yarn-error.log*
!**/__fixtures__/**/node_modules/
# Yarn 2 (Not using Zero-Installs)
# https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored
# ------------------------------------------------------------------------------
.pnp.*
.yarn/*
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
# Environment
# ------------------------------------------------------------------------------
.env*
.flaskenv*
!.env.example
!.env.project
!.env.vault
!.env.zsh
# Sensitive Data
# ------------------------------------------------------------------------------
.lego/
.service-accounts/
acme.json
# TypeScript
# ------------------------------------------------------------------------------
*.tsbuildinfo
# Testing
# ------------------------------------------------------------------------------
**/*config.*.timestamp*
**/__tests__/report.json
**/coverage/
**/tsconfig*temp.json
*.lcov
codecov
# ESLint
# ------------------------------------------------------------------------------
**/.eslintcache
# Builds
# ------------------------------------------------------------------------------
**/.temp/
**/dist/
!**/__fixtures__/**/*.log
!**/__fixtures__/**/dist/
!**/typings/**/dist/
# Releases
# ------------------------------------------------------------------------------
**/RELEASE_NOTES.md
# Vercel
# ------------------------------------------------------------------------------
**/.vercel/
# Misc
# ------------------------------------------------------------------------------
**/scratch.*
{
"*": ["prettier --check", "cspell lint --color --no-progress --relative $@"],
"**/*.{cjs,cts,gql,js,json,json5,jsonc,md,mjs,mts,ts,yml}": [
"eslint --exit-on-fatal-error"
],
"**/*.{cts,mts,ts}": "vitest typecheck --changed --run",
"**/yarn.lock": "yarn dedupe --check",
"src/**/*.ts": [
"vitest --changed --coverage --run",
"yarn build",
"yarn check:types:build"
]
}
// MARKDOWNLINT CONFIGURATION
// https://github.com/DavidAnson/vscode-markdownlint#configure
{
"MD001": true,
"MD002": {
"level": 1
},
"MD003": {
"style": "consistent"
},
"MD004": {
"style": "consistent"
},
"MD005": true,
"MD006": true,
"MD007": {
"indent": 2,
"start_indent": 2,
"start_indented": false
},
"MD009": {
"br_spaces": 2,
"list_item_empty_lines": false
},
"MD010": {
"code_blocks": true,
"spaces_per_tab": 1
},
"MD011": true,
"MD012": {
"maximum": 1
},
"MD013": {
"code_block_line_length": 120,
"code_blocks": true,
"headers": true,
"heading_line_length": 120,
"headings": true,
"line_length": 120,
"stern": false,
"strict": false,
"tables": false
},
"MD014": true,
"MD018": true,
"MD019": true,
"MD020": true,
"MD021": true,
"MD022": {
"lines_above": 1,
"lines_below": 1
},
"MD023": true,
"MD024": {
"siblings_only": true
},
"MD025": {
"front_matter_title": "^\\s*title\\s*[:=]",
"level": 1
},
"MD026": {
"punctuation": ".,;!。,;:!"
},
"MD027": true,
"MD028": true,
"MD029": {
"style": "one_or_ordered"
},
"MD030": {
"ol_multi": 1,
"ol_single": 1,
"ul_multi": 1,
"ul_single": 1
},
"MD031": {
"list_items": true
},
"MD032": true,
"MD033": {
"allowed_elements": [
"a",
"blockquote",
"br",
"code",
"div",
"figcaption",
"figure",
"img",
"script",
"small",
"style",
"sub",
"sup"
]
},
"MD034": true,
"MD035": {
"style": "consistent"
},
"MD036": {
"punctuation": ".,;!?。,;:!?"
},
"MD037": true,
"MD038": false,
"MD039": true,
"MD040": true,
"MD041": {
"front_matter_title": "^\\s*title\\s*[:=]",
"level": 1
},
"MD042": true,
"MD043": true,
"MD044": {
"code_blocks": true,
"names": []
},
"MD045": true,
"MD046": {
"style": "fenced"
},
"MD047": true,
"MD048": {
"style": "backtick"
},
"MD049": {
"style": "asterisk"
},
"MD050": {
"style": "asterisk"
},
"default": true,
"extends": null
}
# MARKDOWNLINT IGNORE
# https://github.com/DavidAnson/vscode-markdownlint#markdownlintignore
# DIRECTORIES & FILES
**/CHANGELOG.md
**/LICENSE.md
**/RELEASE_NOTES.md
# PRETTIER IGNORE
# https://prettier.io/docs/en/ignore.html
# DIRECTORIES & FILES
**/*.md
**/*.patch
**/*.snap
**/*config.*.timestamp*
**/.temp/
**/.vercel/
**/__tests__/report.json
**/coverage/
**/dist/
**/node_modules/
**/tsconfig*temp.json
.husky/_/
.yarn/
yarn.lock
# NEGATIONS
!**/__fixtures__/**/dist/
!**/__fixtures__/**/node_modules/
!**/typings/**/dist/
{
"arrowParens": "avoid",
"bracketSpacing": true,
"overrides": [
{
"files": [
"**/*.sh",
"**/*.txt",
"**/.*ignore",
"**/.*rc",
"**/.env*",
"**/.eslintcache",
"**/.git*",
".editorconfig",
".husky/commit-msg",
".husky/pre-commit",
".husky/pre-push",
"Brewfile"
],
"options": {
"functionNextLine": false,
"keepComments": true,
"keepPadding": false,
"parser": "sh",
"spaceRedirects": false,
"switchCaseIndent": true,
"variant": 0
}
}
],
"plugins": ["prettier-plugin-sh"],
"proseWrap": "always",
"quoteProps": "as-needed",
"semi": false,
"trailingComma": "none"
}
# https://yarnpkg.com/configuration/yarnrc
changesetBaseRefs:
- main
- origin/main
changesetIgnorePatterns:
- '!{docs,src/**,{CHANGELOG,LICENSE,README}.md,package.json}'
- '**/*.snap'
- '**/*.spec.ts'
- '**/*.spec-d.ts'
defaultSemverRangePrefix: ''
enableInlineBuilds: true
enableTransparentWorkspaces: false
nmHoistingLimits: dependencies
nodeLinker: node-modules
npmScopes:
flex-development:
npmAlwaysAuth: true
npmAuthToken: ${GITHUB_TOKEN:-$PAT_BOT}
npmRegistryServer: https://npm.pkg.github.com
patchFolder: ./patches
yarnPath: .yarn/releases/yarn-4.0.0-rc.39.cjs
/**
* @file Configuration - Changelog
* @module config/changelog
* @see https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-conventionalcommits
* @see https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-core
* @see https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-writer
* @see https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-commits-parser
* @see https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/git-raw-commits
*/
import {
Type,
parserPreset,
type Commit
} from '@flex-development/commitlint-config'
import pathe from '@flex-development/pathe'
import { CompareResult, isNIL } from '@flex-development/tutils'
import addStream from 'add-stream'
import conventionalChangelog from 'conventional-changelog'
import type { Options } from 'conventional-changelog-core'
import type {
CommitGroup,
GeneratedContext
} from 'conventional-changelog-writer'
import dateformat from 'dateformat'
import type mri from 'mri'
import {
createReadStream,
createWriteStream,
readFileSync,
type ReadStream,
type WriteStream
} from 'node:fs'
import type { Readable } from 'node:stream'
import sade from 'sade'
import semver from 'semver'
import tempfile from 'tempfile'
import pkg from '../package.json' assert { type: 'json' }
/**
* Parsed commit with additional fields.
*
* @extends {Commit}
*/
interface CommitEnhanced extends Commit {
raw: Commit
version?: string
}
/**
* CLI flags.
*/
interface Flags {
/**
* Output unreleased changelog.
*
* To the set the current version, pass a string value.
*
* @example
* '1.0.0-alpha.7'
* @example
* true
*
* @default true
*/
'output-unreleased'?: boolean | string
/**
* Number of releases to be generated.
*
* If `0`, the changelog will be regenerated and the output file will be
* overwritten.
*
* @default 1
*/
'release-count'?: number
/**
* Output content to {@linkcode infile}.
*
* @default false
*/
'same-file'?: boolean
/**
* Enable verbose output.
*
* @default false
*/
debug?: boolean
/**
* Read CHANGELOG from this file.
*/
infile?: string
/**
* Write content to this file.
*
* **Note**: Requires {@linkcode write} to be `true`.
*/
outfile?: string
/**
* Write content to file instead of {@linkcode process.stdout}.
*
* @default false
*/
write?: boolean
}
sade('changelog', true)
.option('-d, --debug', 'Enable verbose output', false)
.option('-i, --infile', 'Read CHANGELOG from this file')
.option('-o, --outfile', 'Write content to this file (requires --write)')
.option('-r, --release-count', 'Number of releases to be generated', 1)
.option('-s, --same-file', 'Output content to infile', false)
.option('-u, --output-unreleased', 'Output unreleased changelog')
.option('-w, --write', 'Write content to file', false)
.action((flags: mri.Argv<Flags>): void => {
const {
'release-count': releaseCount = 1,
debug = false,
write = false
} = flags
let {
'output-unreleased': outputUnreleased = true,
infile,
'same-file': samefile,
outfile
} = flags
// convert possible 'false' or 'true' to boolean value
if (outputUnreleased === 'false' || outputUnreleased === 'true') {
outputUnreleased = JSON.parse(outputUnreleased)
}
/**
* Regex used to extract a release version from a string containing
* Git tags.
*
* @const {RegExp} vgx
*/
const vgx: RegExp = pkg.tagPrefix
? new RegExp(`tag:\\s*[=]?${pkg.tagPrefix}(.+?)[,)]`, 'gi')
: /tag:\s*[=v]?(.+?)[),]/gi
/**
* Changelog content stream.
*
* @const {Readable} changelog
*/
// @ts-expect-error type definitions are incorrect
const changelog: Readable = conventionalChangelog<Commit>(
{
append: false,
debug: debug ? console.debug.bind(console) : undefined,
outputUnreleased:
typeof outputUnreleased === 'boolean'
? outputUnreleased
: typeof outputUnreleased === 'string'
? !!outputUnreleased.trim()
: false,
pkg: { path: pathe.resolve('package.json') },
preset: {
header: '',
name: parserPreset.name,
releaseCommitMessageFormat: 'release: {{currentTag}}',
types: [
{ section: ':package: Build', type: Type.BUILD },
{ section: ':house_with_garden: Housekeeping', type: Type.CHORE },
{ section: ':robot: Continuous Integration', type: Type.CI },
{ section: ':pencil: Documentation', type: Type.DOCS },
{ section: ':sparkles: Features', type: Type.FEAT },
{ section: ':bug: Fixes', type: Type.FIX },
{ section: ':fire: Performance Improvements', type: Type.PERF },
{ section: ':mechanical_arm: Refactors', type: Type.REFACTOR },
{ hidden: true, type: Type.RELEASE },
{ section: ':wastebasket: Reverts', type: Type.REVERT },
{ hidden: true, type: Type.STYLE },
{ section: ':white_check_mark: Testing', type: Type.TEST },
{ hidden: true, type: Type.WIP }
]
},
releaseCount,
skipUnstable: false,
tagPrefix: pkg.tagPrefix,
/**
* Raw commit transformer.
*
* @see https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-core#transform-1
* @see https://github.com/conventional-changelog/conventional-changelog/issues/415
*
* @param {Commit} commit - Commit object
* @param {Options.Transform.Callback} apply - Commit handler
* @return {void} Nothing when complete
*/
transform(commit: Commit, apply: Options.Transform.Callback): void {
return void apply(null, {
...commit,
committerDate: dateformat(commit.committerDate, 'yyyy-mm-dd', true),
mentions: commit.mentions.filter(m => m !== 'flexdevelopment'),
// @ts-expect-error ts(2322)
raw: commit,
references: commit.references.filter(ref => ref.action !== null),
version: commit.gitTags ? vgx.exec(commit.gitTags)?.[1] : undefined
})
},
warn: parserPreset.parserOpts.warn
},
{},
{
debug: debug ? parserPreset.parserOpts.warn : undefined,
format:
'%B%n-hash-%n%H%n-shortHash-%n%h%n-gitTags-%n%d%n-committerDate-%n%ci%n'
},
parserPreset.parserOpts,
{
/**
* Sorts commit groups in descending order by group title.
*
* GitHub emojis in titles will be ignored.
*
* @see https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-writer#commitgroupssort
*
* @param {CommitGroup} a - Commit group object
* @param {CommitGroup} b - Commit group object to compare to `a`
* @return {number} Compare result
*/
commitGroupsSort(a: CommitGroup, b: CommitGroup): number {
if (a.title === false) return CompareResult.GREATER_THAN
if (b.title === false) return CompareResult.LESS_THAN
/**
* Regex used to extract commit group titles without GitHub emojis.
*
* @const {RegExp} tgx - Regex used to extract commit group title
*/
const tgx: RegExp = /([A-Z])\w+/
return tgx.exec(a.title)![0]!.localeCompare(tgx.exec(b.title)![0]!)
},
/**
* Sorts commits in descending order by commit header and date.
*
* @see https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-writer#commitssort
*
* @param {Commit} a - Commit object
* @param {Commit} b - Commit object to compare to `b`
* @return {number} Compare result
*/
commitsSort(a: Commit, b: Commit): number {
/**
* Compare result for {@linkcode b.committerDate} and
* {@linkcode a.committerDate}.
*
* @const {number} by_date
*/
const by_date: number =
new Date(b.committerDate).getTime() -
new Date(a.committerDate).getTime()
return a.header && b.header
? a.header.localeCompare(b.header) || by_date
: by_date
},
/**
* Modifies `context` before the changelog is generated.
*
* This includes:
*
* - Setting the current and previous release tags
* - Setting the release date
* - Determining patch release state
* - Determining if compare links should be generated
*
* @see https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-core#finalizecontext
* @see https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-writer#finalizecontext
*
* @param {GeneratedContext} context - Generated changelog context
* @param {Options} options - `conventional-changelog-core` options
* @param {CommitEnhanced[]} commits - Commits for release
* @param {CommitEnhanced?} key - Release commit
* @return {GeneratedContext} Final changelog context
*/
finalizeContext(
context: GeneratedContext,
options: Options,
commits: CommitEnhanced[],
key?: CommitEnhanced
): GeneratedContext {
const { gitSemverTags = [], isPatch, linkCompare, version } = context
let { currentTag, previousTag } = context
/**
* First commit in release.
*
* @const {CommitEnhanced?} first_commit
*/
const first_commit: CommitEnhanced | undefined = commits.at(0)
/**
* Last commit in release.
*
* @const {CommitEnhanced?} last_commit
*/
const last_commit: CommitEnhanced | undefined = commits.at(-1)
// set current and previous tags
if (key && (!currentTag || !previousTag)) {
currentTag = key.version ?? undefined
// try setting previous tag based on current tag
if (gitSemverTags.includes(currentTag ?? '')) {
const { version = '' } = key
previousTag = gitSemverTags[gitSemverTags.indexOf(version) + 1]
if (!previousTag) previousTag = last_commit?.hash ?? undefined
}
} else {
currentTag = /^unreleased$/i.test(version ?? '')
? currentTag ??
(typeof outputUnreleased === 'string' && outputUnreleased
? outputUnreleased
: first_commit?.hash ?? undefined)
: !currentTag && version
? pkg.tagPrefix + version
: currentTag ?? version
previousTag = previousTag ?? gitSemverTags[0]
}
// set release date
context.date =
key?.committerDate ??
dateformat(new Date().toLocaleDateString(), 'yyyy-mm-dd', true)
// determine patch release state
if (version && semver.valid(version)) {
context.isPatch = isPatch ?? semver.patch(version) !== 0
}
// @ts-expect-error ts(2322)
return {
...context,
currentTag,
linkCompare: isNIL(linkCompare) && !!currentTag && !!previousTag,
previousTag,
repoUrl: pkg.repository.slice(0, -4)
}
},
headerPartial: readFileSync(
'config/templates/changelog/header.hbs',
'utf8'
),
ignoreReverted: false
}
).on('error', err => console.error(err.stack))
// override samefile if infile and outfile are the same file
if (infile && infile === outfile) samefile = true
// reset outfile if changelog should be output to infile
if (samefile) outfile = infile = infile ?? 'CHANGELOG.md'
/**
* Creates a file writer.
*
* If writing to file is disabled, {@linkcode process.stdout} will be used
* to write content to the terminal.
*
* Otherwise, {@linkcode createWriteStream} will be used to create a stream
* for {@linkcode outfile}.
*
* @see {@linkcode NodeJS.WriteStream}
* @see {@linkcode WriteStream}
* @see {@linkcode createWriteStream}
*
* @return {WriteStream | (NodeJS.WriteStream & { fd: 1 })} File writer
*/
const writer = (): WriteStream | (NodeJS.WriteStream & { fd: 1 }) => {
return write && outfile ? createWriteStream(outfile) : process.stdout
}
// write changelog to infile, outfile, or stdout
switch (true) {
case infile && releaseCount !== 0:
/**
* Changelog file stream.
*
* @const {ReadStream} rs
*/
const rs: ReadStream = createReadStream(infile!).on('error', () => {
if (debug) console.error('infile does not exist.')
if (samefile) changelog.pipe(writer())
})
// write changelog to infile or stdout
if (samefile) {
/**
* Absolute path to random temporary file.
*
* @const {string} tmp
*/
const tmp: string = tempfile()
changelog
.pipe(addStream(rs))
.pipe(createWriteStream(tmp))
.on('finish', () => createReadStream(tmp).pipe(writer()))
} else {
changelog.pipe(addStream(rs)).pipe(writer())
}
break
default:
changelog.pipe(writer())
break
}
return void 0
})
.parse(process.argv)
# Dependabot Configuration
#
# References:
#
# - https://docs.github.com/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/configuration-options-for-dependency-updates
---
version: 2
registries:
github:
token: ${{ secrets.PAT_BOT }}
type: npm-registry
url: https://npm.pkg.github.com
updates:
- package-ecosystem: github-actions
commit-message:
prefix: ci
include: scope
directory: /
labels:
- scope:dependencies
- type:ci
reviewers:
- flex-development/dependabot-review
- flexdevelopment
schedule:
interval: daily
- package-ecosystem: npm
commit-message:
prefix: build
include: scope
directory: /
labels:
- scope:dependencies
- type:build
registries:
- github
reviewers:
- flex-development/dependabot-review
- flexdevelopment
schedule:
interval: daily
# FUNDING PLATFORMS
#
# https://docs.github.com/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/displaying-a-sponsor-button-in-your-repository
---
github:
- flex-development
## {{#if @root.linkCompare~}}
[{{currentTag}}]({{host}}/{{owner}}/{{repository}}/compare/{{previousTag}}...{{currentTag}})
{{~else}}
{{~currentTag}}
{{~/if}}
{{~#if title}} "{{title}}"{{~/if}}
{{~#if date}} ({{date}}){{/if}}
# REPOSITORY LABELS
# flag labels
# indicate additional work is needed
- name: flag:breaking-change
description: contains changes that require major version bump
color: fbca04
- name: flag:duplicate
description: issue, pull request, or discussion already exists
color: fbca04
- name: flag:needs-discussion
description: discussion required before implementation
color: fbca04
- name: flag:needs-docs
description: missing documentation or needs existing documentation update
color: fbca04
- name: flag:needs-refactor
description: code improvements required before being merged
color: fbca04
# scope labels
# project-specific groups for issues, pull requests, and discussions
- name: scope:dependencies
description: dependency updates
color: 74cefc
- name: scope:esm
description: es modules
color: 74cefc
- name: scope:install
description: package install
color: 74cefc
- name: scope:release
description: package release
color: 74cefc
- name: scope:tests
description: testing
color: 74cefc
- name: scope:ts
description: typescript support
color: 74cefc
# status labels
# current state of an issue, pull request, or discussion
- name: status:awaiting-answers
description: needs clarification or more information from author
color: e7034b
- name: status:blocked
description: blocked by other work tracked in different issue
color: e7034b
- name: status:cannot-reproduce
description: bug report cannot be reproduced
color: e7034b
- name: status:fixed
description: fixed, but not released
color: e7034b
- name: status:help-wanted
description: extra attention is needed
color: e7034b
- name: status:icebox
description: changes that won't be implemented
color: e7034b
- name: status:invalid
description: no action to be taken or missing information
color: e7034b
- name: status:merged
description: merged, but not released
color: e7034b
- name: status:needs-triage
description: needs further assessment
color: e7034b
- name: status:prereleased
description: merged and prereleased
color: e7034b
- name: status:released
description: merged and released
color: e7034b
- name: status:stale
description: superseded by different issue, pull request, or discussion
color: e7034b
- name: status:triaged
description: bug confirmed
color: e7034b
- name: status:wip
description: work in progress
color: e7034b
# type labels
# types of issues, pull requests, and discussions
- name: type:build
description: changes to the build system or external dependencies
color: 0052cc
- name: type:chore
description: housekeeping tasks / changes that don't impact external users
color: 0052cc
- name: type:ci
description: ci/cd configuration
color: 0052cc
- name: type:docs
description: documentation improvements
color: 0052cc
- name: type:feat
description: new features and improvements
color: 0052cc
- name: type:fix
description: bug reports and fixes
color: 0052cc
- name: type:perf
description: performance updates
color: 0052cc
- name: type:question
description: questions
color: 0052cc
- name: type:refactor
description: code improvements
color: 0052cc
- name: type:task
description: project tasks
color: 0052cc
/**
* @file Custom Loader Hooks
* @module loader
* @see https://nodejs.org/api/esm.html#loaders
*/
import { DECORATOR_REGEX } from '@flex-development/decorator-regex'
import * as esm from '@flex-development/esm-types'
import * as mlly from '@flex-development/mlly'
import * as pathe from '@flex-development/pathe'
import * as tscu from '@flex-development/tsconfig-utils'
import * as tutils from '@flex-development/tutils'
import * as esbuild from 'esbuild'
import { URL, fileURLToPath, pathToFileURL } from 'node:url'
import ts from 'typescript'
// add support for extensionless files in "bin" scripts
// https://github.com/nodejs/modules/issues/488
mlly.EXTENSION_FORMAT_MAP.set('', mlly.Format.COMMONJS)
/**
* {@linkcode URL} of tsconfig file.
*
* @type {URL}
* @const tsconfig
*/
const tsconfig = mlly.toURL('tsconfig.json')
/**
* TypeScript compiler options.
*
* @type {tscu.CompilerOptions}
* @const compilerOptions
*/
const compilerOptions = tscu.loadCompilerOptions(tsconfig)
/**
* Determines how the given module `url` should be interpreted, retrieved, and
* parsed.
*
* @see {@linkcode esm.LoadHookContext}
* @see {@linkcode esm.LoadHookResult}
* @see {@linkcode esm.LoadHook}
* @see {@linkcode esm.ResolvedModuleUrl}
* @see https://nodejs.org/api/esm.html#loadurl-context-nextload
*
* @async
*
* @param {esm.ResolvedModuleUrl} url - Resolved module URL
* @param {esm.LoadHookContext} context - Hook context
* @return {Promise<esm.LoadHookResult>} Hook result
*/
export const load = async (url, context) => {
// get module format
context.format = context.format ?? (await mlly.getFormat(url))
// validate import assertions
mlly.validateAssertions(url, context.format, context.importAssertions)
/**
* File extension of {@linkcode url}.
*
* @type {pathe.Ext | tutils.EmptyString}
* @const ext
*/
const ext = pathe.extname(url)
/**
* Module source code.
*
* @type {esm.Source<Uint8Array | string> | undefined}
* @var source
*/
let source = await mlly.getSource(url, { format: context.format })
// transform typescript files
if (/^\.(?:cts|mts|tsx?)$/.test(ext) && !/\.d\.(?:cts|mts|ts)$/.test(url)) {
// push require condition for .cts files and update format
if (ext === '.cts') {
context.conditions = context.conditions ?? []
context.conditions.unshift('require', 'node')
context.format = mlly.Format.MODULE
}
// resolve path aliases
source = await tscu.resolvePaths(source, {
conditions: context.conditions,
ext: '',
parent: url,
tsconfig
})
// resolve modules
source = await mlly.resolveModules(source, {
conditions: context.conditions,
parent: url
})
// emit decorator metadata
if (DECORATOR_REGEX.test(source)) {
const { outputText } = ts.transpileModule(source, {
compilerOptions: { ...compilerOptions, sourceMap: false },
fileName: url
})
source = outputText
}
// transpile source code
const { code } = await esbuild.transform(source, {
format: 'esm',
loader: ext.slice(/^\.[cm]/.test(ext) ? 2 : 1),
minify: false,
sourcefile: fileURLToPath(url),
sourcemap: 'inline',
target: `node${process.versions.node}`,
tsconfigRaw: { compilerOptions }
})
// set source code to transpiled source
source = code
}
return { format: context.format, shortCircuit: true, source }
}
/**
* Resolves the given module `specifier`, and its module format as a hint to the
* {@linkcode load} hook.
*
* Adds supports for:
*
* - Path alias resolution
* - Extensionless file and directory index resolution
*
* @see {@linkcode esm.ResolveHookContext}
* @see {@linkcode esm.ResolveHookResult}
* @see {@linkcode esm.ResolveHook}
* @see https://nodejs.org/api/esm.html#resolvespecifier-context-nextresolve
*
* @async
*
* @param {string} specifier - Module specifier
* @param {esm.ResolveHookContext} context - Hook context
* @return {Promise<esm.ResolveHookResult>} Hook result
*/
export const resolve = async (specifier, context) => {
const { conditions, parentURL: parent } = context
// resolve path alias
specifier = await mlly.resolveAlias(specifier, {
aliases: compilerOptions.paths,
conditions,
cwd: pathToFileURL(compilerOptions.baseUrl),
parent
})
/**
* Resolved module {@linkcode URL}.
*
* @type {URL}
* @const url
*/
const url = await mlly.resolveModule(specifier, {
conditions,
parent: parent?.startsWith('file:') ? parent : specifier
})
return {
format: await mlly.getFormat(url),
shortCircuit: true,
url: url.href
}
}
/**
* @file Reporters - Notifier
* @module tests/reporters/Notifier
*/
import type { OneOrMany } from '@flex-development/tutils'
import ci from 'is-ci'
import notifier from 'node-notifier'
import type NotificationCenter from 'node-notifier/notifiers/notificationcenter'
import { performance } from 'node:perf_hooks'
import { promisify } from 'node:util'
import { dedent } from 'ts-dedent'
import type { File, Reporter, Task, Test, Vitest } from 'vitest'
/**
* Custom reporter that sends a notification when all tests have been ran.
*
* @see https://vitest.dev/config/#reporters
*
* @implements {Reporter}
*/
class Notifier implements Reporter {
/**
* Test reporter context.
*
* @public
* @instance
* @member {Vitest} ctx
*/
public ctx: Vitest = {} as Vitest
/**
* Test run end time (in milliseconds).
*
* @public
* @instance
* @member {number} end
*/
public end: number = 0
/**
* Test run start time (in milliseconds).
*
* @public
* @instance
* @member {number} start
*/
public start: number = 0
/**
* Sends a notification.
*
* **Note**: Does nothing in CI environments.
*
* @protected
* @async
*
* @param {File[]} [files=this.ctx.state.getFiles()] - File objects
* @param {unknown[]} [errors=this.ctx.state.getUnhandledErrors()] - Errors
* @return {Promise<void>} Nothing when complete
*/
protected async notify(
files: File[] = this.ctx.state.getFiles(),
errors: unknown[] = this.ctx.state.getUnhandledErrors()
): Promise<void> {
// do nothing in ci environments
if (ci) return void ci
/**
* Test objects.
*
* @const {Test[]} tests
*/
const tests: Test[] = this.tests(files)
/**
* Total number of failed tests.
*
* @const {number} fails
*/
const fails: number = tests.filter(t => t.result?.state === 'fail').length
/**
* Total number of passed tests.
*
* @const {number} passes
*/
const passes: number = tests.filter(t => t.result?.state === 'pass').length
/**
* Notification message.
*
* @var {string} message
*/
let message: string = ''
/**
* Notification title.
*
* @var {string} title
*/
let title: string = ''
// get notification title and message based on number of failed tests
if (fails || errors.length > 0) {
message = dedent`
${fails} of ${tests.length} tests failed
${errors.length} unhandled errors
`
title = '\u274C Failed'
} else {
/**
* Total time to run all tests (in milliseconds).
*
* @const {number} time
*/
const time: number = this.end - this.start
message = dedent`
${passes} tests passed in ${
time > 1000 ? `${(time / 1000).toFixed(2)}ms` : `${Math.round(time)}ms`
}
`
title = '\u2705 Passed'
}
// send notification
return void (await promisify<NotificationCenter.Notification>(
notifier.notify.bind(notifier)
)({
message,
sound: true,
timeout: 10,
title
}))
}
/**
* Sends a notification after all tests have ran.
*
* @public
* @async
*
* @param {File[]} [files=this.ctx.state.getFiles()] - File objects
* @param {unknown[]} [errors=this.ctx.state.getUnhandledErrors()] - Errors
* @return {Promise<void>} Nothing when complete
*/
public async onFinished(
files: File[] = this.ctx.state.getFiles(),
errors: unknown[] = this.ctx.state.getUnhandledErrors()
): Promise<void> {
this.end = performance.now()
return void (await this.notify(files, errors))
}
/**
* Initializes the reporter.
*
* @public
*
* @param {Vitest} context - Test reporter context
* @return {void} Nothing when complete
*/
public onInit(context: Vitest): void {
this.ctx = context
return void (this.start = performance.now())
}
/**
* Returns an array of {@linkcode Test} objects.
*
* @protected
*
* @param {OneOrMany<Task>} [tasks=[]] - Tasks to collect tests from
* @return {Test[]} `Test` object array
*/
protected tests(tasks: OneOrMany<Task> = []): Test[] {
const { mode } = this.ctx
return (Array.isArray(tasks) ? tasks : [tasks]).flatMap(task => {
const { type } = task
return mode === 'typecheck' && type === 'suite' && task.tasks.length === 0
? ([task] as unknown as [Test])
: type === 'test'
? [task]
: 'tasks' in task
? task.tasks.flatMap(t => (t.type === 'test' ? [t] : this.tests(t)))
: []
})
}
}
export default Notifier
{
"scripts": {
"changelog": "node --loader=./loader.mjs ./config/changelog.config",
"recommended-bump": "conventional-recommended-bump --preset=conventionalcommits --tag-prefix=$(jq .tagPrefix package.json -r) --verbose"
},
"devDependencies": {
"@flex-development/commitlint-config": "1.0.1",
"@flex-development/esm-types": "1.0.0",
"@flex-development/mkbuild": "1.0.0-alpha.16",
"@flex-development/mlly": "1.0.0-alpha.15",
"@flex-development/pathe": "1.0.3",
"@flex-development/tsconfig-utils": "1.1.1",
"@flex-development/tutils": "6.0.0-alpha.10"
"@types/conventional-changelog": "3.1.1",
"@types/conventional-changelog-core": "4.2.1",
"@types/conventional-changelog-writer": "4.0.2",
"@types/conventional-recommended-bump": "6.1.0",
"@types/dateformat": "5.0.0",
"@types/git-raw-commits": "2.0.1",
"@types/node": "18.14.6",
"@types/semver": "7.3.13",
"add-stream": "1.0.0",
"conventional-changelog": "3.1.25",
"conventional-changelog-conventionalcommits": "5.0.0",
"conventional-changelog-core": "4.2.4",
"conventional-changelog-writer": "5.0.1",
"conventional-recommended-bump": "6.1.0",
"dateformat": "5.0.3",
"mri": "1.2.0",
"sade": "1.8.1",
"semver": "7.3.8",
"tempfile": "5.0.0"
},
"tagPrefix": ""
}
#!/bin/sh
# Local Release Workflow
#
# 1. run typecheck
# 2. run tests
# 3. build project
# 4. run postbuild typecheck
# 5. print package size report
# 6. get new package version
# 7. get release branch name
# 8. switch to release branch
# 9. stage changes
# 10. commit changes
# 11. push release branch to origin
# 12. create pull request
#
# References:
#
# - https://cli.github.com/manual/gh_pr_create
yarn typecheck
yarn test:cov
yarn build
yarn check:types:build
yarn pkg-size
VERSION=$(jq .version package.json -r)
RELEASE_BRANCH=release/$VERSION
git switch -c $RELEASE_BRANCH
git add .
git commit -s -m "release: $(jq .tagPrefix package.json -r)$VERSION"
git push origin -u --no-verify $RELEASE_BRANCH
gh pr create --assignee @me --label scope:release --web
{
"compilerOptions": {
"declaration": true,
"noEmitOnError": true,
"skipLibCheck": false,
"target": "es2022"
},
"exclude": ["**/__mocks__", "**/__tests__"],
"extends": "./tsconfig.json",
"include": ["./dist/*", "./src/*"]
}
{
"compilerOptions": {
"composite": true,
"customConditions": ["require", "node", "default"],
"declaration": true,
"emitDeclarationOnly": true,
"noEmit": false,
"module": "commonjs",
"moduleResolution": "nodenext",
"outDir": ".",
"verbatimModuleSyntax": false
},
"exclude": ["**/coverage", "**/dist", "**/node_modules"],
"extends": "./tsconfig.json",
"include": ["**/**.cjs", "**/**.cts", "**/**.json", "**/.*.cjs", "**/.*.cts"]
}
{
"compilerOptions": {
"allowImportingTsExtensions": true,
"allowJs": true,
"allowUnreachableCode": false,
"alwaysStrict": false,
"baseUrl": ".",
"checkJs": false,
"customConditions": ["import", "node", "default"],
"declaration": false,
"declarationMap": false,
"emitDecoratorMetadata": false,
"esModuleInterop": true,
"exactOptionalPropertyTypes": true,
"experimentalDecorators": false,
"forceConsistentCasingInFileNames": true,
"lib": ["es2022"],
"module": "esnext",
"moduleResolution": "bundler",
"newLine": "lf",
"noEmit": true,
"noErrorTruncation": true,
"noFallthroughCasesInSwitch": true,
"noImplicitAny": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
"noUncheckedIndexedAccess": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"outDir": "dist",
"paths": {
"#fixtures/*": ["__fixtures__/*"],
"#src": ["src/index"],
"#src/*": ["src/*"],
"#tests/*": ["__tests__/*"]
},
"preserveConstEnums": true,
"preserveSymlinks": false,
"pretty": true,
"resolveJsonModule": true,
"resolvePackageJsonExports": true,
"resolvePackageJsonImports": true,
"rootDir": ".",
"skipDefaultLibCheck": false,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"strictNullChecks": true,
"strictPropertyInitialization": true,
"target": "esnext",
"useDefineForClassFields": true,
"useUnknownInCatchVariables": true,
"verbatimModuleSyntax": true
},
"exclude": ["**/coverage", "**/dist", "**/node_modules"],
"include": ["**/**.js", "**/**.mjs", "**/**.mts", "**/**.ts"],
"references": [{ "path": "./tsconfig.cjs.json" }]
}
{
"compilerOptions": {
"target": "es2022"
},
"extends": "./tsconfig.json",
"include": [
"**/**.cts",
"**/**.mts",
"**/**.ts",
"**/.*.cts",
"**/.*.mts",
"**/.*.ts"
]
}
#!/bin/sh
# Postbuild Typecheck Workflow
# set initial tsconfig file
TSCONFIG='tsconfig.build.json'
# change tsconfig file if typescript version is not at least 5
[[ ! $(jq .devDependencies.typescript package.json -r) = 5* ]] && TSCONFIG=__tests__/ts/v4/$TSCONFIG
# run typecheck
tsc -p $TSCONFIG
/// <reference types='vitest/globals' />
interface ImportMetaEnv {
readonly BASE_URL: string
readonly DEV: '1' | import('@flex-development/tutils').EmptyString
readonly LINT_STAGED?: '0' | '1'
readonly MODE: import('@flex-development/tutils').NodeEnv.TEST
readonly NODE_ENV: import('@flex-development/tutils').NodeEnv.TEST
readonly PROD: '1' | import('@flex-development/tutils').EmptyString
readonly PWD: string
readonly SSR: '1' | import('@flex-development/tutils').EmptyString
readonly TEST: 'true'
readonly TYPESCRIPT_VERSION?: string
readonly USER: string
readonly VITEST: 'true'
readonly VITEST_CLI_WRAPPER: 'true'
readonly VITEST_MODE: 'DEV' | 'RUN'
readonly VITEST_POOL_ID: `${number}`
readonly VITEST_WORKER_ID: `${number}`
readonly VITE_ROOT: string
readonly VITE_USER_NODE_ENV: import('@flex-development/tutils').NodeEnv.TEST
}
interface ImportMeta {
readonly env: ImportMetaEnv
}
/**
* @file Vitest Configuration
* @module config/vitest
* @see https://vitest.dev/config/
*/
import pathe from '@flex-development/pathe'
import { NodeEnv } from '@flex-development/tutils'
import ci from 'is-ci'
import { template } from 'radash'
import tsconfigpaths from 'vite-tsconfig-paths'
import GithubActionsReporter from 'vitest-github-actions-reporter'
import {
defineConfig,
type UserConfig,
type UserConfigExport
} from 'vitest/config'
import { BaseSequencer, type WorkspaceSpec } from 'vitest/node'
/**
* Vitest configuration export.
*
* @const {UserConfigExport} config
*/
const config: UserConfigExport = defineConfig((): UserConfig => {
/**
* [`lint-staged`][1] check.
*
* [1]: https://github.com/okonet/lint-staged
*
* @const {boolean} LINT_STAGED
*/
const LINT_STAGED: boolean = !!Number.parseInt(process.env.LINT_STAGED ?? '0')
/**
* Boolean indicating if the current running version of [`typescript`][1] is
* at least `5`.
*
* @const {boolean} TYPESCRIPT_V5
*/
const TYPESCRIPT_V5: boolean =
process.env.TYPESCRIPT_VERSION?.startsWith('5') ?? true
return {
define: {
'import.meta.env.NODE_ENV': JSON.stringify(NodeEnv.TEST)
},
plugins: [tsconfigpaths({ projects: [pathe.resolve('tsconfig.json')] })],
test: {
allowOnly: !ci,
benchmark: {},
chaiConfig: {
includeStack: true,
showDiff: true,
truncateThreshold: 0
},
clearMocks: true,
coverage: {
all: !LINT_STAGED,
clean: true,
cleanOnRerun: true,
exclude: [
'**/__mocks__/**',
'**/__tests__/**',
'**/index.ts',
'src/interfaces/',
'src/types/'
],
extension: ['.ts'],
include: ['src'],
provider: 'c8',
reporter: [ci ? 'lcovonly' : 'lcov', 'text'],
reportsDirectory: './coverage',
skipFull: false
},
environment: 'node',
environmentOptions: {},
globalSetup: [],
globals: true,
hookTimeout: 10 * 1000,
include: [`**/__tests__/*.spec${LINT_STAGED ? ',spec-d' : ''}.{ts,tsx}`],
isolate: true,
mockReset: true,
outputFile: { json: './__tests__/report.json' },
passWithNoTests: true,
reporters: [
'json',
'verbose',
ci ? new GithubActionsReporter() : './__tests__/reporters/notifier.ts'
],
/**
* Stores snapshots next to `file`'s directory.
*
* @param {string} file - Path to test file
* @param {string} extension - Snapshot extension
* @return {string} Custom snapshot path
*/
resolveSnapshotPath(file: string, extension: string): string {
return pathe.resolve(
pathe.resolve(pathe.dirname(pathe.dirname(file)), '__snapshots__'),
pathe.basename(file).replace(/\.spec.tsx?/, '') + extension
)
},
restoreMocks: true,
root: process.cwd(),
sequence: {
sequencer: class Sequencer extends BaseSequencer {
/**
* Determines test file execution order.
*
* @public
* @override
* @async
*
* @param {WorkspaceSpec[]} specs - Workspace spec objects
* @return {Promise<WorkspaceSpec[]>} `files` sorted
*/
public override async sort(
specs: WorkspaceSpec[]
): Promise<WorkspaceSpec[]> {
return (await super.sort(specs)).sort(([, file1], [, file2]) => {
return file1.localeCompare(file2)
})
}
}
},
setupFiles: ['./__tests__/setup/index.ts'],
silent: false,
singleThread: true,
slowTestThreshold: 3000,
snapshotFormat: {
callToJSON: true,
min: false,
printBasicPrototype: false,
printFunctionName: true
},
testTimeout: 10 * 1000,
typecheck: {
allowJs: false,
checker: 'tsc',
ignoreSourceErrors: false,
include: ['**/__tests__/*.spec-d.ts'],
tsconfig: template('{{0}}/tsconfig.typecheck.json', {
0: pathe.resolve(TYPESCRIPT_V5 ? '' : '__tests__/ts/v4')
})
},
unstubEnvs: true,
unstubGlobals: true
}
}
})
export default config
@unicornware
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment