#!/usr/bin/env node
import {
  assertReducerContractPreflight
} from "./chunk-YDIOW2BO.mjs";
import {
  assertReducerBundleSmoke
} from "./chunk-Z6OZWUIZ.mjs";
import {
  resolveRemoteProject
} from "./chunk-QZH6IEZS.mjs";
import {
  CONFIG_FLAG_ARGS,
  createGameRevisionSdk,
  defineCommand,
  mapUpsertBlobContentsByContentHash,
  materializeSourceChangeOperations,
  parseSyncCommandArgs,
  uploadProjectSourceBlobsSdk
} from "./chunk-KDBSVLCF.mjs";
import {
  getProjectAuthoringState,
  getProjectLocalMaintainerRegistry,
  getProjectPendingAuthoringSync,
  updateProjectAuthoringState,
  updateProjectLocalMaintainerRegistry
} from "./chunk-YE7UAO3T.mjs";
import {
  runLocalTypecheck
} from "./chunk-UIOLGH4A.mjs";
import "./chunk-XKCJBIRY.mjs";
import "./chunk-IDVQXGAO.mjs";
import {
  assertReleaseEnvironmentPortableDependencies,
  consola,
  resolveProjectContext
} from "./chunk-JO5AMVZU.mjs";
import "./chunk-5NYBTZB4.mjs";
import {
  collectLocalFiles,
  getLocalDiff,
  loadManifest,
  loadRule,
  writeSnapshot
} from "./chunk-27EEIZCI.mjs";
import {
  assertCliStaticScaffoldComplete,
  scaffoldStaticWorkspace
} from "./chunk-MW2QIWWA.mjs";
import "./chunk-F2DIOJJZ.mjs";
import {
  BUILD_CHANNEL,
  IS_PUBLISHED_BUILD,
  updateProjectState
} from "./chunk-776W3UGV.mjs";
import {
  computeManifestHash,
  isAllowedGamePath,
  isDynamicGeneratedPath
} from "./chunk-C3VW3DTA.mjs";
import "./chunk-JZTH3EMV.mjs";
import {
  resolveCliRepoRoot
} from "./chunk-QBAF7EYR.mjs";
import "./chunk-NAK77WXW.mjs";
import "./chunk-TAEQKBJB.mjs";
import {
  applyWorkspaceCodegen
} from "./chunk-G42BGGG2.mjs";
import "./chunk-IAYRNVUC.mjs";
import {
  reconcileWorkspaceDependencies
} from "./chunk-XQXDOBYB.mjs";
import "./chunk-ZEELHSY3.mjs";
import {
  ENVIRONMENT_CONFIGS,
  RULE_FILE
} from "./chunk-H76MT5UR.mjs";
import "./chunk-H6XDQJ3N.mjs";

// src/services/project/source-revision-paths.ts
function isSourceRevisionPath(filePath) {
  return filePath !== RULE_FILE && isAllowedGamePath(filePath);
}
function shouldAlwaysUpsertSourcePath(filePath) {
  return filePath === ".npmrc" || isDynamicGeneratedPath(filePath);
}

// src/services/project/local-maintainer-registry.ts
import { spawn } from "child_process";
import { existsSync, readFileSync } from "fs";
import path2 from "path";
import { fileURLToPath } from "url";

