@s2script/sdk 0.7.0 → 0.8.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/chat.d.ts +7 -2
- package/dist/cli.js +259 -57
- package/package.json +1 -1
package/chat.d.ts
CHANGED
|
@@ -11,8 +11,13 @@ export declare const Chat: {
|
|
|
11
11
|
/**
|
|
12
12
|
* An opaque prefix prepended to every chat message (NOT rcon/console replies). Color is content the
|
|
13
13
|
* caller owns (SourceMod-parity), so the engine-generic layer never picks one. A game/plugin sets this
|
|
14
|
-
* to a color control byte — e.g. `Chat.color = ChatColors.Green` from `@s2script/cs2`. `""` =
|
|
15
|
-
*
|
|
14
|
+
* to a color control byte — e.g. `Chat.color = ChatColors.Green` from `@s2script/cs2`. `""` = no prefix.
|
|
15
|
+
* A message may also embed its own colors mid-string.
|
|
16
|
+
*
|
|
17
|
+
* You do NOT need a leading space for color to render: every chat line is auto-prefixed with an invisible
|
|
18
|
+
* zero-width space (U+200B) so a Source 2 chat box doesn't swallow the message's first color byte. Just
|
|
19
|
+
* write `Chat.toSlot(slot, ChatColors.Green + "hi")` — the green lands. (Idempotent: a line you already
|
|
20
|
+
* lead with a space or a ZWSP is passed through unchanged.)
|
|
16
21
|
*/
|
|
17
22
|
color: string;
|
|
18
23
|
/** Print to the chat of the client in `slot` (0-based). */
|
package/dist/cli.js
CHANGED
|
@@ -3075,8 +3075,15 @@ import { join as join7 } from "node:path";
|
|
|
3075
3075
|
function credentialsPath() {
|
|
3076
3076
|
return join7(homedir(), ".s2script", "credentials.json");
|
|
3077
3077
|
}
|
|
3078
|
+
function normalizeRegistryUrl(url) {
|
|
3079
|
+
const trimmed = url.replace(/\/$/, "");
|
|
3080
|
+
if (trimmed === "https://s2script.com") return "https://www.s2script.com";
|
|
3081
|
+
return trimmed;
|
|
3082
|
+
}
|
|
3078
3083
|
function defaultRegistryUrl() {
|
|
3079
|
-
|
|
3084
|
+
const fromEnv = process.env.S2SCRIPT_REGISTRY_URL;
|
|
3085
|
+
if (fromEnv) return normalizeRegistryUrl(fromEnv);
|
|
3086
|
+
return "https://www.s2script.com";
|
|
3080
3087
|
}
|
|
3081
3088
|
function loadCredentials() {
|
|
3082
3089
|
const fromEnv = process.env.S2SCRIPT_TOKEN || process.env.S2SCRIPT_DEPLOY_TOKEN;
|
|
@@ -3089,7 +3096,7 @@ function loadCredentials() {
|
|
|
3089
3096
|
const j = JSON.parse(readFileSync6(p, "utf8"));
|
|
3090
3097
|
if (!j.token) return null;
|
|
3091
3098
|
return {
|
|
3092
|
-
registryUrl: (j.registryUrl || defaultRegistryUrl())
|
|
3099
|
+
registryUrl: normalizeRegistryUrl(j.registryUrl || defaultRegistryUrl()),
|
|
3093
3100
|
token: j.token
|
|
3094
3101
|
};
|
|
3095
3102
|
} catch {
|
|
@@ -3102,7 +3109,7 @@ function saveCredentials(creds) {
|
|
|
3102
3109
|
writeFileSync3(
|
|
3103
3110
|
credentialsPath(),
|
|
3104
3111
|
JSON.stringify(
|
|
3105
|
-
{ registryUrl: creds.registryUrl
|
|
3112
|
+
{ registryUrl: normalizeRegistryUrl(creds.registryUrl), token: creds.token },
|
|
3106
3113
|
null,
|
|
3107
3114
|
2
|
|
3108
3115
|
) + "\n",
|
|
@@ -3112,13 +3119,62 @@ function saveCredentials(creds) {
|
|
|
3112
3119
|
|
|
3113
3120
|
// src/registry/client.ts
|
|
3114
3121
|
var RegistryError = class extends Error {
|
|
3122
|
+
// Fields are declared/assigned explicitly rather than as constructor parameter
|
|
3123
|
+
// properties: the test runner loads src/*.ts under node's strip-only type
|
|
3124
|
+
// stripping, which rejects `constructor(public status: number)`.
|
|
3115
3125
|
constructor(message, status, body) {
|
|
3116
3126
|
super(message);
|
|
3127
|
+
this.name = "RegistryError";
|
|
3117
3128
|
this.status = status;
|
|
3118
3129
|
this.body = body;
|
|
3119
|
-
this.name = "RegistryError";
|
|
3120
3130
|
}
|
|
3121
3131
|
};
|
|
3132
|
+
var TIMEOUTS_MS = {
|
|
3133
|
+
deploy: 3e5,
|
|
3134
|
+
// up to ~40 MiB on a slow uplink
|
|
3135
|
+
resolve: 3e4,
|
|
3136
|
+
meta: 3e4,
|
|
3137
|
+
"types download": 12e4
|
|
3138
|
+
};
|
|
3139
|
+
function rootCause(err2) {
|
|
3140
|
+
let cur = err2;
|
|
3141
|
+
for (let i2 = 0; i2 < 5 && cur instanceof Error && cur.cause; i2++) cur = cur.cause;
|
|
3142
|
+
return cur instanceof Error ? cur.message : String(cur);
|
|
3143
|
+
}
|
|
3144
|
+
function hintFor(status, raw) {
|
|
3145
|
+
if (status === 401) return "run `s2s login` (or set S2SCRIPT_TOKEN)";
|
|
3146
|
+
if (status === 403 && /cross-site/i.test(raw)) {
|
|
3147
|
+
return "the request was rejected before it reached the registry API \u2014 this s2s CLI and the server are incompatible; run: npm i -g @s2script/sdk@latest";
|
|
3148
|
+
}
|
|
3149
|
+
if (status === 403) return "the token may lack permission for this package, or belong to another account \u2014 check `s2s login`";
|
|
3150
|
+
if (status === 413) return "the plugin or its types exceed the registry size limits \u2014 trim the archive and retry";
|
|
3151
|
+
if (status === 415) return "the server rejected this CLI's request format; run: npm i -g @s2script/sdk@latest";
|
|
3152
|
+
if (status === 429) return "rate limited \u2014 wait a minute and retry";
|
|
3153
|
+
if (status >= 500) return "server-side failure \u2014 retry in a moment; if it persists, report it with the message above";
|
|
3154
|
+
return "";
|
|
3155
|
+
}
|
|
3156
|
+
async function errorFromResponse(res, op) {
|
|
3157
|
+
const raw = await res.text().catch(() => "");
|
|
3158
|
+
let body;
|
|
3159
|
+
let detail;
|
|
3160
|
+
try {
|
|
3161
|
+
body = JSON.parse(raw);
|
|
3162
|
+
const b = body;
|
|
3163
|
+
if (b && typeof b.error === "string") detail = b.error;
|
|
3164
|
+
else if (b && typeof b.message === "string") detail = b.message;
|
|
3165
|
+
} catch {
|
|
3166
|
+
}
|
|
3167
|
+
if (!detail) {
|
|
3168
|
+
if (/^\s*</.test(raw)) detail = "server returned an HTML error page";
|
|
3169
|
+
else if (raw.trim()) detail = raw.trim().slice(0, 200);
|
|
3170
|
+
else detail = res.statusText || "no response body";
|
|
3171
|
+
}
|
|
3172
|
+
let msg = `${op} failed (HTTP ${res.status}): ${detail}`;
|
|
3173
|
+
const hint = hintFor(res.status, raw);
|
|
3174
|
+
if (hint) msg += `
|
|
3175
|
+
hint: ${hint}`;
|
|
3176
|
+
return new RegistryError(msg, res.status, body ?? raw);
|
|
3177
|
+
}
|
|
3122
3178
|
var RegistryClient = class {
|
|
3123
3179
|
constructor(opts) {
|
|
3124
3180
|
this.baseUrl = opts.baseUrl.replace(/\/$/, "");
|
|
@@ -3130,67 +3186,80 @@ var RegistryClient = class {
|
|
|
3130
3186
|
if (this.token) h2.Authorization = `Bearer ${this.token}`;
|
|
3131
3187
|
return h2;
|
|
3132
3188
|
}
|
|
3189
|
+
async request(op, url, init) {
|
|
3190
|
+
const timeoutMs = TIMEOUTS_MS[op];
|
|
3191
|
+
const host = new URL(String(url)).host;
|
|
3192
|
+
let res;
|
|
3193
|
+
try {
|
|
3194
|
+
res = await this.fetchFn(url, {
|
|
3195
|
+
method: init?.method ?? "GET",
|
|
3196
|
+
// Cast: a Uint8Array is a valid BodyInit at runtime, but the ambient
|
|
3197
|
+
// fetch typings here only accept ArrayBuffer-backed views.
|
|
3198
|
+
body: init?.body,
|
|
3199
|
+
headers: this.headers(init?.contentType ? { "content-type": init.contentType } : void 0),
|
|
3200
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
3201
|
+
});
|
|
3202
|
+
} catch (e) {
|
|
3203
|
+
const name = e instanceof Error ? e.name : "";
|
|
3204
|
+
if (name === "TimeoutError" || name === "AbortError") {
|
|
3205
|
+
throw new RegistryError(
|
|
3206
|
+
`${op}: timed out after ${timeoutMs / 1e3}s talking to ${host}
|
|
3207
|
+
hint: check your connection or set S2SCRIPT_REGISTRY_URL if you meant a different registry`,
|
|
3208
|
+
0
|
|
3209
|
+
);
|
|
3210
|
+
}
|
|
3211
|
+
throw new RegistryError(
|
|
3212
|
+
`${op}: network error talking to ${host} \u2014 ${rootCause(e)}
|
|
3213
|
+
hint: check your connection, proxy, and that ${host} is reachable`,
|
|
3214
|
+
0,
|
|
3215
|
+
e
|
|
3216
|
+
);
|
|
3217
|
+
}
|
|
3218
|
+
if (!res.ok) throw await errorFromResponse(res, op);
|
|
3219
|
+
return res;
|
|
3220
|
+
}
|
|
3221
|
+
async json(res, op) {
|
|
3222
|
+
try {
|
|
3223
|
+
return await res.json();
|
|
3224
|
+
} catch {
|
|
3225
|
+
throw new RegistryError(`${op}: server returned invalid JSON (HTTP ${res.status})`, res.status);
|
|
3226
|
+
}
|
|
3227
|
+
}
|
|
3133
3228
|
async deploy(opts) {
|
|
3134
|
-
const
|
|
3135
|
-
|
|
3136
|
-
|
|
3229
|
+
const entries = {
|
|
3230
|
+
"manifest.json": new TextEncoder().encode(JSON.stringify(opts.manifest)),
|
|
3231
|
+
"plugin.s2sp": new Uint8Array(opts.s2sp)
|
|
3232
|
+
};
|
|
3137
3233
|
if (opts.types && opts.types.length) {
|
|
3138
|
-
|
|
3234
|
+
entries["types.tgz"] = new Uint8Array(opts.types);
|
|
3139
3235
|
}
|
|
3140
|
-
const
|
|
3236
|
+
const body = zipSync(entries, { level: 0 });
|
|
3237
|
+
const res = await this.request("deploy", `${this.baseUrl}/api/v1/deploy`, {
|
|
3141
3238
|
method: "POST",
|
|
3142
|
-
|
|
3143
|
-
|
|
3239
|
+
body,
|
|
3240
|
+
contentType: "application/octet-stream"
|
|
3144
3241
|
});
|
|
3145
|
-
|
|
3146
|
-
if (!res.ok) {
|
|
3147
|
-
throw new RegistryError(
|
|
3148
|
-
body.error || `deploy failed (${res.status})`,
|
|
3149
|
-
res.status,
|
|
3150
|
-
body
|
|
3151
|
-
);
|
|
3152
|
-
}
|
|
3153
|
-
return body;
|
|
3242
|
+
return this.json(res, "deploy");
|
|
3154
3243
|
}
|
|
3155
3244
|
async resolve(name, range = "*") {
|
|
3156
3245
|
const u4 = new URL(`${this.baseUrl}/api/v1/resolve`);
|
|
3157
3246
|
u4.searchParams.set("name", name);
|
|
3158
3247
|
u4.searchParams.set("range", range);
|
|
3159
|
-
const res = await this.
|
|
3160
|
-
|
|
3161
|
-
if (!res.ok) {
|
|
3162
|
-
throw new RegistryError(
|
|
3163
|
-
body.message || `resolve failed (${res.status})`,
|
|
3164
|
-
res.status,
|
|
3165
|
-
body
|
|
3166
|
-
);
|
|
3167
|
-
}
|
|
3168
|
-
return body;
|
|
3248
|
+
const res = await this.request("resolve", u4);
|
|
3249
|
+
return this.json(res, "resolve");
|
|
3169
3250
|
}
|
|
3170
3251
|
async downloadTypes(name, version) {
|
|
3171
3252
|
const u4 = new URL(`${this.baseUrl}/api/v1/download/types`);
|
|
3172
3253
|
u4.searchParams.set("name", name);
|
|
3173
3254
|
u4.searchParams.set("version", version);
|
|
3174
|
-
const res = await this.
|
|
3175
|
-
if (!res.ok) {
|
|
3176
|
-
const body = await res.json().catch(() => ({}));
|
|
3177
|
-
throw new RegistryError(
|
|
3178
|
-
body.message || `types download failed (${res.status})`,
|
|
3179
|
-
res.status,
|
|
3180
|
-
body
|
|
3181
|
-
);
|
|
3182
|
-
}
|
|
3255
|
+
const res = await this.request("types download", u4);
|
|
3183
3256
|
return Buffer.from(await res.arrayBuffer());
|
|
3184
3257
|
}
|
|
3185
3258
|
async meta(name) {
|
|
3186
3259
|
const u4 = new URL(`${this.baseUrl}/api/v1/meta`);
|
|
3187
3260
|
u4.searchParams.set("name", name);
|
|
3188
|
-
const res = await this.
|
|
3189
|
-
|
|
3190
|
-
if (!res.ok) {
|
|
3191
|
-
throw new RegistryError(`meta failed (${res.status})`, res.status, body);
|
|
3192
|
-
}
|
|
3193
|
-
return body;
|
|
3261
|
+
const res = await this.request("meta", u4);
|
|
3262
|
+
return this.json(res, "meta");
|
|
3194
3263
|
}
|
|
3195
3264
|
};
|
|
3196
3265
|
|
|
@@ -3724,26 +3793,158 @@ async function run4(argv2) {
|
|
|
3724
3793
|
}
|
|
3725
3794
|
}
|
|
3726
3795
|
|
|
3796
|
+
// src/registry/login.ts
|
|
3797
|
+
import { hostname } from "node:os";
|
|
3798
|
+
|
|
3799
|
+
// src/registry/device-flow.ts
|
|
3800
|
+
var REQUEST_TIMEOUT_MS = 3e4;
|
|
3801
|
+
function rootCause2(err2) {
|
|
3802
|
+
let cur = err2;
|
|
3803
|
+
for (let i2 = 0; i2 < 5 && cur instanceof Error && cur.cause; i2++) cur = cur.cause;
|
|
3804
|
+
return cur instanceof Error ? cur.message : String(cur);
|
|
3805
|
+
}
|
|
3806
|
+
async function describeError(res) {
|
|
3807
|
+
const raw = await res.text().catch(() => "");
|
|
3808
|
+
try {
|
|
3809
|
+
const body = JSON.parse(raw);
|
|
3810
|
+
if (body && typeof body.error === "string") return body.error;
|
|
3811
|
+
if (body && typeof body.message === "string") return body.message;
|
|
3812
|
+
} catch {
|
|
3813
|
+
}
|
|
3814
|
+
if (/^\s*</.test(raw)) return "server returned an HTML error page";
|
|
3815
|
+
if (raw.trim()) return raw.trim().slice(0, 200);
|
|
3816
|
+
return res.statusText || "no response body";
|
|
3817
|
+
}
|
|
3818
|
+
async function startDeviceAuth(registryUrl, opts) {
|
|
3819
|
+
const fetchFn = opts?.fetchFn ?? fetch;
|
|
3820
|
+
let res;
|
|
3821
|
+
try {
|
|
3822
|
+
res = await fetchFn(`${registryUrl}/api/v1/auth/device/start`, {
|
|
3823
|
+
method: "POST",
|
|
3824
|
+
headers: { "content-type": "application/json" },
|
|
3825
|
+
body: JSON.stringify({ client: opts?.client }),
|
|
3826
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
3827
|
+
});
|
|
3828
|
+
} catch (e) {
|
|
3829
|
+
throw new Error(`could not reach ${registryUrl} \u2014 ${rootCause2(e)}`);
|
|
3830
|
+
}
|
|
3831
|
+
if (res.status === 404 || res.status === 405) return null;
|
|
3832
|
+
if (!res.ok) {
|
|
3833
|
+
throw new Error(`login start failed (HTTP ${res.status}): ${await describeError(res)}`);
|
|
3834
|
+
}
|
|
3835
|
+
const body = await res.json().catch(() => null);
|
|
3836
|
+
if (!body || typeof body.deviceCode !== "string" || typeof body.userCode !== "string") {
|
|
3837
|
+
throw new Error(`login start returned an unexpected response from ${registryUrl}`);
|
|
3838
|
+
}
|
|
3839
|
+
return body;
|
|
3840
|
+
}
|
|
3841
|
+
async function pollForToken(registryUrl, start, opts) {
|
|
3842
|
+
const fetchFn = opts?.fetchFn ?? fetch;
|
|
3843
|
+
const sleep = opts?.sleep ?? ((ms) => new Promise((r2) => setTimeout(r2, ms)));
|
|
3844
|
+
let interval = Math.max(1, start.interval);
|
|
3845
|
+
const deadline = Date.now() + start.expiresIn * 1e3;
|
|
3846
|
+
while (Date.now() < deadline) {
|
|
3847
|
+
await sleep(interval * 1e3);
|
|
3848
|
+
let res;
|
|
3849
|
+
try {
|
|
3850
|
+
res = await fetchFn(`${registryUrl}/api/v1/auth/device/poll`, {
|
|
3851
|
+
method: "POST",
|
|
3852
|
+
headers: { "content-type": "application/json" },
|
|
3853
|
+
body: JSON.stringify({ deviceCode: start.deviceCode }),
|
|
3854
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
3855
|
+
});
|
|
3856
|
+
} catch (e) {
|
|
3857
|
+
throw new Error(`could not reach ${registryUrl} \u2014 ${rootCause2(e)}`);
|
|
3858
|
+
}
|
|
3859
|
+
if (!res.ok) {
|
|
3860
|
+
throw new Error(`login poll failed (HTTP ${res.status}): ${await describeError(res)}`);
|
|
3861
|
+
}
|
|
3862
|
+
const body = await res.json().catch(() => null);
|
|
3863
|
+
if (!body) throw new Error("login poll returned a non-JSON response");
|
|
3864
|
+
switch (body.status) {
|
|
3865
|
+
case "pending":
|
|
3866
|
+
break;
|
|
3867
|
+
case "slow_down":
|
|
3868
|
+
interval += 5;
|
|
3869
|
+
break;
|
|
3870
|
+
case "approved":
|
|
3871
|
+
if (!body.token) throw new Error("server approved the login but sent no token");
|
|
3872
|
+
return { token: body.token, tokenName: body.tokenName };
|
|
3873
|
+
case "denied":
|
|
3874
|
+
throw new Error("login request was denied in the browser");
|
|
3875
|
+
case "expired":
|
|
3876
|
+
throw new Error("login request expired \u2014 run `s2s login` again");
|
|
3877
|
+
default:
|
|
3878
|
+
throw new Error(`unexpected login status: ${body.status}`);
|
|
3879
|
+
}
|
|
3880
|
+
}
|
|
3881
|
+
throw new Error("login request expired \u2014 run `s2s login` again");
|
|
3882
|
+
}
|
|
3883
|
+
|
|
3884
|
+
// src/registry/open-browser.ts
|
|
3885
|
+
import { spawn } from "node:child_process";
|
|
3886
|
+
function tryOpenBrowser(url) {
|
|
3887
|
+
if (process.platform === "linux" && !process.env.DISPLAY && !process.env.WAYLAND_DISPLAY) {
|
|
3888
|
+
return false;
|
|
3889
|
+
}
|
|
3890
|
+
let cmd;
|
|
3891
|
+
let args;
|
|
3892
|
+
if (process.platform === "darwin") {
|
|
3893
|
+
cmd = "open";
|
|
3894
|
+
args = [url];
|
|
3895
|
+
} else if (process.platform === "win32") {
|
|
3896
|
+
cmd = "cmd";
|
|
3897
|
+
args = ["/c", "start", '""', url];
|
|
3898
|
+
} else {
|
|
3899
|
+
cmd = "xdg-open";
|
|
3900
|
+
args = [url];
|
|
3901
|
+
}
|
|
3902
|
+
try {
|
|
3903
|
+
const child = spawn(cmd, args, { stdio: "ignore", detached: true });
|
|
3904
|
+
child.on("error", () => {
|
|
3905
|
+
});
|
|
3906
|
+
child.unref();
|
|
3907
|
+
return true;
|
|
3908
|
+
} catch {
|
|
3909
|
+
return false;
|
|
3910
|
+
}
|
|
3911
|
+
}
|
|
3912
|
+
|
|
3727
3913
|
// src/registry/login.ts
|
|
3728
3914
|
async function loginInteractive(opts) {
|
|
3729
|
-
const registryUrl = (opts?.registryUrl || defaultRegistryUrl())
|
|
3915
|
+
const registryUrl = normalizeRegistryUrl(opts?.registryUrl || defaultRegistryUrl());
|
|
3730
3916
|
let token = opts?.token || process.env.S2SCRIPT_TOKEN || process.env.S2SCRIPT_DEPLOY_TOKEN;
|
|
3731
3917
|
if (!token) {
|
|
3732
3918
|
if (!isInteractive({ ci: opts?.ci })) {
|
|
3733
|
-
throw new Error(
|
|
3734
|
-
"no deploy token: set S2SCRIPT_TOKEN or run `s2s login` interactively"
|
|
3735
|
-
);
|
|
3919
|
+
throw new Error("no deploy token: set S2SCRIPT_TOKEN or run `s2s login` interactively");
|
|
3736
3920
|
}
|
|
3737
3921
|
intro2("s2script login");
|
|
3738
3922
|
log2.info(`Registry: ${registryUrl}`);
|
|
3739
|
-
|
|
3740
|
-
|
|
3923
|
+
const start = await startDeviceAuth(registryUrl, { client: hostname() });
|
|
3924
|
+
if (start) {
|
|
3925
|
+
log2.info(`Confirmation code: ${start.userCode}`);
|
|
3926
|
+
log2.info(`Approve at: ${start.verificationUriComplete}`);
|
|
3927
|
+
if (!opts?.noBrowser && tryOpenBrowser(start.verificationUriComplete)) {
|
|
3928
|
+
log2.info("Opened your browser \u2014 approve the request there.");
|
|
3929
|
+
} else {
|
|
3930
|
+
log2.info("Open that link in any browser (it can be on another machine).");
|
|
3931
|
+
}
|
|
3932
|
+
const approved = await task(
|
|
3933
|
+
"Waiting for approval in the browser\u2026",
|
|
3934
|
+
() => pollForToken(registryUrl, start),
|
|
3935
|
+
{ interactive: true, done: (r2) => `Approved (token "${r2.tokenName ?? "cli"}")` }
|
|
3936
|
+
);
|
|
3937
|
+
token = approved.token;
|
|
3938
|
+
} else {
|
|
3939
|
+
log2.info(
|
|
3940
|
+
`Sign in (or create an account), then mint a deploy token at:
|
|
3741
3941
|
${registryUrl}/account/tokens`
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3942
|
+
);
|
|
3943
|
+
token = (await password2({
|
|
3944
|
+
message: "Paste your deploy token",
|
|
3945
|
+
validate: (v) => (v ?? "").trim().startsWith("s2s_") ? void 0 : 'token must start with "s2s_"'
|
|
3946
|
+
})).trim();
|
|
3947
|
+
}
|
|
3747
3948
|
}
|
|
3748
3949
|
if (!token || !token.startsWith("s2s_")) {
|
|
3749
3950
|
throw new Error('token must start with "s2s_"');
|
|
@@ -3760,7 +3961,8 @@ async function run5(argv2) {
|
|
|
3760
3961
|
const creds = await loginInteractive({
|
|
3761
3962
|
token: parseFlag(argv2, "--token"),
|
|
3762
3963
|
registryUrl: parseFlag(argv2, "--registry") || defaultRegistryUrl(),
|
|
3763
|
-
ci: hasFlag(argv2, "--ci")
|
|
3964
|
+
ci: hasFlag(argv2, "--ci"),
|
|
3965
|
+
noBrowser: hasFlag(argv2, "--no-browser")
|
|
3764
3966
|
});
|
|
3765
3967
|
const msg = `logged in \u2192 ${creds.registryUrl} (credentials saved)`;
|
|
3766
3968
|
if (interactive) outro2(msg);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@s2script/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"license": "MIT OR Apache-2.0",
|
|
5
5
|
"description": "s2script SDK — the builtin capability types (imported as @s2script/sdk/entity, …) plus the `s2s` CLI for building Source 2 / CS2 plugins.",
|
|
6
6
|
"type": "module",
|