diff --git a/packages/interfacectl-cli/dist/commands/analyze.d.ts.map b/packages/interfacectl-cli/dist/commands/analyze.d.ts.map index 6f9776c..bedb5e0 100644 --- a/packages/interfacectl-cli/dist/commands/analyze.d.ts.map +++ b/packages/interfacectl-cli/dist/commands/analyze.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../../src/commands/analyze.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACpB,MAAM,gCAAgC,CAAC;AAIxC,KAAK,WAAW,GAAG,kBAAkB,CAAC;AAEtC,MAAM,WAAW,qBAAqB;IACpC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,cAAc,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAoCD,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,MAAM,CAAC,CAoFvF"} \ No newline at end of file +{"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../../src/commands/analyze.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACpB,MAAM,gCAAgC,CAAC;AASxC,KAAK,WAAW,GAAG,kBAAkB,CAAC;AAEtC,MAAM,WAAW,qBAAqB;IACpC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,cAAc,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAoCD,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,MAAM,CAAC,CAuFvF"} \ No newline at end of file diff --git a/packages/interfacectl-cli/dist/commands/analyze.js b/packages/interfacectl-cli/dist/commands/analyze.js index f3e6a82..b53d9fe 100644 --- a/packages/interfacectl-cli/dist/commands/analyze.js +++ b/packages/interfacectl-cli/dist/commands/analyze.js @@ -3,7 +3,7 @@ import { mkdir, writeFile } from "node:fs/promises"; import path from "node:path"; import { inspectAuthProfile } from "../utils/auth-profiles.js"; import { analyzeSurface, stringifyStableArtifact, } from "../utils/first-run-analysis.js"; -import { suggestSurfaceIdFromPath, suggestSurfaceIdFromUrl, suggestSurfaceName } from "../utils/onboarding.js"; +import { normalizeRemoteUrlInput, suggestSurfaceIdFromPath, suggestSurfaceIdFromUrl, suggestSurfaceName, } from "../utils/onboarding.js"; import { redactSensitiveText } from "../utils/redaction.js"; const DEFAULT_OUT_DIR = "contracts/generated"; function normalizeSurfaceId(raw) { @@ -39,6 +39,9 @@ export async function runAnalyzeCommand(options) { const rootDir = process.cwd(); try { const sourceMode = inferSourceMode(options); + const normalizedUrl = sourceMode === "remote-url" && options.url + ? normalizeRemoteUrlInput(options.url) + : undefined; if (sourceMode === "remote-url" && !options.url) { throw new Error("Missing required --url for remote-url analysis."); } @@ -52,16 +55,16 @@ export async function runAnalyzeCommand(options) { } } const surfaceSuggestion = options.surface ?? - (sourceMode === "remote-url" && options.url - ? suggestSurfaceIdFromUrl(options.url) + (sourceMode === "remote-url" && normalizedUrl + ? suggestSurfaceIdFromUrl(normalizedUrl) : suggestSurfaceIdFromPath(options.appRoot ?? "surface")); const surfaceId = normalizeSurfaceId(surfaceSuggestion); const surfaceName = options.surfaceName ?? suggestSurfaceName(surfaceId); let authMode = "none"; let authProfileName; let authStorageState; - if (sourceMode === "remote-url" && options.authProfile && options.url) { - const url = new URL(options.url); + if (sourceMode === "remote-url" && options.authProfile && normalizedUrl) { + const url = new URL(normalizedUrl); const inspection = await inspectAuthProfile(options.authProfile, url.hostname); if (inspection.status !== "ready" || !inspection.profile || !inspection.storageState) { const reason = inspection.status === "missing" @@ -83,7 +86,7 @@ export async function runAnalyzeCommand(options) { surfaceName, sourceMode, appRoot: options.appRoot, - url: options.url, + url: normalizedUrl, surfaceKindOverride: options.surfaceKind, authMode, authProfileName, @@ -95,7 +98,7 @@ export async function runAnalyzeCommand(options) { console.log(`Wrote analysis: ${outputPath}`); console.log(`Inferred surface kind: ${result.analysis.classification.inferredKind} (${result.analysis.classification.confidence.toFixed(2)})`); if (result.analysis.sourceHealth.status !== "ok") { - console.log(`Source access: ${result.analysis.sourceHealth.status} (${result.analysis.sourceHealth.confidence}) at ${result.analysis.sourceHealth.finalUrl ?? options.url}`); + console.log(`Source access: ${result.analysis.sourceHealth.status} (${result.analysis.sourceHealth.confidence}) at ${result.analysis.sourceHealth.finalUrl ?? normalizedUrl}`); } if (result.analysis.classification.requiresConfirmation && !options.surfaceKind) { console.log("Note: classification is low confidence; pass --surface-kind to confirm seeding intent."); diff --git a/packages/interfacectl-cli/dist/commands/auth.d.ts.map b/packages/interfacectl-cli/dist/commands/auth.d.ts.map index ed41f3f..526dd9b 100644 --- a/packages/interfacectl-cli/dist/commands/auth.d.ts.map +++ b/packages/interfacectl-cli/dist/commands/auth.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC1B;AAkBD,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC,CAE1D;AAED,wBAAsB,6BAA6B,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,CA0BhG;AAED,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,CA+DxF;AAED,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,CA0JrF;AAED,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,CAgCtF"} \ No newline at end of file +{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC1B;AAkBD,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC,CAE1D;AAED,wBAAsB,6BAA6B,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,CA0BhG;AAED,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,CA+DxF;AAED,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,CA4JrF;AAED,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,CAgCtF"} \ No newline at end of file diff --git a/packages/interfacectl-cli/dist/commands/auth.js b/packages/interfacectl-cli/dist/commands/auth.js index 991fd88..5de91d6 100644 --- a/packages/interfacectl-cli/dist/commands/auth.js +++ b/packages/interfacectl-cli/dist/commands/auth.js @@ -1,5 +1,6 @@ import { captureBrowserStorageState, observeRemotePage } from "../utils/browser-session.js"; import { clearAuthProfiles, getAuthStorageMode, inspectAuthProfile, isLegacyAuthProfile, isProfileExpired, isProfileReplayReady, listAuthProfiles, saveReplayAuthProfile, } from "../utils/auth-profiles.js"; +import { normalizeRemoteUrlInput } from "../utils/onboarding.js"; function buildProfileStatus(profile) { if (!profile) { return "missing"; @@ -54,7 +55,7 @@ export async function runAuthCaptureCommand(options) { return 1; } try { - const requestedUrl = new URL(options.url); + const requestedUrl = new URL(normalizeRemoteUrlInput(options.url)); const captured = await captureBrowserStorageState({ url: requestedUrl.toString(), }); @@ -105,7 +106,8 @@ export async function runAuthTestCommand(options) { console.error("Missing --profile for auth test."); return 1; } - const domain = options.url ? new URL(options.url).hostname : options.domain; + const normalizedUrl = options.url ? normalizeRemoteUrlInput(options.url) : undefined; + const domain = normalizedUrl ? new URL(normalizedUrl).hostname : options.domain; if (!domain) { if (options.format === "json") { console.log(JSON.stringify({ ok: false, error: "Provide --domain or --url for auth test." }, null, 2)); @@ -158,8 +160,9 @@ export async function runAuthTestCommand(options) { return 0; } try { + const targetUrl = normalizedUrl ?? normalizeRemoteUrlInput(options.url); const observation = await observeRemotePage({ - url: options.url, + url: targetUrl, storageState: inspection.storageState, }); const ok = new URL(observation.finalUrl).hostname === inspection.profile.domain && diff --git a/packages/interfacectl-cli/dist/commands/init.d.ts.map b/packages/interfacectl-cli/dist/commands/init.d.ts.map index 4599bd7..a11d002 100644 --- a/packages/interfacectl-cli/dist/commands/init.d.ts.map +++ b/packages/interfacectl-cli/dist/commands/init.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AA2BA,OAAO,EASL,KAAK,sBAAsB,EAE5B,MAAM,8BAA8B,CAAC;AAgCtC,MAAM,WAAW,WAAY,SAAQ,sBAAsB;IACzD,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAsiBD,wBAAsB,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CA8O1E"} \ No newline at end of file +{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AA4BA,OAAO,EASL,KAAK,sBAAsB,EAE5B,MAAM,8BAA8B,CAAC;AAgCtC,MAAM,WAAW,WAAY,SAAQ,sBAAsB;IACzD,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAsiBD,wBAAsB,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CA8O1E"} \ No newline at end of file diff --git a/packages/interfacectl-cli/dist/commands/init.js b/packages/interfacectl-cli/dist/commands/init.js index bdf121a..f149849 100644 --- a/packages/interfacectl-cli/dist/commands/init.js +++ b/packages/interfacectl-cli/dist/commands/init.js @@ -8,7 +8,7 @@ import { runValidateExtractedCommand } from "./validate-extracted.js"; import { getAuthStorageMode, inspectAuthProfile, saveReplayAuthProfile, } from "../utils/auth-profiles.js"; import { captureBrowserStorageState, observeRemotePage } from "../utils/browser-session.js"; import { analyzeSurface, stringifyStableArtifact, } from "../utils/first-run-analysis.js"; -import { emitOnboardingRunArtifact, suggestSurfaceIdFromPath, suggestSurfaceIdFromUrl, suggestSurfaceName, } from "../utils/onboarding.js"; +import { emitOnboardingRunArtifact, normalizeRemoteUrlInput, suggestSurfaceIdFromPath, suggestSurfaceIdFromUrl, suggestSurfaceName, } from "../utils/onboarding.js"; import { inferSourceMode, normalizeSurfaceId, promptGateResolution, promptInteractiveInitInputs, promptSurfaceKindConfirmation, promptWriteConfirmation, } from "../utils/init-interactive.js"; import { redactSensitiveText } from "../utils/redaction.js"; const DEFAULT_OUT_DIR = "contracts/generated"; @@ -74,7 +74,7 @@ async function resolveInputs(options) { const surfaceName = options.surfaceName ?? suggestSurfaceName(surfaceId); return { sourceMode, - url: options.url ? new URL(options.url).toString() : undefined, + url: options.url ? normalizeRemoteUrlInput(options.url) : undefined, appRoot: options.appRoot, surfaceId, surfaceName, diff --git a/packages/interfacectl-cli/dist/utils/init-interactive.d.ts.map b/packages/interfacectl-cli/dist/utils/init-interactive.d.ts.map index d5bacb0..f7d6d40 100644 --- a/packages/interfacectl-cli/dist/utils/init-interactive.d.ts.map +++ b/packages/interfacectl-cli/dist/utils/init-interactive.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"init-interactive.d.ts","sourceRoot":"","sources":["../../src/utils/init-interactive.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,kBAAkB,EAClB,uBAAuB,EACvB,cAAc,EACf,MAAM,yBAAyB,CAAC;AAOjC,MAAM,MAAM,WAAW,GAAG,kBAAkB,CAAC;AAE7C,MAAM,WAAW,sBAAsB;IACrC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,cAAc,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,WAAW,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,cAAc,CAAC;IAC7B,YAAY,EAAE,OAAO,CAAC;IACtB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED,MAAM,MAAM,oBAAoB,GAC5B,cAAc,GACd,iBAAiB,GACjB,mBAAmB,GACnB,MAAM,CAAC;AAIX,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAOtD;AAED,wBAAgB,eAAe,CAC7B,OAAO,EAAE,IAAI,CAAC,sBAAsB,EAAE,aAAa,GAAG,SAAS,GAAG,KAAK,CAAC,GACvE,WAAW,CAWb;AAED,wBAAsB,2BAA2B,CAC/C,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,kBAAkB,CAAC,CAgE7B;AAED,wBAAsB,6BAA6B,CACjD,QAAQ,EAAE,uBAAuB,GAChC,OAAO,CAAC,cAAc,CAAC,CA2BzB;AAED,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,uBAAuB,GAChC,OAAO,CAAC,oBAAoB,CAAC,CAqC/B;AAED,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,OAAO,CAAC,CAgBhE"} \ No newline at end of file +{"version":3,"file":"init-interactive.d.ts","sourceRoot":"","sources":["../../src/utils/init-interactive.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,kBAAkB,EAClB,uBAAuB,EACvB,cAAc,EACf,MAAM,yBAAyB,CAAC;AAQjC,MAAM,MAAM,WAAW,GAAG,kBAAkB,CAAC;AAE7C,MAAM,WAAW,sBAAsB;IACrC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,cAAc,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,WAAW,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,cAAc,CAAC;IAC7B,YAAY,EAAE,OAAO,CAAC;IACtB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED,MAAM,MAAM,oBAAoB,GAC5B,cAAc,GACd,iBAAiB,GACjB,mBAAmB,GACnB,MAAM,CAAC;AAIX,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAOtD;AAED,wBAAgB,eAAe,CAC7B,OAAO,EAAE,IAAI,CAAC,sBAAsB,EAAE,aAAa,GAAG,SAAS,GAAG,KAAK,CAAC,GACvE,WAAW,CAWb;AAED,wBAAsB,2BAA2B,CAC/C,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,kBAAkB,CAAC,CAgE7B;AAED,wBAAsB,6BAA6B,CACjD,QAAQ,EAAE,uBAAuB,GAChC,OAAO,CAAC,cAAc,CAAC,CA2BzB;AAED,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,uBAAuB,GAChC,OAAO,CAAC,oBAAoB,CAAC,CAqC/B;AAED,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,OAAO,CAAC,CAgBhE"} \ No newline at end of file diff --git a/packages/interfacectl-cli/dist/utils/init-interactive.js b/packages/interfacectl-cli/dist/utils/init-interactive.js index 906d2fb..f9fb908 100644 --- a/packages/interfacectl-cli/dist/utils/init-interactive.js +++ b/packages/interfacectl-cli/dist/utils/init-interactive.js @@ -1,6 +1,6 @@ import readline from "node:readline/promises"; import { stdin as input, stdout as output } from "node:process"; -import { suggestSurfaceIdFromPath, suggestSurfaceIdFromUrl, suggestSurfaceName, } from "./onboarding.js"; +import { normalizeRemoteUrlInput, suggestSurfaceIdFromPath, suggestSurfaceIdFromUrl, suggestSurfaceName, } from "./onboarding.js"; const VALID_SURFACE_KINDS = new Set(["marketing", "application", "unknown"]); export function normalizeSurfaceId(raw) { return raw @@ -31,7 +31,7 @@ export async function promptInteractiveInitInputs(options) { inferredMode).toLowerCase(); const sourceMode = rawMode === "remote-url" ? "remote-url" : "local-root"; const url = sourceMode === "remote-url" - ? new URL(options.url ?? (await rl.question("Surface URL: ")).trim()).toString() + ? normalizeRemoteUrlInput(options.url ?? (await rl.question("Surface URL: ")).trim()) : options.url?.trim() || undefined; const appRoot = sourceMode === "local-root" ? (options.appRoot ?? (await rl.question("Local app root: "))).trim() diff --git a/packages/interfacectl-cli/dist/utils/onboarding.d.ts b/packages/interfacectl-cli/dist/utils/onboarding.d.ts index eb984ff..ebe71d3 100644 --- a/packages/interfacectl-cli/dist/utils/onboarding.d.ts +++ b/packages/interfacectl-cli/dist/utils/onboarding.d.ts @@ -29,6 +29,7 @@ export interface BootstrapExtractionReport { }; }; } +export declare function normalizeRemoteUrlInput(rawInput: string): string; export declare function suggestSurfaceIdFromUrl(rawUrl: string): string; export declare function suggestSurfaceName(surfaceId: string): string; export declare function suggestSurfaceIdFromPath(rawPath: string): string; diff --git a/packages/interfacectl-cli/dist/utils/onboarding.d.ts.map b/packages/interfacectl-cli/dist/utils/onboarding.d.ts.map index db80906..8bea287 100644 --- a/packages/interfacectl-cli/dist/utils/onboarding.d.ts.map +++ b/packages/interfacectl-cli/dist/utils/onboarding.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"onboarding.d.ts","sourceRoot":"","sources":["../../src/utils/onboarding.ts"],"names":[],"mappings":"AAKA,OAAO,EAA2B,KAAK,iBAAiB,EAAE,KAAK,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC7G,KAAK,iBAAiB,GAAG,iBAAiB,CAAC;AAC3C,MAAM,MAAM,mBAAmB,GAAG,iBAAiB,CAAC;AAEpD,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC9C,SAAS,EAAE;QACT,MAAM,EAAE,MAAM,EAAE,CAAC;QACjB,QAAQ,EAAE,OAAO,CAAC;QAClB,sBAAsB,EAAE,MAAM,EAAE,CAAC;QACjC,SAAS,EAAE,OAAO,CAAC;KACpB,CAAC;IACF,UAAU,EAAE;QACV,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,MAAM,GAAG,iBAAiB,CAAC;QACrC,WAAW,EAAE,YAAY,GAAG,YAAY,CAAC;QACzC,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,SAAS,EAAE,MAAM,CAAC;QAClB,WAAW,EAAE,MAAM,CAAC;QACpB,SAAS,EAAE;YACT,OAAO,EAAE,MAAM,CAAC;YAChB,SAAS,EAAE,MAAM,CAAC;YAClB,OAAO,EAAE,MAAM,CAAC;SACjB,CAAC;KACH,CAAC;CACH;AAuBD,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAgB9D;AAED,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAM5D;AAED,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAQhE;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE;IAC5C,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;CACpB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA+C1B;AAED,wBAAsB,uBAAuB,CAAC,KAAK,EAAE;IACnD,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,MAAM,EAAE,yBAAyB,CAAC;CACnC,GAAG,OAAO,CAAC;IACV,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC,CASD;AAED,wBAAsB,yBAAyB,CAAC,KAAK,EAAE;IACrD,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,mBAAmB,CAAC;IAC5B,MAAM,EAAE,iBAAiB,CAAC;IAC1B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAW7B;AAED,wBAAsB,wBAAwB,CAAC,KAAK,EAAE;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAK7B"} \ No newline at end of file +{"version":3,"file":"onboarding.d.ts","sourceRoot":"","sources":["../../src/utils/onboarding.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/interfacectl-cli/dist/utils/onboarding.js b/packages/interfacectl-cli/dist/utils/onboarding.js index ac9bc36..fe600f5 100644 --- a/packages/interfacectl-cli/dist/utils/onboarding.js +++ b/packages/interfacectl-cli/dist/utils/onboarding.js @@ -24,8 +24,54 @@ async function readJson(filePath, fallback) { function sha256FromContent(content) { return createHash("sha256").update(content).digest("hex"); } +function toRelative(root, candidate) { + return path.relative(root, candidate); +} +const EXPLICIT_URL_SCHEME_PATTERN = /^[a-z][a-z\d+\-.]*:\/\//i; +function stripPort(candidate) { + if (candidate.startsWith("[")) { + const closingBracket = candidate.indexOf("]"); + if (closingBracket >= 0) { + return candidate.slice(1, closingBracket); + } + } + return candidate.replace(/:\d+$/, ""); +} +function hostnameFromUrlInput(rawInput) { + const withoutProtocolRelativePrefix = rawInput.replace(/^\/\//, ""); + const authority = withoutProtocolRelativePrefix.split(/[/?#]/, 1)[0] ?? ""; + return stripPort(authority).toLowerCase(); +} +function isLocalHostname(hostname) { + if (!hostname) { + return false; + } + if (hostname === "localhost" || hostname === "::1" || hostname === "0.0.0.0") { + return true; + } + if (/^\d{1,3}(?:\.\d{1,3}){3}$/.test(hostname)) { + return hostname.startsWith("127.") || + hostname.startsWith("10.") || + hostname.startsWith("192.168.") || + /^172\.(1[6-9]|2\d|3[0-1])\./.test(hostname); + } + return hostname.endsWith(".local"); +} +export function normalizeRemoteUrlInput(rawInput) { + const trimmed = rawInput.trim(); + if (trimmed.length === 0) { + return new URL(trimmed).toString(); + } + if (EXPLICIT_URL_SCHEME_PATTERN.test(trimmed)) { + return new URL(trimmed).toString(); + } + const hostname = hostnameFromUrlInput(trimmed); + const scheme = isLocalHostname(hostname) ? "http://" : "https://"; + const candidate = trimmed.replace(/^\/\//, ""); + return new URL(`${scheme}${candidate}`).toString(); +} export function suggestSurfaceIdFromUrl(rawUrl) { - const url = new URL(rawUrl); + const url = new URL(normalizeRemoteUrlInput(rawUrl)); const host = url.hostname.replace(/^www\./, ""); const pathToken = url.pathname .split("/") diff --git a/packages/interfacectl-cli/src/commands/analyze.ts b/packages/interfacectl-cli/src/commands/analyze.ts index 876d2fa..9149666 100644 --- a/packages/interfacectl-cli/src/commands/analyze.ts +++ b/packages/interfacectl-cli/src/commands/analyze.ts @@ -8,7 +8,12 @@ import { type AnalysisSourceMode, type WebSurfaceKind, } from "../utils/first-run-analysis.js"; -import { suggestSurfaceIdFromPath, suggestSurfaceIdFromUrl, suggestSurfaceName } from "../utils/onboarding.js"; +import { + normalizeRemoteUrlInput, + suggestSurfaceIdFromPath, + suggestSurfaceIdFromUrl, + suggestSurfaceName, +} from "../utils/onboarding.js"; import { redactSensitiveText } from "../utils/redaction.js"; type ExtractMode = AnalysisSourceMode; @@ -64,6 +69,9 @@ export async function runAnalyzeCommand(options: AnalyzeCommandOptions): Promise try { const sourceMode = inferSourceMode(options); + const normalizedUrl = sourceMode === "remote-url" && options.url + ? normalizeRemoteUrlInput(options.url) + : undefined; if (sourceMode === "remote-url" && !options.url) { throw new Error("Missing required --url for remote-url analysis."); } @@ -81,8 +89,8 @@ export async function runAnalyzeCommand(options: AnalyzeCommandOptions): Promise const surfaceSuggestion = options.surface ?? ( - sourceMode === "remote-url" && options.url - ? suggestSurfaceIdFromUrl(options.url) + sourceMode === "remote-url" && normalizedUrl + ? suggestSurfaceIdFromUrl(normalizedUrl) : suggestSurfaceIdFromPath(options.appRoot ?? "surface") ); const surfaceId = normalizeSurfaceId(surfaceSuggestion); @@ -91,8 +99,8 @@ export async function runAnalyzeCommand(options: AnalyzeCommandOptions): Promise let authMode: "none" | "browser-session" = "none"; let authProfileName: string | undefined; let authStorageState: string | undefined; - if (sourceMode === "remote-url" && options.authProfile && options.url) { - const url = new URL(options.url); + if (sourceMode === "remote-url" && options.authProfile && normalizedUrl) { + const url = new URL(normalizedUrl); const inspection = await inspectAuthProfile(options.authProfile, url.hostname); if (inspection.status !== "ready" || !inspection.profile || !inspection.storageState) { const reason = inspection.status === "missing" @@ -115,7 +123,7 @@ export async function runAnalyzeCommand(options: AnalyzeCommandOptions): Promise surfaceName, sourceMode, appRoot: options.appRoot, - url: options.url, + url: normalizedUrl, surfaceKindOverride: options.surfaceKind, authMode, authProfileName, @@ -132,7 +140,7 @@ export async function runAnalyzeCommand(options: AnalyzeCommandOptions): Promise ); if (result.analysis.sourceHealth.status !== "ok") { console.log( - `Source access: ${result.analysis.sourceHealth.status} (${result.analysis.sourceHealth.confidence}) at ${result.analysis.sourceHealth.finalUrl ?? options.url}`, + `Source access: ${result.analysis.sourceHealth.status} (${result.analysis.sourceHealth.confidence}) at ${result.analysis.sourceHealth.finalUrl ?? normalizedUrl}`, ); } if (result.analysis.classification.requiresConfirmation && !options.surfaceKind) { diff --git a/packages/interfacectl-cli/src/commands/auth.ts b/packages/interfacectl-cli/src/commands/auth.ts index 47fe33f..369ac30 100644 --- a/packages/interfacectl-cli/src/commands/auth.ts +++ b/packages/interfacectl-cli/src/commands/auth.ts @@ -10,6 +10,7 @@ import { listAuthProfiles, saveReplayAuthProfile, } from "../utils/auth-profiles.js"; +import { normalizeRemoteUrlInput } from "../utils/onboarding.js"; export interface AuthCommandOptions { profile?: string; @@ -79,7 +80,7 @@ export async function runAuthCaptureCommand(options: AuthCommandOptions): Promis } try { - const requestedUrl = new URL(options.url); + const requestedUrl = new URL(normalizeRemoteUrlInput(options.url)); const captured = await captureBrowserStorageState({ url: requestedUrl.toString(), }); @@ -142,7 +143,8 @@ export async function runAuthTestCommand(options: AuthCommandOptions): Promise return { sourceMode, - url: options.url ? new URL(options.url).toString() : undefined, + url: options.url ? normalizeRemoteUrlInput(options.url) : undefined, appRoot: options.appRoot, surfaceId, surfaceName, diff --git a/packages/interfacectl-cli/src/utils/init-interactive.ts b/packages/interfacectl-cli/src/utils/init-interactive.ts index 30ee2dc..984ea55 100644 --- a/packages/interfacectl-cli/src/utils/init-interactive.ts +++ b/packages/interfacectl-cli/src/utils/init-interactive.ts @@ -6,6 +6,7 @@ import type { WebSurfaceKind, } from "./first-run-analysis.js"; import { + normalizeRemoteUrlInput, suggestSurfaceIdFromPath, suggestSurfaceIdFromUrl, suggestSurfaceName, @@ -86,7 +87,7 @@ export async function promptInteractiveInitInputs( const sourceMode: ExtractMode = rawMode === "remote-url" ? "remote-url" : "local-root"; const url = sourceMode === "remote-url" - ? new URL(options.url ?? (await rl.question("Surface URL: ")).trim()).toString() + ? normalizeRemoteUrlInput(options.url ?? (await rl.question("Surface URL: ")).trim()) : options.url?.trim() || undefined; const appRoot = sourceMode === "local-root" ? (options.appRoot ?? (await rl.question("Local app root: "))).trim() diff --git a/packages/interfacectl-cli/src/utils/onboarding.ts b/packages/interfacectl-cli/src/utils/onboarding.ts index 767c8cf..c9591d5 100644 --- a/packages/interfacectl-cli/src/utils/onboarding.ts +++ b/packages/interfacectl-cli/src/utils/onboarding.ts @@ -54,8 +54,60 @@ function sha256FromContent(content: string): string { return createHash("sha256").update(content).digest("hex"); } +function toRelative(root: string, candidate: string): string { + return path.relative(root, candidate); +} + +const EXPLICIT_URL_SCHEME_PATTERN = /^[a-z][a-z\d+\-.]*:\/\//i; + +function stripPort(candidate: string): string { + if (candidate.startsWith("[")) { + const closingBracket = candidate.indexOf("]"); + if (closingBracket >= 0) { + return candidate.slice(1, closingBracket); + } + } + return candidate.replace(/:\d+$/, ""); +} + +function hostnameFromUrlInput(rawInput: string): string { + const withoutProtocolRelativePrefix = rawInput.replace(/^\/\//, ""); + const authority = withoutProtocolRelativePrefix.split(/[/?#]/, 1)[0] ?? ""; + return stripPort(authority).toLowerCase(); +} + +function isLocalHostname(hostname: string): boolean { + if (!hostname) { + return false; + } + if (hostname === "localhost" || hostname === "::1" || hostname === "0.0.0.0") { + return true; + } + if (/^\d{1,3}(?:\.\d{1,3}){3}$/.test(hostname)) { + return hostname.startsWith("127.") || + hostname.startsWith("10.") || + hostname.startsWith("192.168.") || + /^172\.(1[6-9]|2\d|3[0-1])\./.test(hostname); + } + return hostname.endsWith(".local"); +} + +export function normalizeRemoteUrlInput(rawInput: string): string { + const trimmed = rawInput.trim(); + if (trimmed.length === 0) { + return new URL(trimmed).toString(); + } + if (EXPLICIT_URL_SCHEME_PATTERN.test(trimmed)) { + return new URL(trimmed).toString(); + } + + const hostname = hostnameFromUrlInput(trimmed); + const scheme = isLocalHostname(hostname) ? "http://" : "https://"; + const candidate = trimmed.replace(/^\/\//, ""); + return new URL(`${scheme}${candidate}`).toString(); +} export function suggestSurfaceIdFromUrl(rawUrl: string): string { - const url = new URL(rawUrl); + const url = new URL(normalizeRemoteUrlInput(rawUrl)); const host = url.hostname.replace(/^www\./, ""); const pathToken = url.pathname .split("/") diff --git a/packages/interfacectl-cli/test/bare-onboarding.test.mjs b/packages/interfacectl-cli/test/bare-onboarding.test.mjs index 87c233c..c34041f 100644 --- a/packages/interfacectl-cli/test/bare-onboarding.test.mjs +++ b/packages/interfacectl-cli/test/bare-onboarding.test.mjs @@ -572,13 +572,14 @@ test("bare interfacectl remote onboarding does not stop on a public Next-like pa const address = server.address(); assert.ok(address && typeof address === "object"); const baseUrl = `http://127.0.0.1:${address.port}`; + const bareUrl = baseUrl.replace(/^https?:\/\//, ""); const result = await runInteractive([], { cwd, env: forceWelcomeEnv, steps: [ { when: /> $/, input: "2\n" }, - { when: /Surface URL: $/, input: `${baseUrl}/\n` }, + { when: /Surface URL: $/, input: `${bareUrl}/\n` }, { when: /Surface id \[[^\]]+\]: $/, input: "\n" }, { when: /Surface name \[[^\]]+\]: $/, input: "\n" }, { when: /sign in to see the real page\? \(y\/N\) $/, input: "\n" }, diff --git a/packages/interfacectl-cli/test/init-auth.test.mjs b/packages/interfacectl-cli/test/init-auth.test.mjs index 72f83b1..8af8078 100644 --- a/packages/interfacectl-cli/test/init-auth.test.mjs +++ b/packages/interfacectl-cli/test/init-auth.test.mjs @@ -222,6 +222,7 @@ test("init: non-interactive remote-url writes first-run artifacts and run metada const address = server.address(); assert.ok(address && typeof address === "object"); const baseUrl = `http://127.0.0.1:${address.port}`; + const bareUrl = baseUrl.replace(/^https?:\/\//, ""); await mkdir(path.join(cwd, "contracts"), { recursive: true }); await writeFile( @@ -243,7 +244,7 @@ test("init: non-interactive remote-url writes first-run artifacts and run metada "init", "--non-interactive", "--url", - `${baseUrl}/`, + `${bareUrl}/`, "--surface", "customer-products", "--surface-kind", @@ -394,9 +395,10 @@ test("auth: capture/list/test/clear operate on replayable local profile store", const address = server.address(); assert.ok(address && typeof address === "object"); const baseUrl = `http://127.0.0.1:${address.port}`; + const bareUrl = baseUrl.replace(/^https?:\/\//, ""); const capture = await run( - ["auth", "capture", "--profile", "demo", "--url", `${baseUrl}/session/start`, "--format", "json"], + ["auth", "capture", "--profile", "demo", "--url", `${bareUrl}/session/start`, "--format", "json"], { cwd, env: { @@ -425,7 +427,7 @@ test("auth: capture/list/test/clear operate on replayable local profile store", assert.equal(listPayload.profiles[0].replayReady, true); const testProfile = await run( - ["auth", "test", "--profile", "demo", "--url", `${baseUrl}/app`, "--format", "json"], + ["auth", "test", "--profile", "demo", "--url", `${bareUrl}/app`, "--format", "json"], { cwd, env: { @@ -583,9 +585,10 @@ test("analyze: public Next-like page with framework auth strings remains sourceH const address = server.address(); assert.ok(address && typeof address === "object"); const baseUrl = `http://127.0.0.1:${address.port}`; + const bareUrl = baseUrl.replace(/^https?:\/\//, ""); const result = await run( - ["analyze", "--url", `${baseUrl}/`, "--surface", "public-site"], + ["analyze", "--url", `${bareUrl}/`, "--surface", "public-site"], { cwd, env: { ...forceFileStorageEnv } }, ); assert.equal(result.exitCode, 0, result.stderr); diff --git a/packages/interfacectl-cli/test/normalize.test.mjs b/packages/interfacectl-cli/test/normalize.test.mjs index f12e3d9..6eb466a 100644 --- a/packages/interfacectl-cli/test/normalize.test.mjs +++ b/packages/interfacectl-cli/test/normalize.test.mjs @@ -6,6 +6,7 @@ import { normalizeContract, normalizeDescriptor, } from "../dist/utils/normalize.js"; +import { normalizeRemoteUrlInput, suggestSurfaceIdFromUrl } from "../dist/utils/onboarding.js"; test("normalizeSetField sorts arrays deterministically", () => { const input = ["c", "a", "b"]; @@ -106,3 +107,28 @@ test("normalizeDescriptor strips ephemeral fields", () => { assert.equal(normalized.descriptor.icons[0].source, undefined); assert.equal(normalized.descriptor.layout.source, undefined); }); + +test("normalizeRemoteUrlInput defaults bare domains to https", () => { + assert.equal( + normalizeRemoteUrlInput("surfaces.systems"), + "https://surfaces.systems/", + ); +}); + +test("normalizeRemoteUrlInput defaults local hosts to http", () => { + assert.equal( + normalizeRemoteUrlInput("127.0.0.1:3000/app"), + "http://127.0.0.1:3000/app", + ); + assert.equal( + normalizeRemoteUrlInput("localhost:3000/app"), + "http://localhost:3000/app", + ); +}); + +test("suggestSurfaceIdFromUrl accepts bare domains", () => { + assert.equal( + suggestSurfaceIdFromUrl("surfaces.systems/start"), + "surfaces-systems-start", + ); +});