Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: full ESM config support using Ziti (build #1041) #1048

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions examples/advanced-project/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
"license": "ISC",
"devDependencies": {
"checkly": "latest",
"ts-node": "10.9.1",
"typescript": "4.9.5"
"jiti": "^2"
}
}
3 changes: 1 addition & 2 deletions examples/boilerplate-project/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
"license": "ISC",
"devDependencies": {
"checkly": "latest",
"ts-node": "10.9.1",
"typescript": "4.9.5"
"jiti": "^2"
}
}
36 changes: 36 additions & 0 deletions package-lock.json

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

9 changes: 9 additions & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -113,13 +113,22 @@
"config": "3.3.9",
"cross-env": "7.0.3",
"jest": "29.7.0",
"jiti": "2.4.2",
"nanoid": "3.3.4",
"oclif": "3.7.3",
"simple-git-hooks": "2.11.1",
"ts-jest": "29.2.4",
"ts-node": "10.9.1",
"typescript": "5.3.3"
},
"peerDependencies": {
"jiti": ">=2"
},
"peerDependenciesMeta": {
"jiti": {
"optional": true
}
},
"jest": {
"testTimeout": 30000,
"projects": [
Expand Down
26 changes: 15 additions & 11 deletions packages/cli/src/playwright/playwright-config-loader.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
import path from 'path'
import { loadFile } from '../services/checkly-config-loader'
import fs from 'fs'
import path from 'path'
import { loadFile } from '../services/util'

export async function loadPlaywrightConfig () {
let config
const filenames = ['playwright.config.ts', 'playwright.config.js']
const filenames = [
'playwright.config.ts',
'playwright.config.mts',
'playwright.config.cts',
'playwright.config.js',
'playwright.config.mjs',
'playwright.config.cjs',
]
for (const configFile of filenames) {
if (!fs.existsSync(path.resolve(path.dirname(configFile)))) {
const configPath = path.resolve(configFile)
if (!fs.existsSync(configPath)) {
continue
}
const dir = path.resolve(path.dirname(configFile))
config = await loadFile(path.join(dir, configFile))
if (config) {
break
}
const result = await loadFile(configPath)
return result
}
return config
return undefined
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ describe('loadChecklyConfig()', () => {
await loadChecklyConfig(configDir)
} catch (e: any) {
expect(e.message).toContain(`Unable to locate a config at ${configDir} with ${
['checkly.config.ts', 'checkly.config.js', 'checkly.config.mjs'].join(', ')}.`)
['checkly.config.ts', 'checkly.config.mts', 'checkly.config.cts', 'checkly.config.js', 'checkly.config.mjs', 'checkly.config.cjs'].join(', ')}.`)
}
})
it('config TS file should export an object', async () => {
Expand Down
41 changes: 13 additions & 28 deletions packages/cli/src/services/checkly-config-loader.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as path from 'path'
import { existsSync } from 'fs'
import { loadJsFile, loadTsFile } from './util'
import { loadFile } from './util'
import { CheckProps } from '../constructs/check'
import { Session } from '../constructs'
import { Construct } from '../constructs/construct'
Expand Down Expand Up @@ -73,35 +73,19 @@ export type ChecklyConfig = {
}
}

// eslint-disable-next-line no-restricted-syntax
enum Extension {
JS = '.js',
MJS = '.mjs',
TS = '.ts',
}

export function loadFile (file: string) {
if (!existsSync(file)) {
return Promise.resolve(null)
}
switch (path.extname(file)) {
case Extension.JS:
return loadJsFile(file)
case Extension.MJS:
return loadJsFile(file)
case Extension.TS:
return loadTsFile(file)
default:
throw new Error(`Unsupported file extension ${file} for the config file`)
}
}

function isString (obj: any) {
return (Object.prototype.toString.call(obj) === '[object String]')
}

export function getChecklyConfigFile (): {checklyConfig: string, fileName: string} | undefined {
const filenames: string[] = ['checkly.config.ts', 'checkly.config.js', 'checkly.config.mjs']
const filenames = [
'checkly.config.ts',
'checkly.config.mts',
'checkly.config.cts',
'checkly.config.js',
'checkly.config.mjs',
'checkly.config.cjs',
]
let config
for (const configFile of filenames) {
const dir = path.resolve(path.dirname(configFile))
Expand All @@ -120,13 +104,14 @@ export function getChecklyConfigFile (): {checklyConfig: string, fileName: strin
return config
}

export async function loadChecklyConfig (dir: string, filenames = ['checkly.config.ts', 'checkly.config.js', 'checkly.config.mjs']): Promise<{ config: ChecklyConfig, constructs: Construct[] }> {
export async function loadChecklyConfig (dir: string, filenames = ['checkly.config.ts', 'checkly.config.mts', 'checkly.config.cts', 'checkly.config.js', 'checkly.config.mjs', 'checkly.config.cjs']): Promise<{ config: ChecklyConfig, constructs: Construct[] }> {
let config
Session.loadingChecklyConfigFile = true
Session.checklyConfigFileConstructs = []
for (const filename of filenames) {
config = await loadFile(path.join(dir, filename))
if (config) {
const filePath = path.join(dir, filename)
if (existsSync(filePath)) {
config = await loadFile(filePath)
break
}
}
Expand Down
12 changes: 4 additions & 8 deletions packages/cli/src/services/project-parser.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { glob } from 'glob'
import * as path from 'path'
import { loadJsFile, loadTsFile, pathToPosix } from './util'
import { loadFile, pathToPosix } from './util'
import {
Check, BrowserCheck, CheckGroup, Project, Session,
PrivateLocation, PrivateLocationCheckAssignment, PrivateLocationGroupAssignment, MultiStepCheck,
Expand Down Expand Up @@ -85,15 +85,11 @@ async function loadAllCheckFiles (
// setting the checkFilePath is used for filtering by file name on the command line
Session.checkFileAbsolutePath = checkFile
Session.checkFilePath = pathToPosix(path.relative(directory, checkFile))
if (checkFile.endsWith('.js')) {
await loadJsFile(checkFile)
} else if (checkFile.endsWith('.mjs')) {
await loadJsFile(checkFile)
} else if (checkFile.endsWith('.ts')) {
await loadTsFile(checkFile)
if (/\.[mc]?(js|ts)$/.test(checkFile)) {
await loadFile(checkFile)
} else {
throw new Error('Unable to load check configuration file with unsupported extension. ' +
`Please use a .js, .msj or .ts file instead.\n${checkFile}`)
`Please use a .js, .mjs, .cjs, .ts, .mts or .cts file instead.\n${checkFile}`)
}
Session.checkFilePath = undefined
Session.checkFileAbsolutePath = undefined
Expand Down
83 changes: 56 additions & 27 deletions packages/cli/src/services/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@ import { parse } from 'dotenv'
import { getProxyForUrl } from 'proxy-from-env'
import { httpOverHttp, httpsOverHttp, httpOverHttps, httpsOverHttps } from 'tunnel'

// Copied from oclif/core
// eslint-disable-next-line
const _importDynamic = new Function('modulePath', 'return import(modulePath)')

export interface GitInformation {
commitId: string
repoUrl?: string | null
Expand Down Expand Up @@ -54,17 +50,15 @@ export function findFilesRecursively (directory: string, ignoredPaths: Array<str
return files
}

export async function loadJsFile (filepath: string): Promise<any> {
export async function loadFile (filepath: string): Promise<any> {
try {
// There is a Node opened issue related with a segmentation fault using ES6 modules
// with jest https://github.com/nodejs/node/issues/35889
// As a work around, we check if Jest is running to modify the way to import the module.
// TODO: investigate if the issue is fixed to clean up the conditional import
let { default: exported } = typeof jest !== 'undefined'
? { default: await require(filepath) }
: await _importDynamic(pathToPosix(filepath))

if (exported instanceof Function) {
let exported: any
if (/\.[mc]?ts$/.test(filepath)) {
exported = await loadTsFileDefault(filepath)
} else {
exported = (await import(pathToPosix(filepath))).default
}
if (typeof exported === 'function') {
exported = await exported()
}
return exported
Expand All @@ -73,28 +67,63 @@ export async function loadJsFile (filepath: string): Promise<any> {
}
}

export async function loadTsFile (filepath: string): Promise<any> {
try {
const tsCompiler = await getTsCompiler()
async function loadTsFileDefault (filepath: string): Promise<any> {
const jiti = await getJiti()
if (jiti) {
return jiti.import<any>(filepath, {
default: true,
})
}

// Backward-compatibility for users who installed ts-node.
const tsCompiler = await getTsNodeService()
if (tsCompiler) {
tsCompiler.enabled(true)
let { default: exported } = await require(filepath)
if (exported instanceof Function) {
exported = await exported()
let exported: any
try {
exported = (await require(filepath)).default
} catch (err: any) {
if (err.message && err.message.contains('Unable to compile TypeScript')) {
throw new Error(`You might try installing "jiti" instead of "ts-node" for improved TypeScript support\n${err.stack}`)
}
throw err
}
tsCompiler.enabled(false) // Re-disable the TS compiler
return exported
}

throw new Error('Please install "jiti" to use TypeScript files')
}
Comment on lines +70 to +96
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Potential runtime error: String.prototype.contains is not standard JavaScript.

In the catch block at line 86, calling err.message.contains() may fail on environments that lack this method. Use the standard includes method instead:

- if (err.message && err.message.contains('Unable to compile TypeScript')) {
+ if (err.message && err.message.includes('Unable to compile TypeScript')) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async function loadTsFileDefault (filepath: string): Promise<any> {
const jiti = await getJiti()
if (jiti) {
return jiti.import<any>(filepath, {
default: true,
})
}
// Backward-compatibility for users who installed ts-node.
const tsCompiler = await getTsNodeService()
if (tsCompiler) {
tsCompiler.enabled(true)
let { default: exported } = await require(filepath)
if (exported instanceof Function) {
exported = await exported()
let exported: any
try {
exported = (await require(filepath)).default
} catch (err: any) {
if (err.message && err.message.contains('Unable to compile TypeScript')) {
throw new Error(`You might try installing "jiti" instead of "ts-node" for improved TypeScript support\n${err.stack}`)
}
throw err
}
tsCompiler.enabled(false) // Re-disable the TS compiler
return exported
}
throw new Error('Please install "jiti" to use TypeScript files')
}
async function loadTsFileDefault (filepath: string): Promise<any> {
const jiti = await getJiti()
if (jiti) {
return jiti.import<any>(filepath, {
default: true,
})
}
// Backward-compatibility for users who installed ts-node.
const tsCompiler = await getTsNodeService()
if (tsCompiler) {
tsCompiler.enabled(true)
let exported: any
try {
exported = (await require(filepath)).default
} catch (err: any) {
if (err.message && err.message.includes('Unable to compile TypeScript')) {
throw new Error(`You might try installing "jiti" instead of "ts-node" for improved TypeScript support\n${err.stack}`)
}
throw err
}
tsCompiler.enabled(false) // Re-disable the TS compiler
return exported
}
throw new Error('Please install "jiti" to use TypeScript files')
}
🧰 Tools
🪛 Biome (1.9.4)

[error] 86-86: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


// Regular type import gave issue with jest.
type Jiti = ReturnType<(typeof import('jiti', {
with: {
'resolution-mode': 'import'
}
}))['createJiti']>

// To avoid a dependency on typescript for users with no TS checks, we need to dynamically import jiti
let jiti: Jiti
async function getJiti (): Promise<Jiti | undefined> {
if (jiti) return jiti
try {
jiti = (await import('jiti')).createJiti(__filename)
} catch (err: any) {
throw new Error(`Error loading file ${filepath}\n${err.stack}`)
if (err.code === 'ERR_MODULE_NOT_FOUND' || err.code === 'MODULE_NOT_FOUND') {
return undefined
}
throw err
}
return jiti
}

// To avoid a dependency on typescript for users with no TS checks, we need to dynamically import ts-node
let tsCompiler: Service
async function getTsCompiler (): Promise<Service> {
if (tsCompiler) return tsCompiler
let tsNodeService: Service
async function getTsNodeService (): Promise<Service | undefined> {
if (tsNodeService) return tsNodeService
try {
const tsNode = await import('ts-node')
tsCompiler = tsNode.register({
tsNodeService = tsNode.register({
moduleTypes: {
'**/*': 'cjs',
},
Expand All @@ -104,11 +133,11 @@ async function getTsCompiler (): Promise<Service> {
})
} catch (err: any) {
if (err.code === 'ERR_MODULE_NOT_FOUND' || err.code === 'MODULE_NOT_FOUND') {
throw new Error('Please install "ts-node" and "typescript" to use TypeScript configuration files')
return undefined
}
throw err
}
return tsCompiler
return tsNodeService
}

/**
Expand Down
Loading
Loading