Skip to content

Commit a2bf1c5

Browse files
Merge branch 'main' into fix-observe-user
2 parents 0352a2d + fa48688 commit a2bf1c5

File tree

11 files changed

+101
-202
lines changed

11 files changed

+101
-202
lines changed

.changeset/stale-stingrays-fail.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@shopify/cli-kit': patch
3+
'@shopify/cli': patch
4+
---
5+
6+
Remove POLARIS_UNIFIED flag

packages/app/src/cli/prompts/init/init.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -52,16 +52,6 @@ export const templates = {
5252
options: {
5353
javascript: {branch: 'javascript', label: 'JavaScript'},
5454
typescript: {branch: 'main', label: 'TypeScript'},
55-
...(process.env.POLARIS_UNIFIED && {
56-
javascriptPolarisEarlyAccess: {
57-
branch: 'polaris-docs-2025-js',
58-
label: 'JavaScript (Polaris Release Candidate)',
59-
},
60-
typescriptPolarisEarlyAccess: {
61-
branch: 'polaris-docs-2025',
62-
label: 'TypeScript (Polaris Release Candidate)',
63-
},
64-
}),
6555
},
6656
},
6757
} as Template,

packages/app/src/cli/services/generate/extension.ts

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -244,8 +244,6 @@ async function uiExtensionInit({
244244

245245
if (templateLanguage === 'javascript') {
246246
await changeIndexFileExtension(directory, srcFileExtension)
247-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
248-
await removeUnwantedTemplateFilesPerFlavor(directory, extensionFlavor!.value)
249247
}
250248
},
251249
},
@@ -325,19 +323,6 @@ async function changeIndexFileExtension(extensionDirectory: string, fileExtensio
325323
await Promise.all(srcFileExensionsToChange)
326324
}
327325

