Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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/app-automation-token-deprecation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@shopify/cli-kit': minor
---

Warn that `SHOPIFY_APP_AUTOMATION_TOKEN` and `SHOPIFY_CLI_PARTNERS_TOKEN` are deprecated in favor of `SHOPIFY_SERVICE_ACCOUNT_TOKEN`, which takes the same tokens. The warning shows at most once a day, and both variables keep working for now.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
automationTokenAuthMethod,
automationTokenVariable,
automationTokenVariablesProblem,
deprecatedAutomationTokenVariable,
} from './automation-token.js'
import {environmentVariables} from '../constants.js'
import {describe, expect, test} from 'vitest'
Expand Down Expand Up @@ -47,6 +48,25 @@ describe('automationTokenAuthMethod', () => {
})
})

describe('deprecatedAutomationTokenVariable', () => {
test.each([
{name: 'the app variable', env: {[APP]: 'a'}, expected: APP},
{name: 'the app variable over the Partners variable', env: {[APP]: 'a', [PARTNERS]: 'p'}, expected: APP},
{name: 'the Partners variable', env: {[PARTNERS]: 'p'}, expected: PARTNERS},
{name: 'no automation token variable', env: {}, expected: undefined},
{name: 'the service account variable', env: {[SERVICE_ACCOUNT]: 's'}, expected: undefined},
{name: 'an empty app variable', env: {[APP]: ''}, expected: undefined},
{name: 'an empty Partners variable', env: {[PARTNERS]: ''}, expected: undefined},
{
name: 'the app variable set together with the service account variable',
env: {[SERVICE_ACCOUNT]: 's', [APP]: 'a'},
expected: undefined,
},
])('returns $expected for $name', ({env, expected}) => {
expect(deprecatedAutomationTokenVariable(env)).toBe(expected)
})
})

