jiek 2.0.2-alpha.13 → 2.0.2-alpha.14

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,91 @@
1
+ import fs from 'node:fs'
2
+ import { createRequire } from 'node:module'
3
+ import path from 'node:path'
4
+
5
+ import { filterPackagesFromDir } from '@pnpm/filter-workspace-packages'
6
+ import { program } from 'commander'
7
+ import { load } from 'js-yaml'
8
+
9
+ import { getRoot } from '#~/utils/getRoot.ts'
10
+ import { getWD } from '#~/utils/getWD.ts'
11
+
12
+ export let type = ''
13
+
14
+ try {
15
+ const require = createRequire(import.meta.url)
16
+ require.resolve('@pnpm/filter-workspace-packages')
17
+ type = 'pnpm'
18
+ } catch { /* empty */ }
19
+
20
+ export interface ProjectsGraph {
21
+ wd: string
22
+ root: string
23
+ value?: Record<string, {
24
+ name?: string
25
+ type?: string
26
+ exports?: string | string[] | Record<string, unknown>
27
+ }>
28
+ }
29
+
30
+ export function filterPackagesGraph(filters: string[]): Promise<ProjectsGraph[]> {
31
+ return Promise.all(filters.map(async filter => getSelectedProjectsGraph(filter)))
32
+ }
33
+
34
+ export async function getSelectedProjectsGraph(
35
+ filter = program.getOptionValue('filter')
36
+ ): Promise<ProjectsGraph> {
37
+ let root = getRoot()
38
+ const { wd, notWorkspace } = getWD()
39
+ if (notWorkspace) {
40
+ return {
41
+ wd,
42
+ root,
43
+ value: {
44
+ [wd]: JSON.parse(fs.readFileSync(path.resolve(wd, 'package.json'), 'utf-8'))
45
+ }
46
+ }
47
+ }
48
+ if (type === 'pnpm') {
49
+ const pnpmWorkspaceFilePath = path.resolve(wd, 'pnpm-workspace.yaml')
50
+ const pnpmWorkspaceFileContent = fs.readFileSync(pnpmWorkspaceFilePath, 'utf-8')
51
+ const pnpmWorkspace = load(pnpmWorkspaceFileContent) as {
52
+ packages: string[]
53
+ }
54
+ if (root === wd && !filter) {
55
+ throw new Error('root path is workspace root, please provide a filter')
56
+ // TODO inquirer prompt support user select packages
57
+ }
58
+ if (root === undefined) {
59
+ root = process.cwd()
60
+ }
61
+ if (root !== wd && !filter) {
62
+ const packageJSONIsExist = fs.existsSync(path.resolve(root, 'package.json'))
63
+ if (!packageJSONIsExist) {
64
+ throw new Error('root path is not workspace root, please provide a filter')
65
+ }
66
+ const packageJSON = JSON.parse(fs.readFileSync(path.resolve(root, 'package.json'), 'utf-8'))
67
+ if (!packageJSON.name) {
68
+ throw new Error('root path is not workspace root, please provide a filter')
69
+ }
70
+ filter = packageJSON.name
71
+ }
72
+ const { selectedProjectsGraph } = await filterPackagesFromDir(wd, [{
73
+ filter: filter ?? '',
74
+ followProdDepsOnly: true
75
+ }], {
76
+ prefix: root,
77
+ workspaceDir: wd,
78
+ patterns: pnpmWorkspace.packages
79
+ })
80
+ return {
81
+ wd,
82
+ root,
83
+ value: Object.entries(selectedProjectsGraph)
84
+ .reduce((acc, [key, value]) => {
85
+ acc[key] = value.package.manifest
86
+ return acc
87
+ }, {} as NonNullable<ProjectsGraph['value']>)
88
+ }
89
+ }
90
+ throw new Error(`not supported package manager ${type}`)
91
+ }
@@ -0,0 +1,140 @@
1
+ import { isAbsolute, relative, resolve } from 'node:path'
2
+
3
+ import {
4
+ DEFAULT_SKIP_VALUES,
5
+ entrypoints2Exports,
6
+ type Entrypoints2ExportsOptions,
7
+ filterLeafs,
8
+ type RecursiveRecord,
9
+ resolveEntrypoints
10
+ } from '@jiek/pkger/entrypoints'
11
+ import type { Config } from 'jiek'
12
+ import { isMatch } from 'micromatch'
13
+
14
+ const intersection = <T>(a: Set<T>, b: Set<T>) => new Set([...a].filter(i => b.has(i)))
15
+
16
+ const {
17
+ JIEK_OUT_DIR
18
+ } = process.env
19
+
20
+ const OUTDIR = JIEK_OUT_DIR ?? 'dist'
21
+
22
+ export function getOutDirs({
23
+ cwd = process.cwd(),
24
+ defaultOutdir = OUTDIR,
25
+ config,
26
+ pkgName
27
+ }: {
28
+ cwd?: string
29
+ defaultOutdir?: string
30
+ config?: Config
31
+ pkgName?: string
32
+ }) {
33
+ const { build = {} } = config ?? {}
34
+ const outdir = build?.output?.dir
35
+ function resolveOutdir(type: 'js' | 'dts') {
36
+ const dir = (typeof outdir === 'object'
37
+ ? outdir[type] ?? outdir[
38
+ ({
39
+ js: 'dts',
40
+ dts: 'js'
41
+ } as const)[type]
42
+ ]
43
+ : outdir) ?? defaultOutdir
44
+ return (
45
+ isAbsolute(dir)
46
+ ? dir
47
+ : `./${relative(cwd, resolve(cwd, dir))}`
48
+ ).replace('{{PKG_NAME}}', pkgName!)
49
+ }
50
+ return {
51
+ js: resolveOutdir('js'),
52
+ dts: resolveOutdir('dts')
53
+ }
54
+ }
55
+
56
+ export function getExports({
57
+ entrypoints,
58
+ pkgName,
59
+ pkgIsModule,
60
+ entries,
61
+ config,
62
+ dir,
63
+ defaultOutdir = OUTDIR,
64
+ // FIXME dts support
65
+ outdir = getOutDirs({ pkgName, defaultOutdir, config, cwd: dir }).js,
66
+ noFilter,
67
+ isPublish
68
+ }: {
69
+ entrypoints: string | string[] | Record<string, unknown>
70
+ pkgName: string
71
+ pkgIsModule: boolean
72
+ entries?: string[]
73
+ config?: Config
74
+ dir?: string
75
+ outdir?: string
76
+ defaultOutdir?: string
77
+ noFilter?: boolean
78
+ isPublish?: boolean
79
+ }) {
80
+ const {
81
+ build = {},
82
+ publish: {
83
+ withSuffix = false,
84
+ withSource = true
85
+ } = {}
86
+ } = config ?? {}
87
+ const {
88
+ crossModuleConvertor = true
89
+ } = build
90
+ const [, resolvedEntrypoints] = resolveEntrypoints(entrypoints)
91
+ if (entries) {
92
+ Object
93
+ .entries(resolvedEntrypoints)
94
+ .forEach(([key]) => {
95
+ if (!entries.some(e => isMatch(key, e, { matchBase: true }))) {
96
+ delete resolvedEntrypoints[key]
97
+ }
98
+ })
99
+ }
100
+ const filteredResolvedEntrypoints = noFilter ? resolvedEntrypoints : filterLeafs(
101
+ resolvedEntrypoints as RecursiveRecord<string>,
102
+ {
103
+ skipValue: [
104
+ // ignore values that filename starts with `.jk-noentry`
105
+ /(^|\/)\.jk-noentry/,
106
+ ...DEFAULT_SKIP_VALUES
107
+ ]
108
+ }
109
+ )
110
+ const crossModuleWithConditional: Entrypoints2ExportsOptions['withConditional'] = crossModuleConvertor
111
+ ? {
112
+ import: opts =>
113
+ !pkgIsModule && intersection(
114
+ new Set(opts.conditionals),
115
+ new Set(['import', 'module'])
116
+ ).size === 0
117
+ ? opts.dist.replace(/\.js$/, '.mjs')
118
+ : false,
119
+ require: opts =>
120
+ pkgIsModule && intersection(
121
+ new Set(opts.conditionals),
122
+ new Set(['require', 'node'])
123
+ ).size === 0
124
+ ? opts.dist.replace(/\.js$/, '.cjs')
125
+ : false
126
+ }
127
+ : {}
128
+ return [
129
+ filteredResolvedEntrypoints,
130
+ entrypoints2Exports(filteredResolvedEntrypoints, {
131
+ outdir,
132
+ withSuffix: isPublish ? withSuffix : undefined,
133
+ withSource: isPublish ? withSource : undefined,
134
+ withConditional: {
135
+ ...crossModuleWithConditional
136
+ }
137
+ }),
138
+ outdir
139
+ ] as const
140
+ }
@@ -0,0 +1,16 @@
1
+ import path from 'node:path'
2
+
3
+ import { program } from 'commander'
4
+
5
+ let root: string
6
+ export function getRoot() {
7
+ if (root) return root
8
+
9
+ const rootOption = program.getOptionValue('root')
10
+ root = rootOption
11
+ ? path.isAbsolute(rootOption)
12
+ ? rootOption
13
+ : path.resolve(process.cwd(), rootOption)
14
+ : undefined
15
+ return root
16
+ }
@@ -0,0 +1,31 @@
1
+ import { getWorkspaceDir, isWorkspaceDir } from '@jiek/utils/getWorkspaceDir'
2
+
3
+ import { type } from './filterSupport'
4
+ import { getRoot } from './getRoot'
5
+
6
+ let wd: string
7
+ let notWorkspace = false
8
+
9
+ export function getWD() {
10
+ if (wd) return { wd, notWorkspace }
11
+
12
+ const root = getRoot()
13
+ if (root !== undefined) {
14
+ const isWorkspace = isWorkspaceDir(root, type)
15
+ notWorkspace = !isWorkspace
16
+ wd = root
17
+ return { wd, notWorkspace }
18
+ }
19
+ try {
20
+ wd = getWorkspaceDir(type)
21
+ } catch (e) {
22
+ // @ts-ignore
23
+ if ('message' in e && e.message === 'workspace root not found') {
24
+ wd = root
25
+ notWorkspace = true
26
+ } else {
27
+ throw e
28
+ }
29
+ }
30
+ return { wd, notWorkspace }
31
+ }
@@ -0,0 +1,111 @@
1
+ import fs from 'node:fs'
2
+ import { createRequire } from 'node:module'
3
+ import path from 'node:path'
4
+
5
+ import { program } from 'commander'
6
+ import type { Config } from 'jiek'
7
+ import { load } from 'js-yaml'
8
+
9
+ import { getWD } from './getWD'
10
+ import { tsRegisterName } from './tsRegister'
11
+
12
+ const require = createRequire(import.meta.url)
13
+
14
+ let configName = 'jiek.config'
15
+
16
+ function getConfigPath(root: string, dir?: string) {
17
+ const isSupportTsLoader = !!tsRegisterName
18
+ function configWithExtIsExist(ext: string) {
19
+ const filenames = [
20
+ path.resolve(process.cwd(), `${configName}.${ext}`),
21
+ path.resolve(process.cwd(), `.${configName}.${ext}`),
22
+ path.resolve(root, `${configName}.${ext}`),
23
+ path.resolve(root, `.${configName}.${ext}`)
24
+ ]
25
+ if (dir) {
26
+ filenames.unshift(...[
27
+ path.resolve(dir, `${configName}.${ext}`),
28
+ path.resolve(dir, `.${configName}.${ext}`)
29
+ ])
30
+ }
31
+ for (const filename of filenames) {
32
+ if (
33
+ fs.existsSync(filename)
34
+ && fs.lstatSync(filename)
35
+ .isFile()
36
+ ) {
37
+ return filename
38
+ }
39
+ }
40
+ return
41
+ }
42
+ configName = configWithExtIsExist('js') ?? configName
43
+ configName = configWithExtIsExist('json') ?? configName
44
+ configName = configWithExtIsExist('yaml') ?? configName
45
+ if (isSupportTsLoader) {
46
+ configName = configWithExtIsExist('ts') ?? configName
47
+ }
48
+ return path.resolve(root, configName)
49
+ }
50
+
51
+ interface LoadConfigOptions {
52
+ dir?: string
53
+ root?: string
54
+ }
55
+
56
+ export function loadConfig(options?: LoadConfigOptions): Config
57
+ export function loadConfig(dir?: string): Config
58
+ export function loadConfig(dirOrOptions?: string | LoadConfigOptions): Config {
59
+ let dir: string | undefined
60
+ let root: string
61
+ if (typeof dirOrOptions === 'object') {
62
+ dir = dirOrOptions.dir
63
+ root = dirOrOptions.root ?? getWD().wd
64
+ } else {
65
+ dir = dirOrOptions
66
+ root = getWD().wd
67
+ }
68
+
69
+ let configPath = program.getOptionValue('configPath')
70
+
71
+ if (!configPath) {
72
+ configPath = getConfigPath(root, dir)
73
+ } else {
74
+ if (!fs.existsSync(configPath)) {
75
+ throw new Error(`config file not found: ${configPath}`)
76
+ }
77
+ if (!path.isAbsolute(configPath)) {
78
+ configPath = path.resolve(root, configPath)
79
+ }
80
+ }
81
+ const ext = path.extname(configPath)
82
+
83
+ let module: any
84
+ switch (ext) {
85
+ case '.js':
86
+ module = require(configPath)
87
+ break
88
+ case '.json':
89
+ return require(configPath)
90
+ case '.yaml':
91
+ return load(fs.readFileSync(configPath, 'utf-8')) as Config
92
+ case '.ts':
93
+ if (tsRegisterName) {
94
+ require(tsRegisterName)
95
+ module = require(configPath)
96
+ break
97
+ }
98
+ throw new Error(
99
+ 'ts config file is not supported without ts register, '
100
+ + 'please install esbuild-register or set JIEK_TS_REGISTER env for custom ts register'
101
+ )
102
+ case '.config':
103
+ module = {}
104
+ break
105
+ default:
106
+ throw new Error(`unsupported config file type: ${ext}`)
107
+ }
108
+ if (!module) throw new Error('config file is empty')
109
+
110
+ return module.default ?? module
111
+ }
@@ -0,0 +1,13 @@
1
+ import fs from 'node:fs'
2
+ import { resolve } from 'node:path'
3
+
4
+ export const recusiveListFiles = (dir: string): string[] =>
5
+ fs.readdirSync(dir).reduce((acc, file) => {
6
+ const filePath = resolve(dir, file)
7
+ if (fs.statSync(filePath).isDirectory()) {
8
+ if (filePath.endsWith('/node_modules')) return acc
9
+
10
+ return [...acc, ...recusiveListFiles(filePath)]
11
+ }
12
+ return [...acc, filePath]
13
+ }, [] as string[])
@@ -0,0 +1,94 @@
1
+ import fs from 'node:fs'
2
+ import { dirname, resolve } from 'node:path'
3
+
4
+ import { parse } from 'jsonc-parser'
5
+ import { isMatch } from 'micromatch'
6
+
7
+ type TSConfig = {
8
+ extends?: string | string[]
9
+ compilerOptions?: Record<string, unknown>
10
+ references?: { path: string }[]
11
+ files?: string[]
12
+ include?: string[]
13
+ exclude?: string[]
14
+ }
15
+
16
+ const getTSConfig = (p: string): TSConfig =>
17
+ !fs.existsSync(p) || !fs.statSync(p).isFile()
18
+ ? {}
19
+ : parse(fs.readFileSync(p, 'utf-8'), [], { allowTrailingComma: true, allowEmptyContent: true })
20
+
21
+ const getExtendTSConfig = (tsconfigPath: string): TSConfig => {
22
+ tsconfigPath = resolve(tsconfigPath)
23
+ const tsconfigPathDirname = dirname(tsconfigPath)
24
+ const { extends: exts, ...tsconfig } = getTSConfig(tsconfigPath)
25
+ const resolvePaths = (paths: string[] | undefined) => paths?.map(p => resolve(tsconfigPathDirname, p)) ?? []
26
+
27
+ const extendsPaths = resolvePaths(
28
+ exts ? Array.isArray(exts) ? exts : [exts] : []
29
+ )
30
+ if (extendsPaths.length === 0) return tsconfig
31
+ return extendsPaths
32
+ .map(getExtendTSConfig)
33
+ .concat(tsconfig)
34
+ // https://www.typescriptlang.org/tsconfig/#files:~:text=Currently%2C%20the%20only%20top%2Dlevel%20property%20that%20is%20excluded%20from%20inheritance%20is%20references.
35
+ // Currently, the only top-level property that is excluded from inheritance is references.
36
+ .reduce((acc, { compilerOptions = {}, references: _, ...curr }) => ({
37
+ ...acc,
38
+ ...curr,
39
+ compilerOptions: {
40
+ ...acc.compilerOptions,
41
+ ...compilerOptions
42
+ }
43
+ }), {})
44
+ }
45
+
46
+ export const getCompilerOptionsByFilePath = (
47
+ tsconfigPath: string,
48
+ filePath: string
49
+ ): Record<string, unknown> | undefined => {
50
+ tsconfigPath = resolve(tsconfigPath)
51
+ filePath = resolve(filePath)
52
+ const tsconfigPathDirname = dirname(tsconfigPath)
53
+ // https://www.typescriptlang.org/tsconfig/#files:~:text=It%E2%80%99s%20worth%20noting%20that%20files%2C%20include%2C%20and%20exclude%20from%20the%20inheriting%20config%20file%20overwrite%20those%20from%20the%20base%20config%20file%2C%20and%20that%20circularity%20between%20configuration%20files%20is%20not%20allowed.
54
+ // It’s worth noting that files, include, and exclude from the inheriting config file overwrite
55
+ // those from the base config file, and that circularity between configuration files is not allowed.
56
+ const tsconfig = getExtendTSConfig(tsconfigPath)
57
+
58
+ const resolvePaths = (paths: string[] | undefined) => paths?.map(p => resolve(tsconfigPathDirname, p)) ?? []
59
+
60
+ const [
61
+ references,
62
+ files,
63
+ include,
64
+ exclude
65
+ ] = [
66
+ tsconfig.references?.map(({ path }) => path),
67
+ tsconfig.files,
68
+ tsconfig.include,
69
+ tsconfig.exclude
70
+ ].map(resolvePaths)
71
+ if (exclude.length > 0 && exclude.some(i => isMatch(filePath, i))) return
72
+
73
+ // when files or include is not empty, the tsconfig should be ignored
74
+ if (tsconfig.files?.length === 0 && tsconfig.include?.length === 0) return
75
+ let isInclude = false
76
+ isInclude ||= files.length > 0 && files.includes(filePath)
77
+ isInclude ||= include.length > 0 && include.some(i => isMatch(filePath, i))
78
+ if (isInclude) {
79
+ return tsconfig.compilerOptions ?? {}
80
+ } else {
81
+ // when files or include is not empty, but the file is not matched, the tsconfig should be ignored
82
+ if (
83
+ (tsconfig.files && tsconfig.files.length > 0)
84
+ || (tsconfig.include && tsconfig.include.length > 0)
85
+ ) return
86
+ }
87
+
88
+ references.reverse()
89
+ for (const ref of references) {
90
+ const compilerOptions = getCompilerOptionsByFilePath(ref, filePath)
91
+ if (compilerOptions) return compilerOptions
92
+ }
93
+ return tsconfig.compilerOptions
94
+ }
@@ -0,0 +1,26 @@
1
+ import { createRequire } from 'node:module'
2
+
3
+ const require = createRequire(import.meta.url)
4
+
5
+ function packageIsExist(name: string) {
6
+ try {
7
+ require.resolve(name)
8
+ return true
9
+ } catch (e) {
10
+ return false
11
+ }
12
+ }
13
+
14
+ export let tsRegisterName: string | undefined
15
+ const registers = [
16
+ process.env.JIEK_TS_REGISTER,
17
+ 'esbuild-register',
18
+ '@swc-node/register',
19
+ 'ts-node/register'
20
+ ].filter(Boolean) as string[]
21
+ for (const register of registers) {
22
+ if (packageIsExist(register)) {
23
+ tsRegisterName = register
24
+ break
25
+ }
26
+ }