328-
async function removeUnwantedTemplateFilesPerFlavor(extensionDirectory: string, extensionFlavor: ExtensionFlavorValue) {
329-
// Preact needs the tsconfig.json to set the `"jsxImportSource": "preact"` so it can properly build
330-
if (extensionFlavor === 'preact') {
331-
return
332-
}
333-
334-
// tsconfig.json file is only needed in extension folder to inform the IDE
335-
// About the `react-jsx` tsconfig option, so IDE don't complain about missing react import
336-
if (extensionFlavor !== 'typescript-react') {
337-
await removeFile(joinPath(extensionDirectory, 'tsconfig.json'))
338-
}
339-
}
340-
341326
async function addResolutionOrOverrideIfNeeded(directory: string, extensionFlavor: ExtensionFlavorValue | undefined) {
342327
if (extensionFlavor === 'typescript-react') {
343328
await addResolutionOrOverride(directory, {'@types/react': versions.reactTypes})
Lines changed: 8 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
11
import {fetchExtensionTemplates} from './fetch-template-specifications.js'
22
import {ExtensionFlavorValue} from './extension.js'
33
import {testDeveloperPlatformClient, testOrganizationApp} from '../../models/app/app.test-data.js'
4-
import {ExtensionFlavor} from '../../models/app/template.js'
5-
import {describe, expect, test, vi} from 'vitest'
6-
import * as experimentModule from '@shopify/cli-kit/node/is-polaris-unified-enabled'
7-
8-
vi.mock('@shopify/cli-kit/node/is-polaris-unified-enabled', () => ({
9-
isPolarisUnifiedEnabled: vi.fn().mockReturnValue(false),
10-
}))
4+
import {describe, expect, test} from 'vitest'
115

126
describe('fetchTemplateSpecifications', () => {
137
test('returns the remote specs', async () => {
@@ -34,12 +28,7 @@ describe('fetchTemplateSpecifications', () => {
3428
})
3529

3630
describe('ui_extension', () => {
37-
const preactFlavor: ExtensionFlavor = {
38-
name: 'Preact',
39-
value: 'preact' as ExtensionFlavorValue,
40-
}
41-
42-
const oldFlavors = [
31+
const allFlavors = [
4332
{
4433
name: 'JavaScript React',
4534
value: 'react' as ExtensionFlavorValue,
@@ -60,8 +49,11 @@ describe('fetchTemplateSpecifications', () => {
6049
value: 'typescript' as ExtensionFlavorValue,
6150
path: 'admin-action',
6251
},
52+
{
53+
name: 'Preact',
54+
value: 'preact' as ExtensionFlavorValue,
55+
},
6356
]
64-
const allFlavors = [...oldFlavors, preactFlavor]
6557

6658
async function getTemplates() {
6759
const {templates} = await fetchExtensionTemplates(
@@ -90,86 +82,12 @@ describe('fetchTemplateSpecifications', () => {
9082
return templates
9183
}
9284

93-
test('returns only the preact flavor when POLARIS_UNIFIED is enabled', async () => {
94-
// Given
95-
vi.spyOn(experimentModule, 'isPolarisUnifiedEnabled').mockReturnValueOnce(true)
96-
85+
test('includes all flavors', async () => {
9786
// When
9887
const templates = await getTemplates()
9988

10089
// Then
101-
expect(templates[0]!.supportedFlavors).toEqual([preactFlavor])
102-
})
103-
104-
test('excludes the preact flavor by default', async () => {
105-
// When
106-
const templates = await getTemplates()
107-
108-
// Then
109-
expect(templates[0]!.supportedFlavors).toEqual(oldFlavors)
110-
})
111-
112-
test('filter out templates that have no flavors available by default', async () => {
113-
// When
114-
const {templates} = await fetchExtensionTemplates(
115-
testDeveloperPlatformClient({
116-
templateSpecifications: () =>
117-
Promise.resolve({
118-
templates: [
119-
{
120-
identifier: 'ui_extension',
121-
name: 'UI Extension',
122-
defaultName: 'ui-extension',
123-
group: 'Merchant Admin',
124-
supportLinks: [],
125-
type: 'ui_extension',
126-
url: 'https://github.com/Shopify/extensions-templates',
127-
extensionPoints: [],
128-
supportedFlavors: [preactFlavor],
129-
},
130-
],
131-
groupOrder: [],
132-
}),
133-
}),
134-
testOrganizationApp(),
135-
['ui_extension'],
136-
)
137-
138-
// Then
139-
expect(templates).toEqual([])
140-
})
141-
142-
test('filter out templates that have no flavors available POLARIS_UNIFIED is enabled', async () => {
143-
// Given
144-
vi.spyOn(experimentModule, 'isPolarisUnifiedEnabled').mockReturnValueOnce(true)
145-
146-
// When
147-
const {templates} = await fetchExtensionTemplates(
148-
testDeveloperPlatformClient({
149-
templateSpecifications: () =>
150-
Promise.resolve({
151-
templates: [
152-
{
153-
identifier: 'ui_extension',
154-
name: 'UI Extension',
155-
defaultName: 'ui-extension',
156-
group: 'Merchant Admin',
157-
supportLinks: [],
158-
type: 'ui_extension',
159-
url: 'https://github.com/Shopify/extensions-templates',
160-
extensionPoints: [],
161-
supportedFlavors: oldFlavors,
162-
},
163-
],
164-
groupOrder: [],
165-
}),
166-
}),
167-
testOrganizationApp(),
168-
['ui_extension'],
169-
)
170-
171-
// Then
172-
expect(templates).toEqual([])
90+
expect(templates[0]!.supportedFlavors).toEqual(allFlavors)
17391
})
17492
})
17593
})

packages/app/src/cli/services/generate/fetch-template-specifications.ts

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,18 @@
11
import {ExtensionTemplatesResult} from '../../models/app/template.js'
22
import {MinimalAppIdentifiers} from '../../models/organization.js'
33
import {DeveloperPlatformClient} from '../../utilities/developer-platform-client.js'
4-
import {isPolarisUnifiedEnabled} from '@shopify/cli-kit/node/is-polaris-unified-enabled'
54

65
export async function fetchExtensionTemplates(
76
developerPlatformClient: DeveloperPlatformClient,
87
app: MinimalAppIdentifiers,
98
availableSpecifications: string[],
109
): Promise<ExtensionTemplatesResult> {
1110
const {templates: remoteTemplates, groupOrder} = await developerPlatformClient.templateSpecifications(app)
12-
const polarisUnifiedEnabled = isPolarisUnifiedEnabled()
1311

14-
const filteredTemplates = remoteTemplates
15-
.filter(
16-
(template) =>
17-
availableSpecifications.includes(template.identifier) || availableSpecifications.includes(template.type),
18-
)
19-
.map((template) => {
20-
if (template.type === 'ui_extension') {
21-
return {
22-
...template,
23-
supportedFlavors: template.supportedFlavors.filter((flavor) =>
24-
polarisUnifiedEnabled ? flavor.value === 'preact' : flavor.value !== 'preact',
25-
),
26-
}
27-
}
28-
return template
29-
})
30-
.filter((template) => template.supportedFlavors.length > 0)
12+
const filteredTemplates = remoteTemplates.filter(
13+
(template) =>
14+
availableSpecifications.includes(template.identifier) || availableSpecifications.includes(template.type),
15+
)
3116

3217
return {
3318
templates: filteredTemplates,

packages/cli-kit/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@
100100
},
101101
"dependencies": {
102102
"@apidevtools/json-schema-ref-parser": "11.7.3",
103-
"@bugsnag/js": "7.25.0",
103+
"@bugsnag/js": "8.6.0",
104104
"@graphql-typed-document-node/core": "3.2.0",
105105
"@iarna/toml": "2.2.5",
106106
"@oclif/core": "4.5.3",

packages/cli-kit/src/public/node/error-handler.test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {errorHandler, cleanStackFrameFilePath, addBugsnagMetadata, sendErrorToBugsnag} from './error-handler.js'
2+
import * as metadata from './metadata.js'
23
import {ciPlatform, cloudEnvironment, isUnitTest, macAddress} from './context/local.js'
34
import {mockAndCaptureOutput} from './testing/output.js'
45
import * as error from './error.js'
@@ -10,6 +11,7 @@ import {beforeEach, describe, expect, test, vi} from 'vitest'
1011

1112
const onNotify = vi.fn()
1213
const capturedEventHandler = vi.fn()
14+
let lastBugsnagEvent: {addMetadata: ReturnType<typeof vi.fn>} | undefined
1315

1416
vi.mock('process')
1517
vi.mock('@bugsnag/js', () => {
@@ -22,12 +24,15 @@ vi.mock('@bugsnag/js', () => {
2224
severity: '',
2325
unhandled: false,
2426
setUser: vi.fn(),
27+
addMetadata: vi.fn(),
2528
}
2629
eventHandler(mockEvent)
2730
capturedEventHandler(mockEvent)
31+
lastBugsnagEvent = mockEvent as any
2832
callback(null)
2933
},
3034
isStarted: () => true,
35+
addOnError: vi.fn(),
3136
},
3237
}
3338
})
@@ -51,6 +56,7 @@ beforeEach(() => {
5156
vi.mocked(isUnitTest).mockReturnValue(true)
5257
onNotify.mockClear()
5358
capturedEventHandler.mockClear()
59+
lastBugsnagEvent = undefined
5460
vi.mocked(settings).debug = false
5561
vi.mocked(isLocalEnvironment).mockReturnValue(false)
5662
vi.mocked(getLastSeenUserIdAfterAuth).mockResolvedValue('test-user-id-123')
@@ -259,4 +265,39 @@ describe('sends errors to Bugsnag', () => {
259265
const mockEvent = capturedEventHandler.mock.calls[0][0]
260266
expect(mockEvent.setUser).toHaveBeenCalledWith('unknown')
261267
})
268+
269+
test('attaches custom metadata with allowed slice_name when startCommand is present', async () => {
270+
await metadata.addSensitiveMetadata(() => ({
271+
commandStartOptions: {startTime: Date.now(), startCommand: 'app dev', startArgs: []},
272+
}))
273+
274+
await sendErrorToBugsnag(new Error('boom'), 'unexpected_error')
275+
276+
expect(lastBugsnagEvent).toBeDefined()
277+
expect(lastBugsnagEvent!.addMetadata).toHaveBeenCalledWith('custom', {slice_name: 'app'})
278+
})
279+
280+
test('does not attach custom slice_name when startCommand is missing', async () => {
281+
await metadata.addSensitiveMetadata(() => ({
282+
commandStartOptions: {startTime: Date.now(), startCommand: undefined as unknown as string, startArgs: []},
283+
}))
284+
285+
await sendErrorToBugsnag(new Error('boom'), 'unexpected_error')
286+
287+
expect(lastBugsnagEvent).toBeDefined()
288+
const calls = (lastBugsnagEvent!.addMetadata as any).mock.calls as any[]
289+
const customCall = calls.find(([section]: [string]) => section === 'custom')
290+
expect(customCall).toBeUndefined()
291+
})
292+
293+
test('defaults slice_name to cli when first word not allowed', async () => {
294+
await metadata.addSensitiveMetadata(() => ({
295+
commandStartOptions: {startTime: Date.now(), startCommand: 'version', startArgs: []},
296+
}))
297+
298+
await sendErrorToBugsnag(new Error('boom'), 'unexpected_error')
299+
300+
expect(lastBugsnagEvent).toBeDefined()
301+
expect(lastBugsnagEvent!.addMetadata).toHaveBeenCalledWith('custom', {slice_name: 'cli'})
302+
})
262303
})

packages/cli-kit/src/public/node/error-handler.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ import StackTracey from 'stacktracey'
2222
import Bugsnag, {Event} from '@bugsnag/js'
2323
import {realpath} from 'fs/promises'
2424

25+
// Allowed slice names for error analytics grouping.
26+
// Hardcoded list per product slices to keep analytics consistent.
27+
const ALLOWED_SLICE_NAMES = new Set<string>(['app', 'theme', 'hydrogen', 'store'])
28+
2529
export async function errorHandler(
2630
error: Error & {exitCode?: number | undefined},
2731
config?: Interfaces.Config,
@@ -129,6 +133,14 @@ export async function sendErrorToBugsnag(
129133
event.severity = 'error'
130134
event.unhandled = unhandled
131135
event.setUser(userId)
136+
// Attach command metadata so we know which CLI command triggered the error
137+
const {commandStartOptions} = metadata.getAllSensitiveMetadata()
138+
const {startCommand} = commandStartOptions ?? {}
139+
if (startCommand) {
140+
const firstWord = startCommand.trim().split(/\s+/)[0] ?? 'cli'
141+
const sliceName = ALLOWED_SLICE_NAMES.has(firstWord) ? firstWord : 'cli'
142+
event.addMetadata('custom', {slice_name: sliceName})
143+
}
132144
}
133145
const errorHandler = (error: unknown) => {
134146
if (error) {
@@ -286,5 +298,10 @@ function initializeBugsnag() {
286298
notify: 'https://error-analytics-production.shopifysvc.com',
287299
sessions: 'https://error-analytics-sessions-production.shopifysvc.com',
288300
},
301+
// Set the project root to `null` to prevent the default behavior of
302+
// Bugsnag which is to set it to the cwd. That is unhelpful for us because
303+
// the cwd can be anywhere in the user's filesystem, not necessarily
304+
// related to the CLI codebase.
305+
projectRoot: null,
289306
})
290307
}

packages/cli-kit/src/public/node/is-polaris-unified-enabled.test.ts

Lines changed: 0 additions & 18 deletions
This file was deleted.

packages/cli-kit/src/public/node/is-polaris-unified-enabled.ts

Lines changed: 0 additions & 10 deletions
This file was deleted.

0 commit comments

Comments
 (0)