// src/services/project/local-maintainer-registry-shared.ts
import crypto from "crypto";
import path from "path";
var localRegistryAddress = readLocalRegistryAddress();
var LOCAL_REGISTRY_HOST = localRegistryAddress.host;
var LOCAL_REGISTRY_PORT = localRegistryAddress.port;
var LOCAL_REGISTRY_URL = `http://${LOCAL_REGISTRY_HOST}:${LOCAL_REGISTRY_PORT}`;
var LOCAL_SCOPE_NPMRC_CONTENT = `@dreamboard-games:registry=${LOCAL_REGISTRY_URL}
`;
function readLocalRegistryAddress() {
  const urlOverride = process.env.DREAMBOARD_LOCAL_REGISTRY_URL?.trim();
  const hostOverride = process.env.DREAMBOARD_LOCAL_REGISTRY_HOST?.trim();
  const portOverride = process.env.DREAMBOARD_LOCAL_REGISTRY_PORT?.trim();
  if (urlOverride) {
    let parsed;
    try {
      parsed = new URL(urlOverride);
    } catch {
      throw new Error(
        `Invalid DREAMBOARD_LOCAL_REGISTRY_URL '${urlOverride}'. Expected an http://host:port URL.`
      );
    }
    if (parsed.protocol !== "http:" || !parsed.hostname || !parsed.port) {
      throw new Error(
        `Invalid DREAMBOARD_LOCAL_REGISTRY_URL '${urlOverride}'. Expected an http://host:port URL.`
      );
    }
    return {
      host: hostOverride || parsed.hostname,
      port: parseLocalRegistryPort(portOverride || parsed.port)
    };
  }
  return {
    host: hostOverride || "127.0.0.1",
    port: portOverride ? parseLocalRegistryPort(portOverride) : 4873
  };
}
function parseLocalRegistryPort(raw) {
  const port = Number.parseInt(raw, 10);
  if (!Number.isInteger(port) || port < 1 || port > 65535) {
    throw new Error(
      `Invalid DREAMBOARD_LOCAL_REGISTRY_PORT '${raw}'. Expected a TCP port from 1 to 65535.`
    );
  }
  return port;
}
function shortHash(value) {
  return crypto.createHash("sha256").update(value).digest("hex").slice(0, 12);
}
function isLocalMaintainerRegistryEnabled(apiBaseUrl) {
  let isLocalLoopbackUrl = false;
  try {
    const parsed = new URL(apiBaseUrl);
    isLocalLoopbackUrl = parsed.protocol === "http:" && (parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1");
  } catch {
    isLocalLoopbackUrl = false;
  }
  return !IS_PUBLISHED_BUILD && (apiBaseUrl === ENVIRONMENT_CONFIGS.local?.apiBaseUrl || isLocalLoopbackUrl) && BUILD_CHANNEL === "development";
}
function didLocalMaintainerSnapshotChange(previous, next) {
  if (!next) {
    return false;
  }
  return previous?.snapshotId !== next.snapshotId;
}

// src/services/project/local-maintainer-registry.ts
var MODULE_DIR = path2.dirname(fileURLToPath(import.meta.url));
function getCliPackageRoot() {
  try {
    return path2.join(
      resolveCliRepoRoot(import.meta.url),
      "apps",
      "dreamboard-cli"
    );
  } catch {
    return resolveInstalledCliPackageRoot(MODULE_DIR);
  }
}
function resolveInstalledCliPackageRoot(moduleDir) {
  let current = moduleDir;
  while (true) {
    const packageJsonPath = path2.join(current, "package.json");
    if (existsSync(packageJsonPath)) {
      try {
        const packageJson = JSON.parse(
          readFileSync(packageJsonPath, "utf8")
        );
        if (packageJson.name === "@dreamboard-games/cli" || packageJson.name === "dreamboard" || packageJson.name === "dreamboard-cli") {
          return current;
        }
      } catch {
      }
    }
    const parent = path2.dirname(current);
    if (parent === current) {
      return moduleDir;
    }
    current = parent;
  }
}
function getScriptInvocation() {
  const cliPackageRoot = getCliPackageRoot();
  const scriptPath = getLocalMaintainerScriptPath(cliPackageRoot);
  if (!existsSync(scriptPath)) {
    throw new Error(
      [
        "Dreamboard local maintainer registry support is only available from a source checkout.",
        `Expected helper script at ${scriptPath}.`
      ].join(" ")
    );
  }
  const command = process.platform === "win32" ? "pnpm.cmd" : "pnpm";
  const args = ["exec", "tsx", scriptPath];
  return {
    command,
    args,
    attemptedCommand: `${command} ${args.join(" ")}`,
    cwd: cliPackageRoot
  };
}
function getLocalMaintainerScriptPath(cliPackageRoot) {
  return path2.join(cliPackageRoot, "scripts", "local-maintainer-registry.ts");
}
function isInstalledCliPackageRoot(cliPackageRoot) {
  return cliPackageRoot.split(path2.sep).includes("node_modules");
}
function shouldSkipLocalMaintainerHelper() {
  const cliPackageRoot = getCliPackageRoot();
  const scriptPath = getLocalMaintainerScriptPath(cliPackageRoot);
  return !existsSync(scriptPath) && isInstalledCliPackageRoot(cliPackageRoot);
}
function buildInstalledCliLocalMaintainerSnapshot(packageJson, registryUrl = LOCAL_REGISTRY_URL) {
  const sdkVersion = packageJson.dependencies?.["@dreamboard-games/sdk"];
  if (typeof sdkVersion !== "string" || !sdkVersion.includes("-local.")) {
    return null;
  }
  return {
    registryUrl,
    snapshotId: shortHash(sdkVersion),
    fingerprint: shortHash(sdkVersion),
    publishedAt: "",
    packages: {
      "@dreamboard-games/sdk": sdkVersion
    }
  };
}
function readInstalledCliLocalMaintainerSnapshot() {
  const cliPackageRoot = getCliPackageRoot();
  if (!isInstalledCliPackageRoot(cliPackageRoot)) {
    return null;
  }
  const packageJsonPath = path2.join(cliPackageRoot, "package.json");
  if (!existsSync(packageJsonPath)) {
    return null;
  }
  const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
  return buildInstalledCliLocalMaintainerSnapshot(packageJson);
}
function buildScriptSetupError(options) {
  return new Error(
    [
      "Dreamboard local maintainer registry support requires the source-checkout CLI tooling.",
      options.message,
      `Attempted command: ${options.attemptedCommand}`,
      options.stderr?.trim() ? `stderr:
${options.stderr.trim()}` : null
    ].filter(Boolean).join("\n")
  );
}
function parseJsonPayload(output) {
  const payload = output.trim().split(/\r?\n/).map((line) => line.trim()).reverse().find(
    (line) => line === "null" || line.startsWith("{") || line.startsWith("[")
  );
  if (!payload) {
    throw new Error("completed without returning a JSON payload");
  }
  return JSON.parse(payload);
}
async function runLocalMaintainerScript(args) {
  const invocation = getScriptInvocation();
  return new Promise((resolve, reject) => {
    const child = spawn(invocation.command, [...invocation.args, ...args], {
      cwd: invocation.cwd,
      stdio: ["ignore", "pipe", "pipe"],
      env: process.env
    });
    let stdout = "";
    let stderr = "";
    child.stdout.on("data", (chunk) => {
      stdout += chunk.toString();
    });
    child.stderr.on("data", (chunk) => {
      stderr += chunk.toString();
    });
    child.on("error", (error) => {
      const errnoError = error;
      if (errnoError.code === "ENOENT") {
        reject(
          buildScriptSetupError({
            attemptedCommand: invocation.attemptedCommand,
            message: "`pnpm` was not found on PATH, so the source-checkout local maintainer helper could not run."
          })
        );
        return;
      }
      reject(error);
    });
    child.on("close", (code) => {
      if (code !== 0) {
        const missingTsx = stderr.includes('Command "tsx" not found') || stderr.includes("tsx: command not found") || stderr.includes("tsx: not found");
        reject(
          buildScriptSetupError({
            attemptedCommand: invocation.attemptedCommand,
            message: missingTsx ? "`tsx` is not available for the source-checkout CLI package." : "The source-checkout local maintainer helper failed.",
            stderr
          })
        );
        return;
      }
      const trimmedStdout = stdout.trim();
      if (!trimmedStdout) {
        reject(
          buildScriptSetupError({
            attemptedCommand: invocation.attemptedCommand,
            message: "The source-checkout local maintainer helper completed without returning JSON.",
            stderr
          })
        );
        return;
      }
      try {
        resolve(parseJsonPayload(trimmedStdout));
      } catch (error) {
        reject(
          buildScriptSetupError({
            attemptedCommand: invocation.attemptedCommand,
            message: `Failed to parse JSON from the source-checkout local maintainer helper: ${error instanceof Error ? error.message : String(error)}`,
            stderr: [stderr.trim(), trimmedStdout].filter(Boolean).join("\n")
          })
        );
      }
    });
  });
}
async function ensureLocalMaintainerSnapshot(apiBaseUrl) {
  if (!isLocalMaintainerRegistryEnabled(apiBaseUrl)) {
    return null;
  }
  if (shouldSkipLocalMaintainerHelper()) {
    return readInstalledCliLocalMaintainerSnapshot();
  }
  return runLocalMaintainerScript([
    "ensure-snapshot",
    "--api-base-url",
    apiBaseUrl
  ]);
}

// src/commands/sync.ts
async function runLoggedStep(message, task) {
  consola.start(message);
  return task();
}
async function persistProjectConfig(options) {
  await updateProjectState(options.projectRoot, options.projectConfig);
  return options.projectConfig;
}
async function finalizeLocalSync(options) {
  const { projectRoot, projectConfig } = options;
  await scaffoldStaticWorkspace(projectRoot, "update", {
    localMaintainerRegistry: getProjectLocalMaintainerRegistry(projectConfig)
  });
  await applyWorkspaceCodegen({
    projectRoot,
    manifest: await loadManifest(projectRoot)
  });
  const finalizedProjectConfig = await persistProjectConfig({
    projectRoot,
    projectConfig
  });
  await writeSnapshot(projectRoot);
  return finalizedProjectConfig;
}
function buildSourceSnapshotChanges(localFiles) {
  return Object.entries(localFiles).filter(
    ([filePath]) => isSourceRevisionPath(filePath) || shouldAlwaysUpsertSourcePath(filePath)
  ).sort(([left], [right]) => left.localeCompare(right)).map(([path3, content]) => ({
    kind: "upsert",
    path: path3,
    content
  }));
}
var sync_default = defineCommand({
  meta: {
    name: "sync",
    description: "Upload authored changes and advance the remote authoring head"
  },
  args: {
    force: {
      type: "boolean",
      description: "Replace the full authored source tree, manifest, and rules, overwriting the remote head with the local copy even when the remote has moved",
      default: false
    },
    yes: {
      type: "boolean",
      alias: "y",
      description: "Reserved for non-interactive scaffold flows",
      default: false
    },
    ...CONFIG_FLAG_ARGS
  },
  async run({ args }) {
    const parsedArgs = parseSyncCommandArgs(args);
    const { projectRoot, projectConfig, config } = await resolveProjectContext(parsedArgs);
    let nextProjectConfig = projectConfig;
    await assertReleaseEnvironmentPortableDependencies({
      projectRoot,
      projectConfig: nextProjectConfig,
      environment: config.environment
    });
    const localMaintainerEnabled = isLocalMaintainerRegistryEnabled(
      config.apiBaseUrl
    );
    const existingLocalMaintainerRegistry = getProjectLocalMaintainerRegistry(projectConfig);
    const refreshedLocalMaintainerRegistry = localMaintainerEnabled ? await runLoggedStep(
      "Checking local SDK snapshot...",
      () => ensureLocalMaintainerSnapshot(config.apiBaseUrl)
    ) : await ensureLocalMaintainerSnapshot(config.apiBaseUrl);
    const localMaintainerRegistry = refreshedLocalMaintainerRegistry ?? (localMaintainerEnabled ? existingLocalMaintainerRegistry ?? null : null);
    const localMaintainerSnapshotChanged = didLocalMaintainerSnapshotChange(
      existingLocalMaintainerRegistry,
      refreshedLocalMaintainerRegistry
    );
    if (refreshedLocalMaintainerRegistry) {
      nextProjectConfig = updateProjectLocalMaintainerRegistry(
        nextProjectConfig,
        refreshedLocalMaintainerRegistry
      );
      consola.info(
        localMaintainerSnapshotChanged ? "Local SDK snapshot refreshed." : "Using existing local SDK snapshot."
      );
    } else if (localMaintainerRegistry) {
      consola.info("Using workspace-pinned local SDK snapshot.");
    }
    await runLoggedStep(
      "Refreshing static scaffold...",
      () => scaffoldStaticWorkspace(projectRoot, "update", {
        localMaintainerRegistry
      })
    );
    const localManifest = await loadManifest(projectRoot);
    await runLoggedStep(
      "Applying workspace codegen...",
      async () => applyWorkspaceCodegen({
        projectRoot,
        manifest: localManifest
      })
    );
    const dependencyState = await runLoggedStep(
      "Reconciling workspace dependencies...",
      () => reconcileWorkspaceDependencies(projectRoot)
    );
    if (dependencyState.packageManagerNormalized || dependencyState.lockfileGenerated || dependencyState.installed || localMaintainerSnapshotChanged) {
      consola.info("Workspace dependencies reconciled.");
    } else {
      consola.info("Workspace dependencies already up to date.");
    }
    await runLoggedStep(
      "Validating reducer contract...",
      () => assertReducerContractPreflight(projectRoot)
    );
    const typecheckResult = await runLoggedStep(
      "Running local typecheck...",
      () => runLocalTypecheck(projectRoot)
    );
    if (typecheckResult.skipped) {
      if (typecheckResult.output) {
        consola.warn(typecheckResult.output);
      }
    } else if (!typecheckResult.success) {
      if (typecheckResult.output) {
        consola.error(typecheckResult.output);
      }
      throw new Error(
        "Local typecheck failed. Fix the diagnostics before syncing."
      );
    }
    await runLoggedStep(
      "Smoke-testing reducer bundle...",
      async () => assertReducerBundleSmoke({
        projectRoot,
        manifest: localManifest
      })
    );
    consola.success("Reducer bundle smoke test passed.");
    const remoteProject = await runLoggedStep(
      "Ensuring remote project...",
      () => resolveRemoteProject({
        projectRoot,
        projectConfig: nextProjectConfig,
        config
      })
    );
    nextProjectConfig = remoteProject.projectConfig;
    const localDiff = await getLocalDiff(projectRoot);
    await assertCliStaticScaffoldComplete(projectRoot, localDiff.deleted);
    const localAuthoring = getProjectAuthoringState(nextProjectConfig);
    const pendingSync = getProjectPendingAuthoringSync(nextProjectConfig);
    const remoteHeadDigest = remoteProject.project.head?.revisionDigest;
    const localHeadDigest = localAuthoring.revisionDigest ?? nextProjectConfig.remoteHeadDigest;
    if (pendingSync && !parsedArgs.force) {
      throw new Error(
        "This workspace has an unfinished legacy sync checkpoint. Run 'dreamboard sync --force' to replace it with an atomic project revision."
      );
    }
    if (remoteHeadDigest && localHeadDigest && remoteHeadDigest !== localHeadDigest) {
      if (parsedArgs.force) {
        consola.warn(
          `Remote project head has moved to ${remoteHeadDigest}. --force will overwrite it with this workspace's local copy.`
        );
      } else {
        throw new Error(
          `Remote project head has moved to ${remoteHeadDigest}. Run 'dreamboard pull' before syncing local changes, or pass --force to overwrite the remote with the local copy.`
        );
      }
    }
    if (remoteHeadDigest && !localHeadDigest) {
      if (parsedArgs.force) {
        consola.warn(
          `This workspace has no authored base but the remote project head is ${remoteHeadDigest}. --force will overwrite it with this workspace's local copy.`
        );
      } else {
        throw new Error(
          `This workspace has no authored base but the remote project head is ${remoteHeadDigest}. Re-clone, run 'dreamboard pull --force' into a clean workspace, or pass --force to overwrite the remote with the local copy.`
        );
      }
    }
    const hasChanges = localDiff.modified.length > 0 || localDiff.added.length > 0 || localDiff.deleted.length > 0;
    const localManifestContentHash = computeManifestHash(localManifest);
    const manifestOutOfSync = localAuthoring.localManifestContentHash !== localManifestContentHash;
    if (!hasChanges && !parsedArgs.force && localHeadDigest != null && remoteHeadDigest === localHeadDigest && !pendingSync && !manifestOutOfSync) {
      consola.info("No local authored changes to sync.");
      return;
    }
    const localFiles = await collectLocalFiles(projectRoot);
    const sourceChanges = buildSourceSnapshotChanges(localFiles);
    const { changes } = await materializeSourceChangeOperations(sourceChanges);
    const uploadBlobs = mapUpsertBlobContentsByContentHash(
      sourceChanges,
      changes
    );
    await uploadProjectSourceBlobsSdk(
      nextProjectConfig.projectId,
      Array.from(uploadBlobs.values())
    );
    const sourceFiles = changes.filter((change) => change.kind === "upsert").map(({ path: path3, contentHash, byteSize }) => ({
      path: path3,
      contentHash,
      byteSize
    }));
    const revision = await createGameRevisionSdk({
      projectId: nextProjectConfig.projectId,
      request: {
        ...remoteHeadDigest ? { baseRevisionDigest: remoteHeadDigest } : {},
        source: { files: sourceFiles },
        ruleText: await loadRule(projectRoot),
        manifest: localManifest
      }
    });
    nextProjectConfig = await persistProjectConfig({
      projectRoot,
      projectConfig: {
        ...updateProjectAuthoringState(nextProjectConfig, {
          revisionDigest: revision.revisionDigest,
          sourceTreeHash: revision.sourceTreeHash,
          manifestContentHash: revision.manifestContentHash,
          localManifestContentHash
        }),
        remoteHeadDigest: revision.revisionDigest
      }
    });
    nextProjectConfig = await finalizeLocalSync({
      projectRoot,
      projectConfig: nextProjectConfig
    });
    consola.success(
      `Synced revision ${revision.revisionDigest}. Run 'dreamboard compile' when you're ready.`
    );
  }
});
export {
  sync_default as default
};
//# sourceMappingURL=sync-C6S3OGCD.mjs.map