@symbo.ls/cli 2.33.35 → 2.33.36
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 +88 -13
- package/bin/fetch.js +20 -3
- package/bin/fs.js +23 -10
- package/bin/push.js +10 -3
- package/bin/sync.js +31 -5
- package/helpers/changesUtils.js +22 -3
- package/helpers/dependenciesUtils.js +174 -0
- package/helpers/orderUtils.js +46 -0
- package/package.json +5 -5
package/bin/collab.js
CHANGED
|
@@ -11,6 +11,14 @@ import { stringifyFunctionsForTransport } from '../helpers/transportUtils.js'
|
|
|
11
11
|
import { getCurrentProjectData } from '../helpers/apiUtils.js'
|
|
12
12
|
import { computeCoarseChanges, computeOrdersForTuples, preprocessChanges } from '../helpers/changesUtils.js'
|
|
13
13
|
import { createFs } from './fs.js'
|
|
14
|
+
import { stripOrderFields } from '../helpers/orderUtils.js'
|
|
15
|
+
import { normalizeKeys } from '../helpers/compareUtils.js'
|
|
16
|
+
import {
|
|
17
|
+
augmentProjectWithLocalPackageDependencies,
|
|
18
|
+
ensureSchemaDependencies,
|
|
19
|
+
findNearestPackageJson,
|
|
20
|
+
syncPackageJsonDependencies
|
|
21
|
+
} from '../helpers/dependenciesUtils.js'
|
|
14
22
|
|
|
15
23
|
// Lazy import socket.io-client and chokidar to avoid adding cost for non-collab users
|
|
16
24
|
async function importDeps() {
|
|
@@ -21,6 +29,29 @@ async function importDeps() {
|
|
|
21
29
|
return { io, chokidar }
|
|
22
30
|
}
|
|
23
31
|
|
|
32
|
+
function toExportNameFromFileStem(stem) {
|
|
33
|
+
// Mirror fs.js behavior loosely: kebab/snake/path -> camelCase export name.
|
|
34
|
+
// e.g. "add-network" -> "addNetwork"
|
|
35
|
+
if (!stem || typeof stem !== 'string') return stem
|
|
36
|
+
const parts = stem.split(/[^a-zA-Z0-9]+/).filter(Boolean)
|
|
37
|
+
if (!parts.length) return stem
|
|
38
|
+
const first = parts[0]
|
|
39
|
+
return (
|
|
40
|
+
first +
|
|
41
|
+
parts
|
|
42
|
+
.slice(1)
|
|
43
|
+
.map((p) => (p ? p[0].toUpperCase() + p.slice(1) : ''))
|
|
44
|
+
.join('')
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function toPagesRouteKeyFromFileStem(stem) {
|
|
49
|
+
// createFs writes `/foo` -> pages/foo.js and `/` -> pages/main.js
|
|
50
|
+
if (!stem || typeof stem !== 'string') return stem
|
|
51
|
+
if (stem === 'main') return '/'
|
|
52
|
+
return `/${stem}`
|
|
53
|
+
}
|
|
54
|
+
|
|
24
55
|
function debounce(fn, wait) {
|
|
25
56
|
let t = null
|
|
26
57
|
const debounced = (...args) => {
|
|
@@ -52,6 +83,8 @@ export async function startCollab(options) {
|
|
|
52
83
|
resolveDistDir(symbolsConfig) ||
|
|
53
84
|
path.join(process.cwd(), 'smbls')
|
|
54
85
|
|
|
86
|
+
const packageJsonPath = findNearestPackageJson(process.cwd())
|
|
87
|
+
|
|
55
88
|
if (!appKey) {
|
|
56
89
|
console.log(chalk.red('Missing project key. Add it to symbols.json or .symbols/config.json'))
|
|
57
90
|
process.exit(1)
|
|
@@ -141,9 +174,19 @@ export async function startCollab(options) {
|
|
|
141
174
|
}
|
|
142
175
|
|
|
143
176
|
async function writeProjectAndFs(fullObj) {
|
|
177
|
+
// Avoid persisting ordering metadata into local repository files
|
|
178
|
+
const persistedObj = stripOrderFields(fullObj)
|
|
179
|
+
// Keep schema.dependencies consistent and sync dependencies into local package.json
|
|
180
|
+
try {
|
|
181
|
+
ensureSchemaDependencies(persistedObj)
|
|
182
|
+
if (packageJsonPath && persistedObj?.dependencies) {
|
|
183
|
+
syncPackageJsonDependencies(packageJsonPath, persistedObj.dependencies, { overwriteExisting: true })
|
|
184
|
+
}
|
|
185
|
+
} catch (_) {}
|
|
186
|
+
|
|
144
187
|
const { projectPath } = getConfigPaths()
|
|
145
188
|
try {
|
|
146
|
-
await fs.promises.writeFile(projectPath, JSON.stringify(
|
|
189
|
+
await fs.promises.writeFile(projectPath, JSON.stringify(persistedObj, null, 2))
|
|
147
190
|
} catch (_) {}
|
|
148
191
|
// Avoid echoing the changes we are about to materialize
|
|
149
192
|
suppressLocalChanges = true
|
|
@@ -152,7 +195,7 @@ export async function startCollab(options) {
|
|
|
152
195
|
sendLocalChanges.cancel()
|
|
153
196
|
}
|
|
154
197
|
try {
|
|
155
|
-
await createFs(
|
|
198
|
+
await createFs(persistedObj, distDir, { update: true, metadata: false })
|
|
156
199
|
} finally {
|
|
157
200
|
// Extend suppression window to allow file events to settle fully
|
|
158
201
|
suppressUntil = Date.now() + suppressionWindowMs
|
|
@@ -167,6 +210,12 @@ export async function startCollab(options) {
|
|
|
167
210
|
{ branch, includePending: true }
|
|
168
211
|
)
|
|
169
212
|
const initialData = prime?.data || {}
|
|
213
|
+
try {
|
|
214
|
+
ensureSchemaDependencies(initialData)
|
|
215
|
+
if (packageJsonPath && initialData?.dependencies) {
|
|
216
|
+
syncPackageJsonDependencies(packageJsonPath, initialData.dependencies, { overwriteExisting: true })
|
|
217
|
+
}
|
|
218
|
+
} catch (_) {}
|
|
170
219
|
const etag = prime?.etag || null
|
|
171
220
|
writeLock({
|
|
172
221
|
etag,
|
|
@@ -180,7 +229,7 @@ export async function startCollab(options) {
|
|
|
180
229
|
try {
|
|
181
230
|
const { projectPath } = getConfigPaths()
|
|
182
231
|
await fs.promises.mkdir(path.dirname(projectPath), { recursive: true })
|
|
183
|
-
await fs.promises.writeFile(projectPath, JSON.stringify(initialData, null, 2))
|
|
232
|
+
await fs.promises.writeFile(projectPath, JSON.stringify(stripOrderFields(initialData), null, 2))
|
|
184
233
|
} catch (_) {}
|
|
185
234
|
currentBase = { ...(initialData || {}) }
|
|
186
235
|
|
|
@@ -240,9 +289,8 @@ export async function startCollab(options) {
|
|
|
240
289
|
: preprocessChanges(currentBase || {}, payload?.changes || []).granularChanges
|
|
241
290
|
if (!Array.isArray(tuples) || !tuples.length) return
|
|
242
291
|
applyTuples(currentBase, tuples)
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
}
|
|
292
|
+
// If server omits schema.dependencies updates, ensure it's present locally
|
|
293
|
+
ensureSchemaDependencies(currentBase)
|
|
246
294
|
await writeProjectAndFs(currentBase)
|
|
247
295
|
writeLock({ pulledAt: new Date().toISOString() })
|
|
248
296
|
if (options.verbose) console.log(chalk.gray('Applied incoming ops to local workspace'))
|
|
@@ -268,7 +316,8 @@ export async function startCollab(options) {
|
|
|
268
316
|
const { loadModule } = await import('./require.js')
|
|
269
317
|
await buildDirectory(distDir, outputDir)
|
|
270
318
|
const loaded = await loadModule(outputFile, { silent: true, noCache: true })
|
|
271
|
-
|
|
319
|
+
// Ensure a plain, mutable object (avoid getter-only export objects)
|
|
320
|
+
return normalizeKeys(loaded)
|
|
272
321
|
} catch (e) {
|
|
273
322
|
if (options.verbose) console.error('Build failed while watching:', e.message)
|
|
274
323
|
return null
|
|
@@ -312,9 +361,17 @@ export async function startCollab(options) {
|
|
|
312
361
|
const filename = entries[j]
|
|
313
362
|
if (!filename.endsWith('.js') || filename === 'index.js') continue
|
|
314
363
|
|
|
315
|
-
const
|
|
364
|
+
const fileStem = filename.slice(0, -3)
|
|
365
|
+
const key = type === 'pages'
|
|
366
|
+
? toPagesRouteKeyFromFileStem(fileStem)
|
|
367
|
+
: fileStem
|
|
368
|
+
const altKey = type === 'pages' ? fileStem : null
|
|
369
|
+
|
|
370
|
+
// Skip if already present (support legacy/broken non-slash page keys too)
|
|
316
371
|
if (Object.prototype.hasOwnProperty.call(container, key)) continue
|
|
372
|
+
if (altKey && Object.prototype.hasOwnProperty.call(container, altKey)) continue
|
|
317
373
|
if (Object.prototype.hasOwnProperty.call(baseSection, key)) continue
|
|
374
|
+
if (altKey && Object.prototype.hasOwnProperty.call(baseSection, altKey)) continue
|
|
318
375
|
|
|
319
376
|
const compiledPath = path.join(outputDir, type, filename)
|
|
320
377
|
let mod
|
|
@@ -332,7 +389,14 @@ export async function startCollab(options) {
|
|
|
332
389
|
|
|
333
390
|
let value = null
|
|
334
391
|
if (mod && typeof mod === 'object') {
|
|
335
|
-
|
|
392
|
+
const exportName = toExportNameFromFileStem(fileStem)
|
|
393
|
+
value =
|
|
394
|
+
mod.default ||
|
|
395
|
+
mod[exportName] ||
|
|
396
|
+
mod[fileStem] ||
|
|
397
|
+
mod[key] ||
|
|
398
|
+
(altKey ? mod[altKey] : null) ||
|
|
399
|
+
null
|
|
336
400
|
}
|
|
337
401
|
if (!value || typeof value !== 'object') continue
|
|
338
402
|
|
|
@@ -343,9 +407,11 @@ export async function startCollab(options) {
|
|
|
343
407
|
|
|
344
408
|
const sendLocalChanges = debounce(async () => {
|
|
345
409
|
if (suppressLocalChanges) return
|
|
346
|
-
|
|
410
|
+
let local = await loadLocalProject()
|
|
347
411
|
if (!local) return
|
|
348
412
|
await augmentLocalWithNewFsItems(local)
|
|
413
|
+
// Include package.json deps into local snapshot so dependency edits can be synced
|
|
414
|
+
local = augmentProjectWithLocalPackageDependencies(local, packageJsonPath) || local
|
|
349
415
|
// Prepare safe, JSON-serialisable snapshots for diffing & transport
|
|
350
416
|
const base = currentBase || {}
|
|
351
417
|
const safeBase = stringifyFunctionsForTransport(base)
|
|
@@ -371,9 +437,6 @@ export async function startCollab(options) {
|
|
|
371
437
|
? granularChanges
|
|
372
438
|
: changes
|
|
373
439
|
applyTuples(currentBase, tuplesToApply)
|
|
374
|
-
if (Array.isArray(orders) && orders.length) {
|
|
375
|
-
applyOrders(currentBase, orders)
|
|
376
|
-
}
|
|
377
440
|
} catch (e) {
|
|
378
441
|
if (options.verbose) {
|
|
379
442
|
console.error('Failed to apply local ops to in-memory base', e)
|
|
@@ -407,6 +470,18 @@ export async function startCollab(options) {
|
|
|
407
470
|
.on('change', onFsEvent)
|
|
408
471
|
.on('unlink', onFsEvent)
|
|
409
472
|
|
|
473
|
+
// Also watch package.json for dependency changes
|
|
474
|
+
if (packageJsonPath) {
|
|
475
|
+
const pkgWatcher = chokidar.watch(packageJsonPath, {
|
|
476
|
+
ignoreInitial: true,
|
|
477
|
+
persistent: true
|
|
478
|
+
})
|
|
479
|
+
pkgWatcher
|
|
480
|
+
.on('add', onFsEvent)
|
|
481
|
+
.on('change', onFsEvent)
|
|
482
|
+
.on('unlink', onFsEvent)
|
|
483
|
+
}
|
|
484
|
+
|
|
410
485
|
console.log(chalk.green('Watching local changes and syncing over socket...'))
|
|
411
486
|
console.log(chalk.gray('Press Ctrl+C to exit'))
|
|
412
487
|
}
|
package/bin/fetch.js
CHANGED
|
@@ -12,6 +12,8 @@ import { getCurrentProjectData } from '../helpers/apiUtils.js'
|
|
|
12
12
|
import { showAuthRequiredMessages } from '../helpers/buildMessages.js'
|
|
13
13
|
import { loadSymbolsConfig, resolveDistDir } from '../helpers/symbolsConfig.js'
|
|
14
14
|
import { loadCliConfig, readLock, writeLock, updateLegacySymbolsJson, getConfigPaths } from '../helpers/config.js'
|
|
15
|
+
import { ensureSchemaDependencies, findNearestPackageJson, syncPackageJsonDependencies } from '../helpers/dependenciesUtils.js'
|
|
16
|
+
import { stripOrderFields } from '../helpers/orderUtils.js'
|
|
15
17
|
const { isObjectLike } = (utils.default || utils)
|
|
16
18
|
|
|
17
19
|
const debugMsg = chalk.dim(
|
|
@@ -81,7 +83,9 @@ export const fetchFromCli = async (opts) => {
|
|
|
81
83
|
try {
|
|
82
84
|
const { projectPath } = getConfigPaths()
|
|
83
85
|
await fs.promises.mkdir(path.dirname(projectPath), { recursive: true })
|
|
84
|
-
|
|
86
|
+
// Ensure schema.dependencies exists for payload.dependencies
|
|
87
|
+
ensureSchemaDependencies(payload)
|
|
88
|
+
await fs.promises.writeFile(projectPath, JSON.stringify(stripOrderFields(payload), null, 2))
|
|
85
89
|
} catch (e) {
|
|
86
90
|
console.error(chalk.bold.red('\nError writing file'))
|
|
87
91
|
if (verbose) console.error(e)
|
|
@@ -89,6 +93,19 @@ export const fetchFromCli = async (opts) => {
|
|
|
89
93
|
process.exit(1)
|
|
90
94
|
}
|
|
91
95
|
|
|
96
|
+
// Sync project dependencies into local package.json
|
|
97
|
+
try {
|
|
98
|
+
const packageJsonPath = findNearestPackageJson(process.cwd())
|
|
99
|
+
if (packageJsonPath && payload?.dependencies) {
|
|
100
|
+
const res = syncPackageJsonDependencies(packageJsonPath, payload.dependencies, { overwriteExisting: true })
|
|
101
|
+
if (verbose && res?.ok && res.changed) {
|
|
102
|
+
console.log(chalk.gray(`Updated package.json dependencies from fetched project data`))
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
} catch (e) {
|
|
106
|
+
if (verbose) console.error('Failed updating package.json dependencies', e)
|
|
107
|
+
}
|
|
108
|
+
|
|
92
109
|
const { version: fetchedVersion, ...config } = payload
|
|
93
110
|
|
|
94
111
|
for (const t in config) {
|
|
@@ -117,9 +134,9 @@ export const fetchFromCli = async (opts) => {
|
|
|
117
134
|
}
|
|
118
135
|
|
|
119
136
|
if (update || force) {
|
|
120
|
-
createFs(payload, distDir, { update: true, metadata: false })
|
|
137
|
+
createFs(stripOrderFields(payload), distDir, { update: true, metadata: false })
|
|
121
138
|
} else {
|
|
122
|
-
createFs(payload, distDir, { metadata: false })
|
|
139
|
+
createFs(stripOrderFields(payload), distDir, { metadata: false })
|
|
123
140
|
}
|
|
124
141
|
}
|
|
125
142
|
|
package/bin/fs.js
CHANGED
|
@@ -226,25 +226,38 @@ export async function createFs (
|
|
|
226
226
|
const dirs = []
|
|
227
227
|
|
|
228
228
|
if (body[key] && isObject(body[key])) {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
229
|
+
// Normalize + dedupe entries before writing files. This is especially
|
|
230
|
+
// important for `pages` where both "/node" and "node" can otherwise map
|
|
231
|
+
// to the same filename and cause concurrent writes / corrupted output.
|
|
232
|
+
const normalized = new Map()
|
|
233
|
+
for (const [rawEntryKey, value] of Object.entries(body[key])) {
|
|
234
|
+
if (shouldSkipEntryKey(rawEntryKey)) continue
|
|
235
|
+
|
|
236
|
+
let entryKey = rawEntryKey
|
|
237
|
+
if (key === 'pages') {
|
|
235
238
|
if (entryKey.startsWith('/')) entryKey = entryKey.slice(1)
|
|
236
239
|
if (entryKey === '') entryKey = 'main'
|
|
237
240
|
if (entryKey.includes('*')) entryKey = 'fallback'
|
|
241
|
+
}
|
|
238
242
|
|
|
239
|
-
|
|
240
|
-
|
|
243
|
+
// Prefer the canonical slash-prefixed route form when there are collisions
|
|
244
|
+
const priority = key === 'pages' && rawEntryKey.startsWith('/') ? 1 : 0
|
|
245
|
+
const existing = normalized.get(entryKey)
|
|
246
|
+
if (!existing || priority > existing.priority) {
|
|
247
|
+
normalized.set(entryKey, { value, priority })
|
|
241
248
|
}
|
|
242
|
-
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const promises = Array.from(normalized.entries()).map(async ([entryKey, info]) => {
|
|
252
|
+
await createOrUpdateFile(dirPath, entryKey, info.value, update)
|
|
253
|
+
dirs.push(entryKey)
|
|
254
|
+
})
|
|
243
255
|
|
|
244
256
|
await Promise.all(promises)
|
|
245
257
|
}
|
|
246
258
|
|
|
247
|
-
|
|
259
|
+
// Ensure deterministic + unique index generation
|
|
260
|
+
await generateIndexjsFile(Array.from(new Set(dirs)), dirPath, key)
|
|
248
261
|
}
|
|
249
262
|
|
|
250
263
|
async function createOrUpdateFile(dirPath, childKey, value, update) {
|
package/bin/push.js
CHANGED
|
@@ -14,6 +14,8 @@ import { computeCoarseChanges, computeOrdersForTuples, preprocessChanges } from
|
|
|
14
14
|
import { showAuthRequiredMessages, showProjectNotFoundMessages, showBuildErrorMessages } from '../helpers/buildMessages.js'
|
|
15
15
|
import { loadSymbolsConfig, resolveDistDir } from '../helpers/symbolsConfig.js'
|
|
16
16
|
import { loadCliConfig, readLock, writeLock, updateLegacySymbolsJson } from '../helpers/config.js'
|
|
17
|
+
import { stripOrderFields } from '../helpers/orderUtils.js'
|
|
18
|
+
import { augmentProjectWithLocalPackageDependencies, findNearestPackageJson } from '../helpers/dependenciesUtils.js'
|
|
17
19
|
|
|
18
20
|
|
|
19
21
|
async function buildLocalProject (distDir) {
|
|
@@ -117,11 +119,16 @@ export async function pushProjectChanges(options) {
|
|
|
117
119
|
resolveDistDir(symbolsConfig) ||
|
|
118
120
|
path.join(process.cwd(), 'smbls')
|
|
119
121
|
|
|
122
|
+
const packageJsonPath = findNearestPackageJson(process.cwd())
|
|
123
|
+
|
|
120
124
|
// Build and load local project
|
|
121
125
|
console.log(chalk.dim('Building local project...'))
|
|
122
126
|
let localProject
|
|
123
127
|
try {
|
|
124
128
|
localProject = await buildLocalProject(distDir)
|
|
129
|
+
localProject = augmentProjectWithLocalPackageDependencies(localProject, packageJsonPath) || localProject
|
|
130
|
+
// Never push `__order` (platform metadata) from local files
|
|
131
|
+
localProject = stripOrderFields(localProject)
|
|
125
132
|
console.log(chalk.gray('Local project built successfully'))
|
|
126
133
|
} catch (buildError) {
|
|
127
134
|
showBuildErrorMessages(buildError)
|
|
@@ -146,8 +153,8 @@ export async function pushProjectChanges(options) {
|
|
|
146
153
|
console.log(chalk.gray('Server state fetched successfully'))
|
|
147
154
|
|
|
148
155
|
// Calculate coarse local changes vs server snapshot (or base)
|
|
149
|
-
const base = normalizeKeys(serverProject || {})
|
|
150
|
-
const changes = computeCoarseChanges(base, localProject)
|
|
156
|
+
const base = normalizeKeys(stripOrderFields(serverProject || {}))
|
|
157
|
+
const changes = computeCoarseChanges(base, stripOrderFields(localProject))
|
|
151
158
|
|
|
152
159
|
if (!changes.length) {
|
|
153
160
|
console.log(chalk.bold.yellow('\nNo changes to push'))
|
|
@@ -178,7 +185,7 @@ export async function pushProjectChanges(options) {
|
|
|
178
185
|
const operationId = `cli-${Date.now()}`
|
|
179
186
|
// Derive granular changes against server base and compute orders using local for pending children
|
|
180
187
|
const { granularChanges } = preprocessChanges(base, changes)
|
|
181
|
-
const orders = computeOrdersForTuples(localProject, granularChanges)
|
|
188
|
+
const orders = computeOrdersForTuples(stripOrderFields(localProject), granularChanges)
|
|
182
189
|
const result = await postProjectChanges(projectId, authToken, {
|
|
183
190
|
branch,
|
|
184
191
|
type,
|
package/bin/sync.js
CHANGED
|
@@ -16,6 +16,13 @@ import { createFs } from './fs.js'
|
|
|
16
16
|
import { showAuthRequiredMessages, showBuildErrorMessages } from '../helpers/buildMessages.js'
|
|
17
17
|
import { loadSymbolsConfig, resolveDistDir } from '../helpers/symbolsConfig.js'
|
|
18
18
|
import { loadCliConfig, readLock, writeLock, getConfigPaths, updateLegacySymbolsJson } from '../helpers/config.js'
|
|
19
|
+
import { stripOrderFields } from '../helpers/orderUtils.js'
|
|
20
|
+
import {
|
|
21
|
+
augmentProjectWithLocalPackageDependencies,
|
|
22
|
+
ensureSchemaDependencies,
|
|
23
|
+
findNearestPackageJson,
|
|
24
|
+
syncPackageJsonDependencies
|
|
25
|
+
} from '../helpers/dependenciesUtils.js'
|
|
19
26
|
|
|
20
27
|
async function buildLocalProject(distDir) {
|
|
21
28
|
try {
|
|
@@ -167,6 +174,8 @@ export async function syncProjectChanges(options) {
|
|
|
167
174
|
resolveDistDir(symbolsConfig) ||
|
|
168
175
|
path.join(process.cwd(), 'smbls')
|
|
169
176
|
|
|
177
|
+
const packageJsonPath = findNearestPackageJson(process.cwd())
|
|
178
|
+
|
|
170
179
|
if (options.verbose) {
|
|
171
180
|
console.log(chalk.dim('\nSync configuration:'))
|
|
172
181
|
console.log(chalk.gray(`App Key: ${chalk.cyan(appKey)}`))
|
|
@@ -182,6 +191,10 @@ export async function syncProjectChanges(options) {
|
|
|
182
191
|
let localProject
|
|
183
192
|
try {
|
|
184
193
|
localProject = await buildLocalProject(distDir)
|
|
194
|
+
// Include local package.json dependencies into the project object for diffing/sync
|
|
195
|
+
localProject = augmentProjectWithLocalPackageDependencies(localProject, packageJsonPath) || localProject
|
|
196
|
+
// Never sync/persist `__order` (platform metadata)
|
|
197
|
+
localProject = stripOrderFields(localProject)
|
|
185
198
|
console.log(chalk.gray('Local project built successfully'))
|
|
186
199
|
} catch (buildError) {
|
|
187
200
|
showBuildErrorMessages(buildError)
|
|
@@ -192,7 +205,7 @@ export async function syncProjectChanges(options) {
|
|
|
192
205
|
const baseSnapshot = (() => {
|
|
193
206
|
try {
|
|
194
207
|
const raw = fs.readFileSync(projectPath, 'utf8')
|
|
195
|
-
return JSON.parse(raw)
|
|
208
|
+
return stripOrderFields(JSON.parse(raw))
|
|
196
209
|
} catch (_) {
|
|
197
210
|
return {}
|
|
198
211
|
}
|
|
@@ -208,10 +221,15 @@ export async function syncProjectChanges(options) {
|
|
|
208
221
|
const serverProject = serverResp.data || {}
|
|
209
222
|
console.log(chalk.gray('Server data fetched successfully'))
|
|
210
223
|
|
|
224
|
+
// Ensure schema.dependencies exists wherever dependencies exist (base/remote/local)
|
|
225
|
+
ensureSchemaDependencies(baseSnapshot)
|
|
226
|
+
ensureSchemaDependencies(serverProject)
|
|
227
|
+
ensureSchemaDependencies(localProject)
|
|
228
|
+
|
|
211
229
|
// Generate coarse local and remote changes via simple three-way rebase
|
|
212
230
|
const base = normalizeKeys(baseSnapshot || {})
|
|
213
|
-
const local = normalizeKeys(localProject || {})
|
|
214
|
-
const remote = normalizeKeys(serverProject || {})
|
|
231
|
+
const local = normalizeKeys(stripOrderFields(localProject || {}))
|
|
232
|
+
const remote = normalizeKeys(stripOrderFields(serverProject || {}))
|
|
215
233
|
const { ours, theirs, conflicts, finalChanges } = threeWayRebase(base, local, remote)
|
|
216
234
|
|
|
217
235
|
const localChanges = ours
|
|
@@ -290,9 +308,17 @@ export async function syncProjectChanges(options) {
|
|
|
290
308
|
)
|
|
291
309
|
const updatedServerData = updated?.data || {}
|
|
292
310
|
|
|
311
|
+
// Ensure fetched snapshot has dependency schema and sync deps into local package.json
|
|
312
|
+
try {
|
|
313
|
+
ensureSchemaDependencies(updatedServerData)
|
|
314
|
+
if (packageJsonPath && updatedServerData?.dependencies) {
|
|
315
|
+
syncPackageJsonDependencies(packageJsonPath, updatedServerData.dependencies, { overwriteExisting: true })
|
|
316
|
+
}
|
|
317
|
+
} catch (_) {}
|
|
318
|
+
|
|
293
319
|
// Apply changes to local files
|
|
294
320
|
console.log(chalk.dim('Updating local files...'))
|
|
295
|
-
await createFs(updatedServerData, distDir, { update: true, metadata: false })
|
|
321
|
+
await createFs(stripOrderFields(updatedServerData), distDir, { update: true, metadata: false })
|
|
296
322
|
console.log(chalk.gray('Local files updated successfully'))
|
|
297
323
|
|
|
298
324
|
console.log(chalk.bold.green('\nProject synced successfully!'))
|
|
@@ -308,7 +334,7 @@ export async function syncProjectChanges(options) {
|
|
|
308
334
|
})
|
|
309
335
|
try {
|
|
310
336
|
const { projectPath } = getConfigPaths()
|
|
311
|
-
await fs.promises.writeFile(projectPath, JSON.stringify(updatedServerData, null, 2))
|
|
337
|
+
await fs.promises.writeFile(projectPath, JSON.stringify(stripOrderFields(updatedServerData), null, 2))
|
|
312
338
|
} catch (_) {}
|
|
313
339
|
|
|
314
340
|
} catch (error) {
|
package/helpers/changesUtils.js
CHANGED
|
@@ -10,6 +10,9 @@ export const DATA_KEYS = [
|
|
|
10
10
|
]
|
|
11
11
|
|
|
12
12
|
const SCHEMA_CODE_TYPES = new Set(['pages', 'components', 'functions', 'methods', 'snippets'])
|
|
13
|
+
// Types that should auto-create a schema entry when a new key is added.
|
|
14
|
+
// NOTE: dependencies schema objects are not "code", but we still want them present for transport.
|
|
15
|
+
const SCHEMA_AUTO_CREATE_TYPES = new Set([...SCHEMA_CODE_TYPES, 'dependencies'])
|
|
13
16
|
|
|
14
17
|
function stripMetaDeep(val) {
|
|
15
18
|
if (Array.isArray(val)) {
|
|
@@ -103,7 +106,7 @@ export function computeCoarseChanges(base, local, keys = DATA_KEYS) {
|
|
|
103
106
|
// New item
|
|
104
107
|
changes.push(['update', [typeKey, itemKey], bVal])
|
|
105
108
|
const hadSchema = aSchemaSection && Object.prototype.hasOwnProperty.call(aSchemaSection, itemKey)
|
|
106
|
-
if (
|
|
109
|
+
if (SCHEMA_AUTO_CREATE_TYPES.has(typeKey) && !hadSchema) {
|
|
107
110
|
const schemaItem = buildSchemaItemFromData(typeKey, itemKey, bVal)
|
|
108
111
|
if (schemaItem) {
|
|
109
112
|
changes.push(['update', ['schema', typeKey, itemKey], schemaItem])
|
|
@@ -112,8 +115,10 @@ export function computeCoarseChanges(base, local, keys = DATA_KEYS) {
|
|
|
112
115
|
} else if (!equal(aVal, bVal)) {
|
|
113
116
|
// Updated item
|
|
114
117
|
changes.push(['update', [typeKey, itemKey], bVal])
|
|
115
|
-
// When
|
|
116
|
-
|
|
118
|
+
// When a code-backed item changes, drop its schema.code to be regenerated
|
|
119
|
+
if (SCHEMA_CODE_TYPES.has(typeKey)) {
|
|
120
|
+
changes.push(['delete', ['schema', typeKey, itemKey, 'code']])
|
|
121
|
+
}
|
|
117
122
|
}
|
|
118
123
|
}
|
|
119
124
|
}
|
|
@@ -277,6 +282,20 @@ export function buildSchemaCodeFromObject(obj) {
|
|
|
277
282
|
function buildSchemaItemFromData(type, key, value) {
|
|
278
283
|
const schemaType = type
|
|
279
284
|
|
|
285
|
+
if (schemaType === 'dependencies') {
|
|
286
|
+
const version =
|
|
287
|
+
typeof value === 'string' && value.length
|
|
288
|
+
? value
|
|
289
|
+
: (value && typeof value === 'object' && typeof value.version === 'string' ? value.version : 'latest')
|
|
290
|
+
return {
|
|
291
|
+
key,
|
|
292
|
+
resolvedVersion: version,
|
|
293
|
+
type: 'dependency',
|
|
294
|
+
version,
|
|
295
|
+
status: 'done'
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
280
299
|
const base = {
|
|
281
300
|
title: key,
|
|
282
301
|
key,
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
|
|
4
|
+
function isPlainObject(val) {
|
|
5
|
+
return !!val && typeof val === 'object' && !Array.isArray(val)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function findNearestPackageJson(startDir = process.cwd()) {
|
|
9
|
+
let cur = path.resolve(startDir)
|
|
10
|
+
for (let i = 0; i < 50; i++) {
|
|
11
|
+
const candidate = path.join(cur, 'package.json')
|
|
12
|
+
if (fs.existsSync(candidate)) return candidate
|
|
13
|
+
const parent = path.dirname(cur)
|
|
14
|
+
if (parent === cur) break
|
|
15
|
+
cur = parent
|
|
16
|
+
}
|
|
17
|
+
return null
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function readPackageJson(packageJsonPath) {
|
|
21
|
+
try {
|
|
22
|
+
const raw = fs.readFileSync(packageJsonPath, 'utf8')
|
|
23
|
+
const parsed = JSON.parse(raw)
|
|
24
|
+
return isPlainObject(parsed) ? parsed : null
|
|
25
|
+
} catch (_) {
|
|
26
|
+
return null
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function writePackageJson(packageJsonPath, json) {
|
|
31
|
+
try {
|
|
32
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(json, null, 2) + '\n')
|
|
33
|
+
return true
|
|
34
|
+
} catch (_) {
|
|
35
|
+
return false
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function getPackageDependencies(packageJsonPath) {
|
|
40
|
+
const pkg = readPackageJson(packageJsonPath)
|
|
41
|
+
if (!pkg) return {}
|
|
42
|
+
const deps = isPlainObject(pkg.dependencies) ? pkg.dependencies : {}
|
|
43
|
+
return { ...deps }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function sortObjectKeys(obj) {
|
|
47
|
+
if (!isPlainObject(obj)) return obj
|
|
48
|
+
const out = {}
|
|
49
|
+
Object.keys(obj).sort().forEach((k) => {
|
|
50
|
+
out[k] = obj[k]
|
|
51
|
+
})
|
|
52
|
+
return out
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Merge dependency map into package.json dependencies.
|
|
57
|
+
* - If overwriteExisting is true, remote versions win.
|
|
58
|
+
* - If false, only missing deps are added.
|
|
59
|
+
*/
|
|
60
|
+
export function syncPackageJsonDependencies(packageJsonPath, depsMap, { overwriteExisting = true } = {}) {
|
|
61
|
+
if (!packageJsonPath) return { ok: false, reason: 'missing_package_json_path' }
|
|
62
|
+
const pkg = readPackageJson(packageJsonPath)
|
|
63
|
+
if (!pkg) return { ok: false, reason: 'invalid_package_json' }
|
|
64
|
+
if (!isPlainObject(depsMap) || !Object.keys(depsMap).length) {
|
|
65
|
+
return { ok: true, changed: false }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const existing = isPlainObject(pkg.dependencies) ? { ...pkg.dependencies } : {}
|
|
69
|
+
let changed = false
|
|
70
|
+
|
|
71
|
+
for (const [name, ver] of Object.entries(depsMap)) {
|
|
72
|
+
if (typeof name !== 'string' || !name) continue
|
|
73
|
+
if (typeof ver !== 'string' || !ver) continue
|
|
74
|
+
if (!Object.prototype.hasOwnProperty.call(existing, name)) {
|
|
75
|
+
existing[name] = ver
|
|
76
|
+
changed = true
|
|
77
|
+
continue
|
|
78
|
+
}
|
|
79
|
+
if (overwriteExisting && existing[name] !== ver) {
|
|
80
|
+
existing[name] = ver
|
|
81
|
+
changed = true
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (!changed) return { ok: true, changed: false }
|
|
86
|
+
pkg.dependencies = sortObjectKeys(existing)
|
|
87
|
+
const ok = writePackageJson(packageJsonPath, pkg)
|
|
88
|
+
return { ok, changed: ok }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function ensureSchemaContainer(project) {
|
|
92
|
+
if (!project || typeof project !== 'object') return null
|
|
93
|
+
if (!isPlainObject(project.schema)) project.schema = {}
|
|
94
|
+
return project.schema
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function ensureSchemaDependencies(project) {
|
|
98
|
+
if (!project || typeof project !== 'object') return project
|
|
99
|
+
const deps = isPlainObject(project.dependencies) ? project.dependencies : null
|
|
100
|
+
if (!deps) return project
|
|
101
|
+
|
|
102
|
+
const schema = ensureSchemaContainer(project)
|
|
103
|
+
if (!isPlainObject(schema.dependencies)) schema.dependencies = {}
|
|
104
|
+
|
|
105
|
+
for (const [name, ver] of Object.entries(deps)) {
|
|
106
|
+
if (typeof name !== 'string' || !name) continue
|
|
107
|
+
const version = typeof ver === 'string' && ver.length ? ver : 'latest'
|
|
108
|
+
const existing = schema.dependencies[name]
|
|
109
|
+
if (isPlainObject(existing)) {
|
|
110
|
+
if (typeof existing.key !== 'string') existing.key = name
|
|
111
|
+
if (typeof existing.type !== 'string') existing.type = 'dependency'
|
|
112
|
+
if (typeof existing.version !== 'string') existing.version = version
|
|
113
|
+
if (typeof existing.resolvedVersion !== 'string') existing.resolvedVersion = version
|
|
114
|
+
if (typeof existing.status !== 'string') existing.status = 'done'
|
|
115
|
+
} else {
|
|
116
|
+
schema.dependencies[name] = {
|
|
117
|
+
key: name,
|
|
118
|
+
resolvedVersion: version,
|
|
119
|
+
type: 'dependency',
|
|
120
|
+
version,
|
|
121
|
+
status: 'done'
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return project
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Augment a project object with local package.json dependencies so sync/collab
|
|
130
|
+
* can detect and push dependency additions/updates.
|
|
131
|
+
*/
|
|
132
|
+
export function augmentProjectWithLocalPackageDependencies(project, packageJsonPath) {
|
|
133
|
+
if (!project || typeof project !== 'object') return project
|
|
134
|
+
if (!packageJsonPath) return project
|
|
135
|
+
const pkgDeps = getPackageDependencies(packageJsonPath)
|
|
136
|
+
if (!Object.keys(pkgDeps).length) return project
|
|
137
|
+
|
|
138
|
+
// Some build outputs expose getters-only exports (e.g. Babel interop / CJS wrappers).
|
|
139
|
+
// Avoid mutating such objects by cloning into a plain, extensible object.
|
|
140
|
+
let target = project
|
|
141
|
+
try {
|
|
142
|
+
const desc = Object.getOwnPropertyDescriptor(target, 'dependencies')
|
|
143
|
+
const canAssign =
|
|
144
|
+
!desc ||
|
|
145
|
+
desc.writable === true ||
|
|
146
|
+
typeof desc.set === 'function'
|
|
147
|
+
if (!canAssign || !Object.isExtensible(target)) {
|
|
148
|
+
target = { ...target }
|
|
149
|
+
}
|
|
150
|
+
} catch (_) {
|
|
151
|
+
target = { ...target }
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const existing = isPlainObject(target.dependencies) ? target.dependencies : {}
|
|
155
|
+
const merged = { ...existing, ...pkgDeps }
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
target.dependencies = merged
|
|
159
|
+
} catch (_) {
|
|
160
|
+
try {
|
|
161
|
+
Object.defineProperty(target, 'dependencies', {
|
|
162
|
+
value: merged,
|
|
163
|
+
enumerable: true,
|
|
164
|
+
configurable: true,
|
|
165
|
+
writable: true
|
|
166
|
+
})
|
|
167
|
+
} catch (_) {}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
ensureSchemaDependencies(target)
|
|
171
|
+
return target
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strip Symbols ordering metadata (`__order`) from a project object.
|
|
3
|
+
*
|
|
4
|
+
* We treat `__order` as platform/transport metadata and avoid persisting it
|
|
5
|
+
* into local repositories (generated files or base snapshots).
|
|
6
|
+
*
|
|
7
|
+
* - Preserves functions and non-plain objects as-is
|
|
8
|
+
* - Clones arrays and plain objects
|
|
9
|
+
* - Handles cycles via WeakMap
|
|
10
|
+
*/
|
|
11
|
+
export function stripOrderFields(input) {
|
|
12
|
+
const seen = new WeakMap()
|
|
13
|
+
|
|
14
|
+
function isPlainObject(v) {
|
|
15
|
+
return Object.prototype.toString.call(v) === '[object Object]'
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function walk(value) {
|
|
19
|
+
if (Array.isArray(value)) {
|
|
20
|
+
if (seen.has(value)) return seen.get(value)
|
|
21
|
+
const out = new Array(value.length)
|
|
22
|
+
seen.set(value, out)
|
|
23
|
+
for (let i = 0; i < value.length; i++) out[i] = walk(value[i])
|
|
24
|
+
return out
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (value && typeof value === 'object') {
|
|
28
|
+
// Keep non-plain objects (Date, Map, etc.) as-is
|
|
29
|
+
if (!isPlainObject(value)) return value
|
|
30
|
+
if (seen.has(value)) return seen.get(value)
|
|
31
|
+
const out = {}
|
|
32
|
+
seen.set(value, out)
|
|
33
|
+
for (const k of Object.keys(value)) {
|
|
34
|
+
if (k === '__order') continue
|
|
35
|
+
out[k] = walk(value[k])
|
|
36
|
+
}
|
|
37
|
+
return out
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return value
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return walk(input)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@symbo.ls/cli",
|
|
3
|
-
"version": "2.33.
|
|
3
|
+
"version": "2.33.36",
|
|
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.36",
|
|
19
|
+
"@symbo.ls/init": "^2.33.36",
|
|
20
|
+
"@symbo.ls/socket": "^2.33.36",
|
|
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": "c49777e3aee153b8b68f55a2aa9a3a5988483e2f"
|
|
32
32
|
}
|