@symbo.ls/cli 2.33.27 → 2.33.28
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 +86 -0
- package/helpers/changesUtils.js +76 -5
- package/package.json +5 -5
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,
|
package/helpers/changesUtils.js
CHANGED
|
@@ -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
|
-
|
|
101
|
+
const hadPrev = typeof aVal !== 'undefined'
|
|
102
|
+
if (!hadPrev) {
|
|
94
103
|
// New item
|
|
95
104
|
changes.push(['update', [typeKey, itemKey], bVal])
|
|
96
|
-
|
|
97
|
-
|
|
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
|
|
116
|
-
const
|
|
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.
|
|
3
|
+
"version": "2.33.28",
|
|
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.
|
|
19
|
-
"@symbo.ls/init": "^2.33.
|
|
20
|
-
"@symbo.ls/socket": "^2.33.
|
|
18
|
+
"@symbo.ls/fetch": "^2.33.28",
|
|
19
|
+
"@symbo.ls/init": "^2.33.28",
|
|
20
|
+
"@symbo.ls/socket": "^2.33.28",
|
|
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": "
|
|
31
|
+
"gitHead": "58b2f9f34b228712ccc4352e15913c52d6007afb"
|
|
32
32
|
}
|