Skip to content

Commit

Permalink
feat: add typescript and esm support
Browse files Browse the repository at this point in the history
  • Loading branch information
lukekarrys committed Nov 15, 2023
1 parent 3fca74f commit 17ea62d
Show file tree
Hide file tree
Showing 19 changed files with 375 additions and 188 deletions.
1 change: 1 addition & 0 deletions .gitignore
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
@@ -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
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

0 comments on commit 17ea62d

Please sign in to comment.