@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.d.ts
CHANGED
|
@@ -1318,6 +1318,12 @@ declare class RepoManager {
|
|
|
1318
1318
|
hasLocalClone(repoId: string): Promise<boolean>;
|
|
1319
1319
|
/** Force-create the SERVICEME home directory tree (idempotent). */
|
|
1320
1320
|
ensureHome(): Promise<void>;
|
|
1321
|
+
/**
|
|
1322
|
+
* Returns `true` when `p` contains a `.git` entry — i.e. it is an
|
|
1323
|
+
* initialised git repository and safe to `pull`. Empty directories
|
|
1324
|
+
* (e.g. from an interrupted clone) return `false`.
|
|
1325
|
+
*/
|
|
1326
|
+
private isValidGitRepo;
|
|
1321
1327
|
private pathExists;
|
|
1322
1328
|
}
|
|
1323
1329
|
|
|
@@ -1654,14 +1660,12 @@ interface MigrationResult {
|
|
|
1654
1660
|
/** Soft issues surfaced by migrateV1ToV2 (e.g. unknown taskType). */
|
|
1655
1661
|
issues: string[];
|
|
1656
1662
|
}
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
static run(options: MigrateToGlobalOptions): Promise<MigrationResult>;
|
|
1664
|
-
}
|
|
1663
|
+
/**
|
|
1664
|
+
* Run the migration. Idempotent: each v1 file is deleted after processing,
|
|
1665
|
+
* so re-running this on a partially-migrated state is safe (the
|
|
1666
|
+
* already-migrated files no longer exist; only the remainder is processed).
|
|
1667
|
+
*/
|
|
1668
|
+
declare function migrateToGlobal(options: MigrateToGlobalOptions): Promise<MigrationResult>;
|
|
1665
1669
|
|
|
1666
1670
|
interface TaskEventListener {
|
|
1667
1671
|
onStarted(params: TaskStartedEventParams): void;
|
|
@@ -1816,4 +1820,4 @@ declare class SkillStore {
|
|
|
1816
1820
|
declare const unzipFile: (zipPath: string, dest: string) => Promise<void>;
|
|
1817
1821
|
declare const moveFiles: (sourceDir: string, destDir: string, overwrite?: boolean) => Promise<void>;
|
|
1818
1822
|
|
|
1819
|
-
export { AGENT_DRAFTS_SUBDIR, type AddUserRepoInput, type AddUserRepoResult, type AgentCatalog, AgentCatalogClient, type AgentCatalogClientOptions, type AgentDownloadFile, type AgentInstallScope, type AgentMutateResult, AgentReconciler, type AgentReconcilerDependencies, AgentStore, type AgentStoreFileSystem, type AgentStoreOptions, type AgentsStateFile, type AnyRepoConfig, type AppendLogInput, type BootstrapPhase5Result, CACHE_SUBDIR, CREDENTIALS_CONFIG_FILENAME, CannotRemoveDefaultRepoError, type CreateTaskInput, FIXED_ADDED_AT as DEFAULT_REPO_FIXED_ADDED_AT, DEFAULT_REPO_ID, DEVICE_JSON_FILENAME, DRAFTS_SUBDIR, DaemonLogger, type DefaultRepoConfig, type DraftDetail, DraftNotFoundError, type DraftSummary, DraftsError, DraftsStore, type EditTaskInput, EnvironmentInspector, type EnvironmentInspectorOptions, type ExecutorResult, type FsWatcherCallbacks, type FsWatcherHandle, GitClient, GithubCopilotCliExecutor, HttpRequestExecutor, ImageTools, type InstalledAgent, InvalidDraftError, InvalidRepoUrlError, type IsoTimestamp, type JsonTools, KNOWN_WORKSPACES_FILENAME, type LoadResult, MACHINE_ID_FILENAME, MIGRATION_FAILURES_FILENAME,
|
|
1823
|
+
export { AGENT_DRAFTS_SUBDIR, type AddUserRepoInput, type AddUserRepoResult, type AgentCatalog, AgentCatalogClient, type AgentCatalogClientOptions, type AgentDownloadFile, type AgentInstallScope, type AgentMutateResult, AgentReconciler, type AgentReconcilerDependencies, AgentStore, type AgentStoreFileSystem, type AgentStoreOptions, type AgentsStateFile, type AnyRepoConfig, type AppendLogInput, type BootstrapPhase5Result, CACHE_SUBDIR, CREDENTIALS_CONFIG_FILENAME, CannotRemoveDefaultRepoError, type CreateTaskInput, FIXED_ADDED_AT as DEFAULT_REPO_FIXED_ADDED_AT, DEFAULT_REPO_ID, DEVICE_JSON_FILENAME, DRAFTS_SUBDIR, DaemonLogger, type DefaultRepoConfig, type DraftDetail, DraftNotFoundError, type DraftSummary, DraftsError, DraftsStore, type EditTaskInput, EnvironmentInspector, type EnvironmentInspectorOptions, type ExecutorResult, type FsWatcherCallbacks, type FsWatcherHandle, GitClient, GithubCopilotCliExecutor, HttpRequestExecutor, ImageTools, type InstalledAgent, InvalidDraftError, InvalidRepoUrlError, type IsoTimestamp, type JsonTools, KNOWN_WORKSPACES_FILENAME, type LoadResult, MACHINE_ID_FILENAME, MIGRATION_FAILURES_FILENAME, type MigrateToGlobalOptions, type MigrationResult, type OutputEventCallback, PROFILES_JSON_FILENAME, PidManager, type ProbeError, type ProbeOptions, type ProbeResult, ProjectTools, REPOS_CONFIG_FILENAME, REPOS_SUBDIR, RepoAlreadyExistsError, RepoCloneConflictError, type RepoConfig, RepoManager, RepoManagerError, type RepoManagerOptions, RepoNotFoundError, RepoNotInStoreError, type RepoSource, type ReposFile, type ReposFileInput, ReposLoader, type ReposLoaderFileSystem, type ReposLoaderOptions, ReposStore, type ReposStoreChange, type ReposStoreFileSystem, type ReposStoreListener, type ReposStoreOptions, SAFE_REPO_ID_PATTERN, SCHEDULED_TASKS_CONFIG_FILENAME, SCHEDULED_TASKS_LOG_FILENAME, SCHEDULER_LOCK_FILENAME, SCHEDULER_LOG_FILENAME, SCHEDULER_PID_FILENAME, SERVICEME_DIR_NAME, SERVICEME_HOME_ENV, SKILL_DRAFTS_SUBDIR, type SaveDraftOptions, SchedulerDaemon, SchedulerDaemonV2, type SchedulerDaemonV2Options, type ServiceMeLogger, ShellExecutor, type SkillCatalog, SkillCatalogClient, type SkillCatalogClientOptions, type SkillDownloadFile, type SkillMutateResult, SkillReconciler, type SkillReconcilerDependencies, SkillStore, type SkillStoreFileSystem, type SkillStoreOptions, type StreamingExecutorHandle, type StreamingTaskExecutor, type SyncReport, type SyncStatus, TOOLBOX_JSON_FILENAME, TOOL_RISK_MAP, TaskConfigManager, type TaskEventListener, TaskExecutionEngine, type TaskExecutor, TaskLogManager, type UserRepoConfig, WorkspaceProbe, repoSchema as anyRepoConfigSchema, assertSafeRepoId, bootstrapDefaults, bootstrapPhase5Placeholders, buildDefaultReposFile, copilotDoctor, copilotPrompt, createConsoleLogger, createCopilotAuthRequiredError, createCopilotNotInstalledError, createImageTools, createJsonTools, createProjectTools, createReposStore, defaultRepoSchema as defaultRepoConfigSchema, ensureDefaultsInstalled, generateDraftId, getAgentDraftsDir, getAllDefaultRepoConfigs, getCacheDir, getCredentialsConfigPath, getDefaultRepoConfig, getDeviceJsonPath, getDraftsDir, getExecutor, getHomeDir, getHomePlatform, getKnownWorkspacesPath, getMachineIdPath, getMigrationFailuresPath, getProfilesJsonPath, getRepoCacheDir, getRepoDir, getReposConfigPath, getReposDir, getScheduledTasksConfigPath, getScheduledTasksLogPath, getSchedulerLockPath, getSchedulerLogPath, getSchedulerPidPath, getServicemeHome, getSkillDraftsDir, getToolboxJsonPath, isCopilotAuthenticated, isDefaultRepo, isStreamingTaskExecutor, isUserRepo, matchesCron, migrateToGlobal, moveFiles, narrowRepoConfig, noopLogger, parseAgentToolPermissions, parseIntervalMs, reposFileSchema, resetUserHomeOverrides, resolveDraftDir, resolveTaskExecutionPayload, setUserHomeOverrides, unzipFile, userRepoSchema as userRepoConfigSchema, validateReposFile, validateTaskPayload };
|
package/dist/index.js
CHANGED
|
@@ -75,7 +75,6 @@ __export(src_exports, {
|
|
|
75
75
|
MIGRATION_FAILURES_FILENAME: () => MIGRATION_FAILURES_FILENAME,
|
|
76
76
|
MicrosoftAuthProvider: () => MicrosoftAuthProvider,
|
|
77
77
|
MicrosoftProviderNotHostedError: () => MicrosoftProviderNotHostedError,
|
|
78
|
-
MigrateToGlobal: () => MigrateToGlobal,
|
|
79
78
|
NodeGitSpawner: () => NodeGitSpawner,
|
|
80
79
|
PROFILES_JSON_FILENAME: () => PROFILES_JSON_FILENAME,
|
|
81
80
|
PidManager: () => PidManager,
|
|
@@ -174,6 +173,7 @@ __export(src_exports, {
|
|
|
174
173
|
isUserRepo: () => isUserRepo,
|
|
175
174
|
matchesCron: () => matchesCron,
|
|
176
175
|
mergeWithDefaults: () => mergeWithDefaults,
|
|
176
|
+
migrateToGlobal: () => migrateToGlobal,
|
|
177
177
|
moveFiles: () => moveFiles,
|
|
178
178
|
narrowRepoConfig: () => narrowRepoConfig,
|
|
179
179
|
noopLogger: () => noopLogger,
|
|
@@ -1035,6 +1035,11 @@ var DEFAULT_DEVICE_CODE_URL = "https://github.com/login/device/code";
|
|
|
1035
1035
|
var DEFAULT_TOKEN_URL = "https://github.com/login/oauth/access_token";
|
|
1036
1036
|
var DEFAULT_USER_URL = "https://api.github.com/user";
|
|
1037
1037
|
var DEFAULT_SCOPE = "read:user user:email";
|
|
1038
|
+
var DEVICE_CODE_MAX_ATTEMPTS = 3;
|
|
1039
|
+
var DEFAULT_DEVICE_CODE_RETRY_BASE_DELAY_MS = 500;
|
|
1040
|
+
function isTransientNetworkError(error) {
|
|
1041
|
+
return error instanceof TypeError;
|
|
1042
|
+
}
|
|
1038
1043
|
var GitHubAuthProvider = class {
|
|
1039
1044
|
constructor(config) {
|
|
1040
1045
|
this.providerId = "github";
|
|
@@ -1050,7 +1055,8 @@ var GitHubAuthProvider = class {
|
|
|
1050
1055
|
minPollIntervalMs: config.minPollIntervalMs ?? 1e3,
|
|
1051
1056
|
maxPollIntervalMs: config.maxPollIntervalMs ?? 15e3,
|
|
1052
1057
|
maxWaitMs: config.maxWaitMs,
|
|
1053
|
-
fetchImpl: config.fetchImpl ?? fetch
|
|
1058
|
+
fetchImpl: config.fetchImpl ?? fetch,
|
|
1059
|
+
deviceCodeRetryBaseDelayMs: config.deviceCodeRetryBaseDelayMs ?? DEFAULT_DEVICE_CODE_RETRY_BASE_DELAY_MS
|
|
1054
1060
|
};
|
|
1055
1061
|
}
|
|
1056
1062
|
async requestDeviceFlow(opts) {
|
|
@@ -1058,29 +1064,45 @@ var GitHubAuthProvider = class {
|
|
|
1058
1064
|
client_id: this.cfg.clientId,
|
|
1059
1065
|
scope: opts?.scope ?? this.cfg.scope
|
|
1060
1066
|
});
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1067
|
+
let lastNetworkError;
|
|
1068
|
+
for (let attempt = 1; attempt <= DEVICE_CODE_MAX_ATTEMPTS; attempt++) {
|
|
1069
|
+
let resp;
|
|
1070
|
+
try {
|
|
1071
|
+
resp = await this.cfg.fetchImpl(this.cfg.deviceCodeUrl, {
|
|
1072
|
+
method: "POST",
|
|
1073
|
+
headers: {
|
|
1074
|
+
Accept: "application/json",
|
|
1075
|
+
"Content-Type": "application/json",
|
|
1076
|
+
"User-Agent": "serviceme-core"
|
|
1077
|
+
},
|
|
1078
|
+
body
|
|
1079
|
+
});
|
|
1080
|
+
} catch (error) {
|
|
1081
|
+
if (!isTransientNetworkError(error) || attempt === DEVICE_CODE_MAX_ATTEMPTS) {
|
|
1082
|
+
throw error;
|
|
1083
|
+
}
|
|
1084
|
+
lastNetworkError = error;
|
|
1085
|
+
await sleep(this.cfg.deviceCodeRetryBaseDelayMs * 2 ** (attempt - 1));
|
|
1086
|
+
continue;
|
|
1087
|
+
}
|
|
1088
|
+
if (!resp.ok) {
|
|
1089
|
+
throw new Error(
|
|
1090
|
+
`GitHub device-code request failed: HTTP ${resp.status}`
|
|
1091
|
+
);
|
|
1092
|
+
}
|
|
1093
|
+
const data = await resp.json();
|
|
1094
|
+
if (!data.device_code || !data.user_code || !data.verification_uri) {
|
|
1095
|
+
throw new Error("GitHub device-code response missing required fields");
|
|
1096
|
+
}
|
|
1097
|
+
return {
|
|
1098
|
+
provider: this.providerId,
|
|
1099
|
+
userCode: data.user_code,
|
|
1100
|
+
verificationUrl: data.verification_uri,
|
|
1101
|
+
expiresAt: Date.now() + data.expires_in * 1e3,
|
|
1102
|
+
message: `Open ${data.verification_uri} and enter ${data.user_code}`
|
|
1103
|
+
};
|
|
1076
1104
|
}
|
|
1077
|
-
|
|
1078
|
-
provider: this.providerId,
|
|
1079
|
-
userCode: data.user_code,
|
|
1080
|
-
verificationUrl: data.verification_uri,
|
|
1081
|
-
expiresAt: Date.now() + data.expires_in * 1e3,
|
|
1082
|
-
message: `Open ${data.verification_uri} and enter ${data.user_code}`
|
|
1083
|
-
};
|
|
1105
|
+
throw lastNetworkError ?? new Error("GitHub device-code request failed");
|
|
1084
1106
|
}
|
|
1085
1107
|
async completeDeviceFlow(deviceCode, shouldContinue) {
|
|
1086
1108
|
const start = Date.now();
|
|
@@ -1098,21 +1120,36 @@ var GitHubAuthProvider = class {
|
|
|
1098
1120
|
if (shouldContinue && !shouldContinue()) {
|
|
1099
1121
|
throw new Error("GitHub device flow cancelled by caller");
|
|
1100
1122
|
}
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1123
|
+
let resp;
|
|
1124
|
+
try {
|
|
1125
|
+
resp = await this.cfg.fetchImpl(this.cfg.tokenUrl, {
|
|
1126
|
+
method: "POST",
|
|
1127
|
+
headers: {
|
|
1128
|
+
Accept: "application/json",
|
|
1129
|
+
"Content-Type": "application/json",
|
|
1130
|
+
"User-Agent": "serviceme-core"
|
|
1131
|
+
},
|
|
1132
|
+
body: JSON.stringify({
|
|
1133
|
+
client_id: this.cfg.clientId,
|
|
1134
|
+
device_code: deviceCode,
|
|
1135
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
1136
|
+
})
|
|
1137
|
+
});
|
|
1138
|
+
} catch (error) {
|
|
1139
|
+
if (!isTransientNetworkError(error)) {
|
|
1140
|
+
throw error;
|
|
1141
|
+
}
|
|
1142
|
+
pollIntervalMs = Math.min(
|
|
1143
|
+
Math.round(pollIntervalMs * 1.5),
|
|
1144
|
+
this.cfg.maxPollIntervalMs
|
|
1145
|
+
);
|
|
1146
|
+
continue;
|
|
1147
|
+
}
|
|
1114
1148
|
if (!resp.ok) {
|
|
1115
|
-
pollIntervalMs = Math.min(
|
|
1149
|
+
pollIntervalMs = Math.min(
|
|
1150
|
+
Math.round(pollIntervalMs * 1.5),
|
|
1151
|
+
this.cfg.maxPollIntervalMs
|
|
1152
|
+
);
|
|
1116
1153
|
continue;
|
|
1117
1154
|
}
|
|
1118
1155
|
const data = await resp.json();
|
|
@@ -1138,7 +1175,9 @@ var GitHubAuthProvider = class {
|
|
|
1138
1175
|
if (data.error === "slow_down") {
|
|
1139
1176
|
consecutiveSlowDown++;
|
|
1140
1177
|
pollIntervalMs = Math.min(
|
|
1141
|
-
Math.round(
|
|
1178
|
+
Math.round(
|
|
1179
|
+
pollIntervalMs * 1.5 + Math.min(consecutiveSlowDown * 500, 2e3)
|
|
1180
|
+
),
|
|
1142
1181
|
this.cfg.maxPollIntervalMs
|
|
1143
1182
|
);
|
|
1144
1183
|
continue;
|
|
@@ -1174,7 +1213,9 @@ var GitHubAuthProvider = class {
|
|
|
1174
1213
|
}
|
|
1175
1214
|
const data = await resp.json();
|
|
1176
1215
|
if (!data.access_token) {
|
|
1177
|
-
throw new Error(
|
|
1216
|
+
throw new Error(
|
|
1217
|
+
`GitHub refresh failed: ${data.error ?? "no_access_token"}`
|
|
1218
|
+
);
|
|
1178
1219
|
}
|
|
1179
1220
|
const rawUser = await this.fetchGitHubUser(data.access_token);
|
|
1180
1221
|
const email = await this.resolveEmail(data.access_token, rawUser);
|
|
@@ -1244,13 +1285,16 @@ var GitHubAuthProvider = class {
|
|
|
1244
1285
|
*/
|
|
1245
1286
|
async resolveEmail(token, user) {
|
|
1246
1287
|
if (user.email) return user.email;
|
|
1247
|
-
const emailsResp = await this.cfg.fetchImpl(
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1288
|
+
const emailsResp = await this.cfg.fetchImpl(
|
|
1289
|
+
"https://api.github.com/user/emails",
|
|
1290
|
+
{
|
|
1291
|
+
headers: {
|
|
1292
|
+
Accept: "application/vnd.github+json",
|
|
1293
|
+
Authorization: `Bearer ${token}`,
|
|
1294
|
+
"User-Agent": "serviceme-core"
|
|
1295
|
+
}
|
|
1252
1296
|
}
|
|
1253
|
-
|
|
1297
|
+
);
|
|
1254
1298
|
if (!emailsResp.ok) {
|
|
1255
1299
|
return null;
|
|
1256
1300
|
}
|
|
@@ -2284,14 +2328,7 @@ function extractFrontmatter(raw) {
|
|
|
2284
2328
|
}
|
|
2285
2329
|
return { data, body };
|
|
2286
2330
|
}
|
|
2287
|
-
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
2288
|
-
".git",
|
|
2289
|
-
"node_modules",
|
|
2290
|
-
".vscode",
|
|
2291
|
-
"dist",
|
|
2292
|
-
"build",
|
|
2293
|
-
"out"
|
|
2294
|
-
]);
|
|
2331
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", ".vscode", "dist", "build", "out"]);
|
|
2295
2332
|
var SKIP_TOP_LEVEL_SCAN_DIRS = /* @__PURE__ */ new Set([...SKIP_DIRS, "plugins"]);
|
|
2296
2333
|
|
|
2297
2334
|
// src/drafts/index.ts
|
|
@@ -3552,7 +3589,9 @@ function idFromUrl(url) {
|
|
|
3552
3589
|
const last = trimmed.split("/").pop() ?? "";
|
|
3553
3590
|
const safe = last.replace(/[^a-zA-Z0-9_-]/g, "-").toLowerCase();
|
|
3554
3591
|
if (!SAFE_REPO_ID_PATTERN.test(safe)) {
|
|
3555
|
-
throw new InvalidRepoUrlError(
|
|
3592
|
+
throw new InvalidRepoUrlError(
|
|
3593
|
+
`cannot derive a valid repo id from url: ${url}`
|
|
3594
|
+
);
|
|
3556
3595
|
}
|
|
3557
3596
|
return safe;
|
|
3558
3597
|
}
|
|
@@ -3594,7 +3633,10 @@ var RepoManager = class {
|
|
|
3594
3633
|
if (!repo.enabled) continue;
|
|
3595
3634
|
const localPath = getRepoDir(repo.id);
|
|
3596
3635
|
const exists = await this.pathExists(localPath);
|
|
3597
|
-
if (exists)
|
|
3636
|
+
if (exists) {
|
|
3637
|
+
if (await this.isValidGitRepo(localPath)) continue;
|
|
3638
|
+
await fs8.rm(localPath, { recursive: true, force: true });
|
|
3639
|
+
}
|
|
3598
3640
|
try {
|
|
3599
3641
|
if (!this.skipClone) {
|
|
3600
3642
|
await fs8.mkdir(path11.dirname(localPath), { recursive: true });
|
|
@@ -3629,7 +3671,10 @@ var RepoManager = class {
|
|
|
3629
3671
|
if (!repo) throw new RepoNotInStoreError(repoId);
|
|
3630
3672
|
const localPath = getRepoDir(repoId);
|
|
3631
3673
|
const exists = await this.pathExists(localPath);
|
|
3632
|
-
if (!
|
|
3674
|
+
if (exists && !await this.isValidGitRepo(localPath)) {
|
|
3675
|
+
await fs8.rm(localPath, { recursive: true, force: true });
|
|
3676
|
+
}
|
|
3677
|
+
if (!exists || !await this.pathExists(localPath)) {
|
|
3633
3678
|
await fs8.mkdir(path11.dirname(localPath), { recursive: true });
|
|
3634
3679
|
if (!this.skipClone) {
|
|
3635
3680
|
await this.git.clone(repoId, repo.url, localPath);
|
|
@@ -3695,7 +3740,9 @@ var RepoManager = class {
|
|
|
3695
3740
|
async addUserRepo(input) {
|
|
3696
3741
|
const url = input.url.trim();
|
|
3697
3742
|
if (!/^https?:\/\//.test(url) && !url.startsWith("git@")) {
|
|
3698
|
-
throw new InvalidRepoUrlError(
|
|
3743
|
+
throw new InvalidRepoUrlError(
|
|
3744
|
+
`expected https:// or git@ URL, got: ${url}`
|
|
3745
|
+
);
|
|
3699
3746
|
}
|
|
3700
3747
|
const id = idFromUrl(url);
|
|
3701
3748
|
assertSafeRepoId(id);
|
|
@@ -3777,6 +3824,14 @@ var RepoManager = class {
|
|
|
3777
3824
|
async ensureHome() {
|
|
3778
3825
|
await fs8.mkdir(getServicemeHome(), { recursive: true });
|
|
3779
3826
|
}
|
|
3827
|
+
/**
|
|
3828
|
+
* Returns `true` when `p` contains a `.git` entry — i.e. it is an
|
|
3829
|
+
* initialised git repository and safe to `pull`. Empty directories
|
|
3830
|
+
* (e.g. from an interrupted clone) return `false`.
|
|
3831
|
+
*/
|
|
3832
|
+
async isValidGitRepo(p) {
|
|
3833
|
+
return this.pathExists(path11.join(p, ".git"));
|
|
3834
|
+
}
|
|
3780
3835
|
async pathExists(p) {
|
|
3781
3836
|
try {
|
|
3782
3837
|
await fs8.stat(p);
|
|
@@ -5949,7 +6004,10 @@ function readV1Config(v1Path) {
|
|
|
5949
6004
|
try {
|
|
5950
6005
|
raw = fs19.readFileSync(v1Path, "utf-8");
|
|
5951
6006
|
} catch (err) {
|
|
5952
|
-
return {
|
|
6007
|
+
return {
|
|
6008
|
+
ok: false,
|
|
6009
|
+
error: `read failed: ${err instanceof Error ? err.message : String(err)}`
|
|
6010
|
+
};
|
|
5953
6011
|
}
|
|
5954
6012
|
let parsed;
|
|
5955
6013
|
try {
|
|
@@ -6004,79 +6062,77 @@ function disambiguateName(task, existingNames, workspaceName) {
|
|
|
6004
6062
|
existingNames.add(candidate);
|
|
6005
6063
|
return { ...task, name: candidate };
|
|
6006
6064
|
}
|
|
6007
|
-
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
|
|
6011
|
-
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
|
|
6015
|
-
|
|
6016
|
-
|
|
6017
|
-
|
|
6018
|
-
|
|
6019
|
-
|
|
6020
|
-
const
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
if (!v1.ok) {
|
|
6030
|
-
failures.push({
|
|
6031
|
-
workspacePath,
|
|
6032
|
-
v1Path,
|
|
6033
|
-
error: v1.error,
|
|
6034
|
-
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
6035
|
-
});
|
|
6036
|
-
safeDelete(v1Path);
|
|
6037
|
-
continue;
|
|
6038
|
-
}
|
|
6039
|
-
let workspaceRef;
|
|
6040
|
-
try {
|
|
6041
|
-
workspaceRef = await probe(workspacePath);
|
|
6042
|
-
} catch (err) {
|
|
6043
|
-
failures.push({
|
|
6044
|
-
workspacePath,
|
|
6045
|
-
v1Path,
|
|
6046
|
-
error: `probe failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
6047
|
-
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
6048
|
-
});
|
|
6049
|
-
safeDelete(v1Path);
|
|
6050
|
-
continue;
|
|
6051
|
-
}
|
|
6052
|
-
const { config, issues: taskIssues } = (0, import_devtools_protocol9.migrateV1ToV2)(v1.config, workspaceRef);
|
|
6053
|
-
issues.push(...taskIssues);
|
|
6054
|
-
const newTasks = config.tasks.map((t) => {
|
|
6055
|
-
if (existingNames.has(t.name)) {
|
|
6056
|
-
conflicts.push(t.name);
|
|
6057
|
-
return disambiguateName(t, existingNames, workspaceRef.name);
|
|
6058
|
-
}
|
|
6059
|
-
existingNames.add(t.name);
|
|
6060
|
-
return t;
|
|
6065
|
+
async function migrateToGlobal(options) {
|
|
6066
|
+
const globalConfigPath = options.globalConfigPath ?? getScheduledTasksConfigPath();
|
|
6067
|
+
const migrationFailuresPath = options.migrationFailuresPath ?? getMigrationFailuresPath();
|
|
6068
|
+
const probe = options.probe ?? defaultProbe;
|
|
6069
|
+
const existing = readJsonFile(globalConfigPath);
|
|
6070
|
+
const baseConfig = existing && existing.version === 2 ? existing : { version: 2, tasks: [] };
|
|
6071
|
+
const existingNames = new Set(baseConfig.tasks.map((t) => t.name));
|
|
6072
|
+
const priorFailures = readJsonFile(migrationFailuresPath) ?? [];
|
|
6073
|
+
const failures = [...priorFailures];
|
|
6074
|
+
let migrated = 0;
|
|
6075
|
+
const conflicts = [];
|
|
6076
|
+
const issues = [];
|
|
6077
|
+
for (const workspacePath of options.workspacePaths) {
|
|
6078
|
+
const v1Path = path20.join(workspacePath, WORKSPACE_DIR, V1_FILENAME);
|
|
6079
|
+
if (!fs19.existsSync(v1Path)) continue;
|
|
6080
|
+
const v1 = readV1Config(v1Path);
|
|
6081
|
+
if (!v1.ok) {
|
|
6082
|
+
failures.push({
|
|
6083
|
+
workspacePath,
|
|
6084
|
+
v1Path,
|
|
6085
|
+
error: v1.error,
|
|
6086
|
+
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
6061
6087
|
});
|
|
6062
|
-
baseConfig.tasks.push(...newTasks);
|
|
6063
|
-
migrated += 1;
|
|
6064
6088
|
safeDelete(v1Path);
|
|
6089
|
+
continue;
|
|
6065
6090
|
}
|
|
6066
|
-
|
|
6067
|
-
|
|
6068
|
-
|
|
6069
|
-
|
|
6070
|
-
|
|
6071
|
-
|
|
6072
|
-
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
|
|
6091
|
+
let workspaceRef;
|
|
6092
|
+
try {
|
|
6093
|
+
workspaceRef = await probe(workspacePath);
|
|
6094
|
+
} catch (err) {
|
|
6095
|
+
failures.push({
|
|
6096
|
+
workspacePath,
|
|
6097
|
+
v1Path,
|
|
6098
|
+
error: `probe failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
6099
|
+
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
6100
|
+
});
|
|
6101
|
+
safeDelete(v1Path);
|
|
6102
|
+
continue;
|
|
6076
6103
|
}
|
|
6077
|
-
|
|
6104
|
+
const { config, issues: taskIssues } = (0, import_devtools_protocol9.migrateV1ToV2)(v1.config, workspaceRef);
|
|
6105
|
+
issues.push(...taskIssues);
|
|
6106
|
+
const newTasks = config.tasks.map((t) => {
|
|
6107
|
+
if (existingNames.has(t.name)) {
|
|
6108
|
+
conflicts.push(t.name);
|
|
6109
|
+
return disambiguateName(t, existingNames, workspaceRef.name);
|
|
6110
|
+
}
|
|
6111
|
+
existingNames.add(t.name);
|
|
6112
|
+
return t;
|
|
6113
|
+
});
|
|
6114
|
+
baseConfig.tasks.push(...newTasks);
|
|
6115
|
+
migrated += 1;
|
|
6116
|
+
safeDelete(v1Path);
|
|
6078
6117
|
}
|
|
6079
|
-
|
|
6118
|
+
if (migrated > 0) {
|
|
6119
|
+
ensureDir(globalConfigPath);
|
|
6120
|
+
const tmp = `${globalConfigPath}.tmp`;
|
|
6121
|
+
fs19.writeFileSync(tmp, JSON.stringify(baseConfig, null, " "), "utf-8");
|
|
6122
|
+
fs19.renameSync(tmp, globalConfigPath);
|
|
6123
|
+
}
|
|
6124
|
+
if (failures.length > priorFailures.length) {
|
|
6125
|
+
writeJsonFile(migrationFailuresPath, failures);
|
|
6126
|
+
} else if (failures.length === 0 && priorFailures.length > 0) {
|
|
6127
|
+
safeDelete(migrationFailuresPath);
|
|
6128
|
+
}
|
|
6129
|
+
return {
|
|
6130
|
+
migrated,
|
|
6131
|
+
failed: failures.length - priorFailures.length,
|
|
6132
|
+
conflicts,
|
|
6133
|
+
issues
|
|
6134
|
+
};
|
|
6135
|
+
}
|
|
6080
6136
|
|
|
6081
6137
|
// src/scheduled-tasks/workspace-probe/WorkspaceProbe.ts
|
|
6082
6138
|
var import_node_child_process5 = require("child_process");
|
|
@@ -6979,7 +7035,6 @@ var ToolboxCore = class {
|
|
|
6979
7035
|
MIGRATION_FAILURES_FILENAME,
|
|
6980
7036
|
MicrosoftAuthProvider,
|
|
6981
7037
|
MicrosoftProviderNotHostedError,
|
|
6982
|
-
MigrateToGlobal,
|
|
6983
7038
|
NodeGitSpawner,
|
|
6984
7039
|
PROFILES_JSON_FILENAME,
|
|
6985
7040
|
PidManager,
|
|
@@ -7078,6 +7133,7 @@ var ToolboxCore = class {
|
|
|
7078
7133
|
isUserRepo,
|
|
7079
7134
|
matchesCron,
|
|
7080
7135
|
mergeWithDefaults,
|
|
7136
|
+
migrateToGlobal,
|
|
7081
7137
|
moveFiles,
|
|
7082
7138
|
narrowRepoConfig,
|
|
7083
7139
|
noopLogger,
|