@symbo.ls/cli 2.33.11 → 2.33.13

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/bin/sync.js CHANGED
@@ -1,128 +1,329 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ import fs from 'fs'
4
+ import path from 'path'
3
5
  import chalk from 'chalk'
4
- import { program } from './program.js'
6
+ import inquirer from 'inquirer'
5
7
  import { loadModule } from './require.js'
6
- import { updateDynamycFile } from '@symbo.ls/socket'
7
- import * as utils from '@domql/utils'
8
+ import { program } from './program.js'
9
+ import { CredentialManager } from '../helpers/credentialManager.js'
10
+ import { buildDirectory } from '../helpers/fileUtils.js'
11
+ import { generateDiffDisplay, showDiffPager } from '../helpers/diffUtils.js'
12
+ import { normalizeKeys } from '../helpers/compareUtils.js'
13
+ import { getCurrentProjectData, postProjectChanges } from '../helpers/apiUtils.js'
14
+ import { computeCoarseChanges, threeWayRebase, computeOrdersForTuples, preprocessChanges } from '../helpers/changesUtils.js'
15
+ import { createFs } from './fs.js'
16
+ import { showAuthRequiredMessages, showBuildErrorMessages } from '../helpers/buildMessages.js'
17
+ import { loadSymbolsConfig } from '../helpers/symbolsConfig.js'
18
+ import { loadCliConfig, readLock, writeLock, getConfigPaths, updateLegacySymbolsJson } from '../helpers/config.js'
19
+ const RC_PATH = process.cwd() + '/symbols.json'
20
+ const distDir = path.join(process.cwd(), 'smbls')
21
+
22
+ async function buildLocalProject() {
23
+ try {
24
+ const outputDirectory = path.join(distDir, 'dist')
25
+ await buildDirectory(distDir, outputDirectory)
26
+ const outputFile = path.join(outputDirectory, 'index.js')
27
+ return normalizeKeys(await loadModule(outputFile, { silent: false }))
28
+ } catch (error) {
29
+ // Enhance error with build context
30
+ error.buildContext = {
31
+ command: 'sync',
32
+ workspace: process.cwd()
33
+ }
34
+ throw error
35
+ }
36
+ }
37
+
38
+ async function resolveTopLevelConflicts(conflictsKeys, ours, theirs) {
39
+ const choices = conflictsKeys.map((key) => {
40
+ const ourChange = ours.find((c) => c[1][0] === key)
41
+ const theirChange = theirs.find((c) => c[1][0] === key)
42
+ return {
43
+ name: `${chalk.cyan(key)} ${chalk.green('Local')} vs ${chalk.red('Remote')}`,
44
+ value: key,
45
+ short: key,
46
+ description: `${chalk.green('+ Local:')} ${JSON.stringify(ourChange?.[2])}
47
+ ${chalk.red('- Remote:')} ${JSON.stringify(theirChange?.[2])}`
48
+ }
49
+ })
8
50
 
9
- import * as socketClient from '@symbo.ls/socket/client.js'
10
- import { fetchFromCli } from './fetch.js'
11
- import { convertFromCli } from './convert.js'
51
+ const { selected } = await inquirer.prompt([{
52
+ type: 'checkbox',
53
+ name: 'selected',
54
+ message: 'Conflicts detected. Select the keys to keep from Local (unchecked will keep Remote):',
55
+ choices,
56
+ pageSize: 10
57
+ }])
12
58
 
13
- const { debounce } = utils.default || utils
59
+ const final = conflictsKeys.map((key) => {
60
+ if (selected.includes(key)) {
61
+ return ours.find((c) => c[1][0] === key)
62
+ }
63
+ return theirs.find((c) => c[1][0] === key)
64
+ }).filter(Boolean)
14
65
 
15
- const SOCKET_API_URL_LOCAL = 'http://localhost:8080/'
16
- const SOCKET_API_URL = 'https://api.symbols.app/'
66
+ return final
67
+ }
17
68
 
