Projects STRLCPY gradejs Commits 3465f41a
🤬
Revision indexing in progress... (symbol navigation in revisions will be accurate after indexed)
  • ■ ■ ■ ■ ■ ■
    .eslintignore
    1 1  .eslintrc.js
    2 2  index.d.ts
    3  -webpack.config.js
    4 3  node_modules/
    5 4  packages/*/build
    6 5  packages/*/dist
    7 6   
    8  - 
  • ■ ■ ■ ■ ■
    .gitignore
    skipped 13 lines
    14 14   
    15 15  # misc
    16 16  .DS_Store
     17 +.log
    17 18   
    18 19  npm-debug.log*
    19 20  yarn-debug.log*
    skipped 4 lines
  • ■ ■ ■ ■ ■ ■
    .platform/confighooks/prebuild/01_extract_artifacts.sh
     1 +#!/bin/bash
     2 + 
     3 +bash `dirname "$0"`/../../hooks/prebuild/01_extract_artifacts.sh
     4 + 
  • ■ ■ ■ ■ ■
    .platform/hooks/prebuild/01_extract_artifacts.sh
     1 +#!/bin/bash
     2 + 
     3 +tar xf build.tar
     4 + 
     5 + 
  • ■ ■ ■ ■ ■ ■
    .platform/nginx/conf.d/elasticbeanstalk/001_plausible_redirect.conf
     1 +location = /api/event {
     2 + proxy_pass https://plausible.io/api/event;
     3 + proxy_buffering on;
     4 + proxy_http_version 1.1;
     5 + 
     6 + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
     7 + proxy_set_header X-Forwarded-Proto $scheme;
     8 + proxy_set_header X-Forwarded-Host $host;
     9 +}
     10 + 
  • ■ ■ ■ ■ ■ ■
    .platform/nginx/conf.d/elasticbeanstalk/00_application.conf
     1 +location / {
     2 + # Base config from elasticbeanstalk:
     3 + proxy_pass http://127.0.0.1:8080;
     4 + proxy_http_version 1.1;
     5 + 
     6 + proxy_set_header Connection $connection_upgrade;
     7 + proxy_set_header Upgrade $http_upgrade;
     8 + proxy_set_header Host $host;
     9 + proxy_set_header X-Real-IP $remote_addr;
     10 + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
     11 + 
     12 + # Add forwarded protocol and port details
     13 + proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
     14 + proxy_set_header X-Forwarded-Port $http_x_forwarded_port;
     15 + 
     16 + # Override gzip directive in server block
     17 + gzip on;
     18 + gzip_comp_level 5;
     19 + gzip_types text/* application/json application/javascript application/x-javascript application/xml application/xml+rss;
     20 +}
     21 + 
     22 + 
  • ■ ■ ■ ■ ■
    CONTRIBUTING.md
    skipped 57 lines
    58 58   
    59 59  - Open the [AWS RDS](https://us-east-2.console.aws.amazon.com/rds/home) page.
    60 60  - In the database list click on the `Create Database` button.
    61  -- Select the following parameters: Standart create, Amazon Aurora PostgreSQL-Compatible Edition version 13, Production template.
     61 +- Select the following parameters: Standard create, Amazon Aurora PostgreSQL-Compatible Edition version 13, Production template.
    62 62  - Enter the database name, for example `gradejs-public`.
    63 63  - Select a burstable DB instance class: `db.t3.medium`
    64 64  - Select `Don't create an Aurora Replica` option.
    skipped 9 lines
    74 74  4. Create [Amazon SQS](https://us-east-2.console.aws.amazon.com/sqs/v2/home) worker queues:
    75 75   
    76 76  - Open the page and click on the `Create queue` button.
    77  -- Select the standart queue option and specify the name `gradejs-public`.
     77 +- Select the standard queue option and specify the name `gradejs-public`.
    78 78  - Set the `visibility timeout` to 2 minutes.
    79 79  - Click on the `Create queue` button.
    80 80   
    skipped 2 lines
    83 83  - Open the [AWS Elastic Beanstalk](https://us-east-2.console.aws.amazon.com/elasticbeanstalk/home) page and click on the `Create a new environment` button.
    84 84  - Select `Web server environment` option.
    85 85  - Type the newly created application name (`gradejs`).
    86  -- Specify an environment name, for example `gradejs-public-api`. Leave the domain and description name fileds blank.
     86 +- Specify an environment name, for example `gradejs-public-api`. Leave the domain and description name fields blank.
    87 87  - Select the latest version of Node.js 16 managed platform on Amazon Linux 2. The version minimal required version is 5.5.1.
    88 88  - Use the sample application code and click on the `Configure more options` button.
    89 89  - Select the `Custom configuration` configuration preset.
    90 90  - In the `Software` section click on the `Edit` button.
    91 91  - Enable the `CloudWatch` log streaming with a 5 day retention.
    92  -- Specify the `AWS_REGION` environment variable. It should be the same as your current AWS environemnt. For example, `us-east-2`.
     92 +- Specify the `AWS_REGION` environment variable. It should be the same as your current AWS environment. For example, `us-east-2`.
    93 93  - Specify the `DB_URL` environment variable. It should point to the newly created RDS database. For example: `postgres://postgres:<secret>@<hostname>/gradejs`.
    94 94  - Specify the `INTERNAL_API_ORIGIN` environment variable. Consider using our staging internal API origin: `http://fpjs-dev-gradejs-internal-api.eba-fybi4md5.us-east-1.elasticbeanstalk.com`
    95 95  - Specify the `SQS_WORKER_QUEUE_URL` queue. It should be a newly created `gradejs-backend` queue. For example `https://sqs.us-east-2.amazonaws.com/<account_id>/gradejs-backend`.
    96  -- Specify the `AWS_REGION` environment variable. It should be the same as your current AWS environemnt. For example, `us-east-2`.
     96 +- Specify the `CORS_ALLOWED_ORIGIN` variable. Use comma-separated URLs your API will be talking to, for example: `http://localhost:3000,https://staging.gradejs.com`.
    97 97  - Specify the `EB_START` environment variable: `api`.
    98 98  - Click on the `Save` button and then click on the `Create environment` button.
    99 99   
    skipped 1 lines
    101 101   
    102 102  - Select `Worker environment` option.
    103 103  - Type the newly created application name (`gradejs-public`).
    104  -- Specify an environment name: `gradejs-public-worker`. Leave the domain and description name fileds blank.
     104 +- Specify an environment name: `gradejs-public-worker`. Leave the domain and description name fields blank.
    105 105  - Select the latest version of Node.js 16 managed platform on Amazon Linux 2. The version minimal required version is 5.5.1.
    106 106  - Use the sample application code and click on the `Configure more options` button.
    107 107  - Select the `Custom configuration` configuration preset.
    skipped 3 lines
    111 111  - Set the `EB_START` environment variable to `worker`.
    112 112  - Click on the `Save` button and then click on the `Create environment` button.
    113 113   
    114  -7. Setting up a `Continuous Deploy` pipeline:
     114 +7. Create an [AWS Elastic Beanstalk](https://us-east-2.console.aws.amazon.com/elasticbeanstalk/home) web environment:
     115 + 
     116 +- Open the [AWS Elastic Beanstalk](https://us-east-2.console.aws.amazon.com/elasticbeanstalk/home) page and click on the `Create a new environment` button.
     117 +- Select `Web server environment` option.
     118 +- Type the newly created application name (`gradejs-web`).
     119 +- Specify an environment name, for example `gradejs-public-web`. Leave the domain and description name fields blank.
     120 +- Select the latest version of Node.js 16 managed platform on Amazon Linux 2. The version minimal required version is 5.5.1.
     121 +- Use the sample application code and click on the `Configure more options` button.
     122 +- Select the `Custom configuration` configuration preset.
     123 +- In the `Software` section click on the `Edit` button.
     124 +- Enable the `CloudWatch` log streaming with a 5 day retention.
     125 +- Specify the `API_ORIGIN` environment variable. It should point to public API entrypoint you created in chapter 5.
     126 +- Specify the `CORS_ORIGIN` environment variable, set it to empty string to disable CORS checks.
     127 +- Specify `PLAUSIBLE_DOMAIN`, `GA_ID` (strings) and `DUMP_ANALYTICS` (bool) to setup analytics if required. Set to empty if not required.
     128 +- Specify the `EB_START` environment variable: `web`.
     129 +- Click on the `Save` button and then click on the `Create environment` button.
     130 + 
     131 +8. Setting up a `Continuous Deploy` pipeline:
    115 132   
    116 133  - Go to the [AWS CodePipeline](https://us-east-2.console.aws.amazon.com/codesuite/codepipeline/pipelines) page and click on the `Create pipeline` page.
    117 134  - Type a pipeline name `gradejs-public@production-deploy` and click on the `Next` button.
    skipped 14 lines
  • ■ ■ ■ ■ ■ ■
    README.md
    skipped 1 lines
    2 2   
    3 3  GradeJS is an open-source project that allows you to analyze webpack production bundles without having access to the source code of a website. It detects a list of bundled NPM libraries and works even for minified or tree-shaken bundles.
    4 4   
     5 +![Preview](./docs/preview.png)
     6 + 
    5 7  More info:
    6 8   
    7 9  - [How it works?](https://github.com/gradejs/gradejs/discussions/6)
    skipped 16 lines
  • ■ ■ ■ ■ ■ ■
    buildspec.yml
    skipped 8 lines
    9 9   - docker-compose -f docker-compose.aws.yml up -d db
    10 10   - yarn install
    11 11   - yarn build:backend
     12 + - yarn workspace @gradejs-public/web build
    12 13   - yarn workspace @gradejs-public/shared migration:run
    13 14   - yarn test
    14 15   - docker-compose -f docker-compose.aws.yml down --volumes
     16 + - bash ./cli/archive_artifacts.sh
    15 17  artifacts:
    16 18   files:
    17  - - packages/public-api/build/**/*
    18  - - packages/public-api/package.json
    19  - - packages/worker/build/**/*
    20  - - packages/worker/package.json
    21  - - packages/shared/build/**/*
    22  - - packages/shared/package.json
    23  - - package.json
    24  - - eb-start.sh
     19 + - build.tar
     20 + - .platform/**/*
    25 21   
  • ■ ■ ■ ■ ■ ■
    cli/archive_artifacts.sh
     1 +#!/bin/bash
     2 + 
     3 +# Strip dev packages
     4 +yarn install --production --pure-lockfile
     5 + 
     6 +# Tar is required to keep the symlinks inside node_modules alive
     7 +# and thus avoid running `yarn install --production` on server
     8 +tar cf build.tar \
     9 + packages/public-api/build \
     10 + packages/public-api/package.json \
     11 + packages/public-api/node_modules \
     12 + packages/worker/build \
     13 + packages/worker/package.json \
     14 + packages/worker/node_modules \
     15 + packages/shared/build \
     16 + packages/shared/package.json \
     17 + packages/shared/node_modules \
     18 + packages/web/dist \
     19 + packages/web/package.json \
     20 + packages/web/node_modules \
     21 + node_modules \
     22 + package.json \
     23 + eb-start.sh
     24 + 
  • ■ ■ ■ ■ ■ ■
    cli/local_start.sh
    skipped 53 lines
    54 54  API_PID=$!
    55 55   
    56 56  echo "Starting web package dev server"
    57  -API_ORIGIN=http://localhost:8083 CORS_ORIGIN=http://localhost:3000 \
    58  - npm run start:dev --prefix packages/web 2>&1 &
     57 +PORT=3000 API_ORIGIN=http://localhost:8083 CORS_ORIGIN=http://localhost:3000 PLAUSIBLE_DOMAIN= GA_ID= DUMP_ANALYTICS= \
     58 + npm run dev:start --prefix packages/web 2>&1 &
    59 59  WEB_PID=$!
    60 60   
    61 61  # Some magic to shut down all services at once when requested
    skipped 54 lines
  • docs/preview.png
  • ■ ■ ■ ■ ■
    eb-start.sh
    1  - 
    2 1  #!/bin/bash
    3 2   
    4 3  case "$EB_START" in
    skipped 7 lines
    12 11   npm start --prefix packages/worker
    13 12   ;;
    14 13   
     14 + "web")
     15 + echo "Starting web package"
     16 + npm start --prefix packages/web
     17 + ;;
    15 18   *)
    16 19   echo "Unknown package to start EB_START=$EB_START"
    17 20   exit 1
    18 21   ;;
    19 22  esac
     23 + 
  • ■ ■ ■ ■
    package.json
    skipped 21 lines
    22 22   "build:public-api": "yarn workspace @gradejs-public/public-api run build",
    23 23   "build:backend": "yarn build:shared && yarn build:public-api && yarn build:worker",
    24 24   "dev:start": "bash cli/local_start.sh",
    25  - "dev:start:onlyWeb": "API_ORIGIN=https://api.staging.gradejs.com npm start --prefix packages/web"
     25 + "dev:start:web": "PORT=3000 API_ORIGIN=https://api.staging.gradejs.com npm run dev:start --prefix packages/web"
    26 26   },
    27 27   "devDependencies": {
    28 28   "@swc/core": "^1.2.233",
    skipped 15 lines
  • ■ ■ ■ ■ ■
    packages/public-api/tsconfig.build.json
    skipped 10 lines
    11 11   "**/*.test.ts"
    12 12   ]
    13 13  }
     14 + 
  • ■ ■ ■ ■ ■ ■
    packages/shared/src/database/entities/packageMetadata.ts
    1 1  import { Column, Entity, Index, PrimaryColumn, BaseEntity } from 'typeorm';
    2 2   
     3 +type Maintainer = {
     4 + name: string;
     5 + email: string;
     6 + avatar: string;
     7 +};
     8 +type VersionData = { dependencies: Record<string, string>; unpackedSize?: number };
     9 + 
    3 10  @Entity({ name: 'package_metadata' })
    4 11  @Index(['name'], { unique: true })
    5 12  export class PackageMetadata extends BaseEntity {
    skipped 11 lines
    17 24   
    18 25   @Column()
    19 26   description?: string;
     27 + 
     28 + @Column()
     29 + fullDescription?: string;
     30 + 
     31 + @Column({ type: 'jsonb' })
     32 + maintainers?: Maintainer[];
     33 + 
     34 + @Column({ type: 'jsonb' })
     35 + keywords?: string[];
     36 + 
     37 + @Column({ type: 'jsonb' })
     38 + versionSpecificValues?: Record<string, VersionData>;
    20 39   
    21 40   @Column()
    22 41   homepageUrl?: string;
    skipped 14 lines
  • ■ ■ ■ ■ ■ ■
    packages/shared/src/database/migrations/1662405581390-ExtendPackageMetadata.ts
     1 +import { MigrationInterface, QueryRunner } from 'typeorm';
     2 + 
     3 +export class ExtendPackageMetadata1662405581390 implements MigrationInterface {
     4 + public async up(queryRunner: QueryRunner): Promise<void> {
     5 + await queryRunner.query(`
     6 + alter table "package_metadata" add column "full_description" text;
     7 + alter table "package_metadata" add column "maintainers" jsonb not null default '[]';
     8 + alter table "package_metadata" add column "keywords" jsonb not null default '[]';
     9 + alter table "package_metadata" add column "version_specific_values" jsonb not null default '{}';
     10 + `);
     11 + }
     12 + 
     13 + public async down(queryRunner: QueryRunner): Promise<void> {
     14 + await queryRunner.query(`
     15 + alter table "package_metadata" drop column "full_description";
     16 + alter table "package_metadata" drop column "maintainers";
     17 + alter table "package_metadata" drop column "keywords";
     18 + alter table "package_metadata" drop column "version_specific_values";
     19 + `);
     20 + }
     21 +}
     22 + 
  • ■ ■ ■ ■ ■ ■
    packages/shared/src/utils/env.ts
    skipped 3 lines
    4 4   Port = 'PORT',
    5 5   DatabaseUrl = 'DB_URL',
    6 6   
     7 + // Web related
     8 + PublicApiOrigin = 'API_ORIGIN',
     9 + PublicCorsOrigin = 'CORS_ORIGIN',
     10 + PlausibleDomain = 'PLAUSIBLE_DOMAIN',
     11 + AnalyticsId = 'GA_ID',
     12 + VerboseAnalytics = 'DUMP_ANALYTICS',
     13 + 
    7 14   // AWS
    8 15   AwsRegion = 'AWS_REGION',
    9 16   SqsWorkerQueueUrl = 'SQS_WORKER_QUEUE_URL',
    skipped 54 lines
    64 71   keys.forEach((key) => key && getEnv(key));
    65 72  }
    66 73   
     74 +export const getClientVars = () => {
     75 + return [
     76 + Env.PublicApiOrigin,
     77 + Env.PublicCorsOrigin,
     78 + Env.PlausibleDomain,
     79 + Env.AnalyticsId,
     80 + Env.VerboseAnalytics,
     81 + ].reduce((acc, val) => {
     82 + acc[val] = getEnvUnsafe(val) ?? '';
     83 + return acc;
     84 + }, {} as Record<string, string>);
     85 +};
     86 + 
  • ■ ■ ■ ■ ■ ■
    packages/shared/tsconfig.build.json
    skipped 5 lines
    6 6   "baseUrl": "./src"
    7 7   },
    8 8   "include": [
    9  - "./src/**/*",
    10  - "../../index.d.ts"
     9 + "./src/**/*"
    11 10   ],
    12 11   "exclude": [
    13 12   "**/*.test.ts"
    14 13   ]
    15 14  }
     15 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/global.d.ts
    skipped 8 lines
    9 9   export default path;
    10 10  }
    11 11   
     12 +declare const __isServer__: boolean; // Replaced with DefinePlugin during build
     13 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/index.html
    skipped 14 lines
    15 15   content="GradeJS analyzes production JavaScript files and matches bundled NPM packages."
    16 16   />
    17 17   <meta property="og:image" content="/static/sharing-image.png" />
     18 + <script type="text/javascript">
     19 + window.process = { env: {} };
     20 + </script>
    18 21   
    19 22   <title>GradeJS | Production Webpack Bundle Analyzer</title>
    20 23   </head>
    skipped 12 lines
  • ■ ■ ■ ■ ■ ■
    packages/web/package.json
    skipped 6 lines
    7 7   "prettier": "prettier --check ./src",
    8 8   "typecheck": "tsc --project tsconfig.json --noEmit",
    9 9   "test": "echo 'Not implemented yet'",
    10  - "start": "webpack serve --open",
    11 10   "storybook": "start-storybook -p 6006",
    12  - "build": "rm -rf dist && webpack --mode=production",
    13  - "build:storybook": "build-storybook -o dist/storybook"
     11 + "build": "ts-node --swc webpack/build.ts --production",
     12 + "build:vercel": "ts-node --swc webpack/build-vercel-deploy-preview.ts && build-storybook -o dist/static/storybook",
     13 + "dev:start": "./webpack/start_dev.sh",
     14 + "start": "node dist/main.js",
     15 + "dev:build": "ts-node --swc webpack/build.ts"
    14 16   },
    15 17   "repository": {
    16 18   "type": "git",
    skipped 21 lines
    38 40   "copy-webpack-plugin": "^11.0.0",
    39 41   "css-loader": "^6.2.0",
    40 42   "dotenv": "^10.0.0",
     43 + "html-replace-webpack-plugin": "^2.6.0",
    41 44   "html-webpack-plugin": "^5.3.2",
    42 45   "mini-css-extract-plugin": "^2.3.0",
    43 46   "node-sass": "^7.0.1",
     47 + "nodemon": "^2.0.19",
     48 + "null-loader": "^4.0.1",
    44 49   "sass-loader": "^12.1.0",
    45 50   "storybook": "^6.3.8",
    46 51   "style-loader": "^3.2.1",
    47  - "svg-sprite-loader": "git+https://github.com/ctizen/svg-sprite-loader.git#f73ede9aa3e357b7d671d2fb89e9d8846c0aa2ba",
     52 + "svg-sprite-loader": "git+https://github.com/ctizen/svg-sprite-loader.git#5fc8046921ce575698126e2fa17742678b0b91ac",
    48 53   "ts-loader": "^9.2.5",
    49 54   "typescript": "^4.6.4",
    50 55   "webpack": "^5.52.1",
    skipped 1 lines
    52 57   "webpack-dev-server": "^4.2.1"
    53 58   },
    54 59   "dependencies": {
     60 + "@gradejs-public/shared": "^0.1.0",
    55 61   "@reduxjs/toolkit": "^1.8.3",
    56 62   "@trpc/client": "^9.27.0",
    57 63   "@types/lodash.memoize": "^4.1.7",
    58 64   "clsx": "^1.1.1",
     65 + "express": "^4.18.1",
    59 66   "lodash.memoize": "^4.1.2",
    60 67   "plausible-tracker": "^0.3.8",
    61 68   "react": "^17.0.2",
    skipped 10 lines
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/App.tsx
    skipped 1 lines
    2 2  import { Routes, Route, Navigate, useLocation } from 'react-router-dom';
    3 3  import { HomePage } from './pages/Home';
    4 4  import { WebsiteResultsPage } from './pages/WebsiteResults';
    5  -import { initAnalytics } from '../services/analytics';
    6  -const locationChangeHandler = initAnalytics();
    7  - 
     5 +type AppProps = {
     6 + locationChangeHandler: (url?: string | URL) => void;
     7 +};
    8 8  /**
    9 9   * The App component has not any router wrapper because it uses both with tests, storybook and browser.
    10 10   * Each environment should had a high order router component
    skipped 3 lines
    14 14   * <App />
    15 15   * </BrowserRouter>
    16 16   */
    17  -export function App() {
     17 +export function App({ locationChangeHandler }: AppProps) {
    18 18   const location = useLocation();
    19 19   locationChangeHandler(location.pathname);
    20 20   return (
    skipped 8 lines
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/layouts/Filters/Filters.tsx
    skipped 1 lines
    2 2  import React, { useEffect } from 'react';
    3 3  import clsx from 'clsx';
    4 4  import { SubmitHandler, useForm } from 'react-hook-form';
    5  -import { Button, TextInput } from 'components/ui';
     5 +import { Button, TextInput } from '../../ui';
    6 6  import Dropdown from '../../ui/Dropdown/Dropdown';
    7 7  import Radio from '../../ui/Radio/Radio';
    8 8  import styles from './Filters.module.scss';
    skipped 21 lines
    30 30   const watchFilterByName = watch('filter');
    31 31   let hideHandle: () => void;
    32 32   useEffect(() => {
    33  - document.body.addEventListener('click', hideHandle);
     33 + if (typeof document !== 'undefined') {
     34 + document.body.addEventListener('click', hideHandle);
     35 + }
    34 36   });
    35 37   
    36 38   return (
    skipped 76 lines
  • ■ ■ ■ ■ ■
    packages/web/src/components/pages/WebsiteResults.tsx
    skipped 21 lines
    22 22   );
    23 23   const setFilters = (filters: FiltersState) => dispatch(applyFilters(filters));
    24 24   
     25 + // TODO: discuss. Looks ugly
     26 + // Fetch data for SSR if host is already processed
     27 + if (__isServer__ && hostname) {
     28 + dispatch(getWebsite({ hostname, useRetry: false }));
     29 + }
     30 + 
    25 31   useEffect(() => {
    26 32   if (hostname && !isLoading && isPending) {
    27  - const promise = dispatch(getWebsite(hostname));
     33 + const promise = dispatch(getWebsite({ hostname }));
    28 34   return function cleanup() {
    29 35   promise.abort();
    30 36   };
    skipped 63 lines
  • ■ ■ ■ ■
    packages/web/src/components/ui/Icon/Icon.tsx
    skipped 81 lines
    82 82   stroke={stroke}
    83 83   xmlns='http://www.w3.org/2000/svg'
    84 84   >
    85  - <use xlinkHref={`/sprite.svg#${icons[kind].id}`} />
     85 + <use xlinkHref={`/static/sprite.svg#${icons[kind].id}`} />
    86 86   </svg>
    87 87   );
    88 88  }
    skipped 1 lines
  • ■ ■ ■ ■ ■ ■
    packages/web/src/index.tsx
    skipped 3 lines
    4 4  import { BrowserRouter } from 'react-router-dom';
    5 5  import { store } from './store';
    6 6  import { App } from './components/App';
     7 +import { initAnalytics } from './services/analytics';
    7 8  import 'styles/global.scss';
    8 9   
    9  -ReactDOM.render(
     10 +const locationChangeHandler = initAnalytics();
     11 + 
     12 +ReactDOM.hydrate(
    10 13   <Provider store={store}>
    11 14   <BrowserRouter>
    12  - <App />
     15 + <App locationChangeHandler={locationChangeHandler} />
    13 16   </BrowserRouter>
    14 17   </Provider>,
    15 18   document.getElementById('app')
    skipped 2 lines
  • ■ ■ ■ ■ ■ ■
    packages/web/src/server.tsx
     1 +import express from 'express';
     2 +import React from 'react';
     3 +import { Provider } from 'react-redux';
     4 +import ReactDOMServer from 'react-dom/server';
     5 +import { StaticRouter } from 'react-router-dom/server';
     6 +// TODO: fix import from different monorepo package
     7 +import { getPort, getClientVars } from '../../shared/src/utils/env';
     8 +import { store } from './store';
     9 +import { App } from './components/App';
     10 +import path from 'path';
     11 +import { readFileSync, readFile } from 'fs';
     12 + 
     13 +const app = express();
     14 +const layout = readFileSync(path.resolve(__dirname, 'static', 'index.html'), { encoding: 'utf-8' });
     15 + 
     16 +app.use('/static', express.static(path.join(__dirname, 'static')));
     17 +app.get('/robots.txt', (_, res) =>
     18 + readFile(path.join(__dirname, '/robots.txt'), { encoding: 'utf-8' }, (err, data) => {
     19 + if (!err) {
     20 + res.send(data);
     21 + } else {
     22 + res.status(404);
     23 + res.send(null);
     24 + }
     25 + })
     26 +);
     27 + 
     28 +app.get('*', (req, res) => {
     29 + const html = ReactDOMServer.renderToString(
     30 + <Provider store={store}>
     31 + <StaticRouter location={req.url}>
     32 + <App locationChangeHandler={() => {}} />
     33 + </StaticRouter>
     34 + </Provider>
     35 + );
     36 + 
     37 + res.send(
     38 + layout
     39 + .replace('<div id="app"></div>', '<div id="app">' + html + '</div>')
     40 + // Little magic to support process.env calls on client side without additional replacements in bundle
     41 + .replace(
     42 + 'window.process = { env: {} };',
     43 + 'window.process = { env: ' + JSON.stringify(getClientVars()) + ' };'
     44 + )
     45 + );
     46 +});
     47 + 
     48 +app.listen(getPort(8080));
     49 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/src/services/analytics.ts
    skipped 24 lines
    25 25   pageView(url ? (typeof url === 'string' ? url : url.pathname) : window.location.pathname);
    26 26  }
    27 27   
    28  -export function pageView(url: string = window.location.pathname, referrer?: string) {
     28 +export function pageView(url?: string, referrer?: string) {
     29 + if (!plausible) {
     30 + return;
     31 + }
     32 + 
     33 + if (!url) {
     34 + url = window.location.pathname;
     35 + }
     36 + 
    29 37   if (process.env.PLAUSIBLE_DOMAIN) {
    30 38   plausible.trackPageview({
    31 39   url: window.location.origin + url,
    skipped 12 lines
    44 52  }
    45 53   
    46 54  export function trackCustomEvent(category: string, action: string, props?: CustomEventProperties) {
     55 + if (!plausible) {
     56 + return;
     57 + }
     58 + 
    47 59   if (process.env.PLAUSIBLE_DOMAIN) {
    48 60   plausible.trackEvent(`${category}_${action}`, { props });
    49 61   }
    skipped 10 lines
  • ■ ■ ■ ■ ■ ■
    packages/web/src/services/apiClient.ts
    skipped 1 lines
    2 2  import type { inferProcedureOutput } from '@trpc/server';
    3 3  import type { AppRouter, Api } from '../../../public-api/src/router';
    4 4   
     5 +// polyfill fetch, trpc needs it
     6 +import f from 'node-fetch';
     7 + 
    5 8  // Helper types
    6 9  export type TQuery = keyof AppRouter['_def']['queries'];
    7 10  export type TMutation = keyof AppRouter['_def']['mutations'];
    skipped 10 lines
    18 21   
    19 22  export const client = createTRPCClient<AppRouter>({
    20 23   url: process.env.API_ORIGIN,
     24 + fetch: typeof window === 'undefined' ? (f as any) : window.fetch.bind(window),
     25 + headers: {
     26 + Origin: process.env.CORS_ORIGIN,
     27 + },
    21 28  });
    22 29   
    23 30  export type SyncWebsiteOutput = InferMutationOutput<'syncWebsite'>;
    skipped 4 lines
  • ■ ■ ■ ■ ■ ■
    packages/web/src/store/slices/websiteResults.ts
    skipped 23 lines
    24 24  const hasPendingPages = (result: DetectionResult) =>
    25 25   !!result.webpages.find((item) => item.status === 'pending');
    26 26   
    27  -const getWebsite = createAsyncThunk('websiteResults/getWebsite', async (hostname: string) => {
    28  - const loadStartTime = Date.now();
    29  - let results = await client.mutation('syncWebsite', hostname);
    30  - while (hasPendingPages(results)) {
    31  - await sleep(5000);
    32  - results = await client.mutation('syncWebsite', hostname);
     27 +const getWebsite = createAsyncThunk(
     28 + 'websiteResults/getWebsite',
     29 + async ({ hostname, useRetry = true }: { hostname: string; useRetry?: boolean }) => {
     30 + const loadStartTime = Date.now();
     31 + let results = await client.mutation('syncWebsite', hostname);
     32 + if (useRetry) {
     33 + while (hasPendingPages(results)) {
     34 + await sleep(5000);
     35 + results = await client.mutation('syncWebsite', hostname);
     36 + }
     37 + }
     38 + // TODO: move to tracking middleware?
     39 + trackCustomEvent('HostnamePage', 'WebsiteLoaded', {
     40 + value: Date.now() - loadStartTime,
     41 + });
     42 + return results;
    33 43   }
    34  - // TODO: move to tracking middleware?
    35  - trackCustomEvent('HostnamePage', 'WebsiteLoaded', {
    36  - value: Date.now() - loadStartTime,
    37  - });
    38  - return results;
    39  -});
     44 +);
    40 45   
    41 46  const websiteResults = createSlice({
    42 47   name: 'websiteResults',
    skipped 31 lines
  • ■ ■ ■ ■ ■ ■
    packages/web/tsconfig.build.json
     1 +{
     2 + "extends": "./tsconfig.json",
     3 + "compilerOptions": {
     4 + "outDir": "./webpack",
     5 + "baseUrl": "./src"
     6 + },
     7 + "include": [
     8 + "./src/**/*"
     9 + ],
     10 + "exclude": [
     11 + "**/*.test.ts"
     12 + ],
     13 + "files": ["global.d.ts"]
     14 +}
     15 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/vercel.json
    1 1  {
    2 2   "rewrites": [{
    3  - "source": "/api/event",
    4  - "destination": "https://plausible.io/api/event"
    5  - },
    6  - {
    7  - "source": "/(.*)",
    8  - "destination": "/index.html"
    9  - }
    10  - ]
     3 + "source": "/(.*)",
     4 + "destination": "/index.html"
     5 + }]
    11 6  }
     7 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/webpack/build-vercel-deploy-preview.ts
     1 +import webpack from 'webpack';
     2 +import { getClientVars } from '../../shared/src/utils/env';
     3 +import { clientConfig } from './client';
     4 + 
     5 +// eslint-disable-next-line @typescript-eslint/no-var-requires
     6 +const HtmlReplaceWebpackPlugin = require('html-replace-webpack-plugin');
     7 + 
     8 +// Vercel webpack build for branch deploy previews without SSR
     9 +webpack(
     10 + [
     11 + clientConfig({
     12 + mode: 'production',
     13 + publicPath: '/',
     14 + plugins: [
     15 + new HtmlReplaceWebpackPlugin([
     16 + {
     17 + pattern: 'window.process = { env: {} };',
     18 + replacement: 'window.process = { env: ' + JSON.stringify(getClientVars()) + ' };',
     19 + },
     20 + ]),
     21 + ],
     22 + }),
     23 + ],
     24 + (err, stats) => {
     25 + // [Stats Object](#stats-object)
     26 + process.stdout.write(stats!.toString() + '\n');
     27 + }
     28 +);
     29 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/webpack/build.ts
     1 +import webpack from 'webpack';
     2 +import yargs from 'yargs/yargs';
     3 +import { hideBin } from 'yargs/helpers';
     4 +import { clientConfig } from './client';
     5 +import { serverConfig } from './server';
     6 + 
     7 +const args = yargs(hideBin(process.argv))
     8 + .option('production', {
     9 + alias: 'p',
     10 + type: 'boolean',
     11 + description: 'Run in production mode',
     12 + default: false,
     13 + })
     14 + .option('watch', {
     15 + alias: 'w',
     16 + type: 'boolean',
     17 + description: 'Watch changes',
     18 + default: false,
     19 + })
     20 + .parse();
     21 + 
     22 +const publicPath = '/static/';
     23 + 
     24 +webpack(
     25 + [
     26 + clientConfig({
     27 + mode: args.production ? 'production' : 'development',
     28 + watch: args.watch,
     29 + publicPath,
     30 + }),
     31 + serverConfig({
     32 + mode: args.production ? 'production' : 'development',
     33 + watch: args.watch,
     34 + publicPath,
     35 + }),
     36 + ],
     37 + (err, stats) => {
     38 + // [Stats Object](#stats-object)
     39 + process.stdout.write(stats!.toString() + '\n');
     40 + }
     41 +);
     42 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/webpack/client.ts
     1 +import { join, resolve } from 'path';
     2 +import HtmlWebpackPlugin from 'html-webpack-plugin';
     3 +import MiniCssExtractPlugin from 'mini-css-extract-plugin';
     4 +import CopyPlugin from 'copy-webpack-plugin';
     5 +import { configCommon, pluginsCommon, srcDir } from './common';
     6 +import { Configuration } from 'webpack';
     7 +import { WebpackConfigOptions } from './config';
     8 + 
     9 +const distDir = 'dist/static';
     10 + 
     11 +export const clientConfig: (options: WebpackConfigOptions) => Configuration = ({
     12 + mode,
     13 + publicPath,
     14 + watch = false,
     15 + plugins = [],
     16 +}) => ({
     17 + entry: join(__dirname, '..', srcDir, 'index.tsx'),
     18 + ...configCommon(mode),
     19 + module: {
     20 + rules: [
     21 + {
     22 + test: /\.(png|svg|jpg|jpeg|gif|woff(2)?|ttf|eot)$/i,
     23 + exclude: /sprite\/([^\/]*)\.svg$/,
     24 + type: 'asset/resource',
     25 + },
     26 + {
     27 + test: /sprite\/([^\/]*)\.svg$/,
     28 + loader: 'svg-sprite-loader',
     29 + options: {
     30 + extract: true,
     31 + spriteFilename: 'sprite.svg',
     32 + },
     33 + },
     34 + {
     35 + test: /\.(tsx|ts)?$/,
     36 + use: 'ts-loader',
     37 + exclude: '/node_modules/',
     38 + },
     39 + {
     40 + test: /\.module\.scss$/,
     41 + use: [
     42 + MiniCssExtractPlugin.loader,
     43 + {
     44 + loader: 'css-loader',
     45 + options: {
     46 + modules: true,
     47 + sourceMap: mode === 'development',
     48 + },
     49 + },
     50 + {
     51 + loader: 'sass-loader',
     52 + options: {
     53 + sourceMap: mode === 'development',
     54 + },
     55 + },
     56 + ],
     57 + },
     58 + {
     59 + test: /\.s?css$/,
     60 + exclude: /\.module.scss$/,
     61 + use: [
     62 + MiniCssExtractPlugin.loader,
     63 + 'css-loader',
     64 + {
     65 + loader: 'sass-loader',
     66 + options: {
     67 + sourceMap: mode === 'development',
     68 + },
     69 + },
     70 + ],
     71 + },
     72 + ],
     73 + },
     74 + plugins: [
     75 + ...pluginsCommon(mode, false),
     76 + new HtmlWebpackPlugin({
     77 + template: join(__dirname, '..', 'index.html'),
     78 + }),
     79 + new CopyPlugin({
     80 + patterns: [{ from: 'src/assets/sharing-image.png', to: 'sharing-image.png' }],
     81 + }),
     82 + ...plugins,
     83 + ],
     84 + output: {
     85 + filename: 'bundle.[fullhash].js',
     86 + path: resolve(__dirname, '..', distDir),
     87 + publicPath,
     88 + assetModuleFilename: '[hash][ext]',
     89 + },
     90 + watch,
     91 +});
     92 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/webpack/common.ts
     1 +import { resolve } from 'path';
     2 +import { Configuration, DefinePlugin } from 'webpack';
     3 +import MiniCssExtractPlugin from 'mini-css-extract-plugin';
     4 +// eslint-disable-next-line @typescript-eslint/no-var-requires
     5 +const SpritePlugin = require('svg-sprite-loader/plugin');
     6 + 
     7 +export const srcDir = 'src';
     8 + 
     9 +export const configCommon: (mode: 'development' | 'production') => Configuration = (mode) => ({
     10 + mode,
     11 + resolve: {
     12 + extensions: ['.ts', '.tsx', '.js'],
     13 + modules: [resolve(__dirname, '..', srcDir), 'node_modules'],
     14 + },
     15 + devtool: mode === 'development' ? 'eval-source-map' : false,
     16 +});
     17 + 
     18 +export const pluginsCommon = (mode: string, isServer: boolean) => [
     19 + new DefinePlugin({
     20 + __isServer__: isServer,
     21 + }),
     22 + new MiniCssExtractPlugin({
     23 + filename: mode === 'development' ? '[name].css' : '[name].[fullhash].css',
     24 + chunkFilename: mode === 'development' ? '[id].css' : '[id].[fullhash].css',
     25 + }),
     26 + new SpritePlugin(),
     27 +];
     28 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/webpack/config.ts
     1 +export type WebpackConfigOptions = {
     2 + mode: 'production' | 'development';
     3 + watch?: boolean;
     4 + publicPath: string;
     5 + plugins?: unknown[];
     6 +};
     7 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/webpack/server.ts
     1 +import { join } from 'path';
     2 +import MiniCssExtractPlugin from 'mini-css-extract-plugin';
     3 +import CopyPlugin from 'copy-webpack-plugin';
     4 +import { configCommon, pluginsCommon, srcDir } from './common';
     5 +import { Configuration } from 'webpack';
     6 +import { WebpackConfigOptions } from './config';
     7 + 
     8 +const distDir = 'dist';
     9 + 
     10 +export const serverConfig: (options: WebpackConfigOptions) => Configuration = ({
     11 + mode,
     12 + watch = false,
     13 +}) => ({
     14 + entry: join(__dirname, '..', srcDir, 'server.tsx'),
     15 + ...configCommon(mode),
     16 + module: {
     17 + rules: [
     18 + {
     19 + test: /\.(png|svg|jpg|jpeg|gif|woff(2)?|ttf|eot)$/i,
     20 + exclude: /sprite\/([^\/]*)\.svg$/,
     21 + use: 'null-loader',
     22 + },
     23 + {
     24 + test: /sprite\/([^\/]*)\.svg$/,
     25 + loader: 'svg-sprite-loader',
     26 + options: {
     27 + extract: true,
     28 + spriteFilename: 'sprite.svg',
     29 + },
     30 + },
     31 + {
     32 + test: /\.(tsx|ts)?$/,
     33 + use: 'ts-loader',
     34 + exclude: '/node_modules/',
     35 + },
     36 + {
     37 + test: /\.module\.scss$/,
     38 + use: [
     39 + MiniCssExtractPlugin.loader,
     40 + {
     41 + loader: 'css-loader',
     42 + options: {
     43 + modules: true,
     44 + },
     45 + },
     46 + 'sass-loader',
     47 + ],
     48 + },
     49 + {
     50 + test: /\.s?css$/,
     51 + exclude: /\.module.scss$/,
     52 + use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
     53 + },
     54 + ],
     55 + },
     56 + plugins: [
     57 + ...pluginsCommon(mode, true),
     58 + new CopyPlugin({
     59 + patterns: [{ from: 'robots.txt', to: 'robots.txt' }],
     60 + }),
     61 + ],
     62 + output: {
     63 + filename: '[name].js',
     64 + path: join(__dirname, '..', distDir),
     65 + library: { type: 'commonjs2' },
     66 + globalObject: 'this',
     67 + },
     68 + optimization: {
     69 + minimize: false,
     70 + },
     71 + target: 'node',
     72 + watch,
     73 +});
     74 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/webpack/start_dev.sh
     1 +#!/usr/bin/env bash
     2 + 
     3 +# Note: script should be run from package root.
     4 + 
     5 +# Add ts-node and nodemon to PATH
     6 +export PATH=$PATH:./node_modules/.bin:../../node_modules/.bin
     7 + 
     8 +# Cleanup
     9 +rm -rf ./dist
     10 + 
     11 +echo "Building client/server bundles"
     12 +ts-node --swc webpack/build.ts --watch 2>&1 &
     13 +WEBPACK_PID=$!
     14 + 
     15 +# Wait until server file appears
     16 +until [ -f ./dist/main.js ]
     17 +do
     18 + sleep 1
     19 +done
     20 + 
     21 +echo "Starting server bundle"
     22 +nodemon --watch dist/main.js -V dist/main.js 2>&1 &
     23 +SRV_PID=$!
     24 + 
     25 +# Some magic to shut down all services at once when requested
     26 + 
     27 +TRAPPED_SIGNAL=false
     28 +trap "TRAPPED_SIGNAL=true; kill -15 $WEBPACK_PID; kill -15 $SRV_PID" SIGTERM SIGINT
     29 + 
     30 +while :
     31 +do
     32 + kill -0 $WEBPACK_PID 2> /dev/null
     33 + WEBPACK_STATUS=$?
     34 + 
     35 + kill -0 $SRV_PID 2> /dev/null
     36 + SRV_STATUS=$?
     37 + 
     38 + if [ "$TRAPPED_SIGNAL" = "false" ]; then
     39 + if [ $WEBPACK_STATUS -ne 0 ] || [ $SRV_STATUS -ne 0 ]; then
     40 + if [ $WEBPACK_STATUS -eq 0 ]; then
     41 + kill -15 $WEBPACK_PID;
     42 + wait $WEBPACK_PID;
     43 + fi
     44 + if [ $SRV_STATUS -eq 0 ]; then
     45 + kill -15 $SRV_PID;
     46 + wait $SRV_PID;
     47 + fi
     48 + exit 1;
     49 + fi
     50 + else
     51 + if [ $WEBPACK_STATUS -ne 0 ] && [ $SRV_STATUS -ne 0 ]; then
     52 + exit 0;
     53 + fi
     54 + fi
     55 + 
     56 + sleep 1
     57 +done
     58 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/webpack.config.js
    1  -const path = require('path');
    2  -const webpack = require('webpack');
    3  -const HtmlWebpackPlugin = require('html-webpack-plugin');
    4  -const MiniCssExtractPlugin = require('mini-css-extract-plugin');
    5  -const CopyPlugin = require('copy-webpack-plugin');
    6  -const SpritePlugin = require('svg-sprite-loader/plugin');
    7  - 
    8  -const srcDir = 'src';
    9  -const distDir = 'dist';
    10  - 
    11  -const env = ['API_ORIGIN', 'PLAUSIBLE_DOMAIN', 'GA_ID', 'DUMP_ANALYTICS'].reduce((acc, val) => {
    12  - acc[val] = `"${process.env[val]}"`;
    13  - return acc;
    14  -}, {});
    15  - 
    16  -module.exports = (_, argv) => {
    17  - const { mode = 'development' } = argv;
    18  - const isDevelopment = mode === 'development';
    19  - 
    20  - return {
    21  - mode,
    22  - entry: path.join(__dirname, srcDir, 'index.tsx'),
    23  - resolve: {
    24  - extensions: ['.ts', '.tsx', '.js'],
    25  - modules: [path.resolve(__dirname, srcDir), 'node_modules'],
    26  - },
    27  - devtool: isDevelopment ? 'eval-source-map' : false,
    28  - module: {
    29  - rules: [
    30  - {
    31  - test: /\.(png|svg|jpg|jpeg|gif|woff(2)?|ttf|eot)$/i,
    32  - exclude: /sprite\/([^\/]*)\.svg$/,
    33  - type: 'asset/resource',
    34  - },
    35  - {
    36  - test: /sprite\/([^\/]*)\.svg$/,
    37  - loader: 'svg-sprite-loader',
    38  - options: {
    39  - extract: true,
    40  - spriteFilename: 'sprite.svg',
    41  - },
    42  - },
    43  - {
    44  - test: /\.(tsx|ts)?$/,
    45  - use: 'ts-loader',
    46  - exclude: '/node_modules/',
    47  - },
    48  - {
    49  - test: /\.module\.scss$/,
    50  - use: [
    51  - isDevelopment ? 'style-loader' : MiniCssExtractPlugin.loader,
    52  - {
    53  - loader: 'css-loader',
    54  - options: {
    55  - modules: true,
    56  - sourceMap: isDevelopment,
    57  - },
    58  - },
    59  - {
    60  - loader: 'sass-loader',
    61  - options: {
    62  - sourceMap: isDevelopment,
    63  - },
    64  - },
    65  - ],
    66  - },
    67  - {
    68  - test: /\.s?css$/,
    69  - exclude: /\.module.scss$/,
    70  - use: [
    71  - isDevelopment ? 'style-loader' : MiniCssExtractPlugin.loader,
    72  - 'css-loader',
    73  - {
    74  - loader: 'sass-loader',
    75  - options: {
    76  - sourceMap: isDevelopment,
    77  - },
    78  - },
    79  - ],
    80  - },
    81  - ],
    82  - },
    83  - plugins: [
    84  - new MiniCssExtractPlugin({
    85  - filename: isDevelopment ? '[name].css' : '[name].[fullhash].css',
    86  - chunkFilename: isDevelopment ? '[id].css' : '[id].[fullhash].css',
    87  - }),
    88  - new HtmlWebpackPlugin({
    89  - template: path.join(__dirname, 'index.html'),
    90  - }),
    91  - new webpack.EnvironmentPlugin(env),
    92  - new CopyPlugin({
    93  - patterns: [
    94  - { from: 'robots.txt', to: 'robots.txt' },
    95  - { from: 'src/assets/sharing-image.png', to: 'static/sharing-image.png' },
    96  - ],
    97  - }),
    98  - new SpritePlugin(),
    99  - ],
    100  - devServer: {
    101  - static: {
    102  - directory: path.join(__dirname, distDir),
    103  - },
    104  - compress: true,
    105  - historyApiFallback: {
    106  - disableDotRule: true,
    107  - },
    108  - port: 3000,
    109  - },
    110  - output: {
    111  - filename: 'bundle.[fullhash].js',
    112  - path: path.resolve(__dirname, distDir),
    113  - publicPath: '/',
    114  - assetModuleFilename: 'static/[hash][ext]',
    115  - },
    116  - };
    117  -};
    118  - 
  • ■ ■ ■ ■ ■ ■
    packages/worker/src/npmRegistry/api.test.ts
    skipped 6 lines
    7 7  // TODO: The replicate npm registry may be down sometimes and these tests are flaky
    8 8  describe.skip('npmRegistry / api', () => {
    9 9   it('fetchPackageMetadata', async () => {
    10  - const metadata = await fetchPackageMetadata('react');
     10 + const metadata = await fetchPackageMetadata('react-ga');
    11 11   
    12  - expect(semver.gte(metadata.latestVersion, '18.0.0')).toBeTruthy();
    13  - expect(metadata.repositoryUrl).toEqual('https://github.com/facebook/react');
    14  - expect(metadata.homepageUrl).toEqual('https://reactjs.org/');
     12 + expect(semver.gte(metadata.latestVersion, '3.0.0')).toBeTruthy();
     13 + expect(metadata.repositoryUrl).toEqual('git+ssh://git@github.com/react-ga/react-ga.git');
     14 + expect(metadata.homepageUrl).toEqual('https://github.com/react-ga/react-ga');
     15 + expect(metadata.maintainers[0].name).toEqual('simeonc');
     16 + expect(metadata.keywords).toContain('Google Analytics');
     17 + expect(metadata.versionSpecificValues).toHaveProperty('3.2.1');
     18 + expect(metadata.versionSpecificValues['3.2.1'].unpackedSize).toEqual(212680);
     19 + expect(metadata.versionSpecificValues['3.2.1'].dependencies).toHaveProperty('react');
    15 20   expect(metadata.description).toContain('React');
    16  - expect(metadata.license).toEqual('MIT');
    17  - expect(metadata.versionList).toContain('17.0.0');
     21 + expect(metadata.fullDescription).toContain('React Google Analytics Module');
     22 + expect(metadata.license).toEqual('Apache-2.0');
    18 23   expect(metadata.updatedAt > new Date(2010, 1, 1)).toBeTruthy();
    19 24   expect(metadata.updateSeq > 100_000).toBeTruthy();
    20 25   });
    skipped 21 lines
  • ■ ■ ■ ■ ■
    packages/worker/src/npmRegistry/api.ts
    1 1  import nano from 'nano';
     2 +import { createHash } from 'crypto';
    2 3  import fetch from 'node-fetch';
    3 4   
    4 5  // We use the relicate API since the `registry.npmjs.com`
    5 6  // does not support `local_seq` parameter and the `/changes` endpoint.
    6 7  const REGISTRY_URL = 'https://replicate.npmjs.com';
    7 8  const NPM_API_ORIGIN = 'https://api.npmjs.org';
     9 +const makeAvatarUrl = (email: string) =>
     10 + `https://s.gravatar.com/avatar/${createHash('md5').update(email).digest('hex')}`;
    8 11   
    9 12  type DocumentScope = {
    10 13   description?: string;
     14 + readme?: string;
     15 + maintainers?: Array<{ name: string; email: string }>;
     16 + keywords?: string[];
    11 17   license?: string;
    12 18   homepage?: string;
    13 19   repository?:
    skipped 11 lines
    25 31   'dist-tags': {
    26 32   latest: string;
    27 33   };
    28  - versions: Record<string, Record<string, unknown>>;
     34 + versions?: Record<
     35 + string,
     36 + {
     37 + dependencies?: Record<string, string>;
     38 + peerDependencies?: Record<string, string>;
     39 + dist: {
     40 + unpackedSize?: number;
     41 + };
     42 + }
     43 + >;
    29 44  };
    30 45   
    31 46  const registry = nano(REGISTRY_URL).scope<DocumentScope>('registry');
    32 47   
    33 48  export async function fetchPackageMetadata(name: string) {
    34 49   const document = await registry.get(name, { local_seq: true });
    35  - const versionList = Object.keys(document.versions);
     50 + const versionList = Object.keys(document.versions ?? {});
    36 51   
    37 52   let homepageUrl, repositoryUrl;
    38 53   
    skipped 16 lines
    55 70   // );
    56 71   
    57 72   return {
    58  - description: document.description ?? undefined,
    59  - license: document.license ?? undefined,
     73 + description: document.description,
     74 + fullDescription: document.readme,
     75 + maintainers: (document.maintainers ?? []).map((author) => ({
     76 + name: author.name,
     77 + email: author.email,
     78 + avatar: makeAvatarUrl(author.email),
     79 + })),
     80 + keywords: document.keywords ?? [],
     81 + license: document.license,
    60 82   homepageUrl,
    61 83   repositoryUrl,
    62 84   updateSeq: Number(document._local_seq),
    63 85   updatedAt: new Date(document.time.modified),
    64 86   latestVersion: document['dist-tags'].latest,
    65  - versionList,
     87 + versionSpecificValues: versionList.reduce((acc, el) => {
     88 + acc[el] = {
     89 + dependencies: {
     90 + ...(document.versions?.[el].dependencies ?? {}),
     91 + ...(document.versions?.[el].peerDependencies ?? {}),
     92 + },
     93 + unpackedSize: document.versions?.[el].dist.unpackedSize,
     94 + };
     95 + return acc;
     96 + }, {} as Record<string, { dependencies: Record<string, string>; unpackedSize?: number }>),
    66 97   };
    67 98  }
    68 99   
    skipped 76 lines
  • ■ ■ ■ ■ ■ ■
    packages/worker/src/tasks/syncPackageIndexBatch.test.ts
    skipped 21 lines
    22 22   Promise.resolve({
    23 23   updateSeq: 123,
    24 24   updatedAt,
    25  - versionList: ['0.1.0', '1.0.0', '1.0.1', '1.1.0'],
     25 + versionSpecificValues: { '0.1.0': {}, '1.0.0': {}, '1.0.1': {}, '1.1.0': {} },
    26 26   latestVersion: '1.1.0',
    27 27   downloads: 500,
    28 28   } as any)
    skipped 35 lines
    64 64   Promise.resolve({
    65 65   updateSeq: savedPackage.updateSeq,
    66 66   updatedAt: savedPackage.updatedAt,
    67  - versionList: ['0.1.0', '1.0.0'],
     67 + versionSpecificValues: { '0.1.0': {}, '1.0.0': {} },
    68 68   latestVersion: savedPackage.latestVersion,
    69 69   downloads: 100,
    70 70   } as any)
    skipped 19 lines
  • ■ ■ ■ ■ ■
    packages/worker/src/tasks/syncPackageIndexBatch.ts
    skipped 23 lines
    24 24   await Promise.all([
    25 25   internalApi.requestPackageIndexing({
    26 26   name,
    27  - versions: statsAndMetadata.versionList,
     27 + versions: Object.keys(statsAndMetadata.versionSpecificValues),
    28 28   }),
    29 29   PackageMetadata.upsert(
    30 30   {
    31 31   name: name,
    32 32   latestVersion: statsAndMetadata.latestVersion,
    33 33   description: statsAndMetadata.description,
     34 + fullDescription: statsAndMetadata.fullDescription,
     35 + maintainers: statsAndMetadata.maintainers,
     36 + keywords: statsAndMetadata.keywords,
    34 37   homepageUrl: statsAndMetadata.homepageUrl,
    35 38   repositoryUrl: statsAndMetadata.repositoryUrl,
    36 39   license: statsAndMetadata.license,
     40 + versionSpecificValues: statsAndMetadata.versionSpecificValues,
    37 41   monthlyDownloads: statsAndMetadata.downloads,
    38 42   updateSeq: statsAndMetadata.updateSeq,
    39 43   updatedAt: statsAndMetadata.updatedAt,
    skipped 6 lines
  • ■ ■ ■ ■ ■ ■
    packages/worker/tsconfig.build.json
    skipped 4 lines
    5 5   "baseUrl": "./src"
    6 6   },
    7 7   "include": [
    8  - "./src/**/*",
    9  - "../../index.d.ts"
     8 + "./src/**/*"
    10 9   ],
    11 10   "exclude": [
    12 11   "**/*.test.ts"
    13 12   ]
    14 13  }
     14 + 
  • ■ ■ ■ ■ ■
    yarn.lock
    skipped 5791 lines
    5792 5792   optionalDependencies:
    5793 5793   fsevents "^1.2.7"
    5794 5794   
    5795  -chokidar@^3.4.1, chokidar@^3.4.2, chokidar@^3.5.3:
     5795 +chokidar@^3.4.1, chokidar@^3.4.2, chokidar@^3.5.2, chokidar@^3.5.3:
    5796 5796   version "3.5.3"
    5797 5797   resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
    5798 5798   integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
    skipped 695 lines
    6494 6494   dependencies:
    6495 6495   ms "2.1.2"
    6496 6496   
    6497  -debug@^3.0.0, debug@^3.1.1:
     6497 +debug@^3.0.0, debug@^3.1.1, debug@^3.2.7:
    6498 6498   version "3.2.7"
    6499 6499   resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
    6500 6500   integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==
    skipped 2152 lines
    8653 8653   relateurl "^0.2.7"
    8654 8654   terser "^5.10.0"
    8655 8655   
     8656 +html-replace-webpack-plugin@^2.6.0:
     8657 + version "2.6.0"
     8658 + resolved "https://registry.yarnpkg.com/html-replace-webpack-plugin/-/html-replace-webpack-plugin-2.6.0.tgz#506d81e06cb5d6519281ce7c7dde7aeee04620e7"
     8659 + integrity sha512-BL0DgtqIAef2C8+Dq8v3Ork7FWLPVVkuFkd3DpFB8XxI8hgXiWytvWhGTztSwdiIrPstUPahL5g7W8ts/vuERw==
     8660 + 
    8656 8661  html-tags@^3.1.0:
    8657 8662   version "3.1.0"
    8658 8663   resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.1.0.tgz#7b5e6f7e665e9fb41f30007ed9e0d41e97fb2140"
    skipped 206 lines
    8865 8870   resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501"
    8866 8871   integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE=
    8867 8872   
     8873 +ignore-by-default@^1.0.1:
     8874 + version "1.0.1"
     8875 + resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09"
     8876 + integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==
     8877 + 
    8868 8878  ignore@^4.0.3:
    8869 8879   version "4.0.6"
    8870 8880   resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
    skipped 2220 lines
    11091 11101   stdout-stream "^1.4.0"
    11092 11102   "true-case-path" "^1.0.2"
    11093 11103   
     11104 +nodemon@^2.0.19:
     11105 + version "2.0.19"
     11106 + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.19.tgz#cac175f74b9cb8b57e770d47841995eebe4488bd"
     11107 + integrity sha512-4pv1f2bMDj0Eeg/MhGqxrtveeQ5/G/UVe9iO6uTZzjnRluSA4PVWf8CW99LUPwGB3eNIA7zUFoP77YuI7hOc0A==
     11108 + dependencies:
     11109 + chokidar "^3.5.2"
     11110 + debug "^3.2.7"
     11111 + ignore-by-default "^1.0.1"
     11112 + minimatch "^3.0.4"
     11113 + pstree.remy "^1.1.8"
     11114 + semver "^5.7.1"
     11115 + simple-update-notifier "^1.0.7"
     11116 + supports-color "^5.5.0"
     11117 + touch "^3.1.0"
     11118 + undefsafe "^2.0.5"
     11119 + 
    11094 11120  nopt@^5.0.0:
    11095 11121   version "5.0.0"
    11096 11122   resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88"
    11097 11123   integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==
     11124 + dependencies:
     11125 + abbrev "1"
     11126 + 
     11127 +nopt@~1.0.10:
     11128 + version "1.0.10"
     11129 + resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee"
     11130 + integrity sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==
    11098 11131   dependencies:
    11099 11132   abbrev "1"
    11100 11133   
    skipped 80 lines
    11181 11214   dependencies:
    11182 11215   boolbase "^1.0.0"
    11183 11216   
     11217 +null-loader@^4.0.1:
     11218 + version "4.0.1"
     11219 + resolved "https://registry.yarnpkg.com/null-loader/-/null-loader-4.0.1.tgz#8e63bd3a2dd3c64236a4679428632edd0a6dbc6a"
     11220 + integrity sha512-pxqVbi4U6N26lq+LmgIbB5XATP0VdZKOG25DhHi8btMmJJefGArFyDg1yc4U3hWCJbMqSrw0qyrz1UQX+qYXqg==
     11221 + dependencies:
     11222 + loader-utils "^2.0.0"
     11223 + schema-utils "^3.0.0"
     11224 + 
    11184 11225  num2fraction@^1.2.2:
    11185 11226   version "1.2.2"
    11186 11227   resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede"
    skipped 1001 lines
    12188 12229   version "1.8.0"
    12189 12230   resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24"
    12190 12231   integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==
     12232 + 
     12233 +pstree.remy@^1.1.8:
     12234 + version "1.1.8"
     12235 + resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a"
     12236 + integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==
    12191 12237   
    12192 12238  public-encrypt@^4.0.0:
    12193 12239   version "4.0.3"
    skipped 1023 lines
    13217 13263   dependencies:
    13218 13264   semver "^6.3.0"
    13219 13265   
    13220  -"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.6.0:
     13266 +"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.6.0, semver@^5.7.1:
    13221 13267   version "5.7.1"
    13222 13268   resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
    13223 13269   integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
    13224 13270   
    13225  -[email protected]:
     13271 +[email protected], semver@~7.0.0:
    13226 13272   version "7.0.0"
    13227 13273   resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e"
    13228 13274   integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==
    skipped 217 lines
    13446 13492   resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
    13447 13493   integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
    13448 13494   
     13495 +simple-update-notifier@^1.0.7:
     13496 + version "1.0.7"
     13497 + resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-1.0.7.tgz#7edf75c5bdd04f88828d632f762b2bc32996a9cc"
     13498 + integrity sha512-BBKgR84BJQJm6WjWFMHgLVuo61FBDSj1z/xSFUIozqO6wO7ii0JxCqlIud7Enr/+LhlbNI0whErq96P2qHNWew==
     13499 + dependencies:
     13500 + semver "~7.0.0"
     13501 + 
    13449 13502  sisteransi@^1.0.5:
    13450 13503   version "1.0.5"
    13451 13504   resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
    skipped 522 lines
    13974 14027   dependencies:
    13975 14028   has-flag "^1.0.0"
    13976 14029   
    13977  -supports-color@^5.3.0:
     14030 +supports-color@^5.3.0, supports-color@^5.5.0:
    13978 14031   version "5.5.0"
    13979 14032   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
    13980 14033   integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
    skipped 55 lines
    14036 14089   query-string "^4.3.2"
    14037 14090   traverse "^0.6.6"
    14038 14091   
    14039  -"svg-sprite-loader@git+https://github.com/ctizen/svg-sprite-loader.git#f73ede9aa3e357b7d671d2fb89e9d8846c0aa2ba":
     14092 +"svg-sprite-loader@git+https://github.com/ctizen/svg-sprite-loader.git#5fc8046921ce575698126e2fa17742678b0b91ac":
    14040 14093   version "6.0.11"
    14041  - resolved "git+https://github.com/ctizen/svg-sprite-loader.git#f73ede9aa3e357b7d671d2fb89e9d8846c0aa2ba"
     14094 + resolved "git+https://github.com/ctizen/svg-sprite-loader.git#5fc8046921ce575698126e2fa17742678b0b91ac"
    14042 14095   dependencies:
    14043 14096   bluebird "^3.5.0"
    14044 14097   deepmerge "1.3.2"
    skipped 273 lines
    14318 14371   resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
    14319 14372   integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
    14320 14373   
     14374 +touch@^3.1.0:
     14375 + version "3.1.0"
     14376 + resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b"
     14377 + integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==
     14378 + dependencies:
     14379 + nopt "~1.0.10"
     14380 + 
    14321 14381  tough-cookie@^4.0.0:
    14322 14382   version "4.0.0"
    14323 14383   resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4"
    skipped 278 lines
    14602 14662   has-bigints "^1.0.1"
    14603 14663   has-symbols "^1.0.2"
    14604 14664   which-boxed-primitive "^1.0.2"
     14665 + 
     14666 +undefsafe@^2.0.5:
     14667 + version "2.0.5"
     14668 + resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c"
     14669 + integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==
    14605 14670   
    14606 14671  unfetch@^4.2.0:
    14607 14672   version "4.2.0"
    skipped 977 lines
Please wait...
Page is in error, reload to recover