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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-sonicjs",
3
- "version": "2.0.10",
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.8',
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
+ }
@@ -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
+ }