describe('automationTokenVariablesProblem', () => {
test.each([
{name: 'no automation token variable', env: {}},
Expand Down
16 changes: 16 additions & 0 deletions packages/cli-kit/src/private/node/session/automation-token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,22 @@ export function automationTokenAuthMethod(
return automationTokenVariable(env) === SERVICE_ACCOUNT_TOKEN ? 'service_account_token' : 'partners_token'
}

/**
* Returns the deprecated automation token variable the CLI authenticates with: SHOPIFY_APP_AUTOMATION_TOKEN or
* SHOPIFY_CLI_PARTNERS_TOKEN. Both are deprecated in favor of SHOPIFY_SERVICE_ACCOUNT_TOKEN, which takes the same
* tokens.
*
* Returns undefined when the variables can't be used, since the CLI reports that problem instead.
*
* @param env - Environment variables to read.
* @returns The deprecated variable in use, or undefined.
*/
export function deprecatedAutomationTokenVariable(env: NodeJS.ProcessEnv = process.env): string | undefined {
if (automationTokenVariablesProblem(env)) return undefined
const variable = automationTokenVariable(env)
return variable === APP_TOKEN || variable === PARTNERS_TOKEN ? variable : undefined
}

/**
* Explains why the automation token variables can't be used, so a misconfigured environment fails instead of
* falling back to the logged-in user.
Expand Down
111 changes: 111 additions & 0 deletions packages/cli-kit/src/public/node/base-command.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,22 @@ import {mockAndCaptureOutput} from './testing/output.js'
import {unstyled} from './output.js'
import {defineJsonOutputSchema} from './json-output-schema.js'
import {zod} from './schema.js'
import {runAtMinimumInterval} from '../../private/node/conf-store.js'
import {afterEach, beforeEach, describe, expect, test, vi} from 'vitest'
import {Flags} from '@oclif/core'
import {Ajv} from 'ajv'

vi.mock('../../private/node/conf-store.js', async (importOriginal) => {
const actual = await importOriginal<typeof import('../../private/node/conf-store.js')>()
return {
...actual,
runAtMinimumInterval: vi.fn(async (_key: string, _interval: unknown, task: () => Promise<void>) => {
await task()
return true
}),
}
})

let originalStdinIsTTY: boolean | undefined
let originalStderrIsTTY: boolean | undefined

Expand Down Expand Up @@ -260,6 +272,25 @@ describe('command events', () => {
})
})

test('reports the SHOPIFY_APP_AUTOMATION_TOKEN deprecation as a JSON diagnostic for JSON commands', async () => {
const outputMock = mockAndCaptureOutput()
outputMock.clear()
vi.stubEnv('SHOPIFY_APP_AUTOMATION_TOKEN', 'app-token')

await MockCommandWithEvents.run(['--json'])

const events = outputMock
.info()
.split('\n')
.map((line) => JSON.parse(line))
expect(events).toContainEqual({
type: 'diagnostic',
level: 'warning',
timestamp: expect.any(String),
message: expect.stringContaining('SHOPIFY_APP_AUTOMATION_TOKEN is deprecated.'),
})
})

test('does not duplicate events already displayed by the text UI', async () => {
const outputMock = mockAndCaptureOutput()
outputMock.clear()
Expand Down Expand Up @@ -856,6 +887,86 @@ describe('applying environments', async () => {
`)
})

test('shows a deprecation warning when SHOPIFY_APP_AUTOMATION_TOKEN is set', async () => {
// Given
const outputMock = mockAndCaptureOutput()
outputMock.clear()
vi.stubEnv('SHOPIFY_APP_AUTOMATION_TOKEN', 'app-token')

// When
await MockCommand.run()

// Then
expect(outputMock.warn()).toMatchInlineSnapshot(`
"╭─ warning ────────────────────────────────────────────────────────────────────╮
│ │
│ SHOPIFY_APP_AUTOMATION_TOKEN is deprecated. │
│ │
│ Move the token to SHOPIFY_SERVICE_ACCOUNT_TOKEN. │
│ SHOPIFY_APP_AUTOMATION_TOKEN still works for now, but a future release │
│ will stop reading it. │
│ │
╰──────────────────────────────────────────────────────────────────────────────╯
"
`)
})

test('shows a deprecation warning when SHOPIFY_CLI_PARTNERS_TOKEN is set', async () => {
// Given
const outputMock = mockAndCaptureOutput()
outputMock.clear()
vi.stubEnv('SHOPIFY_CLI_PARTNERS_TOKEN', 'partners-token')

// When
await MockCommand.run()

// Then
expect(outputMock.warn()).toMatchInlineSnapshot(`
"╭─ warning ────────────────────────────────────────────────────────────────────╮
│ │
│ SHOPIFY_CLI_PARTNERS_TOKEN is deprecated. │
│ │
│ Move the token to SHOPIFY_SERVICE_ACCOUNT_TOKEN. │
│ SHOPIFY_CLI_PARTNERS_TOKEN still works for now, but a future release will │
│ stop reading it. │
│ │
╰──────────────────────────────────────────────────────────────────────────────╯
"
`)
})

test('shows the deprecation warning at most once a day', async () => {
// Given
const outputMock = mockAndCaptureOutput()
outputMock.clear()
vi.stubEnv('SHOPIFY_APP_AUTOMATION_TOKEN', 'app-token')
vi.mocked(runAtMinimumInterval).mockResolvedValueOnce(false)

// When
await MockCommand.run()

// Then
expect(runAtMinimumInterval).toHaveBeenCalledWith(
'warn-on-deprecated-automation-token-variable',
{days: 1},
expect.any(Function),
)
expect(outputMock.warn()).toMatchInlineSnapshot('""')
})

test('does not show the deprecation warning when SHOPIFY_SERVICE_ACCOUNT_TOKEN is set', async () => {
// Given
const outputMock = mockAndCaptureOutput()
outputMock.clear()
vi.stubEnv('SHOPIFY_SERVICE_ACCOUNT_TOKEN', 'service-account-token')

// When
await MockCommand.run()

// Then
expect(outputMock.warn()).toMatchInlineSnapshot('""')
})

test('does not show a warning about NPM separator when no flag matches a NPM config env variable', async () => {
// Given
const outputMock = mockAndCaptureOutput()
Expand Down
20 changes: 20 additions & 0 deletions packages/cli-kit/src/public/node/base-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import {isTruthy} from './context/utilities.js'
import {setCurrentCommandId} from './global-context.js'
import {type JsonOutputSchema} from './json-output-schema.js'
import {JsonMap} from '../../private/common/json.js'
import {environmentVariables} from '../../private/node/constants.js'
import {deprecatedAutomationTokenVariable} from '../../private/node/session/automation-token.js'
import {runAtMinimumInterval} from '../../private/node/conf-store.js'
import {underscore} from '../common/string.js'
import {Command, Config, Errors, Flags} from '@oclif/core'
import {OutputFlags, Input, ParserOutput, FlagInput, OutputArgs} from '@oclif/core/parser'
Expand Down Expand Up @@ -94,6 +97,7 @@ abstract class BaseCommand extends Command {
}
await removeDuplicatedPlugins(this.config)
this.showNpmFlagWarning()
await this.showDeprecatedAutomationTokenVariableWarning()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are showing the warning in every command run? do we want to limit like once per day or something like that?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call. It now goes through runAtMinimumInterval with {days: 1}, like the upgrade and multiple-installation warnings. CI runs usually start from a fresh environment, so they still see it once per job.

const {showNotificationsIfNeeded} = await import('./notifications-system.js')
await showNotificationsIfNeeded()
return super.init()
Expand Down Expand Up @@ -122,6 +126,22 @@ abstract class BaseCommand extends Command {
}
}

// SHOPIFY_APP_AUTOMATION_TOKEN and SHOPIFY_CLI_PARTNERS_TOKEN keep working, but SHOPIFY_SERVICE_ACCOUNT_TOKEN replaces
// them and takes the same tokens. This warning is how people still using an old name find out about the new one.
protected async showDeprecatedAutomationTokenVariableWarning(): Promise<void> {
const variable = deprecatedAutomationTokenVariable()
if (!variable) return

// Show the warning only once per day
await runAtMinimumInterval('warn-on-deprecated-automation-token-variable', {days: 1}, async () => {
const {renderWarning} = await import('./ui.js')
renderWarning({
headline: `${variable} is deprecated.`,
body: `Move the token to ${environmentVariables.serviceAccountToken}. ${variable} still works for now, but a future release will stop reading it.`,
})
})
}

protected exitWithTimestampWhenEnvVariablePresent() {
if (isTruthy(process.env.SHOPIFY_CLI_ENV_STARTUP_PERFORMANCE_RUN)) {
outputResult(`
Expand Down
2 changes: 1 addition & 1 deletion packages/cli-kit/src/public/node/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export function getEnvironmentVariables(): NodeJS.ProcessEnv {

/**
* Returns the automation token the CLI authenticates with, from the first of these variables that is set:
* SHOPIFY_SERVICE_ACCOUNT_TOKEN, SHOPIFY_APP_AUTOMATION_TOKEN, or the deprecated SHOPIFY_CLI_PARTNERS_TOKEN.
* SHOPIFY_SERVICE_ACCOUNT_TOKEN, or the deprecated SHOPIFY_APP_AUTOMATION_TOKEN and SHOPIFY_CLI_PARTNERS_TOKEN.
*
* Returns undefined when the variables can't be used (an empty value, or the service account variable set alongside
* another one). Callers then fall back to the login flow, which reports the problem instead of logging in.
Expand Down
Loading