agentwheel 0.4.1 → 0.5.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/README.md +25 -2
- package/dist/index.js +122 -29
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,9 +33,9 @@ No lock-in. No central gatekeeper. Your packages live in plain git repos, your c
|
|
|
33
33
|
|
|
34
34
|
---
|
|
35
35
|
|
|
36
|
-
> **Status: early (v0.
|
|
36
|
+
> **Status: early (v0.5).** The lifecycle core is real and tested — local/git/skillkit/vercel
|
|
37
37
|
> sources, optional registry discovery, plan/sync/update/drift/uninstall, overlays, eject/remember,
|
|
38
|
-
> profiles, runtime auto-detection, fleet targeting, rich JSON merge, and pluggable adapters.
|
|
38
|
+
> profiles, runtime auto-detection, fleet targeting, asset-includes, rich JSON merge, and pluggable adapters.
|
|
39
39
|
> Expect sharp edges.
|
|
40
40
|
|
|
41
41
|
## What it does
|
|
@@ -147,6 +147,28 @@ A package is a git repo (or folder) with a JSON manifest and a canonical layout:
|
|
|
147
147
|
}
|
|
148
148
|
```
|
|
149
149
|
|
|
150
|
+
Packages can compose shared files into each directory artifact at staging time. This keeps one
|
|
151
|
+
canonical copy in the package repo while installing self-contained skills:
|
|
152
|
+
|
|
153
|
+
```jsonc
|
|
154
|
+
{
|
|
155
|
+
"type": "skills",
|
|
156
|
+
"path": "skills",
|
|
157
|
+
"assets": [
|
|
158
|
+
{
|
|
159
|
+
"from": "packages/tmux-bridge/bin",
|
|
160
|
+
"into": "bin",
|
|
161
|
+
"include": ["*.sh"],
|
|
162
|
+
"mode": "preserve"
|
|
163
|
+
}
|
|
164
|
+
]
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
`mode: "preserve"` keeps executable bits on copied scripts. The composed files are included in
|
|
169
|
+
the skill directory hash, so idempotency and drift detection work as if the assets had always
|
|
170
|
+
belonged to the skill.
|
|
171
|
+
|
|
150
172
|
Publish by pushing to any git host. A registry exists only for short names and discovery — it's
|
|
151
173
|
optional, and `agentwheel add <url|path>` always works without it.
|
|
152
174
|
|
|
@@ -221,6 +243,7 @@ clear conversion format.
|
|
|
221
243
|
- [x] **v0.2** — git source driver; `update` (pinned & tracking); overlays/additive/override/eject; `init`; hermes + copilot adapters; commands/mcp/hooks artifacts; OpenClaw semantic plugin planning.
|
|
222
244
|
- [x] **v0.3** — skillkit/vercel source drivers; optional registry & federation; programmatic adapters behind `--allow-adapter-code`; rich JSON merge for mcp/hooks/settings; profiles.
|
|
223
245
|
- [x] **v0.4** — runtime auto-detection; no `--target-root` needed for normal use; fleet config with named agents; global + project config merge; `--agent` and `--all`.
|
|
246
|
+
- [x] **v0.5** — asset-includes compose shared files into skills at install time; executable bits preserved; hashes include composed assets.
|
|
224
247
|
|
|
225
248
|
## Design docs
|
|
226
249
|
|
package/dist/index.js
CHANGED
|
@@ -34,6 +34,12 @@ var artifactTypeSchema = z.enum([
|
|
|
34
34
|
"plugins"
|
|
35
35
|
]);
|
|
36
36
|
var fileKindSchema = z.enum(["file", "dir"]);
|
|
37
|
+
var packageAssetSchema = z.object({
|
|
38
|
+
from: z.string().min(1),
|
|
39
|
+
into: z.string().min(1),
|
|
40
|
+
include: z.array(z.string().min(1)).optional(),
|
|
41
|
+
mode: z.enum(["preserve", "copy"]).default("preserve")
|
|
42
|
+
});
|
|
37
43
|
var artifactSchema = z.object({
|
|
38
44
|
type: artifactTypeSchema,
|
|
39
45
|
name: z.string().min(1),
|
|
@@ -43,7 +49,8 @@ var artifactSchema = z.object({
|
|
|
43
49
|
kind: fileKindSchema,
|
|
44
50
|
hash: z.string().min(16),
|
|
45
51
|
packageName: z.string().min(1).optional(),
|
|
46
|
-
channel: z.enum(["managed", "overlay", "addition", "override", "ejected"]).default("managed")
|
|
52
|
+
channel: z.enum(["managed", "overlay", "addition", "override", "ejected"]).default("managed"),
|
|
53
|
+
assets: z.array(packageAssetSchema).optional()
|
|
47
54
|
});
|
|
48
55
|
|
|
49
56
|
// src/model/adapter.ts
|
|
@@ -804,7 +811,8 @@ import { parse as parse2, printParseErrorCode as printParseErrorCode2 } from "js
|
|
|
804
811
|
import { z as z4 } from "zod";
|
|
805
812
|
var packageProvideSchema = z4.object({
|
|
806
813
|
type: artifactTypeSchema,
|
|
807
|
-
path: z4.string().min(1)
|
|
814
|
+
path: z4.string().min(1),
|
|
815
|
+
assets: z4.array(packageAssetSchema).optional()
|
|
808
816
|
});
|
|
809
817
|
var packageManifestSchema = z4.object({
|
|
810
818
|
schemaVersion: z4.literal(1),
|
|
@@ -973,7 +981,7 @@ async function listFromManifest(root, packageName) {
|
|
|
973
981
|
const stats = await stat2(full);
|
|
974
982
|
if (provide.type === "instructions") {
|
|
975
983
|
if (stats.isFile()) {
|
|
976
|
-
artifacts.push(await artifactForFile(provide.type, basename(full), full, provide.path, packageName));
|
|
984
|
+
artifacts.push(await artifactForFile(provide.type, basename(full), full, provide.path, packageName, provide.assets));
|
|
977
985
|
}
|
|
978
986
|
continue;
|
|
979
987
|
}
|
|
@@ -981,16 +989,16 @@ async function listFromManifest(root, packageName) {
|
|
|
981
989
|
for (const entry of await sortedDirEntries(full)) {
|
|
982
990
|
const child = join6(full, entry.name);
|
|
983
991
|
if (provide.type === "skills" && entry.isDirectory()) {
|
|
984
|
-
artifacts.push(await artifactForDir(provide.type, entry.name, child, join6(provide.path, entry.name), packageName));
|
|
992
|
+
artifacts.push(await artifactForDir(provide.type, entry.name, child, join6(provide.path, entry.name), packageName, provide.assets));
|
|
985
993
|
} else if (provide.type === "plugins" && entry.isDirectory()) {
|
|
986
|
-
artifacts.push(await artifactForDir(provide.type, entry.name, child, join6(provide.path, entry.name), packageName));
|
|
994
|
+
artifacts.push(await artifactForDir(provide.type, entry.name, child, join6(provide.path, entry.name), packageName, provide.assets));
|
|
987
995
|
} else if (entry.isFile()) {
|
|
988
996
|
const name = provide.type === "rules" && entry.name.endsWith(".md") ? entry.name : entry.name;
|
|
989
|
-
artifacts.push(await artifactForFile(provide.type, name, child, join6(provide.path, entry.name), packageName));
|
|
997
|
+
artifacts.push(await artifactForFile(provide.type, name, child, join6(provide.path, entry.name), packageName, provide.assets));
|
|
990
998
|
}
|
|
991
999
|
}
|
|
992
1000
|
} else if (stats.isFile()) {
|
|
993
|
-
artifacts.push(await artifactForFile(provide.type, basename(full), full, provide.path, packageName));
|
|
1001
|
+
artifacts.push(await artifactForFile(provide.type, basename(full), full, provide.path, packageName, provide.assets));
|
|
994
1002
|
}
|
|
995
1003
|
}
|
|
996
1004
|
return artifacts;
|
|
@@ -1007,7 +1015,7 @@ async function listGenericArtifacts(type, dir, relativeRoot, packageName) {
|
|
|
1007
1015
|
}
|
|
1008
1016
|
return artifacts;
|
|
1009
1017
|
}
|
|
1010
|
-
async function artifactForFile(type, name, sourcePath, relativePath, packageName) {
|
|
1018
|
+
async function artifactForFile(type, name, sourcePath, relativePath, packageName, assets) {
|
|
1011
1019
|
return {
|
|
1012
1020
|
type,
|
|
1013
1021
|
name,
|
|
@@ -1016,10 +1024,11 @@ async function artifactForFile(type, name, sourcePath, relativePath, packageName
|
|
|
1016
1024
|
kind: "file",
|
|
1017
1025
|
hash: await hashPath(sourcePath),
|
|
1018
1026
|
packageName,
|
|
1019
|
-
channel: "managed"
|
|
1027
|
+
channel: "managed",
|
|
1028
|
+
assets
|
|
1020
1029
|
};
|
|
1021
1030
|
}
|
|
1022
|
-
async function artifactForDir(type, name, sourcePath, relativePath, packageName) {
|
|
1031
|
+
async function artifactForDir(type, name, sourcePath, relativePath, packageName, assets) {
|
|
1023
1032
|
return {
|
|
1024
1033
|
type,
|
|
1025
1034
|
name,
|
|
@@ -1028,7 +1037,8 @@ async function artifactForDir(type, name, sourcePath, relativePath, packageName)
|
|
|
1028
1037
|
kind: "dir",
|
|
1029
1038
|
hash: await hashPath(sourcePath),
|
|
1030
1039
|
packageName,
|
|
1031
|
-
channel: "managed"
|
|
1040
|
+
channel: "managed",
|
|
1041
|
+
assets
|
|
1032
1042
|
};
|
|
1033
1043
|
}
|
|
1034
1044
|
|
|
@@ -1463,8 +1473,8 @@ function getSourceDriver(name = "local") {
|
|
|
1463
1473
|
}
|
|
1464
1474
|
|
|
1465
1475
|
// src/staging/staging.ts
|
|
1466
|
-
import { cp as cp3, mkdir as mkdir5, mkdtemp } from "fs/promises";
|
|
1467
|
-
import { dirname as dirname5, join as join12 } from "path";
|
|
1476
|
+
import { chmod, cp as cp3, mkdir as mkdir5, mkdtemp, readdir as readdir4, stat as stat6 } from "fs/promises";
|
|
1477
|
+
import { basename as basename7, dirname as dirname5, join as join12, relative as relative2, resolve as resolve8, sep } from "path";
|
|
1468
1478
|
import { tmpdir as tmpdir2 } from "os";
|
|
1469
1479
|
|
|
1470
1480
|
// src/staging/customize.ts
|
|
@@ -1583,6 +1593,7 @@ async function stageSource(driver, source, options = {}) {
|
|
|
1583
1593
|
const stagedPath = join12(root, artifact.relativePath);
|
|
1584
1594
|
await mkdir5(dirname5(stagedPath), { recursive: true });
|
|
1585
1595
|
await cp3(artifact.sourcePath, stagedPath, { recursive: artifact.kind === "dir", dereference: true });
|
|
1596
|
+
await composeAssets(artifact, resolved.resolvedPath, stagedPath);
|
|
1586
1597
|
stagedArtifacts.push({
|
|
1587
1598
|
...artifact,
|
|
1588
1599
|
stagedPath,
|
|
@@ -1623,11 +1634,93 @@ async function stageSource(driver, source, options = {}) {
|
|
|
1623
1634
|
}
|
|
1624
1635
|
};
|
|
1625
1636
|
}
|
|
1637
|
+
async function composeAssets(artifact, packageRoot, stagedPath) {
|
|
1638
|
+
if (!artifact.assets?.length) return;
|
|
1639
|
+
if (artifact.kind !== "dir") {
|
|
1640
|
+
throw new Error(`Asset includes require a directory artifact: ${artifact.type}/${artifact.name}`);
|
|
1641
|
+
}
|
|
1642
|
+
for (const asset of artifact.assets) {
|
|
1643
|
+
const source = resolvePackagePath(packageRoot, asset.from);
|
|
1644
|
+
const dest = join12(stagedPath, asset.into);
|
|
1645
|
+
await copyAsset(asset, source, dest);
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
async function copyAsset(asset, source, dest) {
|
|
1649
|
+
const sourceStats = await stat6(source);
|
|
1650
|
+
if (sourceStats.isFile()) {
|
|
1651
|
+
if (matchesAny(basename7(source), asset.include)) {
|
|
1652
|
+
await mkdir5(dest, { recursive: true });
|
|
1653
|
+
await copyAssetFile(source, join12(dest, basename7(source)), asset);
|
|
1654
|
+
}
|
|
1655
|
+
return;
|
|
1656
|
+
}
|
|
1657
|
+
if (!sourceStats.isDirectory()) {
|
|
1658
|
+
throw new Error(`Asset include source is not a file or directory: ${source}`);
|
|
1659
|
+
}
|
|
1660
|
+
if (!asset.include?.length) {
|
|
1661
|
+
await mkdir5(dirname5(dest), { recursive: true });
|
|
1662
|
+
await cp3(source, dest, { recursive: true, dereference: true });
|
|
1663
|
+
if (asset.mode === "copy") await normalizeCopiedModes(dest);
|
|
1664
|
+
return;
|
|
1665
|
+
}
|
|
1666
|
+
for (const file of await listFiles(source)) {
|
|
1667
|
+
const rel = relative2(source, file).replaceAll("\\", "/");
|
|
1668
|
+
if (!matchesAny(rel, asset.include) && !matchesAny(basename7(file), asset.include)) continue;
|
|
1669
|
+
await copyAssetFile(file, join12(dest, rel), asset);
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
async function copyAssetFile(source, dest, asset) {
|
|
1673
|
+
await mkdir5(dirname5(dest), { recursive: true });
|
|
1674
|
+
await cp3(source, dest, { dereference: true });
|
|
1675
|
+
if (asset.mode === "copy") await chmod(dest, 420);
|
|
1676
|
+
}
|
|
1677
|
+
function resolvePackagePath(packageRoot, path) {
|
|
1678
|
+
const resolved = resolve8(packageRoot, path);
|
|
1679
|
+
const root = resolve8(packageRoot);
|
|
1680
|
+
if (resolved !== root && !resolved.startsWith(`${root}${sep}`)) {
|
|
1681
|
+
throw new Error(`Asset include escapes package root: ${path}`);
|
|
1682
|
+
}
|
|
1683
|
+
return resolved;
|
|
1684
|
+
}
|
|
1685
|
+
async function listFiles(root) {
|
|
1686
|
+
const out = [];
|
|
1687
|
+
async function walk2(dir) {
|
|
1688
|
+
for (const entry of (await readdir4(dir, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
1689
|
+
const full = join12(dir, entry.name);
|
|
1690
|
+
if (entry.isDirectory()) {
|
|
1691
|
+
await walk2(full);
|
|
1692
|
+
} else if (entry.isFile()) {
|
|
1693
|
+
out.push(full);
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
await walk2(root);
|
|
1698
|
+
return out;
|
|
1699
|
+
}
|
|
1700
|
+
async function normalizeCopiedModes(path) {
|
|
1701
|
+
const stats = await stat6(path);
|
|
1702
|
+
if (stats.isFile()) {
|
|
1703
|
+
await chmod(path, 420);
|
|
1704
|
+
return;
|
|
1705
|
+
}
|
|
1706
|
+
if (!stats.isDirectory()) return;
|
|
1707
|
+
for (const entry of await readdir4(path, { withFileTypes: true })) {
|
|
1708
|
+
await normalizeCopiedModes(join12(path, entry.name));
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
function matchesAny(path, patterns) {
|
|
1712
|
+
if (!patterns?.length) return true;
|
|
1713
|
+
return patterns.some((pattern) => matchesGlob(path, pattern));
|
|
1714
|
+
}
|
|
1715
|
+
function matchesGlob(path, pattern) {
|
|
1716
|
+
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
1717
|
+
return new RegExp(`^${escaped}$`).test(path);
|
|
1718
|
+
}
|
|
1626
1719
|
|
|
1627
1720
|
// src/model/workspace.ts
|
|
1628
1721
|
import { readFile as readFile8 } from "fs/promises";
|
|
1629
1722
|
import { homedir as homedir3 } from "os";
|
|
1630
|
-
import { dirname as dirname6, join as join13, resolve as
|
|
1723
|
+
import { dirname as dirname6, join as join13, resolve as resolve9 } from "path";
|
|
1631
1724
|
import { z as z5 } from "zod";
|
|
1632
1725
|
var workspacePackageSchema = z5.object({
|
|
1633
1726
|
name: z5.string().min(1),
|
|
@@ -1687,11 +1780,11 @@ function globalWorkspaceConfigPath(globalRoot = homedir3()) {
|
|
|
1687
1780
|
return join13(globalRoot, ".agentwheel", "config.json");
|
|
1688
1781
|
}
|
|
1689
1782
|
async function findWorkspaceRoot(start = process.cwd()) {
|
|
1690
|
-
let current =
|
|
1783
|
+
let current = resolve9(start);
|
|
1691
1784
|
while (true) {
|
|
1692
1785
|
if (await pathExists(workspaceConfigPath(current))) return current;
|
|
1693
1786
|
const parent = dirname6(current);
|
|
1694
|
-
if (parent === current) return
|
|
1787
|
+
if (parent === current) return resolve9(start);
|
|
1695
1788
|
current = parent;
|
|
1696
1789
|
}
|
|
1697
1790
|
}
|
|
@@ -1715,9 +1808,9 @@ function mergeWorkspaceConfig(global, project) {
|
|
|
1715
1808
|
});
|
|
1716
1809
|
}
|
|
1717
1810
|
function resolveConfigPath(path, baseRoot) {
|
|
1718
|
-
if (path.startsWith("~/")) return
|
|
1811
|
+
if (path.startsWith("~/")) return resolve9(homedir3(), path.slice(2));
|
|
1719
1812
|
if (path === "~") return homedir3();
|
|
1720
|
-
return path.startsWith("/") ?
|
|
1813
|
+
return path.startsWith("/") ? resolve9(path) : resolve9(baseRoot, path);
|
|
1721
1814
|
}
|
|
1722
1815
|
function emptyWorkspaceConfig() {
|
|
1723
1816
|
return { schemaVersion: 1, packages: [], registry: {}, profiles: {}, agents: {} };
|
|
@@ -1781,9 +1874,9 @@ import { rm as rm7 } from "fs/promises";
|
|
|
1781
1874
|
import { join as join16 } from "path";
|
|
1782
1875
|
|
|
1783
1876
|
// src/registry/client.ts
|
|
1784
|
-
import { readFile as readFile9, rm as rm6, stat as
|
|
1877
|
+
import { readFile as readFile9, rm as rm6, stat as stat7 } from "fs/promises";
|
|
1785
1878
|
import { homedir as homedir4 } from "os";
|
|
1786
|
-
import { dirname as dirname8, join as join15, resolve as
|
|
1879
|
+
import { dirname as dirname8, join as join15, resolve as resolve10 } from "path";
|
|
1787
1880
|
import { fileURLToPath } from "url";
|
|
1788
1881
|
|
|
1789
1882
|
// src/model/registry.ts
|
|
@@ -1886,8 +1979,8 @@ var RegistryClient = class {
|
|
|
1886
1979
|
}
|
|
1887
1980
|
const filePath = source.startsWith("file:") ? fileURLToPath(source) : source;
|
|
1888
1981
|
if (await pathExists(filePath)) {
|
|
1889
|
-
const fullPath =
|
|
1890
|
-
const stats = await
|
|
1982
|
+
const fullPath = resolve10(filePath);
|
|
1983
|
+
const stats = await stat7(fullPath);
|
|
1891
1984
|
return readFile9(stats.isDirectory() ? join15(fullPath, "index.json") : fullPath, "utf8");
|
|
1892
1985
|
}
|
|
1893
1986
|
const resolved = await this.git.fetch(await this.git.resolve(source, { cacheRoot: join15(dirname8(this.cachePath), "registry-repos") }));
|
|
@@ -2021,7 +2114,7 @@ function shouldUpdatePackage(pkg, lock) {
|
|
|
2021
2114
|
}
|
|
2022
2115
|
|
|
2023
2116
|
// src/runtime/target.ts
|
|
2024
|
-
import { basename as
|
|
2117
|
+
import { basename as basename8, dirname as dirname9, join as join18, resolve as resolve11 } from "path";
|
|
2025
2118
|
var runtimeMarkers = [
|
|
2026
2119
|
{ adapter: "openclaw", dirs: [".openclaw", ".clawdbot", ".moltbot"] },
|
|
2027
2120
|
{ adapter: "claude", dirs: [".claude"] },
|
|
@@ -2030,9 +2123,9 @@ var runtimeMarkers = [
|
|
|
2030
2123
|
{ adapter: "copilot", dirs: [".github"] }
|
|
2031
2124
|
];
|
|
2032
2125
|
async function resolveRuntimeTarget(request = {}) {
|
|
2033
|
-
const cwd =
|
|
2126
|
+
const cwd = resolve11(request.cwd ?? process.cwd());
|
|
2034
2127
|
if (request.targetRoot) {
|
|
2035
|
-
const targetRoot =
|
|
2128
|
+
const targetRoot = resolve11(request.targetRoot);
|
|
2036
2129
|
return {
|
|
2037
2130
|
adapter: request.adapter ?? "openclaw",
|
|
2038
2131
|
targetRoot,
|
|
@@ -2059,7 +2152,7 @@ async function resolveRuntimeTarget(request = {}) {
|
|
|
2059
2152
|
async function resolveAllRuntimeTargets(request = {}) {
|
|
2060
2153
|
if (request.targetRoot) return [await resolveRuntimeTarget(request)];
|
|
2061
2154
|
if (request.agent) return [await resolveRuntimeTarget(request)];
|
|
2062
|
-
const cwd =
|
|
2155
|
+
const cwd = resolve11(request.cwd ?? process.cwd());
|
|
2063
2156
|
const workspaceRoot = await findWorkspaceRoot(cwd);
|
|
2064
2157
|
const config = await readMergedWorkspaceConfig(workspaceRoot, { globalRoot: request.globalRoot });
|
|
2065
2158
|
const targets = Object.entries(config.agents).map(([name]) => targetFromAgent(name, config, workspaceRoot));
|
|
@@ -2069,12 +2162,12 @@ async function resolveAllRuntimeTargets(request = {}) {
|
|
|
2069
2162
|
return targets;
|
|
2070
2163
|
}
|
|
2071
2164
|
async function detectRuntimeTarget(cwd = process.cwd(), adapterFilter) {
|
|
2072
|
-
const root =
|
|
2165
|
+
const root = resolve11(cwd);
|
|
2073
2166
|
const matches = [];
|
|
2074
2167
|
for (const marker of runtimeMarkers) {
|
|
2075
2168
|
if (adapterFilter && marker.adapter !== adapterFilter) continue;
|
|
2076
2169
|
for (const dir of marker.dirs) {
|
|
2077
|
-
if (
|
|
2170
|
+
if (basename8(root) === dir) {
|
|
2078
2171
|
matches.push({ adapter: marker.adapter, targetRoot: dirname9(root) });
|
|
2079
2172
|
} else if (await pathExists(join18(root, dir))) {
|
|
2080
2173
|
matches.push({ adapter: marker.adapter, targetRoot: root });
|
|
@@ -2110,7 +2203,7 @@ function dedupeTargets(matches) {
|
|
|
2110
2203
|
|
|
2111
2204
|
// src/cli/index.ts
|
|
2112
2205
|
var program = new Command();
|
|
2113
|
-
program.name("agentwheel").description("Multi-runtime agent artifact orchestrator").version("0.
|
|
2206
|
+
program.name("agentwheel").description("Multi-runtime agent artifact orchestrator").version("0.5.0");
|
|
2114
2207
|
program.command("init").argument("[kind]", "workspace or package", "workspace").option("--target-root <path>", "workspace root", process.cwd()).action(async (kind, options) => {
|
|
2115
2208
|
const root = normalizeTargetRoot(options.targetRoot);
|
|
2116
2209
|
if (kind === "package") {
|