diff --git a/.eslintignore b/.eslintignore index e72e8e43..f868bfeb 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,2 +1,17 @@ **/node_modules/* parser/react-native-live-markdown-parser.js + +# any js file inside android and ios folders +**/android/**/*.js +**/ios/**/*.js + +# Output of the build process & scripts +lib/**/* +scripts/**/* + +babel.config.js +metro.config.js +react-native.config.js +jest.config.js +webpack.config.js +.eslintrc.js diff --git a/.eslintrc.js b/.eslintrc.js index 46b28e77..ef20f0bc 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -4,10 +4,15 @@ module.exports = { project: true, tsconfigRootDir: __dirname, }, + env: { + jest: true, + }, extends: [ + 'expensify', 'plugin:@typescript-eslint/recommended', - 'prettier', + 'plugin:@typescript-eslint/stylistic', 'plugin:import/typescript', + 'prettier', 'plugin:prettier/recommended', ], plugins: [ @@ -24,34 +29,24 @@ module.exports = { }, root: true, rules: { - 'prettier/prettier': [ - 'error', + 'rulesdir/prefer-underscore-method': 'off', + 'react/jsx-props-no-spreading': 'off', + 'react/require-default-props': 'off', + 'react/jsx-filename-extension': ['error', { extensions: ['.tsx', '.jsx'] }], + "import/extensions": [ + "error", + "ignorePackages", { - quoteProps: 'consistent', - singleQuote: true, - tabWidth: 2, - trailingComma: 'es5', - useTabs: false, - }, - ], - 'curly': 'error', + "jsx": "never", + "ts": "never", + "tsx": "never" + } + ], 'import/no-unresolved': 'error', 'import/consistent-type-specifier-style': ['error', 'prefer-top-level'], - 'react/jsx-uses-vars': 'error', - 'react/jsx-uses-react': 'error', 'no-use-before-define': 'off', '@typescript-eslint/no-use-before-define': 'off', // TODO consider enabling this (currently it reports styles defined at the bottom of the file) - '@typescript-eslint/ban-ts-comment': [ - 'error', - { - 'ts-ignore': 'allow-with-description', - 'ts-expect-error': 'allow-with-description', - }, - ], '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], - '@typescript-eslint/no-var-requires': 'warn', - 'eqeqeq': 'error', - 'no-unreachable': 'error', '@typescript-eslint/consistent-type-imports': [ 'error', { prefer: 'type-imports' }, @@ -62,5 +57,7 @@ module.exports = { ], 'tsdoc/syntax': 'error', '@typescript-eslint/no-non-null-assertion': 'off', + '@typescript-eslint/array-type': ['error', {default: 'array-simple'}], + '@typescript-eslint/consistent-type-definitions': 'off', }, }; diff --git a/.github/OSBotify private key.gpg b/.github/OSBotify private key.gpg new file mode 100644 index 00000000..c19d5c97 Binary files /dev/null and b/.github/OSBotify private key.gpg differ diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..a103adfa --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,18 @@ + + +### Details + + +### Related Issues + +GH_LINK + +### Manual Tests + + +### Linked PRs + \ No newline at end of file diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml new file mode 100644 index 00000000..32bd5243 --- /dev/null +++ b/.github/workflows/cla.yml @@ -0,0 +1,39 @@ +name: CLA Assistant + +on: + issue_comment: + types: [created] + pull_request_target: + types: [opened, closed, synchronize] + +jobs: + CLA: + runs-on: ubuntu-latest + # This job only runs for pull request comments or pull request target events (not issue comments) + if: github.event.issue.pull_request || github.event_name == 'pull_request_target' + steps: + - uses: actions-ecosystem/action-regex-match@9c35fe9ac1840239939c59e5db8839422eed8a73 + id: sign + with: + text: ${{ github.event.comment.body }} + regex: '\s*I have read the CLA Document and I hereby sign the CLA\s*' + - uses: actions-ecosystem/action-regex-match@9c35fe9ac1840239939c59e5db8839422eed8a73 + id: recheck + with: + text: ${{ github.event.comment.body }} + regex: '\s*recheck\s*' + - name: CLA Assistant + if: ${{ steps.recheck.outputs.match != '' || steps.sign.outputs.match != '' }} || github.event_name == 'pull_request_target' + # Version: 2.1.2-beta + uses: cla-assistant/github-action@948230deb0d44dd38957592f08c6bd934d96d0cf + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PERSONAL_ACCESS_TOKEN : ${{ secrets.CLA_BOTIFY_TOKEN }} + with: + path-to-signatures: '${{ github.repository }}/cla.json' + path-to-document: 'https://github.com/${{ github.repository }}/blob/main/CLA.md' + branch: 'main' + remote-organization-name: 'Expensify' + remote-repository-name: 'CLA' + lock-pullrequest-aftermerge: false + allowlist: 'snyk-bot,OSBotify' diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000..252b646d --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,39 @@ +name: Lint JavaScript + +on: + pull_request: + types: [opened, synchronize] + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - uses: actions/cache@v2 + with: + path: ~/.yarn + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + + - name: Setup Node + uses: actions/setup-node@v1 + with: + node-version: "16.x" + + - name: Setup web example + run: npm ci + working-directory: ./WebExample + + - run: yarn install --immutable + + + - name: Verify there's no Prettier diff + run: | + yarn lint --fix --quiet + if ! git diff --name-only --exit-code; then + # shellcheck disable=SC2016 + echo 'Error: Prettier diff detected! Please run `npm run prettier` and commit the changes.' + exit 1 + fi diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 00000000..cd465c93 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,87 @@ +# name: Publish package to npmjs + +# # This workflow runs when code is pushed to `main` (i.e: when a pull request is merged) +# on: +# push: +# branches: [main] + +# # Ensure that only once instance of this workflow executes at a time. +# # If multiple PRs are merged in quick succession, there will only ever be one publish workflow running and one pending. +# concurrency: ${{ github.workflow }} + +# jobs: +# version: +# runs-on: ubuntu-latest + +# # OSBotify will update the version on `main`, so this check is important to prevent an infinite loop +# if: ${{ github.actor != 'OSBotify' }} + +# steps: +# - uses: actions/checkout@v3 +# with: +# ref: main + +# - name: Decrypt & Import OSBotify GPG key +# run: | +# cd .github +# gpg --quiet --batch --yes --decrypt --passphrase="$LARGE_SECRET_PASSPHRASE" --output OSBotify-private-key.asc OSBotify-private-key.asc.gpg +# gpg --import OSBotify-private-key.asc +# env: +# LARGE_SECRET_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} + +# - name: Set up git for OSBotify +# run: | +# git config --global user.signingkey 367811D53E34168C +# git config --global commit.gpgsign true +# git config --global user.name OSBotify +# git config --global user.email infra+osbotify@expensify.com + +# - uses: actions/setup-node@v3 +# with: +# node-version: '16.x' +# registry-url: 'https://registry.npmjs.org' + +# - name: Generate branch name +# run: echo "BRANCH_NAME=OSBotify-bump-version-$(uuidgen)" >> $GITHUB_ENV + +# - name: Create branch for version-bump pull request +# run: git checkout -b ${{ env.BRANCH_NAME }} + +# - name: Install npm packages +# run: npm ci + +# - name: Update npm version +# run: npm version patch + +# - name: Set new version in GitHub ENV +# run: echo "NEW_VERSION=$(jq '.version' package.json)" >> $GITHUB_ENV + +# - name: Push branch and publish tags +# run: git push --set-upstream origin ${{ env.BRANCH_NAME }} && git push --tags + +# - name: Create pull request +# run: | +# gh pr create \ +# --title "Update version to ${{ env.NEW_VERSION }}" \ +# --body "Update version to ${{ env.NEW_VERSION }}" +# sleep 5 +# env: +# GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }} + +# - name: Auto-approve pull request +# run: gh pr review --approve ${{ env.BRANCH_NAME }} +# env: +# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +# - name: Auto-merge pull request +# run: gh pr merge --merge --delete-branch ${{ env.BRANCH_NAME }} +# env: +# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +# - name: Build package +# run: npm run build + +# - name: Publish to npm +# run: npm publish +# env: +# NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.gitignore b/.gitignore index 8edd0608..1c31ccdd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,16 +1,17 @@ # OSX -# .DS_Store -# XDE -.expo/ +# IDE +.idea # VSCode .vscode/ jsconfig.json +# NPM file created by GitHub actions +.npmrc + # Xcode -# build/ *.pbxuser !default.pbxuser @@ -30,7 +31,6 @@ DerivedData project.xcworkspace # Android/IJ -# .classpath .cxx .gradle @@ -48,11 +48,11 @@ example/ios/Pods example/vendor/ # node.js -# node_modules/ npm-debug.log yarn-debug.log yarn-error.log +dist/ # BUCK buck-out/ diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 00000000..3ed37e3b --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,9 @@ +module.exports = { + tabWidth: 2, + singleQuote: true, + trailingComma: 'all', + bracketSpacing: false, + arrowParens: 'always', + printWidth: 190, + singleAttributePerLine: true, +}; diff --git a/.watchmanconfig b/.watchmanconfig index 9e26dfee..0967ef42 100644 --- a/.watchmanconfig +++ b/.watchmanconfig @@ -1 +1 @@ -{} \ No newline at end of file +{} diff --git a/WebExample/app.json b/WebExample/app.json index bb70e18b..72f02f68 100644 --- a/WebExample/app.json +++ b/WebExample/app.json @@ -11,9 +11,7 @@ "resizeMode": "contain", "backgroundColor": "#ffffff" }, - "assetBundlePatterns": [ - "**/*" - ], + "assetBundlePatterns": ["**/*"], "ios": { "supportsTablet": true }, diff --git a/WebExample/babel.config.js b/WebExample/babel.config.js index 054ccfa5..23a65e62 100644 --- a/WebExample/babel.config.js +++ b/WebExample/babel.config.js @@ -11,12 +11,8 @@ module.exports = function (api) { { extensions: ['.tsx', '.ts', '.js', '.json'], alias: { - 'react': path.join(__dirname, 'node_modules', 'react'), - 'react-native': path.join( - __dirname, - 'node_modules', - 'react-native-web' - ), + react: path.join(__dirname, 'node_modules', 'react'), + 'react-native': path.join(__dirname, 'node_modules', 'react-native-web'), [pak.name]: path.join(__dirname, '..', pak.source), }, }, diff --git a/WebExample/webpack.config.js b/WebExample/webpack.config.js index 59c9eeda..66344e0f 100644 --- a/WebExample/webpack.config.js +++ b/WebExample/webpack.config.js @@ -8,7 +8,7 @@ module.exports = async function (env, argv) { dangerouslyAddModulePathsToTranspile: ['react-native-live-markdown'], }, }, - argv + argv, ); return config; }; diff --git a/babel.config.js b/babel.config.js index f842b77f..10851900 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,3 +1,15 @@ -module.exports = { - presets: ['module:metro-react-native-babel-preset'], +module.exports = (api) => { + const isWeb = api.caller((caller) => caller && caller.target === 'web'); + if (isWeb) { + return { + // Default browser list is a reasonable preset covering a wide list of non-dead browsers + // https://github.com/browserslist/browserslist#best-practices + targets: 'defaults', + presets: ['@babel/preset-env', '@babel/preset-react'], + }; + } + + return { + presets: ['module:metro-react-native-babel-preset'], + }; }; diff --git a/example/index.js b/example/index.js deleted file mode 100644 index 117ddcae..00000000 --- a/example/index.js +++ /dev/null @@ -1,5 +0,0 @@ -import { AppRegistry } from 'react-native'; -import App from './src/App'; -import { name as appName } from './app.json'; - -AppRegistry.registerComponent(appName, () => App); diff --git a/example/index.ts b/example/index.ts new file mode 100644 index 00000000..ef707c25 --- /dev/null +++ b/example/index.ts @@ -0,0 +1,5 @@ +import {AppRegistry} from 'react-native'; +import App from './src/App'; +import {name as appName} from './app.json'; + +AppRegistry.registerComponent(appName, () => App); diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index ef33d93c..81351803 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -673,11 +673,11 @@ SPEC CHECKSUMS: Flipper-PeerTalk: 116d8f857dc6ef55c7a5a75ea3ceaafe878aadc9 FlipperKit: 2efad7007d6745a3f95e4034d547be637f89d3f6 fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 - glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b - hermes-engine: 9b9bb14184a11b8ceb4131b09abf634880f0f46d + glog: 791fe035093b84822da7f0870421a25839ca7870 + hermes-engine: c0fcd29517813123a2153e9f7943bc30675f5229 libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c - RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1 + RCT-Folly: 85766c3226c7ec638f05ad7cb3cf6a268d6c4241 RCTRequired: f30c3213569b1dc43659ecc549a6536e1e11139e RCTTypeSafety: e1ed3137728804fa98bce30b70e3da0b8e23054e React: 54070abee263d5773486987f1cf3a3616710ed52 diff --git a/example/src/App.tsx b/example/src/App.tsx index 41e774fe..6d14c907 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -1,20 +1,11 @@ import * as React from 'react'; -import { Button, Platform, StyleSheet, Text, View } from 'react-native'; +import {Button, Platform, StyleSheet, Text, View} from 'react-native'; -import { MarkdownTextInput } from '@expensify/react-native-live-markdown'; -import type { TextInput } from 'react-native'; +import {MarkdownTextInput} from '@expensify/react-native-live-markdown'; +import type {TextInput} from 'react-native'; -const DEFAULT_TEXT = [ - 'Hello, *world*!', - 'https://expensify.com', - '# Lorem ipsum', - '> Hello world', - '`foo`', - '```\nbar\n```', - '@here', - '@someone@swmansion.com', -].join('\n'); +const DEFAULT_TEXT = ['Hello, *world*!', 'https://expensify.com', '# Lorem ipsum', '> Hello world', '`foo`', '```\nbar\n```', '@here', '@someone@swmansion.com'].join('\n'); function isWeb() { return Platform.OS === 'web'; @@ -24,7 +15,7 @@ function getPlatform() { if (isWeb()) { return 'web'; } - // @ts-ignore it works + // @ts-expect-error it works return Platform.constants.systemName || Platform.constants.Brand; } @@ -39,12 +30,14 @@ function getBundle() { function getRuntime() { if ('HermesInternal' in global) { const version = - // @ts-ignore this is fine + // @ts-expect-error this is fine + // eslint-disable-next-line es/no-optional-chaining global.HermesInternal?.getRuntimeProperties?.()['OSS Release Version']; return `Hermes (${version})`; } if ('_v8runtime' in global) { - // @ts-ignore this is fine + // @ts-expect-error this is fine + // eslint-disable-next-line no-underscore-dangle const version = global._v8runtime().version; return `V8 (${version})`; } @@ -56,7 +49,7 @@ function getArchitecture() { } function getReactNativeVersion() { - const { major, minor, patch } = Platform.constants.reactNativeVersion; + const {major, minor, patch} = Platform.constants.reactNativeVersion; return `${major}.${minor}.${patch}`; } @@ -113,10 +106,32 @@ export default function App() { style={styles.input} /> */} {JSON.stringify(value)} -