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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

## Unreleased

## 0.5.0

- Add the target-aware `UG-TAX-001` review finding when a Shopify app config subscribes to `tax_summaries/create` and the scan targets API version 2027-01 or later. The warning identifies the affected integration but does not claim to prove numeric-ID assumptions in its handler.
- Parse Shopify app TOML before evaluating the tax webhook rule, so malformed configuration fails with a value-free error and topic-like text inside another TOML string cannot create a finding.
- Upgrade the GraphQL parser from 16.14.2 to 17.0.2.

## 0.4.0

Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Use `npx shopify-upgrade-guard --help` for all commands. See the [CLI guide](doc

## GitHub Action

Pin the released v0.4.0 commit:
Pin the v0.5.0 release tag for the `UG-TAX-001` rule. The repository ruleset prevents released semantic version tags from being changed or deleted:

```yaml
name: Check Shopify API migration
Expand All @@ -48,7 +48,7 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- uses: RexCode-Digital/shopify-upgrade-guard@71f9cdca7d0b34b07c1dfb60c2dc649e2e595139 # v0.4.0
- uses: RexCode-Digital/shopify-upgrade-guard@v0.5.0 # v0.5.0
with:
target: 2026-10
fail-on: warning
Expand Down Expand Up @@ -80,7 +80,7 @@ The example is shortened from a scan of a synthetic GraphQL document.

Rules cover documented Shopify API and platform lifecycle changes across supported Admin GraphQL, Customer Account, Checkout UI, POS, extension-configuration and other tracked surfaces. Each rule defines its affected version and migration guidance; the [CLI guide](docs/cli.md), generated rule catalogue from the rules command, and [migration documentation](docs/2027-01-migration.md) describe current coverage. Rules only apply at their documented version boundary.

The source branch also contains the proposed `UG-TAX-001` check for an explicit `tax_summaries/create` subscription and target `2027-01`. It is not part of the published v0.4.0 package or Action. See the [2027-01 research and limits](docs/2027-01-migration.md).
Version 0.5.0 adds `UG-TAX-001` for an explicit `tax_summaries/create` subscription when the selected target is `2027-01` or later. It reports a review warning; it cannot determine whether the handler already accepts Global IDs. See the [2027-01 research and limits](docs/2027-01-migration.md).

## Limitations and privacy

Expand Down Expand Up @@ -112,4 +112,4 @@ Use [SECURITY.md](SECURITY.md) for private vulnerability reporting. Do not post

## Licence and affiliation

MIT licensed. Shopify's names and marks belong to Shopify. This independent open-source project is not affiliated with, endorsed by, or certified by Shopify.
MIT licensed. Shopify's names and marks belong to Shopify. This independent open-source project is not affiliated with, endorsed by, or certified by Shopify.
2 changes: 1 addition & 1 deletion dist/index.js

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions docs/2027-01-migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,6 @@ Upgrade Guard reports `UG-TAX-001` only when it finds the exact `tax_summaries/c

Test tax calculation and webhook fixtures using GIDs, keep identifiers as opaque strings, and check any code that converts identifiers to numbers. This rule does not match tax-like strings in ordinary source files, comments, or Shopify Events subscriptions. It also cannot discover shop-specific subscriptions created through the Admin API.

The rule is part of the unreleased source changes and is not included in the published `0.4.0` package or Action.
Upgrade Guard v0.5.0 includes `UG-TAX-001` in the CLI package and bundled Action. Version 0.4.0 does not include this rule. The warning only identifies the configured webhook and version boundary; it does not establish how the handler processes identifiers.

Evidence: [Shopify's 2027-01 tax ID changelog](https://shopify.dev/changelog/posts/tax-webhook-summary-and-calculation-requests-now-use-global-ids), [Shopify's webhook subscription configuration](https://shopify.dev/docs/apps/build/webhooks/subscribe), and [the 2027-01 release notes](https://shopify.dev/changelog/release-notes/2027-01).
Evidence: [Shopify's 2027-01 tax ID changelog](https://shopify.dev/changelog/posts/tax-webhook-summary-and-calculation-requests-now-use-global-ids), [the changelog introducing `tax_summaries/create`](https://shopify.dev/changelog/posts/taxsummariescreate-webhook-and-taxsummarycreate-mutation-now-available), [Shopify's webhook subscription configuration](https://shopify.dev/docs/apps/build/webhooks/subscribe), and [the 2027-01 release notes](https://shopify.dev/changelog/release-notes/2027-01).
2 changes: 1 addition & 1 deletion examples/github-action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
with:
fetch-depth: 0
persist-credentials: false
- uses: RexCode-Digital/shopify-upgrade-guard@223ec33e119f920ecc9f9a5df722a2f6fda0df40 # v0.2.5
- uses: RexCode-Digital/shopify-upgrade-guard@v0.5.0 # v0.5.0
with:
target: 2026-10
fail-on: warning
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "shopify-upgrade-guard",
"version": "0.4.0",
"version": "0.5.0",
"description": "Check documented Shopify API and platform upgrade compatibility risks with an offline CLI and GitHub Action.",
"type": "module",
"bin": {
Expand Down
29 changes: 16 additions & 13 deletions src/rules.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import TOML from '@iarna/toml';
import { isAtOrAfter, statusFor, latestStable, becomesUnsupportedBefore } from './versions.js';

import { rootFieldMatches, fieldMatches, namedTypeMatches, mutationEnumMatches, productVariantBarcodeMatches, segmentQueryMatches } from './graphql.js';
Expand Down Expand Up @@ -176,31 +177,33 @@ function matches(text, pattern, guidance) { return [...text.matchAll(pattern)].m
function taxSummaryWebhookMatches(file, guidance) {
const filename = file.relativePath.split('/').at(-1);
if (!/^shopify\.app(?:\.[^/]+)?\.toml$/.test(filename)) return [];
const lines = maskTomlComments(file.text).split('\n');
let manifest;
try {
manifest = TOML.parse(file.text);
} catch {
throw new Error(`Malformed Shopify app TOML in ${file.relativePath}`);
}
const subscriptions = manifest.webhooks?.subscriptions;
if (!Array.isArray(subscriptions)) return [];

const lines = file.text.split(/\r?\n/);
const found = [];
let inWebhookSubscription = false;
let subscriptionIndex = -1;
for (let index = 0; index < lines.length; index += 1) {
const line = lines[index];
const arrayTable = line.match(/^\s*\[\[([^\]]+)\]\]\s*$/);
const table = line.match(/^\s*\[([^\]]+)\]\s*$/);
if (arrayTable || table) {
inWebhookSubscription = arrayTable?.[1] === 'webhooks.subscriptions';
if (inWebhookSubscription) subscriptionIndex += 1;
continue;
}
if (!inWebhookSubscription) continue;
const topics = line.match(/^\s*topics\s*=\s*\[/);
const topics = line.match(/^\s*topics\s*=/);
if (!topics) continue;
let values = line.slice(topics[0].length);
let closingIndex = values.indexOf(']');
let endLine = index;
while (closingIndex === -1 && endLine + 1 < lines.length) {
endLine += 1;
values += '\n' + lines[endLine];
closingIndex = values.indexOf(']');
}
if (closingIndex === -1) continue;
const arrayContents = values.slice(0, closingIndex);
if (!/(['"])tax_summaries\/create\1/.test(arrayContents)) continue;
const topicList = subscriptions[subscriptionIndex]?.topics;
if (!Array.isArray(topicList) || !topicList.includes('tax_summaries/create')) continue;
found.push({
file: null,
line: index + 1,
Expand Down
2 changes: 1 addition & 1 deletion src/version.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
// Generated from package.json.
export const TOOL_VERSION = "0.4.0";
export const TOOL_VERSION = "0.5.0";
49 changes: 49 additions & 0 deletions tests/tax-webhook.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ uri = "/webhooks/tax-summary"

assert.equal((await scan(root, { target: '2026-10' })).findings.some((item) => item.ruleId === 'UG-TAX-001'), false);
const finding = (await scan(root, { target: '2027-01' })).findings.find((item) => item.ruleId === 'UG-TAX-001');
const laterTarget = (await scan(root, { target: '2027-04' })).findings.find((item) => item.ruleId === 'UG-TAX-001');
assert.ok(finding);
assert.ok(laterTarget);
assert.equal(finding.line, 5);
assert.equal(finding.severity, 'warning');
assert.equal(finding.classification, 'target');
Expand Down Expand Up @@ -90,3 +92,50 @@ uri = "/events/tax-summary"

assert.equal((await scan(root, { target: '2027-01' })).findings.some((item) => item.ruleId === 'UG-TAX-001'), false);
});

test('does not match a tax topic embedded in another valid TOML string', async (t) => {
const root = fixture(t, {
'shopify.app.toml': `[webhooks]
api_version = "2027-01"

[[webhooks.subscriptions]]
topics = ["orders/create", "not-a-topic \\\"tax_summaries/create"]
uri = "/webhooks/orders"
`
});

assert.equal((await scan(root, { target: '2027-01' })).findings.some((item) => item.ruleId === 'UG-TAX-001'), false);
});

test('ignores a topic-like scalar where Shopify expects an array', async (t) => {
const root = fixture(t, {
'shopify.app.toml': `[webhooks]
api_version = "2027-01"

[[webhooks.subscriptions]]
topics = "tax_summaries/create"
uri = "/webhooks/orders"
`
});

assert.equal((await scan(root, { target: '2027-01' })).findings.some((item) => item.ruleId === 'UG-TAX-001'), false);
});

test('rejects malformed Shopify app TOML without echoing its values', async (t) => {
const root = fixture(t, {
'shopify.app.toml': `[webhooks]
api_version = "2027-01"

[[webhooks.subscriptions]]
topics = ["tax_summaries/create"]
uri = "PRIVATE_CONFIGURATION_VALUE"
uri = "/duplicate-key"
`
});

await assert.rejects(scan(root, { target: '2027-01' }), (error) => {
assert.match(error.message, /Malformed Shopify app TOML in shopify\.app\.toml/);
assert.doesNotMatch(error.message, /PRIVATE_CONFIGURATION_VALUE/);
return true;
});
});
Loading