From 020fe18dda3d8eb57de4085bf8ce73856242dba4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Rialland?= Date: Tue, 1 Mar 2022 15:52:55 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20Correction=20de=20deux=20liens?= =?UTF-8?q?=20dans=20les=20metadata-src=20(#2030)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🐛 Correction de deux liens dans les metadata-src ✨ Ajout de type sur le sitePath ✨ Refacto du script algolia:update en typescript * Fix eslint error * Ajout de commentaire sur les types ✨ Refacto du type de retour de getSimulatorsData --- site/package.json | 3 +- site/scripts/search/update-data.js | 157 ------------------ site/scripts/search/update-data.ts | 152 +++++++++++++++++ .../layout/Footer/useShowFeedback.ts | 2 +- .../components/search/SimulatorHits.tsx | 16 +- site/source/pages/Dev/IntegrationTest.tsx | 8 +- site/source/pages/Iframes/index.tsx | 34 ++-- site/source/pages/Simulateurs/Home.tsx | 13 +- .../pages/Simulateurs/Page/NextSteps.tsx | 10 +- site/source/pages/Simulateurs/Page/index.tsx | 22 ++- .../{metadata-src.js => metadata-src.ts} | 17 +- site/source/pages/Simulateurs/metadata.tsx | 155 ++++++----------- site/source/pages/Stats/Stats.tsx | 2 +- site/source/pages/integration/Iframe.tsx | 72 +++++--- site/source/sitePaths.ts | 51 ++++-- site/tsconfig.json | 1 + yarn.lock | 88 +++++++++- 17 files changed, 463 insertions(+), 340 deletions(-) delete mode 100644 site/scripts/search/update-data.js create mode 100644 site/scripts/search/update-data.ts rename site/source/pages/Simulateurs/{metadata-src.js => metadata-src.ts} (98%) diff --git a/site/package.json b/site/package.json index 54e3299ef..72a490d63 100644 --- a/site/package.json +++ b/site/package.json @@ -32,7 +32,7 @@ "test:dev-e2e:mon-entreprise": "cypress open --browser chromium", "test:dev-e2e:mycompanyinfrance": "cypress open --browser chromium --config baseUrl=http://localhost:8080/infrance,integrationFolder=cypress/integration/mon-entreprise/english --env language=en", "test:record-http-calls:mon-entreprise": "cypress run --env record_http=", - "algolia:update": "node scripts/search/update-data.js", + "algolia:update": "node --loader ts-node/esm scripts/search/update-data.ts", "algolia:clean": "node scripts/search/clean.js", "i18n:check": "yarn i18n:rules:check && yarn i18n:ui:check", "i18n:translate": "yarn i18n:rules:translate && yarn i18n:ui:translate", @@ -93,6 +93,7 @@ "reduce-reducers": "^1.0.4", "redux": "^4.0.4", "styled-components": "^5.3.1", + "ts-node": "^10.5.0", "whatwg-fetch": "^3.0.0", "yaml": "^1.9.2" }, diff --git a/site/scripts/search/update-data.js b/site/scripts/search/update-data.js deleted file mode 100644 index f1c78674d..000000000 --- a/site/scripts/search/update-data.js +++ /dev/null @@ -1,157 +0,0 @@ -import algoliasearch from 'algoliasearch' -import 'dotenv/config.js' -import rawRules from 'modele-social' -import { parsePublicodes } from 'publicodes' -import getSimulationData from '../../source/pages/Simulateurs/metadata-src.js' - -const rules = parsePublicodes(rawRules) - -const ALGOLIA_APP_ID = process.env.ALGOLIA_APP_ID -const ALGOLIA_ADMIN_KEY = process.env.ALGOLIA_ADMIN_KEY -const ALGOLIA_INDEX_PREFIX = process.env.ALGOLIA_INDEX_PREFIX || '' - -const client = algoliasearch(ALGOLIA_APP_ID, ALGOLIA_ADMIN_KEY) - -const rulesIndex = client.initIndex(`${ALGOLIA_INDEX_PREFIX}rules`) -const simulateursIndex = client.initIndex(`${ALGOLIA_INDEX_PREFIX}simulateurs`) - -const formatRulesToAlgolia = (rules) => - Object.entries(rules) - .map(([n, rule]) => { - if (!rule) return - const path = n.split(' . ') - const { - title, - rawNode: { icônes = '', description, acronyme, résumé }, - } = rule - const ruleName = `${title} ${' ' + icônes}`.trim() - const namespace = path.slice(0, -1) - - return { - objectID: n, - path, - ruleName, - namespace, - pathDepth: path.length, - acronyme: acronyme, - titre: title, - icone: icônes, - description: description || résumé, - } - }) - .filter(Boolean) - -const formatSimulationDataToAlgolia = (simulations) => - Object.entries(simulations).map(([id, simulation]) => ({ - ...simulation, - objectID: id, - title: simulation.title || simulation.shortName || simulation.meta.title, - tooltip: simulation.tooltip || '', - description: simulation.meta?.description, - })) - -;(async function () { - try { - console.log('Algolia update START') - - console.log('Clearing: rules') - await rulesIndex.clearObjects().wait() - console.log('Configure index: rules') - await rulesIndex - .setSettings({ - // Parameters are documented on Algolia website https://www.algolia.com/doc/api-reference/api-parameters/ - minWordSizefor1Typo: 4, - minWordSizefor2Typos: 8, - hitsPerPage: 20, - maxValuesPerFacet: 100, - attributesToIndex: ['unordered(ruleName)', 'unordered(namespace)'], - numericAttributesToIndex: null, - attributesToRetrieve: null, - unretrievableAttributes: null, - optionalWords: null, - attributesForFaceting: null, - attributesToSnippet: null, - attributesToHighlight: ['ruleName', 'namespace'], - paginationLimitedTo: 1000, - attributeForDistinct: null, - exactOnSingleWordQuery: 'attribute', - ranking: [ - 'typo', - 'geo', - 'words', - 'filters', - 'proximity', - 'attribute', - 'exact', - 'custom', - ], - customRanking: ['asc(pathDepth)'], - separatorsToIndex: '', - removeWordsIfNoResults: 'none', - queryType: 'prefixLast', - highlightPreTag: '', - highlightPostTag: '', - snippetEllipsisText: '', - alternativesAsExact: ['ignorePlurals', 'singleWordSynonym'], - }) - .wait() - - console.log('Uploading: rules') - - await rulesIndex.saveObjects(formatRulesToAlgolia(rules)).wait() - - console.log('Clearing: simulateurs') - await simulateursIndex.clearObjects().wait() - console.log('Configure index: simulateurs') - await simulateursIndex - .setSettings({ - // Parameters are documented on Algolia website https://www.algolia.com/doc/api-reference/api-parameters/ - minWordSizefor1Typo: 4, - minWordSizefor2Typos: 8, - hitsPerPage: 20, - maxValuesPerFacet: 100, - attributesToIndex: [ - 'unordered(title)', - 'unordered(tooltip)', - 'unordered(description)', - ], - numericAttributesToIndex: null, - attributesToRetrieve: null, - unretrievableAttributes: null, - optionalWords: null, - attributesForFaceting: null, - attributesToSnippet: null, - attributesToHighlight: ['title'], - paginationLimitedTo: 1000, - attributeForDistinct: null, - exactOnSingleWordQuery: 'attribute', - ranking: [ - 'typo', - 'geo', - 'words', - 'filters', - 'proximity', - 'attribute', - 'exact', - 'custom', - ], - customRanking: null, - separatorsToIndex: '', - removeWordsIfNoResults: 'none', - queryType: 'prefixLast', - highlightPreTag: '', - highlightPostTag: '', - snippetEllipsisText: '', - alternativesAsExact: ['ignorePlurals', 'singleWordSynonym'], - }) - .wait() - console.log('Uploading: simulateurs') - await simulateursIndex - .saveObjects(formatSimulationDataToAlgolia(getSimulationData())) - .wait() - - console.log('Algolia update DONE') - } catch (e) { - console.log(JSON.stringify(e, null, 2)) - } -})() diff --git a/site/scripts/search/update-data.ts b/site/scripts/search/update-data.ts new file mode 100644 index 000000000..15608e145 --- /dev/null +++ b/site/scripts/search/update-data.ts @@ -0,0 +1,152 @@ +import algoliasearch from 'algoliasearch' +import 'dotenv/config.js' +import rawRules from 'modele-social' +import { ParsedRules, parsePublicodes } from 'publicodes' +import getSimulationData, { + MetadataSrc, +} from '../../source/pages/Simulateurs/metadata-src.js' + +const rules = parsePublicodes(rawRules) + +// @ts-ignore Needed by ts-node/esm +const env = process.env + +const ALGOLIA_APP_ID = env.ALGOLIA_APP_ID || '' +const ALGOLIA_ADMIN_KEY = env.ALGOLIA_ADMIN_KEY || '' +const ALGOLIA_INDEX_PREFIX = env.ALGOLIA_INDEX_PREFIX || '' + +const client = algoliasearch(ALGOLIA_APP_ID, ALGOLIA_ADMIN_KEY) + +const rulesIndex = client.initIndex(`${ALGOLIA_INDEX_PREFIX}rules`) +const simulateursIndex = client.initIndex(`${ALGOLIA_INDEX_PREFIX}simulateurs`) + +const falsy = (value: T | false): value is T => Boolean(value) + +const formatRulesToAlgolia = (rules: ParsedRules) => + Object.entries(rules) + .map(([n, rule]) => { + if (!rule) return false + const path = n.split(' . ') + const { + title, + rawNode: { icônes = '', description, acronyme, résumé }, + } = rule + const ruleName = `${title} ${' ' + icônes}`.trim() + const namespace = path.slice(0, -1) + + return { + objectID: n, + path, + ruleName, + namespace, + pathDepth: path.length, + acronyme: acronyme, + titre: title, + icone: icônes, + description: description || résumé, + } + }) + .filter(falsy) + +const formatSimulationDataToAlgolia = (simulations: MetadataSrc) => + Object.entries(simulations).map(([id, simulation]) => ({ + ...simulation, + objectID: id, + title: + ('title' in simulation && simulation.title) || + simulation.shortName || + simulation.meta.title, + tooltip: ('tooltip' in simulation && simulation.tooltip) || '', + description: simulation.meta?.description, + })) + +try { + console.log('Algolia update START') + + console.log('Clearing: rules') + await rulesIndex.clearObjects().wait() + console.log('Configure index: rules') + await rulesIndex + .setSettings({ + // Parameters are documented on Algolia website https://www.algolia.com/doc/api-reference/api-parameters/ + minWordSizefor1Typo: 4, + minWordSizefor2Typos: 8, + hitsPerPage: 20, + maxValuesPerFacet: 100, + attributesToIndex: ['unordered(ruleName)', 'unordered(namespace)'], + attributesToHighlight: ['ruleName', 'namespace'], + paginationLimitedTo: 1000, + exactOnSingleWordQuery: 'attribute', + ranking: [ + 'typo', + 'geo', + 'words', + 'filters', + 'proximity', + 'attribute', + 'exact', + 'custom', + ], + customRanking: ['asc(pathDepth)'], + separatorsToIndex: '', + removeWordsIfNoResults: 'none', + queryType: 'prefixLast', + highlightPreTag: '', + highlightPostTag: '', + snippetEllipsisText: '', + alternativesAsExact: ['ignorePlurals', 'singleWordSynonym'], + }) + .wait() + + console.log('Uploading: rules') + + await rulesIndex.saveObjects(formatRulesToAlgolia(rules)).wait() + + console.log('Clearing: simulateurs') + await simulateursIndex.clearObjects().wait() + console.log('Configure index: simulateurs') + await simulateursIndex + .setSettings({ + // Parameters are documented on Algolia website https://www.algolia.com/doc/api-reference/api-parameters/ + minWordSizefor1Typo: 4, + minWordSizefor2Typos: 8, + hitsPerPage: 20, + maxValuesPerFacet: 100, + attributesToIndex: [ + 'unordered(title)', + 'unordered(tooltip)', + 'unordered(description)', + ], + attributesToHighlight: ['title'], + paginationLimitedTo: 1000, + exactOnSingleWordQuery: 'attribute', + ranking: [ + 'typo', + 'geo', + 'words', + 'filters', + 'proximity', + 'attribute', + 'exact', + 'custom', + ], + separatorsToIndex: '', + removeWordsIfNoResults: 'none', + queryType: 'prefixLast', + highlightPreTag: '', + highlightPostTag: '', + snippetEllipsisText: '', + alternativesAsExact: ['ignorePlurals', 'singleWordSynonym'], + }) + .wait() + console.log('Uploading: simulateurs') + await simulateursIndex + .saveObjects( + formatSimulationDataToAlgolia(getSimulationData((_, text) => text)) + ) + .wait() + + console.log('Algolia update DONE') +} catch (e) { + console.log(JSON.stringify(e, null, 2)) +} diff --git a/site/source/components/layout/Footer/useShowFeedback.ts b/site/source/components/layout/Footer/useShowFeedback.ts index 618af0158..558cb255f 100644 --- a/site/source/components/layout/Footer/useShowFeedback.ts +++ b/site/source/components/layout/Footer/useShowFeedback.ts @@ -13,7 +13,7 @@ export const useShowFeedback = () => { simulators['comparaison-statuts'], simulators['demande-mobilité'], ] - .map((s) => s.path) + .map((s) => s.path as string) .includes(currentPath) ) { return true diff --git a/site/source/components/search/SimulatorHits.tsx b/site/source/components/search/SimulatorHits.tsx index 3b0bfa74d..4afba236d 100644 --- a/site/source/components/search/SimulatorHits.tsx +++ b/site/source/components/search/SimulatorHits.tsx @@ -4,7 +4,8 @@ import { SitePathsContext } from 'Components/utils/SitePathsContext' import { SmallCard } from 'DesignSystem/card' import InfoBulle from 'DesignSystem/InfoBulle' import { H3 } from 'DesignSystem/typography/heading' -import { SimulatorData } from 'pages/Simulateurs/metadata' +import { ExtractFromSimuData } from 'pages/Simulateurs/metadata' +import { MetadataSrc } from 'pages/Simulateurs/metadata-src' import { path } from 'ramda' import { useContext } from 'react' import { Trans } from 'react-i18next' @@ -15,7 +16,7 @@ import { Highlight } from './Hightlight' type AlgoliaSimulatorHit = Hit<{ icône: string title: string - pathId: string + pathId: MetadataSrc[keyof MetadataSrc]['pathId'] }> type SimulatorHitsProps = { @@ -26,7 +27,9 @@ const SimulateurCardHit = ({ hit, path, tooltip, -}: Pick & { +}: { + path: ExtractFromSimuData<'path'> + tooltip?: ExtractFromSimuData<'tooltip'> hit: AlgoliaSimulatorHit }) => { return ( @@ -65,7 +68,12 @@ export const SimulatorHits = connectHits< + } /> ) diff --git a/site/source/pages/Dev/IntegrationTest.tsx b/site/source/pages/Dev/IntegrationTest.tsx index e29995c58..fa0767669 100644 --- a/site/source/pages/Dev/IntegrationTest.tsx +++ b/site/source/pages/Dev/IntegrationTest.tsx @@ -9,11 +9,13 @@ export default function IntegrationTest() { const integrableModuleNames = useMemo( () => Object.values(simulators) - .map((s) => s.iframePath) - .filter(Boolean), + .map((s) => 'iframePath' in s && s.iframePath) + .filter(((el) => Boolean(el)) as (x: T | false) => x is T), [simulators] ) - const [currentModule, setCurrentModule] = useState(integrableModuleNames[0]) + const [currentModule, setCurrentModule] = useState( + integrableModuleNames[0] + ) const [color, setColor] = useState('#005aa1') const [version, setVersion] = useState(0) const domNode = useRef(null) diff --git a/site/source/pages/Iframes/index.tsx b/site/source/pages/Iframes/index.tsx index 7050f648f..57a64291d 100644 --- a/site/source/pages/Iframes/index.tsx +++ b/site/source/pages/Iframes/index.tsx @@ -30,21 +30,25 @@ export default function Iframes() { {Object.values(simulators) - .filter(({ iframePath }) => !!iframePath) - .map((s) => ( - ( - <> - - - - - - )} - /> - ))} + .filter((el) => !!('iframePath' in el && el.iframePath)) + .map( + (s) => + 'iframePath' in s && + s.iframePath && ( + ( + <> + + + + + + )} + /> + ) + )} diff --git a/site/source/pages/Simulateurs/Home.tsx b/site/source/pages/Simulateurs/Home.tsx index 8d3967ed8..bd40035d9 100644 --- a/site/source/pages/Simulateurs/Home.tsx +++ b/site/source/pages/Simulateurs/Home.tsx @@ -18,7 +18,7 @@ import { ThemeProvider } from 'styled-components' import { TrackPage } from '../../ATInternetTracking' import Meta from '../../components/utils/Meta' import simulatorSvg from './images/illustration-simulateur.svg' -import useSimulatorsData, { SimulatorData } from './metadata' +import useSimulatorsData, { ExtractFromSimuData } from './metadata' export default function Simulateurs() { const { t } = useTranslation() @@ -135,10 +135,13 @@ export function SimulateurCard({ iframePath, fromGérer = false, icône, -}: Pick< - SimulatorData[keyof SimulatorData], - 'shortName' | 'meta' | 'path' | 'tooltip' | 'iframePath' | 'icône' -> & { +}: { + shortName: ExtractFromSimuData<'shortName'> + meta: ExtractFromSimuData<'meta'> + path: ExtractFromSimuData<'path'> + tooltip?: ExtractFromSimuData<'tooltip'> + iframePath?: ExtractFromSimuData<'iframePath'> + icône: ExtractFromSimuData<'icône'> small?: boolean fromGérer?: boolean }) { diff --git a/site/source/pages/Simulateurs/Page/NextSteps.tsx b/site/source/pages/Simulateurs/Page/NextSteps.tsx index 08456372c..989fc06fe 100644 --- a/site/source/pages/Simulateurs/Page/NextSteps.tsx +++ b/site/source/pages/Simulateurs/Page/NextSteps.tsx @@ -9,12 +9,12 @@ import { FAQAutoEntrepreneurArticle } from '../../../pages/Creer/CreationCheckli import { GuideURSSAFCard } from '../cards/GuideURSSAFCard' import { IframeIntegrationCard } from '../cards/IframeIntegrationCard' import { SimulatorRessourceCard } from '../cards/SimulatorRessourceCard' -import { SimulatorData } from '../metadata' +import { ExtractFromSimuData } from '../metadata' -type NextStepsProps = Pick< - SimulatorData[keyof SimulatorData], - 'iframePath' | 'nextSteps' -> +interface NextStepsProps { + iframePath: ExtractFromSimuData<'iframePath'> + nextSteps: ExtractFromSimuData<'nextSteps'> +} export function NextSteps({ iframePath, nextSteps }: NextStepsProps) { const sitePaths = useContext(SitePathsContext) diff --git a/site/source/pages/Simulateurs/Page/index.tsx b/site/source/pages/Simulateurs/Page/index.tsx index da7415304..2fb45b4a9 100644 --- a/site/source/pages/Simulateurs/Page/index.tsx +++ b/site/source/pages/Simulateurs/Page/index.tsx @@ -11,10 +11,28 @@ import { ComponentPropsWithoutRef, useContext } from 'react' import { useTranslation } from 'react-i18next' import { useLocation } from 'react-router-dom' import { TrackChapter } from '../../../ATInternetTracking' -import { CurrentSimulatorDataProvider, SimulatorData } from '../metadata' +import { CurrentSimulatorDataProvider, ExtractFromSimuData } from '../metadata' import { NextSteps } from './NextSteps' -export default function PageData(props: SimulatorData[keyof SimulatorData]) { +export interface PageDataProps { + meta: ExtractFromSimuData<'meta'> + config?: ExtractFromSimuData<'config'> + tracking: ExtractFromSimuData<'tracking'> + tooltip?: ExtractFromSimuData<'tooltip'> + description?: ExtractFromSimuData<'description'> + iframePath?: ExtractFromSimuData<'iframePath'> + seoExplanations?: ExtractFromSimuData<'seoExplanations'> + nextSteps?: ExtractFromSimuData<'nextSteps'> + path: ExtractFromSimuData<'path'> + title?: ExtractFromSimuData<'title'> + private?: ExtractFromSimuData<'private'> + component: ExtractFromSimuData<'component'> + icône: ExtractFromSimuData<'icône'> + pathId: ExtractFromSimuData<'pathId'> + shortName: ExtractFromSimuData<'shortName'> +} + +export default function PageData(props: PageDataProps) { const { meta, config, diff --git a/site/source/pages/Simulateurs/metadata-src.js b/site/source/pages/Simulateurs/metadata-src.ts similarity index 98% rename from site/source/pages/Simulateurs/metadata-src.js rename to site/source/pages/Simulateurs/metadata-src.ts index 8d4cb6080..b719c0f85 100644 --- a/site/source/pages/Simulateurs/metadata-src.js +++ b/site/source/pages/Simulateurs/metadata-src.ts @@ -1,10 +1,12 @@ +import { TFunction } from 'react-i18next' + /** * Contient l'intégralité des données concernant les différents simulateurs * sans dépendance qui compliquerait leur import dans le script de mise à jour * des données pour Algolia. */ -export default ({ t = (_, text) => text } = {}) => { - return { +const metadataSrc = (t: TFunction<'translation', string>) => { + const data = { salarié: { tracking: 'salarie', icône: '🤝', @@ -359,7 +361,7 @@ export default ({ t = (_, text) => text } = {}) => { 'Déclaration de revenus indépendant : calcul du montant des cotisations' ), }, - pathId: 'simulateurs.déclarationIndépendant', + pathId: 'gérer.déclarationIndépendant', shortName: t( 'pages.gérer.aide-déclaration-indépendant.shortname', 'Aide à la déclaration de revenu' @@ -638,7 +640,7 @@ export default ({ t = (_, text) => text } = {}) => { ), color: '#11965f', }, - pathId: 'simulateurs.aide-embauche', + pathId: 'simulateurs.aides-embauche', iframePath: 'aides-embauche', shortName: t( 'pages.simulateurs.aides-embauche.meta.title', @@ -696,5 +698,10 @@ export default ({ t = (_, text) => text } = {}) => { nextSteps: ['salarié', 'is', 'comparaison-statuts'], }, - } + } as const + + return data } + +export type MetadataSrc = ReturnType +export default metadataSrc diff --git a/site/source/pages/Simulateurs/metadata.tsx b/site/source/pages/Simulateurs/metadata.tsx index 3b211a50b..db8fd5275 100644 --- a/site/source/pages/Simulateurs/metadata.tsx +++ b/site/source/pages/Simulateurs/metadata.tsx @@ -5,9 +5,9 @@ import { H2 } from 'DesignSystem/typography/heading' import { Link } from 'DesignSystem/typography/link' import { Body } from 'DesignSystem/typography/paragraphs' import React, { createContext, useContext, useMemo } from 'react' -import { Trans, useTranslation } from 'react-i18next' +import { TFunction, Trans, useTranslation } from 'react-i18next' import { SimulationConfig } from 'Reducers/rootReducer' -import { constructLocalizedSitePath } from '../../sitePaths' +import { constructLocalizedSitePath, SitePathsType } from '../../sitePaths' import Créer from '../Creer/Home' import AideDéclarationIndépendant from '../Gerer/AideDéclarationIndépendant' import FormulaireMobilitéIndépendant from '../Gerer/DemandeMobilite' @@ -43,91 +43,23 @@ import SalariéSimulation from './Salarié' import { SASUSimulation } from './SASU' import SchemeComparaisonPage from './SchemeComparaison' -const simulateurs = [ - 'salarié', - 'auto-entrepreneur', - 'indépendant', - 'eirl', - 'eurl', - 'sasu', - 'chômage-partiel', - 'artiste-auteur', - 'comparaison-statuts', - 'entreprise-individuelle', - 'économie-collaborative', - 'aide-déclaration-indépendant', - 'demande-mobilité', - 'profession-libérale', - 'médecin', - 'choix-statut', - 'pharmacien', - 'chirurgien-dentiste', - 'sage-femme', - 'auxiliaire-médical', - 'avocat', - 'expert-comptable', - 'pamc', - 'is', - 'aides-embauche', - 'dividendes', -] as const +interface SimulatorsDataParams { + t?: TFunction<'translation', string> + sitePaths?: SitePathsType + language?: string +} -export type SimulatorId = typeof simulateurs[number] - -export type PureSimulatorData = Record< - SimulatorId, - { - meta: { - title: string - description: string - ogTitle?: string - ogDescription?: string - ogImage?: string - color?: string - } - tracking: - | { - chapter2?: string - chapter3?: string - chapter1?: string - } - | string - icône?: string - shortName: string - path?: string - tooltip?: string - iframePath?: string - title?: string - description?: React.ReactNode - config?: SimulationConfig - seoExplanations?: React.ReactNode - private?: boolean - pathId: string - } -> -export type SimulatorData = PureSimulatorData & - Record< - SimulatorId, - { - path?: string - description?: React.ReactNode - config?: SimulationConfig - seoExplanations?: React.ReactNode - nextSteps?: Array - component: () => JSX.Element - } - > - -export function getSimulatorsData({ - t = (_: unknown, text: string) => text, +function getSimulatorsData({ + t = (_, text) => text, sitePaths = constructLocalizedSitePath('fr'), language = 'fr', -} = {}): SimulatorData { - const pureSimulatorsData: PureSimulatorData = getData({ t }) +}: SimulatorsDataParams = {}) { + const pureSimulatorsData = getData(t) + return { salarié: { ...pureSimulatorsData['salarié'], - config: salariéConfig, + config: salariéConfig as SimulationConfig, component: SalariéSimulation, meta: { ...pureSimulatorsData['salarié'].meta, @@ -210,7 +142,7 @@ export function getSimulatorsData({ }, 'entreprise-individuelle': { ...pureSimulatorsData['entreprise-individuelle'], - config: entrepriseIndividuelleConfig, + config: entrepriseIndividuelleConfig as SimulationConfig, meta: { ...pureSimulatorsData['entreprise-individuelle']?.meta, ogImage: AutoEntrepreneurPreview, @@ -283,7 +215,7 @@ export function getSimulatorsData({ }, eirl: { ...pureSimulatorsData['eirl'], - config: indépendantConfig, + config: indépendantConfig as SimulationConfig, meta: { ...pureSimulatorsData['eirl']?.meta, ogImage: AutoEntrepreneurPreview, @@ -294,7 +226,7 @@ export function getSimulatorsData({ }, sasu: { ...pureSimulatorsData['sasu'], - config: sasuConfig, + config: sasuConfig as SimulationConfig, meta: { ...pureSimulatorsData['sasu']?.meta, ogImage: RémunérationSASUPreview, @@ -345,7 +277,7 @@ export function getSimulatorsData({ }, eurl: { ...pureSimulatorsData['eurl'], - config: eurlConfig, + config: eurlConfig as SimulationConfig, meta: { ...pureSimulatorsData['eurl']?.meta, ogImage: RémunérationSASUPreview, @@ -356,7 +288,7 @@ export function getSimulatorsData({ 'auto-entrepreneur': { ...pureSimulatorsData['auto-entrepreneur'], tracking: 'auto_entrepreneur', - config: autoEntrepreneurConfig, + config: autoEntrepreneurConfig as SimulationConfig, meta: { ...pureSimulatorsData['auto-entrepreneur']?.meta, ogImage: AutoEntrepreneurPreview, @@ -430,7 +362,7 @@ export function getSimulatorsData({ }, indépendant: { ...pureSimulatorsData['indépendant'], - config: indépendantConfig, + config: indépendantConfig as SimulationConfig, path: sitePaths.simulateurs.indépendant, component: IndépendantSimulation, }, @@ -442,7 +374,7 @@ export function getSimulatorsData({ 'chômage-partiel': { ...pureSimulatorsData['chômage-partiel'], component: ChômagePartielComponent, - config: chômageParielConfig, + config: chômageParielConfig as SimulationConfig, path: sitePaths.simulateurs['chômage-partiel'], meta: { ...pureSimulatorsData['chômage-partiel']?.meta, @@ -591,14 +523,14 @@ export function getSimulatorsData({ }, 'profession-libérale': { ...pureSimulatorsData['profession-libérale'], - config: professionLibéraleConfig, + config: professionLibéraleConfig as SimulationConfig, path: sitePaths.simulateurs['profession-libérale'].index, component: IndépendantPLSimulation, }, pamc: { ...pureSimulatorsData['pamc'], path: sitePaths.simulateurs.pamc, - config: professionLibéraleConfig, + config: professionLibéraleConfig as SimulationConfig, component: PAMCHome, }, 'aides-embauche': { @@ -674,7 +606,7 @@ export function getSimulatorsData({ ...pureSimulatorsData['dividendes'], path: sitePaths.simulateurs.dividendes, component: DividendesSimulation, - config: dividendesConfig, + config: dividendesConfig as SimulationConfig, seoExplanations: (

Les dividendes et distributions

@@ -724,9 +656,25 @@ export function getSimulatorsData({
), }, - } + } as const } +export type SimulatorData = ReturnType + +/** + * Extract type if U extends T + * Else return an object with value undefined + */ +type ExtractOrUndefined = T extends U ? T : Record + +/** + * Extract type from a key of SimulatorData + */ +export type ExtractFromSimuData = ExtractOrUndefined< + SimulatorData[keyof SimulatorData], + Record +>[T] + export default function useSimulatorsData(): SimulatorData { const { t, i18n } = useTranslation() const sitePaths = useContext(SitePathsContext) @@ -737,18 +685,19 @@ export default function useSimulatorsData(): SimulatorData { ) } -export const CurrentSimulatorDataContext = createContext< - SimulatorData[keyof SimulatorData] | null ->(null) +type Overwrite = Pick> & U + +export const CurrentSimulatorDataContext = createContext } +> | null>(null) export const CurrentSimulatorDataProvider = CurrentSimulatorDataContext.Provider -professionLibéraleConfig as SimulationConfig - const configFromPLMetier = (metier: string): SimulationConfig => ({ - ...professionLibéraleConfig, + ...(professionLibéraleConfig as SimulationConfig), situation: { - ...professionLibéraleConfig.situation, + ...(professionLibéraleConfig as SimulationConfig).situation, 'entreprise . activité . libérale réglementée': 'oui', 'dirigeant . indépendant . PL . métier': `'${metier}'`, }, @@ -762,16 +711,16 @@ const sageFemmeConfig = configFromPLMetier('santé . sage-femme') const avocatConfig = configFromPLMetier('avocat') const expertComptableConfig = configFromPLMetier('expert-comptable') const eurlConfig = { - ...indépendantConfig, + ...(indépendantConfig as SimulationConfig), situation: { - ...indépendantConfig.situation, + ...(indépendantConfig as SimulationConfig).situation, 'entreprise . imposition': "'IS'", }, } const entrepriseIndividuelleConfig = { - ...indépendantConfig, + ...(indépendantConfig as SimulationConfig), situation: { - ...indépendantConfig.situation, + ...(indépendantConfig as SimulationConfig).situation, 'entreprise . imposition': "'IR'", }, } diff --git a/site/source/pages/Stats/Stats.tsx b/site/source/pages/Stats/Stats.tsx index 98e057a09..051116333 100644 --- a/site/source/pages/Stats/Stats.tsx +++ b/site/source/pages/Stats/Stats.tsx @@ -293,7 +293,7 @@ export default function Stats() { } function getChapter2(s: SimulatorData[keyof SimulatorData]): Chapter2 | '' { - if (s.iframePath === 'pamc') { + if ('iframePath' in s && s.iframePath === 'pamc') { return 'PAM' } if (!s.tracking) { diff --git a/site/source/pages/integration/Iframe.tsx b/site/source/pages/integration/Iframe.tsx index f1052617c..1ca3eb730 100644 --- a/site/source/pages/integration/Iframe.tsx +++ b/site/source/pages/integration/Iframe.tsx @@ -22,7 +22,7 @@ import Créer from '../Creer' import Documentation from '../Documentation' import Iframes from '../Iframes' import Simulateurs from '../Simulateurs' -import useSimulatorsData from '../Simulateurs/metadata' +import useSimulatorsData, { SimulatorData } from '../Simulateurs/metadata' import './iframe.css' import apecLogo from './images/apec.png' import cciLogo from './images/cci.png' @@ -31,13 +31,22 @@ import poleEmploiLogo from './images/pole-emploi.png' const LazyColorPicker = lazy(() => import('../Dev/ColorPicker')) +const checkIframe = (obj: SimulatorData[keyof SimulatorData]) => + 'iframePath' in obj && obj.iframePath && !('private' in obj && obj.private) + +const getFromSimu = ( + obj: S, + key: T +) => + key in obj && + obj[key as keyof SimulatorData] && + checkIframe(obj[key as keyof SimulatorData]) + ? obj[key as keyof SimulatorData] + : undefined + function IntegrationCustomizer() { const { search } = useLocation() - const simulators = Object.fromEntries( - Object.entries(useSimulatorsData()).filter( - ([, s]) => s.iframePath && !s.private - ) - ) + const simulatorsData = useSimulatorsData() const sitePaths = useContext(SitePathsContext) const history = useHistory() @@ -45,7 +54,9 @@ function IntegrationCustomizer() { new URLSearchParams(search ?? '').get('module') ?? '' const [currentModule, setCurrentModule] = useState( - simulators[defaultModuleFromUrl] ? defaultModuleFromUrl : 'salarié' + getFromSimu(simulatorsData, defaultModuleFromUrl) + ? defaultModuleFromUrl + : 'salarié' ) useEffect(() => { @@ -54,6 +65,14 @@ function IntegrationCustomizer() { const [color, setColor] = useState() + const currentSimulator = getFromSimu(simulatorsData, currentModule) + + const currentIframePath = + (currentSimulator && + 'iframePath' in currentSimulator && + currentSimulator.iframePath) || + '' + return ( <>

@@ -70,17 +89,25 @@ function IntegrationCustomizer() { onSelectionChange={(val) => setCurrentModule(String(val))} selectedKey={currentModule} > - {Object.entries(simulators).map(([module, s]) => ( - - {s.icône && ( - <> - -   - - )} - {s.shortName ?? s.title} - - ))} + {Object.entries(simulatorsData) + .map( + ([module, s]) => + getFromSimu(simulatorsData, module) && ( + + {s.icône && ( + <> + +   + + )} + {s.shortName ?? ('title' in s ? s.title : '')} + + ) + ) + .filter(((el) => Boolean(el)) as (x: T | undefined) => x is T)}

@@ -103,10 +130,7 @@ function IntegrationCustomizer() { Voici le code à copier-coller sur votre site : - +

@@ -115,9 +139,7 @@ function IntegrationCustomizer() { = [ ] type LocalizedPath = string -type PathFactory = (...args: Array) => LocalizedPath - type SitePathObject = { index: LocalizedPath } & { - [key in keyof T]: string | PathFactory | SitePathObject + [key in keyof T]: string | SitePathObject } -const sitePathsFr = { +const rawSitePathsFr = { index: '', créer: { index: '/créer', @@ -92,10 +91,10 @@ const sitePathsFr = { }, } as const -const sitePathsEn = { - ...sitePathsFr, +const rawSitePathsEn = { + ...rawSitePathsFr, créer: { - ...sitePathsFr.créer, + ...rawSitePathsFr.créer, index: '/create', après: '/after-registration', guideStatut: { @@ -150,30 +149,58 @@ const sitePathsEn = { accessibilité: '/accessibility', integration: { - ...sitePathsFr.integration, + ...rawSitePathsFr.integration, index: '/integration', library: '/library', }, } as const +/** + * Le but des types suivants est d'obtenir un typage statique des chaînes de caractères + * comme "simulateurs.auto-entrepreneur" utilisés comme identifiants des routes (via les pathId dans metadat-src.ts). + * Cela permet de ne pas avoir de faute dans les clés comme 'aide-embauche' au lieu de 'aides-embauche' + */ + +// Transfrom string type like PathToType<'simulateurs.auto-entrepreneur', number> +// into { simulateurs : { auto-entrepreneur: number }} +type PathToType = T extends `${infer U}.${infer V}` + ? { [key in U]: V extends string ? PathToType : never } + : { [key in T]: W } + +// Transform type A | B into A & B +type UnionToIntersection = ( + T extends unknown ? (x: T) => void : never +) extends (x: infer R) => void + ? R + : never + +// Union of pathId +type PathIds = MetadataSrc[keyof MetadataSrc]['pathId'] + +type RequiredPath = Required>> + +// If there is a type error here, check rawSitePathsFr object matches the metadata-src.ts pathId +const checkedSitePathsFr: RequiredPath & typeof rawSitePathsFr = rawSitePathsFr + +// If there is a type error here, check rawSitePathsEn object matches the metadata-src.ts pathId +const checkedSitePathsEn: RequiredPath & typeof rawSitePathsEn = rawSitePathsEn + function constructSitePaths>( root: string, { index, ...sitePaths }: T ): T { return { index: root + index, - ...map((value: LocalizedPath | PathFactory | SitePathObject) => + ...map((value: LocalizedPath | SitePathObject) => typeof value === 'string' ? root + index + value - : typeof value === 'function' - ? (...args: Array) => root + index + String(value(...args)) : constructSitePaths(root + index, value as any) )(sitePaths as any), } as any } export const constructLocalizedSitePath = (language: 'en' | 'fr') => { - const sitePaths = language === 'fr' ? sitePathsFr : sitePathsEn + const sitePaths = language === 'fr' ? checkedSitePathsFr : checkedSitePathsEn return constructSitePaths('', sitePaths) } diff --git a/site/tsconfig.json b/site/tsconfig.json index 7135c9697..800c261d0 100644 --- a/site/tsconfig.json +++ b/site/tsconfig.json @@ -31,6 +31,7 @@ }, "include": [ "source", + "scripts", "test/**/*.ts", "vite.config.ts", "vite-iframe-script.config.ts" diff --git a/yarn.lock b/yarn.lock index 2983a9f71..a1e8674f3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -364,6 +364,18 @@ "@babel/helper-validator-identifier" "^7.16.7" to-fast-properties "^2.0.0" +"@cspotcode/source-map-consumer@0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b" + integrity sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg== + +"@cspotcode/source-map-support@0.7.0": + version "0.7.0" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz#4789840aa859e46d2f3173727ab707c66bf344f5" + integrity sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA== + dependencies: + "@cspotcode/source-map-consumer" "0.8.0" + "@cypress/request@^2.88.5": version "2.88.10" resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.10.tgz#b66d76b07f860d3a4b8d7a0604d020c662752cce" @@ -1591,6 +1603,26 @@ "@sentry/types" "6.17.2" tslib "^1.9.3" +"@tsconfig/node10@^1.0.7": + version "1.0.8" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9" + integrity sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg== + +"@tsconfig/node12@^1.0.7": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.9.tgz#62c1f6dee2ebd9aead80dc3afa56810e58e1a04c" + integrity sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw== + +"@tsconfig/node14@^1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.1.tgz#95f2d167ffb9b8d2068b0b235302fafd4df711f2" + integrity sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg== + +"@tsconfig/node16@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e" + integrity sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA== + "@types/chai-subset@^1.3.3": version "1.3.3" resolved "https://registry.yarnpkg.com/@types/chai-subset/-/chai-subset-1.3.3.tgz#97893814e92abd2c534de422cb377e0e0bdaac94" @@ -1982,7 +2014,12 @@ acorn-jsx@^5.3.1: resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn@^8.7.0: +acorn-walk@^8.1.1: + version "8.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" + integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== + +acorn@^8.4.1, acorn@^8.7.0: version "8.7.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ== @@ -2088,6 +2125,11 @@ arch@^2.2.0: resolved "https://registry.yarnpkg.com/arch/-/arch-2.2.0.tgz#1bc47818f305764f23ab3306b0bfc086c5a29d11" integrity sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ== +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -2667,6 +2709,11 @@ cosmiconfig@^6.0.0: path-type "^4.0.0" yaml "^1.7.2" +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + cross-fetch@^3.0.4: version "3.1.5" resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f" @@ -2952,6 +2999,11 @@ dfa@^1.2.0: resolved "https://registry.yarnpkg.com/dfa/-/dfa-1.2.0.tgz#96ac3204e2d29c49ea5b57af8d92c2ae12790657" integrity sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q== +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -4636,6 +4688,11 @@ magic-string@^0.25.7: dependencies: sourcemap-codec "^1.4.4" +make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + markdown-to-jsx@^7.1.5: version "7.1.6" resolved "https://registry.yarnpkg.com/markdown-to-jsx/-/markdown-to-jsx-7.1.6.tgz#421487df2a66fe4231d94db653a34da033691e62" @@ -6235,6 +6292,25 @@ tr46@~0.0.3: resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= +ts-node@^10.5.0: + version "10.5.0" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.5.0.tgz#618bef5854c1fbbedf5e31465cbb224a1d524ef9" + integrity sha512-6kEJKwVxAJ35W4akuiysfKwKmjkbYxwQMTBaAxo9KKAx/Yd26mPUyhGz3ji+EsJoAgrLqVsYHNuuYwQe22lbtw== + dependencies: + "@cspotcode/source-map-support" "0.7.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.0" + yn "3.1.1" + ts-toolbelt@^6.3.3: version "6.15.5" resolved "https://registry.yarnpkg.com/ts-toolbelt/-/ts-toolbelt-6.15.5.tgz#cb3b43ed725cb63644782c64fbcad7d8f28c0a83" @@ -6392,6 +6468,11 @@ uuid@^8.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== +v8-compile-cache-lib@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz#0582bcb1c74f3a2ee46487ceecf372e46bce53e8" + integrity sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA== + v8-compile-cache@^2.0.3: version "2.3.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" @@ -6646,3 +6727,8 @@ yauzl@^2.10.0: dependencies: buffer-crc32 "~0.2.3" fd-slicer "~1.1.0" + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==