@serviceme/devtools-core 0.2.0 → 0.2.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/dist/auth.d.mts +2 -0
- package/dist/auth.d.ts +2 -0
- package/dist/auth.js +89 -45
- package/dist/auth.js.map +1 -1
- package/dist/auth.mjs +89 -45
- package/dist/auth.mjs.map +1 -1
- package/dist/index.d.mts +13 -9
- package/dist/index.d.ts +13 -9
- package/dist/index.js +184 -128
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +183 -127
- package/dist/index.mjs.map +1 -1
- package/dist/skill-linker.js.map +1 -1
- package/dist/skill-linker.mjs.map +1 -1
- package/dist/skill-store.js +1 -8
- package/dist/skill-store.js.map +1 -1
- package/dist/skill-store.mjs +1 -8
- package/dist/skill-store.mjs.map +1 -1
- package/package.json +3 -2
package/dist/index.mjs
CHANGED
|
@@ -842,6 +842,11 @@ var DEFAULT_DEVICE_CODE_URL = "https://github.com/login/device/code";
|
|
|
842
842
|
var DEFAULT_TOKEN_URL = "https://github.com/login/oauth/access_token";
|
|
843
843
|
var DEFAULT_USER_URL = "https://api.github.com/user";
|
|
844
844
|
var DEFAULT_SCOPE = "read:user user:email";
|
|
845
|
+
var DEVICE_CODE_MAX_ATTEMPTS = 3;
|
|
846
|
+
var DEFAULT_DEVICE_CODE_RETRY_BASE_DELAY_MS = 500;
|
|
847
|
+
function isTransientNetworkError(error) {
|
|
848
|
+
return error instanceof TypeError;
|
|
849
|
+
}
|
|
845
850
|
var GitHubAuthProvider = class {
|
|
846
851
|
constructor(config) {
|
|
847
852
|
this.providerId = "github";
|
|
@@ -857,7 +862,8 @@ var GitHubAuthProvider = class {
|
|
|
857
862
|
minPollIntervalMs: config.minPollIntervalMs ?? 1e3,
|
|
858
863
|
maxPollIntervalMs: config.maxPollIntervalMs ?? 15e3,
|
|
859
864
|
maxWaitMs: config.maxWaitMs,
|
|
860
|
-
fetchImpl: config.fetchImpl ?? fetch
|
|
865
|
+
fetchImpl: config.fetchImpl ?? fetch,
|
|
866
|
+
deviceCodeRetryBaseDelayMs: config.deviceCodeRetryBaseDelayMs ?? DEFAULT_DEVICE_CODE_RETRY_BASE_DELAY_MS
|
|
861
867
|
};
|
|
862
868
|
}
|
|
863
869
|
async requestDeviceFlow(opts) {
|
|
@@ -865,29 +871,45 @@ var GitHubAuthProvider = class {
|
|
|
865
871
|
client_id: this.cfg.clientId,
|
|
866
872
|
scope: opts?.scope ?? this.cfg.scope
|
|
867
873
|
});
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
874
|
+
let lastNetworkError;
|
|
875
|
+
for (let attempt = 1; attempt <= DEVICE_CODE_MAX_ATTEMPTS; attempt++) {
|
|
876
|
+
let resp;
|
|
877
|
+
try {
|
|
878
|
+
resp = await this.cfg.fetchImpl(this.cfg.deviceCodeUrl, {
|
|
879
|
+
method: "POST",
|
|
880
|
+
headers: {
|
|
881
|
+
Accept: "application/json",
|
|
882
|
+
"Content-Type": "application/json",
|
|
883
|
+
"User-Agent": "serviceme-core"
|
|
884
|
+
},
|
|
885
|
+
body
|
|
886
|
+
});
|
|
887
|
+
} catch (error) {
|
|
888
|
+
if (!isTransientNetworkError(error) || attempt === DEVICE_CODE_MAX_ATTEMPTS) {
|
|
889
|
+
throw error;
|
|
890
|
+
}
|
|
891
|
+
lastNetworkError = error;
|
|
892
|
+
await sleep(this.cfg.deviceCodeRetryBaseDelayMs * 2 ** (attempt - 1));
|
|
893
|
+
continue;
|
|
894
|
+
}
|
|
895
|
+
if (!resp.ok) {
|
|
896
|
+
throw new Error(
|
|
897
|
+
`GitHub device-code request failed: HTTP ${resp.status}`
|
|
898
|
+
);
|
|
899
|
+
}
|
|
900
|
+
const data = await resp.json();
|
|
901
|
+
if (!data.device_code || !data.user_code || !data.verification_uri) {
|
|
902
|
+
throw new Error("GitHub device-code response missing required fields");
|
|
903
|
+
}
|
|
904
|
+
return {
|
|
905
|
+
provider: this.providerId,
|
|
906
|
+
userCode: data.user_code,
|
|
907
|
+
verificationUrl: data.verification_uri,
|
|
908
|
+
expiresAt: Date.now() + data.expires_in * 1e3,
|
|
909
|
+
message: `Open ${data.verification_uri} and enter ${data.user_code}`
|
|
910
|
+
};
|
|
883
911
|
}
|
|
884
|
-
|
|
885
|
-
provider: this.providerId,
|
|
886
|
-
userCode: data.user_code,
|
|
887
|
-
verificationUrl: data.verification_uri,
|
|
888
|
-
expiresAt: Date.now() + data.expires_in * 1e3,
|
|
889
|
-
message: `Open ${data.verification_uri} and enter ${data.user_code}`
|
|
890
|
-
};
|
|
912
|
+
throw lastNetworkError ?? new Error("GitHub device-code request failed");
|
|
891
913
|
}
|
|
892
914
|
async completeDeviceFlow(deviceCode, shouldContinue) {
|
|
893
915
|
const start = Date.now();
|
|
@@ -905,21 +927,36 @@ var GitHubAuthProvider = class {
|
|
|
905
927
|
if (shouldContinue && !shouldContinue()) {
|
|
906
928
|
throw new Error("GitHub device flow cancelled by caller");
|
|
907
929
|
}
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
930
|
+
let resp;
|
|
931
|
+
try {
|
|
932
|
+
resp = await this.cfg.fetchImpl(this.cfg.tokenUrl, {
|
|
933
|
+
method: "POST",
|
|
934
|
+
headers: {
|
|
935
|
+
Accept: "application/json",
|
|
936
|
+
"Content-Type": "application/json",
|
|
937
|
+
"User-Agent": "serviceme-core"
|
|
938
|
+
},
|
|
939
|
+
body: JSON.stringify({
|
|
940
|
+
client_id: this.cfg.clientId,
|
|
941
|
+
device_code: deviceCode,
|
|
942
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
943
|
+
})
|
|
944
|
+
});
|
|
945
|
+
} catch (error) {
|
|
946
|
+
if (!isTransientNetworkError(error)) {
|
|
947
|
+
throw error;
|
|
948
|
+
}
|
|
949
|
+
pollIntervalMs = Math.min(
|
|
950
|
+
Math.round(pollIntervalMs * 1.5),
|
|
951
|
+
this.cfg.maxPollIntervalMs
|
|
952
|
+
);
|
|
953
|
+
continue;
|
|
954
|
+
}
|
|
921
955
|
if (!resp.ok) {
|
|
922
|
-
pollIntervalMs = Math.min(
|
|
956
|
+
pollIntervalMs = Math.min(
|
|
957
|
+
Math.round(pollIntervalMs * 1.5),
|
|
958
|
+
this.cfg.maxPollIntervalMs
|
|
959
|
+
);
|
|
923
960
|
continue;
|
|
924
961
|
}
|
|
925
962
|
const data = await resp.json();
|
|
@@ -945,7 +982,9 @@ var GitHubAuthProvider = class {
|
|
|
945
982
|
if (data.error === "slow_down") {
|
|
946
983
|
consecutiveSlowDown++;
|
|
947
984
|
pollIntervalMs = Math.min(
|
|
948
|
-
Math.round(
|
|
985
|
+
Math.round(
|
|
986
|
+
pollIntervalMs * 1.5 + Math.min(consecutiveSlowDown * 500, 2e3)
|
|
987
|
+
),
|
|
949
988
|
this.cfg.maxPollIntervalMs
|
|
950
989
|
);
|
|
951
990
|
continue;
|
|
@@ -981,7 +1020,9 @@ var GitHubAuthProvider = class {
|
|
|
981
1020
|
}
|
|
982
1021
|
const data = await resp.json();
|
|
983
1022
|
if (!data.access_token) {
|
|
984
|
-
throw new Error(
|
|
1023
|
+
throw new Error(
|
|
1024
|
+
`GitHub refresh failed: ${data.error ?? "no_access_token"}`
|
|
1025
|
+
);
|
|
985
1026
|
}
|
|
986
1027
|
const rawUser = await this.fetchGitHubUser(data.access_token);
|
|
987
1028
|
const email = await this.resolveEmail(data.access_token, rawUser);
|
|
@@ -1051,13 +1092,16 @@ var GitHubAuthProvider = class {
|
|
|
1051
1092
|
*/
|
|
1052
1093
|
async resolveEmail(token, user) {
|
|
1053
1094
|
if (user.email) return user.email;
|
|
1054
|
-
const emailsResp = await this.cfg.fetchImpl(
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1095
|
+
const emailsResp = await this.cfg.fetchImpl(
|
|
1096
|
+
"https://api.github.com/user/emails",
|
|
1097
|
+
{
|
|
1098
|
+
headers: {
|
|
1099
|
+
Accept: "application/vnd.github+json",
|
|
1100
|
+
Authorization: `Bearer ${token}`,
|
|
1101
|
+
"User-Agent": "serviceme-core"
|
|
1102
|
+
}
|
|
1059
1103
|
}
|
|
1060
|
-
|
|
1104
|
+
);
|
|
1061
1105
|
if (!emailsResp.ok) {
|
|
1062
1106
|
return null;
|
|
1063
1107
|
}
|
|
@@ -2098,14 +2142,7 @@ function extractFrontmatter(raw) {
|
|
|
2098
2142
|
}
|
|
2099
2143
|
return { data, body };
|
|
2100
2144
|
}
|
|
2101
|
-
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
2102
|
-
".git",
|
|
2103
|
-
"node_modules",
|
|
2104
|
-
".vscode",
|
|
2105
|
-
"dist",
|
|
2106
|
-
"build",
|
|
2107
|
-
"out"
|
|
2108
|
-
]);
|
|
2145
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", ".vscode", "dist", "build", "out"]);
|
|
2109
2146
|
var SKIP_TOP_LEVEL_SCAN_DIRS = /* @__PURE__ */ new Set([...SKIP_DIRS, "plugins"]);
|
|
2110
2147
|
|
|
2111
2148
|
// src/drafts/index.ts
|
|
@@ -3376,7 +3413,9 @@ function idFromUrl(url) {
|
|
|
3376
3413
|
const last = trimmed.split("/").pop() ?? "";
|
|
3377
3414
|
const safe = last.replace(/[^a-zA-Z0-9_-]/g, "-").toLowerCase();
|
|
3378
3415
|
if (!SAFE_REPO_ID_PATTERN.test(safe)) {
|
|
3379
|
-
throw new InvalidRepoUrlError(
|
|
3416
|
+
throw new InvalidRepoUrlError(
|
|
3417
|
+
`cannot derive a valid repo id from url: ${url}`
|
|
3418
|
+
);
|
|
3380
3419
|
}
|
|
3381
3420
|
return safe;
|
|
3382
3421
|
}
|
|
@@ -3418,7 +3457,10 @@ var RepoManager = class {
|
|
|
3418
3457
|
if (!repo.enabled) continue;
|
|
3419
3458
|
const localPath = getRepoDir(repo.id);
|
|
3420
3459
|
const exists = await this.pathExists(localPath);
|
|
3421
|
-
if (exists)
|
|
3460
|
+
if (exists) {
|
|
3461
|
+
if (await this.isValidGitRepo(localPath)) continue;
|
|
3462
|
+
await fs8.rm(localPath, { recursive: true, force: true });
|
|
3463
|
+
}
|
|
3422
3464
|
try {
|
|
3423
3465
|
if (!this.skipClone) {
|
|
3424
3466
|
await fs8.mkdir(path11.dirname(localPath), { recursive: true });
|
|
@@ -3453,7 +3495,10 @@ var RepoManager = class {
|
|
|
3453
3495
|
if (!repo) throw new RepoNotInStoreError(repoId);
|
|
3454
3496
|
const localPath = getRepoDir(repoId);
|
|
3455
3497
|
const exists = await this.pathExists(localPath);
|
|
3456
|
-
if (!
|
|
3498
|
+
if (exists && !await this.isValidGitRepo(localPath)) {
|
|
3499
|
+
await fs8.rm(localPath, { recursive: true, force: true });
|
|
3500
|
+
}
|
|
3501
|
+
if (!exists || !await this.pathExists(localPath)) {
|
|
3457
3502
|
await fs8.mkdir(path11.dirname(localPath), { recursive: true });
|
|
3458
3503
|
if (!this.skipClone) {
|
|
3459
3504
|
await this.git.clone(repoId, repo.url, localPath);
|
|
@@ -3519,7 +3564,9 @@ var RepoManager = class {
|
|
|
3519
3564
|
async addUserRepo(input) {
|
|
3520
3565
|
const url = input.url.trim();
|
|
3521
3566
|
if (!/^https?:\/\//.test(url) && !url.startsWith("git@")) {
|
|
3522
|
-
throw new InvalidRepoUrlError(
|
|
3567
|
+
throw new InvalidRepoUrlError(
|
|
3568
|
+
`expected https:// or git@ URL, got: ${url}`
|
|
3569
|
+
);
|
|
3523
3570
|
}
|
|
3524
3571
|
const id = idFromUrl(url);
|
|
3525
3572
|
assertSafeRepoId(id);
|
|
@@ -3601,6 +3648,14 @@ var RepoManager = class {
|
|
|
3601
3648
|
async ensureHome() {
|
|
3602
3649
|
await fs8.mkdir(getServicemeHome(), { recursive: true });
|
|
3603
3650
|
}
|
|
3651
|
+
/**
|
|
3652
|
+
* Returns `true` when `p` contains a `.git` entry — i.e. it is an
|
|
3653
|
+
* initialised git repository and safe to `pull`. Empty directories
|
|
3654
|
+
* (e.g. from an interrupted clone) return `false`.
|
|
3655
|
+
*/
|
|
3656
|
+
async isValidGitRepo(p) {
|
|
3657
|
+
return this.pathExists(path11.join(p, ".git"));
|
|
3658
|
+
}
|
|
3604
3659
|
async pathExists(p) {
|
|
3605
3660
|
try {
|
|
3606
3661
|
await fs8.stat(p);
|
|
@@ -5779,7 +5834,10 @@ function readV1Config(v1Path) {
|
|
|
5779
5834
|
try {
|
|
5780
5835
|
raw = fs19.readFileSync(v1Path, "utf-8");
|
|
5781
5836
|
} catch (err) {
|
|
5782
|
-
return {
|
|
5837
|
+
return {
|
|
5838
|
+
ok: false,
|
|
5839
|
+
error: `read failed: ${err instanceof Error ? err.message : String(err)}`
|
|
5840
|
+
};
|
|
5783
5841
|
}
|
|
5784
5842
|
let parsed;
|
|
5785
5843
|
try {
|
|
@@ -5834,79 +5892,77 @@ function disambiguateName(task, existingNames, workspaceName) {
|
|
|
5834
5892
|
existingNames.add(candidate);
|
|
5835
5893
|
return { ...task, name: candidate };
|
|
5836
5894
|
}
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
const
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
5858
|
-
|
|
5859
|
-
if (!v1.ok) {
|
|
5860
|
-
failures.push({
|
|
5861
|
-
workspacePath,
|
|
5862
|
-
v1Path,
|
|
5863
|
-
error: v1.error,
|
|
5864
|
-
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
5865
|
-
});
|
|
5866
|
-
safeDelete(v1Path);
|
|
5867
|
-
continue;
|
|
5868
|
-
}
|
|
5869
|
-
let workspaceRef;
|
|
5870
|
-
try {
|
|
5871
|
-
workspaceRef = await probe(workspacePath);
|
|
5872
|
-
} catch (err) {
|
|
5873
|
-
failures.push({
|
|
5874
|
-
workspacePath,
|
|
5875
|
-
v1Path,
|
|
5876
|
-
error: `probe failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
5877
|
-
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
5878
|
-
});
|
|
5879
|
-
safeDelete(v1Path);
|
|
5880
|
-
continue;
|
|
5881
|
-
}
|
|
5882
|
-
const { config, issues: taskIssues } = migrateV1ToV22(v1.config, workspaceRef);
|
|
5883
|
-
issues.push(...taskIssues);
|
|
5884
|
-
const newTasks = config.tasks.map((t) => {
|
|
5885
|
-
if (existingNames.has(t.name)) {
|
|
5886
|
-
conflicts.push(t.name);
|
|
5887
|
-
return disambiguateName(t, existingNames, workspaceRef.name);
|
|
5888
|
-
}
|
|
5889
|
-
existingNames.add(t.name);
|
|
5890
|
-
return t;
|
|
5895
|
+
async function migrateToGlobal(options) {
|
|
5896
|
+
const globalConfigPath = options.globalConfigPath ?? getScheduledTasksConfigPath();
|
|
5897
|
+
const migrationFailuresPath = options.migrationFailuresPath ?? getMigrationFailuresPath();
|
|
5898
|
+
const probe = options.probe ?? defaultProbe;
|
|
5899
|
+
const existing = readJsonFile(globalConfigPath);
|
|
5900
|
+
const baseConfig = existing && existing.version === 2 ? existing : { version: 2, tasks: [] };
|
|
5901
|
+
const existingNames = new Set(baseConfig.tasks.map((t) => t.name));
|
|
5902
|
+
const priorFailures = readJsonFile(migrationFailuresPath) ?? [];
|
|
5903
|
+
const failures = [...priorFailures];
|
|
5904
|
+
let migrated = 0;
|
|
5905
|
+
const conflicts = [];
|
|
5906
|
+
const issues = [];
|
|
5907
|
+
for (const workspacePath of options.workspacePaths) {
|
|
5908
|
+
const v1Path = path20.join(workspacePath, WORKSPACE_DIR, V1_FILENAME);
|
|
5909
|
+
if (!fs19.existsSync(v1Path)) continue;
|
|
5910
|
+
const v1 = readV1Config(v1Path);
|
|
5911
|
+
if (!v1.ok) {
|
|
5912
|
+
failures.push({
|
|
5913
|
+
workspacePath,
|
|
5914
|
+
v1Path,
|
|
5915
|
+
error: v1.error,
|
|
5916
|
+
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
5891
5917
|
});
|
|
5892
|
-
baseConfig.tasks.push(...newTasks);
|
|
5893
|
-
migrated += 1;
|
|
5894
5918
|
safeDelete(v1Path);
|
|
5919
|
+
continue;
|
|
5895
5920
|
}
|
|
5896
|
-
|
|
5897
|
-
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
5921
|
+
let workspaceRef;
|
|
5922
|
+
try {
|
|
5923
|
+
workspaceRef = await probe(workspacePath);
|
|
5924
|
+
} catch (err) {
|
|
5925
|
+
failures.push({
|
|
5926
|
+
workspacePath,
|
|
5927
|
+
v1Path,
|
|
5928
|
+
error: `probe failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
5929
|
+
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
5930
|
+
});
|
|
5931
|
+
safeDelete(v1Path);
|
|
5932
|
+
continue;
|
|
5906
5933
|
}
|
|
5907
|
-
|
|
5934
|
+
const { config, issues: taskIssues } = migrateV1ToV22(v1.config, workspaceRef);
|
|
5935
|
+
issues.push(...taskIssues);
|
|
5936
|
+
const newTasks = config.tasks.map((t) => {
|
|
5937
|
+
if (existingNames.has(t.name)) {
|
|
5938
|
+
conflicts.push(t.name);
|
|
5939
|
+
return disambiguateName(t, existingNames, workspaceRef.name);
|
|
5940
|
+
}
|
|
5941
|
+
existingNames.add(t.name);
|
|
5942
|
+
return t;
|
|
5943
|
+
});
|
|
5944
|
+
baseConfig.tasks.push(...newTasks);
|
|
5945
|
+
migrated += 1;
|
|
5946
|
+
safeDelete(v1Path);
|
|
5908
5947
|
}
|
|
5909
|
-
|
|
5948
|
+
if (migrated > 0) {
|
|
5949
|
+
ensureDir(globalConfigPath);
|
|
5950
|
+
const tmp = `${globalConfigPath}.tmp`;
|
|
5951
|
+
fs19.writeFileSync(tmp, JSON.stringify(baseConfig, null, " "), "utf-8");
|
|
5952
|
+
fs19.renameSync(tmp, globalConfigPath);
|
|
5953
|
+
}
|
|
5954
|
+
if (failures.length > priorFailures.length) {
|
|
5955
|
+
writeJsonFile(migrationFailuresPath, failures);
|
|
5956
|
+
} else if (failures.length === 0 && priorFailures.length > 0) {
|
|
5957
|
+
safeDelete(migrationFailuresPath);
|
|
5958
|
+
}
|
|
5959
|
+
return {
|
|
5960
|
+
migrated,
|
|
5961
|
+
failed: failures.length - priorFailures.length,
|
|
5962
|
+
conflicts,
|
|
5963
|
+
issues
|
|
5964
|
+
};
|
|
5965
|
+
}
|
|
5910
5966
|
|
|
5911
5967
|
// src/scheduled-tasks/workspace-probe/WorkspaceProbe.ts
|
|
5912
5968
|
import { spawn as spawn5 } from "child_process";
|
|
@@ -6808,7 +6864,6 @@ export {
|
|
|
6808
6864
|
MIGRATION_FAILURES_FILENAME,
|
|
6809
6865
|
MicrosoftAuthProvider,
|
|
6810
6866
|
MicrosoftProviderNotHostedError,
|
|
6811
|
-
MigrateToGlobal,
|
|
6812
6867
|
NodeGitSpawner,
|
|
6813
6868
|
PROFILES_JSON_FILENAME,
|
|
6814
6869
|
PidManager,
|
|
@@ -6907,6 +6962,7 @@ export {
|
|
|
6907
6962
|
isUserRepo,
|
|
6908
6963
|
matchesCron,
|
|
6909
6964
|
mergeWithDefaults,
|
|
6965
|
+
migrateToGlobal,
|
|
6910
6966
|
moveFiles,
|
|
6911
6967
|
narrowRepoConfig,
|
|
6912
6968
|
noopLogger,
|