@retailcrm/embed-ui 0.9.21 → 0.9.22-alpha.2

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.
@@ -1,617 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { createInterface } from 'node:readline/promises'
4
- import { execFileSync } from 'node:child_process'
5
- import fs from 'node:fs'
6
- import path from 'node:path'
7
- import { pathToFileURL } from 'node:url'
8
- import process from 'node:process'
9
-
10
- export const ROOT_PACKAGE = '@retailcrm/embed-ui'
11
- export const TARGET_SECTIONS = [
12
- 'dependencies',
13
- 'devDependencies',
14
- 'peerDependencies',
15
- 'optionalDependencies',
16
- ]
17
-
18
- export const INSTALLABLE_PACKAGES = [
19
- {
20
- id: 'embed-ui',
21
- name: ROOT_PACKAGE,
22
- section: 'dependencies',
23
- description: 'Базовый пакет с общим API и согласованными v1-зависимостями.',
24
- },
25
- {
26
- id: 'components',
27
- name: '@retailcrm/embed-ui-v1-components',
28
- section: 'dependencies',
29
- description: 'UI-компоненты для host/remote приложений.',
30
- },
31
- {
32
- id: 'contexts',
33
- name: '@retailcrm/embed-ui-v1-contexts',
34
- section: 'dependencies',
35
- description: 'Реактивные контексты RetailCRM JS API.',
36
- },
37
- {
38
- id: 'types',
39
- name: '@retailcrm/embed-ui-v1-types',
40
- section: 'dependencies',
41
- description: 'Базовые type declarations для RetailCRM JS API.',
42
- },
43
- {
44
- id: 'testing',
45
- name: '@retailcrm/embed-ui-v1-testing',
46
- section: 'devDependencies',
47
- description: 'Вспомогательные утилиты и типы для тестов интеграций.',
48
- },
49
- {
50
- id: 'endpoint',
51
- name: '@retailcrm/embed-ui-v1-endpoint',
52
- section: 'dependencies',
53
- description: 'Endpoint API для интеграций в RetailCRM.',
54
- },
55
- ]
56
-
57
- const DEFAULT_INDENT = ' '
58
- const DEFAULT_NEWLINE = '\n'
59
- const SKIP_DIRECTORIES = new Set([
60
- '.git',
61
- '.hg',
62
- '.svn',
63
- '.yarn',
64
- 'node_modules',
65
- 'dist',
66
- 'build',
67
- 'coverage',
68
- ])
69
-
70
- const HELP_TEXT = `Usage:
71
- npx @retailcrm/embed-ui [target] [version] [options]
72
-
73
- Options:
74
- -t, --target <path> Target path (default: current directory)
75
- -v, --version <ver> Target version. If omitted, latest npm version is used
76
- --exact Use exact version instead of range
77
- --dry-run Show changes without writing package.json
78
- --add Add selected embed-ui packages into one package.json
79
- --packages <list> Comma-separated package ids or names for --add
80
- -h, --help Show this help
81
-
82
- Examples:
83
- npx @retailcrm/embed-ui
84
- npx @retailcrm/embed-ui --version 0.9.11
85
- npx @retailcrm/embed-ui ./my-project 0.9.11
86
- npx @retailcrm/embed-ui --target ./my-project --dry-run
87
- npx @retailcrm/embed-ui --add
88
- npx @retailcrm/embed-ui --add --packages components,contexts
89
- `
90
-
91
- const isSemverLike = (value) => /^v?\d+\.\d+\.\d+/.test(value)
92
- const stripLeadingV = (value) => value.replace(/^v/, '')
93
-
94
- const parsePackageList = (value) =>
95
- value
96
- .split(',')
97
- .map((entry) => entry.trim())
98
- .filter(Boolean)
99
-
100
- export const parseArgs = (argv) => {
101
- const options = {
102
- target: process.cwd(),
103
- version: null,
104
- dryRun: false,
105
- exact: false,
106
- add: false,
107
- packages: null,
108
- }
109
-
110
- const positionals = []
111
-
112
- for (let index = 0; index < argv.length; index++) {
113
- const argument = argv[index]
114
-
115
- if (argument === '-h' || argument === '--help') {
116
- console.log(HELP_TEXT)
117
- process.exit(0)
118
- }
119
-
120
- if (argument === '-t' || argument === '--target') {
121
- const value = argv[index + 1]
122
- if (!value) {
123
- throw new Error('Option --target requires a value')
124
- }
125
-
126
- options.target = path.resolve(process.cwd(), value)
127
- index++
128
- continue
129
- }
130
-
131
- if (argument === '-v' || argument === '--version') {
132
- const value = argv[index + 1]
133
- if (!value) {
134
- throw new Error('Option --version requires a value')
135
- }
136
-
137
- options.version = stripLeadingV(value)
138
- index++
139
- continue
140
- }
141
-
142
- if (argument === '--packages') {
143
- const value = argv[index + 1]
144
- if (!value) {
145
- throw new Error('Option --packages requires a value')
146
- }
147
-
148
- options.packages = parsePackageList(value)
149
- index++
150
- continue
151
- }
152
-
153
- if (argument === '--dry-run') {
154
- options.dryRun = true
155
- continue
156
- }
157
-
158
- if (argument === '--exact') {
159
- options.exact = true
160
- continue
161
- }
162
-
163
- if (argument === '--add') {
164
- options.add = true
165
- continue
166
- }
167
-
168
- if (argument.startsWith('-')) {
169
- throw new Error(`Unknown option: ${argument}`)
170
- }
171
-
172
- positionals.push(argument)
173
- }
174
-
175
- if (positionals.length > 2) {
176
- throw new Error('Too many positional arguments')
177
- }
178
-
179
- if (positionals.length >= 1) {
180
- const first = positionals[0]
181
- if (!options.version && isSemverLike(first)) {
182
- options.version = stripLeadingV(first)
183
- } else {
184
- options.target = path.resolve(process.cwd(), first)
185
- }
186
- }
187
-
188
- if (positionals.length === 2) {
189
- if (options.version) {
190
- throw new Error('Version is already specified')
191
- }
192
-
193
- options.version = stripLeadingV(positionals[1])
194
- }
195
-
196
- if (options.packages && !options.add) {
197
- throw new Error('Option --packages can only be used together with --add')
198
- }
199
-
200
- return options
201
- }
202
-
203
- export const resolveLatestVersion = () => {
204
- const output = execFileSync(
205
- 'npm',
206
- ['view', ROOT_PACKAGE, 'version'],
207
- {
208
- encoding: 'utf8',
209
- stdio: ['ignore', 'pipe', 'pipe'],
210
- }
211
- ).trim()
212
-
213
- if (!output) {
214
- throw new Error(`Cannot resolve latest version for ${ROOT_PACKAGE}`)
215
- }
216
-
217
- return output
218
- }
219
-
220
- export const isTargetPackage = (name) =>
221
- name === ROOT_PACKAGE || name.startsWith(`${ROOT_PACKAGE}-`)
222
-
223
- const createRange = (version, exact) => exact ? version : `^${version}`
224
-
225
- export const formatRange = (currentRange, nextVersion, exact) => {
226
- if (exact) {
227
- return nextVersion
228
- }
229
-
230
- if (currentRange.startsWith('workspace:')) {
231
- return currentRange
232
- }
233
-
234
- if (currentRange.startsWith('~')) {
235
- return `~${nextVersion}`
236
- }
237
-
238
- if (currentRange.startsWith('^')) {
239
- return `^${nextVersion}`
240
- }
241
-
242
- return `^${nextVersion}`
243
- }
244
-
245
- export const detectFormatting = (source) => {
246
- const newline = source.includes('\r\n') ? '\r\n' : DEFAULT_NEWLINE
247
- const indentMatch = source.match(/\n([ \t]+)"/)
248
-
249
- return {
250
- indent: indentMatch?.[1] ?? DEFAULT_INDENT,
251
- newline,
252
- trailingNewline: source.endsWith('\n') || source.endsWith('\r\n'),
253
- }
254
- }
255
-
256
- export const serializePackageJson = (packageJson, formatting) => {
257
- const serialized = JSON.stringify(packageJson, null, formatting.indent)
258
- .replace(/\n/g, formatting.newline)
259
-
260
- return formatting.trailingNewline
261
- ? `${serialized}${formatting.newline}`
262
- : serialized
263
- }
264
-
265
- const ensureDirectoryExists = (targetPath) => {
266
- if (!fs.existsSync(targetPath)) {
267
- throw new Error(`Path not found: ${targetPath}`)
268
- }
269
-
270
- const stat = fs.statSync(targetPath)
271
- if (!stat.isDirectory()) {
272
- throw new Error(`Target is not a directory: ${targetPath}`)
273
- }
274
- }
275
-
276
- export const resolvePackageJsonPath = (targetPath) => {
277
- if (path.basename(targetPath) === 'package.json') {
278
- if (!fs.existsSync(targetPath)) {
279
- throw new Error(`package.json not found: ${targetPath}`)
280
- }
281
-
282
- return targetPath
283
- }
284
-
285
- const packageJsonPath = path.resolve(targetPath, 'package.json')
286
-
287
- if (!fs.existsSync(packageJsonPath)) {
288
- throw new Error(`package.json not found: ${packageJsonPath}`)
289
- }
290
-
291
- return packageJsonPath
292
- }
293
-
294
- export const collectPackageJsonPaths = (targetPath) => {
295
- const resolvedTarget = path.resolve(targetPath)
296
-
297
- if (!fs.existsSync(resolvedTarget)) {
298
- throw new Error(`Path not found: ${resolvedTarget}`)
299
- }
300
-
301
- if (path.basename(resolvedTarget) === 'package.json') {
302
- return [resolvedTarget]
303
- }
304
-
305
- ensureDirectoryExists(resolvedTarget)
306
-
307
- const packageJsonPaths = []
308
-
309
- const visit = (directoryPath) => {
310
- const packageJsonPath = path.join(directoryPath, 'package.json')
311
- if (fs.existsSync(packageJsonPath) && fs.statSync(packageJsonPath).isFile()) {
312
- packageJsonPaths.push(packageJsonPath)
313
- }
314
-
315
- for (const entry of fs.readdirSync(directoryPath, { withFileTypes: true })) {
316
- if (!entry.isDirectory() || entry.isSymbolicLink()) {
317
- continue
318
- }
319
-
320
- if (SKIP_DIRECTORIES.has(entry.name)) {
321
- continue
322
- }
323
-
324
- visit(path.join(directoryPath, entry.name))
325
- }
326
- }
327
-
328
- visit(resolvedTarget)
329
-
330
- return packageJsonPaths.sort()
331
- }
332
-
333
- const findDependencySection = (packageJson, packageName) => {
334
- for (const section of TARGET_SECTIONS) {
335
- const dependencyMap = packageJson[section]
336
-
337
- if (dependencyMap && typeof dependencyMap === 'object' && packageName in dependencyMap) {
338
- return section
339
- }
340
- }
341
-
342
- return null
343
- }
344
-
345
- export const updatePackageJson = (packageJson, version, exact) => {
346
- const updates = []
347
-
348
- for (const section of TARGET_SECTIONS) {
349
- const dependencyMap = packageJson[section]
350
-
351
- if (!dependencyMap || typeof dependencyMap !== 'object') {
352
- continue
353
- }
354
-
355
- for (const [name, currentRange] of Object.entries(dependencyMap)) {
356
- if (!isTargetPackage(name) || typeof currentRange !== 'string') {
357
- continue
358
- }
359
-
360
- const nextRange = formatRange(currentRange, version, exact)
361
- if (nextRange === currentRange) {
362
- continue
363
- }
364
-
365
- dependencyMap[name] = nextRange
366
- updates.push({
367
- type: 'update',
368
- section,
369
- name,
370
- currentRange,
371
- nextRange,
372
- })
373
- }
374
- }
375
-
376
- return updates
377
- }
378
-
379
- export const resolveInstallPackages = (tokens) => {
380
- const selectedPackages = []
381
- const seen = new Set()
382
-
383
- for (const token of tokens) {
384
- const normalized = token.trim()
385
- if (!normalized) {
386
- continue
387
- }
388
-
389
- const numericIndex = Number(normalized)
390
- const selectedPackage =
391
- Number.isInteger(numericIndex) && numericIndex >= 1 && numericIndex <= INSTALLABLE_PACKAGES.length
392
- ? INSTALLABLE_PACKAGES[numericIndex - 1]
393
- : INSTALLABLE_PACKAGES.find((entry) => entry.id === normalized || entry.name === normalized)
394
-
395
- if (!selectedPackage) {
396
- const supported = INSTALLABLE_PACKAGES
397
- .map((entry, index) => `${index + 1}/${entry.id}/${entry.name}`)
398
- .join(', ')
399
-
400
- throw new Error(`Unknown add target "${normalized}". Supported values: ${supported}`)
401
- }
402
-
403
- if (seen.has(selectedPackage.name)) {
404
- continue
405
- }
406
-
407
- seen.add(selectedPackage.name)
408
- selectedPackages.push(selectedPackage)
409
- }
410
-
411
- return selectedPackages
412
- }
413
-
414
- export const installPackages = (packageJson, packages, version, exact) => {
415
- const updates = []
416
-
417
- for (const selectedPackage of packages) {
418
- const section = findDependencySection(packageJson, selectedPackage.name) ?? selectedPackage.section
419
- const dependencyMap = packageJson[section] ?? {}
420
-
421
- if (!(section in packageJson)) {
422
- packageJson[section] = dependencyMap
423
- }
424
-
425
- const currentRange = dependencyMap[selectedPackage.name]
426
- const nextRange = typeof currentRange === 'string'
427
- ? formatRange(currentRange, version, exact)
428
- : createRange(version, exact)
429
-
430
- if (currentRange === nextRange) {
431
- continue
432
- }
433
-
434
- dependencyMap[selectedPackage.name] = nextRange
435
- updates.push({
436
- type: typeof currentRange === 'string' ? 'update' : 'install',
437
- section,
438
- name: selectedPackage.name,
439
- currentRange: typeof currentRange === 'string' ? currentRange : null,
440
- nextRange,
441
- })
442
- }
443
-
444
- return updates
445
- }
446
-
447
- export const promptForInstallSelection = async (packageJson) => {
448
- if (!process.stdin.isTTY || !process.stdout.isTTY) {
449
- throw new Error('Interactive add mode requires a TTY. Use --packages to select packages explicitly.')
450
- }
451
-
452
- console.log('Выберите пакеты для установки в текущий package.json:')
453
- for (const [index, selectedPackage] of INSTALLABLE_PACKAGES.entries()) {
454
- const currentSection = findDependencySection(packageJson, selectedPackage.name)
455
- const installedHint = currentSection ? ` Уже есть в ${currentSection}.` : ''
456
-
457
- console.log(` ${index + 1}. ${selectedPackage.name} (${selectedPackage.id})`)
458
- console.log(` ${selectedPackage.description} Раздел по умолчанию: ${selectedPackage.section}.${installedHint}`)
459
- }
460
-
461
- const readline = createInterface({
462
- input: process.stdin,
463
- output: process.stdout,
464
- })
465
-
466
- try {
467
- while (true) {
468
- const answer = await readline.question(
469
- 'Введите номера, ids или имена пакетов через запятую (например: 1,3 или components,types): '
470
- )
471
-
472
- const tokens = parsePackageList(answer)
473
- if (tokens.length === 0) {
474
- return []
475
- }
476
-
477
- try {
478
- return resolveInstallPackages(tokens)
479
- } catch (error) {
480
- const message = error instanceof Error ? error.message : String(error)
481
- console.error(message)
482
- }
483
- }
484
- } finally {
485
- readline.close()
486
- }
487
- }
488
-
489
- const readPackageJson = (packageJsonPath) => {
490
- const source = fs.readFileSync(packageJsonPath, 'utf8')
491
-
492
- return {
493
- formatting: detectFormatting(source),
494
- packageJson: JSON.parse(source),
495
- }
496
- }
497
-
498
- const writePackageJson = (packageJsonPath, packageJson, formatting) => {
499
- fs.writeFileSync(packageJsonPath, serializePackageJson(packageJson, formatting), 'utf8')
500
- }
501
-
502
- const printChanges = (changes) => {
503
- for (const change of changes) {
504
- const prefix = change.type === 'install'
505
- ? `${change.section}: ${change.name} -> ${change.nextRange}`
506
- : `${change.section}: ${change.name} ${change.currentRange} -> ${change.nextRange}`
507
-
508
- console.log(` ${prefix}`)
509
- }
510
- }
511
-
512
- export const runUpdate = (options) => {
513
- const version = options.version ?? resolveLatestVersion()
514
- const packageJsonPaths = collectPackageJsonPaths(options.target)
515
- const reports = []
516
-
517
- for (const packageJsonPath of packageJsonPaths) {
518
- const { formatting, packageJson } = readPackageJson(packageJsonPath)
519
- const updates = updatePackageJson(packageJson, version, options.exact)
520
-
521
- if (updates.length === 0) {
522
- continue
523
- }
524
-
525
- if (!options.dryRun) {
526
- writePackageJson(packageJsonPath, packageJson, formatting)
527
- }
528
-
529
- reports.push({ packageJsonPath, updates })
530
- }
531
-
532
- if (reports.length === 0) {
533
- console.log(`No ${ROOT_PACKAGE}* dependencies found or changed under ${path.resolve(options.target)}`)
534
- return
535
- }
536
-
537
- const totalUpdates = reports.reduce((sum, report) => sum + report.updates.length, 0)
538
-
539
- console.log(`Resolved version: ${version}`)
540
- for (const report of reports) {
541
- console.log(report.packageJsonPath)
542
- printChanges(report.updates)
543
- }
544
-
545
- if (options.dryRun) {
546
- console.log('Dry run enabled, package.json files were not modified')
547
- return
548
- }
549
-
550
- console.log(
551
- `Updated ${totalUpdates} dependency entries in ${reports.length} package.json file(s) under ${path.resolve(options.target)}`
552
- )
553
- }
554
-
555
- export const runAdd = async (options) => {
556
- const version = options.version ?? resolveLatestVersion()
557
- const packageJsonPath = resolvePackageJsonPath(path.resolve(options.target))
558
- const { formatting, packageJson } = readPackageJson(packageJsonPath)
559
- const selectedPackages = options.packages
560
- ? resolveInstallPackages(options.packages)
561
- : await promptForInstallSelection(packageJson)
562
-
563
- if (selectedPackages.length === 0) {
564
- console.log('Nothing selected, package.json was not modified')
565
- return
566
- }
567
-
568
- const updates = installPackages(packageJson, selectedPackages, version, options.exact)
569
-
570
- if (updates.length === 0) {
571
- console.log(`Selected packages are already installed with matching ranges in ${packageJsonPath}`)
572
- return
573
- }
574
-
575
- console.log(`Resolved version: ${version}`)
576
- console.log(packageJsonPath)
577
- printChanges(updates)
578
-
579
- if (options.dryRun) {
580
- console.log('Dry run enabled, package.json was not modified')
581
- return
582
- }
583
-
584
- writePackageJson(packageJsonPath, packageJson, formatting)
585
- console.log(`Installed ${updates.length} package entries in ${packageJsonPath}`)
586
- }
587
-
588
- export const main = async (argv = process.argv.slice(2)) => {
589
- const options = parseArgs(argv)
590
-
591
- if (options.add) {
592
- await runAdd(options)
593
- return
594
- }
595
-
596
- runUpdate(options)
597
- }
598
-
599
- const isExecutedDirectly = () => {
600
- const entryPath = process.argv[1]
601
-
602
- if (!entryPath) {
603
- return false
604
- }
605
-
606
- return pathToFileURL(path.resolve(entryPath)).href === import.meta.url
607
- }
608
-
609
- if (isExecutedDirectly()) {
610
- try {
611
- await main()
612
- } catch (error) {
613
- const message = error instanceof Error ? error.message : String(error)
614
- console.error(message)
615
- process.exit(1)
616
- }
617
- }