@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.
- package/dist/index.js +225 -26
- 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
|
|
2880
|
-
return join12(dataDir(),
|
|
2879
|
+
function legacyCredentialsPath() {
|
|
2880
|
+
return join12(dataDir(), LEGACY_CREDENTIALS_FILE);
|
|
2881
2881
|
}
|
|
2882
|
-
function
|
|
2883
|
-
|
|
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(
|
|
2886
|
-
`, { mode: 384 });
|
|
2898
|
+
writeFileSync5(path, JSON.stringify({ version: 1, ...reg }, null, 2));
|
|
2887
2899
|
}
|
|
2888
|
-
function
|
|
2889
|
-
const
|
|
2890
|
-
if (
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
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
|
|
2949
|
-
|
|
2950
|
-
|
|
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
|
|
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 (
|
|
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("
|
|
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
|
-
|
|
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.
|
|
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));
|