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

TypeScript, ESM, tap18 support #371

Merged
merged 4 commits into from
Nov 15, 2023
Merged
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
Prev Previous commit
Next Next commit
feat: add typescript and esm support
  • Loading branch information
lukekarrys committed Nov 10, 2023
commit 321cb10537962d1ff51315777d27861bb83ea148
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ tap-testdir*/
!/SECURITY.md
!/tap-snapshots/
!/test/
!/tsconfig.json
!/workspace/
/workspace/*
!/workspace/test-workspace/
209 changes: 92 additions & 117 deletions lib/config.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
const { relative, dirname, join, extname, posix, win32 } = require('path')
const { defaults, pick, omit, uniq, isPlainObject } = require('lodash')
const { relative, dirname, join, extname } = require('path')
const { defaults, defaultsDeep, pick, omit, uniq, isPlainObject } = require('lodash')
const ciVersions = require('./util/ci-versions.js')
const parseDependabot = require('./util/dependabot.js')
const git = require('./util/git.js')
const gitignore = require('./util/gitignore.js')
const { mergeWithCustomizers, customizers } = require('./util/merge.js')
const { FILE_KEYS, parseConfig: parseFiles, getAddedFiles, mergeFiles } = require('./util/files.js')
const template = require('./util/template.js')
const getCmdPath = require('./util/get-cmd-path.js')
const importOrRequire = require('./util/import-or-require.js')
const { makePosix, deglob, posixDir, posixGlob } = require('./util/path.js')
const { name: NAME, version: LATEST_VERSION } = require('../package.json')

const CONFIG_KEY = 'templateOSS'
const getPkgConfig = (pkg) => pkg[CONFIG_KEY] || {}

const { name: NAME, version: LATEST_VERSION } = require('../package.json')
const MERGE_KEYS = [...FILE_KEYS, 'defaultContent', 'content']
const DEFAULT_CONTENT = require.resolve(NAME)
const getPkgConfig = (pkg) => pkg[CONFIG_KEY] || {}

const merge = mergeWithCustomizers(
customizers.mergeArrays('branches', 'distPaths', 'allowPaths', 'ignorePaths'),
Expand All @@ -23,43 +26,6 @@ const merge = mergeWithCustomizers(
}
)

const makePosix = (v) => v.split(win32.sep).join(posix.sep)
const deglob = (v) => makePosix(v).replace(/[/*]+$/, '')
const posixDir = (v) => `${v === '.' ? '' : deglob(v).replace(/\/$/, '')}${posix.sep}`
const posixGlob = (str) => `${posixDir(str)}**`

const getCmdPath = (key, { pkgConfig, rootConfig, isRoot, pkg, rootPkg }) => {
const result = (local, isRelative) => {
let root = local
const isLocal = local.startsWith('.') || local.startsWith('/')

if (isLocal) {
if (isRelative) {
// Make a path relative from a workspace to the root if we are in a workspace
local = makePosix(join(relative(pkg.path, rootPkg.path), local))
}
local = `node ${local}`
root = `node ${root}`
}

return {
isLocal,
local,
root,
}
}

if (pkgConfig[key]) {
return result(pkgConfig[key])
}

if (rootConfig[key]) {
return result(rootConfig[key], !isRoot)
}

return result(key)
}

const mergeConfigs = (...configs) => {
const mergedConfig = merge(...configs.map(c => pick(c, MERGE_KEYS)))
return defaults(mergedConfig, {
Expand All @@ -72,37 +38,33 @@ const mergeConfigs = (...configs) => {
})
}

const readContentPath = (path) => {
const readContentPath = async (path) => {
if (!path) {
return {}
}

let content = {}
const index = extname(path) === '.js' ? path : join(path, 'index.js')
const dir = dirname(index)

try {
content = require(index)
} catch {
// its ok if this fails since the content dir
// might only be to provide other files. the
// index.js is optional
}
const content = await importOrRequire(index)

return { content, dir }
}

const getConfig = (path, rawConfig) => {
const config = omit(readContentPath(path).content, FILE_KEYS)
const getConfig = async (path, rawConfig) => {
const { content } = await readContentPath(path)
const config = omit(content, FILE_KEYS)
return merge(config, rawConfig ? omit(rawConfig, FILE_KEYS) : {})
}

const getFiles = (path, rawConfig) => {
const { content, dir } = readContentPath(path)
const getFiles = async (path, rawConfig, templateSettings) => {
const { content, dir } = await readContentPath(path)
if (!dir) {
return []
}
return [parseFiles(pick(content, FILE_KEYS), dir, pick(rawConfig, FILE_KEYS)), dir]
return [
parseFiles(pick(content, FILE_KEYS), dir, pick(rawConfig, FILE_KEYS), templateSettings),
dir,
]
}

const getFullConfig = async ({
Expand All @@ -127,39 +89,15 @@ const getFullConfig = async ({
// These config items are merged betweent the root and child workspaces and only come from
// the package.json because they can be used to read configs from other the content directories
const mergedConfig = mergeConfigs(rootPkg.config, pkg.config)

const defaultConfig = getConfig(DEFAULT_CONTENT)
const [defaultFiles, defaultDir] = getFiles(DEFAULT_CONTENT, mergedConfig)
const defaultConfig = await getConfig(DEFAULT_CONTENT)
const useDefault = mergedConfig.defaultContent && defaultConfig

const rootConfig = getConfig(rootPkg.config.content, rootPkg.config)
const [rootFiles, rootDir] = getFiles(rootPkg.config.content, mergedConfig)
const rootConfig = await getConfig(rootPkg.config.content, rootPkg.config)

// The content config only gets set from the package we are in, it doesn't inherit
// anything from the root
const rootPkgConfig = merge(useDefault, rootConfig)
const pkgConfig = merge(useDefault, getConfig(pkg.config.content, pkg.config))
const [pkgFiles, pkgDir] = getFiles(mergedConfig.content, mergedConfig)

// Files get merged in from the default content (that template-oss provides) as well
// as any content paths provided from the root or the workspace
const fileDirs = uniq([useDefault && defaultDir, rootDir, pkgDir].filter(Boolean))
const files = mergeFiles(useDefault && defaultFiles, rootFiles, pkgFiles)
const repoFiles = isRoot ? files.rootRepo : files.workspaceRepo
const moduleFiles = isRoot ? files.rootModule : files.workspaceModule

const allowRootDirs = [
// Allways allow module files in root or workspaces
...getAddedFiles(moduleFiles),
...isRoot ? [
// in the root allow all repo files
...getAddedFiles(repoFiles),
// and allow all workspace repo level files in the root
...pkgs
.filter(p => p.path !== rootPkg.path && p.config.workspaceRepo !== false)
.flatMap(() => getAddedFiles(files.workspaceRepo)),
] : [],
]
const pkgConfig = merge(useDefault, await getConfig(pkg.config.content, pkg.config))

const npmPath = getCmdPath('npm', { pkgConfig, rootConfig, isRoot, pkg, rootPkg })
const npxPath = getCmdPath('npx', { pkgConfig, rootConfig, isRoot, pkg, rootPkg })
Expand All @@ -185,6 +123,8 @@ const getFullConfig = async ({
? pkgConfig.releaseBranch.replace(/\*/g, pkgConfig.backport)
: defaultBranch

