Skip to content
Draft
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
9,722 changes: 4,763 additions & 4,959 deletions packages/cli/oclif.manifest.json

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@
"@shopify/cli-kit": "3.86.0",
"@shopify/plugin-cloudflare": "3.86.0",
"@shopify/plugin-did-you-mean": "3.86.0",
"@shopify/store": "3.86.0",
"@shopify/theme": "3.86.0",
"@shopify/cli-hydrogen": "11.1.5",
"@types/global-agent": "3.0.0",
Expand Down Expand Up @@ -153,6 +154,9 @@
"app": {
"description": "Build Shopify apps."
},
"store": {
"description": "Execute GraphQL operations on stores."
},
"app:config": {
"description": "Manage app configuration."
},
Expand Down
8 changes: 8 additions & 0 deletions packages/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {createGlobalProxyAgent} from 'global-agent'
import ThemeCommands from '@shopify/theme'
import {COMMANDS as HydrogenCommands, HOOKS as HydrogenHooks} from '@shopify/cli-hydrogen'
import {commands as AppCommands} from '@shopify/app'
import {commands as StoreCommands} from '@shopify/store'
import {commands as PluginCommandsCommands} from '@oclif/plugin-commands'
import {commands as PluginPluginsCommands} from '@oclif/plugin-plugins'
import {DidYouMeanCommands} from '@shopify/plugin-did-you-mean'
Expand Down Expand Up @@ -99,6 +100,12 @@ themeCommands.forEach((command) => {
;(ThemeCommands[command] as any).customPluginName = '@shopify/theme'
})

const storeCommands = Object.keys(StoreCommands) as (keyof typeof StoreCommands)[]
storeCommands.forEach((command) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(StoreCommands[command] as any).customPluginName = '@shopify/store'
})

