Skip to content

Commit

Permalink
fix(build-info): fix detection of Remix sites
Browse files Browse the repository at this point in the history
  • Loading branch information
serhalp committed Aug 28, 2024
1 parent 35bdb8f commit 7a4dfbb
Show file tree
Hide file tree
Showing 9 changed files with 265 additions and 8 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 24 additions & 1 deletion packages/build-info/src/frameworks/framework.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,21 @@ export interface Framework {
id: string
name: string
category: Category
/**
* If this is set, at least ONE of these must exist, anywhere in the project
*/
configFiles: string[]
/**
* if this is set, NONE of these must exist, anywhere in the project
*/
excludedConfigFiles: string[]
/**
* If this is set, at least ONE of these must be present in the `package.json` `dependencies|devDependencies`
*/
npmDependencies: string[]
/**
* if this is set, NONE of these must be present in the `package.json` `dependencies|devDependencies`
*/
excludedNpmDependencies?: string[]
version?: SemVer
/** Information on how it was detected and how accurate the detection is */
Expand Down Expand Up @@ -145,6 +158,7 @@ export abstract class BaseFramework implements Framework {
detected?: Detection
version?: SemVer
configFiles: string[] = []
excludedConfigFiles: string[] = []
npmDependencies: string[] = []
excludedNpmDependencies: string[] = []
plugins: string[] = []
Expand Down Expand Up @@ -256,6 +270,14 @@ export abstract class BaseFramework implements Framework {

/** detect if the framework config file is located somewhere up the tree */
private async detectConfigFile(): Promise<Detection | undefined> {
if (this.excludedConfigFiles?.length) {
const config = await this.project.fs.findUp(this.excludedConfigFiles, {
cwd: this.path || this.project.baseDirectory,
stopAt: this.project.root,
})
console.log('hello', config)
if (config) return
}
if (this.configFiles?.length) {
const config = await this.project.fs.findUp(this.configFiles, {
cwd: this.path || this.project.baseDirectory,
Expand All @@ -275,9 +297,10 @@ export abstract class BaseFramework implements Framework {

/**
* Checks if the project is using a specific framework:
* - if `npmDependencies` is set, one of them must be present in then `package.json` `dependencies|devDependencies`
* - if `npmDependencies` is set, one of them must be present in the `package.json` `dependencies|devDependencies`
* - if `excludedNpmDependencies` is set, none of them must be present in the `package.json` `dependencies|devDependencies`
* - if `configFiles` is set, one of the files must exist
* - if `excludedConfigFiles` is set, none of the files must exist
*/
async detect(): Promise<DetectedFramework | undefined> {
// we can force frameworks as well
Expand Down
2 changes: 2 additions & 0 deletions packages/build-info/src/frameworks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { Qwik } from './qwik.js'
import { ReactStatic } from './react-static.js'
import { CreateReactApp } from './react.js'
import { RedwoodJS } from './redwoodjs.js'
import { RemixClassic } from './remix-classic.js'
import { Remix } from './remix.js'
import { Roots } from './roots.js'
import { Sapper } from './sapper.js'
Expand Down Expand Up @@ -66,6 +67,7 @@ export const frameworks = [
ReactStatic,
RedwoodJS,
Remix,
RemixClassic,
Solid,
SolidStart,
Stencil,
Expand Down
84 changes: 84 additions & 0 deletions packages/build-info/src/frameworks/remix-classic.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { beforeEach, expect, test } from 'vitest'

import { mockFileSystem } from '../../tests/mock-file-system.js'
import { NodeFS } from '../node/file-system.js'
import { Project } from '../project.js'

beforeEach((ctx) => {
ctx.fs = new NodeFS()
})

test('detects a Remix Classic Compiler site with origin SSR as remix-classic', async ({ fs }) => {
const cwd = mockFileSystem({
'remix.config.js': '',
'package.json': JSON.stringify({
scripts: {
build: 'remix build',
dev: 'remix dev',
start: 'netlify serve',
typecheck: 'tsc',
},
dependencies: {
'@netlify/functions': '^2.8.1',
'@remix-run/css-bundle': '^2.9.2',
'@remix-run/node': '^2.9.2',
'@remix-run/react': '^2.9.2',
react: '^18.2.0',
'react-dom': '^18.2.0',
},
devDependencies: {
'@netlify/remix-adapter': '^2.4.0',
'@remix-run/dev': '^2.9.2',
'@remix-run/serve': '^2.9.2',
},
}),
})
const detected = await new Project(fs, cwd).detectFrameworks()

const detectedFrameworks = (detected ?? []).map((framework) => framework.id)
expect(detectedFrameworks).not.toContain('remix')
expect(detectedFrameworks).not.toContain('vite')

expect(detected?.[0].id).toBe('remix-classic')
expect(detected?.[0].build.command).toBe('remix build')
expect(detected?.[0].build.directory).toBe('public')
expect(detected?.[0].dev?.command).toBe('remix watch')
})

test('detects a Remix Classic Compiler site with edge SSR as remix-classic', async ({ fs }) => {
const cwd = mockFileSystem({
'remix.config.js': '',
'package.json': JSON.stringify({
scripts: {
build: 'remix build',
dev: 'remix dev',
start: 'netlify serve',
typecheck: 'tsc',
},
dependencies: {
'@netlify/functions': '^2.8.1',
'@netlify/remix-runtime': '^2.3.0',
'@remix-run/css-bundle': '^2.9.2',
'@remix-run/node': '^2.9.2',
'@remix-run/react': '^2.9.2',
react: '^18.2.0',
'react-dom': '^18.2.0',
},
devDependencies: {
'@netlify/remix-edge-adapter': '^3.3.0',
'@remix-run/dev': '^2.9.2',
'@remix-run/serve': '^2.9.2',
},
}),
})
const detected = await new Project(fs, cwd).detectFrameworks()

const detectedFrameworks = (detected ?? []).map((framework) => framework.id)
expect(detectedFrameworks).not.toContain('remix')
expect(detectedFrameworks).not.toContain('vite')

expect(detected?.[0].id).toBe('remix-classic')
expect(detected?.[0].build.command).toBe('remix build')
expect(detected?.[0].build.directory).toBe('public')
expect(detected?.[0].dev?.command).toBe('remix watch')
})
44 changes: 44 additions & 0 deletions packages/build-info/src/frameworks/remix-classic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { BaseFramework, Category, Framework } from './framework.js'

export class RemixClassic extends BaseFramework implements Framework {
readonly id = 'remix-classic'
name = 'Remix (Classic Compiler)'
npmDependencies = [
'@remix-run/react',
'@remix-run/dev',
'@remix-run/server-runtime',
'@netlify/remix-adapter',
'@netlify/remix-edge-adapter',
'@netlify/remix-runtime',
// Deprecated package name (deprecated in 1.6, removed in 2.0)
'remix',
// Deprecated Netlify packages
'@remix-run/netlify',
'@remix-run/netlify-edge',
]
configFiles = ['remix.config.js']
excludedConfigFiles = [
'vite.config.js',
'vite.config.mjs',
'vite.config.cjs',
'vite.config.ts',
'vite.config.mts',
'vite.config.cts',
]
category = Category.SSG

dev = {
command: 'remix watch',
}

build = {
command: 'remix build',
directory: 'public',
}

logo = {
default: '/logos/remix/default.svg',
light: '/logos/remix/light.svg',
dark: '/logos/remix/dark.svg',
}
}
84 changes: 84 additions & 0 deletions packages/build-info/src/frameworks/remix.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { beforeEach, expect, test } from 'vitest'

import { mockFileSystem } from '../../tests/mock-file-system.js'
import { NodeFS } from '../node/file-system.js'
import { Project } from '../project.js'

beforeEach((ctx) => {
ctx.fs = new NodeFS()
})

test('detects a Remix Vite site with origin SSR as remix', async ({ fs }) => {
const cwd = mockFileSystem({
'vite.config.js': '',
'package.json': JSON.stringify({
scripts: {
build: 'remix vite:build',
dev: 'remix vite:dev',
start: 'remix-serve ./build/server/index.js',
},
dependencies: {
'@remix-run/node': '^2.9.2',
'@remix-run/react': '^2.9.2',
'@remix-run/serve': '^2.9.2',
react: '^18.2.0',
'react-dom': '^18.2.0',
},
devDependencies: {
'@netlify/remix-adapter': '^2.4.0',
'@remix-run/dev': '^2.9.2',
'@types/react': '^18.2.20',
'@types/react-dom': '^18.2.7',
vite: '^5.0.0',
'vite-tsconfig-paths': '^4.2.1',
},
}),
})
const detected = await new Project(fs, cwd).detectFrameworks()
expect(detected?.[0].id).toBe('remix')
expect(detected?.[0].build.command).toBe('remix vite:build')
expect(detected?.[0].build.directory).toBe('dist')
expect(detected?.[0].dev?.command).toBe('remix vite:dev')

const detectedFrameworks = (detected ?? []).map((framework) => framework.id)
expect(detectedFrameworks).not.toContain('remix-classic')
expect(detectedFrameworks).not.toContain('vite')
})

test('detects a Remix Vite site with edge SSR as remix', async ({ fs }) => {
const cwd = mockFileSystem({
'vite.config.js': '',
'package.json': JSON.stringify({
scripts: {
build: 'remix vite:build',
dev: 'remix vite:dev',
start: 'remix-serve ./build/server/index.js',
},
dependencies: {
'@netlify/remix-runtime': '^2.3.0',
'@remix-run/node': '^2.9.2',
'@remix-run/react': '^2.9.2',
'@remix-run/serve': '^2.9.2',
react: '^18.2.0',
'react-dom': '^18.2.0',
},
devDependencies: {
'@netlify/remix-edge-adapter': '^3.3.0',
'@remix-run/dev': '^2.9.2',
'@types/react': '^18.2.20',
'@types/react-dom': '^18.2.7',
vite: '^5.0.0',
'vite-tsconfig-paths': '^4.2.1',
},
}),
})
const detected = await new Project(fs, cwd).detectFrameworks()
expect(detected?.[0].id).toBe('remix')
expect(detected?.[0].build.command).toBe('remix vite:build')
expect(detected?.[0].build.directory).toBe('dist')
expect(detected?.[0].dev?.command).toBe('remix vite:dev')

const detectedFrameworks = (detected ?? []).map((framework) => framework.id)
expect(detectedFrameworks).not.toContain('remix-classic')
expect(detectedFrameworks).not.toContain('vite')
})
26 changes: 21 additions & 5 deletions packages/build-info/src/frameworks/remix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,33 @@ import { BaseFramework, Category, Framework } from './framework.js'
export class Remix extends BaseFramework implements Framework {
readonly id = 'remix'
name = 'Remix'
npmDependencies = ['remix', '@remix-run/netlify', '@remix-run/netlify-edge']
configFiles = ['remix.config.js']
npmDependencies = [
'@remix-run/react',
'@remix-run/dev',
'@remix-run/server-runtime',
'@netlify/remix-adapter',
'@netlify/remix-edge-adapter',
'@netlify/remix-runtime',
]
configFiles = [
'vite.config.js',
'vite.config.mjs',
'vite.config.cjs',
'vite.config.ts',
'vite.config.mts',
'vite.config.cts',
]
excludedConfigFiles = ['remix.config.js']
category = Category.SSG

dev = {
command: 'remix watch',
command: 'remix vite:dev',
port: 5173,
}

build = {
command: 'remix build',
directory: 'public',
command: 'remix vite:build',
directory: 'dist',
}

logo = {
Expand Down
3 changes: 3 additions & 0 deletions packages/build-info/src/frameworks/vite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ export class Vite extends BaseFramework implements Framework {
name = 'Vite'
npmDependencies = ['vite']
excludedNpmDependencies = [
'@remix-run/react',
'@remix-run/dev',
'@remix-run/server-runtime',
'@shopify/hydrogen',
'@builder.io/qwik',
'solid-start',
Expand Down
1 change: 1 addition & 0 deletions packages/build-info/src/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@ export class Project {
// 2. only a config file was specified and matched
// 3. an npm dependency was specified but matched over the config file (least accurate)
// and prefer SSG over build tools
this.logger.debug(`[project.ts]: ${JSON.stringify(detected, null, 2)}`)
return filterByRelevance(detected)
} catch {
return []
Expand Down

0 comments on commit 7a4dfbb

Please sign in to comment.