synthesisui 0.16.57 → 0.16.59

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.
@@ -1,6 +1,6 @@
1
1
  import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
2
- import { basename, join, relative } from "node:path";
3
- import { readToken, resolveRegistry } from "../config.js";
2
+ import { basename, dirname, join, relative } from "node:path";
3
+ import { readCredentials, readToken, resolveRegistry, sameRegistry, } from "../config.js";
4
4
  import { isNearDuplicate, isNeutral, lightness, } from "../doctor/color-distance.js";
5
5
  import { emptyTally, internalSpecifiers, scanComponentsInto, tallyToInventory, } from "../doctor/components-scan.js";
6
6
  import { crosswalk, observedRules } from "../doctor/crosswalk.js";
@@ -894,8 +894,12 @@ export async function runImport(opts) {
894
894
  summarize(census);
895
895
  if (!opts.census)
896
896
  await sayIfWorkspace(root);
897
- const out = join(root, "_synthesisui", "census.json");
898
- await mkdir(join(root, "_synthesisui"), { recursive: true });
897
+ // A census handed to us is written BACK TO ITSELF. Deriving the path from the
898
+ // root instead planted a second census at the repo root when the real one
899
+ // lived in `packages/ui/_synthesisui/` - two files, silently disagreeing
900
+ // about which import happened (dono, 31/07).
901
+ const out = opts.census ?? join(root, "_synthesisui", "census.json");
902
+ await mkdir(dirname(out), { recursive: true });
899
903
  await writeFile(out, `${JSON.stringify(census, null, 2)}\n`, "utf8");
900
904
  console.log("");
901
905
  console.log(body(`Written to ${paint.strong(relative(root, out))}`));
@@ -944,6 +948,20 @@ export async function runImport(opts) {
944
948
  return;
945
949
  }
946
950
  const base = resolveRegistry(opts.registry);
951
+ // A token belongs to the host that issued it. Sending it anywhere else earns
952
+ // a 401 that describes the wrong problem entirely.
953
+ const creds = await readCredentials();
954
+ if (creds && !sameRegistry(creds.registry, base)) {
955
+ console.log("");
956
+ console.log(body(`You are logged in to ${paint.strong(creds.registry ?? "another registry")}, and this would go to ${paint.strong(base)}.`));
957
+ console.log(body("The census is on disk. Either name the one you meant:"));
958
+ console.log("");
959
+ console.log(body(` synthesisui import --census ${out} --registry ${creds.registry}`));
960
+ console.log(body("or log in to this one:"));
961
+ console.log(body(` synthesisui login --registry ${base}`));
962
+ console.log("");
963
+ return;
964
+ }
947
965
  const res = await fetch(`${base}/api/onboarding/import`, {
948
966
  method: "POST",
949
967
  headers: {
@@ -960,6 +978,18 @@ export async function runImport(opts) {
960
978
  .catch(() => null)
961
979
  : null;
962
980
  console.log("");
981
+ // A 401 is not a refusal of the census, it is an expired session - and
982
+ // saying "HTTP 401" at the end of a long read leaves the person guessing at
983
+ // which of the two it was (dono, 31/07). A stale token gets past the
984
+ // `readToken` guard above and only fails here.
985
+ if (res?.status === 401) {
986
+ console.log(body("Your session has expired. The census is on disk."));
987
+ console.log("");
988
+ console.log(body(` ${paint.strong("synthesisui login")}`));
989
+ console.log(body(` synthesisui import --census ${out}${chosen ? ` --name "${chosen}"` : ""}`));
990
+ console.log("");
991
+ return;
992
+ }
963
993
  console.log(body(res
964
994
  ? `The registry refused it (HTTP ${res.status})${detail ? `: ${detail}` : "."}`
965
995
  : "Could not reach the registry. The census is on disk - try again later."));
package/dist/config.js CHANGED
@@ -17,6 +17,33 @@ export const credentialsPath = join(homedir(), ".synthesisui", "credentials.json
17
17
  * Reads the saved token, if any. Optional for now (open gate); the device-flow
18
18
  * is what writes it. Sent as a Bearer header when present.
19
19
  */
20
+ /**
21
+ * The token AND the registry it was issued for.
22
+ *
23
+ * They were always written together and never read together, so a token minted
24
+ * by `login --registry http://localhost:3000` was sent to production, which
25
+ * refused it with a 401 that read as "your session expired" (dono, 31/07). A
26
+ * session cannot expire on a host that never issued it.
27
+ */
28
+ export async function readCredentials() {
29
+ try {
30
+ const raw = await readFile(credentialsPath, "utf8");
31
+ const parsed = JSON.parse(raw);
32
+ if (!parsed.token)
33
+ return null;
34
+ return { token: parsed.token, registry: parsed.registry ?? null };
35
+ }
36
+ catch {
37
+ return null;
38
+ }
39
+ }
40
+ /** Two registry URLs, compared the way a person means them. */
41
+ export function sameRegistry(a, b) {
42
+ if (!a || !b)
43
+ return true;
44
+ const norm = (u) => u.trim().replace(/\/+$/, "").toLowerCase();
45
+ return norm(a) === norm(b);
46
+ }
20
47
  export async function readToken() {
21
48
  try {
22
49
  const raw = await readFile(credentialsPath, "utf8");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.57",
3
+ "version": "0.16.59",
4
4
  "description": "Bring SynthesisUI design systems into any project - tokens, typed components, whole pages and an agent-ready CLAUDE.md manifest.",
5
5
  "type": "module",
6
6
  "bin": {