forgemap 0.7.0-dev.117-ac8b2c6 → 0.7.0-dev.118-ba83f2c

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.
@@ -256,11 +256,15 @@ async function safeStat(path) {
256
256
  * marker and never descends into a repo, which keeps submodules and nested
257
257
  * checkouts out without a special case for either.
258
258
  *
259
- * `segments` is the namespace accumulated below the forge dir; the repo takes
260
- * the last one, so a repo needs at least two.
259
+ * `segments` is the path accumulated below the forge dir; the repo takes the
260
+ * last one and the namespace the rest, so a repo needs at least two.
261
+ *
262
+ * The cap counts those segments inclusive of the repo's own — a namespace at
263
+ * exactly MAX_NAMESPACE_DEPTH must still have its repo visited, or the
264
+ * resolver would accept a path the scanner can never find.
261
265
  */
262
266
  async function walk(ctx, dirPath, segments) {
263
- if (segments.length >= 10) {
267
+ if (segments.length > 10) {
264
268
  ctx.hints.push({
265
269
  path: dirPath,
266
270
  reason: "too-deep"
@@ -803,8 +807,11 @@ var gitlabAdapter = {
803
807
  * GRAPHQL_CHUNK projects, and each miss — `null` could mean gone *or*
804
808
  * renamed — costs a single REST call to tell the two apart.
805
809
  *
806
- * Every input in a batch belongs to one forge (the importer groups by
807
- * server dir), so the host is taken from the first.
810
+ * A batch may span several configured GitLab forges, so the inputs are
811
+ * grouped by host before they are chunked: one server must never be asked
812
+ * about another's projects. Where the same full path exists on both — a
813
+ * public mirror of an internal project — the wrong server would otherwise
814
+ * answer `exists` for a project this one does not have.
808
815
  */
809
816
  async checkRemotes(inputs) {
810
817
  if (inputs.length === 0) return [];
@@ -812,10 +819,16 @@ var gitlabAdapter = {
812
819
  state: "unknown",
813
820
  reason: "glab not installed"
814
821
  }));
815
- const forge = inputs[0].forge;
816
822
  const results = Array.from({ length: inputs.length }, () => null);
817
- for (let start = 0; start < inputs.length; start += GRAPHQL_CHUNK) {
818
- const chunk = inputs.slice(start, start + GRAPHQL_CHUNK);
823
+ const byHost = /* @__PURE__ */ new Map();
824
+ inputs.forEach((input, index) => {
825
+ const indices = byHost.get(input.forge.host);
826
+ if (indices) indices.push(index);
827
+ else byHost.set(input.forge.host, [index]);
828
+ });
829
+ for (const indices of byHost.values()) for (let start = 0; start < indices.length; start += GRAPHQL_CHUNK) {
830
+ const slice = indices.slice(start, start + GRAPHQL_CHUNK);
831
+ const chunk = slice.map((index) => inputs[index]);
819
832
  const res = await execCapture("glab", [
820
833
  "api",
821
834
  "graphql",
@@ -823,7 +836,7 @@ var gitlabAdapter = {
823
836
  `query=${buildQuery(chunk)}`
824
837
  ], {
825
838
  timeoutMs: GLAB_TIMEOUT_MS,
826
- env: glabEnv(forge)
839
+ env: glabEnv(chunk[0].forge)
827
840
  });
828
841
  let data = null;
829
842
  try {
@@ -835,7 +848,7 @@ var gitlabAdapter = {
835
848
  for (let i = 0; i < chunk.length; i++) {
836
849
  const node = data?.[`r${i}`];
837
850
  const canonical = node?.fullPath ? splitFullPath(node.fullPath) : null;
838
- if (canonical) results[start + i] = {
851
+ if (canonical) results[slice[i]] = {
839
852
  state: "exists",
840
853
  canonical
841
854
  };
@@ -1203,16 +1216,31 @@ function remoteBlocker(state) {
1203
1216
  if (state === "exists" || state === "moved") return null;
1204
1217
  return state === "gone" ? "remote no longer exists" : "remote unreachable";
1205
1218
  }
1206
- /** Check each candidate's remote, grouped by forge so GitHub can batch. */
1219
+ /**
1220
+ * Check each candidate's remote, grouped by forge **type and host** so an
1221
+ * adapter can batch.
1222
+ *
1223
+ * The host is half the key, not a detail: two configured forges of one type —
1224
+ * gitlab.com beside a self-hosted instance — are two different servers, and a
1225
+ * batch spanning both would ask one of them about the other's projects. Where
1226
+ * the same path exists on each (a public mirror of an internal project), that
1227
+ * answers `exists` for the wrong server, and `remoteBlocker` reads `exists` as
1228
+ * "safe to delete".
1229
+ */
1207
1230
  async function classifyRemotes(candidates) {
1208
- const byType = /* @__PURE__ */ new Map();
1231
+ const groups = /* @__PURE__ */ new Map();
1209
1232
  for (const c of candidates) {
1210
- const list = byType.get(c.repo.forge.type);
1211
- if (list) list.push(c);
1212
- else byType.set(c.repo.forge.type, [c]);
1233
+ const { type, host } = c.repo.forge;
1234
+ const key = `${type}\0${host}`;
1235
+ const group = groups.get(key);
1236
+ if (group) group.items.push(c);
1237
+ else groups.set(key, {
1238
+ type,
1239
+ items: [c]
1240
+ });
1213
1241
  }
1214
1242
  const results = /* @__PURE__ */ new Map();
1215
- await Promise.all(Array.from(byType, async ([type, items]) => {
1243
+ await Promise.all(Array.from(groups.values(), async ({ type, items }) => {
1216
1244
  const inputs = items.map((c) => ({
1217
1245
  forge: c.repo.forge,
1218
1246
  owner: c.owner,
@@ -2533,7 +2561,7 @@ async function readEntries(path) {
2533
2561
  * reason `import` looks at directories a scan would simply skip.
2534
2562
  */
2535
2563
  async function discoverBelow(serverDir, dirPath, segments, found) {
2536
- if (segments.length >= 10) return;
2564
+ if (segments.length > 10) return;
2537
2565
  const entries = await readEntries(dirPath);
2538
2566
  const isLeaf = entries === null || entries.dirs.length === 0;
2539
2567
  if (segments.length >= 2 && (entries?.isRepo || isLeaf)) {
@@ -3215,7 +3243,7 @@ var infoCommand = defineCommand({
3215
3243
  async run({ args }) {
3216
3244
  const binary = resolveBinary(process.argv[1]);
3217
3245
  const info = {
3218
- version: "0.7.0-dev.117-ac8b2c6",
3246
+ version: "0.7.0-dev.118-ba83f2c",
3219
3247
  build: detectBuild(binary.resolved),
3220
3248
  binary,
3221
3249
  node: process.version,
@@ -4415,7 +4443,7 @@ var completionCommand = defineCommand({
4415
4443
  runMain(defineCommand({
4416
4444
  meta: {
4417
4445
  name: "forgemap",
4418
- version: "0.7.0-dev.117-ac8b2c6",
4446
+ version: "0.7.0-dev.118-ba83f2c",
4419
4447
  description: "Manage a local repo layout of the form <root>/<forge.dir>/<owner>/<repo>"
4420
4448
  },
4421
4449
  subCommands: {