@construct-space/cli 1.8.0 → 1.9.0

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.
Files changed (2) hide show
  1. package/dist/index.js +225 -26
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2876,26 +2876,92 @@ function listDesktopProfiles() {
2876
2876
  }
2877
2877
  return results;
2878
2878
  }
2879
- function credentialsPath() {
2880
- return join12(dataDir(), CREDENTIALS_FILE);
2879
+ function legacyCredentialsPath() {
2880
+ return join12(dataDir(), LEGACY_CREDENTIALS_FILE);
2881
2881
  }
2882
- function store(creds) {
2883
- const path = credentialsPath();
2882
+ function registryPath() {
2883
+ return join12(dataDir(), PROFILES_REGISTRY);
2884
+ }
2885
+ function readRegistry() {
2886
+ const path = registryPath();
2887
+ if (!existsSync10(path))
2888
+ return {};
2889
+ try {
2890
+ return JSON.parse(readFileSync7(path, "utf-8"));
2891
+ } catch {
2892
+ return {};
2893
+ }
2894
+ }
2895
+ function writeRegistry(reg) {
2896
+ const path = registryPath();
2884
2897
  mkdirSync4(dirname4(path), { recursive: true });
2885
- writeFileSync5(path, JSON.stringify(creds, null, 2) + `
2886
- `, { mode: 384 });
2898
+ writeFileSync5(path, JSON.stringify({ version: 1, ...reg }, null, 2));
2887
2899
  }
2888
- function load2() {
2889
- const path = credentialsPath();
2890
- if (existsSync10(path)) {
2891
- const data = JSON.parse(readFileSync7(path, "utf-8"));
2892
- if (data.token) {
2893
- if (!data.publisherKey && data.token.startsWith("csk_live_")) {
2894
- data.publisherKey = data.token;
2895
- }
2896
- return data;
2897
- }
2900
+ function store(creds) {
2901
+ const profileId = creds.profileId || creds.user?.id;
2902
+ if (!profileId) {
2903
+ throw new Error("cannot store credentials without a profile id (user.id or profileId)");
2904
+ }
2905
+ const profilePath = join12(profilesDir(), profileId, "auth.json");
2906
+ mkdirSync4(dirname4(profilePath), { recursive: true });
2907
+ let existing = {};
2908
+ if (existsSync10(profilePath)) {
2909
+ try {
2910
+ existing = JSON.parse(readFileSync7(profilePath, "utf-8"));
2911
+ } catch {}
2912
+ }
2913
+ existing.token = creds.token;
2914
+ existing.authenticated = true;
2915
+ existing.updated_at = new Date().toISOString();
2916
+ if (creds.user) {
2917
+ existing.user = { ...existing.user, ...creds.user };
2918
+ }
2919
+ if (creds.publisherKey) {
2920
+ existing.publisher = {
2921
+ ...existing.publisher,
2922
+ name: creds.publisherName || existing.publisher?.name || "",
2923
+ kind: creds.publisherKind || existing.publisher?.kind || "user",
2924
+ api_key: creds.publisherKey
2925
+ };
2898
2926
  }
2927
+ writeFileSync5(profilePath, JSON.stringify(existing, null, 2), { mode: 384 });
2928
+ const reg = readRegistry();
2929
+ reg.active_profile = profileId;
2930
+ reg.profiles = reg.profiles || [];
2931
+ const existingEntry = reg.profiles.find((p) => p.id === profileId);
2932
+ if (existingEntry) {
2933
+ if (creds.user?.name)
2934
+ existingEntry.name = creds.user.name;
2935
+ if (creds.user?.email)
2936
+ existingEntry.email = creds.user.email;
2937
+ } else if (creds.user) {
2938
+ reg.profiles.push({
2939
+ id: profileId,
2940
+ name: creds.user.name,
2941
+ email: creds.user.email
2942
+ });
2943
+ }
2944
+ writeRegistry(reg);
2945
+ }
2946
+ function migrateLegacyCredentials() {
2947
+ const legacy = legacyCredentialsPath();
2948
+ if (!existsSync10(legacy))
2949
+ return;
2950
+ try {
2951
+ const data = JSON.parse(readFileSync7(legacy, "utf-8"));
2952
+ if (!data.token)
2953
+ return;
2954
+ const pid = data.profileId || data.user?.id;
2955
+ if (!pid)
2956
+ return;
2957
+ const profilePath = join12(profilesDir(), pid, "auth.json");
2958
+ if (existsSync10(profilePath))
2959
+ return;
2960
+ store({ ...data, profileId: pid });
2961
+ } catch {}
2962
+ }
2963
+ function load2() {
2964
+ migrateLegacyCredentials();
2899
2965
  const fromProfile = loadFromActiveProfile();
2900
2966
  if (fromProfile)
2901
2967
  return fromProfile;
@@ -2945,11 +3011,24 @@ function isAuthenticated() {
2945
3011
  }
2946
3012
  }
2947
3013
  function clear() {
2948
- const path = credentialsPath();
2949
- if (existsSync10(path))
2950
- unlinkSync(path);
3014
+ const reg = readRegistry();
3015
+ const activeId = reg.active_profile;
3016
+ if (activeId) {
3017
+ const profilePath = join12(profilesDir(), activeId, "auth.json");
3018
+ if (existsSync10(profilePath)) {
3019
+ try {
3020
+ const data = JSON.parse(readFileSync7(profilePath, "utf-8"));
3021
+ data.authenticated = false;
3022
+ data.updated_at = new Date().toISOString();
3023
+ writeFileSync5(profilePath, JSON.stringify(data, null, 2), { mode: 384 });
3024
+ } catch {}
3025
+ }
3026
+ }
3027
+ const legacy = legacyCredentialsPath();
3028
+ if (existsSync10(legacy))
3029
+ unlinkSync(legacy);
2951
3030
  }
2952
- var CREDENTIALS_FILE = "credentials.json", DEFAULT_PORTAL = "https://my.construct.space/api/developer";
3031
+ var LEGACY_CREDENTIALS_FILE = "credentials.json", PROFILES_REGISTRY = "profiles.json", DEFAULT_PORTAL = "https://my.construct.space/api/developer";
2953
3032
  var init_auth = __esm(() => {
2954
3033
  init_appdir();
2955
3034
  });
@@ -3011,6 +3090,83 @@ var init_whoami = __esm(() => {
3011
3090
  init_auth();
3012
3091
  });
3013
3092
 
3093
+ // src/commands/org.ts
3094
+ var exports_org = {};
3095
+ __export(exports_org, {
3096
+ orgStatus: () => orgStatus
3097
+ });
3098
+ async function orgStatus() {
3099
+ let creds;
3100
+ try {
3101
+ creds = load2();
3102
+ } catch {
3103
+ console.error(source_default.red("Not signed in."));
3104
+ console.error(source_default.dim("Run 'construct login' first."));
3105
+ process.exit(1);
3106
+ }
3107
+ let s;
3108
+ try {
3109
+ const res = await fetch(ACCOUNTS_SCOPE_URL3, {
3110
+ headers: { Authorization: `Bearer ${creds.token}`, Accept: "application/json" }
3111
+ });
3112
+ if (!res.ok) {
3113
+ console.error(source_default.red(`Scope lookup failed (${res.status}).`));
3114
+ console.error(source_default.dim("Token may be expired. Try: construct login"));
3115
+ process.exit(1);
3116
+ }
3117
+ s = await res.json();
3118
+ } catch (err) {
3119
+ console.error(source_default.red(`Could not reach accounts service: ${err?.message || err}`));
3120
+ process.exit(1);
3121
+ }
3122
+ if (!s.authenticated || !s.user) {
3123
+ console.error(source_default.red("Token rejected."));
3124
+ process.exit(1);
3125
+ }
3126
+ if (s.scope === "org" && s.org) {
3127
+ console.log(source_default.cyan("Scope: ") + source_default.bold(`org \u2014 ${s.org.name || s.org.slug || s.org.id}`));
3128
+ if (s.org.slug)
3129
+ console.log(source_default.dim(` slug: ${s.org.slug}`));
3130
+ if (s.org.id)
3131
+ console.log(source_default.dim(` id: ${s.org.id}`));
3132
+ if (s.roles && s.roles.length) {
3133
+ console.log(source_default.dim(` roles: ${s.roles.join(", ")}`));
3134
+ }
3135
+ } else {
3136
+ console.log(source_default.cyan("Scope: ") + source_default.bold("personal"));
3137
+ console.log(source_default.dim(" (switch to an org at https://my.construct.space)"));
3138
+ }
3139
+ console.log();
3140
+ console.log(source_default.cyan("Signed in as"));
3141
+ console.log(` ${s.user.email || s.user.username || s.user.uuid || "(unknown)"}`);
3142
+ if (s.user.uuid)
3143
+ console.log(source_default.dim(` ${s.user.uuid}`));
3144
+ console.log();
3145
+ console.log(source_default.cyan("Publisher (from auth.json)"));
3146
+ if (creds.publisherKey) {
3147
+ const kind = creds.publisherKind || "user";
3148
+ const expected = s.scope === "org" ? "org" : "user";
3149
+ const matches = kind === expected;
3150
+ console.log(` ${source_default.bold(creds.publisherName || "(unnamed)")} ${source_default.dim(`[${kind}]`)}`);
3151
+ console.log(source_default.dim(` key: ${creds.publisherKey.slice(0, 14)}\u2026`));
3152
+ if (!matches) {
3153
+ console.log(source_default.yellow(` \u26A0 publisher kind (${kind}) doesn't match active scope (${expected}).`));
3154
+ console.log(source_default.dim(" Restart the desktop app or run 'construct login' to re-sync."));
3155
+ }
3156
+ } else {
3157
+ console.log(source_default.dim(" (none \u2014 enroll as a developer to get a publisher key)"));
3158
+ }
3159
+ if (s.developer) {
3160
+ console.log();
3161
+ console.log(source_default.dim("developer capability: ") + source_default.green("enabled"));
3162
+ }
3163
+ }
3164
+ var ACCOUNTS_SCOPE_URL3 = "https://my.construct.space/api/accounts/me/scope";
3165
+ var init_org = __esm(() => {
3166
+ init_source();
3167
+ init_auth();
3168
+ });
3169
+
3014
3170
  // src/lib/graphClient.ts
3015
3171
  function graphBaseURL() {
3016
3172
  return process.env.GRAPH_URL || "https://graph.construct.space";
@@ -10389,6 +10545,13 @@ async function publish(options) {
10389
10545
  const yes = options?.yes ?? false;
10390
10546
  const wantPrivate = options?.private ?? false;
10391
10547
  const wantPublic = options?.public ?? false;
10548
+ const scopeOpt = options?.scope ? options.scope.toLowerCase() : undefined;
10549
+ if (scopeOpt && scopeOpt !== "user" && scopeOpt !== "org") {
10550
+ console.error(source_default.red(`--scope must be 'user' or 'org', got '${options?.scope}'.`));
10551
+ process.exit(1);
10552
+ }
10553
+ const wantOrgScope = scopeOpt === "org";
10554
+ const wantUserScope = scopeOpt === "user";
10392
10555
  if (wantPrivate && wantPublic) {
10393
10556
  console.error(source_default.red("Cannot combine --private and --public. Pick one."));
10394
10557
  process.exit(1);
@@ -10482,10 +10645,19 @@ async function publish(options) {
10482
10645
  console.log(source_default.dim(" (Org Settings \u2192 Developer), then re-run this command."));
10483
10646
  console.log();
10484
10647
  }
10485
- if (wantPrivate && creds.publisherKind !== "org") {
10648
+ if (wantUserScope && creds.publisherKind === "org" && !options?.apiKey) {
10649
+ console.error(source_default.red("--scope=user, but the active publisher in auth.json is an org publisher."));
10650
+ console.error(source_default.dim(" Switch context to personal in the desktop app, or pass --api-key with a personal key."));
10651
+ process.exit(1);
10652
+ }
10653
+ if (wantOrgScope && creds.publisherKind === "user" && !options?.apiKey) {
10654
+ creds = { ...creds, publisherKey: undefined, publisherKind: undefined, publisherName: undefined };
10655
+ }
10656
+ if (wantPrivate && creds.publisherKind !== "org" && !wantOrgScope) {
10486
10657
  console.error(source_default.red("--private requires an org publisher key."));
10487
10658
  console.error(source_default.dim(" Personal publishes are always public."));
10488
- console.error(source_default.dim(" Enrol an org from the desktop app (Org Settings \u2192 Developer) and re-run."));
10659
+ console.error(source_default.dim(" Pass --scope=org to publish as your active org, or enrol an org"));
10660
+ console.error(source_default.dim(" from the desktop app (Org Settings \u2192 Developer) first."));
10489
10661
  process.exit(1);
10490
10662
  }
10491
10663
  console.log();
@@ -10530,12 +10702,24 @@ async function publish(options) {
10530
10702
  const uploadSpinner = ora("Uploading & building...").start();
10531
10703
  try {
10532
10704
  const explicitKey = options?.apiKey || process.env.CONSTRUCT_PUBLISHER_KEY;
10533
- const initialKey = explicitKey || creds.publisherKey;
10705
+ let initialKey = explicitKey || creds.publisherKey;
10706
+ if (wantOrgScope && !explicitKey && !initialKey) {
10707
+ uploadSpinner.text = "Fetching org publisher key\u2026";
10708
+ const orgKey = await fetchOrgPublisherKey(creds.portal, creds.token);
10709
+ if (!orgKey) {
10710
+ uploadSpinner.fail("No org publisher available for --scope=org");
10711
+ console.error(source_default.dim(" Switch into an org context in the desktop app, or pass --api-key"));
10712
+ console.error(source_default.dim(" with a csk_live_* key the org owns."));
10713
+ unlinkSync2(tarballPath);
10714
+ process.exit(1);
10715
+ }
10716
+ initialKey = orgKey;
10717
+ }
10534
10718
  let result;
10535
10719
  try {
10536
10720
  result = await uploadSource(creds.portal, creds.token, initialKey, tarballPath, m, { private: wantPrivate, public: wantPublic });
10537
10721
  } catch (e) {
10538
- if (e?.ownerKind === "org" && !creds.publisherKey) {
10722
+ if (e?.ownerKind === "org" && !creds.publisherKey && !wantUserScope) {
10539
10723
  uploadSpinner.text = "Fetching org publisher key\u2026";
10540
10724
  const orgKey = await fetchOrgPublisherKey(creds.portal, creds.token);
10541
10725
  if (!orgKey)
@@ -10786,6 +10970,19 @@ async function loginFromProfile(profiles) {
10786
10970
  console.log(source_default.dim(` profile: ${picked.id}`));
10787
10971
  }
10788
10972
  async function loginWithToken(token) {
10973
+ if (token.startsWith("cst_live_")) {
10974
+ console.error(source_default.red("cst_live_* tokens are being retired."));
10975
+ console.error(source_default.dim(" Sign in via the Construct desktop app, or get a new"));
10976
+ console.error(source_default.dim(` identity token at ${source_default.cyan("https://my.construct.space/settings/tokens")}.`));
10977
+ process.exit(1);
10978
+ }
10979
+ if (token.startsWith("csk_live_")) {
10980
+ console.error(source_default.red("That's a publisher key, not an identity token."));
10981
+ console.error(source_default.dim(" Publisher keys (csk_live_*) authorize publish-as-publisher"));
10982
+ console.error(source_default.dim(" on top of identity. Pass an identity token (cat_*) here;"));
10983
+ console.error(source_default.dim(" the CLI loads your publisher key automatically once logged in."));
10984
+ process.exit(1);
10985
+ }
10789
10986
  let resp;
10790
10987
  try {
10791
10988
  resp = await fetch(ACCOUNTS_SCOPE_URL, {
@@ -11501,7 +11698,7 @@ function graphFork(newSpaceID) {
11501
11698
  // package.json
11502
11699
  var package_default = {
11503
11700
  name: "@construct-space/cli",
11504
- version: "1.8.0",
11701
+ version: "1.9.0",
11505
11702
  description: "Construct CLI \u2014 scaffold, build, develop, and publish spaces",
11506
11703
  type: "module",
11507
11704
  bin: {
@@ -11553,7 +11750,7 @@ program2.command("scaffold [name]").alias("new").alias("create").description("Cr
11553
11750
  program2.command("build").description("Build the space (generate entry + run Vite)").option("--entry-only", "Only generate src/entry.ts").action(async (opts) => build(opts));
11554
11751
  program2.command("dev").description("Start dev mode with file watching and live reload").action(async () => dev());
11555
11752
  program2.command("install").alias("run").description("Install built space to Construct spaces directory").action(() => install());
11556
- program2.command("publish").description("Publish a space to the Construct registry").option("-y, --yes", "Skip all confirmation prompts").option("--bump <type>", "Auto-bump version (patch, minor, major)").option("--private", "Publish as org-private (catalog-listed only inside the owning org). Requires an org publisher key.").option("--public", "Flip a previously-private space back to the public catalog on this publish. Without --private or --public, visibility is preserved on update (and defaults to public on first publish).").option("--api-key <key>", "Publisher API key (csk_live_\u2026). Overrides any key stored in the active profile. Also reads CONSTRUCT_PUBLISHER_KEY.").action(async (opts) => publish(opts));
11753
+ program2.command("publish").description("Publish a space to the Construct registry").option("-y, --yes", "Skip all confirmation prompts").option("--bump <type>", "Auto-bump version (patch, minor, major)").option("--private", "Publish as org-private (catalog-listed only inside the owning org). Requires an org publisher key.").option("--public", "Flip a previously-private space back to the public catalog on this publish. Without --private or --public, visibility is preserved on update (and defaults to public on first publish).").option("--scope <scope>", "Publish as 'user' (personal) or 'org'. Without this flag, the active publisher in auth.json decides.").option("--api-key <key>", "Publisher API key (csk_live_\u2026). Overrides any key stored in the active profile. Also reads CONSTRUCT_PUBLISHER_KEY.").action(async (opts) => publish(opts));
11557
11754
  program2.command("validate").description("Validate space.manifest.json").action(() => validate3());
11558
11755
  program2.command("check").description("Run type-check (vue-tsc) and linter (eslint)").action(() => check());
11559
11756
  program2.command("clean").description("Remove build artifacts").option("--all", "Also remove node_modules and lockfiles").action((opts) => clean(opts));
@@ -11561,6 +11758,8 @@ program2.command("login").description("Authenticate with Construct").option("--p
11561
11758
  program2.command("logout").description("Sign out").action(() => logout());
11562
11759
  program2.command("update").description("Update the CLI to the latest version").action(() => update());
11563
11760
  program2.command("whoami").alias("status").description("Show the signed-in user + current org scope").action(async () => (await Promise.resolve().then(() => (init_whoami(), exports_whoami))).whoami());
11761
+ var org = program2.command("org").description("Inspect the active organization context");
11762
+ org.command("status").description("Show active scope, publisher, and roles").action(async () => (await Promise.resolve().then(() => (init_org(), exports_org))).orgStatus());
11564
11763
  var graph = program2.command("graph").description("Construct Graph \u2014 data models and GraphQL");
11565
11764
  graph.command("init").description("Initialize Graph in a space project").action(() => graphInit());
11566
11765
  graph.command("generate <model> [fields...]").alias("g").description("Generate a data model").option("--access <rules>", "Access rules (e.g. read:member,create:member,update:owner,delete:admin)").action((model, fields, opts) => generate2(model, fields, opts));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@construct-space/cli",
3
- "version": "1.8.0",
3
+ "version": "1.9.0",
4
4
  "description": "Construct CLI — scaffold, build, develop, and publish spaces",
5
5
  "type": "module",
6
6
  "bin": {