@layr-labs/ecloud-cli 0.2.0-dev → 0.2.0-dev.1
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/VERSION +2 -2
- package/dist/commands/auth/generate.js +4 -4
- package/dist/commands/auth/generate.js.map +1 -1
- package/dist/commands/auth/login.js +2 -2
- package/dist/commands/auth/login.js.map +1 -1
- package/dist/commands/auth/logout.js +2 -2
- package/dist/commands/auth/logout.js.map +1 -1
- package/dist/commands/auth/migrate.js +2 -2
- package/dist/commands/auth/migrate.js.map +1 -1
- package/dist/commands/auth/whoami.js +7 -5
- package/dist/commands/auth/whoami.js.map +1 -1
- package/dist/commands/billing/cancel.js +9 -7
- package/dist/commands/billing/cancel.js.map +1 -1
- package/dist/commands/billing/status.js +8 -5
- package/dist/commands/billing/status.js.map +1 -1
- package/dist/commands/billing/subscribe.js +14 -10
- package/dist/commands/billing/subscribe.js.map +1 -1
- package/dist/commands/compute/app/create.js +2 -2
- package/dist/commands/compute/app/create.js.map +1 -1
- package/dist/commands/compute/app/deploy.js +105 -105
- package/dist/commands/compute/app/deploy.js.map +1 -1
- package/dist/commands/compute/app/info.js +55 -8
- package/dist/commands/compute/app/info.js.map +1 -1
- package/dist/commands/compute/app/list.js +8 -9
- package/dist/commands/compute/app/list.js.map +1 -1
- package/dist/commands/compute/app/logs.js +58 -11
- package/dist/commands/compute/app/logs.js.map +1 -1
- package/dist/commands/compute/app/profile/set.js +147 -22
- package/dist/commands/compute/app/profile/set.js.map +1 -1
- package/dist/commands/compute/app/releases.js +110 -143
- package/dist/commands/compute/app/releases.js.map +1 -1
- package/dist/commands/compute/app/start.js +58 -11
- package/dist/commands/compute/app/start.js.map +1 -1
- package/dist/commands/compute/app/stop.js +58 -11
- package/dist/commands/compute/app/stop.js.map +1 -1
- package/dist/commands/compute/app/terminate.js +58 -11
- package/dist/commands/compute/app/terminate.js.map +1 -1
- package/dist/commands/compute/app/upgrade.js +121 -67
- package/dist/commands/compute/app/upgrade.js.map +1 -1
- package/dist/commands/compute/build/info.js +8 -9
- package/dist/commands/compute/build/info.js.map +1 -1
- package/dist/commands/compute/build/list.js +50 -131
- package/dist/commands/compute/build/list.js.map +1 -1
- package/dist/commands/compute/build/logs.js +9 -10
- package/dist/commands/compute/build/logs.js.map +1 -1
- package/dist/commands/compute/build/status.js +9 -10
- package/dist/commands/compute/build/status.js.map +1 -1
- package/dist/commands/compute/build/submit.js +8 -9
- package/dist/commands/compute/build/submit.js.map +1 -1
- package/dist/commands/compute/build/verify.js +59 -12
- package/dist/commands/compute/build/verify.js.map +1 -1
- package/dist/commands/compute/environment/list.js +2 -2
- package/dist/commands/compute/environment/list.js.map +1 -1
- package/dist/commands/compute/environment/set.js +2 -2
- package/dist/commands/compute/environment/set.js.map +1 -1
- package/dist/commands/compute/environment/show.js +2 -2
- package/dist/commands/compute/environment/show.js.map +1 -1
- package/dist/commands/compute/undelegate.js +8 -9
- package/dist/commands/compute/undelegate.js.map +1 -1
- package/dist/commands/{telemetry.js → telemetry/disable.js} +15 -64
- package/dist/commands/telemetry/disable.js.map +1 -0
- package/dist/commands/telemetry/enable.js +164 -0
- package/dist/commands/telemetry/enable.js.map +1 -0
- package/dist/commands/telemetry/status.js +159 -0
- package/dist/commands/telemetry/status.js.map +1 -0
- package/dist/commands/upgrade.js +2 -2
- package/dist/commands/upgrade.js.map +1 -1
- package/dist/commands/version.js +2 -2
- package/dist/commands/version.js.map +1 -1
- package/package.json +4 -2
- package/dist/commands/telemetry.js.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// src/commands/telemetry.ts
|
|
4
|
-
import { Command
|
|
3
|
+
// src/commands/telemetry/disable.ts
|
|
4
|
+
import { Command } from "@oclif/core";
|
|
5
5
|
|
|
6
6
|
// src/utils/globalConfig.ts
|
|
7
7
|
import * as fs from "fs";
|
|
@@ -100,8 +100,8 @@ function createCLITelemetryClient() {
|
|
|
100
100
|
const environment = createAppEnvironment(userUUID);
|
|
101
101
|
const telemetryEnabled = getGlobalTelemetryPreference();
|
|
102
102
|
return createTelemetryClient(environment, "ecloud-cli", {
|
|
103
|
-
telemetryEnabled: telemetryEnabled
|
|
104
|
-
//
|
|
103
|
+
telemetryEnabled: telemetryEnabled !== false
|
|
104
|
+
// Enabled by default, disabled only if explicitly set to false
|
|
105
105
|
});
|
|
106
106
|
}
|
|
107
107
|
async function withTelemetry(command, action) {
|
|
@@ -143,71 +143,22 @@ async function withTelemetry(command, action) {
|
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
// src/commands/telemetry.ts
|
|
147
|
-
var
|
|
148
|
-
static description = "
|
|
149
|
-
static flags = {
|
|
150
|
-
enable: Flags.boolean({
|
|
151
|
-
description: "Enable telemetry collection"
|
|
152
|
-
}),
|
|
153
|
-
disable: Flags.boolean({
|
|
154
|
-
description: "Disable telemetry collection"
|
|
155
|
-
}),
|
|
156
|
-
status: Flags.boolean({
|
|
157
|
-
description: "Show current telemetry status"
|
|
158
|
-
})
|
|
159
|
-
};
|
|
146
|
+
// src/commands/telemetry/disable.ts
|
|
147
|
+
var TelemetryDisable = class extends Command {
|
|
148
|
+
static description = "Disable telemetry collection";
|
|
160
149
|
async run() {
|
|
161
150
|
return withTelemetry(this, async () => {
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
if (enable && disable || !enable && !disable && !status) {
|
|
167
|
-
throw new Error("specify exactly one of --enable, --disable, or --status");
|
|
151
|
+
const currentPreference = getGlobalTelemetryPreference();
|
|
152
|
+
if (currentPreference === false) {
|
|
153
|
+
this.log("\nTelemetry is already disabled");
|
|
154
|
+
return;
|
|
168
155
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
}
|
|
172
|
-
if (enable) {
|
|
173
|
-
return this.enableTelemetry();
|
|
174
|
-
}
|
|
175
|
-
if (disable) {
|
|
176
|
-
return this.disableTelemetry();
|
|
177
|
-
}
|
|
178
|
-
return void 0;
|
|
156
|
+
setGlobalTelemetryPreference(false);
|
|
157
|
+
this.log("\nTelemetry disabled");
|
|
179
158
|
});
|
|
180
159
|
}
|
|
181
|
-
showTelemetryStatus() {
|
|
182
|
-
const globalPreference = getGlobalTelemetryPreference();
|
|
183
|
-
if (globalPreference === void 0) {
|
|
184
|
-
this.log("Telemetry: Not set (defaults to disabled)");
|
|
185
|
-
} else if (globalPreference) {
|
|
186
|
-
this.log("Telemetry: Enabled");
|
|
187
|
-
} else {
|
|
188
|
-
this.log("Telemetry: Disabled");
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
enableTelemetry() {
|
|
192
|
-
const currentPreference = getGlobalTelemetryPreference();
|
|
193
|
-
if (currentPreference === true) {
|
|
194
|
-
this.log("\n\u2705 Telemetry is already enabled");
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
setGlobalTelemetryPreference(true);
|
|
198
|
-
this.log("\n\u2705 Telemetry enabled");
|
|
199
|
-
}
|
|
200
|
-
disableTelemetry() {
|
|
201
|
-
const currentPreference = getGlobalTelemetryPreference();
|
|
202
|
-
if (currentPreference === false) {
|
|
203
|
-
this.log("\n\u2705 Telemetry is already disabled");
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
setGlobalTelemetryPreference(false);
|
|
207
|
-
this.log("\n\u274C Telemetry disabled");
|
|
208
|
-
}
|
|
209
160
|
};
|
|
210
161
|
export {
|
|
211
|
-
|
|
162
|
+
TelemetryDisable as default
|
|
212
163
|
};
|
|
213
|
-
//# sourceMappingURL=
|
|
164
|
+
//# sourceMappingURL=disable.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/commands/telemetry/disable.ts","../../../src/utils/globalConfig.ts","../../../src/telemetry.ts"],"sourcesContent":["import { Command } from \"@oclif/core\";\nimport {\n setGlobalTelemetryPreference,\n getGlobalTelemetryPreference,\n} from \"../../utils/globalConfig\";\nimport { withTelemetry } from \"../../telemetry\";\n\nexport default class TelemetryDisable extends Command {\n static description = \"Disable telemetry collection\";\n\n async run() {\n return withTelemetry(this, async () => {\n const currentPreference = getGlobalTelemetryPreference();\n if (currentPreference === false) {\n this.log(\"\\nTelemetry is already disabled\");\n return;\n }\n\n setGlobalTelemetryPreference(false);\n this.log(\"\\nTelemetry disabled\");\n });\n }\n}\n","/**\n * Global configuration management\n *\n * Stores user-level configuration that persists across all CLI usage.\n * - $XDG_CONFIG_HOME/ecloud[BuildSuffix]/config.yaml (if XDG_CONFIG_HOME is set)\n * - Or ~/.config/ecloud[BuildSuffix]/config.yaml (fallback)\n *\n * Where BuildSuffix is:\n * - \"\" (empty) for production builds\n * - \"-dev\" for development builds\n */\n\nimport * as fs from \"fs\";\nimport * as path from \"path\";\nimport * as os from \"os\";\nimport { load as loadYaml, dump as dumpYaml } from \"js-yaml\";\nimport { getBuildType } from \"@layr-labs/ecloud-sdk\";\nimport * as crypto from \"crypto\";\nconst GLOBAL_CONFIG_FILE = \"config.yaml\";\n\nexport interface ProfileCacheEntry {\n updated_at: number; // Unix timestamp in milliseconds\n profiles: { [appId: string]: string }; // appId -> profile name\n}\n\nexport interface GlobalConfig {\n first_run?: boolean;\n telemetry_enabled?: boolean;\n user_uuid?: string;\n default_environment?: string;\n last_version_check?: number;\n last_known_version?: string;\n profile_cache?: {\n [environment: string]: ProfileCacheEntry;\n };\n directory_links?: {\n [environment: string]: {\n [directoryPath: string]: string;\n };\n };\n}\n\n// Profile cache TTL: 24 hours in milliseconds\nconst PROFILE_CACHE_TTL_MS = 24 * 60 * 60 * 1000;\n\n/**\n * Get the XDG-compliant directory where global ecloud config should be stored\n */\nfunction getGlobalConfigDir(): string {\n // First check XDG_CONFIG_HOME\n const configHome = process.env.XDG_CONFIG_HOME;\n\n let baseDir: string;\n if (configHome && path.isAbsolute(configHome)) {\n baseDir = configHome;\n } else {\n // Fall back to ~/.config\n baseDir = path.join(os.homedir(), \".config\");\n }\n\n // Use environment-specific config directory\n const buildType = getBuildType();\n const buildSuffix = buildType === \"dev\" ? \"-dev\" : \"\";\n const configDirName = `ecloud${buildSuffix}`;\n\n return path.join(baseDir, configDirName);\n}\n\n/**\n * Get the full path to the global config file\n */\nfunction getGlobalConfigPath(): string {\n return path.join(getGlobalConfigDir(), GLOBAL_CONFIG_FILE);\n}\n\n/**\n * Load global configuration, creating defaults if needed\n */\nexport function loadGlobalConfig(): GlobalConfig {\n const configPath = getGlobalConfigPath();\n\n // If file doesn't exist, return defaults for first run\n if (!fs.existsSync(configPath)) {\n return {\n first_run: true,\n };\n }\n\n try {\n const content = fs.readFileSync(configPath, \"utf-8\");\n const config = loadYaml(content) as GlobalConfig;\n return config || { first_run: true };\n } catch {\n // If parsing fails, return defaults\n return {\n first_run: true,\n };\n }\n}\n\n/**\n * Save global configuration to disk\n */\nexport function saveGlobalConfig(config: GlobalConfig): void {\n const configPath = getGlobalConfigPath();\n\n // Ensure directory exists\n const configDir = path.dirname(configPath);\n fs.mkdirSync(configDir, { recursive: true, mode: 0o755 });\n\n // Write config file\n const content = dumpYaml(config, { lineWidth: -1 });\n fs.writeFileSync(configPath, content, { mode: 0o644 });\n}\n\nfunction normalizeDirectoryPath(directoryPath: string): string {\n const resolved = path.resolve(directoryPath);\n try {\n return fs.realpathSync(resolved);\n } catch {\n return resolved;\n }\n}\n\n/**\n * Get linked app ID for a directory in an environment\n */\nexport function getLinkedAppForDirectory(\n environment: string,\n directoryPath: string,\n): string | null {\n if (!directoryPath) {\n return null;\n }\n\n const config = loadGlobalConfig();\n const links = config.directory_links?.[environment];\n if (!links) {\n return null;\n }\n\n const normalizedPath = normalizeDirectoryPath(directoryPath);\n const appId = links[normalizedPath];\n return appId || null;\n}\n\n/**\n * Link a directory to an app ID in an environment\n */\nexport function setLinkedAppForDirectory(\n environment: string,\n directoryPath: string,\n appId: string,\n): void {\n if (!directoryPath || !environment) {\n return;\n }\n\n const config = loadGlobalConfig();\n if (!config.directory_links) {\n config.directory_links = {};\n }\n if (!config.directory_links[environment]) {\n config.directory_links[environment] = {};\n }\n\n const normalizedPath = normalizeDirectoryPath(directoryPath);\n // Normalize appId to lowercase for consistent lookups\n config.directory_links[environment][normalizedPath] = appId.toLowerCase();\n saveGlobalConfig(config);\n}\n\n/**\n * Get the user's preferred deployment environment\n */\nexport function getDefaultEnvironment(): string | undefined {\n const config = loadGlobalConfig();\n return config.default_environment;\n}\n\n/**\n * Set the user's preferred deployment environment\n */\nexport function setDefaultEnvironment(environment: string): void {\n const config = loadGlobalConfig();\n config.default_environment = environment;\n config.first_run = false; // No longer first run after setting environment\n saveGlobalConfig(config);\n}\n\n/**\n * Check if this is the user's first time running the CLI\n */\nexport function isFirstRun(): boolean {\n const config = loadGlobalConfig();\n return config.first_run === true;\n}\n\n/**\n * Mark that the first run has been completed\n */\nexport function markFirstRunComplete(): void {\n const config = loadGlobalConfig();\n config.first_run = false;\n saveGlobalConfig(config);\n}\n\n/**\n * Get the global telemetry preference\n */\nexport function getGlobalTelemetryPreference(): boolean | undefined {\n const config = loadGlobalConfig();\n return config.telemetry_enabled;\n}\n\n/**\n * Set the global telemetry preference\n */\nexport function setGlobalTelemetryPreference(enabled: boolean): void {\n const config = loadGlobalConfig();\n config.telemetry_enabled = enabled;\n config.first_run = false; // No longer first run after setting preference\n saveGlobalConfig(config);\n}\n\n// ==================== Profile Cache Functions ====================\n\n/**\n * Get cached profile names for an environment\n * Returns null if cache is missing or expired (older than 24 hours)\n */\nexport function getProfileCache(environment: string): Record<string, string> | null {\n const config = loadGlobalConfig();\n const cacheEntry = config.profile_cache?.[environment];\n\n if (!cacheEntry) {\n return null;\n }\n\n // Check if cache is expired\n const now = Date.now();\n if (now - cacheEntry.updated_at > PROFILE_CACHE_TTL_MS) {\n return null;\n }\n\n return cacheEntry.profiles;\n}\n\n/**\n * Set cached profile names for an environment\n */\nexport function setProfileCache(environment: string, profiles: Record<string, string>): void {\n const config = loadGlobalConfig();\n\n if (!config.profile_cache) {\n config.profile_cache = {};\n }\n\n config.profile_cache[environment] = {\n updated_at: Date.now(),\n profiles,\n };\n\n saveGlobalConfig(config);\n}\n\n/**\n * Invalidate profile cache for a specific environment or all environments\n */\nexport function invalidateProfileCache(environment?: string): void {\n const config = loadGlobalConfig();\n\n if (!config.profile_cache) {\n return;\n }\n\n if (environment) {\n // Invalidate specific environment\n delete config.profile_cache[environment];\n } else {\n // Invalidate all environments\n config.profile_cache = {};\n }\n\n saveGlobalConfig(config);\n}\n\n/**\n * Update a single profile name in the cache\n * This is useful after deploy or profile set to update just one entry\n */\nexport function updateProfileCacheEntry(\n environment: string,\n appId: string,\n profileName: string,\n): void {\n const config = loadGlobalConfig();\n\n if (!config.profile_cache) {\n config.profile_cache = {};\n }\n\n if (!config.profile_cache[environment]) {\n config.profile_cache[environment] = {\n updated_at: Date.now(),\n profiles: {},\n };\n }\n\n // Normalize appId to lowercase for consistent lookups\n const normalizedAppId = appId.toLowerCase();\n config.profile_cache[environment].profiles[normalizedAppId] = profileName;\n config.profile_cache[environment].updated_at = Date.now();\n\n saveGlobalConfig(config);\n}\n\n/**\n * Get the user UUID from global config, or generate a new one if it doesn't exist\n */\nexport function getOrCreateUserUUID(): string {\n const config = loadGlobalConfig();\n if (config.user_uuid) {\n return config.user_uuid;\n }\n\n // Generate a new UUID (v4)\n const uuid = generateUUID();\n\n // Save it to config\n config.user_uuid = uuid;\n config.first_run = false;\n saveGlobalConfig(config);\n\n return uuid;\n}\n\n/**\n * Generate a UUID v4\n */\nfunction generateUUID(): string {\n // UUID v4 format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\n // Use cryptographically secure random values.\n const bytes = crypto.randomBytes(16);\n // Per RFC 4122 section 4.4, set bits for version and `clock_seq_hi_and_reserved`\n bytes[6] = (bytes[6] & 0x0f) | 0x40; // Version 4\n bytes[8] = (bytes[8] & 0x3f) | 0x80; // Variant 10\n const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\"));\n return (\n hex.slice(0, 4).join(\"\") +\n hex.slice(4, 6).join(\"\") +\n \"-\" +\n hex.slice(6, 8).join(\"\") +\n \"-\" +\n hex.slice(8, 10).join(\"\") +\n \"-\" +\n hex.slice(10, 12).join(\"\") +\n \"-\" +\n hex.slice(12, 16).join(\"\")\n );\n}\n\n/**\n * Save user UUID to global config (preserves existing UUID if present)\n */\nexport function saveUserUUID(userUUID: string): void {\n const config = loadGlobalConfig();\n // Only update if not already set\n if (!config.user_uuid) {\n config.user_uuid = userUUID;\n saveGlobalConfig(config);\n }\n}\n","/**\n * Telemetry utilities for CLI commands\n *\n * Provides helpers to wrap command execution with telemetry tracking\n */\n\nimport {\n createTelemetryClient,\n createAppEnvironment,\n createMetricsContext,\n addMetric,\n addMetricWithDimensions,\n emitMetrics,\n type TelemetryClient,\n getBuildType,\n} from \"@layr-labs/ecloud-sdk\";\nimport { Command } from \"@oclif/core\";\nimport {\n getDefaultEnvironment,\n getOrCreateUserUUID,\n getGlobalTelemetryPreference,\n} from \"./utils/globalConfig\";\n\n/**\n * Create a telemetry client for CLI usage\n */\nexport function createCLITelemetryClient(): TelemetryClient {\n // Get user UUID from CLI's globalConfig (handles I/O)\n const userUUID = getOrCreateUserUUID();\n const environment = createAppEnvironment(userUUID);\n\n // Get telemetry preference from CLI's globalConfig\n const telemetryEnabled = getGlobalTelemetryPreference();\n\n return createTelemetryClient(environment, \"ecloud-cli\", {\n telemetryEnabled: telemetryEnabled !== false, // Enabled by default, disabled only if explicitly set to false\n });\n}\n\n/**\n * Wrap a command execution with telemetry\n *\n * @param command - The CLI command instance\n * @param action - The command action to execute\n * @returns The result of the action\n */\nexport async function withTelemetry<T>(command: Command, action: () => Promise<T>): Promise<T> {\n const client = createCLITelemetryClient();\n const metrics = createMetricsContext();\n\n // Set source to identify CLI usage\n metrics.properties[\"source\"] = \"ecloud-cli\";\n\n // Set command name in properties\n metrics.properties[\"command\"] = command.id || command.constructor.name;\n\n // Set environment in properties\n const environment = getDefaultEnvironment() || \"sepolia\";\n metrics.properties[\"environment\"] = environment;\n\n // Set buildType in properties\n const buildType = getBuildType() || \"prod\";\n metrics.properties[\"build_type\"] = buildType;\n\n // Set CLI version if available\n const cliVersion = command.config.version;\n if (cliVersion) {\n metrics.properties[\"cli_version\"] = cliVersion;\n }\n\n // Add initial count metric\n addMetric(metrics, \"Count\", 1);\n\n let actionError: Error | undefined;\n let result: T;\n\n try {\n result = await action();\n return result;\n } catch (err) {\n actionError = err instanceof Error ? err : new Error(String(err));\n throw err;\n } finally {\n // Add result metric\n const resultValue = actionError ? \"Failure\" : \"Success\";\n const dimensions: Record<string, string> = {};\n if (actionError) {\n dimensions[\"error\"] = actionError.message;\n }\n addMetricWithDimensions(metrics, resultValue, 1, dimensions);\n\n // Add duration metric\n const duration = Date.now() - metrics.startTime.getTime();\n addMetric(metrics, \"DurationMilliseconds\", duration);\n\n // Emit all metrics\n try {\n await emitMetrics(client, metrics);\n await client.close();\n } catch {\n // Silently ignore telemetry errors\n }\n }\n}\n"],"mappings":"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","names":["getBuildType","getBuildType"]}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/commands/telemetry/enable.ts
|
|
4
|
+
import { Command } from "@oclif/core";
|
|
5
|
+
|
|
6
|
+
// src/utils/globalConfig.ts
|
|
7
|
+
import * as fs from "fs";
|
|
8
|
+
import * as path from "path";
|
|
9
|
+
import * as os from "os";
|
|
10
|
+
import { load as loadYaml, dump as dumpYaml } from "js-yaml";
|
|
11
|
+
import { getBuildType } from "@layr-labs/ecloud-sdk";
|
|
12
|
+
import * as crypto from "crypto";
|
|
13
|
+
var GLOBAL_CONFIG_FILE = "config.yaml";
|
|
14
|
+
var PROFILE_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
15
|
+
function getGlobalConfigDir() {
|
|
16
|
+
const configHome = process.env.XDG_CONFIG_HOME;
|
|
17
|
+
let baseDir;
|
|
18
|
+
if (configHome && path.isAbsolute(configHome)) {
|
|
19
|
+
baseDir = configHome;
|
|
20
|
+
} else {
|
|
21
|
+
baseDir = path.join(os.homedir(), ".config");
|
|
22
|
+
}
|
|
23
|
+
const buildType = getBuildType();
|
|
24
|
+
const buildSuffix = buildType === "dev" ? "-dev" : "";
|
|
25
|
+
const configDirName = `ecloud${buildSuffix}`;
|
|
26
|
+
return path.join(baseDir, configDirName);
|
|
27
|
+
}
|
|
28
|
+
function getGlobalConfigPath() {
|
|
29
|
+
return path.join(getGlobalConfigDir(), GLOBAL_CONFIG_FILE);
|
|
30
|
+
}
|
|
31
|
+
function loadGlobalConfig() {
|
|
32
|
+
const configPath = getGlobalConfigPath();
|
|
33
|
+
if (!fs.existsSync(configPath)) {
|
|
34
|
+
return {
|
|
35
|
+
first_run: true
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const content = fs.readFileSync(configPath, "utf-8");
|
|
40
|
+
const config = loadYaml(content);
|
|
41
|
+
return config || { first_run: true };
|
|
42
|
+
} catch {
|
|
43
|
+
return {
|
|
44
|
+
first_run: true
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function saveGlobalConfig(config) {
|
|
49
|
+
const configPath = getGlobalConfigPath();
|
|
50
|
+
const configDir = path.dirname(configPath);
|
|
51
|
+
fs.mkdirSync(configDir, { recursive: true, mode: 493 });
|
|
52
|
+
const content = dumpYaml(config, { lineWidth: -1 });
|
|
53
|
+
fs.writeFileSync(configPath, content, { mode: 420 });
|
|
54
|
+
}
|
|
55
|
+
function getDefaultEnvironment() {
|
|
56
|
+
const config = loadGlobalConfig();
|
|
57
|
+
return config.default_environment;
|
|
58
|
+
}
|
|
59
|
+
function getGlobalTelemetryPreference() {
|
|
60
|
+
const config = loadGlobalConfig();
|
|
61
|
+
return config.telemetry_enabled;
|
|
62
|
+
}
|
|
63
|
+
function setGlobalTelemetryPreference(enabled) {
|
|
64
|
+
const config = loadGlobalConfig();
|
|
65
|
+
config.telemetry_enabled = enabled;
|
|
66
|
+
config.first_run = false;
|
|
67
|
+
saveGlobalConfig(config);
|
|
68
|
+
}
|
|
69
|
+
function getOrCreateUserUUID() {
|
|
70
|
+
const config = loadGlobalConfig();
|
|
71
|
+
if (config.user_uuid) {
|
|
72
|
+
return config.user_uuid;
|
|
73
|
+
}
|
|
74
|
+
const uuid = generateUUID();
|
|
75
|
+
config.user_uuid = uuid;
|
|
76
|
+
config.first_run = false;
|
|
77
|
+
saveGlobalConfig(config);
|
|
78
|
+
return uuid;
|
|
79
|
+
}
|
|
80
|
+
function generateUUID() {
|
|
81
|
+
const bytes = crypto.randomBytes(16);
|
|
82
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
83
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
84
|
+
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0"));
|
|
85
|
+
return hex.slice(0, 4).join("") + hex.slice(4, 6).join("") + "-" + hex.slice(6, 8).join("") + "-" + hex.slice(8, 10).join("") + "-" + hex.slice(10, 12).join("") + "-" + hex.slice(12, 16).join("");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// src/telemetry.ts
|
|
89
|
+
import {
|
|
90
|
+
createTelemetryClient,
|
|
91
|
+
createAppEnvironment,
|
|
92
|
+
createMetricsContext,
|
|
93
|
+
addMetric,
|
|
94
|
+
addMetricWithDimensions,
|
|
95
|
+
emitMetrics,
|
|
96
|
+
getBuildType as getBuildType2
|
|
97
|
+
} from "@layr-labs/ecloud-sdk";
|
|
98
|
+
function createCLITelemetryClient() {
|
|
99
|
+
const userUUID = getOrCreateUserUUID();
|
|
100
|
+
const environment = createAppEnvironment(userUUID);
|
|
101
|
+
const telemetryEnabled = getGlobalTelemetryPreference();
|
|
102
|
+
return createTelemetryClient(environment, "ecloud-cli", {
|
|
103
|
+
telemetryEnabled: telemetryEnabled !== false
|
|
104
|
+
// Enabled by default, disabled only if explicitly set to false
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
async function withTelemetry(command, action) {
|
|
108
|
+
const client = createCLITelemetryClient();
|
|
109
|
+
const metrics = createMetricsContext();
|
|
110
|
+
metrics.properties["source"] = "ecloud-cli";
|
|
111
|
+
metrics.properties["command"] = command.id || command.constructor.name;
|
|
112
|
+
const environment = getDefaultEnvironment() || "sepolia";
|
|
113
|
+
metrics.properties["environment"] = environment;
|
|
114
|
+
const buildType = getBuildType2() || "prod";
|
|
115
|
+
metrics.properties["build_type"] = buildType;
|
|
116
|
+
const cliVersion = command.config.version;
|
|
117
|
+
if (cliVersion) {
|
|
118
|
+
metrics.properties["cli_version"] = cliVersion;
|
|
119
|
+
}
|
|
120
|
+
addMetric(metrics, "Count", 1);
|
|
121
|
+
let actionError;
|
|
122
|
+
let result;
|
|
123
|
+
try {
|
|
124
|
+
result = await action();
|
|
125
|
+
return result;
|
|
126
|
+
} catch (err) {
|
|
127
|
+
actionError = err instanceof Error ? err : new Error(String(err));
|
|
128
|
+
throw err;
|
|
129
|
+
} finally {
|
|
130
|
+
const resultValue = actionError ? "Failure" : "Success";
|
|
131
|
+
const dimensions = {};
|
|
132
|
+
if (actionError) {
|
|
133
|
+
dimensions["error"] = actionError.message;
|
|
134
|
+
}
|
|
135
|
+
addMetricWithDimensions(metrics, resultValue, 1, dimensions);
|
|
136
|
+
const duration = Date.now() - metrics.startTime.getTime();
|
|
137
|
+
addMetric(metrics, "DurationMilliseconds", duration);
|
|
138
|
+
try {
|
|
139
|
+
await emitMetrics(client, metrics);
|
|
140
|
+
await client.close();
|
|
141
|
+
} catch {
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// src/commands/telemetry/enable.ts
|
|
147
|
+
var TelemetryEnable = class extends Command {
|
|
148
|
+
static description = "Enable telemetry collection";
|
|
149
|
+
async run() {
|
|
150
|
+
return withTelemetry(this, async () => {
|
|
151
|
+
const currentPreference = getGlobalTelemetryPreference();
|
|
152
|
+
if (currentPreference === true) {
|
|
153
|
+
this.log("\nTelemetry is already enabled");
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
setGlobalTelemetryPreference(true);
|
|
157
|
+
this.log("\nTelemetry enabled");
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
export {
|
|
162
|
+
TelemetryEnable as default
|
|
163
|
+
};
|
|
164
|
+
//# sourceMappingURL=enable.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/commands/telemetry/enable.ts","../../../src/utils/globalConfig.ts","../../../src/telemetry.ts"],"sourcesContent":["import { Command } from \"@oclif/core\";\nimport {\n setGlobalTelemetryPreference,\n getGlobalTelemetryPreference,\n} from \"../../utils/globalConfig\";\nimport { withTelemetry } from \"../../telemetry\";\n\nexport default class TelemetryEnable extends Command {\n static description = \"Enable telemetry collection\";\n\n async run() {\n return withTelemetry(this, async () => {\n const currentPreference = getGlobalTelemetryPreference();\n if (currentPreference === true) {\n this.log(\"\\nTelemetry is already enabled\");\n return;\n }\n\n setGlobalTelemetryPreference(true);\n this.log(\"\\nTelemetry enabled\");\n });\n }\n}\n","/**\n * Global configuration management\n *\n * Stores user-level configuration that persists across all CLI usage.\n * - $XDG_CONFIG_HOME/ecloud[BuildSuffix]/config.yaml (if XDG_CONFIG_HOME is set)\n * - Or ~/.config/ecloud[BuildSuffix]/config.yaml (fallback)\n *\n * Where BuildSuffix is:\n * - \"\" (empty) for production builds\n * - \"-dev\" for development builds\n */\n\nimport * as fs from \"fs\";\nimport * as path from \"path\";\nimport * as os from \"os\";\nimport { load as loadYaml, dump as dumpYaml } from \"js-yaml\";\nimport { getBuildType } from \"@layr-labs/ecloud-sdk\";\nimport * as crypto from \"crypto\";\nconst GLOBAL_CONFIG_FILE = \"config.yaml\";\n\nexport interface ProfileCacheEntry {\n updated_at: number; // Unix timestamp in milliseconds\n profiles: { [appId: string]: string }; // appId -> profile name\n}\n\nexport interface GlobalConfig {\n first_run?: boolean;\n telemetry_enabled?: boolean;\n user_uuid?: string;\n default_environment?: string;\n last_version_check?: number;\n last_known_version?: string;\n profile_cache?: {\n [environment: string]: ProfileCacheEntry;\n };\n directory_links?: {\n [environment: string]: {\n [directoryPath: string]: string;\n };\n };\n}\n\n// Profile cache TTL: 24 hours in milliseconds\nconst PROFILE_CACHE_TTL_MS = 24 * 60 * 60 * 1000;\n\n/**\n * Get the XDG-compliant directory where global ecloud config should be stored\n */\nfunction getGlobalConfigDir(): string {\n // First check XDG_CONFIG_HOME\n const configHome = process.env.XDG_CONFIG_HOME;\n\n let baseDir: string;\n if (configHome && path.isAbsolute(configHome)) {\n baseDir = configHome;\n } else {\n // Fall back to ~/.config\n baseDir = path.join(os.homedir(), \".config\");\n }\n\n // Use environment-specific config directory\n const buildType = getBuildType();\n const buildSuffix = buildType === \"dev\" ? \"-dev\" : \"\";\n const configDirName = `ecloud${buildSuffix}`;\n\n return path.join(baseDir, configDirName);\n}\n\n/**\n * Get the full path to the global config file\n */\nfunction getGlobalConfigPath(): string {\n return path.join(getGlobalConfigDir(), GLOBAL_CONFIG_FILE);\n}\n\n/**\n * Load global configuration, creating defaults if needed\n */\nexport function loadGlobalConfig(): GlobalConfig {\n const configPath = getGlobalConfigPath();\n\n // If file doesn't exist, return defaults for first run\n if (!fs.existsSync(configPath)) {\n return {\n first_run: true,\n };\n }\n\n try {\n const content = fs.readFileSync(configPath, \"utf-8\");\n const config = loadYaml(content) as GlobalConfig;\n return config || { first_run: true };\n } catch {\n // If parsing fails, return defaults\n return {\n first_run: true,\n };\n }\n}\n\n/**\n * Save global configuration to disk\n */\nexport function saveGlobalConfig(config: GlobalConfig): void {\n const configPath = getGlobalConfigPath();\n\n // Ensure directory exists\n const configDir = path.dirname(configPath);\n fs.mkdirSync(configDir, { recursive: true, mode: 0o755 });\n\n // Write config file\n const content = dumpYaml(config, { lineWidth: -1 });\n fs.writeFileSync(configPath, content, { mode: 0o644 });\n}\n\nfunction normalizeDirectoryPath(directoryPath: string): string {\n const resolved = path.resolve(directoryPath);\n try {\n return fs.realpathSync(resolved);\n } catch {\n return resolved;\n }\n}\n\n/**\n * Get linked app ID for a directory in an environment\n */\nexport function getLinkedAppForDirectory(\n environment: string,\n directoryPath: string,\n): string | null {\n if (!directoryPath) {\n return null;\n }\n\n const config = loadGlobalConfig();\n const links = config.directory_links?.[environment];\n if (!links) {\n return null;\n }\n\n const normalizedPath = normalizeDirectoryPath(directoryPath);\n const appId = links[normalizedPath];\n return appId || null;\n}\n\n/**\n * Link a directory to an app ID in an environment\n */\nexport function setLinkedAppForDirectory(\n environment: string,\n directoryPath: string,\n appId: string,\n): void {\n if (!directoryPath || !environment) {\n return;\n }\n\n const config = loadGlobalConfig();\n if (!config.directory_links) {\n config.directory_links = {};\n }\n if (!config.directory_links[environment]) {\n config.directory_links[environment] = {};\n }\n\n const normalizedPath = normalizeDirectoryPath(directoryPath);\n // Normalize appId to lowercase for consistent lookups\n config.directory_links[environment][normalizedPath] = appId.toLowerCase();\n saveGlobalConfig(config);\n}\n\n/**\n * Get the user's preferred deployment environment\n */\nexport function getDefaultEnvironment(): string | undefined {\n const config = loadGlobalConfig();\n return config.default_environment;\n}\n\n/**\n * Set the user's preferred deployment environment\n */\nexport function setDefaultEnvironment(environment: string): void {\n const config = loadGlobalConfig();\n config.default_environment = environment;\n config.first_run = false; // No longer first run after setting environment\n saveGlobalConfig(config);\n}\n\n/**\n * Check if this is the user's first time running the CLI\n */\nexport function isFirstRun(): boolean {\n const config = loadGlobalConfig();\n return config.first_run === true;\n}\n\n/**\n * Mark that the first run has been completed\n */\nexport function markFirstRunComplete(): void {\n const config = loadGlobalConfig();\n config.first_run = false;\n saveGlobalConfig(config);\n}\n\n/**\n * Get the global telemetry preference\n */\nexport function getGlobalTelemetryPreference(): boolean | undefined {\n const config = loadGlobalConfig();\n return config.telemetry_enabled;\n}\n\n/**\n * Set the global telemetry preference\n */\nexport function setGlobalTelemetryPreference(enabled: boolean): void {\n const config = loadGlobalConfig();\n config.telemetry_enabled = enabled;\n config.first_run = false; // No longer first run after setting preference\n saveGlobalConfig(config);\n}\n\n// ==================== Profile Cache Functions ====================\n\n/**\n * Get cached profile names for an environment\n * Returns null if cache is missing or expired (older than 24 hours)\n */\nexport function getProfileCache(environment: string): Record<string, string> | null {\n const config = loadGlobalConfig();\n const cacheEntry = config.profile_cache?.[environment];\n\n if (!cacheEntry) {\n return null;\n }\n\n // Check if cache is expired\n const now = Date.now();\n if (now - cacheEntry.updated_at > PROFILE_CACHE_TTL_MS) {\n return null;\n }\n\n return cacheEntry.profiles;\n}\n\n/**\n * Set cached profile names for an environment\n */\nexport function setProfileCache(environment: string, profiles: Record<string, string>): void {\n const config = loadGlobalConfig();\n\n if (!config.profile_cache) {\n config.profile_cache = {};\n }\n\n config.profile_cache[environment] = {\n updated_at: Date.now(),\n profiles,\n };\n\n saveGlobalConfig(config);\n}\n\n/**\n * Invalidate profile cache for a specific environment or all environments\n */\nexport function invalidateProfileCache(environment?: string): void {\n const config = loadGlobalConfig();\n\n if (!config.profile_cache) {\n return;\n }\n\n if (environment) {\n // Invalidate specific environment\n delete config.profile_cache[environment];\n } else {\n // Invalidate all environments\n config.profile_cache = {};\n }\n\n saveGlobalConfig(config);\n}\n\n/**\n * Update a single profile name in the cache\n * This is useful after deploy or profile set to update just one entry\n */\nexport function updateProfileCacheEntry(\n environment: string,\n appId: string,\n profileName: string,\n): void {\n const config = loadGlobalConfig();\n\n if (!config.profile_cache) {\n config.profile_cache = {};\n }\n\n if (!config.profile_cache[environment]) {\n config.profile_cache[environment] = {\n updated_at: Date.now(),\n profiles: {},\n };\n }\n\n // Normalize appId to lowercase for consistent lookups\n const normalizedAppId = appId.toLowerCase();\n config.profile_cache[environment].profiles[normalizedAppId] = profileName;\n config.profile_cache[environment].updated_at = Date.now();\n\n saveGlobalConfig(config);\n}\n\n/**\n * Get the user UUID from global config, or generate a new one if it doesn't exist\n */\nexport function getOrCreateUserUUID(): string {\n const config = loadGlobalConfig();\n if (config.user_uuid) {\n return config.user_uuid;\n }\n\n // Generate a new UUID (v4)\n const uuid = generateUUID();\n\n // Save it to config\n config.user_uuid = uuid;\n config.first_run = false;\n saveGlobalConfig(config);\n\n return uuid;\n}\n\n/**\n * Generate a UUID v4\n */\nfunction generateUUID(): string {\n // UUID v4 format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\n // Use cryptographically secure random values.\n const bytes = crypto.randomBytes(16);\n // Per RFC 4122 section 4.4, set bits for version and `clock_seq_hi_and_reserved`\n bytes[6] = (bytes[6] & 0x0f) | 0x40; // Version 4\n bytes[8] = (bytes[8] & 0x3f) | 0x80; // Variant 10\n const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\"));\n return (\n hex.slice(0, 4).join(\"\") +\n hex.slice(4, 6).join(\"\") +\n \"-\" +\n hex.slice(6, 8).join(\"\") +\n \"-\" +\n hex.slice(8, 10).join(\"\") +\n \"-\" +\n hex.slice(10, 12).join(\"\") +\n \"-\" +\n hex.slice(12, 16).join(\"\")\n );\n}\n\n/**\n * Save user UUID to global config (preserves existing UUID if present)\n */\nexport function saveUserUUID(userUUID: string): void {\n const config = loadGlobalConfig();\n // Only update if not already set\n if (!config.user_uuid) {\n config.user_uuid = userUUID;\n saveGlobalConfig(config);\n }\n}\n","/**\n * Telemetry utilities for CLI commands\n *\n * Provides helpers to wrap command execution with telemetry tracking\n */\n\nimport {\n createTelemetryClient,\n createAppEnvironment,\n createMetricsContext,\n addMetric,\n addMetricWithDimensions,\n emitMetrics,\n type TelemetryClient,\n getBuildType,\n} from \"@layr-labs/ecloud-sdk\";\nimport { Command } from \"@oclif/core\";\nimport {\n getDefaultEnvironment,\n getOrCreateUserUUID,\n getGlobalTelemetryPreference,\n} from \"./utils/globalConfig\";\n\n/**\n * Create a telemetry client for CLI usage\n */\nexport function createCLITelemetryClient(): TelemetryClient {\n // Get user UUID from CLI's globalConfig (handles I/O)\n const userUUID = getOrCreateUserUUID();\n const environment = createAppEnvironment(userUUID);\n\n // Get telemetry preference from CLI's globalConfig\n const telemetryEnabled = getGlobalTelemetryPreference();\n\n return createTelemetryClient(environment, \"ecloud-cli\", {\n telemetryEnabled: telemetryEnabled !== false, // Enabled by default, disabled only if explicitly set to false\n });\n}\n\n/**\n * Wrap a command execution with telemetry\n *\n * @param command - The CLI command instance\n * @param action - The command action to execute\n * @returns The result of the action\n */\nexport async function withTelemetry<T>(command: Command, action: () => Promise<T>): Promise<T> {\n const client = createCLITelemetryClient();\n const metrics = createMetricsContext();\n\n // Set source to identify CLI usage\n metrics.properties[\"source\"] = \"ecloud-cli\";\n\n // Set command name in properties\n metrics.properties[\"command\"] = command.id || command.constructor.name;\n\n // Set environment in properties\n const environment = getDefaultEnvironment() || \"sepolia\";\n metrics.properties[\"environment\"] = environment;\n\n // Set buildType in properties\n const buildType = getBuildType() || \"prod\";\n metrics.properties[\"build_type\"] = buildType;\n\n // Set CLI version if available\n const cliVersion = command.config.version;\n if (cliVersion) {\n metrics.properties[\"cli_version\"] = cliVersion;\n }\n\n // Add initial count metric\n addMetric(metrics, \"Count\", 1);\n\n let actionError: Error | undefined;\n let result: T;\n\n try {\n result = await action();\n return result;\n } catch (err) {\n actionError = err instanceof Error ? err : new Error(String(err));\n throw err;\n } finally {\n // Add result metric\n const resultValue = actionError ? \"Failure\" : \"Success\";\n const dimensions: Record<string, string> = {};\n if (actionError) {\n dimensions[\"error\"] = actionError.message;\n }\n addMetricWithDimensions(metrics, resultValue, 1, dimensions);\n\n // Add duration metric\n const duration = Date.now() - metrics.startTime.getTime();\n addMetric(metrics, \"DurationMilliseconds\", duration);\n\n // Emit all metrics\n try {\n await emitMetrics(client, metrics);\n await client.close();\n } catch {\n // Silently ignore telemetry errors\n }\n }\n}\n"],"mappings":"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","names":["getBuildType","getBuildType"]}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/commands/telemetry/status.ts
|
|
4
|
+
import { Command } from "@oclif/core";
|
|
5
|
+
|
|
6
|
+
// src/utils/globalConfig.ts
|
|
7
|
+
import * as fs from "fs";
|
|
8
|
+
import * as path from "path";
|
|
9
|
+
import * as os from "os";
|
|
10
|
+
import { load as loadYaml, dump as dumpYaml } from "js-yaml";
|
|
11
|
+
import { getBuildType } from "@layr-labs/ecloud-sdk";
|
|
12
|
+
import * as crypto from "crypto";
|
|
13
|
+
var GLOBAL_CONFIG_FILE = "config.yaml";
|
|
14
|
+
var PROFILE_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
15
|
+
function getGlobalConfigDir() {
|
|
16
|
+
const configHome = process.env.XDG_CONFIG_HOME;
|
|
17
|
+
let baseDir;
|
|
18
|
+
if (configHome && path.isAbsolute(configHome)) {
|
|
19
|
+
baseDir = configHome;
|
|
20
|
+
} else {
|
|
21
|
+
baseDir = path.join(os.homedir(), ".config");
|
|
22
|
+
}
|
|
23
|
+
const buildType = getBuildType();
|
|
24
|
+
const buildSuffix = buildType === "dev" ? "-dev" : "";
|
|
25
|
+
const configDirName = `ecloud${buildSuffix}`;
|
|
26
|
+
return path.join(baseDir, configDirName);
|
|
27
|
+
}
|
|
28
|
+
function getGlobalConfigPath() {
|
|
29
|
+
return path.join(getGlobalConfigDir(), GLOBAL_CONFIG_FILE);
|
|
30
|
+
}
|
|
31
|
+
function loadGlobalConfig() {
|
|
32
|
+
const configPath = getGlobalConfigPath();
|
|
33
|
+
if (!fs.existsSync(configPath)) {
|
|
34
|
+
return {
|
|
35
|
+
first_run: true
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const content = fs.readFileSync(configPath, "utf-8");
|
|
40
|
+
const config = loadYaml(content);
|
|
41
|
+
return config || { first_run: true };
|
|
42
|
+
} catch {
|
|
43
|
+
return {
|
|
44
|
+
first_run: true
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function saveGlobalConfig(config) {
|
|
49
|
+
const configPath = getGlobalConfigPath();
|
|
50
|
+
const configDir = path.dirname(configPath);
|
|
51
|
+
fs.mkdirSync(configDir, { recursive: true, mode: 493 });
|
|
52
|
+
const content = dumpYaml(config, { lineWidth: -1 });
|
|
53
|
+
fs.writeFileSync(configPath, content, { mode: 420 });
|
|
54
|
+
}
|
|
55
|
+
function getDefaultEnvironment() {
|
|
56
|
+
const config = loadGlobalConfig();
|
|
57
|
+
return config.default_environment;
|
|
58
|
+
}
|
|
59
|
+
function getGlobalTelemetryPreference() {
|
|
60
|
+
const config = loadGlobalConfig();
|
|
61
|
+
return config.telemetry_enabled;
|
|
62
|
+
}
|
|
63
|
+
function getOrCreateUserUUID() {
|
|
64
|
+
const config = loadGlobalConfig();
|
|
65
|
+
if (config.user_uuid) {
|
|
66
|
+
return config.user_uuid;
|
|
67
|
+
}
|
|
68
|
+
const uuid = generateUUID();
|
|
69
|
+
config.user_uuid = uuid;
|
|
70
|
+
config.first_run = false;
|
|
71
|
+
saveGlobalConfig(config);
|
|
72
|
+
return uuid;
|
|
73
|
+
}
|
|
74
|
+
function generateUUID() {
|
|
75
|
+
const bytes = crypto.randomBytes(16);
|
|
76
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
77
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
78
|
+
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0"));
|
|
79
|
+
return hex.slice(0, 4).join("") + hex.slice(4, 6).join("") + "-" + hex.slice(6, 8).join("") + "-" + hex.slice(8, 10).join("") + "-" + hex.slice(10, 12).join("") + "-" + hex.slice(12, 16).join("");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/telemetry.ts
|
|
83
|
+
import {
|
|
84
|
+
createTelemetryClient,
|
|
85
|
+
createAppEnvironment,
|
|
86
|
+
createMetricsContext,
|
|
87
|
+
addMetric,
|
|
88
|
+
addMetricWithDimensions,
|
|
89
|
+
emitMetrics,
|
|
90
|
+
getBuildType as getBuildType2
|
|
91
|
+
} from "@layr-labs/ecloud-sdk";
|
|
92
|
+
function createCLITelemetryClient() {
|
|
93
|
+
const userUUID = getOrCreateUserUUID();
|
|
94
|
+
const environment = createAppEnvironment(userUUID);
|
|
95
|
+
const telemetryEnabled = getGlobalTelemetryPreference();
|
|
96
|
+
return createTelemetryClient(environment, "ecloud-cli", {
|
|
97
|
+
telemetryEnabled: telemetryEnabled !== false
|
|
98
|
+
// Enabled by default, disabled only if explicitly set to false
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
async function withTelemetry(command, action) {
|
|
102
|
+
const client = createCLITelemetryClient();
|
|
103
|
+
const metrics = createMetricsContext();
|
|
104
|
+
metrics.properties["source"] = "ecloud-cli";
|
|
105
|
+
metrics.properties["command"] = command.id || command.constructor.name;
|
|
106
|
+
const environment = getDefaultEnvironment() || "sepolia";
|
|
107
|
+
metrics.properties["environment"] = environment;
|
|
108
|
+
const buildType = getBuildType2() || "prod";
|
|
109
|
+
metrics.properties["build_type"] = buildType;
|
|
110
|
+
const cliVersion = command.config.version;
|
|
111
|
+
if (cliVersion) {
|
|
112
|
+
metrics.properties["cli_version"] = cliVersion;
|
|
113
|
+
}
|
|
114
|
+
addMetric(metrics, "Count", 1);
|
|
115
|
+
let actionError;
|
|
116
|
+
let result;
|
|
117
|
+
try {
|
|
118
|
+
result = await action();
|
|
119
|
+
return result;
|
|
120
|
+
} catch (err) {
|
|
121
|
+
actionError = err instanceof Error ? err : new Error(String(err));
|
|
122
|
+
throw err;
|
|
123
|
+
} finally {
|
|
124
|
+
const resultValue = actionError ? "Failure" : "Success";
|
|
125
|
+
const dimensions = {};
|
|
126
|
+
if (actionError) {
|
|
127
|
+
dimensions["error"] = actionError.message;
|
|
128
|
+
}
|
|
129
|
+
addMetricWithDimensions(metrics, resultValue, 1, dimensions);
|
|
130
|
+
const duration = Date.now() - metrics.startTime.getTime();
|
|
131
|
+
addMetric(metrics, "DurationMilliseconds", duration);
|
|
132
|
+
try {
|
|
133
|
+
await emitMetrics(client, metrics);
|
|
134
|
+
await client.close();
|
|
135
|
+
} catch {
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// src/commands/telemetry/status.ts
|
|
141
|
+
var TelemetryStatus = class extends Command {
|
|
142
|
+
static description = "Show current telemetry status";
|
|
143
|
+
async run() {
|
|
144
|
+
return withTelemetry(this, async () => {
|
|
145
|
+
const globalPreference = getGlobalTelemetryPreference();
|
|
146
|
+
if (globalPreference === void 0) {
|
|
147
|
+
this.log("Telemetry: Enabled (default)");
|
|
148
|
+
} else if (globalPreference) {
|
|
149
|
+
this.log("Telemetry: Enabled");
|
|
150
|
+
} else {
|
|
151
|
+
this.log("Telemetry: Disabled");
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
export {
|
|
157
|
+
TelemetryStatus as default
|
|
158
|
+
};
|
|
159
|
+
//# sourceMappingURL=status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/commands/telemetry/status.ts","../../../src/utils/globalConfig.ts","../../../src/telemetry.ts"],"sourcesContent":["import { Command } from \"@oclif/core\";\nimport { getGlobalTelemetryPreference } from \"../../utils/globalConfig\";\nimport { withTelemetry } from \"../../telemetry\";\n\nexport default class TelemetryStatus extends Command {\n static description = \"Show current telemetry status\";\n\n async run() {\n return withTelemetry(this, async () => {\n const globalPreference = getGlobalTelemetryPreference();\n if (globalPreference === undefined) {\n this.log(\"Telemetry: Enabled (default)\");\n } else if (globalPreference) {\n this.log(\"Telemetry: Enabled\");\n } else {\n this.log(\"Telemetry: Disabled\");\n }\n });\n }\n}\n","/**\n * Global configuration management\n *\n * Stores user-level configuration that persists across all CLI usage.\n * - $XDG_CONFIG_HOME/ecloud[BuildSuffix]/config.yaml (if XDG_CONFIG_HOME is set)\n * - Or ~/.config/ecloud[BuildSuffix]/config.yaml (fallback)\n *\n * Where BuildSuffix is:\n * - \"\" (empty) for production builds\n * - \"-dev\" for development builds\n */\n\nimport * as fs from \"fs\";\nimport * as path from \"path\";\nimport * as os from \"os\";\nimport { load as loadYaml, dump as dumpYaml } from \"js-yaml\";\nimport { getBuildType } from \"@layr-labs/ecloud-sdk\";\nimport * as crypto from \"crypto\";\nconst GLOBAL_CONFIG_FILE = \"config.yaml\";\n\nexport interface ProfileCacheEntry {\n updated_at: number; // Unix timestamp in milliseconds\n profiles: { [appId: string]: string }; // appId -> profile name\n}\n\nexport interface GlobalConfig {\n first_run?: boolean;\n telemetry_enabled?: boolean;\n user_uuid?: string;\n default_environment?: string;\n last_version_check?: number;\n last_known_version?: string;\n profile_cache?: {\n [environment: string]: ProfileCacheEntry;\n };\n directory_links?: {\n [environment: string]: {\n [directoryPath: string]: string;\n };\n };\n}\n\n// Profile cache TTL: 24 hours in milliseconds\nconst PROFILE_CACHE_TTL_MS = 24 * 60 * 60 * 1000;\n\n/**\n * Get the XDG-compliant directory where global ecloud config should be stored\n */\nfunction getGlobalConfigDir(): string {\n // First check XDG_CONFIG_HOME\n const configHome = process.env.XDG_CONFIG_HOME;\n\n let baseDir: string;\n if (configHome && path.isAbsolute(configHome)) {\n baseDir = configHome;\n } else {\n // Fall back to ~/.config\n baseDir = path.join(os.homedir(), \".config\");\n }\n\n // Use environment-specific config directory\n const buildType = getBuildType();\n const buildSuffix = buildType === \"dev\" ? \"-dev\" : \"\";\n const configDirName = `ecloud${buildSuffix}`;\n\n return path.join(baseDir, configDirName);\n}\n\n/**\n * Get the full path to the global config file\n */\nfunction getGlobalConfigPath(): string {\n return path.join(getGlobalConfigDir(), GLOBAL_CONFIG_FILE);\n}\n\n/**\n * Load global configuration, creating defaults if needed\n */\nexport function loadGlobalConfig(): GlobalConfig {\n const configPath = getGlobalConfigPath();\n\n // If file doesn't exist, return defaults for first run\n if (!fs.existsSync(configPath)) {\n return {\n first_run: true,\n };\n }\n\n try {\n const content = fs.readFileSync(configPath, \"utf-8\");\n const config = loadYaml(content) as GlobalConfig;\n return config || { first_run: true };\n } catch {\n // If parsing fails, return defaults\n return {\n first_run: true,\n };\n }\n}\n\n/**\n * Save global configuration to disk\n */\nexport function saveGlobalConfig(config: GlobalConfig): void {\n const configPath = getGlobalConfigPath();\n\n // Ensure directory exists\n const configDir = path.dirname(configPath);\n fs.mkdirSync(configDir, { recursive: true, mode: 0o755 });\n\n // Write config file\n const content = dumpYaml(config, { lineWidth: -1 });\n fs.writeFileSync(configPath, content, { mode: 0o644 });\n}\n\nfunction normalizeDirectoryPath(directoryPath: string): string {\n const resolved = path.resolve(directoryPath);\n try {\n return fs.realpathSync(resolved);\n } catch {\n return resolved;\n }\n}\n\n/**\n * Get linked app ID for a directory in an environment\n */\nexport function getLinkedAppForDirectory(\n environment: string,\n directoryPath: string,\n): string | null {\n if (!directoryPath) {\n return null;\n }\n\n const config = loadGlobalConfig();\n const links = config.directory_links?.[environment];\n if (!links) {\n return null;\n }\n\n const normalizedPath = normalizeDirectoryPath(directoryPath);\n const appId = links[normalizedPath];\n return appId || null;\n}\n\n/**\n * Link a directory to an app ID in an environment\n */\nexport function setLinkedAppForDirectory(\n environment: string,\n directoryPath: string,\n appId: string,\n): void {\n if (!directoryPath || !environment) {\n return;\n }\n\n const config = loadGlobalConfig();\n if (!config.directory_links) {\n config.directory_links = {};\n }\n if (!config.directory_links[environment]) {\n config.directory_links[environment] = {};\n }\n\n const normalizedPath = normalizeDirectoryPath(directoryPath);\n // Normalize appId to lowercase for consistent lookups\n config.directory_links[environment][normalizedPath] = appId.toLowerCase();\n saveGlobalConfig(config);\n}\n\n/**\n * Get the user's preferred deployment environment\n */\nexport function getDefaultEnvironment(): string | undefined {\n const config = loadGlobalConfig();\n return config.default_environment;\n}\n\n/**\n * Set the user's preferred deployment environment\n */\nexport function setDefaultEnvironment(environment: string): void {\n const config = loadGlobalConfig();\n config.default_environment = environment;\n config.first_run = false; // No longer first run after setting environment\n saveGlobalConfig(config);\n}\n\n/**\n * Check if this is the user's first time running the CLI\n */\nexport function isFirstRun(): boolean {\n const config = loadGlobalConfig();\n return config.first_run === true;\n}\n\n/**\n * Mark that the first run has been completed\n */\nexport function markFirstRunComplete(): void {\n const config = loadGlobalConfig();\n config.first_run = false;\n saveGlobalConfig(config);\n}\n\n/**\n * Get the global telemetry preference\n */\nexport function getGlobalTelemetryPreference(): boolean | undefined {\n const config = loadGlobalConfig();\n return config.telemetry_enabled;\n}\n\n/**\n * Set the global telemetry preference\n */\nexport function setGlobalTelemetryPreference(enabled: boolean): void {\n const config = loadGlobalConfig();\n config.telemetry_enabled = enabled;\n config.first_run = false; // No longer first run after setting preference\n saveGlobalConfig(config);\n}\n\n// ==================== Profile Cache Functions ====================\n\n/**\n * Get cached profile names for an environment\n * Returns null if cache is missing or expired (older than 24 hours)\n */\nexport function getProfileCache(environment: string): Record<string, string> | null {\n const config = loadGlobalConfig();\n const cacheEntry = config.profile_cache?.[environment];\n\n if (!cacheEntry) {\n return null;\n }\n\n // Check if cache is expired\n const now = Date.now();\n if (now - cacheEntry.updated_at > PROFILE_CACHE_TTL_MS) {\n return null;\n }\n\n return cacheEntry.profiles;\n}\n\n/**\n * Set cached profile names for an environment\n */\nexport function setProfileCache(environment: string, profiles: Record<string, string>): void {\n const config = loadGlobalConfig();\n\n if (!config.profile_cache) {\n config.profile_cache = {};\n }\n\n config.profile_cache[environment] = {\n updated_at: Date.now(),\n profiles,\n };\n\n saveGlobalConfig(config);\n}\n\n/**\n * Invalidate profile cache for a specific environment or all environments\n */\nexport function invalidateProfileCache(environment?: string): void {\n const config = loadGlobalConfig();\n\n if (!config.profile_cache) {\n return;\n }\n\n if (environment) {\n // Invalidate specific environment\n delete config.profile_cache[environment];\n } else {\n // Invalidate all environments\n config.profile_cache = {};\n }\n\n saveGlobalConfig(config);\n}\n\n/**\n * Update a single profile name in the cache\n * This is useful after deploy or profile set to update just one entry\n */\nexport function updateProfileCacheEntry(\n environment: string,\n appId: string,\n profileName: string,\n): void {\n const config = loadGlobalConfig();\n\n if (!config.profile_cache) {\n config.profile_cache = {};\n }\n\n if (!config.profile_cache[environment]) {\n config.profile_cache[environment] = {\n updated_at: Date.now(),\n profiles: {},\n };\n }\n\n // Normalize appId to lowercase for consistent lookups\n const normalizedAppId = appId.toLowerCase();\n config.profile_cache[environment].profiles[normalizedAppId] = profileName;\n config.profile_cache[environment].updated_at = Date.now();\n\n saveGlobalConfig(config);\n}\n\n/**\n * Get the user UUID from global config, or generate a new one if it doesn't exist\n */\nexport function getOrCreateUserUUID(): string {\n const config = loadGlobalConfig();\n if (config.user_uuid) {\n return config.user_uuid;\n }\n\n // Generate a new UUID (v4)\n const uuid = generateUUID();\n\n // Save it to config\n config.user_uuid = uuid;\n config.first_run = false;\n saveGlobalConfig(config);\n\n return uuid;\n}\n\n/**\n * Generate a UUID v4\n */\nfunction generateUUID(): string {\n // UUID v4 format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\n // Use cryptographically secure random values.\n const bytes = crypto.randomBytes(16);\n // Per RFC 4122 section 4.4, set bits for version and `clock_seq_hi_and_reserved`\n bytes[6] = (bytes[6] & 0x0f) | 0x40; // Version 4\n bytes[8] = (bytes[8] & 0x3f) | 0x80; // Variant 10\n const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\"));\n return (\n hex.slice(0, 4).join(\"\") +\n hex.slice(4, 6).join(\"\") +\n \"-\" +\n hex.slice(6, 8).join(\"\") +\n \"-\" +\n hex.slice(8, 10).join(\"\") +\n \"-\" +\n hex.slice(10, 12).join(\"\") +\n \"-\" +\n hex.slice(12, 16).join(\"\")\n );\n}\n\n/**\n * Save user UUID to global config (preserves existing UUID if present)\n */\nexport function saveUserUUID(userUUID: string): void {\n const config = loadGlobalConfig();\n // Only update if not already set\n if (!config.user_uuid) {\n config.user_uuid = userUUID;\n saveGlobalConfig(config);\n }\n}\n","/**\n * Telemetry utilities for CLI commands\n *\n * Provides helpers to wrap command execution with telemetry tracking\n */\n\nimport {\n createTelemetryClient,\n createAppEnvironment,\n createMetricsContext,\n addMetric,\n addMetricWithDimensions,\n emitMetrics,\n type TelemetryClient,\n getBuildType,\n} from \"@layr-labs/ecloud-sdk\";\nimport { Command } from \"@oclif/core\";\nimport {\n getDefaultEnvironment,\n getOrCreateUserUUID,\n getGlobalTelemetryPreference,\n} from \"./utils/globalConfig\";\n\n/**\n * Create a telemetry client for CLI usage\n */\nexport function createCLITelemetryClient(): TelemetryClient {\n // Get user UUID from CLI's globalConfig (handles I/O)\n const userUUID = getOrCreateUserUUID();\n const environment = createAppEnvironment(userUUID);\n\n // Get telemetry preference from CLI's globalConfig\n const telemetryEnabled = getGlobalTelemetryPreference();\n\n return createTelemetryClient(environment, \"ecloud-cli\", {\n telemetryEnabled: telemetryEnabled !== false, // Enabled by default, disabled only if explicitly set to false\n });\n}\n\n/**\n * Wrap a command execution with telemetry\n *\n * @param command - The CLI command instance\n * @param action - The command action to execute\n * @returns The result of the action\n */\nexport async function withTelemetry<T>(command: Command, action: () => Promise<T>): Promise<T> {\n const client = createCLITelemetryClient();\n const metrics = createMetricsContext();\n\n // Set source to identify CLI usage\n metrics.properties[\"source\"] = \"ecloud-cli\";\n\n // Set command name in properties\n metrics.properties[\"command\"] = command.id || command.constructor.name;\n\n // Set environment in properties\n const environment = getDefaultEnvironment() || \"sepolia\";\n metrics.properties[\"environment\"] = environment;\n\n // Set buildType in properties\n const buildType = getBuildType() || \"prod\";\n metrics.properties[\"build_type\"] = buildType;\n\n // Set CLI version if available\n const cliVersion = command.config.version;\n if (cliVersion) {\n metrics.properties[\"cli_version\"] = cliVersion;\n }\n\n // Add initial count metric\n addMetric(metrics, \"Count\", 1);\n\n let actionError: Error | undefined;\n let result: T;\n\n try {\n result = await action();\n return result;\n } catch (err) {\n actionError = err instanceof Error ? err : new Error(String(err));\n throw err;\n } finally {\n // Add result metric\n const resultValue = actionError ? \"Failure\" : \"Success\";\n const dimensions: Record<string, string> = {};\n if (actionError) {\n dimensions[\"error\"] = actionError.message;\n }\n addMetricWithDimensions(metrics, resultValue, 1, dimensions);\n\n // Add duration metric\n const duration = Date.now() - metrics.startTime.getTime();\n addMetric(metrics, \"DurationMilliseconds\", duration);\n\n // Emit all metrics\n try {\n await emitMetrics(client, metrics);\n await client.close();\n } catch {\n // Silently ignore telemetry errors\n }\n }\n}\n"],"mappings":"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","names":["getBuildType","getBuildType"]}
|
package/dist/commands/upgrade.js
CHANGED
|
@@ -100,8 +100,8 @@ function createCLITelemetryClient() {
|
|
|
100
100
|
const environment = createAppEnvironment(userUUID);
|
|
101
101
|
const telemetryEnabled = getGlobalTelemetryPreference();
|
|
102
102
|
return createTelemetryClient(environment, "ecloud-cli", {
|
|
103
|
-
telemetryEnabled: telemetryEnabled
|
|
104
|
-
//
|
|
103
|
+
telemetryEnabled: telemetryEnabled !== false
|
|
104
|
+
// Enabled by default, disabled only if explicitly set to false
|
|
105
105
|
});
|
|
106
106
|
}
|
|
107
107
|
async function withTelemetry(command, action) {
|