td-ai-tools 1.2.11 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/README.md +2 -2
- package/skills/shopify-lint/SKILL.md +26 -18
- package/skills/shopify-lint/eslint-plugin-theory/README.md +25 -8
- package/skills/shopify-lint/eslint-plugin-theory/src/liquid/mask.test.ts +41 -0
- package/skills/shopify-lint/eslint-plugin-theory/src/liquid/mask.ts +41 -2
- package/skills/shopify-lint/hooks/claude-stop.sh +2 -3
- package/skills/shopify-lint/hooks/codex-stop.sh +2 -3
- package/skills/shopify-lint/scripts/__pycache__/shopify_lint.cpython-312.pyc +0 -0
- package/skills/shopify-lint/scripts/setup.sh +21 -2
- package/skills/shopify-lint/scripts/shopify_lint.py +179 -30
- package/skills/shopify-lint/stylelint-config-theory/.stylelintignore +9 -0
- package/skills/shopify-lint/stylelint-config-theory/README.md +144 -0
- package/skills/shopify-lint/stylelint-config-theory/bin/lint-css.mjs +206 -0
- package/skills/shopify-lint/stylelint-config-theory/package-lock.json +3110 -0
- package/skills/shopify-lint/stylelint-config-theory/package.json +47 -0
- package/skills/shopify-lint/stylelint-config-theory/src/build.test.ts +31 -0
- package/skills/shopify-lint/stylelint-config-theory/src/config.test.ts +150 -0
- package/skills/shopify-lint/stylelint-config-theory/src/config.ts +109 -0
- package/skills/shopify-lint/stylelint-config-theory/src/index.ts +15 -0
- package/skills/shopify-lint/stylelint-config-theory/src/integration.test.ts +187 -0
- package/skills/shopify-lint/stylelint-config-theory/src/liquid/mask.test.ts +300 -0
- package/skills/shopify-lint/stylelint-config-theory/src/liquid/mask.ts +203 -0
- package/skills/shopify-lint/stylelint-config-theory/src/types/shared-configs.d.ts +26 -0
- package/skills/shopify-lint/stylelint-config-theory/tsconfig.build.json +9 -0
- package/skills/shopify-lint/stylelint-config-theory/tsconfig.json +14 -0
- package/skills/shopify-lint/stylelint-config-theory/vitest.config.ts +11 -0
- package/skills/shopify-lint/tests/__pycache__/test_shopify_lint.cpython-312.pyc +0 -0
- package/skills/shopify-lint/tests/test_shopify_lint.py +483 -8
- package/skills/shopify-lint/theme-check-theory/.theme-check.example.yml +7 -0
- package/skills/shopify-lint/theme-check-theory/README.md +48 -0
- package/skills/shopify-pre-pr/SKILL.md +129 -0
- package/skills/shopify-pre-pr/agents/openai.yaml +4 -0
- package/skills/shopify-pre-pr/references/td-change-delimiters.md +88 -0
- package/skills/shopify-pre-pr/scripts/__pycache__/test_vendor_change_audit.cpython-312.pyc +0 -0
- package/skills/shopify-pre-pr/scripts/__pycache__/vendor_change_audit.cpython-312.pyc +0 -0
- package/skills/shopify-pre-pr/scripts/test_vendor_change_audit.py +424 -0
- package/skills/shopify-pre-pr/scripts/vendor_change_audit.py +540 -0
- package/skills/record-changes/SKILL.md +0 -76
- package/skills/record-changes/agents/openai.yaml +0 -4
- /package/skills/{record-changes → shopify-pre-pr}/scripts/branch_diff_context.py +0 -0
package/package.json
CHANGED
package/skills/README.md
CHANGED
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
- `pr-solver`: Resolve GitHub pull request feedback by querying unresolved review conversations with the GitHub GraphQL API…
|
|
15
15
|
- `pull-request`: Generates well-structured GitHub pull request descriptions for Shopify theme development teams by analyzing…
|
|
16
16
|
- `pull-request-statamic`: Generates GitHub pull request descriptions for Statamic and Laravel development by analyzing git diffs and…
|
|
17
|
-
- `record-changes`: Update `docs/changes.md` by summarizing the current branch against the primary development branch.
|
|
18
17
|
- `shopify-cli`: Shopify CLI workflows for theme development.
|
|
19
|
-
- `shopify-lint`: Run Shopify CLI Theme Check and Theory Digital's bundled Liquid and
|
|
18
|
+
- `shopify-lint`: Run Shopify CLI Theme Check and Theory Digital's bundled Liquid, JavaScript, and CSS rules together, linting…
|
|
19
|
+
- `shopify-pre-pr`: Prepare a Shopify theme branch for pull request.
|
|
20
20
|
- `stylesheet-migration`: Migrate Shopify Liquid `{% stylesheet %}` blocks into theme CSS assets using bundled Python scripts.
|
|
21
21
|
- `td-js-vanilla-rules`: Theory Digital vanilla JavaScript standards for Shopify theme work.
|
|
22
22
|
- `td-review`: Run parallel code review agents on a PR (including TD theme compliance) and produce a synthesized findings…
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: shopify-lint
|
|
3
|
-
version: 1.
|
|
4
|
-
description: Run Shopify CLI Theme Check and Theory Digital's bundled Liquid and
|
|
3
|
+
version: 1.6.0
|
|
4
|
+
description: Run Shopify CLI Theme Check and Theory Digital's bundled Liquid, JavaScript, and CSS rules together, linting modified .js and .css files plus the JavaScript and CSS inside Liquid javascript and stylesheet blocks, while reporting and failing only on offenses in files modified on the current Git branch. Use when Codex needs to lint a Shopify theme, validate branch-scoped Liquid, JavaScript, CSS, or theme changes, enforce Theory theme, vanilla JavaScript, and stylesheet rules, flag Liquid written inside stylesheet or javascript blocks where Shopify never renders it, or avoid surfacing pre-existing offenses from untouched files.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Shopify Lint
|
|
8
8
|
|
|
9
9
|
## Setup
|
|
10
10
|
|
|
11
|
-
The bundled `theme-check-theory` (Liquid checks)
|
|
11
|
+
The bundled `theme-check-theory` (Liquid checks), `eslint-plugin-theory` (JavaScript rules), and `stylelint-config-theory` (CSS rules) packages ship as TypeScript source only; their `node_modules` and compiled `dist/` are not committed. Run setup once before the first run (and after upgrading the skill):
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
14
|
bash .agents/skills/shopify-lint/scripts/setup.sh
|
|
@@ -18,15 +18,18 @@ Installing with `td-ai-tools install --setup shopify-lint` runs this automatical
|
|
|
18
18
|
|
|
19
19
|
1. Runs `npm install && npm run build` inside `theme-check-theory/`, producing `dist/index.js` — the CommonJS entry point the root `.theme-check.yml` requires.
|
|
20
20
|
2. Runs `npm install && npm run build` inside `eslint-plugin-theory/`, producing the `dist/` that the JavaScript lint runner imports.
|
|
21
|
-
3.
|
|
22
|
-
4.
|
|
21
|
+
3. Runs `npm install && npm run build` inside `stylelint-config-theory/`, producing the `dist/` that the CSS lint runner imports.
|
|
22
|
+
4. Writes a `.theme-check.yml` at the project root wiring in the bundled checks. Setup replaces any existing file with the skill's current configuration so installs and updates cannot retain stale or incomplete settings.
|
|
23
|
+
5. Through the installer's shared project-hook setup, installs matching Claude and Codex Stop hooks and merges their registrations into `.claude/settings.json` and `.codex/hooks.json` without replacing unrelated settings or hooks. Re-running setup updates the Shopify Lint hook in place instead of duplicating it.
|
|
23
24
|
|
|
24
|
-
No ESLint configuration is written to the project root, and the theme's own ESLint
|
|
25
|
+
No ESLint or Stylelint configuration is written to the project root, and the theme's own ESLint and Stylelint configs are never consulted — the branch lint is a gate, so a theme must not be able to silently disable it. A project `.stylelintignore` is ignored for the same reason. Override the rules per project with `SHOPIFY_LINT_ESLINT_CONFIG=<path>` or `SHOPIFY_LINT_STYLELINT_CONFIG=<path>`, each pointing at a module that exports a `createConfig` function.
|
|
25
26
|
|
|
26
|
-
The Stop hooks run this skill's branch-scoped lint before handoff at `--fail-level warning`, so **every reported offense blocks handoff, warnings included** —
|
|
27
|
+
The Stop hooks run this skill's branch-scoped lint before handoff at `--fail-level warning`, so **every reported offense blocks handoff, warnings included** — the Theory Liquid checks, the Theory JavaScript rules, and the CSS rules alike. Only advisory `info` offenses do not block, and nothing the bundled linters report is graded that way. The hooks return modified-file offenses to the agent and allow handoff after three unsuccessful fix rounds. They require `jq`, `python3`, `node`, and Shopify CLI to be available when the hook runs. Directly running `scripts/setup.sh` performs the package builds and Theme Check configuration; use `td-ai-tools install --setup` or `td-ai-tools update --setup` to apply the shared hook manifest as well.
|
|
27
28
|
|
|
28
29
|
Keep these dependencies inside each bundled package's `node_modules`; do not install Node dependencies at the theme root.
|
|
29
30
|
|
|
31
|
+
Stylelint requires Node **>= 20.19**, which is the skill's effective floor.
|
|
32
|
+
|
|
30
33
|
## Usage
|
|
31
34
|
|
|
32
35
|
Run the bundled Python script from the Shopify theme root:
|
|
@@ -35,28 +38,33 @@ Run the bundled Python script from the Shopify theme root:
|
|
|
35
38
|
python3 .agents/skills/shopify-lint/scripts/shopify_lint.py --path .
|
|
36
39
|
```
|
|
37
40
|
|
|
38
|
-
The script identifies files changed since the current branch's merge-base, adds staged, unstaged, and untracked files, then runs
|
|
41
|
+
The script identifies files changed since the current branch's merge-base, adds staged, unstaged, and untracked files, then runs three linters over that set and merges their results into one report:
|
|
39
42
|
|
|
40
|
-
- **Theme Check** — `shopify theme check --output json`. The root `.theme-check.yml` directly requires the bundled `theme-check-theory` package from this skill directory. `DisallowedScriptOrStyleTag` reports inline executable `<script>` and all `<style>` tags in modified Liquid files while allowing external-source and JSON-data scripts. `HardcodedText` reports rendered hard-coded copy, except content inside Liquid `stylesheet`, `javascript`, and `schema` tags.
|
|
43
|
+
- **Theme Check** — `shopify theme check --output json`. The root `.theme-check.yml` directly requires the bundled `theme-check-theory` package from this skill directory. `DisallowedScriptOrStyleTag` reports inline executable `<script>` and all `<style>` tags in modified Liquid files while allowing external-source and JSON-data scripts. `HardcodedText` reports rendered hard-coded copy, except content inside Liquid `stylesheet`, `javascript`, and `schema` tags. The generated `.theme-check.yml` also pins the upstream `StaticStylesheetAndJavascriptTags` to `error`, so Liquid written inside a `{% stylesheet %}` or `{% javascript %}` block always fails the lint.
|
|
41
44
|
- **JavaScript** — ESLint's recommended rules plus the bundled `eslint-plugin-theory`, over modified `.js` and `.mjs` files *and* the JavaScript inside `{% javascript %}` blocks of modified `.liquid` files.
|
|
45
|
+
- **CSS** — `stylelint-config-standard` through the bundled `stylelint-config-theory`, over modified `.css` files *and* the CSS inside `{% stylesheet %}` blocks of modified `.liquid` files.
|
|
42
46
|
|
|
43
|
-
Standalone JavaScript
|
|
47
|
+
Standalone JavaScript and CSS are linted only when the **filename** contains `td-` (matched case-insensitively, anywhere in the name, so `td-globals.js` and `component-td-card.css` both qualify while `vendor.js` and `theme.css` do not). This keeps vendored and merchant-authored assets from being reported against Theory conventions they were never written to. The match is on the filename alone — a `td-` directory does not opt its contents in — and `*.min.js` and `*.min.css` are always skipped.
|
|
44
48
|
|
|
45
|
-
The filter does **not** apply to Liquid: `{% javascript %}` blocks are linted in every modified `.liquid` file regardless of its name, because that
|
|
49
|
+
The filter does **not** apply to Liquid: `{% javascript %}` and `{% stylesheet %}` blocks are linted in every modified `.liquid` file regardless of its name, because that code is Theory's either way. A `{% stylesheet 'scss' %}` block is skipped, because its body is Sass rather than CSS, as is any block commented out with `{% comment %}` or `{% raw %}`.
|
|
46
50
|
|
|
47
|
-
Offenses in a Liquid block are reported at their real line and column in the `.liquid` file. Check codes say where they came from: `PascalCase` is Theme Check, `theory/…` is a Theory JavaScript rule, `eslint/…` is an upstream ESLint rule.
|
|
51
|
+
Offenses in a Liquid block are reported at their real line and column in the `.liquid` file. Check codes say where they came from: `PascalCase` is Theme Check, `theory/…` is a Theory JavaScript rule, `eslint/…` is an upstream ESLint rule, `stylelint/…` is an upstream Stylelint rule.
|
|
48
52
|
|
|
49
53
|
`theory/guarded-custom-element-define` and `theory/no-unapproved-imports` are errors, as are genuine ESLint correctness failures; the convention rules (`theory/disconnected-callback-cleanup`, `theory/no-class-selectors`, `theory/centralize-selectors`, `theory/require-jsdoc`) are warnings. `theory/require-jsdoc` requires named classes and functions to have JSDoc comments, including typed `@param` and `@returns` tags. The severity distinction only affects `--fail-level`: the Stop hooks run at `--fail-level warning`, so warnings block handoff too. See `eslint-plugin-theory/README.md` for every rule and its blind spots.
|
|
50
54
|
|
|
51
|
-
|
|
55
|
+
For CSS, everything `stylelint-config-recommended` turns on is an error — unknown properties and at-rules, duplicate declarations, invalid values, empty blocks — and the notational conventions `stylelint-config-standard` adds on top are warnings. The naming-pattern rules (`selector-class-pattern`, `custom-property-pattern`, and their siblings) ship disabled because their kebab-case defaults reject Theory's BEM-style class names, and `no-descending-specificity` is demoted to a warning. Stylelint no longer checks formatting at all — its stylistic rules were removed in v15 and v16. See `stylelint-config-theory/README.md` for the full policy.
|
|
56
|
+
|
|
57
|
+
Liquid does not belong inside a `{% stylesheet %}` or `{% javascript %}` block: Shopify concatenates their contents into the compiled asset bundles without rendering them, so interpolation there is served to the browser as literal text. `StaticStylesheetAndJavascriptTags` reports every Liquid construct in one — output tags, control flow, and `{% comment %}`, which is not a comment there either — as an error at its real position. `{% style %}` and `{% script %}`, which Shopify does render, are untouched. Its one false positive is host-language nesting the Liquid parser reads as an output tag, such as `if (a) {{ b(); }}`; the JavaScript and CSS masks treat that as code and keep linting it.
|
|
58
|
+
|
|
59
|
+
Interpolation is still masked out before the JavaScript or CSS is parsed — an output tag becomes a `0` padded with blanks — so a block that still holds Liquid reports its real CSS and JavaScript offenses alongside the Liquid ones instead of collapsing into a single parse error. Because that Liquid is itself an offense, nothing is relaxed to accommodate the substitution: a `{% stylesheet %}` block is graded exactly as a standalone `.css` file is, and a parse error inside `.liquid` is a plain error. Expect a masked value such as `margin: {{ s.gap }}px` to draw a CSS offense as well as the Liquid one; both are fixed by taking the Liquid out.
|
|
52
60
|
|
|
53
61
|
## Workflow
|
|
54
62
|
|
|
55
|
-
1. Confirm `shopify`, `python3`, and `node` (>= 20) are available.
|
|
56
|
-
2. Run the bundled script instead of calling `shopify theme check` or `
|
|
63
|
+
1. Confirm `shopify`, `python3`, and `node` (>= 20.19) are available.
|
|
64
|
+
2. Run the bundled script instead of calling `shopify theme check`, `eslint`, or `stylelint` directly.
|
|
57
65
|
3. Treat exit code `0` as no failing offenses in modified files, `1` as filtered offenses at or above the fail level, and `2` as a Git, CLI, Node, or JSON-processing error.
|
|
58
66
|
4. Fix every reported issue, warnings included, and rerun until the command passes. Run with `--fail-level warning` to match what the Stop hooks enforce. Do not fix offenses in untouched files unless the user expands the scope.
|
|
59
|
-
5. Keep bundled-package dependencies inside `theme-check-theory/node_modules` and `
|
|
67
|
+
5. Keep bundled-package dependencies inside `theme-check-theory/node_modules`, `eslint-plugin-theory/node_modules`, and `stylelint-config-theory/node_modules`. Do not install Node dependencies at the theme root.
|
|
60
68
|
|
|
61
69
|
The default base is `origin/HEAD`, then `origin/main`, `main`, `origin/master`, or `master`. Override it when needed:
|
|
62
70
|
|
|
@@ -64,8 +72,8 @@ The default base is `origin/HEAD`, then `origin/main`, `main`, `origin/master`,
|
|
|
64
72
|
python3 .agents/skills/shopify-lint/scripts/shopify_lint.py --path . --base-ref origin/develop
|
|
65
73
|
```
|
|
66
74
|
|
|
67
|
-
Set `SHOPIFY_LINT_BASE_REF` for the same override in automation. Use `--format json` for machine-readable filtered output. The CLI still defaults to `--fail-level error`; the Stop hooks pass `--fail-level warning` explicitly, and `--fail-level info` additionally fails on advisory
|
|
75
|
+
Set `SHOPIFY_LINT_BASE_REF` for the same override in automation. Use `--format json` for machine-readable filtered output. The CLI still defaults to `--fail-level error`; the Stop hooks pass `--fail-level warning` explicitly, and `--fail-level info` additionally fails on any advisory offense a Theme Check configuration grades that way. When passing `--config <path>`, preserve the bundled package's `require` entry or the Theory checks will not load.
|
|
68
76
|
|
|
69
|
-
Pass `--skip-js` (or set `SHOPIFY_LINT_SKIP_JS=1`)
|
|
77
|
+
Pass `--skip-js` (or set `SHOPIFY_LINT_SKIP_JS=1`) and `--skip-css` (or `SHOPIFY_LINT_SKIP_CSS=1`) to drop those linters — useful when the Node toolchain is unavailable. Passing both reports Theme Check offenses only. Node is not spawned for JavaScript when no `.js`, `.mjs`, or `.liquid` file changed, nor for CSS when no `.css` or `.liquid` file changed.
|
|
70
78
|
|
|
71
79
|
Do not use Theme Check auto-correction through this workflow because it can modify untouched files before filtering.
|
|
@@ -159,20 +159,37 @@ being a literal, it introduces no identifier for `no-undef` to fire on.
|
|
|
159
159
|
block into one bundle, so identifiers defined in another file are legitimately
|
|
160
160
|
global.
|
|
161
161
|
|
|
162
|
-
A file with no `{% javascript %}` block produces no blocks at all and is
|
|
163
|
-
|
|
162
|
+
A file with no `{% javascript %}` block produces no blocks at all and is skipped,
|
|
163
|
+
as is a file whose every block sits inside a `{% comment %}` or `{% raw %}`
|
|
164
|
+
region — a commented-out block is not code the storefront runs.
|
|
164
165
|
|
|
165
|
-
|
|
166
|
-
|
|
166
|
+
### Liquid in a block is an offense, not a case to accommodate
|
|
167
|
+
|
|
168
|
+
Shopify does not render Liquid inside a `{% javascript %}` tag: the block is
|
|
169
|
+
concatenated into the theme's compiled JavaScript verbatim, so anything the mask
|
|
170
|
+
blanks out is already broken theme code. Theme Check's
|
|
171
|
+
`StaticStylesheetAndJavascriptTags` reports it as an **error**, at its exact
|
|
172
|
+
position in the `.liquid` file — see `theme-check-theory/README.md`. Masking it
|
|
173
|
+
anyway is what lets the author see the Liquid offense and the JavaScript offenses
|
|
174
|
+
in one round, rather than a parse error at the first `{{` standing in for
|
|
175
|
+
everything.
|
|
176
|
+
|
|
177
|
+
One case diverges: `if (a) {{ b(); }}` is valid JavaScript that the Liquid parser
|
|
178
|
+
reads as an output tag. The mask treats anything between `{{` and `}}` carrying a
|
|
179
|
+
brace or a semicolon as JavaScript and keeps linting it; the Theme Check offense
|
|
180
|
+
on that line is a false positive.
|
|
181
|
+
|
|
182
|
+
**Known limit.** Liquid control flow that straddles a JavaScript syntax boundary
|
|
183
|
+
cannot mask into valid JavaScript:
|
|
167
184
|
|
|
168
185
|
```liquid
|
|
169
186
|
{% if a %}function f() {{% else %}function g() {{% endif %}}
|
|
170
187
|
```
|
|
171
188
|
|
|
172
|
-
That produces a genuine parse error
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
189
|
+
That produces a genuine parse error, reported as an `error` like any other. It
|
|
190
|
+
used to be demoted to `info`, on the grounds that the author had no reasonable
|
|
191
|
+
way to fix it; the Liquid offense above now names the fix — take the Liquid out
|
|
192
|
+
of the block.
|
|
176
193
|
|
|
177
194
|
Note also that a rule reporting "at the top of the program" reports at line 1
|
|
178
195
|
of the `.liquid` file rather than at the block.
|
|
@@ -187,6 +187,47 @@ describe('maskLiquid', () => {
|
|
|
187
187
|
expect(masked).toContain('const b = 2;');
|
|
188
188
|
});
|
|
189
189
|
|
|
190
|
+
it('skips a block that is commented out', () => {
|
|
191
|
+
const source = ['{% comment %}', '{% javascript %}', 'run();', '{% endjavascript %}', '{% endcomment %}', ''].join(
|
|
192
|
+
'\n',
|
|
193
|
+
);
|
|
194
|
+
expect(maskLiquid(source)).toBeNull();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('skips a block inside a raw tag', () => {
|
|
198
|
+
const source = ['{% raw %}', '{% javascript %}', 'run();', '{% endjavascript %}', '{% endraw %}', ''].join('\n');
|
|
199
|
+
expect(maskLiquid(source)).toBeNull();
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('still lints a live block alongside a commented-out one', () => {
|
|
203
|
+
const masked =
|
|
204
|
+
maskLiquid(
|
|
205
|
+
[
|
|
206
|
+
'{% comment %}',
|
|
207
|
+
'{% javascript %}',
|
|
208
|
+
'old();',
|
|
209
|
+
'{% endjavascript %}',
|
|
210
|
+
'{% endcomment %}',
|
|
211
|
+
'{% javascript %}',
|
|
212
|
+
'live();',
|
|
213
|
+
'{% endjavascript %}',
|
|
214
|
+
'',
|
|
215
|
+
].join('\n'),
|
|
216
|
+
) ?? '';
|
|
217
|
+
|
|
218
|
+
expect(masked).toContain('live();');
|
|
219
|
+
expect(masked).not.toContain('old();');
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('keeps a block that only contains a comment tag', () => {
|
|
223
|
+
// The comment is inside the block, not the other way around: this is live
|
|
224
|
+
// JavaScript, and the comment tag itself is reported by Theme Check.
|
|
225
|
+
const masked = maskLiquid('{% javascript %}{% comment %}note{% endcomment %}run();{% endjavascript %}') ?? '';
|
|
226
|
+
|
|
227
|
+
expect(masked).toContain('run();');
|
|
228
|
+
expect(masked).not.toContain('note');
|
|
229
|
+
});
|
|
230
|
+
|
|
190
231
|
it('blanks Liquid control flow inside the block', () => {
|
|
191
232
|
const masked = maskLiquid('{% javascript %}{% if a %}run();{% endif %}{% endjavascript %}');
|
|
192
233
|
expect(masked).toContain('run();');
|
|
@@ -7,6 +7,13 @@
|
|
|
7
7
|
* already correct for the original `.liquid` file. That identity mapping is
|
|
8
8
|
* the whole point: the ESLint processor can then remap offenses with no
|
|
9
9
|
* arithmetic at all.
|
|
10
|
+
*
|
|
11
|
+
* Shopify does not render Liquid inside a `{% javascript %}` tag, so anything
|
|
12
|
+
* masked out of a block body is already broken theme code, and Theme Check's
|
|
13
|
+
* `StaticStylesheetAndJavascriptTags` reports it as an error. Masking it anyway
|
|
14
|
+
* is what lets the author see the Liquid offense and the JavaScript offenses in
|
|
15
|
+
* the same round, instead of a parse error at the first `{{` standing in for
|
|
16
|
+
* both.
|
|
10
17
|
*/
|
|
11
18
|
|
|
12
19
|
/** Matches a `{% javascript %}` … `{% endjavascript %}` pair, capturing the body. */
|
|
@@ -25,6 +32,10 @@ const LIQUID_TAG = /\{%[\s\S]*?%\}/g;
|
|
|
25
32
|
* Characters that never appear inside a Liquid output tag but do appear in the
|
|
26
33
|
* JavaScript constructs that can be mistaken for one — most importantly a
|
|
27
34
|
* nested block, `if (a) {{ b(); }}`.
|
|
35
|
+
*
|
|
36
|
+
* Theme Check's `StaticStylesheetAndJavascriptTags` applies no such guard, so it
|
|
37
|
+
* does report that construct as Liquid. Keeping the guard here means the block
|
|
38
|
+
* is still linted as the JavaScript it is.
|
|
28
39
|
*/
|
|
29
40
|
const NOT_LIQUID_OUTPUT = /[{};]/;
|
|
30
41
|
|
|
@@ -51,6 +62,26 @@ function overlaps(intervals: Interval[], start: number, end: number): boolean {
|
|
|
51
62
|
return intervals.some((interval) => start < interval.end && end > interval.start);
|
|
52
63
|
}
|
|
53
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Whether an offset falls inside one of the intervals.
|
|
67
|
+
*
|
|
68
|
+
* Used to decide whether a `{% javascript %}` tag is itself commented out.
|
|
69
|
+
* Containment, not overlap, is the question: a block that *contains* a
|
|
70
|
+
* `{% comment %}` is still live JavaScript, and testing for overlap would throw
|
|
71
|
+
* it away.
|
|
72
|
+
*/
|
|
73
|
+
function contains(intervals: Interval[], offset: number): boolean {
|
|
74
|
+
return intervals.some((interval) => offset >= interval.start && offset < interval.end);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Every `{% comment %}` / `{% raw %}` region in the source, in order. */
|
|
78
|
+
function rawContentRegions(source: string): Interval[] {
|
|
79
|
+
return [...source.matchAll(RAW_CONTENT_TAG)].map((match) => ({
|
|
80
|
+
start: match.index,
|
|
81
|
+
end: match.index + match[0].length,
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
84
|
+
|
|
54
85
|
/**
|
|
55
86
|
* Mask the Liquid constructs inside one `{% javascript %}` body, in place.
|
|
56
87
|
*
|
|
@@ -101,8 +132,11 @@ function maskLiquidInBody(source: string, out: string[], start: number, end: num
|
|
|
101
132
|
* Reduce a Liquid source to its `{% javascript %}` content, preserving every
|
|
102
133
|
* character position.
|
|
103
134
|
*
|
|
104
|
-
* Returns `null` when the file contains no complete `{% javascript %}` block
|
|
105
|
-
* which lets the processor skip the file entirely.
|
|
135
|
+
* Returns `null` when the file contains no complete `{% javascript %}` block
|
|
136
|
+
* that is live code, which lets the processor skip the file entirely. A block
|
|
137
|
+
* that sits inside a `{% comment %}` or `{% raw %}` region does not count:
|
|
138
|
+
* commented-out blocks are common mid-migration, and linting one would report
|
|
139
|
+
* offenses in code the storefront never runs.
|
|
106
140
|
*/
|
|
107
141
|
export function maskLiquid(source: string): string | null {
|
|
108
142
|
// Indexed per UTF-16 code unit, not per code point, so that offsets stay
|
|
@@ -112,9 +146,14 @@ export function maskLiquid(source: string): string | null {
|
|
|
112
146
|
out[index] = source[index] === '\n' ? '\n' : ' ';
|
|
113
147
|
}
|
|
114
148
|
|
|
149
|
+
const rawRegions = rawContentRegions(source);
|
|
115
150
|
let found = false;
|
|
116
151
|
|
|
117
152
|
for (const match of source.matchAll(JAVASCRIPT_BLOCK)) {
|
|
153
|
+
if (contains(rawRegions, match.index)) {
|
|
154
|
+
// A commented-out or raw-quoted block is not live JavaScript.
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
118
157
|
found = true;
|
|
119
158
|
const bodyStart = match.index + match[1].length;
|
|
120
159
|
const bodyEnd = bodyStart + match[2].length;
|
|
@@ -4,9 +4,8 @@
|
|
|
4
4
|
# offenses in files modified on the current branch.
|
|
5
5
|
#
|
|
6
6
|
# Runs at --fail-level warning so every reported offense blocks, not just
|
|
7
|
-
# errors. Advisory `info` offenses stay non-blocking
|
|
8
|
-
#
|
|
9
|
-
# always yield parseable code and the agent has no reliable fix.
|
|
7
|
+
# errors. Advisory `info` offenses stay non-blocking; nothing the linters report
|
|
8
|
+
# is graded that way today.
|
|
10
9
|
set -u
|
|
11
10
|
|
|
12
11
|
input=$(cat)
|
|
@@ -4,9 +4,8 @@
|
|
|
4
4
|
# branch so Codex can fix them before trying to stop again.
|
|
5
5
|
#
|
|
6
6
|
# Runs at --fail-level warning so every reported offense blocks, not just
|
|
7
|
-
# errors. Advisory `info` offenses stay non-blocking
|
|
8
|
-
#
|
|
9
|
-
# always yield parseable code and the agent has no reliable fix.
|
|
7
|
+
# errors. Advisory `info` offenses stay non-blocking; nothing the linters report
|
|
8
|
+
# is graded that way today.
|
|
10
9
|
set -u
|
|
11
10
|
|
|
12
11
|
input=$(cat)
|
|
Binary file
|
|
@@ -5,8 +5,9 @@ set -euo pipefail
|
|
|
5
5
|
#
|
|
6
6
|
# Run automatically by the installer (`--setup`) from the installed skill
|
|
7
7
|
# directory, or manually with `bash scripts/setup.sh`. It:
|
|
8
|
-
# 1. Builds the bundled `theme-check-theory` custom-check package
|
|
9
|
-
# bundled `eslint-plugin-theory` JavaScript rules
|
|
8
|
+
# 1. Builds the bundled `theme-check-theory` custom-check package, the
|
|
9
|
+
# bundled `eslint-plugin-theory` JavaScript rules, and the bundled
|
|
10
|
+
# `stylelint-config-theory` CSS configuration (their node_modules and
|
|
10
11
|
# dist/ are intentionally not committed).
|
|
11
12
|
# 2. Writes a `.theme-check.yml` at the project root that wires the bundled
|
|
12
13
|
# checks into Shopify CLI Theme Check.
|
|
@@ -16,6 +17,7 @@ set -euo pipefail
|
|
|
16
17
|
SKILL_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
17
18
|
PKG_DIR="$SKILL_DIR/theme-check-theory"
|
|
18
19
|
ESLINT_PKG_DIR="$SKILL_DIR/eslint-plugin-theory"
|
|
20
|
+
STYLELINT_PKG_DIR="$SKILL_DIR/stylelint-config-theory"
|
|
19
21
|
|
|
20
22
|
# 1. Build the bundled custom-check package.
|
|
21
23
|
echo "shopify-lint setup: building theme-check-theory in $PKG_DIR"
|
|
@@ -32,6 +34,16 @@ cd "$ESLINT_PKG_DIR"
|
|
|
32
34
|
npm install --no-audit --no-fund
|
|
33
35
|
npm run build
|
|
34
36
|
|
|
37
|
+
# 1c. Build the bundled Stylelint configuration used to lint modified CSS.
|
|
38
|
+
#
|
|
39
|
+
# Like 1b, this runs before the install-layout guard below. No configuration is
|
|
40
|
+
# written to the project root for it: the branch lint is a gate, so a theme must
|
|
41
|
+
# not be able to silently disable it.
|
|
42
|
+
echo "shopify-lint setup: building stylelint-config-theory in $STYLELINT_PKG_DIR"
|
|
43
|
+
cd "$STYLELINT_PKG_DIR"
|
|
44
|
+
npm install --no-audit --no-fund
|
|
45
|
+
npm run build
|
|
46
|
+
|
|
35
47
|
# 2. Write the project-root .theme-check.yml.
|
|
36
48
|
#
|
|
37
49
|
# The installed layout is <project>/<.agents|.claude>/skills/shopify-lint, so
|
|
@@ -63,6 +75,13 @@ HardcodedText:
|
|
|
63
75
|
enabled: true
|
|
64
76
|
severity: warning
|
|
65
77
|
|
|
78
|
+
# Upstream check, pinned here so the branch lint always fails on it: Shopify
|
|
79
|
+
# serves `{% stylesheet %}` and `{% javascript %}` bodies verbatim, so Liquid
|
|
80
|
+
# written inside one ships to the browser as literal text.
|
|
81
|
+
StaticStylesheetAndJavascriptTags:
|
|
82
|
+
enabled: true
|
|
83
|
+
severity: error
|
|
84
|
+
|
|
66
85
|
UnusedSectionSettings:
|
|
67
86
|
enabled: false
|
|
68
87
|
severity: warning
|