feat(catalog-sync): report sources that can only be compared, not imported
CI / check (push) Successful in 47s
CI / tests-integration (push) Successful in 2m36s
CI / tests-unit (push) Successful in 2m52s
CI / tests-ui (push) Successful in 3m28s
CI / preflight (push) Skipped
CI / tests-quality (push) Successful in 1m30s
CI / tests-imaging-browsers (push) Successful in 2m47s
CI / deploy (push) Successful in 3m21s

The Habbo presets (and the other furnidata-only sources) carry no
`nitroBaseUrl`, so the clone importer's `${nitroBaseUrl}/${classname}.nitro`
download can never work against them — an `--import-new` run would have failed
for every single item instead of saying why. The SWF path that does work against
`images.habbo.com` lives in `furni-import.ts`, not in the clone importer this
CLI uses.

`stepImportNew` now skips those sources up-front, lists them under
"compared only" in the summary, and the menu prints the same warning when an
import step is chosen with such a source, pointing at the sources that do serve
bundles. Verified: Habbo (COM) reports compare-only, while Habboon and
VirtualCity list real candidates.
This commit is contained in:
openhands committed 2026-10-15 20:18:34 +02:00
1 parent f56b345598
commit 77a0d1010c
3 files changed
+78 -4

No files matched your search

+18
View File
@@ -117,6 +117,21 @@ ACTIONS=(audit)
CONFIRM=""
# Empty for the audit-only branch on purpose: no repair flags are passed.
ACTION_FLAGS=()
# Prints a warning when the chosen source only serves furnidata (no .nitro
# bundles), so the import step would find nothing to download.
warn_compare_only_sources() {
case "$SOURCE_LABEL" in
"Habbo (COM)" | "Habbo (NL)" | "Habbo (DE)" | "Habbo (FR)" | \
"Habbo (ES)" | "Habbo (FI)" | "Habbo (BR)" | "Habbo (TR)" | \
"Habbo (IT - GitHub mirror)" | "YabboHotel" | "Wibbo" | "Hubba.cc")
echo
echo "Note: ${SOURCE_LABEL} only serves furnidata — there are no .nitro"
echo "bundles to import, so the import step can only COMPARE."
echo "To actually import furniture, pick a source that serves bundles"
echo "(Habblet City, VirtualCity, SodaStudios, Habboon, Leet, Hubbly) or 'a'."
;;
esac
}
case "$ACTION_CHOICE" in
1)
ACTIONS=(audit "[nothing written]")
@@ -179,6 +194,9 @@ esac
echo
printf 'Running: %s against %s\n' "${ACTIONS[1]}" "$SOURCE_LABEL"
case "$ACTION_FLAGS" in
*--import-new*) warn_compare_only_sources ;;
esac
if [ -n "$CONFIRM" ]; then
read -r -p "$CONFIRM" REPLY
if [ "$REPLY" != "y" ] && [ "$REPLY" != "Y" ]; then
+31
View File
@@ -3,6 +3,7 @@ import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
canImportBundles,
DEFAULT_CATALOG_SYNC_FLAGS,
describeActions,
parseCatalogSyncArgs,
@@ -131,6 +132,36 @@ describe("catalog-sync import selection", () => {
});
});
describe("catalog-sync import feasibility", () => {
it("imports from a source that serves bundles", () => {
expect(
canImportBundles({
id: "default-habblet-city",
name: "Habblet City",
furnidataUrl: "https://www.habblet.city/gamedata/furnidata.json",
nitroBaseUrl: "https://www.habblet.city/nitro/bundled/furniture",
iconBaseUrl: "",
hotel: "com",
} as never),
).toBe(true);
});
// The Habbo presets have no nitro/icon base URL: their furnidata can be
// compared, but there is nothing to download from.
it("does not import from a compare-only source", () => {
expect(
canImportBundles({
id: "default-habbo-com",
name: "Habbo (COM)",
furnidataUrl: "https://www.habbo.com/gamedata/furnidata_json/1",
nitroBaseUrl: "",
iconBaseUrl: "",
hotel: "com",
} as never),
).toBe(false);
});
});
describe("catalog-sync step composition", () => {
it("--everything turns on every step except the writing ones", () => {
const flags = parseCatalogSyncArgs(["--everything"]);
+29 -4
View File
@@ -83,6 +83,22 @@ export interface ImportOutcome {
skipped: number;
failed: number;
failures: Array<{ classname: string; error: string }>;
/** Sources that were skipped because they cannot serve bundles. */
skippedSources: string[];
}
/**
* Whether a source can actually be imported from.
*
* The clone import downloads `${nitroBaseUrl}/${classname}.nitro` and gives up
* when that is not reachable. Habbo's own entries carry no nitro/icon base URLs
* (their furnidata is served, their assets are not), so they are compare-only:
* perfect for finding out what this hotel lacks, useless for actually fetching
* it. The SWF-based path that works against `images.habbo.com` lives in
* `furni-import.ts`, not in the clone importer this CLI uses.
*/
export function canImportBundles(source: SourceRef): boolean {
return Boolean(source.nitroBaseUrl?.trim());
}
export interface CatalogSyncFlags {
@@ -420,8 +436,16 @@ async function stepImportNew(
itemType: string;
}> = [];
let lastSource = "";
const skippedSources: string[] = [];
for (const source of sources) {
if (!canImportBundles(source)) {
log(
`! ${source.name} serves no .nitro bundles — it can be compared against, but nothing can be imported from it`,
);
skippedSources.push(source.name);
continue;
}
if (flags.importLimit > 0 && planned >= flags.importLimit) break;
const remaining = flags.importLimit
? flags.importLimit - planned
@@ -515,6 +539,7 @@ async function stepImportNew(
skipped,
failed,
failures: failures.slice(0, 25),
skippedSources,
};
}
@@ -736,12 +761,12 @@ function printSummary(report: AuditReport, started: number): void {
console.log(
` imported from source ${outcome.imported} new (${outcome.skipped} already present, ${outcome.failed} failed)`,
);
if (outcome.skippedSources.length > 0)
console.log(
` compared only ${outcome.skippedSources.join(", ")} (no .nitro bundles to import from)`,
);
for (const failure of outcome.failures)
console.log(` ${failure.classname}: ${failure.error}`);
if (outcome.failures.length > 0)
console.log(
` import failures are per-item: a failed download never aborts the run.`,
);
}
if (report.missingFromSources.length > 0) {