diff --git a/.changeset/verify-pack-client-workspace.md b/.changeset/verify-pack-client-workspace.md new file mode 100644 index 0000000..4ef40e8 --- /dev/null +++ b/.changeset/verify-pack-client-workspace.md @@ -0,0 +1,16 @@ +--- +'seamless-cli': patch +--- + +`seamless verify --local` packs `@seamless-auth/client` alongside `@seamless-auth/react` +when the React SDK checkout is an npm workspace. + +The client SDK repo is becoming a workspace that publishes a framework-agnostic +`@seamless-auth/client` next to `@seamless-auth/react`, and the react tarball depends on +the client one. Packing only the root of such a checkout would produce the private root +package, and installing the react tarball alone would go to the registry for a client +version that is not published yet. The harness now reads `workspaces` from the checkout's +`package.json`, packs both packages when it finds one, and the react image installs every +tarball in one `npm install` so the dependency resolves from the sibling file. A checkout +that predates the workspace is packed as before. The version line reported for +`@seamless-auth/react` reads the react package's own manifest in a workspace. diff --git a/src/commands/verify.test.ts b/src/commands/verify.test.ts index 1e9cb78..35cec11 100644 --- a/src/commands/verify.test.ts +++ b/src/commands/verify.test.ts @@ -281,6 +281,45 @@ describe("runVerify — local mode", () => { expect(exitSpy).not.toHaveBeenCalled(); }); + it("packs the react SDK as a single package when the checkout has no workspaces", async () => { + await runVerify(["--local"]); + + const packs = callsFor("npm").filter((a) => a[0] === "pack"); + expect(packs).toEqual([["pack", "--pack-destination", expect.stringContaining("react-vendor")]]); + }); + + it("packs the client core alongside react when the checkout is a workspace", async () => { + vi.mocked(fs.readFileSync).mockImplementation((p: never) => { + const s = String(p); + if (s.endsWith("registry.json")) return REGISTRY_JSON as never; + if (s.endsWith("template.json")) + return JSON.stringify({ verify: { flows: ["oauth"] } }) as never; + if (s === "/fake/reactsdk/package.json") + return JSON.stringify({ private: true, workspaces: ["packages/*"] }) as never; + if (s === "/fake/reactsdk/packages/react/package.json") + return JSON.stringify({ version: "0.13.0" }) as never; + return PKG_JSON as never; + }); + + await runVerify(["--local"]); + + const packs = callsFor("npm").filter((a) => a[0] === "pack"); + expect(packs).toEqual([ + [ + "pack", + "-w", + "@seamless-auth/client", + "-w", + "@seamless-auth/react", + "--pack-destination", + expect.stringContaining("react-vendor"), + ], + ]); + // The version line reads the react package's manifest, not the private root. + expect(logSpy.mock.calls.flat().join("\n")).toContain("@seamless-auth/react"); + expect(logSpy.mock.calls.flat().join("\n")).toContain("0.13.0"); + }); + it("falls back to sibling checkouts when the dir env vars are unset", async () => { // Exercises the default path.resolve(...) branches for the templates root and // the react SDK dir; everything "exists" so resolution succeeds. diff --git a/src/commands/verify.ts b/src/commands/verify.ts index e712d71..34dd797 100644 --- a/src/commands/verify.ts +++ b/src/commands/verify.ts @@ -178,13 +178,31 @@ function cleanVendor(): void { } } -// Build + pack the local @seamless-auth/react into ./react-vendor so the react -// service installs it over the published version (--local browser runs). +// The client SDK repo became an npm workspace publishing @seamless-auth/client +// alongside @seamless-auth/react. The react tarball depends on the client one, +// so both are packed into the vendor dir and the image installs them together. +// A checkout that predates the workspace still packs its single root package. +function reactSdkWorkspaces(sdkDir: string): string[] { + const workspaces = readJson(path.join(sdkDir, "package.json"))?.workspaces; + return Array.isArray(workspaces) && workspaces.length > 0 + ? ["@seamless-auth/client", "@seamless-auth/react"] + : []; +} + +// Build + pack the local @seamless-auth/react (and its client core when the +// checkout is a workspace) into ./react-vendor so the react service installs it +// over the published version (--local browser runs). async function packLocalReactSdk(env: NodeJS.ProcessEnv): Promise { const sdkDir = resolveReactSdkDir(); console.log(kleur.cyan("→ Building & packing local @seamless-auth/react…")); await runCommand("npm", ["run", "build"], sdkDir, env); - await runCommand("npm", ["pack", "--pack-destination", REACT_VENDOR_DIR], sdkDir, env); + const workspaceArgs = reactSdkWorkspaces(sdkDir).flatMap((pkg) => ["-w", pkg]); + await runCommand( + "npm", + ["pack", ...workspaceArgs, "--pack-destination", REACT_VENDOR_DIR], + sdkDir, + env, + ); } // Each adapter image installs core plus its own framework package, so the @@ -299,7 +317,12 @@ function collectPackageVersions( } if (opts.react) { try { - push("@seamless-auth/react", readPkgVersion(path.join(resolveReactSdkDir(), "package.json"))); + const sdkDir = resolveReactSdkDir(); + const reactPkg = + reactSdkWorkspaces(sdkDir).length > 0 + ? path.join(sdkDir, "packages", "react", "package.json") + : path.join(sdkDir, "package.json"); + push("@seamless-auth/react", readPkgVersion(reactPkg)); } catch { // React SDK checkout unavailable. } diff --git a/verify/docker-compose.verify.yml b/verify/docker-compose.verify.yml index 622c75c..fec8eec 100644 --- a/verify/docker-compose.verify.yml +++ b/verify/docker-compose.verify.yml @@ -154,8 +154,10 @@ services: # Gated behind the `react` profile so the api/adapter-only runs skip the build. # # The inline Dockerfile mirrors the starter's own Dockerfile, but additionally - # installs a locally-built @seamless-auth/react tarball when present (--local - # mode packs it into ./react-vendor; that dir is empty otherwise → published SDK). + # installs locally-built @seamless-auth/react (and @seamless-auth/client) + # tarballs when present (--local mode packs them into ./react-vendor; that dir + # is empty otherwise → published SDK). Installed in one command so npm resolves + # react's client dependency from the sibling tarball rather than the registry. react: profiles: ['react'] build: @@ -169,8 +171,8 @@ services: RUN npm ci COPY . . COPY --from=sdk . /tmp/sdk/ - RUN if ls /tmp/sdk/seamless-auth-react-*.tgz >/dev/null 2>&1; then \ - npm install /tmp/sdk/seamless-auth-react-*.tgz; \ + RUN if ls /tmp/sdk/*.tgz >/dev/null 2>&1; then \ + npm install /tmp/sdk/*.tgz; \ fi RUN npm run build FROM nginx:alpine