@inspecto-dev/cli 0.2.0-alpha.1 → 0.2.0-alpha.3
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/.turbo/turbo-build.log +20 -19
- package/.turbo/turbo-test.log +4 -4
- package/CHANGELOG.md +22 -0
- package/README.md +5 -5
- package/dist/bin.js +50 -68
- package/dist/{chunk-DBXT75QF.js → chunk-HIL6365F.js} +718 -443
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -1
- package/package.json +3 -2
- package/src/bin.ts +84 -70
- package/src/commands/doctor.ts +38 -25
- package/src/commands/init.ts +107 -228
- package/src/commands/teardown.ts +13 -23
- package/src/detect/build-tool.ts +169 -21
- package/src/detect/framework.ts +71 -9
- package/src/detect/ide.ts +42 -21
- package/src/detect/package-manager.ts +10 -3
- package/src/detect/provider.ts +151 -0
- package/src/inject/ast-injector.ts +70 -231
- package/src/inject/extension.ts +49 -34
- package/src/inject/gitignore.ts +1 -1
- package/src/inject/strategies/esbuild.ts +35 -0
- package/src/inject/strategies/index.ts +16 -0
- package/src/inject/strategies/rollup.ts +35 -0
- package/src/inject/strategies/rsbuild.ts +29 -0
- package/src/inject/strategies/rspack.ts +34 -0
- package/src/inject/strategies/types.ts +35 -0
- package/src/inject/strategies/vite.ts +30 -0
- package/src/inject/strategies/webpack.ts +36 -0
- package/src/instructions.ts +55 -0
- package/src/prompts.ts +115 -0
- package/src/types.ts +4 -1
- package/tests/ide.test.ts +3 -3
- package/src/detect/ai-tool.ts +0 -127
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { addVitePlugin } from 'magicast/helpers'
|
|
2
|
+
import type { InjectStrategy, InjectOptions } from './types.js'
|
|
3
|
+
import type { BuildTool, BuildToolDetection } from '../../types.js'
|
|
4
|
+
|
|
5
|
+
export class ViteStrategy implements InjectStrategy {
|
|
6
|
+
name = 'Vite'
|
|
7
|
+
|
|
8
|
+
supports(tool: BuildTool): boolean {
|
|
9
|
+
return tool === 'vite'
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
inject({ mod, detection }: InjectOptions): void {
|
|
13
|
+
addVitePlugin(mod, {
|
|
14
|
+
from: '@inspecto-dev/plugin',
|
|
15
|
+
constructor: 'vitePlugin',
|
|
16
|
+
})
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
getManualInstructions(detection: BuildToolDetection, reason: string): string[] {
|
|
20
|
+
return [
|
|
21
|
+
`import { vitePlugin as inspecto } from '@inspecto-dev/plugin'`,
|
|
22
|
+
'',
|
|
23
|
+
'// Add to your plugins array:',
|
|
24
|
+
`plugins: [`,
|
|
25
|
+
` process.env.NODE_ENV !== 'production' && inspecto(),`,
|
|
26
|
+
` ...otherPlugins`,
|
|
27
|
+
`].filter(Boolean)`,
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { InjectStrategy, InjectOptions } from './types.js'
|
|
2
|
+
import type { BuildTool, BuildToolDetection } from '../../types.js'
|
|
3
|
+
|
|
4
|
+
export class WebpackStrategy implements InjectStrategy {
|
|
5
|
+
name = 'Webpack'
|
|
6
|
+
|
|
7
|
+
supports(tool: BuildTool): boolean {
|
|
8
|
+
return tool === 'webpack'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
inject(options: InjectOptions): void {
|
|
12
|
+
// AST manipulation for Webpack configs (often CommonJS or complex objects) is brittle in v1
|
|
13
|
+
throw new Error('Webpack requires manual plugin configuration')
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
getManualInstructions(detection: BuildToolDetection, reason: string): string[] {
|
|
17
|
+
const importPkg = detection.isLegacyWebpack
|
|
18
|
+
? '@inspecto-dev/plugin/legacy/webpack4'
|
|
19
|
+
: '@inspecto-dev/plugin'
|
|
20
|
+
const pluginName = detection.isLegacyWebpack ? 'webpack4Plugin' : 'webpackPlugin'
|
|
21
|
+
|
|
22
|
+
const pluginCall = detection.isLegacyWebpack
|
|
23
|
+
? `process.env.NODE_ENV !== 'production' && inspecto({\n pathType: 'absolute',\n escapeTags: ['Transition', 'AnimatePresence'],\n })`
|
|
24
|
+
: `process.env.NODE_ENV !== 'production' && inspecto()`
|
|
25
|
+
|
|
26
|
+
return [
|
|
27
|
+
`import { ${pluginName} as inspecto } from '${importPkg}'`,
|
|
28
|
+
'',
|
|
29
|
+
'// Add to your plugins array:',
|
|
30
|
+
`plugins: [`,
|
|
31
|
+
` ${pluginCall},`,
|
|
32
|
+
` ...otherPlugins`,
|
|
33
|
+
`].filter(Boolean)`,
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { log } from './utils/logger.js'
|
|
2
|
+
|
|
3
|
+
export function printNuxtManualInstructions() {
|
|
4
|
+
log.blank()
|
|
5
|
+
log.hint('To enable Inspecto in Nuxt, update your nuxt.config.ts:')
|
|
6
|
+
console.log(`\x1b[36m
|
|
7
|
+
import { vitePlugin as inspecto } from '@inspecto-dev/plugin'
|
|
8
|
+
export default defineNuxtConfig({
|
|
9
|
+
vite: {
|
|
10
|
+
plugins: [inspecto()]
|
|
11
|
+
}
|
|
12
|
+
})
|
|
13
|
+
\x1b[0m`)
|
|
14
|
+
log.hint('And create a Nuxt plugin at plugins/inspecto.client.ts:')
|
|
15
|
+
console.log(`\x1b[36m
|
|
16
|
+
export default defineNuxtPlugin(() => {
|
|
17
|
+
if (import.meta.dev) {
|
|
18
|
+
import('@inspecto-dev/core').then(({ mountInspector }) => {
|
|
19
|
+
mountInspector()
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
\x1b[0m`)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function printNextJsManualInstructions() {
|
|
27
|
+
log.blank()
|
|
28
|
+
log.hint('To enable Inspecto in Next.js, update your next.config.mjs:')
|
|
29
|
+
console.log(`\x1b[36m
|
|
30
|
+
import { webpackPlugin as inspecto } from '@inspecto-dev/plugin'
|
|
31
|
+
const nextConfig = {
|
|
32
|
+
webpack: (config, { dev, isServer }) => {
|
|
33
|
+
if (dev && !isServer) config.plugins.push(inspecto())
|
|
34
|
+
return config
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export default nextConfig
|
|
38
|
+
\x1b[0m`)
|
|
39
|
+
log.hint('And initialize the client dynamically in your app/layout.tsx (or pages/_app.tsx):')
|
|
40
|
+
console.log(`\x1b[36m
|
|
41
|
+
'use client'
|
|
42
|
+
import { useEffect } from 'react'
|
|
43
|
+
|
|
44
|
+
export default function RootLayout({ children }) {
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
47
|
+
import('@inspecto-dev/core').then(({ mountInspector }) => {
|
|
48
|
+
mountInspector({ serverUrl: 'http://127.0.0.1:5678' })
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
}, [])
|
|
52
|
+
return <html><body>{children}</body></html>
|
|
53
|
+
}
|
|
54
|
+
\x1b[0m`)
|
|
55
|
+
}
|
package/src/prompts.ts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import prompts from 'prompts'
|
|
2
|
+
import { log } from './utils/logger.js'
|
|
3
|
+
import type { ProviderDetection } from './detect/provider.js'
|
|
4
|
+
import type { BuildToolDetection } from './types.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Interactive prompt for IDE choice.
|
|
8
|
+
*/
|
|
9
|
+
export async function promptIDEChoice(
|
|
10
|
+
detections: { ide: string; supported: boolean }[],
|
|
11
|
+
): Promise<{ ide: string; supported: boolean } | null> {
|
|
12
|
+
if (!process.stdin.isTTY) {
|
|
13
|
+
log.warn('Multiple IDEs detected but stdin is not interactive')
|
|
14
|
+
log.hint(`Using: ${detections[0]!.ide} (first match)`)
|
|
15
|
+
return detections[0]!
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const { choice } = await prompts({
|
|
19
|
+
type: 'select',
|
|
20
|
+
name: 'choice',
|
|
21
|
+
message: 'Detected multiple IDEs, please choose one:',
|
|
22
|
+
choices: detections.map((d, i) => ({
|
|
23
|
+
title: `${d.ide} ${d.supported ? '(supported)' : '(unsupported/limited)'}`,
|
|
24
|
+
value: i,
|
|
25
|
+
})),
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
if (choice === undefined) return null
|
|
29
|
+
return detections[choice]!
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Interactive prompt for AI tool choice.
|
|
34
|
+
*/
|
|
35
|
+
export async function promptProviderChoice(
|
|
36
|
+
detections: ProviderDetection[],
|
|
37
|
+
): Promise<ProviderDetection | null> {
|
|
38
|
+
if (!process.stdin.isTTY) {
|
|
39
|
+
log.warn('Multiple AI tools detected but stdin is not interactive')
|
|
40
|
+
log.hint(`Using: ${detections[0]!.label} (first match)`)
|
|
41
|
+
return detections[0]!
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const { choice } = await prompts({
|
|
45
|
+
type: 'select',
|
|
46
|
+
name: 'choice',
|
|
47
|
+
message: 'Detected multiple providers, please choose one:',
|
|
48
|
+
choices: detections.map((d, i) => {
|
|
49
|
+
const modeLabels = d.providerModes.map(mode =>
|
|
50
|
+
mode === 'extension' ? 'VS Code Extension' : 'Terminal CLI',
|
|
51
|
+
)
|
|
52
|
+
const modeStr = modeLabels.join(' & ')
|
|
53
|
+
return {
|
|
54
|
+
title: `${d.label} ${d.supported ? `(supported ${modeStr})` : '(unsupported/limited)'}`,
|
|
55
|
+
value: i,
|
|
56
|
+
}
|
|
57
|
+
}),
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
if (choice === undefined) return null
|
|
61
|
+
return detections[choice]!
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Interactive prompt for Build Tool Config choice.
|
|
66
|
+
*/
|
|
67
|
+
export async function promptConfigChoice(
|
|
68
|
+
detections: BuildToolDetection[],
|
|
69
|
+
): Promise<BuildToolDetection | null> {
|
|
70
|
+
if (!process.stdin.isTTY) {
|
|
71
|
+
log.warn('Multiple config files detected but stdin is not interactive')
|
|
72
|
+
log.hint(`Using: ${detections[0]!.label} (first match)`)
|
|
73
|
+
return detections[0]!
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const choices = detections.map((d, i) => ({
|
|
77
|
+
title: d.label,
|
|
78
|
+
value: i,
|
|
79
|
+
}))
|
|
80
|
+
|
|
81
|
+
choices.push({
|
|
82
|
+
title: "Skip (I'll configure manually)",
|
|
83
|
+
value: -1,
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
const { choice } = await prompts({
|
|
87
|
+
type: 'select',
|
|
88
|
+
name: 'choice',
|
|
89
|
+
message: 'Detected multiple build tool configs, please choose one to inject:',
|
|
90
|
+
choices,
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
if (choice === undefined || choice === -1) return null
|
|
94
|
+
return detections[choice]!
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Interactive prompt for continuing with unsupported frameworks.
|
|
99
|
+
*/
|
|
100
|
+
export async function promptUnsupportedFrameworkContinue(): Promise<boolean> {
|
|
101
|
+
if (!process.stdin.isTTY) {
|
|
102
|
+
log.error('Unsupported framework detected in non-interactive environment.')
|
|
103
|
+
log.hint('Use --force to skip this check and continue anyway.')
|
|
104
|
+
return false
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const { confirm } = await prompts({
|
|
108
|
+
type: 'confirm',
|
|
109
|
+
name: 'confirm',
|
|
110
|
+
message: 'Inspecto may not work properly. Do you want to continue anyway?',
|
|
111
|
+
initial: false,
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
return !!confirm
|
|
115
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -16,6 +16,8 @@ export interface BuildToolDetection {
|
|
|
16
16
|
label: string
|
|
17
17
|
/** Whether this is a legacy rspack version (< 0.4.0) */
|
|
18
18
|
isLegacyRspack?: boolean
|
|
19
|
+
/** Whether this is Webpack 4.x */
|
|
20
|
+
isLegacyWebpack?: boolean
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
/** Options passed to `inspecto init` */
|
|
@@ -23,9 +25,10 @@ export interface InitOptions {
|
|
|
23
25
|
shared: boolean
|
|
24
26
|
skipInstall: boolean
|
|
25
27
|
dryRun: boolean
|
|
26
|
-
|
|
28
|
+
provider?: string
|
|
27
29
|
noExtension: boolean
|
|
28
30
|
packages?: string[]
|
|
31
|
+
force: boolean
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
/** A single mutation recorded in install.lock */
|
package/tests/ide.test.ts
CHANGED
|
@@ -23,7 +23,7 @@ describe('detectIDE', () => {
|
|
|
23
23
|
vi.mocked(fsUtils.exists).mockResolvedValue(false)
|
|
24
24
|
|
|
25
25
|
const result = await detectIDE('/mock/root')
|
|
26
|
-
expect(result.detected).toEqual([{ ide: '
|
|
26
|
+
expect(result.detected).toEqual([{ ide: 'trae', supported: true }])
|
|
27
27
|
})
|
|
28
28
|
|
|
29
29
|
it('detects Cursor from environment variables', async () => {
|
|
@@ -31,7 +31,7 @@ describe('detectIDE', () => {
|
|
|
31
31
|
vi.mocked(fsUtils.exists).mockResolvedValue(false)
|
|
32
32
|
|
|
33
33
|
const result = await detectIDE('/mock/root')
|
|
34
|
-
expect(result.detected).toEqual([{ ide: '
|
|
34
|
+
expect(result.detected).toEqual([{ ide: 'cursor', supported: true }])
|
|
35
35
|
})
|
|
36
36
|
|
|
37
37
|
it('detects VS Code from environment variables without false positives', async () => {
|
|
@@ -89,6 +89,6 @@ describe('detectIDE', () => {
|
|
|
89
89
|
|
|
90
90
|
const result = await detectIDE('/mock/root')
|
|
91
91
|
// VS Code shouldn't be added since it shares TERM_PROGRAM with Trae
|
|
92
|
-
expect(result.detected).toEqual([{ ide: '
|
|
92
|
+
expect(result.detected).toEqual([{ ide: 'trae', supported: true }])
|
|
93
93
|
})
|
|
94
94
|
})
|
package/src/detect/ai-tool.ts
DELETED
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
// ============================================================
|
|
2
|
-
// src/detect/ai-tool.ts — AI Tool detection (v1)
|
|
3
|
-
//
|
|
4
|
-
// Detects installed AI tools via PATH (CLI) or IDE extensions (Plugin).
|
|
5
|
-
// ============================================================
|
|
6
|
-
import path from 'node:path'
|
|
7
|
-
import { exists, readJSON } from '../utils/fs.js'
|
|
8
|
-
import { which } from '../utils/exec.js'
|
|
9
|
-
import type { AiTool } from '@inspecto-dev/types'
|
|
10
|
-
|
|
11
|
-
export interface AIToolDetection {
|
|
12
|
-
id: AiTool
|
|
13
|
-
label: string
|
|
14
|
-
supported: boolean
|
|
15
|
-
toolModes: Array<'cli' | 'plugin'>
|
|
16
|
-
// The primary mode that will be written to settings if selected
|
|
17
|
-
preferredMode: 'cli' | 'plugin'
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const KNOWN_CLI_TOOLS: { id: AiTool; bin: string; label: string; supported: boolean }[] = [
|
|
21
|
-
{ id: 'claude-code', bin: 'claude', label: 'Claude Code', supported: true },
|
|
22
|
-
{ id: 'coco', bin: 'coco', label: 'Trae CLI (Coco)', supported: true },
|
|
23
|
-
{ id: 'codex', bin: 'codex', label: 'Codex CLI', supported: true },
|
|
24
|
-
{ id: 'gemini', bin: 'gemini', label: 'Gemini CLI', supported: true },
|
|
25
|
-
]
|
|
26
|
-
|
|
27
|
-
const KNOWN_IDE_PLUGINS: { id: AiTool; extId: string; label: string; supported: boolean }[] = [
|
|
28
|
-
{ id: 'claude-code', extId: 'anthropic.claude-code', label: 'Claude Code', supported: true },
|
|
29
|
-
{ id: 'github-copilot', extId: 'github.copilot', label: 'GitHub Copilot', supported: true },
|
|
30
|
-
{ id: 'codex', extId: 'openai.chatgpt', label: 'Codex (ChatGPT)', supported: true },
|
|
31
|
-
{ id: 'gemini', extId: 'google.geminicodeassist', label: 'Gemini Code Assist', supported: true },
|
|
32
|
-
]
|
|
33
|
-
|
|
34
|
-
export interface AIToolProbeResult {
|
|
35
|
-
detected: AIToolDetection[]
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Detect all installed AI tools by checking PATH binaries and IDE extensions.
|
|
40
|
-
*/
|
|
41
|
-
export async function detectAITools(root: string): Promise<AIToolProbeResult> {
|
|
42
|
-
// Use a map to merge duplicate CLI/Plugin detections for the same AI tool
|
|
43
|
-
const detectedMap = new Map<AiTool, AIToolDetection>()
|
|
44
|
-
|
|
45
|
-
// 1. Detect CLI tools
|
|
46
|
-
for (const tool of KNOWN_CLI_TOOLS) {
|
|
47
|
-
if (await which(tool.bin)) {
|
|
48
|
-
detectedMap.set(tool.id, {
|
|
49
|
-
id: tool.id,
|
|
50
|
-
label: tool.label,
|
|
51
|
-
supported: tool.supported,
|
|
52
|
-
toolModes: ['cli'],
|
|
53
|
-
preferredMode: 'cli',
|
|
54
|
-
})
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// 2. Detect IDE plugins (VS Code extensions)
|
|
59
|
-
// Check the local workspace .vscode/extensions.json first (recommendations)
|
|
60
|
-
const extensionsJsonPath = path.join(root, '.vscode', 'extensions.json')
|
|
61
|
-
let recommendedExts: string[] = []
|
|
62
|
-
if (await exists(extensionsJsonPath)) {
|
|
63
|
-
try {
|
|
64
|
-
const extData = await readJSON<{ recommendations?: string[] }>(extensionsJsonPath)
|
|
65
|
-
if (extData && Array.isArray(extData.recommendations)) {
|
|
66
|
-
recommendedExts = extData.recommendations.map(e => e.toLowerCase())
|
|
67
|
-
}
|
|
68
|
-
} catch {
|
|
69
|
-
// ignore JSON parse errors here
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Check user's global VS Code extensions folder
|
|
74
|
-
const homeDir = process.env.HOME || process.env.USERPROFILE || ''
|
|
75
|
-
const globalExtDir = path.join(homeDir, '.vscode', 'extensions')
|
|
76
|
-
const globalExtExists = await exists(globalExtDir)
|
|
77
|
-
|
|
78
|
-
// Wait for all plugin checks to resolve
|
|
79
|
-
for (const plugin of KNOWN_IDE_PLUGINS) {
|
|
80
|
-
let isInstalled = false
|
|
81
|
-
|
|
82
|
-
// Check if it's explicitly recommended in the workspace
|
|
83
|
-
if (recommendedExts.includes(plugin.extId.toLowerCase())) {
|
|
84
|
-
isInstalled = true
|
|
85
|
-
}
|
|
86
|
-
// Otherwise try to find it in the global extensions folder by prefix
|
|
87
|
-
else if (globalExtExists) {
|
|
88
|
-
try {
|
|
89
|
-
const { readdir } = await import('node:fs/promises')
|
|
90
|
-
const folders = await readdir(globalExtDir)
|
|
91
|
-
if (
|
|
92
|
-
folders.some(f => {
|
|
93
|
-
const lower = f.toLowerCase()
|
|
94
|
-
return (
|
|
95
|
-
lower === plugin.extId.toLowerCase() ||
|
|
96
|
-
lower.startsWith(plugin.extId.toLowerCase() + '-')
|
|
97
|
-
)
|
|
98
|
-
})
|
|
99
|
-
) {
|
|
100
|
-
isInstalled = true
|
|
101
|
-
}
|
|
102
|
-
} catch {
|
|
103
|
-
// Fallback or ignore
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (isInstalled) {
|
|
108
|
-
// If we already detected the CLI version of this tool, we append 'plugin' to the modes
|
|
109
|
-
// and set the preferredMode to 'plugin' since plugin integration is generally more seamless.
|
|
110
|
-
const existing = detectedMap.get(plugin.id)
|
|
111
|
-
if (existing) {
|
|
112
|
-
existing.toolModes.push('plugin')
|
|
113
|
-
existing.preferredMode = 'plugin'
|
|
114
|
-
} else {
|
|
115
|
-
detectedMap.set(plugin.id, {
|
|
116
|
-
id: plugin.id,
|
|
117
|
-
label: plugin.label,
|
|
118
|
-
supported: plugin.supported,
|
|
119
|
-
toolModes: ['plugin'],
|
|
120
|
-
preferredMode: 'plugin',
|
|
121
|
-
})
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return { detected: Array.from(detectedMap.values()) }
|
|
127
|
-
}
|