@deploily/deploily-cli 0.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.
Files changed (46) hide show
  1. package/.devcontainer/devcontainer.json +25 -0
  2. package/.devcontainer/docker-compose.yml +18 -0
  3. package/.env.example +8 -0
  4. package/.github/dependabot.yml +12 -0
  5. package/.github/workflows/publish.yml +39 -0
  6. package/LICENSE +201 -0
  7. package/README.md +105 -0
  8. package/dist/auth/callback.d.ts +3 -0
  9. package/dist/auth/callback.d.ts.map +1 -0
  10. package/dist/auth/callback.js +143 -0
  11. package/dist/auth/callback.js.map +1 -0
  12. package/dist/auth/index.d.ts +23 -0
  13. package/dist/auth/index.d.ts.map +1 -0
  14. package/dist/auth/index.js +173 -0
  15. package/dist/auth/index.js.map +1 -0
  16. package/dist/commands/auth.d.ts +4 -0
  17. package/dist/commands/auth.d.ts.map +1 -0
  18. package/dist/commands/auth.js +90 -0
  19. package/dist/commands/auth.js.map +1 -0
  20. package/dist/config/constants.d.ts +22 -0
  21. package/dist/config/constants.d.ts.map +1 -0
  22. package/dist/config/constants.js +25 -0
  23. package/dist/config/constants.js.map +1 -0
  24. package/dist/index.d.ts +3 -0
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/index.js +61 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/storage/credentials.d.ts +47 -0
  29. package/dist/storage/credentials.d.ts.map +1 -0
  30. package/dist/storage/credentials.js +160 -0
  31. package/dist/storage/credentials.js.map +1 -0
  32. package/dist/utils/pkce.d.ts +5 -0
  33. package/dist/utils/pkce.d.ts.map +1 -0
  34. package/dist/utils/pkce.js +30 -0
  35. package/dist/utils/pkce.js.map +1 -0
  36. package/package.json +37 -0
  37. package/pnpm-workspace.yaml +5 -0
  38. package/src/auth/callback.ts +159 -0
  39. package/src/auth/index.ts +238 -0
  40. package/src/commands/auth.ts +114 -0
  41. package/src/config/constants.ts +28 -0
  42. package/src/index.ts +69 -0
  43. package/src/storage/credentials.ts +188 -0
  44. package/src/types/index.d.ts +35 -0
  45. package/src/utils/pkce.ts +36 -0
  46. package/tsconfig.json +24 -0
