@launchframe/cli 1.0.0-beta.21 → 1.0.0-beta.23
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/.claude/settings.local.json +10 -0
- package/package.json +1 -1
- package/src/commands/help.js +7 -0
- package/src/commands/init.js +10 -0
- package/src/index.js +27 -1
- package/src/utils/telemetry.js +229 -0
package/package.json
CHANGED
package/src/commands/help.js
CHANGED
|
@@ -49,6 +49,9 @@ function help() {
|
|
|
49
49
|
console.log(chalk.gray(' cache:clear Delete cache (re-download on next use)\n'));
|
|
50
50
|
console.log(chalk.white('Other commands:'));
|
|
51
51
|
console.log(chalk.gray(' doctor Check project health and configuration'));
|
|
52
|
+
console.log(chalk.gray(' telemetry Show telemetry status'));
|
|
53
|
+
console.log(chalk.gray(' --enable Enable anonymous usage data collection'));
|
|
54
|
+
console.log(chalk.gray(' --disable Disable anonymous usage data collection'));
|
|
52
55
|
console.log(chalk.gray(' help Show this help message\n'));
|
|
53
56
|
console.log(chalk.white('Examples:'));
|
|
54
57
|
console.log(chalk.gray(' # Deploy full app to production'));
|
|
@@ -78,6 +81,10 @@ function help() {
|
|
|
78
81
|
console.log(chalk.gray(' --tenancy <single|multi> Tenancy model (skips prompt)'));
|
|
79
82
|
console.log(chalk.gray(' --user-model <b2b|b2b2c> User model (skips prompt)'));
|
|
80
83
|
console.log(chalk.gray(' help Show this help message\n'));
|
|
84
|
+
console.log(chalk.white('Telemetry:'));
|
|
85
|
+
console.log(chalk.gray(' telemetry Show telemetry status'));
|
|
86
|
+
console.log(chalk.gray(' --enable Enable anonymous usage data collection'));
|
|
87
|
+
console.log(chalk.gray(' --disable Disable anonymous usage data collection\n'));
|
|
81
88
|
console.log(chalk.white('Cache Management:'));
|
|
82
89
|
console.log(chalk.gray(' cache:info Show cache location, size, and cached services'));
|
|
83
90
|
console.log(chalk.gray(' cache:update Force update cache to latest version'));
|
package/src/commands/init.js
CHANGED
|
@@ -7,6 +7,7 @@ const { checkGitHubAccess, showAccessDeniedMessage } = require('../utils/github-
|
|
|
7
7
|
const { ensureCacheReady } = require('../utils/service-cache');
|
|
8
8
|
const { isLaunchFrameProject } = require('../utils/project-helpers');
|
|
9
9
|
const logger = require('../utils/logger');
|
|
10
|
+
const { trackEvent } = require('../utils/telemetry');
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Check if running in development mode (local) vs production (npm install)
|
|
@@ -37,6 +38,8 @@ async function init(options = {}) {
|
|
|
37
38
|
|
|
38
39
|
if (!accessCheck.hasAccess) {
|
|
39
40
|
showAccessDeniedMessage();
|
|
41
|
+
trackEvent('command_executed', { command: 'init', success: false, error_message: 'access_denied' });
|
|
42
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
40
43
|
process.exit(1);
|
|
41
44
|
}
|
|
42
45
|
|
|
@@ -135,6 +138,13 @@ async function init(options = {}) {
|
|
|
135
138
|
console.log(chalk.white('\nGenerating project...\n'));
|
|
136
139
|
await generateProject(answers, variantChoices, templateRoot);
|
|
137
140
|
|
|
141
|
+
trackEvent('command_executed', {
|
|
142
|
+
command: 'init',
|
|
143
|
+
success: true,
|
|
144
|
+
tenancy: variantChoices.tenancy,
|
|
145
|
+
user_model: variantChoices.userModel
|
|
146
|
+
});
|
|
147
|
+
|
|
138
148
|
console.log(chalk.green.bold('\nProject created successfully!\n'));
|
|
139
149
|
console.log(chalk.white('Next steps:'));
|
|
140
150
|
console.log(chalk.gray(` cd ${answers.projectName}`));
|
package/src/index.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const chalk = require('chalk');
|
|
4
4
|
const { isLaunchFrameProject } = require('./utils/project-helpers');
|
|
5
5
|
const logger = require('./utils/logger');
|
|
6
|
+
const { initTelemetry, trackEvent, sanitize, setTelemetryEnabled, showTelemetryStatus } = require('./utils/telemetry');
|
|
6
7
|
|
|
7
8
|
// Import commands
|
|
8
9
|
const { init } = require('./commands/init');
|
|
@@ -64,6 +65,8 @@ function parseFlags(args) {
|
|
|
64
65
|
* Main CLI router
|
|
65
66
|
*/
|
|
66
67
|
async function main() {
|
|
68
|
+
initTelemetry();
|
|
69
|
+
|
|
67
70
|
const inProject = isLaunchFrameProject();
|
|
68
71
|
const flags = parseFlags(args);
|
|
69
72
|
|
|
@@ -167,6 +170,15 @@ async function main() {
|
|
|
167
170
|
case 'cache:update':
|
|
168
171
|
await cacheUpdate();
|
|
169
172
|
break;
|
|
173
|
+
case 'telemetry':
|
|
174
|
+
if (flags.disable) {
|
|
175
|
+
setTelemetryEnabled(false);
|
|
176
|
+
} else if (flags.enable) {
|
|
177
|
+
setTelemetryEnabled(true);
|
|
178
|
+
} else {
|
|
179
|
+
showTelemetryStatus();
|
|
180
|
+
}
|
|
181
|
+
break;
|
|
170
182
|
case 'help':
|
|
171
183
|
case '--help':
|
|
172
184
|
case '-h':
|
|
@@ -179,4 +191,18 @@ async function main() {
|
|
|
179
191
|
}
|
|
180
192
|
}
|
|
181
193
|
|
|
182
|
-
main()
|
|
194
|
+
main()
|
|
195
|
+
.then(() => {
|
|
196
|
+
if (command && command !== 'help' && command !== '--help' && command !== '-h' && command !== '--version') {
|
|
197
|
+
trackEvent('command_executed', { command, success: true });
|
|
198
|
+
}
|
|
199
|
+
})
|
|
200
|
+
.catch((error) => {
|
|
201
|
+
trackEvent('command_executed', {
|
|
202
|
+
command,
|
|
203
|
+
success: false,
|
|
204
|
+
error_message: sanitize(error.message)
|
|
205
|
+
});
|
|
206
|
+
console.error(chalk.red(error.message));
|
|
207
|
+
process.exit(1);
|
|
208
|
+
});
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
const https = require('https');
|
|
2
|
+
const crypto = require('crypto');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const chalk = require('chalk');
|
|
7
|
+
|
|
8
|
+
const MIXPANEL_TOKEN = '3e6214f33ba535dec14021547039427c';
|
|
9
|
+
const CONFIG_DIR = path.join(os.homedir(), '.launchframe');
|
|
10
|
+
const CONFIG_PATH = path.join(CONFIG_DIR, 'config.json');
|
|
11
|
+
|
|
12
|
+
let config = null;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Sanitize error messages by stripping file paths and capping length
|
|
16
|
+
* @param {string} message - Raw error message
|
|
17
|
+
* @returns {string} Sanitized message
|
|
18
|
+
*/
|
|
19
|
+
function sanitize(message) {
|
|
20
|
+
if (!message) return 'unknown';
|
|
21
|
+
return message
|
|
22
|
+
.replace(/\/[\w\-\/.]+/g, '<path>')
|
|
23
|
+
.replace(/[A-Z]:\\[\w\-\\.\\]+/g, '<path>')
|
|
24
|
+
.substring(0, 200);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Read config from disk, or return defaults
|
|
29
|
+
* @returns {Object} Config object
|
|
30
|
+
*/
|
|
31
|
+
function readConfig() {
|
|
32
|
+
try {
|
|
33
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
34
|
+
const raw = fs.readFileSync(CONFIG_PATH, 'utf8');
|
|
35
|
+
return JSON.parse(raw);
|
|
36
|
+
}
|
|
37
|
+
} catch {
|
|
38
|
+
// Corrupted config — start fresh
|
|
39
|
+
}
|
|
40
|
+
return {};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Write config to disk
|
|
45
|
+
* @param {Object} cfg - Config object to write
|
|
46
|
+
*/
|
|
47
|
+
function writeConfig(cfg) {
|
|
48
|
+
try {
|
|
49
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
50
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
51
|
+
}
|
|
52
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(cfg, null, 2) + '\n');
|
|
53
|
+
} catch {
|
|
54
|
+
// Silently ignore write failures
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Check if telemetry is disabled via environment variables
|
|
60
|
+
* @returns {boolean} True if disabled via env
|
|
61
|
+
*/
|
|
62
|
+
function isDisabledByEnv() {
|
|
63
|
+
return process.env.DO_NOT_TRACK === '1' || process.env.LAUNCHFRAME_TELEMETRY_DISABLED === '1';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Check if telemetry is enabled
|
|
68
|
+
* @returns {boolean} True if telemetry is enabled
|
|
69
|
+
*/
|
|
70
|
+
function isEnabled() {
|
|
71
|
+
if (isDisabledByEnv()) return false;
|
|
72
|
+
if (!config || !config.telemetry) return false;
|
|
73
|
+
return config.telemetry.enabled !== false;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Initialize telemetry — call once at CLI startup.
|
|
78
|
+
* Reads/creates config, shows first-run notice if needed.
|
|
79
|
+
* Synchronous and fast.
|
|
80
|
+
*/
|
|
81
|
+
function initTelemetry() {
|
|
82
|
+
try {
|
|
83
|
+
config = readConfig();
|
|
84
|
+
|
|
85
|
+
if (!config.telemetry) {
|
|
86
|
+
config.telemetry = {
|
|
87
|
+
enabled: true,
|
|
88
|
+
noticeShown: false,
|
|
89
|
+
anonymousId: crypto.randomUUID()
|
|
90
|
+
};
|
|
91
|
+
writeConfig(config);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (!config.telemetry.anonymousId) {
|
|
95
|
+
config.telemetry.anonymousId = crypto.randomUUID();
|
|
96
|
+
writeConfig(config);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!config.telemetry.noticeShown && !isDisabledByEnv()) {
|
|
100
|
+
console.log(
|
|
101
|
+
chalk.gray(
|
|
102
|
+
'\nLaunchFrame collects anonymous usage data to improve the CLI.\n' +
|
|
103
|
+
'No personal information is collected. Run `launchframe telemetry --disable` to opt out.\n' +
|
|
104
|
+
'Learn more: https://launchframe.dev/privacy\n'
|
|
105
|
+
)
|
|
106
|
+
);
|
|
107
|
+
config.telemetry.noticeShown = true;
|
|
108
|
+
writeConfig(config);
|
|
109
|
+
}
|
|
110
|
+
} catch {
|
|
111
|
+
// Telemetry init must never break the CLI
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Fire-and-forget event tracking.
|
|
117
|
+
* @param {string} name - Event name
|
|
118
|
+
* @param {Object} properties - Event properties
|
|
119
|
+
*/
|
|
120
|
+
function trackEvent(name, properties = {}) {
|
|
121
|
+
try {
|
|
122
|
+
if (!isEnabled()) return;
|
|
123
|
+
|
|
124
|
+
const cliVersion = (() => {
|
|
125
|
+
try {
|
|
126
|
+
return require('../../package.json').version;
|
|
127
|
+
} catch {
|
|
128
|
+
return 'unknown';
|
|
129
|
+
}
|
|
130
|
+
})();
|
|
131
|
+
|
|
132
|
+
const payload = JSON.stringify([
|
|
133
|
+
{
|
|
134
|
+
event: name,
|
|
135
|
+
properties: {
|
|
136
|
+
token: MIXPANEL_TOKEN,
|
|
137
|
+
distinct_id: config.telemetry.anonymousId,
|
|
138
|
+
cli_version: cliVersion,
|
|
139
|
+
node_version: process.version,
|
|
140
|
+
os: process.platform,
|
|
141
|
+
os_arch: process.arch,
|
|
142
|
+
...properties
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
]);
|
|
146
|
+
|
|
147
|
+
const req = https.request(
|
|
148
|
+
{
|
|
149
|
+
hostname: 'api-eu.mixpanel.com',
|
|
150
|
+
path: '/track',
|
|
151
|
+
method: 'POST',
|
|
152
|
+
headers: {
|
|
153
|
+
'Content-Type': 'application/json',
|
|
154
|
+
Accept: 'text/plain',
|
|
155
|
+
'Content-Length': Buffer.byteLength(payload)
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
(res) => { res.resume(); }
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
req.on('error', () => {});
|
|
162
|
+
req.write(payload);
|
|
163
|
+
req.end();
|
|
164
|
+
req.unref();
|
|
165
|
+
} catch {
|
|
166
|
+
// Telemetry must never break the CLI
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Enable or disable telemetry
|
|
172
|
+
* @param {boolean} enabled - Whether to enable telemetry
|
|
173
|
+
*/
|
|
174
|
+
function setTelemetryEnabled(enabled) {
|
|
175
|
+
config = readConfig();
|
|
176
|
+
|
|
177
|
+
if (!config.telemetry) {
|
|
178
|
+
config.telemetry = {
|
|
179
|
+
enabled,
|
|
180
|
+
noticeShown: true,
|
|
181
|
+
anonymousId: crypto.randomUUID()
|
|
182
|
+
};
|
|
183
|
+
} else {
|
|
184
|
+
config.telemetry.enabled = enabled;
|
|
185
|
+
config.telemetry.noticeShown = true;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
writeConfig(config);
|
|
189
|
+
|
|
190
|
+
if (enabled) {
|
|
191
|
+
console.log(chalk.green('\nTelemetry enabled. Thank you for helping improve LaunchFrame!\n'));
|
|
192
|
+
} else {
|
|
193
|
+
console.log(chalk.yellow('\nTelemetry disabled. No data will be collected.\n'));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Show current telemetry status
|
|
199
|
+
*/
|
|
200
|
+
function showTelemetryStatus() {
|
|
201
|
+
config = readConfig();
|
|
202
|
+
const envDisabled = isDisabledByEnv();
|
|
203
|
+
|
|
204
|
+
console.log(chalk.blue.bold('\nTelemetry Status\n'));
|
|
205
|
+
|
|
206
|
+
if (envDisabled) {
|
|
207
|
+
console.log(chalk.yellow(' Disabled via environment variable'));
|
|
208
|
+
if (process.env.DO_NOT_TRACK === '1') {
|
|
209
|
+
console.log(chalk.gray(' DO_NOT_TRACK=1'));
|
|
210
|
+
}
|
|
211
|
+
if (process.env.LAUNCHFRAME_TELEMETRY_DISABLED === '1') {
|
|
212
|
+
console.log(chalk.gray(' LAUNCHFRAME_TELEMETRY_DISABLED=1'));
|
|
213
|
+
}
|
|
214
|
+
} else if (config.telemetry && config.telemetry.enabled !== false) {
|
|
215
|
+
console.log(chalk.green(' Enabled'));
|
|
216
|
+
} else {
|
|
217
|
+
console.log(chalk.yellow(' Disabled'));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (config.telemetry && config.telemetry.anonymousId) {
|
|
221
|
+
console.log(chalk.gray(` Anonymous ID: ${config.telemetry.anonymousId}`));
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
console.log(chalk.gray('\n To disable: launchframe telemetry --disable'));
|
|
225
|
+
console.log(chalk.gray(' To enable: launchframe telemetry --enable'));
|
|
226
|
+
console.log(chalk.gray(' Env vars: DO_NOT_TRACK=1 or LAUNCHFRAME_TELEMETRY_DISABLED=1\n'));
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
module.exports = { initTelemetry, trackEvent, sanitize, setTelemetryEnabled, showTelemetryStatus };
|