const hydrogenCommands = Object.keys(HydrogenCommands) as (keyof typeof HydrogenCommands)[]
hydrogenCommands.forEach((command) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down Expand Up @@ -127,6 +134,7 @@ pluginPluginsCommands.forEach((command) => {
export const COMMANDS: any = {
...AppCommands,
...ThemeCommands,
...StoreCommands,
...PluginPluginsCommands,
...DidYouMeanCommands,
...PluginCommandsCommands,
Expand Down
31 changes: 31 additions & 0 deletions packages/store/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "@shopify/store",
"version": "3.86.0",
"private": true,
"type": "module",
"exports": {
".": {
"import": "./dist/cli/index.js",
"types": "./dist/cli/index.d.ts"
}
},
"files": [
"/dist"
],
"scripts": {
"build": "nx build",
"clean": "nx clean",
"lint": "nx lint",
"lint:fix": "nx lint:fix",
"test": "nx test",
"type-check": "nx type-check"
},
"dependencies": {
"@oclif/core": "4.5.3",
"@shopify/cli-kit": "3.86.0",
"graphql": "^16.10.0"
},
"engines": {
"node": ">=20.10.0"
}
}
53 changes: 53 additions & 0 deletions packages/store/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"name": "store",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "packages/store/src",
"projectType": "library",
"tags": ["scope:feature"],
"targets": {
"clean": {
"executor": "nx:run-commands",
"options": {
"command": "pnpm rimraf dist/",
"cwd": "packages/store"
}
},
"build": {
"executor": "nx:run-commands",
"outputs": ["{workspaceRoot}/dist"],
"inputs": ["{projectRoot}/src/**/*", "{projectRoot}/package.json"],
"options": {
"command": "pnpm tsc -b ./tsconfig.build.json",
"cwd": "packages/store"
}
},
"lint": {
"executor": "nx:run-commands",
"options": {
"command": "pnpm eslint \"src/**/*.ts\"",
"cwd": "packages/store"
}
},
"lint:fix": {
"executor": "nx:run-commands",
"options": {
"command": "pnpm eslint 'src/**/*.ts' --fix",
"cwd": "packages/store"
}
},
"type-check": {
"executor": "nx:run-commands",
"options": {
"command": "pnpm tsc --noEmit",
"cwd": "packages/store"
}
},
"test": {
"executor": "nx:run-commands",
"options": {
"command": "pnpm vitest run",
"cwd": "packages/store"
}
}
}
}
83 changes: 83 additions & 0 deletions packages/store/src/cli/commands/store/execute.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import {parseGraphQLOperation} from '../../services/graphql-parser.js'
import {runBulkQuery} from '../../services/bulk-operations.js'
import {executeFlags} from '../../flags.js'
import {Command} from '@oclif/core'
import {globalFlags} from '@shopify/cli-kit/node/cli'
import {readFile, writeFile} from '@shopify/cli-kit/node/fs'
import {ensureAuthenticatedAdmin} from '@shopify/cli-kit/node/session'
import {adminRequest} from '@shopify/cli-kit/node/api/admin'
import {outputInfo, outputSuccess} from '@shopify/cli-kit/node/output'

export default class Execute extends Command {
static summary = 'execute a graphql query or mutation on a store'

static description =
'executes a graphql query or mutation on the specified store, and writes the result to stdout or a file. supports bulk operations.'

static flags = {
...globalFlags,
...executeFlags,
}

async run(): Promise<void> {
const {flags} = await this.parse(Execute)

const query = await this.getQuery(flags)

const store = flags.store
if (!store) {
this.error('--store is required')
}

const adminSession = await ensureAuthenticatedAdmin(store)

if (flags['bulk-operation']) {
const operationType = parseGraphQLOperation(query)

if (operationType === 'query') {
const result = await runBulkQuery(query, adminSession, (status, objectCount, rate, spinner) => {
const rateStr = rate > 0 ? ` • ${Math.round(rate)} obj/sec` : ''
process.stderr.write(`\r\x1b[K${status.toLowerCase()}: ${objectCount} objects${rateStr} ${spinner}`)
})

this.log('\n')

if (flags['output-file']) {
await writeFile(flags['output-file'], result.content)
outputSuccess(`wrote ${result.totalObjects} objects to ${flags['output-file']}`)
outputInfo(
`completed in ${result.totalTimeSeconds.toFixed(1)}s (${Math.round(result.averageRate)} obj/sec average)`,
)
} else {
process.stderr.write(
`completed in ${result.totalTimeSeconds.toFixed(1)}s (${Math.round(
result.averageRate,
)} obj/sec average)\n\n`,
)
this.log(result.content)
}
} else {
this.error('bulk mutations not yet implemented')
}
return
}

const result = await adminRequest(query, adminSession)

this.log(JSON.stringify(result, null, 2))
}

private async getQuery(flags: {query?: string; 'query-file'?: string}): Promise<string> {
let query = flags.query

if (!query && flags['query-file']) {
query = await readFile(flags['query-file'])
}

if (!query) {
this.error('either --query or --query-file is required')
}

return query
}
}
41 changes: 41 additions & 0 deletions packages/store/src/cli/flags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import {Flags} from '@oclif/core'

export const executeFlags = {
query: Flags.string({
char: 'q',
description: 'the graphql query or mutation, as a string',
exclusive: ['query-file'],
env: 'SHOPIFY_FLAG_QUERY',
}),
'query-file': Flags.string({
description: 'a file containing the graphql query or mutation',
exclusive: ['query'],
env: 'SHOPIFY_FLAG_QUERY_FILE',
}),
store: Flags.string({
char: 's',
description: 'the myshopify.com domain of the store',
env: 'SHOPIFY_FLAG_STORE',
}),
variables: Flags.string({
char: 'v',
description: 'the values for graphql variables, in json format',
multiple: true,
exclusive: ['variable-file'],
env: 'SHOPIFY_FLAG_VARIABLES',
}),
'variable-file': Flags.string({
description: 'a file containing graphql variables, in jsonl format',
exclusive: ['variables'],
env: 'SHOPIFY_FLAG_VARIABLE_FILE',
}),
'output-file': Flags.string({
description: 'the file name where results should be written',
env: 'SHOPIFY_FLAG_OUTPUT_FILE',
}),
'bulk-operation': Flags.boolean({
description: 'execute as a bulk operation',
default: false,
env: 'SHOPIFY_FLAG_BULK_OPERATION',
}),
}
5 changes: 5 additions & 0 deletions packages/store/src/cli/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import Execute from './commands/store/execute.js'

export const commands = {
'store:execute': Execute,
}
Loading
Loading