const esm = pkg.pkgJson?.type === 'module' || !!pkgConfig.typescript || !!pkgConfig.esm

// all derived keys
const derived = {
isRoot,
Expand All @@ -209,25 +149,22 @@ const getFullConfig = async ({
releaseBranch,
publishTag,
dependabot: parseDependabot(pkgConfig, defaultConfig, gitBranches.branches),
// repo
// paths
repoDir: rootPkg.path,
repoFiles,
applyRepo: !!repoFiles,
// module
moduleDir: pkg.path,
moduleFiles,
applyModule: !!moduleFiles,
// package
pkgName: pkg.pkgJson.name,
pkgNameFs: pkg.pkgJson.name.replace(/\//g, '-').replace(/@/g, ''),
// paths
pkgPath,
pkgDir: posixDir(pkgPath),
pkgGlob: posixGlob(pkgPath),
pkgFlags: isWorkspace ? `-w ${pkg.pkgJson.name}` : '',
allFlags: isMono ? '-ws -iwr --if-present' : '',
workspacePaths,
workspaceGlobs: workspacePaths.map(posixGlob),
// type
esm,
cjsExt: esm ? 'cjs' : 'js',
deleteJsExt: esm ? 'js' : 'cjs',
// booleans to control application of updates
isForce,
isDogFood,
Expand All @@ -243,36 +180,29 @@ const getFullConfig = async ({
lockfile: rootPkgConfig.lockfile,
// ci versions / engines
ciVersions: ciVersions.get(pkg.pkgJson.engines?.node, pkgConfig),
// gitignore
ignorePaths: [
...gitignore.sort([
...gitignore.allowRootDir(allowRootDirs),
...isRoot && pkgConfig.lockfile ? ['!/package-lock.json'] : [],
...(pkgConfig.allowPaths || []).map((p) => `!${p}`),
...(pkgConfig.ignorePaths || []),
]),
// these cant be sorted since they rely on order
// to allow a previously ignored directoy
...isRoot
? gitignore.allowDir(wsPkgs.map((p) => makePosix(relative(rootPkg.path, p.path))))
: [],
],
// needs update if we are dogfooding this repo, with force argv, or its
// behind the current version
needsUpdate: isForce || isDogFood || !isLatest,
// templateoss specific values
__NAME__: NAME,
__CONFIG_KEY__: CONFIG_KEY,
__VERSION__: LATEST_VERSION,
__PARTIAL_DIRS__: fileDirs,
}

if (!pkgConfig.eslint) {
derived.ignorePaths = derived.ignorePaths.filter(p => !p.includes('eslint'))
if (Array.isArray(pkgConfig.requiredPackages?.devDependencies)) {
pkgConfig.requiredPackages.devDependencies =
pkgConfig.requiredPackages.devDependencies.filter(p => !p.includes('eslint'))
}
if (!pkgConfig.eslint && Array.isArray(pkgConfig.requiredPackages?.devDependencies)) {
pkgConfig.requiredPackages.devDependencies =
pkgConfig.requiredPackages.devDependencies.filter(p => !p.includes('eslint'))
}

if (pkgConfig.typescript) {
defaultsDeep(pkgConfig, { allowPaths: [], requiredPackages: { devDependencies: [] } })
pkgConfig.distPaths = null
pkgConfig.allowPaths.push('/src/')
pkgConfig.requiredPackages.devDependencies.push(
'typescript',
'tshy',
'@typescript-eslint/parser'
)
}

const gitUrl = await git.getUrl(rootPkg.path)
Expand All @@ -284,10 +214,55 @@ const getFullConfig = async ({
}
}

return {
...pkgConfig,
...derived,
}
const fullConfig = { ...pkgConfig, ...derived }

// files, come at the end since file names can be based on config
const [defaultFiles, defaultDir] = await getFiles(DEFAULT_CONTENT, mergedConfig, fullConfig)
const [rootFiles, rootDir] = await getFiles(rootPkg.config.content, mergedConfig, fullConfig)
const [pkgFiles, pkgDir] = await getFiles(mergedConfig.content, mergedConfig, fullConfig)

// Files get merged in from the default content (that template-oss provides) as well
// as any content paths provided from the root or the workspace
const fileDirs = uniq([useDefault && defaultDir, rootDir, pkgDir].filter(Boolean))
const files = mergeFiles(useDefault && defaultFiles, rootFiles, pkgFiles)
const repoFiles = isRoot ? files.rootRepo : files.workspaceRepo
const moduleFiles = isRoot ? files.rootModule : files.workspaceModule

Object.assign(fullConfig, {
repoFiles,
moduleFiles,
applyRepo: !!repoFiles,
applyModule: !!moduleFiles,
__PARTIAL_DIRS__: fileDirs,
// gitignore, these use the full config so need to come at the very end
ignorePaths: [
...gitignore.sort([
...gitignore.allowRootDir([
// Allways allow module files in root or workspaces
...getAddedFiles(moduleFiles).map(s => template(s, fullConfig)),
...isRoot ? [
// in the root allow all repo files
...getAddedFiles(repoFiles).map(s => template(s, fullConfig)),
// and allow all workspace repo level files in the root
...pkgs
.filter(p => p.path !== rootPkg.path && p.config.workspaceRepo !== false)
.flatMap(() => getAddedFiles(files.workspaceRepo)),
] : [],
]),
...isRoot && pkgConfig.lockfile ? ['!/package-lock.json'] : [],
...(pkgConfig.allowPaths || []).map((p) => `!${p}`),
...(pkgConfig.distPaths || []).map((p) => `!/${p}`),
...(pkgConfig.ignorePaths || []),
]),
// these cant be sorted since they rely on order
// to allow a previously ignored directoy
...isRoot
? gitignore.allowDir(wsPkgs.map((p) => makePosix(relative(rootPkg.path, p.path))))
: [],
].filter(p => !pkgConfig.eslint ? !p.includes('eslint') : true),
})

return fullConfig
}

module.exports = getFullConfig
Expand Down
11 changes: 11 additions & 0 deletions lib/content/eslintrc-js.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,18 @@ module.exports = {
{{#each workspaceGlobs}}
'{{ . }}',
{{/each}}
{{#if typescript}}
'dist/',
{{/if}}
],
{{#if typescript}}
parser: '@typescript-eslint/parser',
settings: {
'import/resolver': {
typescript: {},
},
},
{{/if}}
extends: [
'@npmcli',
...localConfigs,
Expand Down
Loading