@@ -0,0 +1,188 @@
1
+ import fs from "fs"
2
+ import path from "path"
3
+ import os from "os"
4
+ import { STORAGE_CONFIG } from "../config/constants.js"
5
+ import { StoredCredentials } from "../types/index.js"
6
+
7
+ // Lazy load keytar - it's optional and may not be available
8
+ let keytar: any = null
9
+ let keytarLoaded = false
10
+
11
+ async function getKeytar() {
12
+ if (keytarLoaded) {
13
+ return keytar
14
+ }
15
+ keytarLoaded = true
16
+ try {
17
+ keytar = await import("keytar")
18
+ } catch (error) {
19
+ // keytar not available, will use fallback
20
+ keytar = null
21
+ }
22
+ return keytar
23
+ }
24
+
25
+ class CredentialStorage {
26
+ private serviceName = STORAGE_CONFIG.SERVICE_NAME
27
+ private accountName = "deploily-auth"
28
+ private configDir: string
29
+ private configFilePath: string
30
+
31
+ constructor() {
32
+ this.configDir = this.expandPath(STORAGE_CONFIG.CONFIG_DIR)
33
+ this.configFilePath = path.join(this.configDir, STORAGE_CONFIG.CONFIG_FILE)
34
+ }
35
+
36
+ /**
37
+ * Expand ~ to home directory
38
+ */
39
+ private expandPath(filepath: string): string {
40
+ if (filepath.startsWith("~")) {
41
+ return path.join(os.homedir(), filepath.slice(1))
42
+ }
43
+ return filepath
44
+ }
45
+
46
+ /**
47
+ * Save credentials using keytar (primary) or fallback to config file
48
+ */
49
+ async save(credentials: StoredCredentials): Promise<void> {
50
+ const kt = await getKeytar()
51
+ try {
52
+ // Try keytar first
53
+ if (kt) {
54
+ await kt.setPassword(
55
+ this.serviceName,
56
+ this.accountName,
57
+ JSON.stringify(credentials),
58
+ )
59
+ return
60
+ }
61
+ } catch (error) {
62
+ // Fallback to config file
63
+ }
64
+
65
+ // Fallback to config file
66
+ console.warn("Keytar unavailable, using config file for token storage")
67
+ await this.saveToConfigFile(credentials)
68
+ }
69
+
70
+ /**
71
+ * Load credentials using keytar (primary) or fallback to config file
72
+ */
73
+ async load(): Promise<StoredCredentials | null> {
74
+ const kt = await getKeytar()
75
+ try {
76
+ // Try keytar first
77
+ if (kt) {
78
+ const password = await kt.getPassword(
79
+ this.serviceName,
80
+ this.accountName,
81
+ )
82
+ if (password) {
83
+ return JSON.parse(password)
84
+ }
85
+ }
86
+ } catch (error) {
87
+ // Fallback to config file
88
+ console.warn("Keytar unavailable, checking config file")
89
+ }
90
+
91
+ // Try loading from config file
92
+ return await this.loadFromConfigFile()
93
+ }
94
+
95
+ /**
96
+ * Clear credentials from keytar (primary) and config file
97
+ */
98
+ async clear(): Promise<void> {
99
+ const kt = await getKeytar()
100
+ try {
101
+ // Clear from keytar
102
+ if (kt) {
103
+ await kt.deletePassword(this.serviceName, this.accountName)
104
+ }
105
+ } catch (error) {
106
+ // Fallback to config file
107
+ console.warn("Could not clear keytar credentials")
108
+ }
109
+
110
+ // Clear from config file
111
+ await this.clearConfigFile()
112
+ }
113
+
114
+ /**
115
+ * Save to config file (fallback method)
116
+ */
117
+ private async saveToConfigFile(
118
+ credentials: StoredCredentials,
119
+ ): Promise<void> {
120
+ try {
121
+ // Ensure config directory exists
122
+ if (!fs.existsSync(this.configDir)) {
123
+ fs.mkdirSync(this.configDir, { recursive: true })
124
+ }
125
+
126
+ fs.writeFileSync(
127
+ this.configFilePath,
128
+ JSON.stringify(credentials, null, 2),
129
+ { mode: 0o600 }, // Restrict permissions to owner only
130
+ )
131
+ } catch (error) {
132
+ throw new Error(`Failed to save credentials: ${error}`)
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Load from config file (fallback method)
138
+ */
139
+ private async loadFromConfigFile(): Promise<StoredCredentials | null> {
140
+ try {
141
+ if (!fs.existsSync(this.configFilePath)) {
142
+ return null
143
+ }
144
+
145
+ const content = fs.readFileSync(this.configFilePath, "utf-8")
146
+ return JSON.parse(content)
147
+ } catch (error) {
148
+ return null
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Clear config file
154
+ */
155
+ private async clearConfigFile(): Promise<void> {
156
+ try {
157
+ if (fs.existsSync(this.configFilePath)) {
158
+ fs.unlinkSync(this.configFilePath)
159
+ }
160
+ } catch (error) {
161
+ // Silently fail if file doesn't exist
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Check if credentials exist
167
+ */
168
+ async exists(): Promise<boolean> {
169
+ const credentials = await this.load()
170
+ return credentials !== null
171
+ }
172
+
173
+ /**
174
+ * Check if credentials are expired
175
+ */
176
+ async isExpired(): Promise<boolean> {
177
+ const credentials = await this.load()
178
+ if (!credentials) {
179
+ return true
180
+ }
181
+
182
+ // Check if token expires in less than 5 minutes
183
+ const bufferSeconds = 300
184
+ return credentials.expires_at < Date.now() / 1000 + bufferSeconds
185
+ }
186
+ }
187
+
188
+ export const credentialStorage = new CredentialStorage()
@@ -0,0 +1,35 @@
1
+ export interface CallbackResult {
2
+ code: string
3
+ state: string
4
+ }
5
+
6
+ // auth
7
+ export interface TokenResponse {
8
+ access_token: string
9
+ refresh_token: string
10
+ expires_in: number
11
+ token_type: string
12
+ scope: string
13
+ }
14
+
15
+ export interface UserInfo {
16
+ email: string
17
+ name: string
18
+ preferred_username: string
19
+ given_name: string
20
+ family_name: string
21
+ }
22
+
23
+ export interface AuthContext {
24
+ state: string
25
+ codeVerifier: string
26
+ codeChallenge: string
27
+ }
28
+
29
+ // storage
30
+ export interface StoredCredentials {
31
+ access_token: string
32
+ refresh_token: string
33
+ expires_at: number
34
+ user_email?: string
35
+ }
@@ -0,0 +1,36 @@
1
+ import crypto from "crypto"
2
+
3
+ // Generate a cryptographically secure random string
4
+ export function generateRandomString(length: number = 32): string {
5
+ return crypto.randomBytes(length).toString("hex")
6
+ }
7
+
8
+ // Generate PKCE code verifier
9
+ export function generateCodeVerifier(): string {
10
+ const length = 128
11
+ const charset =
12
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~"
13
+ let result = ""
14
+ const randomValues = crypto.getRandomValues(new Uint8Array(length))
15
+
16
+ for (let i = 0; i < length; i++) {
17
+ result += charset[randomValues[i] % charset.length]
18
+ }
19
+
20
+ return result
21
+ }
22
+
23
+ // Generate PKCE code challenge from verifier
24
+ export function generateCodeChallenge(codeVerifier: string): string {
25
+ const hash = crypto.createHash("sha256").update(codeVerifier).digest()
26
+ return hash
27
+ .toString("base64")
28
+ .replace(/\+/g, "-")
29
+ .replace(/\//g, "_")
30
+ .replace(/=+$/, "")
31
+ }
32
+
33
+ // Generate state parameter for CSRF protection
34
+ export function generateState(): string {
35
+ return generateRandomString(32)
36
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ESNext",
5
+ "lib": ["ES2020"],
6
+ "moduleResolution": "bundler",
7
+ "types": ["node"],
8
+ "esModuleInterop": true,
9
+ "jsx": "react-jsx",
10
+ "jsxImportSource": "hono/jsx",
11
+ "allowSyntheticDefaultImports": true,
12
+ "strict": true,
13
+ "skipLibCheck": true,
14
+ "forceConsistentCasingInFileNames": true,
15
+ "resolveJsonModule": true,
16
+ "declaration": true,
17
+ "declarationMap": true,
18
+ "sourceMap": true,
19
+ "outDir": "./dist",
20
+ "rootDir": "./src"
21
+ },
22
+ "include": ["src/**/*"],
23
+ "exclude": ["node_modules", "dist", "**/*.test.ts"]
24
+ }