@voxgig/sdkgen 3.0.0 → 3.2.0
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/dist/action/doctor.js +65 -10
- package/dist/action/doctor.js.map +1 -1
- package/dist/cmp/ExternalTarget.d.ts +2 -0
- package/dist/cmp/ExternalTarget.js +104 -0
- package/dist/cmp/ExternalTarget.js.map +1 -0
- package/dist/helpers/packageMeta.d.ts +9 -1
- package/dist/helpers/packageMeta.js +54 -0
- package/dist/helpers/packageMeta.js.map +1 -1
- package/dist/sdkgen.d.ts +2 -2
- package/dist/sdkgen.js +229 -2
- package/dist/sdkgen.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types.d.ts +12 -0
- package/dist/utility.js +10 -1
- package/dist/utility.js.map +1 -1
- package/model/sdkgen.aontu +130 -0
- package/package.json +1 -1
- package/project/.sdk/model/target/go-cli.aontu +0 -1
- package/project/.sdk/model/target/go-mcp.aontu +0 -1
- package/project/.sdk/model/target/java.aontu +0 -1
- package/project/.sdk/model/target/kotlin.aontu +0 -1
- package/project/.sdk/model/target/lean.aontu +0 -1
- package/project/.sdk/model/target/py-data.aontu +0 -1
- package/project/.sdk/model/target/scala.aontu +0 -1
- package/project/.sdk/model/target/seneca-provider.aontu +118 -0
- package/project/.sdk/model/target/zig.aontu +0 -1
- package/project/.sdk/src/cmp/js/Package_js.ts +8 -1
- package/project/.sdk/src/cmp/ocaml/Package_ocaml.ts +9 -2
- package/project/.sdk/src/cmp/php/Package_php.ts +9 -2
- package/project/.sdk/src/cmp/rb/Package_rb.ts +9 -2
- package/project/.sdk/src/cmp/seneca-provider/Extras_seneca-provider.ts +3915 -0
- package/project/.sdk/src/cmp/seneca-provider/Main_seneca-provider.ts +960 -0
- package/project/.sdk/src/cmp/ts/Package_ts.ts +8 -1
- package/project/.sdk/tm/seneca-provider/CODE_OF_CONDUCT.md +132 -0
- package/project/.sdk/tm/seneca-provider/LICENSE +21 -0
- package/project/.sdk/tm/seneca-provider/Makefile +15 -0
- package/project/.sdk/tm/seneca-provider/src/tsconfig.json +18 -0
- package/project/.sdk/tm/seneca-provider/test/tsconfig.json +16 -0
- package/project/.sdk/tm/seneca-provider/tsfmt.json +3 -0
- package/src/action/doctor.ts +77 -12
- package/src/cmp/ExternalTarget.ts +125 -0
- package/src/helpers/packageMeta.ts +63 -0
- package/src/sdkgen.ts +280 -1
- package/src/types.ts +24 -1
- package/src/utility.ts +10 -1
package/src/sdkgen.ts
CHANGED
|
@@ -22,6 +22,9 @@ import type {
|
|
|
22
22
|
import { SdkGenError, requirePath, isAuthActive, resolveAuthPrefix } from './utility'
|
|
23
23
|
|
|
24
24
|
import { Main } from './cmp/Main'
|
|
25
|
+
import { ExternalTarget } from './cmp/ExternalTarget'
|
|
26
|
+
|
|
27
|
+
import { KIT } from '@voxgig/apidef'
|
|
25
28
|
import { Deploy } from './cmp/Deploy'
|
|
26
29
|
import { Entity } from './cmp/Entity'
|
|
27
30
|
import { Feature } from './cmp/Feature'
|
|
@@ -78,6 +81,8 @@ import {
|
|
|
78
81
|
pkgDescription,
|
|
79
82
|
nonAffiliation,
|
|
80
83
|
keywords,
|
|
84
|
+
authorInfo,
|
|
85
|
+
contributorList,
|
|
81
86
|
envName,
|
|
82
87
|
envToken,
|
|
83
88
|
goModule,
|
|
@@ -224,10 +229,62 @@ function SdkGen(opts: SdkGenOptions) {
|
|
|
224
229
|
},
|
|
225
230
|
}
|
|
226
231
|
|
|
227
|
-
|
|
232
|
+
// Targets that write OUTSIDE the SDK repo (`output: path`) are generated
|
|
233
|
+
// by their own pass, rooted at that path — see cmp/ExternalTarget for why
|
|
234
|
+
// a folder name cannot do this. The in-tree pass must not see them, or
|
|
235
|
+
// the consumer Root would also emit them into `<sdk-repo>/<target>/`.
|
|
236
|
+
//
|
|
237
|
+
// `folder` may be relative (a consumer's build passes '..' from `.sdk`),
|
|
238
|
+
// so resolve it ONCE: every destination is compared against it, and a
|
|
239
|
+
// comparison between a relative and an absolute path is meaningless.
|
|
240
|
+
const root = Path.resolve(folder)
|
|
241
|
+
const external = externalTargets(model, root)
|
|
242
|
+
|
|
243
|
+
// Before ANY file is written, in-tree included: a destination that turns
|
|
244
|
+
// out to be wrong must abort the whole generation, not leave half of it
|
|
245
|
+
// done. See checkExternalFolders.
|
|
246
|
+
checkExternalFolders(external, root, fs)
|
|
247
|
+
|
|
248
|
+
const jres = await jostraca.generate(
|
|
249
|
+
jopts, () => Root({ model: 0 === external.length ? model : withoutTargets(model, external) }))
|
|
228
250
|
|
|
229
251
|
showChanges(jopts.log, 'generate-result', jres, Path.dirname(process.cwd()))
|
|
230
252
|
|
|
253
|
+
for (const ext of external) {
|
|
254
|
+
// `active: false` is the project's only lever to stop the generator
|
|
255
|
+
// writing into a repo it does not own, so it has to be honoured HERE —
|
|
256
|
+
// the consumer Root iterates targets raw and does not check it. The
|
|
257
|
+
// target is still removed from the in-tree model above (withoutTargets
|
|
258
|
+
// takes every `output: path` target, active or not), so switching one
|
|
259
|
+
// off generates it nowhere rather than relocating it into
|
|
260
|
+
// `<sdk-repo>/<target>/`.
|
|
261
|
+
if (!ext.active) {
|
|
262
|
+
log.info({
|
|
263
|
+
point: 'generate-external-skip', target: ext.name, folder: ext.folder,
|
|
264
|
+
note: ext.name + ' inactive, not generated'
|
|
265
|
+
})
|
|
266
|
+
continue
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
log.info({
|
|
270
|
+
point: 'generate-external', target: ext.name, folder: ext.folder,
|
|
271
|
+
note: ext.name + ' -> ' + ext.folder
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
const eres = await jostraca.generate(
|
|
275
|
+
{ ...jopts, folder: ext.folder },
|
|
276
|
+
() => ExternalTarget({
|
|
277
|
+
model, target: ext.target, cmpfolder: folder,
|
|
278
|
+
// How to walk BACK to the SDK project from the destination. A
|
|
279
|
+
// target generating out of tree usually sits beside the SDK in a
|
|
280
|
+
// known layout, and its own docs, scripts and live tests need to
|
|
281
|
+
// name that path.
|
|
282
|
+
sdkrelpath: externalSdkRel(ext, root, log),
|
|
283
|
+
}))
|
|
284
|
+
|
|
285
|
+
showChanges(jopts.log, 'generate-result', eres, Path.dirname(process.cwd()))
|
|
286
|
+
}
|
|
287
|
+
|
|
231
288
|
const dlogs = dlog.log()
|
|
232
289
|
if (0 < dlogs.length) {
|
|
233
290
|
for (let dlogentry of dlogs) {
|
|
@@ -377,6 +434,226 @@ SdkGen.makeBuild = async function(opts: SdkGenOptions) {
|
|
|
377
434
|
|
|
378
435
|
|
|
379
436
|
|
|
437
|
+
type ExternalSpec = {
|
|
438
|
+
name: string
|
|
439
|
+
target: any
|
|
440
|
+
folder: string
|
|
441
|
+
active: boolean
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
// Targets declaring `output: path` — generated into their own repo rather
|
|
446
|
+
// than into `<sdk-repo>/<target>/`.
|
|
447
|
+
//
|
|
448
|
+
// A relative path resolves against the SDK repo root, so a sibling checkout
|
|
449
|
+
// is '../<repo>'. That is deliberately the SAME base the generator writes
|
|
450
|
+
// everything else against: a path in the model should not depend on the
|
|
451
|
+
// directory the command happened to be run from.
|
|
452
|
+
//
|
|
453
|
+
// An INACTIVE target is still listed: it must be taken out of the in-tree
|
|
454
|
+
// model (see withoutTargets) so that switching it off does not silently
|
|
455
|
+
// relocate it into `<sdk-repo>/<target>/`. The generate loop skips it.
|
|
456
|
+
function externalTargets(model: any, folder: string): ExternalSpec[] {
|
|
457
|
+
const targets = model?.main?.[KIT]?.target || {}
|
|
458
|
+
|
|
459
|
+
return Object.keys(targets).sort()
|
|
460
|
+
.map((name: string) => ({ name, target: targets[name] }))
|
|
461
|
+
.filter((t: any) => {
|
|
462
|
+
const path = t.target?.output?.path
|
|
463
|
+
return null != path && '' !== path
|
|
464
|
+
})
|
|
465
|
+
.map((t: any) => ({
|
|
466
|
+
...t,
|
|
467
|
+
folder: Path.resolve(folder, String(t.target.output.path)),
|
|
468
|
+
active: false !== t.target.active,
|
|
469
|
+
}))
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
// The `.jostraca` bookkeeping tree (meta log + a duplicate of the last
|
|
474
|
+
// generated output) that jostraca leaves at an output root. It is the only
|
|
475
|
+
// on-disk evidence that this toolchain has generated into a directory
|
|
476
|
+
// before, so it doubles as the OWNERSHIP MARKER: a destination carrying one
|
|
477
|
+
// has been generated into already, whoever set it up.
|
|
478
|
+
const EXTERNAL_MARKER = '.jostraca'
|
|
479
|
+
|
|
480
|
+
// Entries that do not count as content when deciding whether a destination
|
|
481
|
+
// is empty. A `git init` (or a clone of an empty repo) leaves only `.git`,
|
|
482
|
+
// and that is exactly the destination a FIRST generation is aimed at.
|
|
483
|
+
const EXTERNAL_EMPTY = ['.git', '.DS_Store']
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
// Refuse a destination the project cannot have meant.
|
|
487
|
+
//
|
|
488
|
+
// The external pass is the one code path that writes outside the repo it was
|
|
489
|
+
// pointed at, at a filesystem path taken verbatim from the model — and
|
|
490
|
+
// generation is overwrite, not merge, while jostraca's ensureDir creates
|
|
491
|
+
// missing parents. So a mistyped `output: path` does not fail: it fabricates
|
|
492
|
+
// a package tree at an arbitrary location, or overwrites an unrelated repo's
|
|
493
|
+
// package.json, README.md, LICENSE and CI workflow in place. The only trace
|
|
494
|
+
// was one INFO line naming the resolved folder.
|
|
495
|
+
//
|
|
496
|
+
// A destination must therefore be:
|
|
497
|
+
// - outside the SDK project, in BOTH directions — inside it is what a
|
|
498
|
+
// typo like '.' or 'ts' produces (and the external pass runs SECOND, so
|
|
499
|
+
// it wins over what the in-tree pass just wrote), while a destination
|
|
500
|
+
// that CONTAINS the project is what '..' produces;
|
|
501
|
+
// - claimed by no other target;
|
|
502
|
+
// - and either absent, empty, or carrying the marker a previous
|
|
503
|
+
// generation left there.
|
|
504
|
+
//
|
|
505
|
+
// Anything else is refused with both paths named. NOT skipped: a project
|
|
506
|
+
// generating somewhere other than it believes must be told. A destination
|
|
507
|
+
// that legitimately holds other content first (a repo seeded with a README
|
|
508
|
+
// and LICENCE) says so once in the model, with `output: adopt: true`.
|
|
509
|
+
function checkExternalFolders(external: ExternalSpec[], root: string, fs: any) {
|
|
510
|
+
const claimed: Record<string, string> = {}
|
|
511
|
+
|
|
512
|
+
for (const ext of external) {
|
|
513
|
+
// An inactive target writes nothing, so its destination is not a hazard —
|
|
514
|
+
// and switching a target off must not require keeping its now-unused
|
|
515
|
+
// path valid.
|
|
516
|
+
if (!ext.active) continue
|
|
517
|
+
|
|
518
|
+
const where = 'Target "' + ext.name + '" has output path "' +
|
|
519
|
+
ext.target.output.path + '", which resolves to: ' + ext.folder +
|
|
520
|
+
'\n (SDK project: ' + root + ')'
|
|
521
|
+
|
|
522
|
+
if (ext.folder === root || folderContains(root, ext.folder)) {
|
|
523
|
+
throw new SdkGenError(
|
|
524
|
+
'External target output path is inside the SDK project.\n ' + where +
|
|
525
|
+
'\n A target generating into the SDK project must leave `output: ' +
|
|
526
|
+
'path` unset — it is then generated in-tree, as <sdk-project>/' +
|
|
527
|
+
ext.name + '/.')
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
if (folderContains(ext.folder, root)) {
|
|
531
|
+
throw new SdkGenError(
|
|
532
|
+
'External target output path contains the SDK project.\n ' + where +
|
|
533
|
+
'\n Generation would write this package over the directory holding ' +
|
|
534
|
+
'the SDK project itself.')
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
if (null != claimed[ext.folder]) {
|
|
538
|
+
throw new SdkGenError(
|
|
539
|
+
'External target output path is already claimed by target "' +
|
|
540
|
+
claimed[ext.folder] + '".\n ' + where +
|
|
541
|
+
'\n Two targets generating into the same folder overwrite each ' +
|
|
542
|
+
'other, in target-name order.')
|
|
543
|
+
}
|
|
544
|
+
claimed[ext.folder] = ext.name
|
|
545
|
+
|
|
546
|
+
if (!fs.existsSync(ext.folder)) continue
|
|
547
|
+
|
|
548
|
+
if (!fs.statSync(ext.folder).isDirectory()) {
|
|
549
|
+
throw new SdkGenError(
|
|
550
|
+
'External target output path is not a folder.\n ' + where)
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
if (true === ext.target.output.adopt) continue
|
|
554
|
+
|
|
555
|
+
const entries: string[] = fs.readdirSync(ext.folder)
|
|
556
|
+
.map((entry: any) => String(entry))
|
|
557
|
+
|
|
558
|
+
if (entries.includes(EXTERNAL_MARKER)) continue
|
|
559
|
+
|
|
560
|
+
const content = entries.filter((entry) => !EXTERNAL_EMPTY.includes(entry))
|
|
561
|
+
|
|
562
|
+
if (0 < content.length) {
|
|
563
|
+
throw new SdkGenError(
|
|
564
|
+
'External target output folder already holds content this generator ' +
|
|
565
|
+
'did not write.\n ' + where +
|
|
566
|
+
'\n Found: ' + content.slice(0, 8).join(', ') +
|
|
567
|
+
(8 < content.length ? ', ...' : '') +
|
|
568
|
+
'\n Generation OVERWRITES, so this would replace that content. ' +
|
|
569
|
+
'Check the path;\n if the folder is right, declare it: `main: kit: ' +
|
|
570
|
+
'target: \'' + ext.name + '\': output: adopt: true`.')
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
// Is `path` inside `folder`? Both must already be resolved. Path.relative
|
|
577
|
+
// rather than a string prefix, so that a sibling whose name merely STARTS
|
|
578
|
+
// with the folder's ('/x/sdk' vs '/x/sdk-provider') is not read as nested.
|
|
579
|
+
function folderContains(folder: string, path: string): boolean {
|
|
580
|
+
const rel = Path.relative(folder, path)
|
|
581
|
+
return '' !== rel && !rel.startsWith('..' + Path.sep) && '..' !== rel &&
|
|
582
|
+
!Path.isAbsolute(rel)
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
// The path from the destination back to the SDK project, which the target's
|
|
587
|
+
// own docs, scripts and live tests name (the companion test server lives in
|
|
588
|
+
// the SDK repo and is not published).
|
|
589
|
+
//
|
|
590
|
+
// It is DERIVED from the two resolved folders by default, which is only
|
|
591
|
+
// honest while the walk back crosses nothing the model does not name. It
|
|
592
|
+
// does not for `output: path: '../<repo>'`; it does for anything ascending
|
|
593
|
+
// further. voxgig-solardemo-sdk declares '../../seneca/solardemo-provider'
|
|
594
|
+
// and the derived inverse came out as '../../voxgig-sdk/voxgig-solardemo-sdk'
|
|
595
|
+
// — where `voxgig-sdk` is the name of the WORKSPACE DIRECTORY holding the SDK
|
|
596
|
+
// checkout on one machine, no part of the model. That string is committed
|
|
597
|
+
// into the destination's README.md and three test files, so a second
|
|
598
|
+
// developer with the same two repos under a differently named parent
|
|
599
|
+
// regenerates a spurious diff in tracked files and an instruction path that
|
|
600
|
+
// is wrong on the first machine.
|
|
601
|
+
//
|
|
602
|
+
// So a project that ascends further declares the walk back explicitly, as
|
|
603
|
+
// `output: sdkrel`, and is warned until it does.
|
|
604
|
+
function externalSdkRel(ext: ExternalSpec, root: string, log: any): string {
|
|
605
|
+
const declared = String(ext.target.output?.sdkrel || '')
|
|
606
|
+
if ('' !== declared) return declared
|
|
607
|
+
|
|
608
|
+
const derived = Path.relative(ext.folder, root).split(Path.sep).join('/')
|
|
609
|
+
|
|
610
|
+
// Every segment that is not '..' is a real directory name on the way back
|
|
611
|
+
// down to the SDK project. The LAST is the project's own folder; any
|
|
612
|
+
// earlier one is a directory ABOVE it, which nothing in the model declares.
|
|
613
|
+
const named = derived.split('/').filter((seg) => '..' !== seg)
|
|
614
|
+
|
|
615
|
+
if (1 < named.length) {
|
|
616
|
+
log.warn({
|
|
617
|
+
point: 'external-sdkrel-derived', target: ext.name, sdkrel: derived,
|
|
618
|
+
note: ext.name + ': path back to the SDK project derived as \'' +
|
|
619
|
+
derived + '\', which names ' + (named.length - 1) +
|
|
620
|
+
' directory(s) above the SDK project that the model does not ' +
|
|
621
|
+
'declare — this machine\'s layout will be committed to the ' +
|
|
622
|
+
'generated files. Declare `output: sdkrel` instead.'
|
|
623
|
+
})
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
return derived
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
// The model the IN-TREE pass sees: the same model with the out-of-tree
|
|
631
|
+
// targets taken out.
|
|
632
|
+
//
|
|
633
|
+
// A shallow clone down to `target` only — the model is large, entities and
|
|
634
|
+
// features are shared with the external pass, and a deep copy would both
|
|
635
|
+
// cost and quietly break identity comparisons.
|
|
636
|
+
function withoutTargets(model: any, external: { name: string }[]): any {
|
|
637
|
+
const drop = new Set(external.map((e) => e.name))
|
|
638
|
+
const targets = model?.main?.[KIT]?.target || {}
|
|
639
|
+
|
|
640
|
+
const kept: any = {}
|
|
641
|
+
for (const name of Object.keys(targets)) {
|
|
642
|
+
if (!drop.has(name)) {
|
|
643
|
+
kept[name] = targets[name]
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
return {
|
|
648
|
+
...model,
|
|
649
|
+
main: {
|
|
650
|
+
...model.main,
|
|
651
|
+
[KIT]: { ...model.main[KIT], target: kept },
|
|
652
|
+
},
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
|
|
380
657
|
// Adapted from https://github.com/sindresorhus/import-fresh - Thanks!
|
|
381
658
|
function clear(path: string) {
|
|
382
659
|
if (null == path) {
|
|
@@ -564,6 +841,8 @@ export {
|
|
|
564
841
|
pkgDescription,
|
|
565
842
|
nonAffiliation,
|
|
566
843
|
keywords,
|
|
844
|
+
authorInfo,
|
|
845
|
+
contributorList,
|
|
567
846
|
envName,
|
|
568
847
|
envToken,
|
|
569
848
|
goModule,
|
package/src/types.ts
CHANGED
|
@@ -56,8 +56,31 @@ type ModelTarget = NameCases & {
|
|
|
56
56
|
active?: boolean
|
|
57
57
|
title?: string
|
|
58
58
|
base?: string
|
|
59
|
-
module?: { name?: string }
|
|
59
|
+
module?: { name?: string, path?: string, package?: string, goversion?: string }
|
|
60
60
|
srcfeature?: boolean
|
|
61
|
+
|
|
62
|
+
// Where this target's files land. Present means OUT OF TREE: the target
|
|
63
|
+
// gets its own generate() pass rooted at `path` rather than a folder inside
|
|
64
|
+
// the SDK repo — see cmp/ExternalTarget and
|
|
65
|
+
// docs/explanation/out-of-tree-targets.
|
|
66
|
+
//
|
|
67
|
+
// Typed rather than left to the index signature because `externalTargets()`
|
|
68
|
+
// decides from these keys whether to write OUTSIDE the repo, and a
|
|
69
|
+
// destination path read off a bare `any` is one a rename can silently
|
|
70
|
+
// change to undefined.
|
|
71
|
+
output?: {
|
|
72
|
+
path?: string
|
|
73
|
+
repo?: string
|
|
74
|
+
adopt?: boolean
|
|
75
|
+
sdkrel?: string
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Per-generation-phase activation. A consumer target (go-cli, go-mcp,
|
|
79
|
+
// py-data, seneca-provider) switches every phase off and emits its whole
|
|
80
|
+
// package from Main. Absent — or present with no `active` — means the phase
|
|
81
|
+
// runs: the defaults are inclusive.
|
|
82
|
+
phase?: Record<string, { active?: boolean }>
|
|
83
|
+
|
|
61
84
|
[extra: string]: any
|
|
62
85
|
}
|
|
63
86
|
|
package/src/utility.ts
CHANGED
|
@@ -6,8 +6,17 @@ import { JostracaResult } from 'jostraca'
|
|
|
6
6
|
import { KIT, getModelPath } from '@voxgig/apidef'
|
|
7
7
|
|
|
8
8
|
|
|
9
|
+
// Where a per-target component is loaded from: `<project>/.sdk/dist/<path>`.
|
|
10
|
+
//
|
|
11
|
+
// `ctx$.folder` is jostraca's OUTPUT folder, which is the project for an
|
|
12
|
+
// ordinary target but the destination repo for one generating out of tree
|
|
13
|
+
// (`output: path`). Components always live in the project that owns the
|
|
14
|
+
// model, never in the place its files land, so an external pass sets
|
|
15
|
+
// `ctx$.cmpfolder` and this prefers it. Without that, generating out of tree
|
|
16
|
+
// looks for `<destination>/.sdk/dist/cmp/...` and fails to resolve.
|
|
9
17
|
function resolvePath(ctx$: any, path: string): any {
|
|
10
|
-
const
|
|
18
|
+
const base = null == ctx$.cmpfolder ? ctx$.folder : ctx$.cmpfolder
|
|
19
|
+
const fullpath = Path.join(base, '.sdk', 'dist', path)
|
|
11
20
|
return fullpath
|
|
12
21
|
}
|
|
13
22
|
|