Skip to content

Commit

Permalink
fix: migrate framework-info to TS and vitest and enable more strictness
Browse files Browse the repository at this point in the history
  • Loading branch information
danez committed Jan 18, 2023
1 parent 13649c6 commit 4b79298
Show file tree
Hide file tree
Showing 36 changed files with 1,748 additions and 911 deletions.
1,301 changes: 1,091 additions & 210 deletions package-lock.json

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions packages/build/src/log/messages/compatibility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const getPluginDescription = function (
},
debug,
) {
const versionedPackage = getVersionedPackage(packageName, version)
const versionedPackage = getVersionedPackage(version)
const pluginOrigin = getPluginOrigin(loadedFrom, origin)
const description = `${THEME.highlightWords(packageName)}${versionedPackage} ${pluginOrigin}`
if (!debug) {
Expand Down Expand Up @@ -106,7 +106,7 @@ const getOutdatedPlugin = function ({
loadedFrom,
origin,
}) {
const versionedPackage = getVersionedPackage(packageName, version)
const versionedPackage = getVersionedPackage(version)
const outdatedDescription = getOutdatedDescription({ latestVersion, migrationGuide, loadedFrom, origin })
return `${THEME.warningHighlightWords(packageName)}${versionedPackage}: ${outdatedDescription}`
}
Expand Down Expand Up @@ -165,14 +165,14 @@ const getIncompatiblePlugin = function ({
compatibleVersion,
compatWarning,
}) {
const versionedPackage = getVersionedPackage(packageName, version)
const versionedPackage = getVersionedPackage(version)
return `${THEME.warningHighlightWords(
packageName,
)}${versionedPackage}: version ${compatibleVersion} is the most recent version compatible with ${compatWarning}`
}

// Make sure we handle `package.json` with `version` being either `undefined`
// or an empty string
const getVersionedPackage = function (packageName, version = '') {
const getVersionedPackage = function (version = '') {
return version === '' ? '' : `@${version}`
}
2 changes: 1 addition & 1 deletion packages/build/src/plugins/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ const loadPluginFiles = async function ({
// Core plugins can only be included once.
// For example, when testing core plugins, they might be included as local plugins,
// in which case they should not be included twice.
const isNotRedundantCorePlugin = function (pluginOptionsA, index, pluginsOptions) {
const isNotRedundantCorePlugin = function (pluginOptionsA, _index, pluginsOptions) {
return (
pluginOptionsA.loadedFrom !== 'core' ||
pluginsOptions.every(
Expand Down
5 changes: 3 additions & 2 deletions packages/cache-utils/src/fs.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { promises as fs } from 'fs'
import { promises as fs, Stats } from 'fs'
import { basename, dirname } from 'path'

import cpy from 'cpy'
Expand Down Expand Up @@ -65,10 +65,11 @@ const getSrcGlob = async function (src: string): Promise<null | { srcGlob: strin
return { srcGlob: srcBasename, cwd, isDir }
}

const getStat = async (src: string) => {
const getStat = async (src: string): Promise<Stats | undefined> => {
try {
return await fs.stat(src)
} catch {
// continue regardless error
return undefined
}
}
2 changes: 1 addition & 1 deletion packages/framework-info/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ node_modules
yarn-error.log
.vscode
dist
build
src/generated
.DS_Store

# Local Netlify folder
Expand Down
21 changes: 9 additions & 12 deletions packages/framework-info/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@
"type": "module",
"main": "./dist/index.umd.cjs",
"exports": {
"node": "./src/main.js",
"node": "./lib/main.js",
"default": "./dist/index.umd.cjs"
},
"files": [
"build/*.js",
"src/**/*.js",
"lib",
"dist/index.umd.cjs"
],
"scripts": {
Expand All @@ -20,9 +19,9 @@
"build:browser": "run-s build:browser:*",
"build:browser:core": "vite build",
"build:browser:site-root": "cpy index.html ./dist",
"test": "npm run test:dev",
"test:dev": "ava",
"test:ci": "c8 -r lcovonly -r text -r json ava"
"test": "vitest run",
"test:dev": "vitest",
"test:ci": "vitest run --reporter=default"
},
"keywords": [
"dependency-management",
Expand Down Expand Up @@ -58,10 +57,9 @@
"url": "https://github.com/netlify/build/issues"
},
"dependencies": {
"ajv": "^8.0.0",
"ajv": "^8.12.0",
"filter-obj": "^3.0.0",
"find-up": "^6.3.0",
"fs-extra": "^10.1.0",
"is-plain-obj": "^4.0.0",
"locate-path": "^7.0.0",
"p-filter": "^3.0.0",
Expand All @@ -72,19 +70,18 @@
"url": "^0.11.0"
},
"devDependencies": {
"ava": "^4.0.0",
"babel-loader": "^8.2.2",
"c8": "^7.11.0",
"cpy": "^9.0.0",
"cpy-cli": "^4.0.0",
"del": "^6.0.0",
"fast-glob": "^3.2.12",
"npm-run-all": "^4.1.5",
"path-browserify": "^1.0.1",
"rollup-plugin-node-polyfills": "^0.2.1",
"test-each": "^4.0.0",
"tmp-promise": "^3.0.2",
"typescript": "^4.9.4",
"vite": "^3.1.6"
"vite": "^4.0.4",
"vitest": "^0.27.1"
},
"engines": {
"node": "^14.14.0 || >=16.0.0"
Expand Down
20 changes: 11 additions & 9 deletions packages/framework-info/scripts/transform_json.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { promises as fs } from 'fs'
import process from 'process'
import { fileURLToPath } from 'url'

import { FRAMEWORK_NAMES } from '../src/frameworks/main.js'
import glob from 'fast-glob'

const FRAMEWORKS_DIR = new URL('../src/frameworks/', import.meta.url)
const BUILD_DIR = new URL('../build/', import.meta.url)
const FRAMEWORKS_BUILD = new URL('frameworks.js', BUILD_DIR)
const BUILD_DIR = new URL('../src/generated/', import.meta.url)
const FRAMEWORKS_BUILD = new URL('frameworks.ts', BUILD_DIR)

// We enforce frameworks to be written with JSON to ensure they remain logicless
// which is simpler for contributors and avoid adding unnecessary logic.
Expand All @@ -14,7 +15,8 @@ const FRAMEWORKS_BUILD = new URL('frameworks.js', BUILD_DIR)
// Therefore, we transform JSON to JavaScript files using at build time.
const transformFrameworks = async function () {
await fs.mkdir(BUILD_DIR, { recursive: true })
const frameworks = await Promise.all(FRAMEWORK_NAMES.map(transformFramework))
const frameworkFiles = await glob('*.json', { cwd: fileURLToPath(FRAMEWORKS_DIR), absolute: true })
const frameworks = await Promise.all(frameworkFiles.map(transformFramework))
const fileContents = `${FRAMEWORKS_HEADER}${JSON.stringify(frameworks, null, 2)}`
await fs.writeFile(FRAMEWORKS_BUILD, fileContents)
}
Expand All @@ -31,16 +33,16 @@ const updateLogoUrls = function (contents) {
return updatedContents
}

const transformFramework = async function (frameworkName) {
const frameworkUrl = new URL(`${frameworkName}.json`, FRAMEWORKS_DIR)
const jsonContents = await fs.readFile(frameworkUrl)
const transformFramework = async function (frameworkFile) {
const jsonContents = await fs.readFile(frameworkFile)
const contents = JSON.parse(jsonContents)

const updatedContents = updateLogoUrls(contents)
return updatedContents
}

const FRAMEWORKS_HEADER = `// This file is autogenerated at build time
export const FRAMEWORKS = `
const FRAMEWORKS_HEADER = `import type { FrameworkDefinition } from "../types.js"
// This file is autogenerated at build time
export const FRAMEWORKS: FrameworkDefinition[] = `

transformFrameworks()
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import { cwd, version as nodejsVersion } from 'process'

import { locatePath } from 'locate-path'
import { readPackageUp } from 'read-pkg-up'
import { PackageJson, readPackageUp } from 'read-pkg-up'

/*
interface PackageJsonInfo {
packageJson?: PackageJson
packageJsonPath?: string
}

interface Context extends PackageJsonInfo {
pathExists: (path: string) => Promise<boolean>
export type PathExists = (path: string) => Promise<boolean>

export interface Context extends PackageJsonInfo {
pathExists: PathExists
nodeVersion: string
}
*/

export const getPackageJson = async (projectDir /*: string*/) /*: Promise<PackageJsonInfo>*/ => {
export const getPackageJson = async (projectDir: string): Promise<PackageJsonInfo> => {
try {
const result = await readPackageUp({ cwd: projectDir, normalize: false })
if (result === undefined) {
Expand All @@ -30,10 +30,7 @@ export const getPackageJson = async (projectDir /*: string*/) /*: Promise<Packag
}
}

export const getContext = async (
projectDir /*: string*/ = cwd(),
nodeVersion /*: string*/ = nodejsVersion,
) /*: Promise<Context>*/ => {
export const getContext = async (projectDir: string = cwd(), nodeVersion: string = nodejsVersion): Promise<Context> => {
const { packageJson, packageJsonPath = projectDir } = await getPackageJson(projectDir)

return {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,71 +1,26 @@
import pFilter from 'p-filter'
import type { PackageJson } from 'read-pkg-up'

import { FRAMEWORKS } from '../build/frameworks.js'

import type { Context, PathExists } from './context.js'
import { usesFramework } from './detect.js'
import { getDevCommands } from './dev.js'
import type { FrameworkName } from './frameworks/main.js'
import { FRAMEWORKS } from './generated/frameworks.js'
import { getPackageJsonContent } from './package.js'
import { getPlugins } from './plugins.js'
import { getRunScriptCommand } from './run_script.js'
import type { Framework, FrameworkDefinition } from './types.js'

const getContext = (context) => {
const getContext = (context: Context) => {
const { pathExists, packageJson, packageJsonPath = '.', nodeVersion } = context

return { pathExists, packageJson, packageJsonPath, nodeVersion }
}

/**
* @typedef {object} PollingStrategy
* @property {'TCP'|'HTTP'} name - Name of the polling strategy. Possible names - TCP,HTTP
*/

/**
* A callback to check if a path exists
* @callback PathExists
* @param {string} path
* @returns {Promise<boolean>}
*/

/**
* @typedef {object} Context
* @property {PathExists} pathExists - Checks if a path exists
* @property {object} packageJson - Content of package.json
* @property {string} [packageJsonPath='.'] - Path of package.json
* @property {nodeVersion} [nodeVersion] - Node.js version of the runtime environment. Used to recommend Netlify build plugins
*/

/**
* @typedef {object} Dev
* @property {string} commands - Dev command. There might be several alternatives or empty
* @property {number} port - Server port
* @property {PollingStrategy[]} pollingStrategies - Dev Server polling strategies
*/

/**
* @typedef {object} Build
* @property {string} commands - Build command. There might be several alternatives
* @property {string} directory - Relative path to the directory where files are built
*/

/**
* @typedef {object} Framework
* @property {string} id - framework id such as `"gatsby"`
* @property {string} name - framework name such as `"Gatsby"`
* @property {string} category - Category among `"static_site_generator"`, `"frontend_framework"` and `"build_tool"`
* @property {Dev} dev - Information about the dev command
* @property {Build} build - Information about the build command
* @property {string} staticAssetsDirectory - Directory where the framework stores static assets. Can be `undefined`
* @property {object} env - Environment variables that should be set when calling the dev command
* @property {string[]} plugins - A list of recommend Netlify build plugins to install for the framework
*/

/**
* Return all the frameworks used by a project.
*
* @param {Context} context - Context
*
* @returns {Promise<Framework[]>} frameworks - Frameworks used by a project
*/
export const listFrameworks = async function (context) {
export const listFrameworks = async function (context: Context): Promise<Framework[]> {
const { pathExists, packageJson, packageJsonPath, nodeVersion } = getContext(context)
const { npmDependencies, scripts, runScriptCommand } = await getProjectInfo({
pathExists,
Expand All @@ -81,13 +36,8 @@ export const listFrameworks = async function (context) {

/**
* Return whether a project uses a specific framework
*
* @param {string} frameworkId - Id such as `"gatsby"`
* @param {Context} [context] - Context
*
* @returns {Promise<boolean>} result - Whether the project uses this framework
*/
export const hasFramework = async function (frameworkId, context) {
export const hasFramework = async function (frameworkId: FrameworkName, context: Context): Promise<boolean> {
const framework = getFrameworkById(frameworkId)
const { pathExists, packageJson, packageJsonPath } = getContext(context)
const { npmDependencies } = await getProjectInfo({ pathExists, packageJson, packageJsonPath })
Expand All @@ -97,13 +47,8 @@ export const hasFramework = async function (frameworkId, context) {

/**
* Return some information about a framework used by a project.
*
* @param {string} frameworkId - Id such as `"gatsby"`
* @param {Context} [context] - Context
*
* @returns {Promise<Framework>} framework - Framework used by a project
*/
export const getFramework = async function (frameworkId, context) {
export const getFramework = async function (frameworkId: FrameworkName, context: Context): Promise<Framework> {
const framework = getFrameworkById(frameworkId)
const { pathExists, packageJson, packageJsonPath, nodeVersion } = getContext(context)
const { scripts, runScriptCommand } = await getProjectInfo({
Expand All @@ -115,25 +60,32 @@ export const getFramework = async function (frameworkId, context) {
return frameworkInfo
}

const getFrameworkById = function (frameworkId) {
const getFrameworkById = function (frameworkId: FrameworkName): FrameworkDefinition {
const framework = FRAMEWORKS.find(({ id }) => id === frameworkId)
if (framework === undefined) {
const frameworkIds = FRAMEWORKS.map((knownFramework) => getFrameworkId(knownFramework))
.sort()
.join(', ')
throw new Error(`Invalid framework "${frameworkId}". It should be one of: ${frameworkIds}`)
}

return framework
}

const getFrameworkId = function ({ id }) {
const getFrameworkId = function ({ id }: FrameworkDefinition): string {
return id
}

const getProjectInfo = async function ({ pathExists, packageJson, packageJsonPath }) {
const { npmDependencies, scripts } = await getPackageJsonContent({
packageJson,
})
const getProjectInfo = async function ({
pathExists,
packageJson,
packageJsonPath,
}: {
pathExists: PathExists
packageJson: PackageJson | undefined
packageJsonPath: string
}) {
const { npmDependencies, scripts } = getPackageJsonContent(packageJson)
const runScriptCommand = await getRunScriptCommand({ pathExists, packageJsonPath })
return { npmDependencies, scripts, runScriptCommand }
}
Expand All @@ -150,11 +102,20 @@ const getFrameworkInfo = function (
env,
logo,
plugins,
}: FrameworkDefinition,
{
scripts,
runScriptCommand,
nodeVersion,
}: {
scripts: Record<string, string>
runScriptCommand: string
nodeVersion: string
},
{ scripts, runScriptCommand, nodeVersion },
) {
): Framework {
const devCommands = getDevCommands({ frameworkDevCommand, scripts, runScriptCommand })
const recommendedPlugins = getPlugins(plugins, { nodeVersion })

return {
id,
name,
Expand Down
Loading

0 comments on commit 4b79298

Please sign in to comment.