18
- const debugMsg = chalk.dim(
19
- 'Use --verbose to debug the error or open the issue at https://github.com/symbo-ls/smbls'
20
- )
69
+ function getAt(obj, pathArr = []) {
70
+ try {
71
+ return pathArr.reduce((acc, k) => (acc == null ? undefined : acc[k]), obj)
72
+ } catch (_) {
73
+ return undefined
74
+ }
75
+ }
21
76
 
22
- const RC_PATH = process.cwd() + '/symbols.json'
23
- let rc = {}
24
- try {
25
- rc = loadModule(RC_PATH) // eslint-disable-line
26
- } catch (e) {
27
- console.error('Please include symbols.json to your root of respository')
77
+ function buildDiffsFromChanges(changes, base, target) {
78
+ const diffs = []
79
+ for (const [op, path, value] of changes) {
80
+ const oldVal = getAt(base, path)
81
+ if (op === 'delete') {
82
+ diffs.push(generateDiffDisplay('delete', path, oldVal))
83
+ } else {
84
+ const newVal = value !== undefined ? value : getAt(target, path)
85
+ diffs.push(generateDiffDisplay('update', path, oldVal, newVal))
86
+ }
87
+ }
88
+ return diffs
28
89
  }
29
90
 
30
- program
31
- .command('sync')
32
- .description('Realtime sync with Symbols')
33
- .option('-d, --dev', 'Running from local server')
34
- .option('-v, --verbose', 'Verbose errors and warnings')
35
- .option(
36
- '-k, --key',
37
- 'Bypass the symbols.json key, overriding the key manually'
38
- )
39
- .option('-f, --fetch', 'Verbose errors and warnings', true)
40
- .option('--convert', 'Verbose errors and warnings', true)
41
- .option('--update', 'overriding changes from platform', true)
42
- .option('--verbose-code', 'Verbose errors and warnings')
43
- .action(async opts => {
44
- const { dev, verbose, fetch: fetchOpt, convert: convertOpt } = opts
45
-
46
- if (fetchOpt) {
47
- await fetchFromCli(opts)
48
- console.log(chalk.dim('\n----------------\n'))
49
- }
50
-
51
- if (!rc) {
52
- console.error('symbols.json not found in the root of the repository')
91
+ async function confirmChanges(localChanges, remoteChanges, base, local, remote) {
92
+ if (localChanges.length === 0 && remoteChanges.length === 0) {
93
+ console.log(chalk.bold.yellow('No changes detected'))
94
+ return false
95
+ }
96
+
97
+ console.log(chalk.bold.white('\nDetected changes:'))
98
+
99
+ if (localChanges.length) {
100
+ const localChangesByType = localChanges.reduce((acc, [type]) => {
101
+ acc[type] = (acc[type] || 0) + 1
102
+ return acc
103
+ }, {})
104
+ console.log(chalk.cyan('\nLocal changes:'))
105
+ Object.entries(localChangesByType).forEach(([type, count]) => {
106
+ console.log(chalk.gray(`- ${type}: ${chalk.cyan(count)} changes`))
107
+ })
108
+ }
109
+
110
+ if (remoteChanges.length) {
111
+ const remoteChangesByType = remoteChanges.reduce((acc, [type]) => {
112
+ acc[type] = (acc[type] || 0) + 1
113
+ return acc
114
+ }, {})
115
+ console.log(chalk.cyan('\nRemote changes:'))
116
+ Object.entries(remoteChangesByType).forEach(([type, count]) => {
117
+ console.log(chalk.gray(`- ${type}: ${chalk.cyan(count)} changes`))
118
+ })
119
+ }
120
+
121
+ const { view } = await inquirer.prompt([{
122
+ type: 'confirm',
123
+ name: 'view',
124
+ message: 'View full list of changes?',
125
+ default: false
126
+ }])
127
+ if (view) {
128
+ const diffs = []
129
+ if (localChanges.length) {
130
+ diffs.push(chalk.bold('\nLocal changes:\n'))
131
+ diffs.push(...buildDiffsFromChanges(localChanges, base, local))
132
+ }
133
+ if (remoteChanges.length) {
134
+ diffs.push(chalk.bold('\nRemote changes:\n'))
135
+ diffs.push(...buildDiffsFromChanges(remoteChanges, base, remote))
136
+ }
137
+ await showDiffPager(diffs)
138
+ }
139
+
140
+ const { proceed } = await inquirer.prompt([{
141
+ type: 'confirm',
142
+ name: 'proceed',
143
+ message: 'Proceed with sync?',
144
+ default: false
145
+ }])
146
+
147
+ return proceed
148
+ }
149
+
150
+ export async function syncProjectChanges(options) {
151
+ const credManager = new CredentialManager()
152
+ const authToken = credManager.ensureAuthToken()
153
+
154
+ if (!authToken) {
155
+ showAuthRequiredMessages()
156
+ process.exit(1)
157
+ }
158
+
159
+ try {
160
+ // Load configuration
161
+ const symbolsConfig = await loadSymbolsConfig()
162
+ const cliConfig = loadCliConfig()
163
+ const lock = readLock()
164
+ const { projectPath } = getConfigPaths()
165
+ const { key: legacyKey } = symbolsConfig
166
+ const appKey = cliConfig.projectKey || legacyKey
167
+ const localBranch = cliConfig.branch || symbolsConfig.branch || 'main'
168
+
169
+ if (options.verbose) {
170
+ console.log(chalk.dim('\nSync configuration:'))
171
+ console.log(chalk.gray(`App Key: ${chalk.cyan(appKey)}`))
172
+ console.log(chalk.gray(`Current version: ${chalk.cyan(lock.version || 'unknown')}`))
173
+ console.log(chalk.gray(`Branch: ${chalk.cyan(localBranch)}`))
174
+ console.log(chalk.gray(`Environment: ${chalk.cyan(options.dev ? 'Development' : 'Production')}\n`))
175
+ } else {
176
+ console.log(chalk.dim('\nSyncing project...'))
177
+ }
178
+
179
+ // Build and load local project
180
+ console.log(chalk.dim('Building local project...'))
181
+ let localProject
182
+ try {
183
+ localProject = await buildLocalProject()
184
+ console.log(chalk.gray('Local project built successfully'))
185
+ } catch (buildError) {
186
+ showBuildErrorMessages(buildError)
187
+ process.exit(1)
188
+ }
189
+
190
+ // Load base snapshot (last pulled)
191
+ const baseSnapshot = (() => {
192
+ try {
193
+ const raw = fs.readFileSync(projectPath, 'utf8')
194
+ return JSON.parse(raw)
195
+ } catch (_) {
196
+ return {}
197
+ }
198
+ })()
199
+
200
+ // Get latest server data (ignore ETag to ensure latest)
201
+ console.log(chalk.dim('Fetching server data...'))
202
+ const serverResp = await getCurrentProjectData(
203
+ { projectKey: appKey, projectId: lock.projectId },
204
+ authToken,
205
+ { branch: localBranch, includePending: true }
206
+ )
207
+ const serverProject = serverResp.data || {}
208
+ console.log(chalk.gray('Server data fetched successfully'))
209
+
210
+ // Generate coarse local and remote changes via simple three-way rebase
211
+ const base = normalizeKeys(baseSnapshot || {})
212
+ const local = normalizeKeys(localProject || {})
213
+ const remote = normalizeKeys(serverProject || {})
214
+ const { ours, theirs, conflicts, finalChanges } = threeWayRebase(base, local, remote)
215
+
216
+ const localChanges = ours
217
+ const remoteChanges = theirs
218
+
219
+ if (!localChanges.length && !remoteChanges.length) {
220
+ console.log(chalk.bold.green('\nProject is already in sync'))
53
221
  return
54
222
  }
55
223
 
56
- // if (rc) return false /// /////////////////////
57
-
58
- await rc.then(symbolsrc => {
59
- const options = { ...symbolsrc, ...opts }
60
- const { framework, distDir, prettify, verboseCode } = options
61
- const key = options.key || opts.key
62
- const socketUrl = dev ? SOCKET_API_URL_LOCAL : SOCKET_API_URL
63
-
64
- console.log('Connecting to:', chalk.bold(socketUrl))
65
- console.log()
66
-
67
- socketClient.connect(key, {
68
- source: 'cli',
69
- socketUrl,
70
- onConnect: (id, socket) => {
71
- console.log(
72
- 'Connected to',
73
- chalk.green(key),
74
- 'from',
75
- chalk.bold('Symbols'),
76
- 'socket server'
77
- )
78
- console.log('Socket id:', id)
79
- console.log(chalk.dim('\nListening to updates...\n'))
80
- },
81
- onChange: debounce(async (event, data) => {
82
- if (event === 'clients') {
83
- console.log(
84
- 'Active clients:',
85
- chalk.green.bold(Object.keys(data).join(', '))
86
- )
87
- return
88
- }
89
-
90
- const parseData = JSON.parse(data)
91
- const d = parseData && parseData.DATA
92
-
93
- if (!d) return
94
-
95
- if (Object.keys(d).length) {
96
- console.log(chalk.dim('\n----------------\n'))
97
- console.log(chalk.dim('Received update:'))
98
- console.log(Object.keys(d).join(', '))
99
- if (verboseCode)
100
- console.log(chalk.dim(JSON.stringify(d, null, prettify ?? 2)))
101
-
102
- if (distDir) {
103
- if (fetchOpt) {
104
- await fetchFromCli(options)
105
- console.log(chalk.dim('\n----------------\n'))
106
- return
107
- }
108
- } else {
109
- updateDynamycFile(d, { framework, ...options })
110
- }
111
- }
112
-
113
- if (d.components && convertOpt && framework) {
114
- convertFromCli(d.components, {
115
- ...options,
116
- framework
117
- })
118
- }
119
- }, 1500),
120
- onError: (err, socket) => {
121
- console.log(chalk.bold.green('Error during connection'))
122
- if (verbose) console.error(err)
123
- else console.log(debugMsg)
124
- },
125
- ...options
224
+ // Show change summaries
225
+ if (localChanges.length) {
226
+ const byType = localChanges.reduce((acc, [t]) => ((acc[t] = (acc[t] || 0) + 1), acc), {})
227
+ console.log(chalk.cyan('\nLocal changes:'))
228
+ Object.entries(byType).forEach(([type, count]) => {
229
+ console.log(chalk.gray(`- ${type}: ${chalk.cyan(count)} changes`))
230
+ })
231
+ }
232
+ if (remoteChanges.length) {
233
+ const byType = remoteChanges.reduce((acc, [t]) => ((acc[t] = (acc[t] || 0) + 1), acc), {})
234
+ console.log(chalk.cyan('\nRemote changes:'))
235
+ Object.entries(byType).forEach(([type, count]) => {
236
+ console.log(chalk.gray(`- ${type}: ${chalk.cyan(count)} changes`))
126
237
  })
238
+ }
239
+
240
+ // Handle conflicts if any
241
+ let toApply = finalChanges && finalChanges.length ? finalChanges : [...localChanges]
242
+ if (conflicts.length) {
243
+ console.log(chalk.yellow(`\nFound ${conflicts.length} conflicts`))
244
+ const chosen = await resolveTopLevelConflicts(conflicts, ours, theirs)
245
+ // Combine non-conflicting ours with chosen resolutions
246
+ const nonConflictOurs = ours.filter(([_, [k]]) => !conflicts.includes(k))
247
+ toApply = [...nonConflictOurs, ...chosen]
248
+ }
249
+
250
+ // Confirm sync
251
+ const shouldProceed = await confirmChanges(localChanges, remoteChanges, base, local, remote)
252
+ if (!shouldProceed) {
253
+ console.log(chalk.yellow('Sync cancelled'))
254
+ return
255
+ }
256
+
257
+ // Update server
258
+ console.log(chalk.dim('\nUpdating server...'))
259
+ const projectId = lock.projectId || serverProject?.projectInfo?.id
260
+ if (!projectId) {
261
+ console.log(chalk.red('Unable to resolve projectId. Please fetch first to initialize lock.'))
262
+ process.exit(1)
263
+ }
264
+ const operationId = `cli-${Date.now()}`
265
+ // Expand into granular changes against remote/server state, compute orders from local
266
+ const { granularChanges } = preprocessChanges(remote, toApply)
267
+ const orders = computeOrdersForTuples(local, granularChanges)
268
+ const result = await postProjectChanges(projectId, authToken, {
269
+ branch: localBranch,
270
+ type: options.type || 'patch',
271
+ operationId,
272
+ changes: toApply,
273
+ granularChanges,
274
+ orders
127
275
  })
128
- })
276
+ const { id: versionId, value: version } = result.noOp ? {} : (result.data || {})
277
+
278
+ // Update symbols.json with new version
279
+ if (version) {
280
+ updateLegacySymbolsJson({ ...(symbolsConfig || {}), version, branch: localBranch, versionId })
281
+ }
282
+
283
+ // Get latest project data after sync
284
+ console.log(chalk.dim('Fetching latest project data...'))
285
+ const updated = await getCurrentProjectData(
286
+ { projectKey: appKey, projectId },
287
+ authToken,
288
+ { branch: localBranch, includePending: true }
289
+ )
290
+ const updatedServerData = updated?.data || {}
291
+
292
+ // Apply changes to local files
293
+ console.log(chalk.dim('Updating local files...'))
294
+ await createFs(updatedServerData, distDir, { update: true, metadata: false })
295
+ console.log(chalk.gray('Local files updated successfully'))
296
+
297
+ console.log(chalk.bold.green('\nProject synced successfully!'))
298
+ console.log(chalk.gray(`New version: ${chalk.cyan(version)}`))
299
+
300
+ // Update lock and base snapshot
301
+ writeLock({
302
+ etag: updated.etag || null,
303
+ version: version || (lock.version || null),
304
+ branch: localBranch,
305
+ projectId,
306
+ pulledAt: new Date().toISOString()
307
+ })
308
+ try {
309
+ const { projectPath } = getConfigPaths()
310
+ await fs.promises.writeFile(projectPath, JSON.stringify(updatedServerData, null, 2))
311
+ } catch (_) {}
312
+
313
+ } catch (error) {
314
+ console.error(chalk.bold.red('\nSync failed:'), chalk.white(error.message))
315
+ if (options.verbose && error.stack) {
316
+ console.error(chalk.dim('\nStack trace:'))
317
+ console.error(chalk.gray(error.stack))
318
+ }
319
+ process.exit(1)
320
+ }
321
+ }
322
+
323
+ program
324
+ .command('sync')
325
+ .description('Sync local changes with remote server')
326
+ .option('-m, --message <message>', 'Specify a commit message')
327
+ .option('-d, --dev', 'Run against local server')
328
+ .option('-v, --verbose', 'Show verbose output')
329
+ .action(syncProjectChanges)
@@ -1,73 +1,75 @@
1
1
  import { getApiUrl } from './config.js'
2
- import { ALLOWED_FIELDS } from './compareUtils.js'
3
2
 
4
- export async function getServerProjectData (appKey, authToken) {
5
- try {
6
- const response = await fetch(`${getApiUrl()}/get/`, {
7
- method: 'GET',
8
- headers: {
9
- 'X-AppKey': appKey,
10
- Authorization: `Bearer ${authToken}`
11
- }
12
- })
13
-
14
- const data = await response.json()
15
-
16
- if (!response.ok) {
17
- const error = new Error(`Failed to fetch server data: ${response.statusText}`)
18
- error.response = {
19
- status: response.status,
20
- data
21
- }
22
- throw error
23
- }
24
-
25
- // Check if response is empty object
26
- if (data && Object.keys(data).length === 0) {
27
- const error = new Error('Project not found')
28
- error.response = {
29
- status: 404,
30
- data
31
- }
32
- throw error
33
- }
34
-
35
- return data
36
- } catch (error) {
37
- if (!error.response) {
38
- error.response = {
39
- status: 500,
40
- data: {}
41
- }
42
- }
43
- throw error
3
+ // New API (adds ETag and new endpoints)
4
+ function buildProjectDataUrl({ projectKey, projectId, branch, includePending = true, version }) {
5
+ const base = getApiUrl()
6
+ let url
7
+ if (projectId) {
8
+ url = new URL(`${base}/core/projects/${encodeURIComponent(projectId)}/data`)
9
+ } else if (projectKey) {
10
+ url = new URL(`${base}/core/projects/key/${encodeURIComponent(projectKey)}/data`)
11
+ } else {
12
+ throw new Error('Missing project identifier (projectId or projectKey)')
44
13
  }
14
+ if (branch) url.searchParams.set('branch', branch)
15
+ if (includePending !== undefined) url.searchParams.set('includePending', includePending ? 'true' : 'false')
16
+ if (version) url.searchParams.set('version', version)
17
+ return url.toString()
45
18
  }
46
19
 
47
- export async function updateProjectOnServer (appKey, authToken, changes, projectData) {
48
- // Validate changes before sending
49
- const validChanges = changes.filter(([type, path]) => {
50
- const normalizedPath = path[0].toLowerCase()
51
- return ALLOWED_FIELDS.includes(normalizedPath)
52
- })
20
+ export async function getCurrentProjectData(project, authToken, opts = {}) {
21
+ const { branch, includePending = true, etag, version } = opts
22
+ const url = buildProjectDataUrl({ projectKey: project.projectKey, projectId: project.projectId, branch, includePending, version })
23
+ const headers = {
24
+ 'Authorization': `Bearer ${authToken}`
25
+ }
26
+ if (etag) headers['If-None-Match'] = etag
27
+ const response = await fetch(url, { method: 'GET', headers })
28
+ if (response.status === 304) {
29
+ return { notModified: true, etag: etag }
30
+ }
31
+ if (!response.ok) {
32
+ const data = await safeJson(response)
33
+ const err = new Error(data?.message || `Failed to fetch project data (${response.status})`)
34
+ err.response = { status: response.status, data }
35
+ throw err
36
+ }
37
+ const json = await response.json()
38
+ return {
39
+ notModified: false,
40
+ etag: response.headers.get('ETag') || response.headers.get('Etag') || null,
41
+ data: json?.data || json
42
+ }
43
+ }
53
44
 
54
- const response = await fetch(`${getApiUrl()}/auth/project/update`, {
45
+ export async function postProjectChanges(projectId, authToken, body) {
46
+ const base = getApiUrl()
47
+ const url = `${base}/core/projects/${encodeURIComponent(projectId)}/changes`
48
+ const response = await fetch(url, {
55
49
  method: 'POST',
56
50
  headers: {
57
51
  'Content-Type': 'application/json',
58
- Authorization: `Bearer ${authToken}`
52
+ 'Authorization': `Bearer ${authToken}`
59
53
  },
60
- body: JSON.stringify({
61
- appKey,
62
- changes: validChanges,
63
- projectUpdates: projectData
64
- })
54
+ body: JSON.stringify(body)
65
55
  })
66
-
56
+ if (response.status === 204) {
57
+ return { noOp: true }
58
+ }
67
59
  if (!response.ok) {
68
- const error = await response.json()
69
- throw new Error(error.error || 'Failed to update project')
60
+ const data = await safeJson(response)
61
+ const err = new Error(data?.message || `Failed to post changes (${response.status})`)
62
+ err.response = { status: response.status, data }
63
+ throw err
70
64
  }
65
+ const json = await response.json()
66
+ return { noOp: false, data: json?.data || json }
67
+ }
71
68
 
72
- return response
69
+ async function safeJson(res) {
70
+ try {
71
+ return await res.json()
72
+ } catch (_) {
73
+ return null
74
+ }
73
75
  }
@@ -0,0 +1,76 @@
1
+ import chalk from 'chalk'
2
+
3
+ const CREATE_PROJECT_URL = 'https://symbols.app/create'
4
+
5
+ export const showAuthRequiredMessages = () => {
6
+ console.log('\n' + chalk.yellow('⚠️ Authentication Required'))
7
+ console.log(chalk.white('\nTo access Symbols resources, please log in first:'))
8
+ console.log(chalk.cyan('\n smbls login'))
9
+
10
+ console.log('\nNeed help?')
11
+ console.log(chalk.dim('• Make sure you have a Symbols account'))
12
+ console.log(chalk.dim('• Visit https://symbols.app/docs/cli for more information'))
13
+ console.log(chalk.dim('• Contact support@symbols.app if you continue having issues\n'))
14
+ }
15
+
16
+ export const showProjectNotFoundMessages = (appKey) => {
17
+ console.error(chalk.bold.red('\nProject not found or access denied.'))
18
+ console.error(chalk.bold.yellow('\nPossible reasons:'))
19
+ console.error(chalk.gray('1. The project does not exist'))
20
+ console.error(chalk.gray("2. You don't have access to this project"))
21
+ console.error(chalk.gray('3. The app key in symbols.json might be incorrect'))
22
+
23
+ console.error(chalk.bold.yellow('\nTo resolve this:'))
24
+ console.error(chalk.white(
25
+ `1. Visit ${chalk.cyan.underline(
26
+ CREATE_PROJECT_URL
27
+ )} to create a new project`
28
+ ))
29
+ console.error(chalk.white(
30
+ '2. After creating the project, update your symbols.json with the correct information:'
31
+ ))
32
+ console.error(chalk.gray(` - Verify the app key: ${chalk.cyan(appKey)}`))
33
+ console.error(chalk.gray(' - Make sure you have the correct permissions'))
34
+
35
+ console.error(chalk.bold.yellow('\nThen try again:'))
36
+ console.error(chalk.cyan('$ smbls push'))
37
+ }
38
+
39
+ export function showBuildErrorMessages(error) {
40
+ console.log(chalk.bold.red('\nBuild Failed ❌'))
41
+ console.log(chalk.white('\nError details:'))
42
+ console.log(chalk.yellow(error.message))
43
+
44
+ // Extract useful information from the error
45
+ const errorLocation = error.errors?.[0]?.location || {}
46
+ if (errorLocation.file) {
47
+ console.log(chalk.white('\nError location:'))
48
+ console.log(chalk.gray(`File: ${chalk.cyan(errorLocation.file)}`))
49
+ if (errorLocation.line) {
50
+ console.log(chalk.gray(`Line: ${chalk.cyan(errorLocation.line)}`))
51
+ }
52
+ }
53
+
54
+ console.log(chalk.white('\nPossible solutions:'))
55
+
56
+ // Common build issues and their solutions
57
+ if (error.message.includes('instanceof')) {
58
+ console.log(chalk.gray('• Check if you are using browser-specific APIs in Node.js environment'))
59
+ console.log(chalk.gray(' Consider using conditional checks: typeof window !== "undefined"'))
60
+ }
61
+ if (error.message.includes('Could not resolve')) {
62
+ console.log(chalk.gray('• Verify all dependencies are installed: npm install'))
63
+ console.log(chalk.gray('• Check import/require paths are correct'))
64
+ }
65
+ if (error.message.includes('Unexpected token')) {
66
+ console.log(chalk.gray('• Ensure the code syntax is compatible with the target environment'))
67
+ console.log(chalk.gray('• Check for syntax errors in the indicated file'))
68
+ }
69
+
70
+ console.log(chalk.white('\nNext steps:'))
71
+ console.log(chalk.gray('1. Fix the build errors in your local project'))
72
+ console.log(chalk.gray('2. Ensure all dependencies are properly installed'))
73
+ console.log(chalk.gray('3. Run the command again'))
74
+
75
+ console.log(chalk.gray('\nFor more help, please check the documentation or report this issue on GitHub'))
76
+ }