create-sonicjs 2.0.10 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -2
- package/src/cli.js +47 -1
- package/src/telemetry-notice.js +43 -0
- package/src/telemetry.js +160 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-sonicjs",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "Create a new SonicJS application with zero configuration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -43,7 +43,8 @@
|
|
|
43
43
|
"ora": "^8.1.3",
|
|
44
44
|
"execa": "^9.6.0",
|
|
45
45
|
"fs-extra": "^11.2.0",
|
|
46
|
-
"validate-npm-package-name": "^7.0.0"
|
|
46
|
+
"validate-npm-package-name": "^7.0.0",
|
|
47
|
+
"posthog-node": "^4.0.0"
|
|
47
48
|
},
|
|
48
49
|
"engines": {
|
|
49
50
|
"node": ">=18.0.0"
|
package/src/cli.js
CHANGED
|
@@ -8,6 +8,8 @@ import kleur from 'kleur'
|
|
|
8
8
|
import ora from 'ora'
|
|
9
9
|
import { execa } from 'execa'
|
|
10
10
|
import validatePackageName from 'validate-npm-package-name'
|
|
11
|
+
import { initTelemetry, track, shutdown, isEnabled } from './telemetry.js'
|
|
12
|
+
import { displayTelemetryNotice } from './telemetry-notice.js'
|
|
11
13
|
|
|
12
14
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
13
15
|
|
|
@@ -52,23 +54,67 @@ const flags = {
|
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
async function main() {
|
|
57
|
+
const startTime = Date.now()
|
|
58
|
+
|
|
55
59
|
try {
|
|
60
|
+
// Initialize telemetry
|
|
61
|
+
await initTelemetry()
|
|
62
|
+
|
|
63
|
+
// Show telemetry notice if enabled
|
|
64
|
+
if (isEnabled()) {
|
|
65
|
+
displayTelemetryNotice()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Track installation started
|
|
69
|
+
await track('installation_started', {
|
|
70
|
+
template: flags.template || 'starter',
|
|
71
|
+
skipInstall: flags.skipInstall || false,
|
|
72
|
+
skipGit: flags.skipGit || false,
|
|
73
|
+
skipCloudflare: flags.skipCloudflare || false
|
|
74
|
+
})
|
|
75
|
+
|
|
56
76
|
// Get project details
|
|
57
77
|
const answers = await getProjectDetails(projectName)
|
|
58
78
|
|
|
59
79
|
// Create project
|
|
60
80
|
await createProject(answers, flags)
|
|
61
81
|
|
|
82
|
+
// Track installation completed
|
|
83
|
+
const duration = Date.now() - startTime
|
|
84
|
+
await track('installation_completed', {
|
|
85
|
+
duration,
|
|
86
|
+
template: answers.template,
|
|
87
|
+
createResources: answers.createResources || false,
|
|
88
|
+
initGit: answers.initGit || false,
|
|
89
|
+
skipInstall: answers.skipInstall || false,
|
|
90
|
+
includeExample: answers.includeExample || false
|
|
91
|
+
})
|
|
92
|
+
|
|
62
93
|
// Success message
|
|
63
94
|
printSuccessMessage(answers)
|
|
64
95
|
|
|
96
|
+
// Shutdown telemetry (flush events)
|
|
97
|
+
await shutdown()
|
|
98
|
+
|
|
65
99
|
} catch (error) {
|
|
66
100
|
if (error.message === 'cancelled') {
|
|
101
|
+
// Track cancellation
|
|
102
|
+
await track('installation_cancelled')
|
|
103
|
+
await shutdown()
|
|
104
|
+
|
|
67
105
|
console.log()
|
|
68
106
|
console.log(kleur.yellow('⚠ Cancelled'))
|
|
69
107
|
process.exit(0)
|
|
70
108
|
}
|
|
71
109
|
|
|
110
|
+
// Track failure
|
|
111
|
+
const duration = Date.now() - startTime
|
|
112
|
+
await track('installation_failed', {
|
|
113
|
+
duration,
|
|
114
|
+
errorType: error.message.split(':')[0].trim()
|
|
115
|
+
})
|
|
116
|
+
await shutdown()
|
|
117
|
+
|
|
72
118
|
console.error()
|
|
73
119
|
console.error(kleur.red('✖ Error:'), error.message)
|
|
74
120
|
console.error()
|
|
@@ -372,7 +418,7 @@ async function copyTemplate(templateName, targetDir, options) {
|
|
|
372
418
|
|
|
373
419
|
// Add @sonicjs-cms/core dependency
|
|
374
420
|
packageJson.dependencies = {
|
|
375
|
-
'@sonicjs-cms/core': '^2.0
|
|
421
|
+
'@sonicjs-cms/core': '^2.2.0',
|
|
376
422
|
...packageJson.dependencies
|
|
377
423
|
}
|
|
378
424
|
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telemetry Notice
|
|
3
|
+
*
|
|
4
|
+
* Displays privacy-respecting telemetry notice to users
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import kleur from 'kleur'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Display telemetry notice
|
|
11
|
+
*/
|
|
12
|
+
export function displayTelemetryNotice() {
|
|
13
|
+
console.log()
|
|
14
|
+
console.log(kleur.dim('━'.repeat(60)))
|
|
15
|
+
console.log()
|
|
16
|
+
console.log(kleur.bold('📊 Anonymous Usage Analytics'))
|
|
17
|
+
console.log()
|
|
18
|
+
console.log(kleur.dim('SonicJS collects anonymous telemetry to improve the product.'))
|
|
19
|
+
console.log(kleur.dim('We do NOT collect any personally identifiable information.'))
|
|
20
|
+
console.log()
|
|
21
|
+
console.log(kleur.dim('Collected data includes:'))
|
|
22
|
+
console.log(kleur.dim(' • Installation success/failure rates'))
|
|
23
|
+
console.log(kleur.dim(' • OS and Node.js version'))
|
|
24
|
+
console.log(kleur.dim(' • Anonymous installation ID'))
|
|
25
|
+
console.log()
|
|
26
|
+
console.log(kleur.dim('To disable telemetry, set:'))
|
|
27
|
+
console.log(kleur.cyan(' export SONICJS_TELEMETRY=false'))
|
|
28
|
+
console.log()
|
|
29
|
+
console.log(kleur.dim('Learn more: https://docs.sonicjs.com/telemetry'))
|
|
30
|
+
console.log()
|
|
31
|
+
console.log(kleur.dim('━'.repeat(60)))
|
|
32
|
+
console.log()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Display opt-out reminder in success message
|
|
37
|
+
*/
|
|
38
|
+
export function displayOptOutReminder() {
|
|
39
|
+
console.log()
|
|
40
|
+
console.log(kleur.dim('💡 Tip: To disable anonymous analytics, run:'))
|
|
41
|
+
console.log(kleur.cyan(' export SONICJS_TELEMETRY=false'))
|
|
42
|
+
console.log()
|
|
43
|
+
}
|
package/src/telemetry.js
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telemetry for create-sonicjs CLI
|
|
3
|
+
*
|
|
4
|
+
* Privacy-first telemetry to track installation metrics
|
|
5
|
+
* - Anonymous installation IDs
|
|
6
|
+
* - No PII collection
|
|
7
|
+
* - Opt-out via environment variable
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { randomUUID } from 'crypto'
|
|
11
|
+
import os from 'os'
|
|
12
|
+
import fs from 'fs-extra'
|
|
13
|
+
import path from 'path'
|
|
14
|
+
|
|
15
|
+
const TELEMETRY_ENABLED = process.env.SONICJS_TELEMETRY !== 'false' && process.env.DO_NOT_TRACK !== '1'
|
|
16
|
+
const TELEMETRY_API_KEY = process.env.POSTHOG_API_KEY || 'phc_VuhFUIJLXzwyGjlgQ67dbNeSh5x4cp9F8i15hZFIDhs'
|
|
17
|
+
const TELEMETRY_HOST = process.env.POSTHOG_HOST || 'https://us.i.posthog.com'
|
|
18
|
+
const DEBUG = process.env.DEBUG === 'true'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get or create installation ID
|
|
22
|
+
*/
|
|
23
|
+
function getInstallationId() {
|
|
24
|
+
const telemetryDir = path.join(os.homedir(), '.sonicjs')
|
|
25
|
+
const telemetryFile = path.join(telemetryDir, 'telemetry.json')
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
// Try to read existing ID
|
|
29
|
+
if (fs.existsSync(telemetryFile)) {
|
|
30
|
+
const data = fs.readJsonSync(telemetryFile)
|
|
31
|
+
if (data.installationId) {
|
|
32
|
+
return data.installationId
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Create new ID
|
|
37
|
+
const installationId = randomUUID()
|
|
38
|
+
fs.ensureDirSync(telemetryDir)
|
|
39
|
+
fs.writeJsonSync(telemetryFile, { installationId, createdAt: new Date().toISOString() })
|
|
40
|
+
|
|
41
|
+
return installationId
|
|
42
|
+
} catch (error) {
|
|
43
|
+
// If we can't read/write the file, generate a temporary ID
|
|
44
|
+
if (DEBUG) {
|
|
45
|
+
console.error('[Telemetry] Failed to get/create installation ID:', error)
|
|
46
|
+
}
|
|
47
|
+
return randomUUID()
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Initialize telemetry
|
|
53
|
+
*/
|
|
54
|
+
let telemetryClient = null
|
|
55
|
+
let installationId = null
|
|
56
|
+
|
|
57
|
+
async function initTelemetry() {
|
|
58
|
+
if (!TELEMETRY_ENABLED) {
|
|
59
|
+
if (DEBUG) {
|
|
60
|
+
console.log('[Telemetry] Disabled via environment variable')
|
|
61
|
+
}
|
|
62
|
+
return null
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
installationId = getInstallationId()
|
|
67
|
+
|
|
68
|
+
// Initialize PostHog
|
|
69
|
+
if (TELEMETRY_API_KEY) {
|
|
70
|
+
const { PostHog } = await import('posthog-node')
|
|
71
|
+
telemetryClient = new PostHog(TELEMETRY_API_KEY, {
|
|
72
|
+
host: TELEMETRY_HOST,
|
|
73
|
+
flushAt: 1, // Flush immediately for CLI
|
|
74
|
+
flushInterval: 1000
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
if (DEBUG) {
|
|
78
|
+
console.log('[Telemetry] Initialized with ID:', installationId)
|
|
79
|
+
}
|
|
80
|
+
} else if (DEBUG) {
|
|
81
|
+
console.log('[Telemetry] No API key, tracking will be logged only')
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return { client: telemetryClient, installationId }
|
|
85
|
+
} catch (error) {
|
|
86
|
+
if (DEBUG) {
|
|
87
|
+
console.error('[Telemetry] Initialization failed:', error)
|
|
88
|
+
}
|
|
89
|
+
return null
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Track an event
|
|
95
|
+
*/
|
|
96
|
+
async function track(event, properties = {}) {
|
|
97
|
+
if (!TELEMETRY_ENABLED) return
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
if (!installationId) {
|
|
101
|
+
await initTelemetry()
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const enrichedProperties = {
|
|
105
|
+
...properties,
|
|
106
|
+
timestamp: new Date().toISOString(),
|
|
107
|
+
os: os.platform(),
|
|
108
|
+
nodeVersion: process.version.split('.').slice(0, 2).join('.'), // e.g., "v18.0"
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (telemetryClient && installationId) {
|
|
112
|
+
telemetryClient.capture({
|
|
113
|
+
distinctId: installationId,
|
|
114
|
+
event,
|
|
115
|
+
properties: enrichedProperties
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
if (DEBUG) {
|
|
119
|
+
console.log('[Telemetry] Tracked:', event, enrichedProperties)
|
|
120
|
+
}
|
|
121
|
+
} else if (DEBUG) {
|
|
122
|
+
console.log('[Telemetry] Event (no client):', event, enrichedProperties)
|
|
123
|
+
}
|
|
124
|
+
} catch (error) {
|
|
125
|
+
// Silent fail - telemetry should never break the CLI
|
|
126
|
+
if (DEBUG) {
|
|
127
|
+
console.error('[Telemetry] Failed to track event:', error)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Shutdown telemetry (flush pending events)
|
|
134
|
+
*/
|
|
135
|
+
async function shutdown() {
|
|
136
|
+
if (telemetryClient) {
|
|
137
|
+
try {
|
|
138
|
+
await telemetryClient.shutdown()
|
|
139
|
+
} catch (error) {
|
|
140
|
+
if (DEBUG) {
|
|
141
|
+
console.error('[Telemetry] Shutdown failed:', error)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Check if telemetry is enabled
|
|
149
|
+
*/
|
|
150
|
+
function isEnabled() {
|
|
151
|
+
return TELEMETRY_ENABLED
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export {
|
|
155
|
+
initTelemetry,
|
|
156
|
+
track,
|
|
157
|
+
shutdown,
|
|
158
|
+
isEnabled,
|
|
159
|
+
getInstallationId
|
|
160
|
+
}
|