Skip to content

Built for people who want to own their automations. Join the waitlist for an invite.

Package listing

@kody/shopify

src/smoke-test.ts

110 lines · 3.0 KB · TypeScript
import {
	listUserSecretNames,
	ADMIN_ACCESS_TOKEN_SECRET,
	CLIENT_ID_SECRET,
	CLIENT_SECRET_SECRET,
	resolveAuthMode,
	resolveShop,
} from './lib/client.ts'
import { runShopHelperSelfCheck } from './lib/shop.ts'
import { getShop } from './shop.ts'
import type { ShopifyClientOptions } from './types.ts'

export type ShopifySmokeTestResult = {
	ok: boolean
	selfCheck: { ok: true; checks: number }
	live: boolean
	authMode: 'admin-token' | 'client-credentials' | null
	shop: string | null
	shopName: string | null
	setup: {
		secrets: {
			shopifyAdminAccessToken: boolean
			shopifyClientId: boolean
			shopifyClientSecret: boolean
		}
		nextSteps: Array<string>
	}
}

/**
 * Local helper checks, plus an optional live `shop { name }` read.
 *
 * Pass `shop` (and have secrets saved) to exercise credentials. Without
 * them this still returns `{ ok: true, live: false }` and setup URLs so
 * the package is forkable before anyone connects a store.
 *
 * @example
 * import smokeTest from 'kody:@kody/shopify/smoke-test'
 * const result = await smokeTest({ shop: 'acme' })
 */
export async function smokeTest(
	params: ShopifyClientOptions = {},
): Promise<ShopifySmokeTestResult> {
	const selfCheck = runShopHelperSelfCheck()
	const names = await listUserSecretNames()
	const secrets = {
		shopifyAdminAccessToken: names.has(ADMIN_ACCESS_TOKEN_SECRET),
		shopifyClientId: names.has(CLIENT_ID_SECRET),
		shopifyClientSecret: names.has(CLIENT_SECRET_SECRET),
	}
	const nextSteps: Array<string> = []
	if (!secrets.shopifyAdminAccessToken && !(secrets.shopifyClientId && secrets.shopifyClientSecret)) {
		nextSteps.push(
			'Save shopifyClientId and shopifyClientSecret from the Shopify Dev Dashboard, or shopifyAdminAccessToken from a legacy custom app.',
		)
		nextSteps.push(
			'https://kody.codes/account/secrets/new?name=shopifyClientId&description=Shopify%20Dev%20Dashboard%20client%20id&scope=user',
		)
		nextSteps.push(
			'https://kody.codes/account/secrets/new?name=shopifyClientSecret&description=Shopify%20Dev%20Dashboard%20client%20secret&scope=user',
		)
	}

	let shop: string | null = null
	try {
		shop = await resolveShop(params.shop)
	} catch {
		shop = null
		nextSteps.push(
			'Pass shop: "your-store" (the *.myshopify.com handle) or set a default after forking via kody:@kody/shopify/config.',
		)
	}
	if (shop) {
		nextSteps.push(
			`Approve host ${shop}.myshopify.com in the account secrets UI after the secret is saved.`,
		)
	}

	const canLive =
		Boolean(shop) &&
		(secrets.shopifyAdminAccessToken ||
			(secrets.shopifyClientId && secrets.shopifyClientSecret))

	if (!canLive) {
		return {
			ok: true,
			selfCheck,
			live: false,
			authMode: null,
			shop,
			shopName: null,
			setup: { secrets, nextSteps },
		}
	}

	const authMode = await resolveAuthMode()
	const profile = await getShop({ shop: shop || undefined, apiVersion: params.apiVersion })
	return {
		ok: true,
		selfCheck,
		live: true,
		authMode,
		shop: profile.myshopifyDomain,
		shopName: profile.name,
		setup: { secrets, nextSteps: [] },
	}
}

/** Default export: smoke test. */
export default smokeTest