diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 65d4fa84d..e2b00c835 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -21,10 +21,13 @@ jobs: uses: actions/setup-node@v3 with: node-version: lts/* - - name: Install - run: npm install --no-package-lock --legacy-peer-deps --force + - name: Setup PNPM + uses: pnpm/action-setup@v2 + with: + version: latest + run_install: true - name: Test - run: npm test + run: pnpm test && node scripts/esm-tests.mjs next: if: github.ref != 'refs/heads/master' runs-on: ubuntu-latest @@ -37,10 +40,15 @@ jobs: uses: actions/setup-node@v3 with: node-version: lts/* + - name: Setup PNPM + uses: pnpm/action-setup@v2 + with: + version: latest + run_install: false - name: Lint run: | - npm install react react-dom next --no-package-lock --legacy-peer-deps - npx next lint + pnpm install react react-dom next + ./node_modules/.bin/next lint urlint: if: github.ref != 'refs/heads/master' @@ -54,7 +62,12 @@ jobs: uses: actions/setup-node@v3 with: node-version: lts/* + - name: Setup PNPM + uses: pnpm/action-setup@v2 + with: + version: latest + run_install: false - name: Lint run: | - npm install urlint --no-package-lock --legacy-peer-deps - npx urlint https://microlink.io/sitemap.xml + pnpm install urlint + ./node_modules/.bin/urlint https://microlink.io/sitemap.xml diff --git a/package.json b/package.json index 5292dd47c..bb37f64a6 100644 --- a/package.json +++ b/package.json @@ -138,7 +138,7 @@ "postcss": "~8.4.27", "postcss-focus": "~7.0.0", "prepend-http": "~4.0.0", - "prettier": "~2.8.8", + "prettier": "~3.0.0", "react": "18", "react-codecopy": "~5.0.1", "react-confetti": "~6.1.0", @@ -182,10 +182,11 @@ "simple-git-hooks": "latest", "standard": "latest", "standard-markdown": "latest", - "standard-version": "latest" + "standard-version": "latest", + "zx": "latest" }, "engines": { - "node": ">= 14" + "node": ">= 18" }, "files": [ "data", diff --git a/scripts/esm-tests.mjs b/scripts/esm-tests.mjs new file mode 100644 index 000000000..0642c2fb9 --- /dev/null +++ b/scripts/esm-tests.mjs @@ -0,0 +1,21 @@ +import { $ } from 'zx' +import { dirname, resolve } from 'path' +import { fileURLToPath } from 'url' +import { readFile, writeFile } from 'fs/promises' + +const __dirname = dirname(fileURLToPath(import.meta.url)) + +const pkgPath = resolve(__dirname, '../package.json') + +const pkg = JSON.parse(await readFile(pkgPath)) +pkg.type = 'module' + +await writeFile(pkgPath, JSON.stringify(pkg, null, 2)) + +try { + await $`pnpm install ava@latest` + await $`./node_modules/.bin/ava test/**/*.mjs` +} finally { + delete pkg.type + await writeFile(pkgPath, JSON.stringify(pkg, null, 2)) +} diff --git a/src/components/elements/CodeEditor/CodeEditor.js b/src/components/elements/CodeEditor/CodeEditor.js index e92ede06b..2a03b975c 100644 --- a/src/components/elements/CodeEditor/CodeEditor.js +++ b/src/components/elements/CodeEditor/CodeEditor.js @@ -1,12 +1,10 @@ import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter' import { hash, prettier, getLines, template } from 'helpers' import { hideScrollbar, wordBreak } from 'helpers/style' -import React, { useState } from 'react' -import identity from 'lodash/identity' +import React, { useEffect, useState } from 'react' import styled from 'styled-components' import { cx, radii } from 'theme' import range from 'lodash/range' -import get from 'dlv' import codeTheme from './theme' import Runkit from '../Runkit/Runkit' @@ -83,22 +81,37 @@ const CodeEditor = ({ title, ...props }) => { - const [isLoaded, setIsLoaded] = useState(false) const className = getClassName(props) const highlightLines = getLines(className) const language = toAlias(getLanguage(className, props)) - const pretty = props.prettier ? get(prettier, language, identity) : identity - const text = pretty(template(children)).trim() const id = `codeditor-${hash(children)}-${theme}` - const isInteractive = Runkit.isSupported({ language, text }) + + const [content, setContent] = useState({ + text: template(children).trim(), + isPretty: false + }) + + const isInteractive = + runkitProps !== false && + Runkit.isSupported({ language, text: content.text }) + + const [isLoaded, setIsLoaded] = useState(!isInteractive) + + useEffect(() => { + async function asyncPretty () { + const prettyText = await prettier[language](content.text) + setContent({ text: prettyText.trim(), isPretty: true }) + } + asyncPretty() + }, []) const TerminalComponent = ( @@ -111,13 +124,13 @@ const CodeEditor = ({ style={{}} wrapLines > - {text} + {content.text} ) - if (!isInteractive) return TerminalComponent + if (!isInteractive || !content.isPretty) return TerminalComponent return ( { setIsLoaded(true) element.style['padding-top'] = '4px' @@ -138,7 +151,7 @@ const CodeEditor = ({ CodeEditor.defaultProps = { blinkCursor: false, - prettier: true, + interactive: {}, showLineNumbers: false, theme: 'light', width: TERMINAL_WIDTH diff --git a/src/components/elements/CodeEditor/CodeEditor.stories.js b/src/components/elements/CodeEditor/CodeEditor.stories.js index dcd8dd5c3..66aa3e0c7 100644 --- a/src/components/elements/CodeEditor/CodeEditor.stories.js +++ b/src/components/elements/CodeEditor/CodeEditor.stories.js @@ -1,4 +1,4 @@ -import { Text, Box, CodeEditor } from 'components/elements' +import { Text, Box, CodeEditor, Toggle } from 'components/elements' import { storiesOf } from '@storybook/react' import { Story } from 'story' import React from 'react' @@ -120,6 +120,12 @@ const jsonCode = JSON.stringify( storiesOf('Elements', module).add('CodeEditor', () => ( + + + {['Interactive', 'Non Interactive']} + + + {""} diff --git a/src/components/elements/Toggle/Toggle.js b/src/components/elements/Toggle/Toggle.js index 93da81c70..9e54c8383 100644 --- a/src/components/elements/Toggle/Toggle.js +++ b/src/components/elements/Toggle/Toggle.js @@ -43,12 +43,10 @@ function Toggle ({ onChange, children, defaultValue }) { key={value} borderRight={!isLast ? 1 : null} borderColor={!isLast ? 'black05' : null} + pl={3} + pr={3} > - + ( - - defaultValue={'MQL'} + + {['SDK', 'MQL', 'API']} ) ` diff --git a/src/components/hook/index.js b/src/components/hook/index.js index 4cf84a18f..fd61e0bbe 100644 --- a/src/components/hook/index.js +++ b/src/components/hook/index.js @@ -12,7 +12,6 @@ export * from './use-fingerprint' export * from './use-healthcheck' export * from './use-hover' export * from './use-local-storage' -export * from './use-mounted-ref' export * from './use-oss' export * from './use-previous' export * from './use-query-state' diff --git a/src/components/hook/use-mounted-ref.js b/src/components/hook/use-mounted-ref.js deleted file mode 100644 index e6cc1bb16..000000000 --- a/src/components/hook/use-mounted-ref.js +++ /dev/null @@ -1,12 +0,0 @@ -import { useRef, useEffect } from 'react' - -export const useMountedRef = () => { - const isMounted = useRef(false) - - useEffect(() => { - isMounted.current = true - return () => (isMounted.current = false) - }, []) - - return isMounted -} diff --git a/src/helpers/mql-code.js b/src/helpers/mql-code.js index b4bda9e58..92161013b 100644 --- a/src/helpers/mql-code.js +++ b/src/helpers/mql-code.js @@ -19,9 +19,7 @@ const stringify = input => { } stringify.python = input => - stringify(input) - .replaceAll('true', 'True') - .replaceAll('false', 'False') + stringify(input).replaceAll('true', 'True').replaceAll('false', 'False') const endpoint = ({ endpoint, headers } = {}) => { const apiKey = headers && headers['x-api-key'] @@ -45,8 +43,8 @@ const stringProps = (props = {}) => { Array.isArray(props[key]) ? stringify(props[key]) : typeof props[key] === 'object' - ? `{${stringProps(props[key])}}` - : stringify(props[key]) + ? `{${stringProps(props[key])}}` + : stringify(props[key]) }${keys.length === 1 || keys.length - 1 === index ? '' : ', '}`, '' ) diff --git a/src/helpers/prettier.js b/src/helpers/prettier.js index ba9be8248..e95c51a97 100644 --- a/src/helpers/prettier.js +++ b/src/helpers/prettier.js @@ -1,8 +1,6 @@ -import prettierStandalone from 'prettier/standalone' -import prettierParserHtml from 'prettier/parser-html' -import prettierParserGraphql from 'prettier/parser-graphql' -import prettierParserMarkdown from 'prettier/parser-markdown' -import parserBabel from 'prettier/parser-babel' +import { format } from 'prettier/standalone' +import estree from 'prettier/plugins/estree' +import babel from 'prettier/plugins/babel' /** * https://prettier.io/docs/en/options.html @@ -19,50 +17,49 @@ const PRETTIER_CONFIG = { const JS_OPTS = { parser: 'babel', - plugins: [parserBabel] + plugins: [babel, estree] } const JSON_OPTS = { parser: 'json', - plugins: [parserBabel] + plugins: [babel, estree] } -const HTML_OPTS = { - parser: 'html', - plugins: [prettierParserHtml] -} - -const GRAPHQL_OPTS = { - parser: 'graphql', - plugins: [prettierParserGraphql] -} - -const MARKDOWN_OPTS = { - parser: 'markdown', - plugins: [prettierParserMarkdown] -} - -export const serializeFmt = (props, { quotes = true } = {}) => { +/** + * It turns an JS object: + * + * { foo: 'bar', apiKey: true } + * + * into fmt representation: + * + * `foo='bar' apiKey` + * + */ +export const serializeFmt = props => { return Object.keys(props).reduce((acc, rawKey) => { const rawValue = props[rawKey] const key = rawValue === true ? rawKey : `${rawKey}=` const value = (() => { if (rawValue === true) return '' - if (Array.isArray(rawValue)) { - return `{[${rawValue.map( - value => `${quotes ? `'${value}'` : value}` - )}]}` + return `{[${rawValue.map(serializePrimitive).join(', ')}]}` } - - return `${quotes ? `'${rawValue}'` : rawValue}` + return serializeValue(rawValue) })() return `${acc}${key}${value} ` }, '') } -export const serializeObject = (props, { quotes = true } = {}) => { +const serializePrimitive = value => + typeof value === 'object' + ? `{ ${serializeObject(value)} }` + : serializeValue(value) + +const serializeValue = value => + typeof value === 'number' ? `{${value}}` : `'${value}'` + +const serializeObject = (props, { quotes = true } = {}) => { return Object.keys(props).reduce((acc, rawKey) => { const rawValue = props[rawKey] const key = rawValue === true ? rawKey : `${rawKey}: ` @@ -73,11 +70,10 @@ export const serializeObject = (props, { quotes = true } = {}) => { }, '') } -const prettier = (code, opts) => { +const prettier = async (code, opts) => { try { - return prettierStandalone - .format(code, { ...PRETTIER_CONFIG, ...opts }) - .replace(';<', '<') + const pretty = await format(code, { ...PRETTIER_CONFIG, ...opts }) + return pretty.replace(';<', '<') } catch (error) { if (error.name !== 'SyntaxError') console.error('[prettier]', error) return code @@ -86,10 +82,7 @@ const prettier = (code, opts) => { prettier.jsx = prettier.js = (code, opts) => prettier(code, { ...JS_OPTS, ...opts }) -prettier.html = (code, opts) => prettier(code, { ...HTML_OPTS, ...opts }) -prettier.graphql = (code, opts) => prettier(code, { ...GRAPHQL_OPTS, ...opts }) -prettier.md = prettier.markdown = (code, opts) => - prettier(code, { ...MARKDOWN_OPTS, ...opts }) + prettier.json = (code, opts) => prettier(code, { ...JSON_OPTS, ...opts }) export default prettier diff --git a/test/helpers/get-lines.js b/test/helpers/get-lines.js index cabde12d4..a97abf5ba 100644 --- a/test/helpers/get-lines.js +++ b/test/helpers/get-lines.js @@ -1,6 +1,6 @@ import test from 'ava' -import getLines from '../../src/helpers/get-lines' +import getLines from '../../src/helpers/get-lines.js' const values = [ ['', null], diff --git a/test/helpers/prettier.js b/test/helpers/prettier.js deleted file mode 100644 index 121a398b3..000000000 --- a/test/helpers/prettier.js +++ /dev/null @@ -1,15 +0,0 @@ -import test from 'ava' - -import prettier from '../../src/helpers/prettier' - -test('js', t => { - const code = `const mql = require('@microlink/mql') - - const { status, data } = await mql('https://geolocation.microlink.io', { apiKey: MyApiToken, proxy: 'https://myproxy:603f60f5@superproxy.cool:8001' }) - - console.log(data)` - - const output = prettier.js(code) - - t.snapshot(output) -}) diff --git a/test/helpers/prettier.mjs b/test/helpers/prettier.mjs new file mode 100644 index 000000000..c44656e76 --- /dev/null +++ b/test/helpers/prettier.mjs @@ -0,0 +1,36 @@ +import test from 'ava' + +import prettier, { serializeFmt } from '../../src/helpers/prettier.js' + +test('js', async t => { + const code = `const mql = require('@microlink/mql') + const { status, data } = await mql('https://geolocation.microlink.io', { apiKey: MyApiToken, proxy: 'https://myproxy:603f60f5@superproxy.cool:8001' }) + console.log(data)` + + const output = await prettier.js(code) + + t.snapshot(output) +}) + +test('json', async t => { + const code = + '{"title":"Wormholes Explained – Breaking Spacetime","description":"To support Kurzgesagt and learn more about Brilliant, go to https://www.brilliant.org/nutshell and sign up for free. The first 688 people that go to that lin...","lang":"en","author":"Kurzgesagt – In a Nutshell","publisher":"YouTube","image":{"url":"https://cdn.microlink.io/data/assets/youtube.com!watch!v=9P6rdqiybaw/img.youtube.com!vi!9P6rdqiybaw!maxresdefault.jpg.jpg","type":"jpg","size":120116,"height":720,"width":1280,"size_pretty":"120 kB","palette":["#C004F9","#EEEEA7","#25047C","#740296","#808018","#2C0494"],"background_color":"#EEEEA7","color":"#AC04DF","alternative_color":"#2C0494"},"audio":{"url":"https://cdn.microlink.io/data/assets/youtube.com!watch!v=9P6rdqiybaw/r3---sn-ab5sznly.googlevideo.com!videoplayback!c=WEB&clen=8935291&dur=552.054&ei=6gpAXv-3POHM8gTqtrm","type":"mp4","duration":552.054422,"size":8935291,"duration_pretty":"9m","size_pretty":"8.94 MB"},"url":"https://www.youtube.com/watch?v=9P6rdqiybaw","iframe":{"html":"","scripts":[]},"date":"2020-02-09T13:36:39.000Z","logo":{"url":"https://cdn.microlink.io/data/assets/youtube.com!watch!v=9P6rdqiybaw/logo.clearbit.com!youtube.com.png","type":"png","size":2421,"height":128,"width":128,"size_pretty":"2.42 kB","palette":["#FC0404","#FC8484","#830101","#970101","#950303","#970101"],"background_color":"#FC0404","color":"#320000","alternative_color":"#320101"},"video":{"url":"https://cdn.microlink.io/data/assets/youtube.com!watch!v=9P6rdqiybaw/r3---sn-ab5sznly.googlevideo.com!videoplayback!c=WEB&dur=552.054&ei=6gpAXv-3POHM8gTqtrmwBA&expire=15","type":"mp4","duration":552.007943,"size":54633895,"height":720,"width":1280,"duration_pretty":"9m","size_pretty":"54.6 MB"}}' + + const output = await prettier.json(code) + + t.snapshot(output) +}) + +test('serializeFmt', t => { + const output = serializeFmt({ + type: 'email', + id: 'input', + placeholder: 'you@domain.com', + suggestions: [{ value: 'you@gmail.com' }, { value: 'you@hotmail.com' }], + width: '9rem', + fontSize: 1, + required: true + }) + + t.snapshot(output) +}) diff --git a/test/helpers/snapshots/prettier.js.md b/test/helpers/snapshots/prettier.js.md deleted file mode 100644 index 0a9c9001e..000000000 --- a/test/helpers/snapshots/prettier.js.md +++ /dev/null @@ -1,19 +0,0 @@ -# Snapshot report for `test/helpers/prettier.js` - -The actual snapshot is saved in `prettier.js.snap`. - -Generated by [AVA](https://avajs.dev). - -## js - -> Snapshot 1 - - `const mql = require('@microlink/mql')␊ - ␊ - const { status, data } = await mql('https://geolocation.microlink.io', {␊ - apiKey: MyApiToken,␊ - proxy: 'https://myproxy:603f60f5@superproxy.cool:8001'␊ - })␊ - ␊ - console.log(data)␊ - ` diff --git a/test/helpers/snapshots/prettier.js.snap b/test/helpers/snapshots/prettier.js.snap deleted file mode 100644 index 02050590d..000000000 Binary files a/test/helpers/snapshots/prettier.js.snap and /dev/null differ diff --git a/test/helpers/snapshots/prettier.mjs.md b/test/helpers/snapshots/prettier.mjs.md new file mode 100644 index 000000000..7a3f1b4df --- /dev/null +++ b/test/helpers/snapshots/prettier.mjs.md @@ -0,0 +1,98 @@ +# Snapshot report for `test/helpers/prettier.mjs` + +The actual snapshot is saved in `prettier.mjs.snap`. + +Generated by [AVA](https://avajs.dev). + +## js + +> Snapshot 1 + + `const mql = require('@microlink/mql')␊ + const { status, data } = await mql('https://geolocation.microlink.io', {␊ + apiKey: MyApiToken,␊ + proxy: 'https://myproxy:603f60f5@superproxy.cool:8001'␊ + })␊ + console.log(data)␊ + ` + +## json + +> Snapshot 1 + + `{␊ + "title": "Wormholes Explained – Breaking Spacetime",␊ + "description": "To support Kurzgesagt and learn more about Brilliant, go to https://www.brilliant.org/nutshell and sign up for free. The first 688 people that go to that lin...",␊ + "lang": "en",␊ + "author": "Kurzgesagt – In a Nutshell",␊ + "publisher": "YouTube",␊ + "image": {␊ + "url": "https://cdn.microlink.io/data/assets/youtube.com!watch!v=9P6rdqiybaw/img.youtube.com!vi!9P6rdqiybaw!maxresdefault.jpg.jpg",␊ + "type": "jpg",␊ + "size": 120116,␊ + "height": 720,␊ + "width": 1280,␊ + "size_pretty": "120 kB",␊ + "palette": [␊ + "#C004F9",␊ + "#EEEEA7",␊ + "#25047C",␊ + "#740296",␊ + "#808018",␊ + "#2C0494"␊ + ],␊ + "background_color": "#EEEEA7",␊ + "color": "#AC04DF",␊ + "alternative_color": "#2C0494"␊ + },␊ + "audio": {␊ + "url": "https://cdn.microlink.io/data/assets/youtube.com!watch!v=9P6rdqiybaw/r3---sn-ab5sznly.googlevideo.com!videoplayback!c=WEB&clen=8935291&dur=552.054&ei=6gpAXv-3POHM8gTqtrm",␊ + "type": "mp4",␊ + "duration": 552.054422,␊ + "size": 8935291,␊ + "duration_pretty": "9m",␊ + "size_pretty": "8.94 MB"␊ + },␊ + "url": "https://www.youtube.com/watch?v=9P6rdqiybaw",␊ + "iframe": {␊ + "html": "",␊ + "scripts": []␊ + },␊ + "date": "2020-02-09T13:36:39.000Z",␊ + "logo": {␊ + "url": "https://cdn.microlink.io/data/assets/youtube.com!watch!v=9P6rdqiybaw/logo.clearbit.com!youtube.com.png",␊ + "type": "png",␊ + "size": 2421,␊ + "height": 128,␊ + "width": 128,␊ + "size_pretty": "2.42 kB",␊ + "palette": [␊ + "#FC0404",␊ + "#FC8484",␊ + "#830101",␊ + "#970101",␊ + "#950303",␊ + "#970101"␊ + ],␊ + "background_color": "#FC0404",␊ + "color": "#320000",␊ + "alternative_color": "#320101"␊ + },␊ + "video": {␊ + "url": "https://cdn.microlink.io/data/assets/youtube.com!watch!v=9P6rdqiybaw/r3---sn-ab5sznly.googlevideo.com!videoplayback!c=WEB&dur=552.054&ei=6gpAXv-3POHM8gTqtrmwBA&expire=15",␊ + "type": "mp4",␊ + "duration": 552.007943,␊ + "size": 54633895,␊ + "height": 720,␊ + "width": 1280,␊ + "duration_pretty": "9m",␊ + "size_pretty": "54.6 MB"␊ + }␊ + }␊ + ` + +## serializeFmt + +> Snapshot 1 + + 'type=\'email\' id=\'input\' placeholder=\'you@domain.com\' suggestions={[{ value: \'you@gmail.com\' }, { value: \'you@hotmail.com\' }]} width=\'9rem\' fontSize={1} required ' diff --git a/test/helpers/snapshots/prettier.mjs.snap b/test/helpers/snapshots/prettier.mjs.snap new file mode 100644 index 000000000..f2e0f2aaa Binary files /dev/null and b/test/helpers/snapshots/prettier.mjs.snap differ