Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/theme-info-json-contract.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@shopify/cli': major
---

Use camelCase fields, string theme IDs, null unavailable values, and one complete environment batch object in `theme info --json`.
36 changes: 34 additions & 2 deletions packages/cli-kit/src/public/node/base-command.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ import Command from './base-command.js'
import {Environments} from './environments.js'
import {encodeToml as encodeTOML} from './toml/codec.js'
import {globalFlags, jsonFlag, requiredIfNonInteractive} from './cli.js'
import {emitCommandEvent} from './command-events.js'
import {emitCommandEvent, runWithCommandEvents} from './command-events.js'
import {inTemporaryDirectory, mkdir, writeFile} from './fs.js'
import {joinPath, resolvePath, cwd} from './path.js'
import {mockAndCaptureOutput} from './testing/output.js'
import {unstyled} from './output.js'
import {defineJsonOutputSchema} from './json-output-schema.js'
import {zod} from './schema.js'
import {afterEach, beforeEach, describe, expect, test, vi} from 'vitest'
import {Flags} from '@oclif/core'
import {Config, Flags} from '@oclif/core'
import {Ajv} from 'ajv'

let originalStdinIsTTY: boolean | undefined
Expand Down Expand Up @@ -54,6 +54,7 @@ class MockCommand extends Command {
default: 'default stringy',
}),
password: Flags.string({}),
'store-password': Flags.string({}),
environment: Flags.string({
multiple: true,
default: [],
Expand Down Expand Up @@ -220,6 +221,11 @@ const environmentWithPassword = {
password: 'password',
}

const environmentWithCredentials = {
password: 'admin-password',
'store-password': 'storefront-secret',
}

const allEnvironments: Environments = {
environments: {
validEnvironment,
Expand All @@ -232,6 +238,7 @@ const allEnvironments: Environments = {
environmentMatchingDefault,
environmentWithDefaultOverride,
environmentWithPassword,
environmentWithCredentials,
},
}

Expand Down Expand Up @@ -787,6 +794,31 @@ describe('applying environments', async () => {
`)
})

runTestInTmpDir('reports environment settings as JSON diagnostics with masked passwords', async (tmpDir) => {
const sink = vi.fn()
const output = mockAndCaptureOutput()
output.clear()
await runWithCommandEvents({outputMode: 'json', sink}, async () => {
const config = new Config({root: __dirname})
await config.load()
const command = new MockCommand(['--path', tmpDir, '--environment', 'environmentWithCredentials'], config)
await command.run()
})

expect(sink).toHaveBeenCalledWith(
expect.objectContaining({
type: 'diagnostic',
level: 'info',
message:
'Using applicable flags from environmentWithCredentials environment:\npassword: ********word\nstore-password: ********cret',
}),
)
for (const credential of Object.values(environmentWithCredentials)) {
expect(JSON.stringify(sink.mock.calls)).not.toContain(credential)
}
expect(output.info()).toBe('')
})

runTestInTmpDir('reports environment settings with masked passwords', async (tmpDir: string) => {
// Given
const outputMock = mockAndCaptureOutput()
Expand Down
18 changes: 15 additions & 3 deletions packages/cli-kit/src/public/node/base-command.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {isDevelopment} from './context/local.js'
import {addPublicMetadata} from './metadata.js'
import {AbortError} from './error.js'
import {runWithCommandEventsForCommand} from './command-events.js'
import {commandEventOutputMode, emitCommandEvent, runWithCommandEventsForCommand} from './command-events.js'
import {outputContent, outputResult, outputToken} from './output.js'
import {setCurrentSessionAlias} from './session.js'
import {terminalSupportsPrompting} from './system.js'
Expand Down Expand Up @@ -145,7 +145,7 @@ abstract class BaseCommand extends Command {
result = await this.resultWithEnvironment<TFlags, TGlobalFlags, TArgs>(result, options, argv)
await setCurrentSessionAlias(result.flags['auth-alias'])
await addFromParsedFlags(result.flags)
this.failMissingNonTTYFlagRequirements(result.flags, this.applicableNonTTYFlagRequirements(result.flags))
this.validateNonTTYFlags(result.flags)
return {...result, ...{argv: result.argv as string[]}}
}

Expand All @@ -154,6 +154,10 @@ abstract class BaseCommand extends Command {
return undefined
}

protected validateNonTTYFlags(flags: FlagOutput): void {
this.failMissingNonTTYFlagRequirements(flags, this.applicableNonTTYFlagRequirements(flags))
}

protected failMissingNonTTYFlags(flags: FlagOutput, requiredFlags: string[]): void {
this.failMissingNonTTYFlagRequirements(
flags,
Expand Down Expand Up @@ -322,13 +326,21 @@ function reportEnvironmentApplication<
const userSpecifiedThisFlag = Object.prototype.hasOwnProperty.call(noDefaultsFlags, name)
const environmentContainsFlag = Object.prototype.hasOwnProperty.call(environment, name)
if (!userSpecifiedThisFlag && environmentContainsFlag) {
const valueToReport = name === 'password' ? `********${value.substr(-4)}` : value
const valueToReport = name === 'password' || name === 'store-password' ? `********${value.substr(-4)}` : value
changes[name] = valueToReport
}
}
if (Object.keys(changes).length === 0) return

const items = Object.entries(changes).map(([name, value]) => `${name}: ${value}`)
if (commandEventOutputMode() === 'json') {
emitCommandEvent({
type: 'diagnostic',
level: 'info',
message: `Using applicable flags from ${environmentName} environment:\n${items.join('\n')}`,
Comment thread
gonzaloriestra marked this conversation as resolved.
})
return
}
// eslint-disable-next-line no-void
void import('./ui.js').then(({renderInfo}) => {
renderInfo({
Expand Down
Loading
Loading