zile 0.0.0 → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +358 -0
- package/dist/Cli.d.ts +15 -0
- package/dist/Cli.d.ts.map +1 -0
- package/dist/Cli.js +30 -0
- package/dist/Cli.js.map +1 -0
- package/dist/Package.d.ts +156 -0
- package/dist/Package.d.ts.map +1 -0
- package/dist/Package.js +399 -0
- package/dist/Package.js.map +1 -0
- package/dist/Packages.d.ts +27 -0
- package/dist/Packages.d.ts.map +1 -0
- package/dist/Packages.js +48 -0
- package/dist/Packages.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/cli/commands.d.ts +17 -0
- package/dist/internal/cli/commands.d.ts.map +1 -0
- package/dist/internal/cli/commands.js +16 -0
- package/dist/internal/cli/commands.js.map +1 -0
- package/dist/internal/package.json +60 -0
- package/package.json +58 -2
- package/src/Cli.test.ts +81 -0
- package/src/Cli.ts +41 -0
- package/src/Package.test.ts +102 -0
- package/src/Package.ts +575 -0
- package/src/Packages.test.ts +222 -0
- package/src/Packages.ts +65 -0
- package/src/__snapshots__/Cli.test.ts.snap +471 -0
- package/src/__snapshots__/Package.test.ts.snap +972 -0
- package/src/__snapshots__/Packages.test.ts.snap +195 -0
- package/src/index.ts +3 -0
- package/src/internal/cli/commands.ts +37 -0
package/src/Package.ts
ADDED
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
import * as cp from 'node:child_process'
|
|
2
|
+
import * as fsSync from 'node:fs'
|
|
3
|
+
import * as fs from 'node:fs/promises'
|
|
4
|
+
import * as path from 'node:path'
|
|
5
|
+
import * as Tsconfig from 'tsconfck'
|
|
6
|
+
import type { PackageJson, TsConfigJson } from 'type-fest'
|
|
7
|
+
|
|
8
|
+
export type { PackageJson, TsConfigJson }
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Builds a package.
|
|
12
|
+
*
|
|
13
|
+
* @param options - Options for building a package
|
|
14
|
+
* @returns Build artifacts.
|
|
15
|
+
*/
|
|
16
|
+
export async function build(options: build.Options): Promise<build.ReturnType> {
|
|
17
|
+
const { cwd = process.cwd(), link = false, project = './tsconfig.json', tsgo } = options
|
|
18
|
+
|
|
19
|
+
let [pkgJson, tsConfig] = await Promise.all([
|
|
20
|
+
readPackageJson({ cwd }),
|
|
21
|
+
readTsconfigJson({ cwd, project }),
|
|
22
|
+
])
|
|
23
|
+
const outDir = tsConfig.compilerOptions?.outDir ?? path.resolve(cwd, 'dist')
|
|
24
|
+
|
|
25
|
+
await checkInput({ cwd, outDir })
|
|
26
|
+
|
|
27
|
+
const entries = getEntries({ cwd, pkgJson })
|
|
28
|
+
|
|
29
|
+
if (!link) {
|
|
30
|
+
const result = await transpile({ cwd, entries, tsgo })
|
|
31
|
+
tsConfig = result.tsConfig
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (link) {
|
|
35
|
+
await fs.rm(outDir, { recursive: true }).catch(() => {})
|
|
36
|
+
await fs.mkdir(outDir, { recursive: true })
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const sourceDir = getSourceDir({ cwd, entries })
|
|
40
|
+
const packageJson = await decoratePackageJson(pkgJson, { cwd, link, outDir, sourceDir })
|
|
41
|
+
|
|
42
|
+
await writePackageJson(cwd, packageJson)
|
|
43
|
+
|
|
44
|
+
return { packageJson, tsConfig }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export declare namespace build {
|
|
48
|
+
type Options = {
|
|
49
|
+
/** Working directory to start searching from. @default process.cwd() */
|
|
50
|
+
cwd?: string | undefined
|
|
51
|
+
/** Whether to link output files to source files for development. @default false */
|
|
52
|
+
link?: boolean | undefined
|
|
53
|
+
/** Path to tsconfig.json file, relative to the working directory. @default './tsconfig.json' */
|
|
54
|
+
project?: string | undefined
|
|
55
|
+
/** Whether to use tsgo for transpilation. @default false */
|
|
56
|
+
tsgo?: boolean | undefined
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
type ReturnType = {
|
|
60
|
+
/** Transformed package.json file. */
|
|
61
|
+
packageJson: PackageJson
|
|
62
|
+
/** tsconfig.json used for transpilation. */
|
|
63
|
+
tsConfig: TsConfigJson
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Checks the inputs of the package.
|
|
69
|
+
*
|
|
70
|
+
* @param options - Options for checking the input.
|
|
71
|
+
* @returns Input check results.
|
|
72
|
+
*/
|
|
73
|
+
export async function checkInput(options: checkInput.Options): Promise<checkInput.ReturnType> {
|
|
74
|
+
return await checkPackageJson(options)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export declare namespace checkInput {
|
|
78
|
+
type Options = {
|
|
79
|
+
/** Working directory to check. @default process.cwd() */
|
|
80
|
+
cwd?: string | undefined
|
|
81
|
+
/** Output directory. @default path.resolve(cwd, 'dist') */
|
|
82
|
+
outDir?: string | undefined
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
type ReturnType = undefined
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Determines if the package.json file is valid for transpiling.
|
|
90
|
+
*
|
|
91
|
+
* @param options - Options for checking the package.json file.
|
|
92
|
+
* @returns Whether the package.json file is valid for transpiling.
|
|
93
|
+
*/
|
|
94
|
+
export async function checkPackageJson(
|
|
95
|
+
options: checkPackageJson.Options,
|
|
96
|
+
): Promise<checkPackageJson.ReturnType> {
|
|
97
|
+
const { cwd = process.cwd(), outDir = path.resolve(cwd, 'dist') } = options
|
|
98
|
+
const pkgJson = await readPackageJson({ cwd })
|
|
99
|
+
|
|
100
|
+
function exists(value: string) {
|
|
101
|
+
if (value.includes(path.relative(cwd, outDir))) return true
|
|
102
|
+
return fsSync.existsSync(path.resolve(cwd, value))
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (!pkgJson.exports && !pkgJson.main && !pkgJson.bin)
|
|
106
|
+
throw new Error('package.json must have an `exports`, `main`, or `bin` field')
|
|
107
|
+
|
|
108
|
+
if (pkgJson.bin)
|
|
109
|
+
if (typeof pkgJson.bin === 'string') {
|
|
110
|
+
if (!exists(pkgJson.bin))
|
|
111
|
+
throw new Error(`\`${pkgJson.bin}\` does not exist on \`package.json#bin\``)
|
|
112
|
+
} else {
|
|
113
|
+
for (const [key, value] of Object.entries(pkgJson.bin)) {
|
|
114
|
+
if (!value) throw new Error(`\`bin.${key}\` value must be a string`)
|
|
115
|
+
if (typeof value === 'string' && !exists(value))
|
|
116
|
+
throw new Error(`\`${value}\` does not exist on \`package.json#bin.${key}\``)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (pkgJson.main)
|
|
121
|
+
if (!exists(pkgJson.main))
|
|
122
|
+
throw new Error(`\`${pkgJson.main}\` does not exist on \`package.json#main\``)
|
|
123
|
+
|
|
124
|
+
if (pkgJson.exports) {
|
|
125
|
+
for (const [key, entry] of Object.entries(pkgJson.exports)) {
|
|
126
|
+
if (typeof entry === 'string' && !exists(entry))
|
|
127
|
+
throw new Error(`\`${entry}\` does not exist on \`package.json#exports["${key}"]\``)
|
|
128
|
+
if (
|
|
129
|
+
typeof entry === 'object' &&
|
|
130
|
+
entry &&
|
|
131
|
+
'src' in entry &&
|
|
132
|
+
typeof entry.src === 'string' &&
|
|
133
|
+
!exists(entry.src)
|
|
134
|
+
)
|
|
135
|
+
throw new Error(`\`${entry.src}\` does not exist on \`package.json#exports["${key}"].src\``)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return undefined
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export declare namespace checkPackageJson {
|
|
143
|
+
type Options = {
|
|
144
|
+
/** Working directory to check. @default process.cwd() */
|
|
145
|
+
cwd?: string | undefined
|
|
146
|
+
/** Output directory. @default path.resolve(cwd, 'dist') */
|
|
147
|
+
outDir?: string | undefined
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
type ReturnType = undefined
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Decorates the package.json file to include publish-specific fields.
|
|
155
|
+
*
|
|
156
|
+
* @param pkgJson - Package.json file to transform.
|
|
157
|
+
* @param options - Options.
|
|
158
|
+
* @returns Transformed package.json file as an object.
|
|
159
|
+
*/
|
|
160
|
+
export async function decoratePackageJson(
|
|
161
|
+
pkgJson: PackageJson,
|
|
162
|
+
options: decoratePackageJson.Options,
|
|
163
|
+
) {
|
|
164
|
+
const { cwd, link, outDir, sourceDir } = options
|
|
165
|
+
|
|
166
|
+
const relativeOutDir = `./${path.relative(cwd, outDir)}`
|
|
167
|
+
const relativeSourceDir = `./${path.relative(cwd, sourceDir)}`
|
|
168
|
+
|
|
169
|
+
const outFile = (name: string, ext: string = '') =>
|
|
170
|
+
'./' +
|
|
171
|
+
path.join(
|
|
172
|
+
relativeOutDir,
|
|
173
|
+
name.replace(relativeSourceDir, '').replace(path.extname(name), '') + ext,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
let bin = pkgJson.bin
|
|
177
|
+
if (bin) {
|
|
178
|
+
if (typeof bin === 'string') {
|
|
179
|
+
if (!bin.startsWith(relativeOutDir))
|
|
180
|
+
bin = {
|
|
181
|
+
// biome-ignore lint/style/noNonNullAssertion: _
|
|
182
|
+
[pkgJson.name!]: outFile(bin, '.js'),
|
|
183
|
+
// biome-ignore lint/style/useTemplate: _
|
|
184
|
+
// biome-ignore lint/style/noNonNullAssertion: _
|
|
185
|
+
[pkgJson.name! + '.src']: bin,
|
|
186
|
+
}
|
|
187
|
+
} else {
|
|
188
|
+
bin = Object.fromEntries(
|
|
189
|
+
Object.entries(bin).flatMap((entry) => {
|
|
190
|
+
const [key, value] = entry
|
|
191
|
+
if (!value) throw new Error(`\`bin.${key}\` field must have a value`)
|
|
192
|
+
return [
|
|
193
|
+
[key.replace('.src', ''), outFile(value, '.js')],
|
|
194
|
+
[key, value],
|
|
195
|
+
]
|
|
196
|
+
}),
|
|
197
|
+
)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
let exps = pkgJson.exports
|
|
202
|
+
|
|
203
|
+
// Support single entrypoint via `main` field
|
|
204
|
+
if (pkgJson.main) {
|
|
205
|
+
// Transform single `package.json#main` field. They
|
|
206
|
+
// must point to the source file. Otherwise, an error is thrown.
|
|
207
|
+
//
|
|
208
|
+
// main: "./src/index.ts"
|
|
209
|
+
// ↓ ↓ ↓
|
|
210
|
+
// main: "./src/index.ts"
|
|
211
|
+
// exports: {
|
|
212
|
+
// ".": {
|
|
213
|
+
// "src": "./src/index.ts",
|
|
214
|
+
// "types": "./dist/index.d.ts",
|
|
215
|
+
// "default": "./dist/index.js",
|
|
216
|
+
// },
|
|
217
|
+
// }
|
|
218
|
+
exps = {
|
|
219
|
+
'.': pkgJson.main,
|
|
220
|
+
...(typeof exps === 'object' ? exps : {}),
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
type Exports = {
|
|
225
|
+
[key: string]: {
|
|
226
|
+
src: string
|
|
227
|
+
types: string
|
|
228
|
+
default: string
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
const exports = Object.fromEntries(
|
|
232
|
+
exps
|
|
233
|
+
? Object.entries(exps).map(([key, value]) => {
|
|
234
|
+
function linkExports(entry: string) {
|
|
235
|
+
try {
|
|
236
|
+
const destJsAbsolute = path.resolve(cwd, outFile(entry, '.js'))
|
|
237
|
+
const destDtsAbsolute = path.resolve(cwd, outFile(entry, '.d.ts'))
|
|
238
|
+
const dir = path.dirname(destJsAbsolute)
|
|
239
|
+
|
|
240
|
+
if (!fsSync.existsSync(dir)) fsSync.mkdirSync(dir, { recursive: true })
|
|
241
|
+
|
|
242
|
+
const srcAbsolute = path.resolve(cwd, entry)
|
|
243
|
+
const srcRelativeJs = path.relative(path.dirname(destJsAbsolute), srcAbsolute)
|
|
244
|
+
const srcRelativeDts = path.relative(path.dirname(destDtsAbsolute), srcAbsolute)
|
|
245
|
+
|
|
246
|
+
fsSync.symlinkSync(srcRelativeJs, destJsAbsolute, 'file')
|
|
247
|
+
fsSync.symlinkSync(srcRelativeDts, destDtsAbsolute, 'file')
|
|
248
|
+
} catch {}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Transform single `package.json#exports` entrypoints. They
|
|
252
|
+
// must point to the source file. Otherwise, an error is thrown.
|
|
253
|
+
//
|
|
254
|
+
// "./utils": "./src/utils.ts"
|
|
255
|
+
// ↓ ↓ ↓
|
|
256
|
+
// "./utils": {
|
|
257
|
+
// "src": "./src/utils.ts",
|
|
258
|
+
// "types": "./dist/utils.js",
|
|
259
|
+
// "default": "./dist/utils.d.ts"
|
|
260
|
+
// }
|
|
261
|
+
if (typeof value === 'string') {
|
|
262
|
+
if (value.startsWith(relativeOutDir)) return [key, value]
|
|
263
|
+
if (!/\.(m|c)?[jt]sx?$/.test(value)) return [key, value]
|
|
264
|
+
if (link) linkExports(value)
|
|
265
|
+
return [
|
|
266
|
+
key,
|
|
267
|
+
{
|
|
268
|
+
src: value,
|
|
269
|
+
types: outFile(value, '.d.ts'),
|
|
270
|
+
default: outFile(value, '.js'),
|
|
271
|
+
},
|
|
272
|
+
]
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Transform object-like `package.json#exports` entrypoints. They
|
|
276
|
+
// must include a `src` field pointing to the source file, otherwise
|
|
277
|
+
// an error is thrown.
|
|
278
|
+
//
|
|
279
|
+
// "./utils": "./src/utils.ts"
|
|
280
|
+
// ↓ ↓ ↓
|
|
281
|
+
// "./utils": {
|
|
282
|
+
// "src": "./src/utils.ts",
|
|
283
|
+
// "types": "./dist/utils.js",
|
|
284
|
+
// "default": "./dist/utils.d.ts"
|
|
285
|
+
// }
|
|
286
|
+
if (
|
|
287
|
+
typeof value === 'object' &&
|
|
288
|
+
value &&
|
|
289
|
+
'src' in value &&
|
|
290
|
+
typeof value.src === 'string'
|
|
291
|
+
) {
|
|
292
|
+
if (value.src.startsWith(relativeOutDir)) return [key, value]
|
|
293
|
+
if (!/\.(m|c)?[jt]sx?$/.test(value.src)) return [key, value]
|
|
294
|
+
if (link) linkExports(value.src)
|
|
295
|
+
return [
|
|
296
|
+
key,
|
|
297
|
+
{
|
|
298
|
+
...value,
|
|
299
|
+
types: outFile(value.src, '.d.ts'),
|
|
300
|
+
default: outFile(value.src, '.js'),
|
|
301
|
+
},
|
|
302
|
+
]
|
|
303
|
+
}
|
|
304
|
+
throw new Error('`exports` field in package.json must be an object with a `src` field')
|
|
305
|
+
})
|
|
306
|
+
: [],
|
|
307
|
+
) as Exports
|
|
308
|
+
|
|
309
|
+
const root = exports['.']
|
|
310
|
+
|
|
311
|
+
return {
|
|
312
|
+
...pkgJson,
|
|
313
|
+
type: pkgJson.type ?? 'module',
|
|
314
|
+
sideEffects: pkgJson.sideEffects ?? false,
|
|
315
|
+
...(bin ? { bin } : {}),
|
|
316
|
+
...(root
|
|
317
|
+
? {
|
|
318
|
+
main: root.default,
|
|
319
|
+
module: root.default,
|
|
320
|
+
types: root.types,
|
|
321
|
+
}
|
|
322
|
+
: {}),
|
|
323
|
+
exports,
|
|
324
|
+
} as PackageJson
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export declare namespace decoratePackageJson {
|
|
328
|
+
type Options = {
|
|
329
|
+
/** Working directory. */
|
|
330
|
+
cwd: string
|
|
331
|
+
/** Whether to link output files to source files for development. */
|
|
332
|
+
link: boolean
|
|
333
|
+
/** Output directory. */
|
|
334
|
+
outDir: string
|
|
335
|
+
/** Source directory. */
|
|
336
|
+
sourceDir: string
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Gets entry files from package.json exports field or main field.
|
|
342
|
+
*
|
|
343
|
+
* @param options - Options for getting entry files.
|
|
344
|
+
* @returns Array of absolute paths to entry files.
|
|
345
|
+
*/
|
|
346
|
+
export function getEntries(options: getEntries.Options): string[] {
|
|
347
|
+
const { cwd, pkgJson } = options
|
|
348
|
+
|
|
349
|
+
const entries: string[] = []
|
|
350
|
+
|
|
351
|
+
if (pkgJson.bin) {
|
|
352
|
+
if (typeof pkgJson.bin === 'string')
|
|
353
|
+
entries.push(path.resolve(cwd, pkgJson.bin))
|
|
354
|
+
else
|
|
355
|
+
entries.push(
|
|
356
|
+
...(Object.entries(pkgJson.bin)
|
|
357
|
+
.map(([key, value]) =>
|
|
358
|
+
// biome-ignore lint/style/noNonNullAssertion: _
|
|
359
|
+
key.endsWith('.src') ? path.resolve(cwd, value!) : undefined,
|
|
360
|
+
)
|
|
361
|
+
.filter(Boolean) as string[]),
|
|
362
|
+
)
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (pkgJson.exports)
|
|
366
|
+
entries.push(
|
|
367
|
+
...Object.values(pkgJson.exports)
|
|
368
|
+
.map((entry) => {
|
|
369
|
+
if (typeof entry === 'string') return entry
|
|
370
|
+
if (typeof entry === 'object' && entry && 'src' in entry && typeof entry.src === 'string')
|
|
371
|
+
return entry.src
|
|
372
|
+
throw new Error('`exports` field in package.json must have a `src` field')
|
|
373
|
+
})
|
|
374
|
+
.map((entry) => path.resolve(cwd, entry))
|
|
375
|
+
.filter((entry) => /\.(m|c)?[jt]sx?$/.test(entry)),
|
|
376
|
+
)
|
|
377
|
+
else if (pkgJson.main) entries.push(path.resolve(cwd, pkgJson.main))
|
|
378
|
+
|
|
379
|
+
return entries
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
export declare namespace getEntries {
|
|
383
|
+
type Options = {
|
|
384
|
+
/** Working directory. */
|
|
385
|
+
cwd: string
|
|
386
|
+
/** Package.json file. */
|
|
387
|
+
pkgJson: PackageJson
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Gets the source directory from the entry files.
|
|
393
|
+
*
|
|
394
|
+
* @param options - Options for getting the source directory.
|
|
395
|
+
* @returns Source directory.
|
|
396
|
+
*/
|
|
397
|
+
export function getSourceDir(options: getSourceDir.Options): string {
|
|
398
|
+
const { cwd = process.cwd(), entries } = options
|
|
399
|
+
|
|
400
|
+
if (entries.length === 0) return path.resolve(cwd, 'src')
|
|
401
|
+
|
|
402
|
+
// Get directories of all entries
|
|
403
|
+
const dirs = entries.map((entry) => path.dirname(entry))
|
|
404
|
+
|
|
405
|
+
// Split each directory into segments
|
|
406
|
+
const segments = dirs.map((dir) => dir.split(path.sep))
|
|
407
|
+
|
|
408
|
+
// Find common segments
|
|
409
|
+
const commonSegments: string[] = []
|
|
410
|
+
const minLength = Math.min(...segments.map((s) => s.length))
|
|
411
|
+
|
|
412
|
+
for (let i = 0; i < minLength; i++) {
|
|
413
|
+
const segment = segments[0][i]
|
|
414
|
+
if (segments.every((s) => s[i] === segment)) commonSegments.push(segment)
|
|
415
|
+
else break
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const commonPath = commonSegments.join(path.sep)
|
|
419
|
+
return path.resolve(cwd, path.relative(cwd, commonPath).split(path.sep)[0])
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
export declare namespace getSourceDir {
|
|
423
|
+
type Options = {
|
|
424
|
+
/** Working directory. */
|
|
425
|
+
cwd?: string | undefined
|
|
426
|
+
/** Entry files. */
|
|
427
|
+
entries: string[]
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Reads the package.json file from the given working directory.
|
|
433
|
+
*
|
|
434
|
+
* @param cwd - Working directory to read the package.json file from.
|
|
435
|
+
* @returns Parsed package.json file as an object.
|
|
436
|
+
*/
|
|
437
|
+
const packageJsonCache: Map<string, PackageJson> = new Map()
|
|
438
|
+
export async function readPackageJson(options: readPackageJson.Options) {
|
|
439
|
+
const { cwd } = options
|
|
440
|
+
|
|
441
|
+
if (packageJsonCache.has(cwd)) return packageJsonCache.get(cwd) as PackageJson
|
|
442
|
+
const packageJson = await fs.readFile(path.resolve(cwd, 'package.json'), 'utf-8').then(JSON.parse)
|
|
443
|
+
packageJsonCache.set(cwd, packageJson)
|
|
444
|
+
return packageJson
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
export declare namespace readPackageJson {
|
|
448
|
+
type Options = {
|
|
449
|
+
/** Working directory. */
|
|
450
|
+
cwd: string
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Reads the tsconfig.json file from the given working directory.
|
|
456
|
+
*
|
|
457
|
+
* @param cwd - Working directory to read the tsconfig.json file from.
|
|
458
|
+
* @param project - Path to tsconfig.json file, relative to the working directory. @default './tsconfig.json'
|
|
459
|
+
* @returns Parsed tsconfig.json file as an object.
|
|
460
|
+
*/
|
|
461
|
+
const tsconfigJsonCache: Map<string, TsConfigJson> = new Map()
|
|
462
|
+
export async function readTsconfigJson(options: readTsconfigJson.Options): Promise<TsConfigJson> {
|
|
463
|
+
const { cwd, project = './tsconfig.json' } = options
|
|
464
|
+
if (tsconfigJsonCache.has(cwd)) return tsconfigJsonCache.get(cwd) as TsConfigJson
|
|
465
|
+
const result = await Tsconfig.parse(path.resolve(cwd, project))
|
|
466
|
+
tsconfigJsonCache.set(cwd, result.tsconfig)
|
|
467
|
+
return result.tsconfig
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
export declare namespace readTsconfigJson {
|
|
471
|
+
type Options = {
|
|
472
|
+
/** Working directory. */
|
|
473
|
+
cwd: string
|
|
474
|
+
/** Path to tsconfig.json file, relative to the working directory. @default './tsconfig.json' */
|
|
475
|
+
project?: string | undefined
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Transpiles a package.
|
|
481
|
+
*
|
|
482
|
+
* @param options - Options for transpiling a package.
|
|
483
|
+
* @returns Transpilation artifacts.
|
|
484
|
+
*/
|
|
485
|
+
export async function transpile(options: transpile.Options): Promise<transpile.ReturnType> {
|
|
486
|
+
const { cwd = process.cwd(), entries, project = './tsconfig.json', tsgo } = options
|
|
487
|
+
|
|
488
|
+
const tsConfigJson = await readTsconfigJson({ cwd, project })
|
|
489
|
+
const tsconfigPath = path.resolve(cwd, project)
|
|
490
|
+
const { module: mod, moduleResolution: modRes } = tsConfigJson.compilerOptions ?? {}
|
|
491
|
+
|
|
492
|
+
// TODO: CLI `zile check --fix` command to add these and rewrite extensions in project (if needed).
|
|
493
|
+
// TODO: extract to Package.checkTsconfig()
|
|
494
|
+
const isNodeNext = (val?: string) => val === 'nodenext' || val === 'NodeNext'
|
|
495
|
+
const errors = []
|
|
496
|
+
if (!isNodeNext(mod))
|
|
497
|
+
errors.push(` - "module" must be "nodenext". Found: ${mod ? `"${mod}"` : 'undefined'}`)
|
|
498
|
+
if (!isNodeNext(modRes))
|
|
499
|
+
errors.push(
|
|
500
|
+
` - "moduleResolution" must be "nodenext". Found: ${modRes ? `"${modRes}"` : 'undefined'}`,
|
|
501
|
+
)
|
|
502
|
+
if (errors.length > 0)
|
|
503
|
+
throw new Error(`${tsconfigPath} has invalid configuration:\n${errors.join('\n')}`)
|
|
504
|
+
|
|
505
|
+
const compilerOptions = {
|
|
506
|
+
...tsConfigJson.compilerOptions,
|
|
507
|
+
composite: false,
|
|
508
|
+
declaration: true,
|
|
509
|
+
declarationDir: tsConfigJson.compilerOptions?.declarationDir,
|
|
510
|
+
declarationMap: true,
|
|
511
|
+
emitDeclarationOnly: false,
|
|
512
|
+
esModuleInterop: true,
|
|
513
|
+
noEmit: false,
|
|
514
|
+
outDir: tsConfigJson.compilerOptions?.outDir ?? path.resolve(cwd, 'dist'),
|
|
515
|
+
skipLibCheck: true,
|
|
516
|
+
sourceMap: true,
|
|
517
|
+
target: tsConfigJson.compilerOptions?.target ?? 'es2021',
|
|
518
|
+
} as const satisfies TsConfigJson['compilerOptions']
|
|
519
|
+
|
|
520
|
+
const tsConfig = {
|
|
521
|
+
compilerOptions,
|
|
522
|
+
exclude: tsConfigJson.exclude ?? [],
|
|
523
|
+
include: [...(tsConfigJson.include ?? []), ...entries] as string[],
|
|
524
|
+
} as const
|
|
525
|
+
|
|
526
|
+
const tmpProject = path.resolve(cwd, 'tsconfig.tmp.json')
|
|
527
|
+
await fs.writeFile(tmpProject, JSON.stringify(tsConfig, null, 2))
|
|
528
|
+
|
|
529
|
+
await fs.rm(compilerOptions.outDir, { recursive: true }).catch(() => {})
|
|
530
|
+
const tsc = path.resolve(import.meta.dirname, '..', 'node_modules', '.bin', tsgo ? 'tsgo' : 'tsc')
|
|
531
|
+
const child = cp.spawn(tsc, ['--project', tmpProject], {
|
|
532
|
+
cwd,
|
|
533
|
+
stdio: 'inherit',
|
|
534
|
+
})
|
|
535
|
+
|
|
536
|
+
const promise = Promise.withResolvers<null>()
|
|
537
|
+
|
|
538
|
+
child.on('close', (code) => {
|
|
539
|
+
if (code === 0) promise.resolve(null)
|
|
540
|
+
else promise.reject(new Error(`tsgo exited with code ${code}`))
|
|
541
|
+
})
|
|
542
|
+
child.on('error', promise.reject)
|
|
543
|
+
|
|
544
|
+
await promise.promise.finally(() => fs.rm(tmpProject))
|
|
545
|
+
|
|
546
|
+
return { tsConfig }
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
export declare namespace transpile {
|
|
550
|
+
type Options = {
|
|
551
|
+
/** Working directory of the package to transpile. @default process.cwd() */
|
|
552
|
+
cwd?: string | undefined
|
|
553
|
+
/** Entry files to include in the transpilation. */
|
|
554
|
+
entries: string[]
|
|
555
|
+
/** Path to tsconfig.json file, relative to the working directory. @default './tsconfig.json' */
|
|
556
|
+
project?: string | undefined
|
|
557
|
+
/** Whether to use tsgo for transpilation. @default false */
|
|
558
|
+
tsgo?: boolean | undefined
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
type ReturnType = {
|
|
562
|
+
/** Transformed tsconfig.json file. */
|
|
563
|
+
tsConfig: TsConfigJson
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Writes the package.json file to the given working directory.
|
|
569
|
+
*
|
|
570
|
+
* @param cwd - Working directory to write the package.json file to.
|
|
571
|
+
* @param pkgJson - Package.json file to write.
|
|
572
|
+
*/
|
|
573
|
+
export async function writePackageJson(cwd: string, pkgJson: PackageJson) {
|
|
574
|
+
await fs.writeFile(path.resolve(cwd, 'package.json'), JSON.stringify(pkgJson, null, 2), 'utf-8')
|
|
575
|
+
}
|