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.
- package/README.md +78 -0
- package/bin/jiek-build.js +16 -0
- package/bin/jiek.js +13 -0
- package/cli-only-build.cjs +1 -1
- package/cli-only-build.js +1 -1
- package/cli.cjs +12 -12
- package/cli.js +12 -12
- package/package.json +1 -1
- package/src/cli-only-build.ts +7 -0
- package/src/cli.ts +2 -0
- package/src/commands/base.ts +18 -0
- package/src/commands/build.ts +459 -0
- package/src/commands/descriptions.ts +17 -0
- package/src/commands/meta.ts +5 -0
- package/src/commands/publish.ts +370 -0
- package/src/index.ts +8 -0
- package/src/inner.ts +11 -0
- package/src/rollup/base.ts +137 -0
- package/src/rollup/index.ts +565 -0
- package/src/rollup/plugins/progress.ts +26 -0
- package/src/rollup/plugins/skip.ts +21 -0
- package/src/rollup/utils/commonOptions.ts +9 -0
- package/src/rollup/utils/externalResolver.ts +35 -0
- package/src/rollup/utils/globalResolver.ts +13 -0
- package/src/rollup/utils/withMinify.ts +18 -0
- package/src/utils/filterSupport.ts +91 -0
- package/src/utils/getExports.ts +140 -0
- package/src/utils/getRoot.ts +16 -0
- package/src/utils/getWD.ts +31 -0
- package/src/utils/loadConfig.ts +111 -0
- package/src/utils/recusiveListFiles.ts +13 -0
- package/src/utils/ts.ts +94 -0
- package/src/utils/tsRegister.ts +26 -0
@@ -0,0 +1,459 @@
|
|
1
|
+
import fs from 'node:fs'
|
2
|
+
import { createRequire } from 'node:module'
|
3
|
+
import path from 'node:path'
|
4
|
+
|
5
|
+
import { confirm } from '@inquirer/prompts'
|
6
|
+
import { MultiBar, Presets } from 'cli-progress'
|
7
|
+
import { program } from 'commander'
|
8
|
+
import { execaCommand } from 'execa'
|
9
|
+
|
10
|
+
import { entriesDescription, filterDescription, outdirDescription } from '#~/commands/descriptions.ts'
|
11
|
+
import { IS_WORKSPACE } from '#~/commands/meta.ts'
|
12
|
+
import type { ProjectsGraph } from '#~/utils/filterSupport.ts'
|
13
|
+
import { filterPackagesGraph, getSelectedProjectsGraph } from '#~/utils/filterSupport.ts'
|
14
|
+
import { getWD } from '#~/utils/getWD.ts'
|
15
|
+
import { loadConfig } from '#~/utils/loadConfig.ts'
|
16
|
+
import { tsRegisterName } from '#~/utils/tsRegister.ts'
|
17
|
+
|
18
|
+
import type { RollupProgressEvent, TemplateOptions } from '../rollup/base'
|
19
|
+
import { BUILDER_TYPE_PACKAGE_NAME_MAP, BUILDER_TYPES } from '../rollup/base'
|
20
|
+
|
21
|
+
declare module 'jiek' {
|
22
|
+
export interface Config {
|
23
|
+
build?: TemplateOptions & {
|
24
|
+
/**
|
25
|
+
* Whether to run in silent mode, only active when configured in the workspace root or cwd.
|
26
|
+
*
|
27
|
+
* @default false
|
28
|
+
*/
|
29
|
+
silent?: boolean
|
30
|
+
}
|
31
|
+
}
|
32
|
+
}
|
33
|
+
|
34
|
+
const FILE_TEMPLATE = (manifest: unknown) => (`
|
35
|
+
module.exports = require('jiek/rollup').template(${JSON.stringify(manifest, null, 2)})
|
36
|
+
`.trimStart())
|
37
|
+
|
38
|
+
const require = createRequire(import.meta.url)
|
39
|
+
|
40
|
+
const isDefault = process.env.JIEK_IS_ONLY_BUILD === 'true'
|
41
|
+
|
42
|
+
const description = `
|
43
|
+
Build the package according to the 'exports' field from the package.json.
|
44
|
+
If you want to through the options to the \`rollup\` command, you can pass the options after '--'.
|
45
|
+
${isDefault ? 'This command is the default command.' : ''}
|
46
|
+
`.trim()
|
47
|
+
|
48
|
+
interface BuildOptions {
|
49
|
+
/**
|
50
|
+
* Auto-detect the builder from the installed dependencies.
|
51
|
+
* If the builder is not installed, it will prompt the user to install it.
|
52
|
+
* If exists multiple builders, it will fall back to the 'esbuild'.
|
53
|
+
*/
|
54
|
+
type?: typeof BUILDER_TYPES[number]
|
55
|
+
/**
|
56
|
+
* The output directory of the build, which relative to the target subpackage root directory.
|
57
|
+
* Support with variables: 'PKG_NAME',
|
58
|
+
* .e.g. 'dist/{{PKG_NAME}}'.
|
59
|
+
*
|
60
|
+
* @default 'dist'
|
61
|
+
*/
|
62
|
+
outdir: string
|
63
|
+
watch: boolean
|
64
|
+
silent: boolean
|
65
|
+
verbose: boolean
|
66
|
+
entries?: string
|
67
|
+
external?: string
|
68
|
+
noJs: boolean
|
69
|
+
noDts: boolean
|
70
|
+
noMin: boolean
|
71
|
+
/**
|
72
|
+
* Do not clean the output directory before building.
|
73
|
+
*/
|
74
|
+
noClean: boolean
|
75
|
+
onlyMin: boolean
|
76
|
+
/**
|
77
|
+
* The type of minify, support 'terser' and 'builder'.
|
78
|
+
*
|
79
|
+
* @default 'builder'
|
80
|
+
*/
|
81
|
+
minType?: string
|
82
|
+
/**
|
83
|
+
* The path of the tsconfig file which is used to generate js and dts files.
|
84
|
+
* If not specified, it will be loaded from:
|
85
|
+
* - ./tsconfig.json
|
86
|
+
* - ./tsconfig.dts.json
|
87
|
+
* - ./tsconfig.build.json
|
88
|
+
*/
|
89
|
+
tsconfig?: string
|
90
|
+
/**
|
91
|
+
* The path of the tsconfig file which is used to generate dts files.
|
92
|
+
* If not specified, it will be loaded from:
|
93
|
+
* - ./tsconfig.json
|
94
|
+
* - ./tsconfig.dts.json
|
95
|
+
*/
|
96
|
+
dtsconfig?: string
|
97
|
+
}
|
98
|
+
|
99
|
+
async function checkDependency(dependency: string) {
|
100
|
+
try {
|
101
|
+
require.resolve(dependency)
|
102
|
+
} catch (e) {
|
103
|
+
console.error(`The package '${dependency}' is not installed, please install it first.`)
|
104
|
+
const { notWorkspace } = getWD()
|
105
|
+
const command = `pnpm install -${notWorkspace ? '' : 'w'}D ${dependency}`
|
106
|
+
if (await confirm({ message: 'Do you want to install it now?' })) {
|
107
|
+
await execaCommand(command)
|
108
|
+
} else {
|
109
|
+
console.warn(`You can run the command '${command}' to install it manually.`)
|
110
|
+
process.exit(1)
|
111
|
+
}
|
112
|
+
}
|
113
|
+
}
|
114
|
+
|
115
|
+
let DEFAULT_BUILDER_TYPE: typeof BUILDER_TYPES[number]
|
116
|
+
Object.entries(BUILDER_TYPE_PACKAGE_NAME_MAP).forEach(([type, packageName]) => {
|
117
|
+
try {
|
118
|
+
require.resolve(packageName)
|
119
|
+
DEFAULT_BUILDER_TYPE = type as typeof BUILDER_TYPES[number]
|
120
|
+
} catch { /* empty */ }
|
121
|
+
})
|
122
|
+
if (!DEFAULT_BUILDER_TYPE!) {
|
123
|
+
DEFAULT_BUILDER_TYPE = 'esbuild'
|
124
|
+
}
|
125
|
+
|
126
|
+
function parseBoolean(v?: unknown) {
|
127
|
+
if (v === undefined) return true
|
128
|
+
return Boolean(v)
|
129
|
+
}
|
130
|
+
|
131
|
+
const buildFilterDescription = `
|
132
|
+
${filterDescription}
|
133
|
+
If you pass the --filter option, it will merge into the filters of the command.
|
134
|
+
`.trim()
|
135
|
+
|
136
|
+
const buildEntriesDescription = `
|
137
|
+
${entriesDescription}
|
138
|
+
If you pass the --entries option, it will merge into the entries of the command.
|
139
|
+
`.trim()
|
140
|
+
|
141
|
+
const command = isDefault
|
142
|
+
? (() => {
|
143
|
+
const c = program
|
144
|
+
.name('jb/jiek-build')
|
145
|
+
.helpCommand(false)
|
146
|
+
if (IS_WORKSPACE) {
|
147
|
+
c.argument('[filters]', buildFilterDescription)
|
148
|
+
} else {
|
149
|
+
c.argument('[entries]', buildEntriesDescription)
|
150
|
+
}
|
151
|
+
return c
|
152
|
+
})()
|
153
|
+
: program
|
154
|
+
.command(`build [${IS_WORKSPACE ? 'filters' : 'entries'}]`)
|
155
|
+
|
156
|
+
command
|
157
|
+
.description(description)
|
158
|
+
.option('-t, --type <TYPE>', `The type of build, support ${BUILDER_TYPES.map(s => `"${s}"`).join(', ')}.`, v => {
|
159
|
+
if (!BUILDER_TYPES.includes(v as any)) {
|
160
|
+
throw new Error(`The value of 'type' must be ${BUILDER_TYPES.map(s => `"${s}"`).join(', ')}`)
|
161
|
+
}
|
162
|
+
return String(v)
|
163
|
+
}, 'esbuild')
|
164
|
+
.option('-o, --outdir <OUTDIR>', outdirDescription, String, 'dist')
|
165
|
+
.option('-e, --entries <ENTRIES>', entriesDescription)
|
166
|
+
.option('--external <EXTERNAL>', 'Specify the external dependencies of the package.', String)
|
167
|
+
.option('-nj, --noJs', 'Do not output js files.', parseBoolean)
|
168
|
+
.option('-nd, --noDts', 'Do not output dts files.', parseBoolean)
|
169
|
+
.option('-nm, --noMin', 'Do not output minify files.', parseBoolean)
|
170
|
+
.option(
|
171
|
+
'--minType <MINTYPE>',
|
172
|
+
'The type of minify, support "builder" and "terser".',
|
173
|
+
v => {
|
174
|
+
if (!['builder', 'terser'].includes(v)) {
|
175
|
+
throw new Error('The value of `minType` must be "builder" or "terser"')
|
176
|
+
}
|
177
|
+
return String(v)
|
178
|
+
}
|
179
|
+
)
|
180
|
+
.option('-nc, --noClean', 'Do not clean the output directory before building.', parseBoolean)
|
181
|
+
.option(
|
182
|
+
'-om, --onlyMin',
|
183
|
+
'Only output minify files, but dts files will still be output, it only replaces the js files.',
|
184
|
+
parseBoolean
|
185
|
+
)
|
186
|
+
.option('--tsconfig <TSCONFIG>', 'The path of the tsconfig file which is used to generate js and dts files.', String)
|
187
|
+
.option('--dtsconfig <DTSCONFIG>', 'The path of the tsconfig file which is used to generate dts files.', String)
|
188
|
+
.option('-w, --watch', 'Watch the file changes.', parseBoolean)
|
189
|
+
.option('-s, --silent', "Don't display logs.", parseBoolean)
|
190
|
+
.option('-v, --verbose', 'Display debug logs.', parseBoolean)
|
191
|
+
.action(async (commandFiltersOrEntries: string | undefined, options: BuildOptions) => {
|
192
|
+
/* eslint-disable prefer-const */
|
193
|
+
let {
|
194
|
+
type,
|
195
|
+
outdir,
|
196
|
+
watch,
|
197
|
+
silent,
|
198
|
+
verbose,
|
199
|
+
entries: optionEntries,
|
200
|
+
external,
|
201
|
+
noJs: withoutJs,
|
202
|
+
noDts: withoutDts,
|
203
|
+
noMin: withoutMin,
|
204
|
+
minType: minifyType,
|
205
|
+
noClean,
|
206
|
+
onlyMin,
|
207
|
+
tsconfig,
|
208
|
+
dtsconfig
|
209
|
+
} = options
|
210
|
+
/* eslint-enable prefer-const */
|
211
|
+
const resolvedType = type ?? DEFAULT_BUILDER_TYPE
|
212
|
+
if (!withoutJs) {
|
213
|
+
await checkDependency(BUILDER_TYPE_PACKAGE_NAME_MAP[resolvedType]!)
|
214
|
+
if (minifyType === 'builder') {
|
215
|
+
minifyType = resolvedType
|
216
|
+
}
|
217
|
+
}
|
218
|
+
if (!withoutMin) {
|
219
|
+
await checkDependency(
|
220
|
+
{
|
221
|
+
...BUILDER_TYPE_PACKAGE_NAME_MAP,
|
222
|
+
terser: '@rollup/plugin-terser'
|
223
|
+
}[resolvedType]!
|
224
|
+
)
|
225
|
+
}
|
226
|
+
let shouldPassThrough = false
|
227
|
+
|
228
|
+
const passThroughOptions = process.argv
|
229
|
+
.reduce(
|
230
|
+
(acc, value) => {
|
231
|
+
if (shouldPassThrough) {
|
232
|
+
acc.push(value)
|
233
|
+
}
|
234
|
+
if (value === '--') {
|
235
|
+
shouldPassThrough = true
|
236
|
+
}
|
237
|
+
return acc
|
238
|
+
},
|
239
|
+
[] as string[]
|
240
|
+
)
|
241
|
+
const { build } = loadConfig()
|
242
|
+
silent = silent ?? build?.silent ?? false
|
243
|
+
|
244
|
+
if (withoutMin && onlyMin) {
|
245
|
+
throw new Error('Cannot use both --without-minify and --only-minify')
|
246
|
+
}
|
247
|
+
if (onlyMin && withoutJs) {
|
248
|
+
throw new Error('Cannot use --without-js and --only-minify at the same time')
|
249
|
+
}
|
250
|
+
|
251
|
+
let entries: string | undefined = [
|
252
|
+
optionEntries,
|
253
|
+
IS_WORKSPACE ? undefined : commandFiltersOrEntries
|
254
|
+
].filter(Boolean).join(',')
|
255
|
+
if (entries.length === 0) {
|
256
|
+
entries = undefined
|
257
|
+
}
|
258
|
+
const env = {
|
259
|
+
...process.env,
|
260
|
+
JIEK_BUILDER: type,
|
261
|
+
JIEK_OUT_DIR: outdir,
|
262
|
+
JIEK_CLEAN: String(!noClean),
|
263
|
+
JIEK_ENTRIES: entries,
|
264
|
+
JIEK_EXTERNAL: external,
|
265
|
+
JIEK_WITHOUT_JS: String(withoutJs),
|
266
|
+
JIEK_WITHOUT_DTS: String(withoutDts),
|
267
|
+
JIEK_WITHOUT_MINIFY: String(withoutMin),
|
268
|
+
JIEK_ONLY_MINIFY: String(onlyMin),
|
269
|
+
JIEK_MINIFY_TYPE: minifyType,
|
270
|
+
JIEK_TSCONFIG: tsconfig,
|
271
|
+
JIEK_DTSCONFIG: dtsconfig
|
272
|
+
}
|
273
|
+
|
274
|
+
const multiBars = new MultiBar({
|
275
|
+
clearOnComplete: false,
|
276
|
+
hideCursor: true,
|
277
|
+
format: '- {bar} | {status} | {pkgName} | {input} | {message}'
|
278
|
+
}, Presets.shades_classic)
|
279
|
+
|
280
|
+
const buildPackage = async ({
|
281
|
+
wd,
|
282
|
+
value = {}
|
283
|
+
}: ProjectsGraph) => {
|
284
|
+
if (Object.keys(value).length === 0) {
|
285
|
+
throw new Error('no package found')
|
286
|
+
}
|
287
|
+
const wdNodeModules = path.resolve(wd, 'node_modules')
|
288
|
+
if (!fs.existsSync(wdNodeModules)) {
|
289
|
+
fs.mkdirSync(wdNodeModules)
|
290
|
+
}
|
291
|
+
const jiekTempDir = (...paths: string[]) => path.resolve(wdNodeModules, '.jiek', ...paths)
|
292
|
+
if (!fs.existsSync(jiekTempDir())) {
|
293
|
+
fs.mkdirSync(jiekTempDir())
|
294
|
+
}
|
295
|
+
|
296
|
+
const rollupBinaryPath = require.resolve('rollup')
|
297
|
+
.replace(/dist\/rollup.js$/, 'dist/bin/rollup')
|
298
|
+
let i = 0
|
299
|
+
await Promise.all(
|
300
|
+
Object.entries(value).map(async ([dir, manifest]) => {
|
301
|
+
if (!manifest.name) {
|
302
|
+
throw new Error('package.json must have a name field')
|
303
|
+
}
|
304
|
+
|
305
|
+
// TODO support auto build child packages in workspaces
|
306
|
+
const escapeManifestName = manifest.name.replace(/^@/g, '').replace(/\//g, '+')
|
307
|
+
const configFile = jiekTempDir(
|
308
|
+
`${escapeManifestName ?? `anonymous-${i++}`}.rollup.config.js`
|
309
|
+
)
|
310
|
+
fs.writeFileSync(configFile, FILE_TEMPLATE(manifest))
|
311
|
+
const command = [rollupBinaryPath, '--silent', '-c', configFile]
|
312
|
+
if (tsRegisterName) {
|
313
|
+
command.unshift(`node -r ${tsRegisterName}`)
|
314
|
+
}
|
315
|
+
if (watch) {
|
316
|
+
command.push('--watch')
|
317
|
+
}
|
318
|
+
command.push(...passThroughOptions)
|
319
|
+
const child = execaCommand(command.join(' '), {
|
320
|
+
ipc: true,
|
321
|
+
cwd: dir,
|
322
|
+
env: {
|
323
|
+
...env,
|
324
|
+
JIEK_NAME: manifest.name,
|
325
|
+
JIEK_ROOT: wd
|
326
|
+
}
|
327
|
+
})
|
328
|
+
const bars: Record<string, ReturnType<typeof multiBars.create>> = {}
|
329
|
+
const times: Record<string, number> = {}
|
330
|
+
const locks: Record<string, boolean> = {}
|
331
|
+
let inputMaxLen = 10
|
332
|
+
child.on('message', (e: RollupProgressEvent) => {
|
333
|
+
if (e.type === 'debug') console.log(...(Array.isArray(e.data) ? e.data : [e.data]))
|
334
|
+
})
|
335
|
+
!silent && child.on('message', (e: RollupProgressEvent) => {
|
336
|
+
if (e.type === 'init') {
|
337
|
+
const { leafMap, targetsLength } = e.data
|
338
|
+
const leafs = Array
|
339
|
+
.from(leafMap.entries())
|
340
|
+
.flatMap(([input, pathAndCondiions]) =>
|
341
|
+
pathAndCondiions.map(([path, ...conditions]) => ({
|
342
|
+
input,
|
343
|
+
path,
|
344
|
+
conditions
|
345
|
+
}))
|
346
|
+
)
|
347
|
+
let initMessage = `Package '${manifest.name}' has ${targetsLength} targets to build`
|
348
|
+
if (watch) {
|
349
|
+
initMessage += ' and watching...'
|
350
|
+
}
|
351
|
+
console.log(initMessage)
|
352
|
+
leafs.forEach(({ input }) => {
|
353
|
+
inputMaxLen = Math.max(inputMaxLen, input.length)
|
354
|
+
})
|
355
|
+
leafs.forEach(({ input, path }) => {
|
356
|
+
const key = `${input}:${path}`
|
357
|
+
if (bars[key]) return
|
358
|
+
bars[key] = multiBars.create(50, 0, {
|
359
|
+
pkgName: manifest.name,
|
360
|
+
input: input.padEnd(inputMaxLen + 5),
|
361
|
+
status: 'waiting'.padEnd(10)
|
362
|
+
}, {
|
363
|
+
barsize: 20,
|
364
|
+
linewrap: true
|
365
|
+
})
|
366
|
+
})
|
367
|
+
}
|
368
|
+
if (e.type === 'progress') {
|
369
|
+
const {
|
370
|
+
path,
|
371
|
+
tags,
|
372
|
+
input,
|
373
|
+
event,
|
374
|
+
message
|
375
|
+
} = e.data
|
376
|
+
const bar = bars[`${input}:${path}`]
|
377
|
+
if (!bar) return
|
378
|
+
const time = times[`${input}:${path}`]
|
379
|
+
bar.update(
|
380
|
+
{
|
381
|
+
start: 0,
|
382
|
+
resolve: 20,
|
383
|
+
end: 50
|
384
|
+
}[event ?? 'start'] ?? 0,
|
385
|
+
{
|
386
|
+
input: (
|
387
|
+
time
|
388
|
+
? `${input}(x${time.toString().padStart(2, '0')})`
|
389
|
+
: input
|
390
|
+
).padEnd(inputMaxLen + 5),
|
391
|
+
status: event?.padEnd(10),
|
392
|
+
message: `${tags?.join(', ')}: ${message}`
|
393
|
+
}
|
394
|
+
)
|
395
|
+
}
|
396
|
+
if (e.type === 'watchChange') {
|
397
|
+
const {
|
398
|
+
path,
|
399
|
+
input
|
400
|
+
} = e.data
|
401
|
+
const key = `${input}:${path}`
|
402
|
+
const bar = bars[key]
|
403
|
+
if (!bar) return
|
404
|
+
let time = times[key] ?? 1
|
405
|
+
if (!locks[key]) {
|
406
|
+
time += 1
|
407
|
+
times[key] = time
|
408
|
+
setTimeout(() => {
|
409
|
+
locks[key] = false
|
410
|
+
}, 100)
|
411
|
+
bar.update(0, {
|
412
|
+
input: `${input}(x${time.toString().padStart(2, '0')})`.padEnd(inputMaxLen + 5),
|
413
|
+
status: 'watching'.padEnd(10),
|
414
|
+
message: 'watching...'
|
415
|
+
})
|
416
|
+
}
|
417
|
+
locks[key] = true
|
418
|
+
}
|
419
|
+
})
|
420
|
+
await new Promise<void>((resolve, reject) => {
|
421
|
+
let errorStr = ''
|
422
|
+
child.stderr?.on('data', (data) => {
|
423
|
+
errorStr += data
|
424
|
+
})
|
425
|
+
child.once('exit', (code) =>
|
426
|
+
code === 0
|
427
|
+
? resolve()
|
428
|
+
: reject(new Error(`rollup build failed:\n${errorStr}`)))
|
429
|
+
verbose && child.stdout?.pipe(process.stdout)
|
430
|
+
})
|
431
|
+
})
|
432
|
+
)
|
433
|
+
}
|
434
|
+
const commandFilters = IS_WORKSPACE ? commandFiltersOrEntries : undefined
|
435
|
+
const filters = [
|
436
|
+
...new Set([
|
437
|
+
...(program.getOptionValue('filter') as string | undefined)
|
438
|
+
?.split(',')
|
439
|
+
.map(s => s.trim())
|
440
|
+
.filter(s => s.length > 0)
|
441
|
+
?? [],
|
442
|
+
...commandFilters
|
443
|
+
?.split(',')
|
444
|
+
.map(s => s.trim())
|
445
|
+
.filter(s => s.length > 0)
|
446
|
+
?? []
|
447
|
+
])
|
448
|
+
]
|
449
|
+
try {
|
450
|
+
if (filters.length > 0) {
|
451
|
+
const packages = await filterPackagesGraph(filters)
|
452
|
+
await Promise.all(packages.map(buildPackage))
|
453
|
+
} else {
|
454
|
+
await buildPackage(await getSelectedProjectsGraph())
|
455
|
+
}
|
456
|
+
} finally {
|
457
|
+
multiBars.stop()
|
458
|
+
}
|
459
|
+
})
|
@@ -0,0 +1,17 @@
|
|
1
|
+
export const entriesDescription = `
|
2
|
+
Specify the build entry-points of the package.json's 'exports' field.
|
3
|
+
Support glob pattern and array.
|
4
|
+
.e.g. '.', './*', './sub/*', './a,./b'.
|
5
|
+
`.trim()
|
6
|
+
|
7
|
+
export const filterDescription = `
|
8
|
+
Filter the packages from the workspace.
|
9
|
+
Support fuzzy match and array.
|
10
|
+
.e.g. 'core,utils'.
|
11
|
+
`.trim()
|
12
|
+
|
13
|
+
export const outdirDescription = `
|
14
|
+
The output directory of the build, which relative to the target subpackage root directory.
|
15
|
+
Support with variables: 'PKG_NAME',
|
16
|
+
.e.g. 'dist/{{PKG_NAME}}'.
|
17
|
+
`.trim()
|