@semilayer/cli 1.1.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/auth-config-3MWVCUTJ.js +117 -0
- package/dist/auth-config-3MWVCUTJ.js.map +1 -0
- package/dist/billing-OY5GJP5X.js +265 -0
- package/dist/billing-OY5GJP5X.js.map +1 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +49 -0
- package/dist/bin.js.map +1 -0
- package/dist/chunk-7TA63VHV.js +38 -0
- package/dist/chunk-7TA63VHV.js.map +1 -0
- package/dist/chunk-ALA4X7UU.js +19 -0
- package/dist/chunk-ALA4X7UU.js.map +1 -0
- package/dist/chunk-NIDLPHWY.js +53 -0
- package/dist/chunk-NIDLPHWY.js.map +1 -0
- package/dist/chunk-QMF7LD67.js +39 -0
- package/dist/chunk-QMF7LD67.js.map +1 -0
- package/dist/chunk-QXIVJY7K.js +56 -0
- package/dist/chunk-QXIVJY7K.js.map +1 -0
- package/dist/chunk-T3UROBMA.js +169 -0
- package/dist/chunk-T3UROBMA.js.map +1 -0
- package/dist/chunk-WZYOSGN3.js +88 -0
- package/dist/chunk-WZYOSGN3.js.map +1 -0
- package/dist/config-DACYO7JC.js +103 -0
- package/dist/config-DACYO7JC.js.map +1 -0
- package/dist/dev-R3AZSONQ.js +57 -0
- package/dist/dev-R3AZSONQ.js.map +1 -0
- package/dist/envs-RNZQ3OQP.js +105 -0
- package/dist/envs-RNZQ3OQP.js.map +1 -0
- package/dist/export-YRFR3JH2.js +81 -0
- package/dist/export-YRFR3JH2.js.map +1 -0
- package/dist/generate-QUETX3TN.js +41 -0
- package/dist/generate-QUETX3TN.js.map +1 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/dist/init-TWJAGUN3.js +187 -0
- package/dist/init-TWJAGUN3.js.map +1 -0
- package/dist/keys-JBKCYKJU.js +111 -0
- package/dist/keys-JBKCYKJU.js.map +1 -0
- package/dist/lenses-VZSDFH3D.js +51 -0
- package/dist/lenses-VZSDFH3D.js.map +1 -0
- package/dist/login-BZ6ZPFHC.js +119 -0
- package/dist/login-BZ6ZPFHC.js.map +1 -0
- package/dist/logout-VMPRV62T.js +38 -0
- package/dist/logout-VMPRV62T.js.map +1 -0
- package/dist/members-DVE5FDLZ.js +110 -0
- package/dist/members-DVE5FDLZ.js.map +1 -0
- package/dist/observe-W346RZBX.js +149 -0
- package/dist/observe-W346RZBX.js.map +1 -0
- package/dist/orgs-YA3TVA3T.js +67 -0
- package/dist/orgs-YA3TVA3T.js.map +1 -0
- package/dist/pause-GQ6PKBUA.js +50 -0
- package/dist/pause-GQ6PKBUA.js.map +1 -0
- package/dist/projects-DMA2AXH3.js +107 -0
- package/dist/projects-DMA2AXH3.js.map +1 -0
- package/dist/push-3ZK3W2AC.js +145 -0
- package/dist/push-3ZK3W2AC.js.map +1 -0
- package/dist/resume-KVRPLXZZ.js +50 -0
- package/dist/resume-KVRPLXZZ.js.map +1 -0
- package/dist/run-IR5B4AE3.js +375 -0
- package/dist/run-IR5B4AE3.js.map +1 -0
- package/dist/sources-S52HUWRK.js +170 -0
- package/dist/sources-S52HUWRK.js.map +1 -0
- package/dist/status-AUECH6RX.js +130 -0
- package/dist/status-AUECH6RX.js.map +1 -0
- package/dist/stream-V7RGHTPR.js +344 -0
- package/dist/stream-V7RGHTPR.js.map +1 -0
- package/dist/sync-NRTC3WX4.js +68 -0
- package/dist/sync-NRTC3WX4.js.map +1 -0
- package/dist/whoami-EQGW6V5D.js +50 -0
- package/dist/whoami-EQGW6V5D.js.map +1 -0
- package/dist/wizard-QLAR33T2.js +306 -0
- package/dist/wizard-QLAR33T2.js.map +1 -0
- package/package.json +40 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/context.ts"],"sourcesContent":["import { readFile, writeFile } from 'node:fs/promises'\nimport { join, dirname } from 'node:path'\nimport { existsSync } from 'node:fs'\n\nexport interface ProjectContext {\n orgSlug: string\n projectSlug: string\n envSlug: string\n serviceUrl: string\n}\n\nconst CONTEXT_FILE = '.semilayerrc'\n\nexport function findContextFile(cwd?: string): string | null {\n let dir = cwd ?? process.cwd()\n const root = dirname(dir) === dir ? dir : undefined // filesystem root\n\n while (true) {\n const candidate = join(dir, CONTEXT_FILE)\n if (existsSync(candidate)) return candidate\n const parent = dirname(dir)\n if (parent === dir || parent === root) return null\n dir = parent\n }\n}\n\nexport async function loadContext(cwd?: string): Promise<ProjectContext | null> {\n const file = findContextFile(cwd)\n if (!file) return null\n try {\n const raw = await readFile(file, 'utf-8')\n return JSON.parse(raw) as ProjectContext\n } catch {\n return null\n }\n}\n\nexport async function saveContext(ctx: ProjectContext, cwd?: string): Promise<void> {\n const target = join(cwd ?? process.cwd(), CONTEXT_FILE)\n await writeFile(target, JSON.stringify(ctx, null, 2) + '\\n', 'utf-8')\n}\n"],"mappings":";;;AAAA,SAAS,UAAU,iBAAiB;AACpC,SAAS,MAAM,eAAe;AAC9B,SAAS,kBAAkB;AAS3B,IAAM,eAAe;AAEd,SAAS,gBAAgB,KAA6B;AAC3D,MAAI,MAAM,OAAO,QAAQ,IAAI;AAC7B,QAAM,OAAO,QAAQ,GAAG,MAAM,MAAM,MAAM;AAE1C,SAAO,MAAM;AACX,UAAM,YAAY,KAAK,KAAK,YAAY;AACxC,QAAI,WAAW,SAAS,EAAG,QAAO;AAClC,UAAM,SAAS,QAAQ,GAAG;AAC1B,QAAI,WAAW,OAAO,WAAW,KAAM,QAAO;AAC9C,UAAM;AAAA,EACR;AACF;AAEA,eAAsB,YAAY,KAA8C;AAC9E,QAAM,OAAO,gBAAgB,GAAG;AAChC,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI;AACF,UAAM,MAAM,MAAM,SAAS,MAAM,OAAO;AACxC,WAAO,KAAK,MAAM,GAAG;AAAA,EACvB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,YAAY,KAAqB,KAA6B;AAClF,QAAM,SAAS,KAAK,OAAO,QAAQ,IAAI,GAAG,YAAY;AACtD,QAAM,UAAU,QAAQ,KAAK,UAAU,KAAK,MAAM,CAAC,IAAI,MAAM,OAAO;AACtE;","names":[]}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/lib/prompt.ts
|
|
4
|
+
import { createInterface } from "readline";
|
|
5
|
+
async function select(question, choices) {
|
|
6
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
7
|
+
for (let i = 0; i < choices.length; i++) {
|
|
8
|
+
console.log(` ${i + 1}) ${choices[i]}`);
|
|
9
|
+
}
|
|
10
|
+
return new Promise((resolve) => {
|
|
11
|
+
rl.question(` ${question} `, (answer) => {
|
|
12
|
+
rl.close();
|
|
13
|
+
const idx = parseInt(answer, 10) - 1;
|
|
14
|
+
resolve(choices[idx] ?? choices[0]);
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
async function input(question, defaultValue) {
|
|
19
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
20
|
+
const suffix = defaultValue ? ` (${defaultValue})` : "";
|
|
21
|
+
return new Promise((resolve) => {
|
|
22
|
+
rl.question(` ${question}${suffix}: `, (answer) => {
|
|
23
|
+
rl.close();
|
|
24
|
+
resolve(answer.trim() || defaultValue || "");
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
async function confirm(question, defaultYes = false) {
|
|
29
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
30
|
+
const hint = defaultYes ? "[Y/n]" : "[y/N]";
|
|
31
|
+
return new Promise((resolve) => {
|
|
32
|
+
rl.question(` ${question} ${hint} `, (answer) => {
|
|
33
|
+
rl.close();
|
|
34
|
+
const a = answer.trim().toLowerCase();
|
|
35
|
+
if (a === "") resolve(defaultYes);
|
|
36
|
+
else resolve(a === "y" || a === "yes");
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
async function secret(question) {
|
|
41
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
42
|
+
return new Promise((resolve) => {
|
|
43
|
+
rl.question(` ${question}: `, (answer) => {
|
|
44
|
+
rl.close();
|
|
45
|
+
resolve(answer.trim());
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export {
|
|
51
|
+
select,
|
|
52
|
+
input,
|
|
53
|
+
confirm,
|
|
54
|
+
secret
|
|
55
|
+
};
|
|
56
|
+
//# sourceMappingURL=chunk-QXIVJY7K.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/prompt.ts"],"sourcesContent":["import { createInterface } from 'node:readline'\n\n/**\n * Prompt user to select from a list of choices. Returns the selected value.\n */\nexport async function select(question: string, choices: string[]): Promise<string> {\n const rl = createInterface({ input: process.stdin, output: process.stdout })\n for (let i = 0; i < choices.length; i++) {\n console.log(` ${i + 1}) ${choices[i]}`)\n }\n return new Promise((resolve) => {\n rl.question(` ${question} `, (answer) => {\n rl.close()\n const idx = parseInt(answer, 10) - 1\n resolve(choices[idx] ?? choices[0]!)\n })\n })\n}\n\n/**\n * Prompt for a text input.\n */\nexport async function input(question: string, defaultValue?: string): Promise<string> {\n const rl = createInterface({ input: process.stdin, output: process.stdout })\n const suffix = defaultValue ? ` (${defaultValue})` : ''\n return new Promise((resolve) => {\n rl.question(` ${question}${suffix}: `, (answer) => {\n rl.close()\n resolve(answer.trim() || defaultValue || '')\n })\n })\n}\n\n/**\n * Prompt for yes/no confirmation. Returns true for yes.\n */\nexport async function confirm(question: string, defaultYes = false): Promise<boolean> {\n const rl = createInterface({ input: process.stdin, output: process.stdout })\n const hint = defaultYes ? '[Y/n]' : '[y/N]'\n return new Promise((resolve) => {\n rl.question(` ${question} ${hint} `, (answer) => {\n rl.close()\n const a = answer.trim().toLowerCase()\n if (a === '') resolve(defaultYes)\n else resolve(a === 'y' || a === 'yes')\n })\n })\n}\n\n/**\n * Prompt for a secret (password/connection string). Input is NOT hidden\n * (Node readline doesn't support hiding easily without raw mode), but\n * we label it clearly.\n */\nexport async function secret(question: string): Promise<string> {\n const rl = createInterface({ input: process.stdin, output: process.stdout })\n return new Promise((resolve) => {\n rl.question(` ${question}: `, (answer) => {\n rl.close()\n resolve(answer.trim())\n })\n })\n}\n"],"mappings":";;;AAAA,SAAS,uBAAuB;AAKhC,eAAsB,OAAO,UAAkB,SAAoC;AACjF,QAAM,KAAK,gBAAgB,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,CAAC;AAC3E,WAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,YAAQ,IAAI,KAAK,IAAI,CAAC,KAAK,QAAQ,CAAC,CAAC,EAAE;AAAA,EACzC;AACA,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,OAAG,SAAS,KAAK,QAAQ,KAAK,CAAC,WAAW;AACxC,SAAG,MAAM;AACT,YAAM,MAAM,SAAS,QAAQ,EAAE,IAAI;AACnC,cAAQ,QAAQ,GAAG,KAAK,QAAQ,CAAC,CAAE;AAAA,IACrC,CAAC;AAAA,EACH,CAAC;AACH;AAKA,eAAsB,MAAM,UAAkB,cAAwC;AACpF,QAAM,KAAK,gBAAgB,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,CAAC;AAC3E,QAAM,SAAS,eAAe,KAAK,YAAY,MAAM;AACrD,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,OAAG,SAAS,KAAK,QAAQ,GAAG,MAAM,MAAM,CAAC,WAAW;AAClD,SAAG,MAAM;AACT,cAAQ,OAAO,KAAK,KAAK,gBAAgB,EAAE;AAAA,IAC7C,CAAC;AAAA,EACH,CAAC;AACH;AAKA,eAAsB,QAAQ,UAAkB,aAAa,OAAyB;AACpF,QAAM,KAAK,gBAAgB,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,CAAC;AAC3E,QAAM,OAAO,aAAa,UAAU;AACpC,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,OAAG,SAAS,KAAK,QAAQ,IAAI,IAAI,KAAK,CAAC,WAAW;AAChD,SAAG,MAAM;AACT,YAAM,IAAI,OAAO,KAAK,EAAE,YAAY;AACpC,UAAI,MAAM,GAAI,SAAQ,UAAU;AAAA,UAC3B,SAAQ,MAAM,OAAO,MAAM,KAAK;AAAA,IACvC,CAAC;AAAA,EACH,CAAC;AACH;AAOA,eAAsB,OAAO,UAAmC;AAC9D,QAAM,KAAK,gBAAgB,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,CAAC;AAC3E,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,OAAG,SAAS,KAAK,QAAQ,MAAM,CAAC,WAAW;AACzC,SAAG,MAAM;AACT,cAAQ,OAAO,KAAK,CAAC;AAAA,IACvB,CAAC;AAAA,EACH,CAAC;AACH;","names":[]}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
loadCredentials,
|
|
4
|
+
saveCredentials
|
|
5
|
+
} from "./chunk-7TA63VHV.js";
|
|
6
|
+
|
|
7
|
+
// src/lib/api.ts
|
|
8
|
+
var CliQuotaError = class extends Error {
|
|
9
|
+
constructor(payload) {
|
|
10
|
+
super(payload.message ?? payload.error ?? "Quota exceeded");
|
|
11
|
+
this.payload = payload;
|
|
12
|
+
this.name = "CliQuotaError";
|
|
13
|
+
}
|
|
14
|
+
payload;
|
|
15
|
+
status = 403;
|
|
16
|
+
};
|
|
17
|
+
var CliRateLimitError = class extends Error {
|
|
18
|
+
constructor(retryAfterSeconds) {
|
|
19
|
+
super(`Rate limit exceeded \u2014 retry after ${retryAfterSeconds}s`);
|
|
20
|
+
this.retryAfterSeconds = retryAfterSeconds;
|
|
21
|
+
this.name = "CliRateLimitError";
|
|
22
|
+
}
|
|
23
|
+
retryAfterSeconds;
|
|
24
|
+
status = 429;
|
|
25
|
+
};
|
|
26
|
+
var MAX_429_RETRIES = 3;
|
|
27
|
+
var BACKOFF_BASE_MS = 250;
|
|
28
|
+
function sleep(ms) {
|
|
29
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
30
|
+
}
|
|
31
|
+
async function refreshAccessToken(creds) {
|
|
32
|
+
const res = await fetch(`${creds.serviceUrl}/auth/refresh`, {
|
|
33
|
+
method: "POST",
|
|
34
|
+
headers: {
|
|
35
|
+
Authorization: `Bearer ${creds.refreshToken}`,
|
|
36
|
+
"Content-Type": "application/json"
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
if (!res.ok) {
|
|
40
|
+
throw new Error("Session expired. Run `semilayer login` to re-authenticate.");
|
|
41
|
+
}
|
|
42
|
+
const data = await res.json();
|
|
43
|
+
return data.accessToken;
|
|
44
|
+
}
|
|
45
|
+
function isExpired(expiresAt) {
|
|
46
|
+
return new Date(expiresAt).getTime() <= Date.now() + 3e4;
|
|
47
|
+
}
|
|
48
|
+
async function createApiClient() {
|
|
49
|
+
const creds = await loadCredentials();
|
|
50
|
+
if (!creds) {
|
|
51
|
+
throw new Error("Not logged in. Run `semilayer login` first.");
|
|
52
|
+
}
|
|
53
|
+
let { accessToken } = creds;
|
|
54
|
+
if (isExpired(creds.expiresAt)) {
|
|
55
|
+
accessToken = await refreshAccessToken(creds);
|
|
56
|
+
await saveCredentials({
|
|
57
|
+
...creds,
|
|
58
|
+
accessToken,
|
|
59
|
+
expiresAt: tokenExpiry(accessToken)
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
async function request(method, path, body) {
|
|
63
|
+
const url = `${creds.serviceUrl}${path}`;
|
|
64
|
+
const headers = {
|
|
65
|
+
Authorization: `Bearer ${accessToken}`,
|
|
66
|
+
"Content-Type": "application/json"
|
|
67
|
+
};
|
|
68
|
+
const isIdempotent = method === "GET";
|
|
69
|
+
let attempt = 0;
|
|
70
|
+
let res;
|
|
71
|
+
while (true) {
|
|
72
|
+
res = await fetch(url, {
|
|
73
|
+
method,
|
|
74
|
+
headers: { ...headers, Authorization: `Bearer ${accessToken}` },
|
|
75
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
76
|
+
});
|
|
77
|
+
if (res.status === 401) {
|
|
78
|
+
try {
|
|
79
|
+
accessToken = await refreshAccessToken(creds);
|
|
80
|
+
await saveCredentials({
|
|
81
|
+
...creds,
|
|
82
|
+
accessToken,
|
|
83
|
+
expiresAt: tokenExpiry(accessToken)
|
|
84
|
+
});
|
|
85
|
+
} catch {
|
|
86
|
+
throw new Error("Session expired. Run `semilayer login` to re-authenticate.");
|
|
87
|
+
}
|
|
88
|
+
const retry = await fetch(url, {
|
|
89
|
+
method,
|
|
90
|
+
headers: { ...headers, Authorization: `Bearer ${accessToken}` },
|
|
91
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
92
|
+
});
|
|
93
|
+
if (!retry.ok) {
|
|
94
|
+
await throwForResponse(retry);
|
|
95
|
+
}
|
|
96
|
+
return retry.json();
|
|
97
|
+
}
|
|
98
|
+
if (res.status === 429) {
|
|
99
|
+
const retryAfter = parseRetryAfter(res.headers.get("retry-after"));
|
|
100
|
+
if (!isIdempotent || attempt >= MAX_429_RETRIES) {
|
|
101
|
+
throw new CliRateLimitError(retryAfter);
|
|
102
|
+
}
|
|
103
|
+
const backoffMs = Math.max(retryAfter * 1e3, BACKOFF_BASE_MS * 2 ** attempt);
|
|
104
|
+
await sleep(backoffMs);
|
|
105
|
+
attempt++;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (res.status === 403) {
|
|
109
|
+
const text = await res.text().catch(() => "");
|
|
110
|
+
try {
|
|
111
|
+
const parsed = JSON.parse(text);
|
|
112
|
+
if (parsed && typeof parsed.error === "string" && typeof parsed.limit === "number") {
|
|
113
|
+
throw new CliQuotaError({
|
|
114
|
+
error: parsed.error,
|
|
115
|
+
message: parsed.message ?? parsed.error,
|
|
116
|
+
limit: parsed.limit,
|
|
117
|
+
current: parsed.current ?? 0,
|
|
118
|
+
upgradeUrl: parsed.upgradeUrl ?? ""
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
} catch (e) {
|
|
122
|
+
if (e instanceof CliQuotaError) throw e;
|
|
123
|
+
}
|
|
124
|
+
throw new Error(`API error 403: ${text}`);
|
|
125
|
+
}
|
|
126
|
+
if (!res.ok) {
|
|
127
|
+
await throwForResponse(res);
|
|
128
|
+
}
|
|
129
|
+
return res.json();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
get: (path) => request("GET", path),
|
|
134
|
+
post: (path, body) => request("POST", path, body),
|
|
135
|
+
patch: (path, body) => request("PATCH", path, body),
|
|
136
|
+
put: (path, body) => request("PUT", path, body),
|
|
137
|
+
del: (path) => request("DELETE", path),
|
|
138
|
+
serviceUrl: creds.serviceUrl
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
async function throwForResponse(res) {
|
|
142
|
+
const text = await res.text().catch(() => "");
|
|
143
|
+
throw new Error(`API error ${res.status}: ${text}`);
|
|
144
|
+
}
|
|
145
|
+
function parseRetryAfter(header) {
|
|
146
|
+
if (!header) return 1;
|
|
147
|
+
const seconds = Number(header);
|
|
148
|
+
if (Number.isFinite(seconds) && seconds > 0) return Math.ceil(seconds);
|
|
149
|
+
const date = Date.parse(header);
|
|
150
|
+
if (Number.isFinite(date)) {
|
|
151
|
+
return Math.max(1, Math.ceil((date - Date.now()) / 1e3));
|
|
152
|
+
}
|
|
153
|
+
return 1;
|
|
154
|
+
}
|
|
155
|
+
function tokenExpiry(jwt) {
|
|
156
|
+
try {
|
|
157
|
+
const payload = JSON.parse(
|
|
158
|
+
Buffer.from(jwt.split(".")[1], "base64url").toString()
|
|
159
|
+
);
|
|
160
|
+
return new Date(payload.exp * 1e3).toISOString();
|
|
161
|
+
} catch {
|
|
162
|
+
return new Date(Date.now() + 15 * 60 * 1e3).toISOString();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export {
|
|
167
|
+
createApiClient
|
|
168
|
+
};
|
|
169
|
+
//# sourceMappingURL=chunk-T3UROBMA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/api.ts"],"sourcesContent":["import {\n loadCredentials,\n saveCredentials,\n type Credentials,\n} from './credentials.js'\n\n/**\n * Step 12 — Structured 403 quota error returned by the service when an\n * operation is blocked by a tier limit. The CLI surfaces these via\n * `printQuotaError` rather than the generic API error handler so the user\n * sees a friendly upgrade message instead of a JSON dump.\n */\nexport interface ApiQuotaError {\n error: string\n message: string\n limit: number\n current: number\n upgradeUrl: string\n}\n\n/**\n * Thrown by the CLI api client when a request blows the per-org or\n * per-resource quota. Carries the structured 403 body so callers can\n * pretty-print it via `printQuotaError`.\n */\nexport class CliQuotaError extends Error {\n readonly status = 403\n constructor(public readonly payload: ApiQuotaError) {\n super(payload.message ?? payload.error ?? 'Quota exceeded')\n this.name = 'CliQuotaError'\n }\n}\n\n/**\n * Step 12 — thrown when the service returns 429 with a Retry-After header.\n * The api client retries idempotent (GET) requests automatically up to 3\n * times with exponential backoff; this error escapes only when retries are\n * exhausted or the request method is non-idempotent.\n */\nexport class CliRateLimitError extends Error {\n readonly status = 429\n constructor(public readonly retryAfterSeconds: number) {\n super(`Rate limit exceeded — retry after ${retryAfterSeconds}s`)\n this.name = 'CliRateLimitError'\n }\n}\n\nexport interface ApiClient {\n get<T>(path: string): Promise<T>\n post<T>(path: string, body?: unknown): Promise<T>\n patch<T>(path: string, body?: unknown): Promise<T>\n put<T>(path: string, body?: unknown): Promise<T>\n del<T>(path: string): Promise<T>\n serviceUrl: string\n}\n\nconst MAX_429_RETRIES = 3\nconst BACKOFF_BASE_MS = 250\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms))\n}\n\nasync function refreshAccessToken(creds: Credentials): Promise<string> {\n const res = await fetch(`${creds.serviceUrl}/auth/refresh`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${creds.refreshToken}`,\n 'Content-Type': 'application/json',\n },\n })\n\n if (!res.ok) {\n throw new Error('Session expired. Run `semilayer login` to re-authenticate.')\n }\n\n const data = (await res.json()) as { accessToken: string }\n return data.accessToken\n}\n\nfunction isExpired(expiresAt: string): boolean {\n return new Date(expiresAt).getTime() <= Date.now() + 30_000 // 30s buffer\n}\n\nexport async function createApiClient(): Promise<ApiClient> {\n const creds = await loadCredentials()\n if (!creds) {\n throw new Error('Not logged in. Run `semilayer login` first.')\n }\n\n let { accessToken } = creds\n\n // Pre-emptive refresh if token is expired or about to expire\n if (isExpired(creds.expiresAt)) {\n accessToken = await refreshAccessToken(creds)\n await saveCredentials({\n ...creds,\n accessToken,\n expiresAt: tokenExpiry(accessToken),\n })\n }\n\n async function request<T>(method: string, path: string, body?: unknown): Promise<T> {\n const url = `${creds!.serviceUrl}${path}`\n const headers: Record<string, string> = {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n }\n\n // Step 12 — only retry idempotent verbs on 429. POST/PUT/PATCH/DELETE\n // bubble up immediately so callers can decide.\n const isIdempotent = method === 'GET'\n let attempt = 0\n let res: Response\n\n while (true) {\n res = await fetch(url, {\n method,\n headers: { ...headers, Authorization: `Bearer ${accessToken}` },\n body: body !== undefined ? JSON.stringify(body) : undefined,\n })\n\n // 401 → refresh once and retry\n if (res.status === 401) {\n try {\n accessToken = await refreshAccessToken(creds!)\n await saveCredentials({\n ...creds!,\n accessToken,\n expiresAt: tokenExpiry(accessToken),\n })\n } catch {\n throw new Error('Session expired. Run `semilayer login` to re-authenticate.')\n }\n // Retry once with the new token\n const retry = await fetch(url, {\n method,\n headers: { ...headers, Authorization: `Bearer ${accessToken}` },\n body: body !== undefined ? JSON.stringify(body) : undefined,\n })\n if (!retry.ok) {\n await throwForResponse(retry)\n }\n return retry.json() as Promise<T>\n }\n\n // 429 → exponential backoff for idempotent requests; bubble for others\n if (res.status === 429) {\n const retryAfter = parseRetryAfter(res.headers.get('retry-after'))\n if (!isIdempotent || attempt >= MAX_429_RETRIES) {\n throw new CliRateLimitError(retryAfter)\n }\n const backoffMs = Math.max(retryAfter * 1000, BACKOFF_BASE_MS * 2 ** attempt)\n await sleep(backoffMs)\n attempt++\n continue\n }\n\n // 403 with structured quota error → CliQuotaError\n if (res.status === 403) {\n const text = await res.text().catch(() => '')\n try {\n const parsed = JSON.parse(text) as Partial<ApiQuotaError>\n if (parsed && typeof parsed.error === 'string' && typeof parsed.limit === 'number') {\n throw new CliQuotaError({\n error: parsed.error,\n message: parsed.message ?? parsed.error,\n limit: parsed.limit,\n current: parsed.current ?? 0,\n upgradeUrl: parsed.upgradeUrl ?? '',\n })\n }\n } catch (e) {\n if (e instanceof CliQuotaError) throw e\n // Non-quota 403 — fall through to generic error\n }\n throw new Error(`API error 403: ${text}`)\n }\n\n if (!res.ok) {\n await throwForResponse(res)\n }\n return res.json() as Promise<T>\n }\n }\n\n return {\n get: <T>(path: string) => request<T>('GET', path),\n post: <T>(path: string, body?: unknown) => request<T>('POST', path, body),\n patch: <T>(path: string, body?: unknown) => request<T>('PATCH', path, body),\n put: <T>(path: string, body?: unknown) => request<T>('PUT', path, body),\n del: <T>(path: string) => request<T>('DELETE', path),\n serviceUrl: creds.serviceUrl,\n }\n}\n\nasync function throwForResponse(res: Response): Promise<never> {\n const text = await res.text().catch(() => '')\n throw new Error(`API error ${res.status}: ${text}`)\n}\n\nfunction parseRetryAfter(header: string | null): number {\n if (!header) return 1\n const seconds = Number(header)\n if (Number.isFinite(seconds) && seconds > 0) return Math.ceil(seconds)\n // HTTP-date form — convert to delay seconds\n const date = Date.parse(header)\n if (Number.isFinite(date)) {\n return Math.max(1, Math.ceil((date - Date.now()) / 1000))\n }\n return 1\n}\n\n/** Decode JWT exp claim to ISO string */\nfunction tokenExpiry(jwt: string): string {\n try {\n const payload = JSON.parse(\n Buffer.from(jwt.split('.')[1]!, 'base64url').toString(),\n )\n return new Date(payload.exp * 1000).toISOString()\n } catch {\n // Fallback: 15 minutes from now\n return new Date(Date.now() + 15 * 60 * 1000).toISOString()\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/lib/output.ts
|
|
4
|
+
var isColorSupported = process.env.FORCE_COLOR !== "0" && (process.env.FORCE_COLOR !== void 0 || process.stdout.isTTY);
|
|
5
|
+
function wrap(code, reset) {
|
|
6
|
+
return (msg) => isColorSupported ? `${code}${msg}${reset}` : msg;
|
|
7
|
+
}
|
|
8
|
+
var green = wrap("\x1B[32m", "\x1B[39m");
|
|
9
|
+
var red = wrap("\x1B[31m", "\x1B[39m");
|
|
10
|
+
var blue = wrap("\x1B[34m", "\x1B[39m");
|
|
11
|
+
var yellow = wrap("\x1B[33m", "\x1B[39m");
|
|
12
|
+
var gray = wrap("\x1B[90m", "\x1B[39m");
|
|
13
|
+
var boldWrap = wrap("\x1B[1m", "\x1B[22m");
|
|
14
|
+
function success(msg) {
|
|
15
|
+
console.log(green(` \u2713 ${msg}`));
|
|
16
|
+
}
|
|
17
|
+
function error(msg) {
|
|
18
|
+
console.error(red(` \u2717 ${msg}`));
|
|
19
|
+
}
|
|
20
|
+
function info(msg) {
|
|
21
|
+
console.log(blue(` \u2139 ${msg}`));
|
|
22
|
+
}
|
|
23
|
+
function warn(msg) {
|
|
24
|
+
console.log(yellow(` \u26A0 ${msg}`));
|
|
25
|
+
}
|
|
26
|
+
function dim(msg) {
|
|
27
|
+
return gray(msg);
|
|
28
|
+
}
|
|
29
|
+
function bold(msg) {
|
|
30
|
+
return boldWrap(msg);
|
|
31
|
+
}
|
|
32
|
+
function table(headersOrRows, maybeRows) {
|
|
33
|
+
let headers = null;
|
|
34
|
+
let rows;
|
|
35
|
+
if (maybeRows !== void 0) {
|
|
36
|
+
headers = headersOrRows;
|
|
37
|
+
rows = maybeRows;
|
|
38
|
+
} else {
|
|
39
|
+
rows = headersOrRows;
|
|
40
|
+
}
|
|
41
|
+
const allRows = headers ? [headers, ...rows] : rows;
|
|
42
|
+
if (allRows.length === 0) return;
|
|
43
|
+
const colCount = Math.max(...allRows.map((r) => r.length));
|
|
44
|
+
const widths = Array.from({ length: colCount }, () => 0);
|
|
45
|
+
for (const row of allRows) {
|
|
46
|
+
for (let i = 0; i < row.length; i++) {
|
|
47
|
+
widths[i] = Math.max(widths[i], row[i].length);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (headers) {
|
|
51
|
+
const headerLine = headers.map((cell, i) => bold(cell.padEnd(widths[i]))).join(" ");
|
|
52
|
+
console.log(` ${headerLine}`);
|
|
53
|
+
}
|
|
54
|
+
for (const row of rows) {
|
|
55
|
+
const line = row.map((cell, i) => cell.padEnd(widths[i])).join(" ");
|
|
56
|
+
console.log(` ${line}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function newline() {
|
|
60
|
+
console.log();
|
|
61
|
+
}
|
|
62
|
+
function label(key, value) {
|
|
63
|
+
console.log(` ${bold(key + ":")} ${value}`);
|
|
64
|
+
}
|
|
65
|
+
function usageBar(current, limit, width = 30) {
|
|
66
|
+
if (limit === null) {
|
|
67
|
+
return gray("\u2595" + " ".repeat(width) + "\u258F Unlimited");
|
|
68
|
+
}
|
|
69
|
+
const pct = Math.min(100, Math.round(current / Math.max(1, limit) * 100));
|
|
70
|
+
const filled = Math.round(pct / 100 * width);
|
|
71
|
+
const bar = "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
|
|
72
|
+
const colored = pct >= 95 ? red(bar) : pct >= 80 ? yellow(bar) : pct >= 60 ? yellow(bar) : green(bar);
|
|
73
|
+
return `${colored} ${pct}%`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export {
|
|
77
|
+
success,
|
|
78
|
+
error,
|
|
79
|
+
info,
|
|
80
|
+
warn,
|
|
81
|
+
dim,
|
|
82
|
+
bold,
|
|
83
|
+
table,
|
|
84
|
+
newline,
|
|
85
|
+
label,
|
|
86
|
+
usageBar
|
|
87
|
+
};
|
|
88
|
+
//# sourceMappingURL=chunk-WZYOSGN3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/output.ts"],"sourcesContent":["/**\n * Terminal output formatting — minimal ANSI helpers, no deps.\n */\n\nconst isColorSupported =\n process.env.FORCE_COLOR !== '0' &&\n (process.env.FORCE_COLOR !== undefined || process.stdout.isTTY)\n\nfunction wrap(code: string, reset: string) {\n return (msg: string) => (isColorSupported ? `${code}${msg}${reset}` : msg)\n}\n\nconst green = wrap('\\x1b[32m', '\\x1b[39m')\nconst red = wrap('\\x1b[31m', '\\x1b[39m')\nconst blue = wrap('\\x1b[34m', '\\x1b[39m')\nconst yellow = wrap('\\x1b[33m', '\\x1b[39m')\nconst gray = wrap('\\x1b[90m', '\\x1b[39m')\nconst boldWrap = wrap('\\x1b[1m', '\\x1b[22m')\n\nexport function success(msg: string): void {\n console.log(green(` ✓ ${msg}`))\n}\n\nexport function error(msg: string): void {\n console.error(red(` ✗ ${msg}`))\n}\n\nexport function info(msg: string): void {\n console.log(blue(` ℹ ${msg}`))\n}\n\nexport function warn(msg: string): void {\n console.log(yellow(` ⚠ ${msg}`))\n}\n\nexport function dim(msg: string): string {\n return gray(msg)\n}\n\nexport function bold(msg: string): string {\n return boldWrap(msg)\n}\n\nexport function table(headersOrRows: string[] | string[][], maybeRows?: string[][]): void {\n let headers: string[] | null = null\n let rows: string[][]\n\n if (maybeRows !== undefined) {\n // Two-arg form: table(headers, rows)\n headers = headersOrRows as string[]\n rows = maybeRows\n } else {\n // One-arg form: table(rows)\n rows = headersOrRows as string[][]\n }\n\n const allRows = headers ? [headers, ...rows] : rows\n if (allRows.length === 0) return\n\n const colCount = Math.max(...allRows.map((r) => r.length))\n const widths: number[] = Array.from({ length: colCount }, () => 0)\n for (const row of allRows) {\n for (let i = 0; i < row.length; i++) {\n widths[i] = Math.max(widths[i]!, row[i]!.length)\n }\n }\n\n if (headers) {\n const headerLine = headers\n .map((cell, i) => bold(cell.padEnd(widths[i]!)))\n .join(' ')\n console.log(` ${headerLine}`)\n }\n for (const row of rows) {\n const line = row\n .map((cell, i) => cell.padEnd(widths[i]!))\n .join(' ')\n console.log(` ${line}`)\n }\n}\n\nexport function newline(): void {\n console.log()\n}\n\nexport function label(key: string, value: string): void {\n console.log(` ${bold(key + ':')} ${value}`)\n}\n\n/**\n * Step 12 — render a 30-cell terminal usage bar for a quota dimension.\n * Color thresholds match the Console UsageBar (green/amber/orange/red).\n */\nexport function usageBar(current: number, limit: number | null, width = 30): string {\n if (limit === null) {\n return gray('▕' + ' '.repeat(width) + '▏ Unlimited')\n }\n const pct = Math.min(100, Math.round((current / Math.max(1, limit)) * 100))\n const filled = Math.round((pct / 100) * width)\n const bar = '█'.repeat(filled) + '░'.repeat(width - filled)\n const colored =\n pct >= 95 ? red(bar) : pct >= 80 ? yellow(bar) : pct >= 60 ? yellow(bar) : green(bar)\n return `${colored} ${pct}%`\n}\n\n/**\n * Step 12 — friendly multi-line print for a structured 403 quota error.\n * Mirrors the Console QuotaWarningBanner so users see consistent messaging\n * across surfaces.\n */\nexport function printQuotaError(payload: {\n error: string\n message: string\n limit: number\n current: number\n upgradeUrl: string\n}): void {\n console.error('')\n console.error(red(` ✗ ${payload.error}`))\n console.error(` ${payload.message}`)\n console.error(` ${dim(`Usage: ${payload.current.toLocaleString()} / ${payload.limit.toLocaleString()}`)}`)\n if (payload.upgradeUrl) {\n console.error(` Upgrade: ${blue(payload.upgradeUrl)}`)\n }\n console.error('')\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
loadContext,
|
|
4
|
+
saveContext
|
|
5
|
+
} from "./chunk-QMF7LD67.js";
|
|
6
|
+
import {
|
|
7
|
+
loadCredentials,
|
|
8
|
+
saveCredentials
|
|
9
|
+
} from "./chunk-7TA63VHV.js";
|
|
10
|
+
import {
|
|
11
|
+
bold,
|
|
12
|
+
dim,
|
|
13
|
+
error,
|
|
14
|
+
info,
|
|
15
|
+
label,
|
|
16
|
+
newline,
|
|
17
|
+
success
|
|
18
|
+
} from "./chunk-WZYOSGN3.js";
|
|
19
|
+
|
|
20
|
+
// src/commands/config.ts
|
|
21
|
+
import { defineCommand } from "citty";
|
|
22
|
+
var show = defineCommand({
|
|
23
|
+
meta: { name: "show", description: "Show current CLI configuration" },
|
|
24
|
+
async run() {
|
|
25
|
+
const creds = await loadCredentials();
|
|
26
|
+
const ctx = await loadContext();
|
|
27
|
+
newline();
|
|
28
|
+
console.log(` ${bold("Credentials")} ${dim("(~/.semilayer/credentials.json)")}`);
|
|
29
|
+
if (creds) {
|
|
30
|
+
label(" Email", creds.email);
|
|
31
|
+
label(" Service", creds.serviceUrl);
|
|
32
|
+
label(" Expires", creds.expiresAt);
|
|
33
|
+
} else {
|
|
34
|
+
info(" Not logged in. Run `semilayer login`.");
|
|
35
|
+
}
|
|
36
|
+
newline();
|
|
37
|
+
console.log(` ${bold("Project context")} ${dim("(.semilayerrc)")}`);
|
|
38
|
+
if (ctx) {
|
|
39
|
+
label(" Org", ctx.orgSlug);
|
|
40
|
+
label(" Project", ctx.projectSlug);
|
|
41
|
+
label(" Environment", ctx.envSlug);
|
|
42
|
+
label(" Service", ctx.serviceUrl);
|
|
43
|
+
} else {
|
|
44
|
+
info(" No context. Run `semilayer init`.");
|
|
45
|
+
}
|
|
46
|
+
newline();
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
var setApi = defineCommand({
|
|
50
|
+
meta: { name: "set-api", description: "Change the service API URL" },
|
|
51
|
+
args: {
|
|
52
|
+
url: {
|
|
53
|
+
type: "positional",
|
|
54
|
+
description: "New service URL (e.g. https://api.semilayer.com)",
|
|
55
|
+
required: true
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
async run({ args }) {
|
|
59
|
+
try {
|
|
60
|
+
const url = args.url.replace(/\/+$/, "");
|
|
61
|
+
try {
|
|
62
|
+
new URL(url);
|
|
63
|
+
} catch {
|
|
64
|
+
error("Invalid URL. Use format: https://api.semilayer.com");
|
|
65
|
+
process.exitCode = 1;
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
let updated = false;
|
|
69
|
+
const ctx = await loadContext();
|
|
70
|
+
if (ctx) {
|
|
71
|
+
await saveContext({ ...ctx, serviceUrl: url });
|
|
72
|
+
updated = true;
|
|
73
|
+
}
|
|
74
|
+
const creds = await loadCredentials();
|
|
75
|
+
if (creds) {
|
|
76
|
+
await saveCredentials({ ...creds, serviceUrl: url });
|
|
77
|
+
updated = true;
|
|
78
|
+
}
|
|
79
|
+
if (updated) {
|
|
80
|
+
success(`Service URL set to ${bold(url)}`);
|
|
81
|
+
if (ctx) info("Updated .semilayerrc");
|
|
82
|
+
if (creds) info("Updated ~/.semilayer/credentials.json");
|
|
83
|
+
} else {
|
|
84
|
+
error("No config files to update. Run `semilayer login` or `semilayer init` first.");
|
|
85
|
+
process.exitCode = 1;
|
|
86
|
+
}
|
|
87
|
+
} catch (err) {
|
|
88
|
+
error(err.message);
|
|
89
|
+
process.exitCode = 1;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
var config_default = defineCommand({
|
|
94
|
+
meta: { name: "config", description: "View and update CLI configuration" },
|
|
95
|
+
subCommands: {
|
|
96
|
+
show,
|
|
97
|
+
"set-api": setApi
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
export {
|
|
101
|
+
config_default as default
|
|
102
|
+
};
|
|
103
|
+
//# sourceMappingURL=config-DACYO7JC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/config.ts"],"sourcesContent":["import { defineCommand } from 'citty'\nimport { loadContext, saveContext } from '../lib/context.js'\nimport { loadCredentials, saveCredentials } from '../lib/credentials.js'\nimport { success, error, info, bold, dim, newline, label } from '../lib/output.js'\n\nconst show = defineCommand({\n meta: { name: 'show', description: 'Show current CLI configuration' },\n async run() {\n const creds = await loadCredentials()\n const ctx = await loadContext()\n\n newline()\n console.log(` ${bold('Credentials')} ${dim('(~/.semilayer/credentials.json)')}`)\n if (creds) {\n label(' Email', creds.email)\n label(' Service', creds.serviceUrl)\n label(' Expires', creds.expiresAt)\n } else {\n info(' Not logged in. Run `semilayer login`.')\n }\n\n newline()\n console.log(` ${bold('Project context')} ${dim('(.semilayerrc)')}`)\n if (ctx) {\n label(' Org', ctx.orgSlug)\n label(' Project', ctx.projectSlug)\n label(' Environment', ctx.envSlug)\n label(' Service', ctx.serviceUrl)\n } else {\n info(' No context. Run `semilayer init`.')\n }\n newline()\n },\n})\n\nconst setApi = defineCommand({\n meta: { name: 'set-api', description: 'Change the service API URL' },\n args: {\n url: {\n type: 'positional',\n description: 'New service URL (e.g. https://api.semilayer.com)',\n required: true,\n },\n },\n async run({ args }) {\n try {\n const url = args.url.replace(/\\/+$/, '') // strip trailing slash\n\n // Validate it looks like a URL\n try {\n new URL(url)\n } catch {\n error('Invalid URL. Use format: https://api.semilayer.com')\n process.exitCode = 1\n return\n }\n\n let updated = false\n\n // Update .semilayerrc\n const ctx = await loadContext()\n if (ctx) {\n await saveContext({ ...ctx, serviceUrl: url })\n updated = true\n }\n\n // Update credentials\n const creds = await loadCredentials()\n if (creds) {\n await saveCredentials({ ...creds, serviceUrl: url })\n updated = true\n }\n\n if (updated) {\n success(`Service URL set to ${bold(url)}`)\n if (ctx) info('Updated .semilayerrc')\n if (creds) info('Updated ~/.semilayer/credentials.json')\n } else {\n error('No config files to update. Run `semilayer login` or `semilayer init` first.')\n process.exitCode = 1\n }\n } catch (err) {\n error((err as Error).message)\n process.exitCode = 1\n }\n },\n})\n\nexport default defineCommand({\n meta: { name: 'config', description: 'View and update CLI configuration' },\n subCommands: {\n show,\n 'set-api': setApi,\n },\n})\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA,SAAS,qBAAqB;AAK9B,IAAM,OAAO,cAAc;AAAA,EACzB,MAAM,EAAE,MAAM,QAAQ,aAAa,iCAAiC;AAAA,EACpE,MAAM,MAAM;AACV,UAAM,QAAQ,MAAM,gBAAgB;AACpC,UAAM,MAAM,MAAM,YAAY;AAE9B,YAAQ;AACR,YAAQ,IAAI,KAAK,KAAK,aAAa,CAAC,IAAI,IAAI,iCAAiC,CAAC,EAAE;AAChF,QAAI,OAAO;AACT,YAAM,WAAW,MAAM,KAAK;AAC5B,YAAM,aAAa,MAAM,UAAU;AACnC,YAAM,aAAa,MAAM,SAAS;AAAA,IACpC,OAAO;AACL,WAAK,yCAAyC;AAAA,IAChD;AAEA,YAAQ;AACR,YAAQ,IAAI,KAAK,KAAK,iBAAiB,CAAC,IAAI,IAAI,gBAAgB,CAAC,EAAE;AACnE,QAAI,KAAK;AACP,YAAM,SAAS,IAAI,OAAO;AAC1B,YAAM,aAAa,IAAI,WAAW;AAClC,YAAM,iBAAiB,IAAI,OAAO;AAClC,YAAM,aAAa,IAAI,UAAU;AAAA,IACnC,OAAO;AACL,WAAK,qCAAqC;AAAA,IAC5C;AACA,YAAQ;AAAA,EACV;AACF,CAAC;AAED,IAAM,SAAS,cAAc;AAAA,EAC3B,MAAM,EAAE,MAAM,WAAW,aAAa,6BAA6B;AAAA,EACnE,MAAM;AAAA,IACJ,KAAK;AAAA,MACH,MAAM;AAAA,MACN,aAAa;AAAA,MACb,UAAU;AAAA,IACZ;AAAA,EACF;AAAA,EACA,MAAM,IAAI,EAAE,KAAK,GAAG;AAClB,QAAI;AACF,YAAM,MAAM,KAAK,IAAI,QAAQ,QAAQ,EAAE;AAGvC,UAAI;AACF,YAAI,IAAI,GAAG;AAAA,MACb,QAAQ;AACN,cAAM,oDAAoD;AAC1D,gBAAQ,WAAW;AACnB;AAAA,MACF;AAEA,UAAI,UAAU;AAGd,YAAM,MAAM,MAAM,YAAY;AAC9B,UAAI,KAAK;AACP,cAAM,YAAY,EAAE,GAAG,KAAK,YAAY,IAAI,CAAC;AAC7C,kBAAU;AAAA,MACZ;AAGA,YAAM,QAAQ,MAAM,gBAAgB;AACpC,UAAI,OAAO;AACT,cAAM,gBAAgB,EAAE,GAAG,OAAO,YAAY,IAAI,CAAC;AACnD,kBAAU;AAAA,MACZ;AAEA,UAAI,SAAS;AACX,gBAAQ,sBAAsB,KAAK,GAAG,CAAC,EAAE;AACzC,YAAI,IAAK,MAAK,sBAAsB;AACpC,YAAI,MAAO,MAAK,uCAAuC;AAAA,MACzD,OAAO;AACL,cAAM,6EAA6E;AACnF,gBAAQ,WAAW;AAAA,MACrB;AAAA,IACF,SAAS,KAAK;AACZ,YAAO,IAAc,OAAO;AAC5B,cAAQ,WAAW;AAAA,IACrB;AAAA,EACF;AACF,CAAC;AAED,IAAO,iBAAQ,cAAc;AAAA,EAC3B,MAAM,EAAE,MAAM,UAAU,aAAa,oCAAoC;AAAA,EACzE,aAAa;AAAA,IACX;AAAA,IACA,WAAW;AAAA,EACb;AACF,CAAC;","names":[]}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
loadConfig
|
|
4
|
+
} from "./chunk-NIDLPHWY.js";
|
|
5
|
+
import {
|
|
6
|
+
dim,
|
|
7
|
+
error,
|
|
8
|
+
info,
|
|
9
|
+
success
|
|
10
|
+
} from "./chunk-WZYOSGN3.js";
|
|
11
|
+
|
|
12
|
+
// src/commands/dev.ts
|
|
13
|
+
import { defineCommand } from "citty";
|
|
14
|
+
import { watch } from "fs";
|
|
15
|
+
var dev_default = defineCommand({
|
|
16
|
+
meta: { name: "dev", description: "Watch config and re-generate on changes" },
|
|
17
|
+
args: {
|
|
18
|
+
out: {
|
|
19
|
+
type: "string",
|
|
20
|
+
description: "Output directory",
|
|
21
|
+
default: "./generated/semilayer"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
async run({ args }) {
|
|
25
|
+
try {
|
|
26
|
+
const { generate } = await import("@semilayer/codegen");
|
|
27
|
+
const { config, configPath } = await loadConfig();
|
|
28
|
+
const result = await generate({ config, outDir: args.out, write: true });
|
|
29
|
+
success(
|
|
30
|
+
`Generated ${result.files.length} files for ${result.lenses.length} lenses in ${result.durationMs}ms`
|
|
31
|
+
);
|
|
32
|
+
info(`Watching ${dim(configPath)} for changes... ${dim("(Ctrl+C to stop)")}`);
|
|
33
|
+
let debounce = null;
|
|
34
|
+
watch(configPath, () => {
|
|
35
|
+
if (debounce) clearTimeout(debounce);
|
|
36
|
+
debounce = setTimeout(async () => {
|
|
37
|
+
try {
|
|
38
|
+
const { config: newConfig } = await loadConfig();
|
|
39
|
+
const r = await generate({ config: newConfig, outDir: args.out, write: true });
|
|
40
|
+
success(`Regenerated (${r.durationMs}ms)`);
|
|
41
|
+
} catch (err) {
|
|
42
|
+
error(err.message);
|
|
43
|
+
}
|
|
44
|
+
}, 200);
|
|
45
|
+
});
|
|
46
|
+
await new Promise(() => {
|
|
47
|
+
});
|
|
48
|
+
} catch (err) {
|
|
49
|
+
error(err.message);
|
|
50
|
+
process.exitCode = 1;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
export {
|
|
55
|
+
dev_default as default
|
|
56
|
+
};
|
|
57
|
+
//# sourceMappingURL=dev-R3AZSONQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/dev.ts"],"sourcesContent":["import { defineCommand } from 'citty'\nimport { loadConfig } from '../lib/config-loader.js'\nimport { success, error, info, dim } from '../lib/output.js'\nimport { watch } from 'node:fs'\n\nexport default defineCommand({\n meta: { name: 'dev', description: 'Watch config and re-generate on changes' },\n args: {\n out: {\n type: 'string',\n description: 'Output directory',\n default: './generated/semilayer',\n },\n },\n async run({ args }) {\n try {\n const { generate } = await import('@semilayer/codegen')\n\n // Initial generate\n const { config, configPath } = await loadConfig()\n const result = await generate({ config, outDir: args.out, write: true })\n success(\n `Generated ${result.files.length} files for ${result.lenses.length} lenses in ${result.durationMs}ms`,\n )\n info(`Watching ${dim(configPath)} for changes... ${dim('(Ctrl+C to stop)')}`)\n\n // Watch for changes\n let debounce: ReturnType<typeof setTimeout> | null = null\n\n watch(configPath, () => {\n if (debounce) clearTimeout(debounce)\n debounce = setTimeout(async () => {\n try {\n const { config: newConfig } = await loadConfig()\n const r = await generate({ config: newConfig, outDir: args.out, write: true })\n success(`Regenerated (${r.durationMs}ms)`)\n } catch (err) {\n error((err as Error).message)\n }\n }, 200)\n })\n\n // Keep process alive\n await new Promise(() => {})\n } catch (err) {\n error((err as Error).message)\n process.exitCode = 1\n }\n },\n})\n"],"mappings":";;;;;;;;;;;;AAAA,SAAS,qBAAqB;AAG9B,SAAS,aAAa;AAEtB,IAAO,cAAQ,cAAc;AAAA,EAC3B,MAAM,EAAE,MAAM,OAAO,aAAa,0CAA0C;AAAA,EAC5E,MAAM;AAAA,IACJ,KAAK;AAAA,MACH,MAAM;AAAA,MACN,aAAa;AAAA,MACb,SAAS;AAAA,IACX;AAAA,EACF;AAAA,EACA,MAAM,IAAI,EAAE,KAAK,GAAG;AAClB,QAAI;AACF,YAAM,EAAE,SAAS,IAAI,MAAM,OAAO,oBAAoB;AAGtD,YAAM,EAAE,QAAQ,WAAW,IAAI,MAAM,WAAW;AAChD,YAAM,SAAS,MAAM,SAAS,EAAE,QAAQ,QAAQ,KAAK,KAAK,OAAO,KAAK,CAAC;AACvE;AAAA,QACE,aAAa,OAAO,MAAM,MAAM,cAAc,OAAO,OAAO,MAAM,cAAc,OAAO,UAAU;AAAA,MACnG;AACA,WAAK,YAAY,IAAI,UAAU,CAAC,mBAAmB,IAAI,kBAAkB,CAAC,EAAE;AAG5E,UAAI,WAAiD;AAErD,YAAM,YAAY,MAAM;AACtB,YAAI,SAAU,cAAa,QAAQ;AACnC,mBAAW,WAAW,YAAY;AAChC,cAAI;AACF,kBAAM,EAAE,QAAQ,UAAU,IAAI,MAAM,WAAW;AAC/C,kBAAM,IAAI,MAAM,SAAS,EAAE,QAAQ,WAAW,QAAQ,KAAK,KAAK,OAAO,KAAK,CAAC;AAC7E,oBAAQ,gBAAgB,EAAE,UAAU,KAAK;AAAA,UAC3C,SAAS,KAAK;AACZ,kBAAO,IAAc,OAAO;AAAA,UAC9B;AAAA,QACF,GAAG,GAAG;AAAA,MACR,CAAC;AAGD,YAAM,IAAI,QAAQ,MAAM;AAAA,MAAC,CAAC;AAAA,IAC5B,SAAS,KAAK;AACZ,YAAO,IAAc,OAAO;AAC5B,cAAQ,WAAW;AAAA,IACrB;AAAA,EACF;AACF,CAAC;","names":[]}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
projectPath
|
|
4
|
+
} from "./chunk-ALA4X7UU.js";
|
|
5
|
+
import {
|
|
6
|
+
loadContext
|
|
7
|
+
} from "./chunk-QMF7LD67.js";
|
|
8
|
+
import {
|
|
9
|
+
confirm
|
|
10
|
+
} from "./chunk-QXIVJY7K.js";
|
|
11
|
+
import {
|
|
12
|
+
createApiClient
|
|
13
|
+
} from "./chunk-T3UROBMA.js";
|
|
14
|
+
import "./chunk-7TA63VHV.js";
|
|
15
|
+
import {
|
|
16
|
+
bold,
|
|
17
|
+
dim,
|
|
18
|
+
error,
|
|
19
|
+
info,
|
|
20
|
+
newline,
|
|
21
|
+
success,
|
|
22
|
+
table
|
|
23
|
+
} from "./chunk-WZYOSGN3.js";
|
|
24
|
+
|
|
25
|
+
// src/commands/envs.ts
|
|
26
|
+
import { defineCommand } from "citty";
|
|
27
|
+
var envs_default = defineCommand({
|
|
28
|
+
meta: { name: "envs", description: "Manage environments" },
|
|
29
|
+
subCommands: {
|
|
30
|
+
list: defineCommand({
|
|
31
|
+
meta: { name: "list", description: "List environments in the current project" },
|
|
32
|
+
async run() {
|
|
33
|
+
const ctx = await loadContext();
|
|
34
|
+
if (!ctx) {
|
|
35
|
+
error("No .semilayerrc found. Run `semilayer init` first.");
|
|
36
|
+
process.exitCode = 1;
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const api = await createApiClient();
|
|
40
|
+
const { environments } = await api.get(`${projectPath(ctx)}/envs`);
|
|
41
|
+
if (environments.length === 0) {
|
|
42
|
+
newline();
|
|
43
|
+
bold("No environments found.");
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
table(
|
|
47
|
+
["Slug", "Name"],
|
|
48
|
+
environments.map((e) => [e.slug, e.name])
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}),
|
|
52
|
+
create: defineCommand({
|
|
53
|
+
meta: { name: "create", description: "Create a new environment" },
|
|
54
|
+
args: {
|
|
55
|
+
name: { type: "string", description: "Environment name", required: true },
|
|
56
|
+
slug: { type: "string", description: "Environment slug", required: true }
|
|
57
|
+
},
|
|
58
|
+
async run({ args }) {
|
|
59
|
+
const ctx = await loadContext();
|
|
60
|
+
if (!ctx) {
|
|
61
|
+
error("No .semilayerrc found. Run `semilayer init` first.");
|
|
62
|
+
process.exitCode = 1;
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const api = await createApiClient();
|
|
66
|
+
const result = await api.post(`${projectPath(ctx)}/envs`, { name: args.name, slug: args.slug });
|
|
67
|
+
success(`Environment "${args.name}" (${args.slug}) created.`);
|
|
68
|
+
newline();
|
|
69
|
+
if (result.keys && result.keys.length > 0) {
|
|
70
|
+
bold("API Keys:");
|
|
71
|
+
for (const k of result.keys) {
|
|
72
|
+
info(` ${k.type}: ${k.key}`);
|
|
73
|
+
}
|
|
74
|
+
newline();
|
|
75
|
+
dim("Save these keys \u2014 they will not be shown again.");
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}),
|
|
79
|
+
delete: defineCommand({
|
|
80
|
+
meta: { name: "delete", description: "Delete an environment" },
|
|
81
|
+
args: {
|
|
82
|
+
env: { type: "string", description: "Environment slug to delete", required: true }
|
|
83
|
+
},
|
|
84
|
+
async run({ args }) {
|
|
85
|
+
const ctx = await loadContext();
|
|
86
|
+
if (!ctx) {
|
|
87
|
+
error("No .semilayerrc found. Run `semilayer init` first.");
|
|
88
|
+
process.exitCode = 1;
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const ok = await confirm(
|
|
92
|
+
`Delete environment "${args.env}" and all its data? This cannot be undone.`
|
|
93
|
+
);
|
|
94
|
+
if (!ok) return;
|
|
95
|
+
const api = await createApiClient();
|
|
96
|
+
await api.del(`${projectPath(ctx)}/envs/${args.env}`);
|
|
97
|
+
success(`Environment "${args.env}" deleted.`);
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
export {
|
|
103
|
+
envs_default as default
|
|
104
|
+
};
|
|
105
|
+
//# sourceMappingURL=envs-RNZQ3OQP.js.map
|