@orchid-labs/pluxx 0.1.0

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.
Files changed (119) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +574 -0
  3. package/bin/pluxx.js +37 -0
  4. package/dist/cli/agent.d.ts +90 -0
  5. package/dist/cli/agent.d.ts.map +1 -0
  6. package/dist/cli/dev.d.ts +2 -0
  7. package/dist/cli/dev.d.ts.map +1 -0
  8. package/dist/cli/doctor.d.ts +19 -0
  9. package/dist/cli/doctor.d.ts.map +1 -0
  10. package/dist/cli/index.d.ts +24 -0
  11. package/dist/cli/index.d.ts.map +1 -0
  12. package/dist/cli/init-from-mcp.d.ts +145 -0
  13. package/dist/cli/init-from-mcp.d.ts.map +1 -0
  14. package/dist/cli/install.d.ts +56 -0
  15. package/dist/cli/install.d.ts.map +1 -0
  16. package/dist/cli/lint.d.ts +18 -0
  17. package/dist/cli/lint.d.ts.map +1 -0
  18. package/dist/cli/migrate.d.ts +2 -0
  19. package/dist/cli/migrate.d.ts.map +1 -0
  20. package/dist/cli/prompt.d.ts +20 -0
  21. package/dist/cli/prompt.d.ts.map +1 -0
  22. package/dist/cli/publish.d.ts +70 -0
  23. package/dist/cli/publish.d.ts.map +1 -0
  24. package/dist/cli/runtime.d.ts +20 -0
  25. package/dist/cli/runtime.d.ts.map +1 -0
  26. package/dist/cli/sync-from-mcp.d.ts +32 -0
  27. package/dist/cli/sync-from-mcp.d.ts.map +1 -0
  28. package/dist/cli/test.d.ts +33 -0
  29. package/dist/cli/test.d.ts.map +1 -0
  30. package/dist/compatibility/matrix.d.ts +14 -0
  31. package/dist/compatibility/matrix.d.ts.map +1 -0
  32. package/dist/config/define.d.ts +18 -0
  33. package/dist/config/define.d.ts.map +1 -0
  34. package/dist/config/load.d.ts +7 -0
  35. package/dist/config/load.d.ts.map +1 -0
  36. package/dist/generators/amp/index.d.ts +13 -0
  37. package/dist/generators/amp/index.d.ts.map +1 -0
  38. package/dist/generators/base.d.ts +49 -0
  39. package/dist/generators/base.d.ts.map +1 -0
  40. package/dist/generators/claude-code/index.d.ts +7 -0
  41. package/dist/generators/claude-code/index.d.ts.map +1 -0
  42. package/dist/generators/cline/index.d.ts +14 -0
  43. package/dist/generators/cline/index.d.ts.map +1 -0
  44. package/dist/generators/codex/index.d.ts +9 -0
  45. package/dist/generators/codex/index.d.ts.map +1 -0
  46. package/dist/generators/cursor/index.d.ts +11 -0
  47. package/dist/generators/cursor/index.d.ts.map +1 -0
  48. package/dist/generators/gemini-cli/index.d.ts +13 -0
  49. package/dist/generators/gemini-cli/index.d.ts.map +1 -0
  50. package/dist/generators/github-copilot/index.d.ts +11 -0
  51. package/dist/generators/github-copilot/index.d.ts.map +1 -0
  52. package/dist/generators/hooks-warning.d.ts +3 -0
  53. package/dist/generators/hooks-warning.d.ts.map +1 -0
  54. package/dist/generators/index.d.ts +11 -0
  55. package/dist/generators/index.d.ts.map +1 -0
  56. package/dist/generators/opencode/index.d.ts +15 -0
  57. package/dist/generators/opencode/index.d.ts.map +1 -0
  58. package/dist/generators/openhands/index.d.ts +11 -0
  59. package/dist/generators/openhands/index.d.ts.map +1 -0
  60. package/dist/generators/roo-code/index.d.ts +14 -0
  61. package/dist/generators/roo-code/index.d.ts.map +1 -0
  62. package/dist/generators/shared/claude-family.d.ts +18 -0
  63. package/dist/generators/shared/claude-family.d.ts.map +1 -0
  64. package/dist/generators/warp/index.d.ts +13 -0
  65. package/dist/generators/warp/index.d.ts.map +1 -0
  66. package/dist/hook-events.d.ts +4 -0
  67. package/dist/hook-events.d.ts.map +1 -0
  68. package/dist/index.d.ts +7 -0
  69. package/dist/index.d.ts.map +1 -0
  70. package/dist/index.js +5302 -0
  71. package/dist/mcp/introspect.d.ts +34 -0
  72. package/dist/mcp/introspect.d.ts.map +1 -0
  73. package/dist/permissions.d.ts +18 -0
  74. package/dist/permissions.d.ts.map +1 -0
  75. package/dist/schema.d.ts +9457 -0
  76. package/dist/schema.d.ts.map +1 -0
  77. package/dist/user-config.d.ts +19 -0
  78. package/dist/user-config.d.ts.map +1 -0
  79. package/dist/validation/platform-rules.d.ts +64 -0
  80. package/dist/validation/platform-rules.d.ts.map +1 -0
  81. package/package.json +76 -0
  82. package/src/cli/agent.ts +1030 -0
  83. package/src/cli/dev.ts +112 -0
  84. package/src/cli/doctor.ts +588 -0
  85. package/src/cli/index.ts +2414 -0
  86. package/src/cli/init-from-mcp.ts +1611 -0
  87. package/src/cli/install.ts +698 -0
  88. package/src/cli/lint.ts +1219 -0
  89. package/src/cli/migrate.ts +614 -0
  90. package/src/cli/prompt.ts +82 -0
  91. package/src/cli/publish.ts +401 -0
  92. package/src/cli/runtime.ts +86 -0
  93. package/src/cli/sync-from-mcp.ts +563 -0
  94. package/src/cli/test.ts +134 -0
  95. package/src/compatibility/matrix.ts +149 -0
  96. package/src/config/define.ts +20 -0
  97. package/src/config/load.ts +74 -0
  98. package/src/generators/amp/index.ts +63 -0
  99. package/src/generators/base.ts +188 -0
  100. package/src/generators/claude-code/index.ts +29 -0
  101. package/src/generators/cline/index.ts +35 -0
  102. package/src/generators/codex/index.ts +120 -0
  103. package/src/generators/cursor/index.ts +158 -0
  104. package/src/generators/gemini-cli/index.ts +83 -0
  105. package/src/generators/github-copilot/index.ts +32 -0
  106. package/src/generators/hooks-warning.ts +51 -0
  107. package/src/generators/index.ts +71 -0
  108. package/src/generators/opencode/index.ts +526 -0
  109. package/src/generators/openhands/index.ts +32 -0
  110. package/src/generators/roo-code/index.ts +35 -0
  111. package/src/generators/shared/claude-family.ts +215 -0
  112. package/src/generators/warp/index.ts +32 -0
  113. package/src/hook-events.ts +33 -0
  114. package/src/index.ts +23 -0
  115. package/src/mcp/introspect.ts +834 -0
  116. package/src/permissions.ts +258 -0
  117. package/src/schema.ts +312 -0
  118. package/src/user-config.ts +177 -0
  119. package/src/validation/platform-rules.ts +565 -0
