@glassly/cli 2.0.3-dev.0 → 2.0.3
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/README.md +1 -1
- package/package.json +2 -2
- package/src/credentials.ts +21 -113
- package/src/index.ts +2 -2
package/README.md
CHANGED
|
@@ -62,5 +62,5 @@ The CLI talks to the production Glassly backend and Developer Console by
|
|
|
62
62
|
default; nothing needs configuring. To point it at another deployment, set
|
|
63
63
|
`CLOUD_CORE_URL`, `CLOUD_CONSOLE_URL`, and that backend's public WorkOS
|
|
64
64
|
AuthKit client id in `WORKOS_CLIENT_ID`. Logins and the bundle signing key are
|
|
65
|
-
stored per backend URL in `~/.glassly/cli
|
|
65
|
+
stored per backend URL in `~/.glassly/cli/`, readable only by your user;
|
|
66
66
|
`glassly logout` removes the login.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glassly/cli",
|
|
3
|
-
"version": "2.0.3
|
|
3
|
+
"version": "2.0.3",
|
|
4
4
|
"description": "The glassly command: develop, pack, release, and publish Glassly miniapps. Bun-only.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"typecheck": "tsc --noEmit"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@glassly/miniapp-cli": "0.1.1
|
|
25
|
+
"@glassly/miniapp-cli": "0.1.1",
|
|
26
26
|
"commander": "^14.0.2",
|
|
27
27
|
"qrcode": "^1.5.4"
|
|
28
28
|
},
|
package/src/credentials.ts
CHANGED
|
@@ -2,12 +2,9 @@ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
const CREDS_DIR = join(CONFIG_DIR, "cli-v2");
|
|
9
|
-
const LEGACY_CREDS_FILE = join(CREDS_DIR, "credentials.json");
|
|
10
|
-
const SIGNING_KEY_SERVICE = "glassly-cli-v2-signing";
|
|
5
|
+
// Logins and the bundle signing key live as 0600 files under ~/.glassly/cli,
|
|
6
|
+
// one per backend URL, the way gh and npm keep theirs.
|
|
7
|
+
const CREDS_DIR = join(homedir(), ".glassly", "cli");
|
|
11
8
|
|
|
12
9
|
export type CliJwk = Record<string, unknown> & {
|
|
13
10
|
kty?: string;
|
|
@@ -36,142 +33,53 @@ export interface CliSigningKey {
|
|
|
36
33
|
createdAt: string;
|
|
37
34
|
}
|
|
38
35
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
// but on some machines a write there succeeds and the next process reads
|
|
42
|
-
// nothing back, which showed up as "login works, whoami says sign in". The
|
|
43
|
-
// keychain is still read so sessions stored there keep working.
|
|
44
|
-
export async function saveCredentials(credentials: CliCredentials): Promise<"keychain" | "file"> {
|
|
45
|
-
const payload = JSON.stringify(credentials);
|
|
46
|
-
mkdirSync(CREDS_DIR, { recursive: true });
|
|
47
|
-
writeFileSync(credentialsFile(credentials.coreUrl), `${payload}\n`, { mode: 0o600 });
|
|
36
|
+
export async function saveCredentials(credentials: CliCredentials): Promise<"file"> {
|
|
37
|
+
writeSecret(credentialsFile(credentials.coreUrl), credentials);
|
|
48
38
|
return "file";
|
|
49
39
|
}
|
|
50
40
|
|
|
51
|
-
export async function loadCredentials(coreUrl
|
|
52
|
-
const targetCoreUrl = coreUrl ? normalizeCoreUrl(coreUrl) : undefined;
|
|
41
|
+
export async function loadCredentials(coreUrl: string): Promise<CliCredentials | null> {
|
|
53
42
|
if (process.env.CLI_TOKEN) {
|
|
54
43
|
return {
|
|
55
44
|
token: process.env.CLI_TOKEN,
|
|
56
45
|
workosUserId: process.env.CLI_WORKOS_USER_ID || "unknown",
|
|
57
46
|
email: process.env.CLI_EMAIL || "unknown",
|
|
58
47
|
organizationId: process.env.CLI_ORGANIZATION_ID,
|
|
59
|
-
coreUrl:
|
|
48
|
+
coreUrl: normalizeCoreUrl(coreUrl),
|
|
60
49
|
storedAt: new Date().toISOString(),
|
|
61
50
|
};
|
|
62
51
|
}
|
|
63
|
-
|
|
64
|
-
const scoped = targetCoreUrl ? await loadScopedCredentials(targetCoreUrl) : null;
|
|
65
|
-
if (scoped) return scoped;
|
|
66
|
-
|
|
67
|
-
try {
|
|
68
|
-
if (typeof Bun !== "undefined" && Bun.secrets) {
|
|
69
|
-
const value = await Bun.secrets.get({
|
|
70
|
-
service: SERVICE,
|
|
71
|
-
name: LEGACY_NAME,
|
|
72
|
-
});
|
|
73
|
-
if (value) {
|
|
74
|
-
const legacy = JSON.parse(value) as CliCredentials;
|
|
75
|
-
if (!targetCoreUrl || normalizeCoreUrl(legacy.coreUrl) === targetCoreUrl) return legacy;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
} catch {
|
|
79
|
-
// Fall through.
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
try {
|
|
83
|
-
if (existsSync(LEGACY_CREDS_FILE)) {
|
|
84
|
-
const legacy = JSON.parse(readFileSync(LEGACY_CREDS_FILE, "utf8")) as CliCredentials;
|
|
85
|
-
if (!targetCoreUrl || normalizeCoreUrl(legacy.coreUrl) === targetCoreUrl) return legacy;
|
|
86
|
-
}
|
|
87
|
-
} catch {
|
|
88
|
-
// Treat corrupt credentials as logged out.
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return null;
|
|
52
|
+
return readSecret<CliCredentials>(credentialsFile(coreUrl));
|
|
92
53
|
}
|
|
93
54
|
|
|
94
|
-
export async function clearCredentials(coreUrl
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
try {
|
|
98
|
-
if (typeof Bun !== "undefined" && Bun.secrets) {
|
|
99
|
-
await Bun.secrets.set({
|
|
100
|
-
service: SERVICE,
|
|
101
|
-
name: targetCoreUrl ? credentialName(targetCoreUrl) : LEGACY_NAME,
|
|
102
|
-
value: "",
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
} catch {
|
|
106
|
-
// Ignore keychain failures; remove file fallback below.
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const path = targetCoreUrl ? credentialsFile(targetCoreUrl) : LEGACY_CREDS_FILE;
|
|
110
|
-
if (existsSync(path)) {
|
|
111
|
-
rmSync(path);
|
|
112
|
-
}
|
|
55
|
+
export async function clearCredentials(coreUrl: string): Promise<void> {
|
|
56
|
+
const path = credentialsFile(coreUrl);
|
|
57
|
+
if (existsSync(path)) rmSync(path);
|
|
113
58
|
}
|
|
114
59
|
|
|
115
|
-
export async function saveSigningKey(key: CliSigningKey): Promise<"
|
|
116
|
-
|
|
117
|
-
mkdirSync(CREDS_DIR, { recursive: true });
|
|
118
|
-
writeFileSync(signingKeyFile(key.coreUrl), `${payload}\n`, { mode: 0o600 });
|
|
60
|
+
export async function saveSigningKey(key: CliSigningKey): Promise<"file"> {
|
|
61
|
+
writeSecret(signingKeyFile(key.coreUrl), key);
|
|
119
62
|
return "file";
|
|
120
63
|
}
|
|
121
64
|
|
|
122
65
|
export async function loadSigningKey(coreUrl: string): Promise<CliSigningKey | null> {
|
|
123
|
-
|
|
124
|
-
try {
|
|
125
|
-
const path = signingKeyFile(targetCoreUrl);
|
|
126
|
-
if (existsSync(path)) return JSON.parse(readFileSync(path, "utf8")) as CliSigningKey;
|
|
127
|
-
} catch {
|
|
128
|
-
// Treat corrupt key storage as missing.
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Keys created by earlier versions sit in the OS keychain.
|
|
132
|
-
try {
|
|
133
|
-
if (typeof Bun !== "undefined" && Bun.secrets) {
|
|
134
|
-
const value = await Bun.secrets.get({
|
|
135
|
-
service: SIGNING_KEY_SERVICE,
|
|
136
|
-
name: credentialName(targetCoreUrl),
|
|
137
|
-
});
|
|
138
|
-
if (value) return JSON.parse(value) as CliSigningKey;
|
|
139
|
-
}
|
|
140
|
-
} catch {
|
|
141
|
-
// Fall through.
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
return null;
|
|
66
|
+
return readSecret<CliSigningKey>(signingKeyFile(coreUrl));
|
|
145
67
|
}
|
|
146
68
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
} catch {
|
|
152
|
-
// Treat corrupt credentials as logged out.
|
|
153
|
-
}
|
|
69
|
+
function writeSecret(path: string, value: unknown): void {
|
|
70
|
+
mkdirSync(CREDS_DIR, { recursive: true, mode: 0o700 });
|
|
71
|
+
writeFileSync(path, `${JSON.stringify(value)}\n`, { mode: 0o600 });
|
|
72
|
+
}
|
|
154
73
|
|
|
155
|
-
|
|
74
|
+
function readSecret<T>(path: string): T | null {
|
|
156
75
|
try {
|
|
157
|
-
if (
|
|
158
|
-
const value = await Bun.secrets.get({
|
|
159
|
-
service: SERVICE,
|
|
160
|
-
name: credentialName(coreUrl),
|
|
161
|
-
});
|
|
162
|
-
if (value) return JSON.parse(value) as CliCredentials;
|
|
163
|
-
}
|
|
76
|
+
if (existsSync(path)) return JSON.parse(readFileSync(path, "utf8")) as T;
|
|
164
77
|
} catch {
|
|
165
|
-
//
|
|
78
|
+
// A corrupt file reads as logged out; the next login overwrites it.
|
|
166
79
|
}
|
|
167
|
-
|
|
168
80
|
return null;
|
|
169
81
|
}
|
|
170
82
|
|
|
171
|
-
function credentialName(coreUrl: string): string {
|
|
172
|
-
return `credentials:${credentialKey(coreUrl)}`;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
83
|
function credentialsFile(coreUrl: string): string {
|
|
176
84
|
return join(CREDS_DIR, `credentials-${credentialKey(coreUrl)}.json`);
|
|
177
85
|
}
|
package/src/index.ts
CHANGED
|
@@ -122,7 +122,7 @@ program
|
|
|
122
122
|
: expiresAtFromToken(token.access_token)
|
|
123
123
|
? new Date(expiresAtFromToken(token.access_token)! * 1000)
|
|
124
124
|
: undefined;
|
|
125
|
-
|
|
125
|
+
await saveCredentials({
|
|
126
126
|
token: token.access_token,
|
|
127
127
|
refreshToken: token.refresh_token,
|
|
128
128
|
workosUserId: token.user.id,
|
|
@@ -135,7 +135,7 @@ program
|
|
|
135
135
|
});
|
|
136
136
|
console.log(`Signed in as ${token.user.email}`);
|
|
137
137
|
if (token.organization_id) console.log(`Organization: ${token.organization_id}`);
|
|
138
|
-
console.log(
|
|
138
|
+
console.log("Credentials stored in ~/.glassly/cli (readable only by you)");
|
|
139
139
|
return;
|
|
140
140
|
}
|
|
141
141
|
|