@symbo.ls/cli 2.33.27 → 2.33.29

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/collab.js CHANGED
@@ -275,10 +275,77 @@ export async function startCollab(options) {
275
275
  }
276
276
  }
277
277
 
278
+ /**
279
+ * Augment the loaded project object with any new items that exist as
280
+ * filesystem files (e.g. newly created component/page/method/function/snippet
281
+ * files) but are not yet present in the in‑memory data object.
282
+ *
283
+ * This bridges the gap between the one‑way createFs → files mapping so that
284
+ * adding a new file like components/MainFooter.js is seen as a new
285
+ * `components.MainFooter` data item and can be sent to the server.
286
+ */
287
+ async function augmentLocalWithNewFsItems(local) {
288
+ if (!local || typeof local !== 'object') return
289
+
290
+ const TYPES = ['components', 'pages', 'snippets', 'methods', 'functions']
291
+
292
+ for (let i = 0; i < TYPES.length; i++) {
293
+ const type = TYPES[i]
294
+ const srcDir = path.join(distDir, type)
295
+
296
+ let entries
297
+ try {
298
+ entries = await fs.promises.readdir(srcDir)
299
+ } catch {
300
+ continue
301
+ }
302
+ if (!Array.isArray(entries) || !entries.length) continue
303
+
304
+ const container = local[type] && typeof local[type] === 'object' && !Array.isArray(local[type])
305
+ ? local[type]
306
+ : (local[type] = {})
307
+ const baseSection = currentBase && currentBase[type] && typeof currentBase[type] === 'object'
308
+ ? currentBase[type]
309
+ : {}
310
+
311
+ for (let j = 0; j < entries.length; j++) {
312
+ const filename = entries[j]
313
+ if (!filename.endsWith('.js') || filename === 'index.js') continue
314
+
315
+ const key = filename.slice(0, -3)
316
+ if (Object.prototype.hasOwnProperty.call(container, key)) continue
317
+ if (Object.prototype.hasOwnProperty.call(baseSection, key)) continue
318
+
319
+ const compiledPath = path.join(outputDir, type, filename)
320
+ let mod
321
+ try {
322
+ const { loadModule } = await import('./require.js')
323
+ mod = await loadModule(compiledPath, { silent: true })
324
+ } catch {
325
+ if (options.verbose) {
326
+ console.error(`Failed to load new ${type} item from`, compiledPath)
327
+ }
328
+ continue
329
+ }
330
+
331
+ if (!mod) continue
332
+
333
+ let value = null
334
+ if (mod && typeof mod === 'object') {
335
+ value = mod.default || mod[key] || null
336
+ }
337
+ if (!value || typeof value !== 'object') continue
338
+
339
+ container[key] = value
340
+ }
341
+ }
342
+ }
343
+
278
344
  const sendLocalChanges = debounce(async () => {
279
345
  if (suppressLocalChanges) return
280
346
  const local = await loadLocalProject()
281
347
  if (!local) return
348
+ await augmentLocalWithNewFsItems(local)
282
349
  // Prepare safe, JSON-serialisable snapshots for diffing & transport
283
350
  const base = currentBase || {}
284
351
  const safeBase = stringifyFunctionsForTransport(base)
@@ -294,6 +361,25 @@ export async function startCollab(options) {
294
361
  const { granularChanges } = preprocessChanges(safeBase, changes)
295
362
  const orders = computeOrdersForTuples(safeLocal, granularChanges)
296
363
  console.log(chalk.gray(`Emitting local ops: ${JSON.stringify({ changes, granularChanges, orders, branch })}`))
364
+
365
+ // Optimistically apply our own ops to the in-memory base so that
366
+ // subsequent diffs are computed against the latest local state.
367
+ // This avoids repeatedly re-emitting the same changes when the
368
+ // server does not immediately send back a fresh snapshot.
369
+ try {
370
+ const tuplesToApply = Array.isArray(granularChanges) && granularChanges.length
371
+ ? granularChanges
372
+ : changes
373
+ applyTuples(currentBase, tuplesToApply)
374
+ if (Array.isArray(orders) && orders.length) {
375
+ applyOrders(currentBase, orders)
376
+ }
377
+ } catch (e) {
378
+ if (options.verbose) {
379
+ console.error('Failed to apply local ops to in-memory base', e)
380
+ }
381
+ }
382
+
297
383
  socket.emit('ops', {
298
384
  changes,
299
385
  granularChanges,
@@ -1,11 +1,16 @@
1
+ import * as utils from '@domql/utils'
1
2
  import { normalizeKeys } from './compareUtils.js'
2
3
 
4
+ const { objectToString } = utils.default || utils
5
+
3
6
  // Keys managed by the CLI filesystem representation (exclude settings/schema/key/thumbnail/etc.)
4
7
  export const DATA_KEYS = [
5
8
  'designSystem','components','state','pages','snippets',
6
9
  'methods','functions','dependencies','files'
7
10
  ]
8
11
 
12
+ const SCHEMA_CODE_TYPES = new Set(['pages', 'components', 'functions', 'methods', 'snippets'])
13
+
9
14
  function stripMetaDeep(val) {
10
15
  if (Array.isArray(val)) {
11
16
  return val.map(stripMetaDeep)
@@ -61,9 +66,12 @@ export function computeCoarseChanges(base, local, keys = DATA_KEYS) {
61
66
 
62
67
  // Generate per-item granular changes one level deeper for each data type.
63
68
  // Ignore 'schema' comparisons; manage schema side-effects below.
69
+ const baseSchema = a?.schema || {}
70
+
64
71
  for (const typeKey of [...keys]) {
65
72
  const aSection = a?.[typeKey] || {}
66
73
  const bSection = b?.[typeKey] || {}
74
+ const aSchemaSection = baseSchema?.[typeKey] || {}
67
75
 
68
76
  // If sections are not plain objects (or are arrays), fallback to coarse replacement on the section itself
69
77
  const aIsObject = aSection && typeof aSection === 'object' && !Array.isArray(aSection)
@@ -90,11 +98,17 @@ export function computeCoarseChanges(base, local, keys = DATA_KEYS) {
90
98
  for (const itemKey of Object.keys(bSection)) {
91
99
  const aVal = aSection[itemKey]
92
100
  const bVal = bSection[itemKey]
93
- if (aVal === undefined) {
101
+ const hadPrev = typeof aVal !== 'undefined'
102
+ if (!hadPrev) {
94
103
  // New item
95
104
  changes.push(['update', [typeKey, itemKey], bVal])
96
- // Ensure any stale schema code is removed (safety)
97
- changes.push(['delete', ['schema', typeKey, itemKey, 'code']])
105
+ const hadSchema = aSchemaSection && Object.prototype.hasOwnProperty.call(aSchemaSection, itemKey)
106
+ if (SCHEMA_CODE_TYPES.has(typeKey) && !hadSchema) {
107
+ const schemaItem = buildSchemaItemFromData(typeKey, itemKey, bVal)
108
+ if (schemaItem) {
109
+ changes.push(['update', ['schema', typeKey, itemKey], schemaItem])
110
+ }
111
+ }
98
112
  } else if (!equal(aVal, bVal)) {
99
113
  // Updated item
100
114
  changes.push(['update', [typeKey, itemKey], bVal])
@@ -112,8 +126,18 @@ export function threeWayRebase(base, local, remote, keys = DATA_KEYS) {
112
126
  const theirsKeys = computeChangedKeys(base, remote, keys)
113
127
  const conflicts = oursKeys.filter(k => theirsKeys.includes(k))
114
128
 
115
- const ours = computeCoarseChanges(base, local, keys).filter(([_, [k]]) => oursKeys.includes(k))
116
- const theirs = computeCoarseChanges(base, remote, keys).filter(([_, [k]]) => theirsKeys.includes(k))
129
+ const allOurs = computeCoarseChanges(base, local, keys)
130
+ const allTheirs = computeCoarseChanges(base, remote, keys)
131
+
132
+ const ownsKey = (path, set) => {
133
+ if (!Array.isArray(path) || !path.length) return false
134
+ const [rootKey, typeKey] = path
135
+ const primary = rootKey === 'schema' ? typeKey : rootKey
136
+ return set.includes(primary)
137
+ }
138
+
139
+ const ours = allOurs.filter(([, path]) => ownsKey(path, oursKeys))
140
+ const theirs = allTheirs.filter(([, path]) => ownsKey(path, theirsKeys))
117
141
 
118
142
  let finalChanges = []
119
143
  if (conflicts.length === 0) {
@@ -224,6 +248,13 @@ function normaliseSchemaCode(code) {
224
248
  .replaceAll('/////tilde', '`')
225
249
  }
226
250
 
251
+ function encodeSchemaCode(code) {
252
+ if (typeof code !== 'string' || !code.length) return ''
253
+ return code
254
+ .replaceAll('\n', '/////n')
255
+ .replaceAll('`', '/////tilde')
256
+ }
257
+
227
258
  function parseExportedObject(code) {
228
259
  const src = normaliseSchemaCode(code)
229
260
  if (!src) return null
@@ -236,6 +267,46 @@ function parseExportedObject(code) {
236
267
  }
237
268
  }
238
269
 
270
+ export function buildSchemaCodeFromObject(obj) {
271
+ if (!obj || typeof obj !== 'object') return ''
272
+ const body = objectToString(obj, 2)
273
+ const src = `export default ${body}`
274
+ return encodeSchemaCode(src)
275
+ }
276
+
277
+ function buildSchemaItemFromData(type, key, value) {
278
+ const schemaType = type
279
+
280
+ const base = {
281
+ title: key,
282
+ key,
283
+ type: schemaType
284
+ }
285
+
286
+ if (['pages', 'components'].includes(schemaType)) {
287
+ base.settings = {
288
+ gridOptions: {}
289
+ }
290
+ base.props = {}
291
+ base.interactivity = []
292
+ base.dataTypes = []
293
+ base.error = null
294
+ }
295
+
296
+ if (SCHEMA_CODE_TYPES.has(schemaType)) {
297
+ try {
298
+ const code = buildSchemaCodeFromObject(value)
299
+ if (code) {
300
+ base.code = code
301
+ }
302
+ } catch (_) {
303
+ // Fallback: omit code field if serialisation fails
304
+ }
305
+ }
306
+
307
+ return base
308
+ }
309
+
239
310
  function extractTopLevelKeysFromCode(code) {
240
311
  const obj = parseExportedObject(code)
241
312
  if (!obj || typeof obj !== 'object') return []
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symbo.ls/cli",
3
- "version": "2.33.27",
3
+ "version": "2.33.29",
4
4
  "description": "Fetch your Symbols configuration",
5
5
  "main": "bin/fetch.js",
6
6
  "author": "Symbols",
@@ -15,9 +15,9 @@
15
15
  "vpatch": "npm version patch && npm publish"
16
16
  },
17
17
  "dependencies": {
18
- "@symbo.ls/fetch": "^2.33.27",
19
- "@symbo.ls/init": "^2.33.27",
20
- "@symbo.ls/socket": "^2.33.27",
18
+ "@symbo.ls/fetch": "^2.33.29",
19
+ "@symbo.ls/init": "^2.33.29",
20
+ "@symbo.ls/socket": "^2.33.29",
21
21
  "chalk": "^5.4.1",
22
22
  "chokidar": "^4.0.3",
23
23
  "commander": "^13.1.0",
@@ -28,5 +28,5 @@
28
28
  "socket.io-client": "^4.8.1",
29
29
  "v8-compile-cache": "^2.4.0"
30
30
  },
31
- "gitHead": "305c78c0cd824b365d3e464dc3ede6b250c8baf1"
31
+ "gitHead": "4a29b29c86a47fa047345b16120cc426bbe57f41"
32
32
  }