@@ -0,0 +1,401 @@
1
+ import { existsSync, mkdirSync, readFileSync, rmSync } from 'fs'
2
+ import { mkdtempSync } from 'fs'
3
+ import { resolve } from 'path'
4
+ import { spawnSync } from 'child_process'
5
+ import { tmpdir } from 'os'
6
+ import type { PluginConfig, TargetPlatform } from '../schema'
7
+
8
+ type PublishChannel = 'npm' | 'github-release'
9
+
10
+ interface CommandResult {
11
+ status: number | null
12
+ stdout: string
13
+ stderr: string
14
+ }
15
+
16
+ type CommandRunner = (
17
+ command: string,
18
+ args: string[],
19
+ options?: { cwd?: string },
20
+ ) => CommandResult
21
+
22
+ export interface PublishPlanOptions {
23
+ requestedChannels?: PublishChannel[]
24
+ version?: string
25
+ tag?: string
26
+ dryRun?: boolean
27
+ rootDir?: string
28
+ runCommand?: CommandRunner
29
+ }
30
+
31
+ export interface PublishAssetPlan {
32
+ platform: TargetPlatform
33
+ name: string
34
+ path: string
35
+ }
36
+
37
+ export interface PublishCheck {
38
+ name: string
39
+ ok: boolean
40
+ code: string
41
+ detail?: string
42
+ }
43
+
44
+ export interface PublishPlan {
45
+ command: 'publish'
46
+ dryRun: boolean
47
+ version: string
48
+ tag: string
49
+ channels: {
50
+ npm: {
51
+ enabled: boolean
52
+ explicit: boolean
53
+ packageName?: string
54
+ packageDir?: string
55
+ wouldPublish: boolean
56
+ }
57
+ githubRelease: {
58
+ enabled: boolean
59
+ explicit: boolean
60
+ releaseTag?: string
61
+ wouldCreateRelease: boolean
62
+ assets: PublishAssetPlan[]
63
+ }
64
+ }
65
+ checks: PublishCheck[]
66
+ }
67
+
68
+ export interface PublishRunResult extends PublishPlan {
69
+ ok: boolean
70
+ execution?: {
71
+ npm?: { ok: boolean; detail?: string }
72
+ githubRelease?: { ok: boolean; detail?: string }
73
+ }
74
+ }
75
+
76
+ function runCommandDefault(command: string, args: string[], options?: { cwd?: string }): CommandResult {
77
+ const result = spawnSync(command, args, {
78
+ cwd: options?.cwd,
79
+ encoding: 'utf-8',
80
+ })
81
+
82
+ return {
83
+ status: result.status,
84
+ stdout: result.stdout ?? '',
85
+ stderr: result.stderr ?? '',
86
+ }
87
+ }
88
+
89
+ function isTargetNpmBacked(platform: TargetPlatform): boolean {
90
+ return platform === 'opencode'
91
+ }
92
+
93
+ function resolveRequestedChannels(options: PublishPlanOptions): {
94
+ requested: Set<PublishChannel>
95
+ explicit: { npm: boolean; githubRelease: boolean }
96
+ } {
97
+ const requested = new Set<PublishChannel>(options.requestedChannels ?? [])
98
+ return {
99
+ requested,
100
+ explicit: {
101
+ npm: requested.has('npm'),
102
+ githubRelease: requested.has('github-release'),
103
+ },
104
+ }
105
+ }
106
+
107
+ function getBuiltTargets(rootDir: string, config: PluginConfig): TargetPlatform[] {
108
+ return config.targets.filter((platform) =>
109
+ existsSync(resolve(rootDir, config.outDir, platform)),
110
+ )
111
+ }
112
+
113
+ function buildReleaseAssets(rootDir: string, config: PluginConfig, version: string, targets: TargetPlatform[]): PublishAssetPlan[] {
114
+ return targets.map((platform) => ({
115
+ platform,
116
+ name: `${platform}-v${version}.tgz`,
117
+ path: resolve(rootDir, config.outDir, platform),
118
+ }))
119
+ }
120
+
121
+ function readNpmPackageName(rootDir: string, config: PluginConfig): { packageName?: string; packageDir?: string } {
122
+ const packageDir = resolve(rootDir, config.outDir, 'opencode')
123
+ const packageJsonPath = resolve(packageDir, 'package.json')
124
+ if (!existsSync(packageJsonPath)) {
125
+ return {}
126
+ }
127
+
128
+ try {
129
+ const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8')) as { name?: string }
130
+ return {
131
+ packageName: pkg.name,
132
+ packageDir,
133
+ }
134
+ } catch {
135
+ return {
136
+ packageDir,
137
+ }
138
+ }
139
+ }
140
+
141
+ function collectChecks(args: {
142
+ rootDir: string
143
+ config: PluginConfig
144
+ npmEnabled: boolean
145
+ githubReleaseEnabled: boolean
146
+ packageDir?: string
147
+ packageName?: string
148
+ runCommand: CommandRunner
149
+ }): PublishCheck[] {
150
+ const builtTargets = getBuiltTargets(args.rootDir, args.config)
151
+ const checks: PublishCheck[] = [
152
+ {
153
+ name: 'artifacts-exist',
154
+ ok: builtTargets.length > 0,
155
+ code: 'artifacts-exist',
156
+ detail: builtTargets.length > 0
157
+ ? `Built targets: ${builtTargets.join(', ')}`
158
+ : `No built platform outputs found in ${args.config.outDir}/`,
159
+ },
160
+ ]
161
+
162
+ const gitStatus = args.runCommand('git', ['status', '--porcelain'], { cwd: args.rootDir })
163
+ checks.push({
164
+ name: 'git-clean',
165
+ ok: gitStatus.status === 0 && gitStatus.stdout.trim() === '',
166
+ code: 'git-clean',
167
+ detail: gitStatus.status !== 0
168
+ ? (gitStatus.stderr || gitStatus.stdout || 'Unable to read git status')
169
+ : gitStatus.stdout.trim() === ''
170
+ ? 'Working tree is clean.'
171
+ : 'Working tree has uncommitted changes.',
172
+ })
173
+
174
+ if (args.npmEnabled) {
175
+ checks.push({
176
+ name: 'npm-package-ready',
177
+ ok: Boolean(args.packageDir && existsSync(resolve(args.packageDir, 'package.json')) && args.packageName),
178
+ code: 'npm-package-ready',
179
+ detail: args.packageDir
180
+ ? `OpenCode package dir: ${args.packageDir}`
181
+ : 'No npm-backed target package found.',
182
+ })
183
+
184
+ const npmAuth = args.runCommand('npm', ['whoami'], { cwd: args.rootDir })
185
+ checks.push({
186
+ name: 'npm-auth',
187
+ ok: npmAuth.status === 0,
188
+ code: 'npm-auth',
189
+ detail: npmAuth.status === 0
190
+ ? (npmAuth.stdout.trim() || 'npm auth OK')
191
+ : (npmAuth.stderr || npmAuth.stdout || 'npm auth unavailable'),
192
+ })
193
+ }
194
+
195
+ if (args.githubReleaseEnabled) {
196
+ const ghAuth = args.runCommand('gh', ['auth', 'status'], { cwd: args.rootDir })
197
+ checks.push({
198
+ name: 'github-release-auth',
199
+ ok: ghAuth.status === 0,
200
+ code: 'github-release-auth',
201
+ detail: ghAuth.status === 0
202
+ ? 'gh auth OK'
203
+ : (ghAuth.stderr || ghAuth.stdout || 'gh auth unavailable'),
204
+ })
205
+ }
206
+
207
+ return checks
208
+ }
209
+
210
+ export function planPublish(config: PluginConfig, options: PublishPlanOptions = {}): PublishPlan {
211
+ const rootDir = options.rootDir ?? process.cwd()
212
+ const runCommand = options.runCommand ?? runCommandDefault
213
+ const builtTargets = getBuiltTargets(rootDir, config)
214
+ const { requested, explicit } = resolveRequestedChannels(options)
215
+ const defaultNpm = builtTargets.some(isTargetNpmBacked)
216
+ const defaultGithubRelease = builtTargets.length > 0
217
+ const npmEnabled = explicit.npm ? true : (requested.size === 0 ? defaultNpm : false)
218
+ const githubReleaseEnabled = explicit.githubRelease ? true : (requested.size === 0 ? defaultGithubRelease : false)
219
+ const version = options.version ?? config.version
220
+ const tag = options.tag ?? 'latest'
221
+ const { packageDir, packageName } = readNpmPackageName(rootDir, config)
222
+ const checks = collectChecks({
223
+ rootDir,
224
+ config,
225
+ npmEnabled,
226
+ githubReleaseEnabled,
227
+ packageDir,
228
+ packageName,
229
+ runCommand,
230
+ })
231
+
232
+ return {
233
+ command: 'publish',
234
+ dryRun: options.dryRun ?? false,
235
+ version,
236
+ tag,
237
+ channels: {
238
+ npm: {
239
+ enabled: npmEnabled,
240
+ explicit: explicit.npm,
241
+ packageName,
242
+ packageDir,
243
+ wouldPublish: npmEnabled,
244
+ },
245
+ githubRelease: {
246
+ enabled: githubReleaseEnabled,
247
+ explicit: explicit.githubRelease,
248
+ releaseTag: githubReleaseEnabled ? `v${version}` : undefined,
249
+ wouldCreateRelease: githubReleaseEnabled,
250
+ assets: githubReleaseEnabled ? buildReleaseAssets(rootDir, config, version, builtTargets) : [],
251
+ },
252
+ },
253
+ checks,
254
+ }
255
+ }
256
+
257
+ function createReleaseArchives(
258
+ rootDir: string,
259
+ config: PluginConfig,
260
+ assets: PublishAssetPlan[],
261
+ runCommand: CommandRunner,
262
+ ): { tempRoot: string; archives: string[] } {
263
+ const tempRoot = mkdtempSync(resolve(tmpdir(), 'pluxx-publish-'))
264
+ const created: string[] = []
265
+
266
+ try {
267
+ for (const asset of assets) {
268
+ const archivePath = resolve(tempRoot, asset.name)
269
+ const result = runCommand(
270
+ 'tar',
271
+ ['-czf', archivePath, '-C', resolve(rootDir, config.outDir), asset.platform],
272
+ { cwd: rootDir },
273
+ )
274
+ if (result.status !== 0) {
275
+ throw new Error(result.stderr || result.stdout || `Failed to create archive for ${asset.platform}`)
276
+ }
277
+ created.push(archivePath)
278
+ }
279
+ return { tempRoot, archives: created }
280
+ } catch (error) {
281
+ rmSync(tempRoot, { recursive: true, force: true })
282
+ throw error
283
+ }
284
+ }
285
+
286
+ export function formatPublishPlan(plan: PublishPlan): string[] {
287
+ const lines: string[] = [
288
+ `Resolved version: ${plan.version}`,
289
+ `Resolved tag: ${plan.tag}`,
290
+ `Channels: ${[
291
+ plan.channels.npm.enabled ? `npm${plan.channels.npm.explicit ? ' (explicit)' : ' (auto)'}` : null,
292
+ plan.channels.githubRelease.enabled ? `github-release${plan.channels.githubRelease.explicit ? ' (explicit)' : ' (auto)'}` : null,
293
+ ].filter(Boolean).join(', ') || 'none'}`,
294
+ '',
295
+ 'Checks:',
296
+ ]
297
+
298
+ for (const check of plan.checks) {
299
+ lines.push(` - ${check.name}: ${check.ok ? 'ok' : 'fail'}${check.detail ? ` — ${check.detail}` : ''}`)
300
+ }
301
+
302
+ lines.push('')
303
+ if (plan.channels.npm.enabled) {
304
+ lines.push(`npm package: ${plan.channels.npm.packageName ?? 'unknown'} (${plan.channels.npm.packageDir ?? 'missing'})`)
305
+ }
306
+
307
+ if (plan.channels.githubRelease.enabled) {
308
+ lines.push(`GitHub release tag: ${plan.channels.githubRelease.releaseTag}`)
309
+ lines.push('Assets:')
310
+ for (const asset of plan.channels.githubRelease.assets) {
311
+ lines.push(` - ${asset.name} <- ${asset.path}`)
312
+ }
313
+ }
314
+
315
+ if (plan.dryRun) {
316
+ lines.push('')
317
+ lines.push('No remote changes were made.')
318
+ }
319
+
320
+ return lines
321
+ }
322
+
323
+ export function runPublish(config: PluginConfig, options: PublishPlanOptions = {}): PublishRunResult {
324
+ const rootDir = options.rootDir ?? process.cwd()
325
+ const runCommand = options.runCommand ?? runCommandDefault
326
+ const plan = planPublish(config, options)
327
+
328
+ const failedChecks = plan.checks.filter((check) => !check.ok)
329
+ if (failedChecks.length > 0) {
330
+ return {
331
+ ...plan,
332
+ ok: false,
333
+ }
334
+ }
335
+
336
+ if (options.dryRun) {
337
+ return {
338
+ ...plan,
339
+ ok: true,
340
+ }
341
+ }
342
+
343
+ const execution: NonNullable<PublishRunResult['execution']> = {}
344
+
345
+ if (plan.channels.npm.enabled) {
346
+ const npmChannel = plan.channels.npm
347
+ const result = runCommand(
348
+ 'npm',
349
+ ['publish', '--tag', plan.tag, '--access', 'public'],
350
+ { cwd: npmChannel.packageDir },
351
+ )
352
+ execution.npm = {
353
+ ok: result.status === 0,
354
+ detail: result.status === 0
355
+ ? (result.stdout.trim() || 'npm publish complete')
356
+ : (result.stderr || result.stdout || 'npm publish failed'),
357
+ }
358
+ }
359
+
360
+ if (plan.channels.githubRelease.enabled) {
361
+ const releaseTag = plan.channels.githubRelease.releaseTag!
362
+ const { tempRoot, archives } = createReleaseArchives(rootDir, config, plan.channels.githubRelease.assets, runCommand)
363
+
364
+ try {
365
+ const existing = runCommand('gh', ['release', 'view', releaseTag], { cwd: rootDir })
366
+
367
+ const result = existing.status === 0
368
+ ? runCommand('gh', ['release', 'upload', releaseTag, '--clobber', ...archives], { cwd: rootDir })
369
+ : runCommand(
370
+ 'gh',
371
+ [
372
+ 'release',
373
+ 'create',
374
+ releaseTag,
375
+ ...archives,
376
+ '--title',
377
+ `${config.name} ${plan.version}`,
378
+ '--notes',
379
+ `Release generated by pluxx publish for ${config.name}@${plan.version}.`,
380
+ ],
381
+ { cwd: rootDir },
382
+ )
383
+
384
+ execution.githubRelease = {
385
+ ok: result.status === 0,
386
+ detail: result.status === 0
387
+ ? (result.stdout.trim() || `GitHub release ${releaseTag} updated`)
388
+ : (result.stderr || result.stdout || 'GitHub release publish failed'),
389
+ }
390
+ } finally {
391
+ rmSync(tempRoot, { recursive: true, force: true })
392
+ }
393
+ }
394
+
395
+ const ok = Object.values(execution).every((channel) => channel.ok)
396
+ return {
397
+ ...plan,
398
+ ok,
399
+ execution,
400
+ }
401
+ }
@@ -0,0 +1,86 @@
1
+ import * as clack from '@clack/prompts'
2
+
3
+ export interface CliRuntime {
4
+ dryRun: boolean
5
+ jsonOutput: boolean
6
+ quiet: boolean
7
+ isCI: boolean
8
+ isTTY: boolean
9
+ isInteractive: boolean
10
+ }
11
+
12
+ export function createCliRuntime(rawArgs: string[]): CliRuntime {
13
+ const isCI = process.env.CI === '1' || process.env.CI === 'true'
14
+ const isTTY = process.stdin.isTTY === true && process.stdout.isTTY === true
15
+
16
+ return {
17
+ dryRun: rawArgs.includes('--dry-run'),
18
+ jsonOutput: rawArgs.includes('--json'),
19
+ quiet: rawArgs.includes('--quiet'),
20
+ isCI,
21
+ isTTY,
22
+ isInteractive: isTTY && !isCI,
23
+ }
24
+ }
25
+
26
+ export function readFlag(rawArgs: string[], flag: string): boolean {
27
+ return rawArgs.includes(flag)
28
+ }
29
+
30
+ export function readOption(rawArgs: string[], flag: string): string | undefined {
31
+ const index = rawArgs.indexOf(flag)
32
+ if (index === -1) return undefined
33
+
34
+ const value = rawArgs[index + 1]
35
+ if (!value || value.startsWith('-')) {
36
+ return undefined
37
+ }
38
+
39
+ return value
40
+ }
41
+
42
+ export function readMultiValueOption(rawArgs: string[], flag: string): string[] | undefined {
43
+ const index = rawArgs.indexOf(flag)
44
+ if (index === -1) return undefined
45
+
46
+ const values: string[] = []
47
+ for (let i = index + 1; i < rawArgs.length; i += 1) {
48
+ const value = rawArgs[i]
49
+ if (value.startsWith('-')) break
50
+ values.push(value)
51
+ }
52
+
53
+ return values.length > 0 ? values : undefined
54
+ }
55
+
56
+ export function createSpinner(runtime: CliRuntime): ReturnType<typeof clack.spinner> | undefined {
57
+ if (runtime.jsonOutput || runtime.quiet || !runtime.isInteractive) {
58
+ return undefined
59
+ }
60
+
61
+ return clack.spinner()
62
+ }
63
+
64
+ export function printJson(value: unknown): void {
65
+ console.log(JSON.stringify(value, null, 2))
66
+ }
67
+
68
+ export function logInfo(runtime: CliRuntime, message: string): void {
69
+ if (!runtime.jsonOutput && !runtime.quiet) {
70
+ console.log(message)
71
+ }
72
+ }
73
+
74
+ export function logWarn(runtime: CliRuntime, message: string): void {
75
+ if (!runtime.jsonOutput && !runtime.quiet) {
76
+ console.warn(message)
77
+ }
78
+ }
79
+
80
+ export function logError(message: string): void {
81
+ console.error(message)
82
+ }
83
+
84
+ export function formatPathList(paths: string[]): string {
85
+ return paths.length > 0 ? paths.join(', ') : 'none'
86
+ }