climaybe 3.0.9 → 3.1.1
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/README.md +60 -3
- package/bin/version.txt +1 -1
- package/package.json +2 -2
- package/src/commands/build-schemas.js +74 -0
- package/src/cursor/rules/00-rule-index.mdc +1 -1
- package/src/cursor/rules/commit-rules.mdc +74 -74
- package/src/cursor/rules/schemas.mdc +124 -104
- package/src/cursor/rules/sections.mdc +3 -1
- package/src/cursor/skills/changelog-release/SKILL.md +7 -7
- package/src/cursor/skills/commit-in-groups/SKILL.md +6 -6
- package/src/index.js +8 -0
- package/src/lib/build-scripts.js +60 -7
- package/src/lib/dev-runtime.js +67 -3
- package/src/lib/schema-builder.js +242 -0
- package/src/lib/theme-dev-kit.js +3 -0
- package/src/lib/update-notifier.js +19 -5
- package/src/workflows/build/build-pipeline.yml +18 -2
- package/src/workflows/build/reusable-build.yml +60 -19
- package/src/workflows/multi/main-to-staging-stores.yml +4 -1
- package/src/workflows/preview/pr-update.yml +28 -0
|
@@ -1,150 +1,170 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Schema design rules —
|
|
2
|
+
description: Schema design rules and schema builder — _schemas/, partials, dynamic generation, inline markers
|
|
3
3
|
globs:
|
|
4
4
|
- "sections/**/*.liquid"
|
|
5
5
|
- "**/sections/*.liquid"
|
|
6
6
|
- "blocks/**/*.liquid"
|
|
7
7
|
- "**/blocks/*.liquid"
|
|
8
|
+
- "_schemas/**/*.js"
|
|
9
|
+
- "_schemas/**/*.json"
|
|
8
10
|
alwaysApply: false
|
|
9
11
|
---
|
|
10
|
-
# Schema Design Rules
|
|
12
|
+
# Schema Design Rules
|
|
11
13
|
|
|
12
|
-
##
|
|
14
|
+
## Schema Builder (`climaybe build-schemas`)
|
|
13
15
|
|
|
14
|
-
|
|
16
|
+
This project uses the climaybe schema builder to generate schemas from JavaScript/JSON source files. Schemas are defined in `_schemas/` and injected into `sections/*.liquid` and `blocks/*.liquid` at build time.
|
|
17
|
+
|
|
18
|
+
### How it works
|
|
19
|
+
|
|
20
|
+
A section or block file contains an inline-comment marker at the end:
|
|
21
|
+
|
|
22
|
+
```liquid
|
|
23
|
+
<section class="hero">{{ section.settings.title }}</section>
|
|
24
|
+
|
|
25
|
+
{% # schema 'hero-banner' %}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`{% # ... %}` is a Liquid inline comment — Shopify ignores it. The builder finds the marker, resolves `_schemas/hero-banner.js` (or `.json`), and writes the generated `{% schema %}...{% endschema %}` block below it. On rebuild, only the generated block is replaced. Everything above the marker (including theme editor changes) is preserved.
|
|
29
|
+
|
|
30
|
+
### Directory structure
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
_schemas/ ← schema definitions (JS or JSON)
|
|
34
|
+
├── partials/ ← shared settings, fieldsets, factory functions
|
|
35
|
+
│ ├── link.js
|
|
36
|
+
│ ├── create-links.js
|
|
37
|
+
│ └── spacing.js
|
|
38
|
+
├── hero-banner.js ← referenced by sections via marker
|
|
39
|
+
├── landing-page.js
|
|
40
|
+
└── page-schema.js
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### When creating or editing schemas
|
|
44
|
+
|
|
45
|
+
1. **Define schemas in `_schemas/`**, not inline in section files. The `{% schema %}...{% endschema %}` block in `sections/` is generated output — never edit it directly.
|
|
46
|
+
|
|
47
|
+
2. **Use JS files** (CommonJS `module.exports`) when you need imports, computation, or comments. Use JSON for simple static schemas.
|
|
48
|
+
|
|
49
|
+
3. **Extract shared settings into `_schemas/partials/`** — these are imported via `require()` and are never referenced directly by section markers.
|
|
50
|
+
|
|
51
|
+
4. **Spread partials into settings arrays:**
|
|
52
|
+
```js
|
|
53
|
+
const linkSettings = require('./partials/link.js');
|
|
54
|
+
const spacingSettings = require('./partials/spacing.js');
|
|
55
|
+
|
|
56
|
+
module.exports = {
|
|
57
|
+
name: 'Featured Collection',
|
|
58
|
+
settings: [
|
|
59
|
+
{ type: 'text', id: 'title', label: 'Heading' },
|
|
60
|
+
...linkSettings,
|
|
61
|
+
...spacingSettings,
|
|
62
|
+
],
|
|
63
|
+
};
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
5. **Use factory functions for repeated field groups:**
|
|
67
|
+
```js
|
|
68
|
+
const createLinks = require('./partials/create-links.js');
|
|
69
|
+
|
|
70
|
+
module.exports = {
|
|
71
|
+
name: 'Hero',
|
|
72
|
+
settings: [
|
|
73
|
+
{ type: 'text', id: 'heading', label: 'Heading' },
|
|
74
|
+
...createLinks(3),
|
|
75
|
+
],
|
|
76
|
+
};
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
6. **Use function exports for per-section overrides:**
|
|
80
|
+
```js
|
|
81
|
+
module.exports = function (filename, content) {
|
|
82
|
+
return {
|
|
83
|
+
name: content.name || filename.replace('.liquid', ''),
|
|
84
|
+
settings: [/* ... */],
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The function receives `(filename, inlineContent)`. Inline content comes from a second inline comment in the section file:
|
|
90
|
+
```liquid
|
|
91
|
+
{% # schema 'page-schema' %}
|
|
92
|
+
{% # { "name": "About Us" } %}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
7. **For static exports**, inline JSON is shallow-merged on top (inline wins):
|
|
96
|
+
```liquid
|
|
97
|
+
{% # schema 'newsletter' %}
|
|
98
|
+
{% # { "name": "Footer Newsletter" } %}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Commands
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
npx climaybe build-schemas # generate schemas in sections/
|
|
105
|
+
npx climaybe build-schemas --dry-run # preview without writing
|
|
106
|
+
npx climaybe build-schemas --list # list schema files and markers
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Schemas also rebuild automatically during `climaybe serve` (watch mode, tagged `[schema]` in console).
|
|
110
|
+
|
|
111
|
+
### Rules for AI assistants
|
|
112
|
+
|
|
113
|
+
- When asked to create a new section or block with a schema, create the schema definition in `_schemas/` and add the `{% # schema 'name' %}` marker to the liquid file. Do not write raw `{% schema %}` JSON.
|
|
114
|
+
- When asked to edit a schema, edit the file in `_schemas/`, not the generated block in `sections/` or `blocks/`.
|
|
115
|
+
- When adding fields that are reused across schemas (links, spacing, colors), extract them into `_schemas/partials/` and spread them.
|
|
116
|
+
- When duplicating a schema for a variant section, use a shared schema file instead. Add the same marker to both sections.
|
|
117
|
+
- After editing `_schemas/` files, remind the user to run `npx climaybe build-schemas` (or note that `climaybe serve` rebuilds automatically).
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Schema Design Principles
|
|
122
|
+
|
|
123
|
+
### 1. Start Simple, Expand Gradually
|
|
15
124
|
- Begin with minimal, essential settings only
|
|
16
125
|
- Add complexity only when there's a clear, demonstrated need
|
|
17
|
-
- Ask for permission before adding multiple settings at once
|
|
18
126
|
|
|
19
|
-
### 2.
|
|
127
|
+
### 2. Avoid Redundancy
|
|
20
128
|
- If text content exists in translation files (`en.default.json`), don't duplicate it in schema
|
|
21
129
|
- Use translation keys for all text content, not schema text inputs
|
|
22
130
|
- Schema should focus on functionality, not content
|
|
23
131
|
|
|
24
|
-
### 3.
|
|
132
|
+
### 3. Question Every Setting
|
|
25
133
|
Before adding any schema setting, ask:
|
|
26
134
|
- Is this truly necessary for the user?
|
|
27
135
|
- Can this be handled by translation files instead?
|
|
28
136
|
- Will this setting be used by most merchants?
|
|
29
137
|
- Does this add value or just complexity?
|
|
30
138
|
|
|
31
|
-
### 4.
|
|
139
|
+
### 4. Limit Settings Per Section
|
|
32
140
|
- **Maximum 5 settings per section** (excluding headers)
|
|
33
141
|
- If more are needed, consider splitting into multiple sections
|
|
34
142
|
- Group related settings under headers
|
|
35
143
|
|
|
36
144
|
## Schema Setting Guidelines
|
|
37
145
|
|
|
38
|
-
###
|
|
146
|
+
### Essential Settings (Always Include)
|
|
39
147
|
- Link list selectors for menus
|
|
40
148
|
- Image upload fields for key visuals
|
|
41
149
|
- Color scheme selectors (if theme supports multiple schemes)
|
|
42
150
|
|
|
43
|
-
###
|
|
151
|
+
### Optional Settings (Add Only When Needed)
|
|
44
152
|
- Layout toggles (show/hide elements)
|
|
45
153
|
- Spacing adjustments
|
|
46
154
|
- Text alignment options
|
|
47
155
|
- Background color/image options
|
|
48
156
|
|
|
49
|
-
###
|
|
157
|
+
### Avoid These Settings
|
|
50
158
|
- Text content inputs (use translation files)
|
|
51
159
|
- Toggle switches for every element
|
|
52
160
|
- Complex layout selectors
|
|
53
161
|
- Settings that duplicate existing functionality
|
|
54
162
|
|
|
55
|
-
## Implementation Rules
|
|
56
|
-
|
|
57
|
-
### **Before Adding Settings**
|
|
58
|
-
1. Check if the functionality can be achieved through:
|
|
59
|
-
- Translation files
|
|
60
|
-
- CSS classes
|
|
61
|
-
- Existing theme settings
|
|
62
|
-
- Liquid conditionals
|
|
63
|
-
|
|
64
|
-
2. Consider the user experience:
|
|
65
|
-
- Will merchants actually use this setting?
|
|
66
|
-
- Is it intuitive and discoverable?
|
|
67
|
-
- Does it solve a real problem?
|
|
68
|
-
|
|
69
|
-
3. Evaluate maintenance cost:
|
|
70
|
-
- Will this setting need updates?
|
|
71
|
-
- Does it create technical debt?
|
|
72
|
-
- Is it worth the complexity?
|
|
73
|
-
|
|
74
|
-
### **When Adding Multiple Settings**
|
|
75
|
-
- **Ask for permission first**
|
|
76
|
-
- Explain the reasoning for each setting
|
|
77
|
-
- Consider if they can be grouped or simplified
|
|
78
|
-
- Test with a small subset first
|
|
79
|
-
|
|
80
|
-
## Examples
|
|
81
|
-
|
|
82
|
-
### ✅ Good Schema (Simple)
|
|
83
|
-
```json
|
|
84
|
-
{
|
|
85
|
-
"settings": [
|
|
86
|
-
{
|
|
87
|
-
"type": "link_list",
|
|
88
|
-
"id": "main_menu",
|
|
89
|
-
"label": "Main menu"
|
|
90
|
-
},
|
|
91
|
-
{
|
|
92
|
-
"type": "image_picker",
|
|
93
|
-
"id": "logo",
|
|
94
|
-
"label": "Logo"
|
|
95
|
-
}
|
|
96
|
-
]
|
|
97
|
-
}
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
### ❌ Bad Schema (Over-engineered)
|
|
101
|
-
```json
|
|
102
|
-
{
|
|
103
|
-
"settings": [
|
|
104
|
-
{
|
|
105
|
-
"type": "link_list",
|
|
106
|
-
"id": "main_menu",
|
|
107
|
-
"label": "Main menu"
|
|
108
|
-
},
|
|
109
|
-
{
|
|
110
|
-
"type": "checkbox",
|
|
111
|
-
"id": "show_menu",
|
|
112
|
-
"label": "Show menu",
|
|
113
|
-
"default": true
|
|
114
|
-
},
|
|
115
|
-
{
|
|
116
|
-
"type": "select",
|
|
117
|
-
"id": "menu_style",
|
|
118
|
-
"label": "Menu style",
|
|
119
|
-
"options": [
|
|
120
|
-
{"value": "horizontal", "label": "Horizontal"},
|
|
121
|
-
{"value": "vertical", "label": "Vertical"},
|
|
122
|
-
{"value": "dropdown", "label": "Dropdown"}
|
|
123
|
-
]
|
|
124
|
-
},
|
|
125
|
-
{
|
|
126
|
-
"type": "range",
|
|
127
|
-
"id": "menu_spacing",
|
|
128
|
-
"min": 0,
|
|
129
|
-
"max": 50,
|
|
130
|
-
"step": 5,
|
|
131
|
-
"label": "Menu spacing"
|
|
132
|
-
}
|
|
133
|
-
]
|
|
134
|
-
}
|
|
135
|
-
```
|
|
136
|
-
|
|
137
163
|
## Review Process
|
|
138
164
|
|
|
139
165
|
Before committing any schema changes:
|
|
140
|
-
1. **Count the settings**
|
|
141
|
-
2. **Check for redundancy**
|
|
142
|
-
3. **Verify necessity**
|
|
143
|
-
4. **
|
|
144
|
-
5. **
|
|
145
|
-
|
|
146
|
-
## Remember
|
|
147
|
-
- **Simplicity is a feature**
|
|
148
|
-
- **Less is more**
|
|
149
|
-
- **When in doubt, leave it out**
|
|
150
|
-
- **Ask before expanding**
|
|
166
|
+
1. **Count the settings** — keep under 5 per section
|
|
167
|
+
2. **Check for redundancy** — ensure no duplicate functionality
|
|
168
|
+
3. **Verify necessity** — each setting should solve a real problem
|
|
169
|
+
4. **Check partials** — could this field group be extracted and shared?
|
|
170
|
+
5. **Consider alternatives** — could this be handled via translations, CSS, or existing settings?
|
|
@@ -11,11 +11,13 @@ alwaysApply: false
|
|
|
11
11
|
|
|
12
12
|
Every section must include:
|
|
13
13
|
|
|
14
|
-
- `{% schema %}` tag with valid JSON
|
|
14
|
+
- Schema definition — either a `{% schema %}` tag with valid JSON, or a `{% # schema 'name' %}` marker referencing a schema from `_schemas/` (see `schemas.mdc` for the schema builder)
|
|
15
15
|
- Proper HTML semantic structure
|
|
16
16
|
- CSS scoping with section classes
|
|
17
17
|
- Translation keys for all text
|
|
18
18
|
|
|
19
|
+
When using the schema builder, add the inline-comment marker at the end of the section file and define the schema in `_schemas/`. The generated `{% schema %}...{% endschema %}` block is build output — do not edit it directly.
|
|
20
|
+
|
|
19
21
|
## Section Patterns
|
|
20
22
|
|
|
21
23
|
_(To be filled in on review.)_
|
|
@@ -12,9 +12,9 @@ Builds a changelog or release notes from commits since a ref (e.g. last tag), gr
|
|
|
12
12
|
When grouping and labeling commits, read and apply:
|
|
13
13
|
|
|
14
14
|
1. `.cursor/rules/00-rule-index.mdc` — rule index
|
|
15
|
-
2. `.cursor/rules/commit-rules.mdc` — commit types (
|
|
15
|
+
2. `.cursor/rules/commit-rules.mdc` — commit types (fix, feat, refactor, style, etc.) and format
|
|
16
16
|
|
|
17
|
-
Use the same type labels
|
|
17
|
+
Use the same type labels for changelog sections so the output matches how the team writes commits.
|
|
18
18
|
|
|
19
19
|
## Workflow
|
|
20
20
|
|
|
@@ -23,8 +23,8 @@ Use the same type labels and emoji for changelog sections so the output matches
|
|
|
23
23
|
3. **Group by type** — Map each commit to a type from commit-rules.mdc (fix, feat, refactor, style, remove, wip, docs, ai, chore, upgrade). Ignore merge commits. Put "unknown" or "other" for non-matching messages.
|
|
24
24
|
4. **Format** — Output markdown:
|
|
25
25
|
- Optional title (e.g. "Changelog since v1.2.0")
|
|
26
|
-
- Sections by type (e.g. "##
|
|
27
|
-
- Under each section: list of commit descriptions (one line each, link to commit if desired).
|
|
26
|
+
- Sections by type (e.g. "## Features", "## Fixes")
|
|
27
|
+
- Under each section: list of commit descriptions (one line each, link to commit if desired).
|
|
28
28
|
5. **Optional** — Add "Breaking changes" subsection if any commit message indicates breaking change (e.g. `!` or "BREAKING CHANGE").
|
|
29
29
|
|
|
30
30
|
## Output Format
|
|
@@ -32,15 +32,15 @@ Use the same type labels and emoji for changelog sections so the output matches
|
|
|
32
32
|
```markdown
|
|
33
33
|
# Changelog (since [ref])
|
|
34
34
|
|
|
35
|
-
##
|
|
35
|
+
## Features
|
|
36
36
|
- add product quick view modal
|
|
37
37
|
- implement infinite scroll for collections
|
|
38
38
|
|
|
39
|
-
##
|
|
39
|
+
## Fixes
|
|
40
40
|
- resolve cart total calculation error
|
|
41
41
|
- modal close button not working
|
|
42
42
|
|
|
43
|
-
##
|
|
43
|
+
## Refactor
|
|
44
44
|
- optimize product card rendering
|
|
45
45
|
```
|
|
46
46
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: commit-in-groups
|
|
3
|
-
description: Groups current working tree changes into logical commits and suggests commit messages per project convention. Use when the user says "/commit-in-groups", "commit in groups", "group my commits", "suggest separate commits", or "split my changes into commits". Follows commit-rules.mdc (
|
|
3
|
+
description: Groups current working tree changes into logical commits and suggests commit messages per project convention. Use when the user says "/commit-in-groups", "commit in groups", "group my commits", "suggest separate commits", or "split my changes into commits". Follows commit-rules.mdc (type + description).
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Commit in Groups
|
|
@@ -14,7 +14,7 @@ Analyzes uncommitted changes, groups them into logical commits, and suggests one
|
|
|
14
14
|
Before suggesting any commit messages, read and apply:
|
|
15
15
|
|
|
16
16
|
1. `.cursor/rules/00-rule-index.mdc` — rule index
|
|
17
|
-
2. `.cursor/rules/commit-rules.mdc` — commit format (
|
|
17
|
+
2. `.cursor/rules/commit-rules.mdc` — commit format (type + description), types (fix, feat, refactor, style, etc.), imperative mood, optional scope
|
|
18
18
|
|
|
19
19
|
Use the exact format and types from commit-rules; no period at end; lowercase description.
|
|
20
20
|
|
|
@@ -27,8 +27,8 @@ Use the exact format and types from commit-rules; no period at end; lowercase de
|
|
|
27
27
|
- Refactor in one area, fix in another → separate commits
|
|
28
28
|
- Locale/schema/docs can be separate if they form a clear unit
|
|
29
29
|
- When in doubt, split rather than combine
|
|
30
|
-
3. **Assign type** — For each group, pick the best type from commit-rules (
|
|
31
|
-
4. **Write messages** — One line per commit: `<
|
|
30
|
+
3. **Assign type** — For each group, pick the best type from commit-rules (fix, feat, refactor, style, remove, docs, chore, etc.) and optional scope (e.g. `sections`, `cart`, `locales`).
|
|
31
|
+
4. **Write messages** — One line per commit: `<type>(scope): <description>`. Imperative, lowercase, no period. Multi-line body only if the change is complex and warrants bullets.
|
|
32
32
|
5. **Output and execute** — List the suggested commits (message + files per commit), then **run the commits yourself**: for each group, run `git add` on the listed files and `git commit -m "..."` with the exact message. Do not only suggest commands; execute them so the working tree is left with the changes committed. Summarize what was committed at the end.
|
|
33
33
|
|
|
34
34
|
## Output Format
|
|
@@ -38,11 +38,11 @@ Show the plan briefly, then run it:
|
|
|
38
38
|
```markdown
|
|
39
39
|
## Suggested commits
|
|
40
40
|
|
|
41
|
-
**Commit 1:**
|
|
41
|
+
**Commit 1:** `feat(sections): add featured collection section`
|
|
42
42
|
- `sections/s--featured-collection.liquid`
|
|
43
43
|
- `snippets/m--product-card.liquid`
|
|
44
44
|
|
|
45
|
-
**Commit 2:**
|
|
45
|
+
**Commit 2:** `style(cart): adjust drawer spacing`
|
|
46
46
|
- `sections/cart--drawer.liquid`
|
|
47
47
|
- `assets/style.css`
|
|
48
48
|
```
|
package/src/index.js
CHANGED
|
@@ -12,6 +12,7 @@ import { appInitCommand } from './commands/app-init.js';
|
|
|
12
12
|
import { migrateLegacyConfigCommand } from './commands/migrate-legacy-config.js';
|
|
13
13
|
import { buildScriptsCommand } from './commands/build-scripts.js';
|
|
14
14
|
import { createEntrypointsCommand } from './commands/create-entrypoints.js';
|
|
15
|
+
import { buildSchemasCommand } from './commands/build-schemas.js';
|
|
15
16
|
import { serveAll, serveAssets, serveShopify, lintAll, buildAll } from './lib/dev-runtime.js';
|
|
16
17
|
|
|
17
18
|
/**
|
|
@@ -84,6 +85,13 @@ function registerThemeCommands(cmd) {
|
|
|
84
85
|
.description('Create _scripts/main.js and _styles/main.css (optional)')
|
|
85
86
|
.action(createEntrypointsCommand);
|
|
86
87
|
|
|
88
|
+
cmd
|
|
89
|
+
.command('build-schemas')
|
|
90
|
+
.description('Generate schemas from _schemas/ JS/JSON into sections/ and blocks/ (uses inline-comment markers)')
|
|
91
|
+
.option('--dry-run', 'Show what would be injected without writing files')
|
|
92
|
+
.option('--list', 'List available schema files and section references')
|
|
93
|
+
.action(buildSchemasCommand);
|
|
94
|
+
|
|
87
95
|
cmd
|
|
88
96
|
.command('update')
|
|
89
97
|
.alias('update-workflows')
|
package/src/lib/build-scripts.js
CHANGED
|
@@ -1,25 +1,29 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync, readdirSync, mkdirSync } from 'node:fs';
|
|
2
2
|
import { join, basename, dirname, normalize } from 'node:path';
|
|
3
3
|
|
|
4
|
-
function
|
|
4
|
+
function extractImportRecords(content) {
|
|
5
5
|
const imports = [];
|
|
6
6
|
// Supports compact imports (import{a}from"./x"), multiline forms,
|
|
7
7
|
// and import attributes (with { type: "json" }).
|
|
8
8
|
const fromImportRegex =
|
|
9
|
-
/(^|\n)\s*import(?:\s+type)?\s*[\s\S]
|
|
9
|
+
/(^|\n)\s*import(?:\s+type)?\s*([\s\S]*?)\s*\bfrom\b\s*['"]([^'"]+)['"](?:\s+with\s*\{[\s\S]*?\})?\s*;?/g;
|
|
10
10
|
const sideEffectImportRegex = /(^|\n)\s*import\s*['"]([^'"]+)['"](?:\s+with\s*\{[\s\S]*?\})?\s*;?/g;
|
|
11
11
|
let match;
|
|
12
12
|
|
|
13
13
|
while ((match = fromImportRegex.exec(content)) !== null) {
|
|
14
|
-
imports.push(match[2]);
|
|
14
|
+
imports.push({ importPath: match[3], hasBindings: Boolean(match[2]?.trim()) });
|
|
15
15
|
}
|
|
16
16
|
while ((match = sideEffectImportRegex.exec(content)) !== null) {
|
|
17
|
-
imports.push(match[2]);
|
|
17
|
+
imports.push({ importPath: match[2], hasBindings: false });
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
return imports;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
function extractImports(content) {
|
|
24
|
+
return extractImportRecords(content).map((record) => record.importPath);
|
|
25
|
+
}
|
|
26
|
+
|
|
23
27
|
function stripModuleSyntax(content) {
|
|
24
28
|
// Remove import statements (including multiline/compact forms and import attributes).
|
|
25
29
|
let cleaned = content.replace(
|
|
@@ -58,7 +62,7 @@ function minifyScriptContent(content) {
|
|
|
58
62
|
return minified;
|
|
59
63
|
}
|
|
60
64
|
|
|
61
|
-
function processScriptFile({ scriptsDir, filePath, processedFiles, minify = false }) {
|
|
65
|
+
function processScriptFile({ scriptsDir, filePath, processedFiles, minify = false, isolateFiles = new Set() }) {
|
|
62
66
|
if (processedFiles.has(filePath)) return '';
|
|
63
67
|
processedFiles.add(filePath);
|
|
64
68
|
|
|
@@ -78,11 +82,20 @@ function processScriptFile({ scriptsDir, filePath, processedFiles, minify = fals
|
|
|
78
82
|
for (const importPath of imports) {
|
|
79
83
|
const resolvedImport = resolveImportPath(filePath, importPath);
|
|
80
84
|
if (!resolvedImport) continue;
|
|
81
|
-
importedContent += processScriptFile({
|
|
85
|
+
importedContent += processScriptFile({
|
|
86
|
+
scriptsDir,
|
|
87
|
+
filePath: resolvedImport,
|
|
88
|
+
processedFiles,
|
|
89
|
+
minify,
|
|
90
|
+
isolateFiles
|
|
91
|
+
});
|
|
82
92
|
}
|
|
83
93
|
|
|
84
94
|
content = stripModuleSyntax(content);
|
|
85
95
|
if (minify) content = minifyScriptContent(content);
|
|
96
|
+
if (isolateFiles.has(filePath)) {
|
|
97
|
+
content = `(function () {\n${content.trim()}\n})();`;
|
|
98
|
+
}
|
|
86
99
|
|
|
87
100
|
return importedContent + '\n' + content;
|
|
88
101
|
}
|
|
@@ -113,6 +126,39 @@ function collectImportedFiles({ scriptsDir, entryFile, seen = new Set() }) {
|
|
|
113
126
|
return seen;
|
|
114
127
|
}
|
|
115
128
|
|
|
129
|
+
function collectFilesToIsolate({ scriptsDir, entryFile }) {
|
|
130
|
+
const seen = new Set();
|
|
131
|
+
const importedWithBindings = new Set();
|
|
132
|
+
|
|
133
|
+
function visit(filePath) {
|
|
134
|
+
if (seen.has(filePath)) return;
|
|
135
|
+
seen.add(filePath);
|
|
136
|
+
|
|
137
|
+
const fullPath = join(scriptsDir, filePath);
|
|
138
|
+
if (!existsSync(fullPath)) return;
|
|
139
|
+
const content = readFileSync(fullPath, 'utf8');
|
|
140
|
+
const imports = extractImportRecords(content);
|
|
141
|
+
|
|
142
|
+
for (const record of imports) {
|
|
143
|
+
const resolved = resolveImportPath(filePath, record.importPath);
|
|
144
|
+
if (!resolved) continue;
|
|
145
|
+
if (record.hasBindings) importedWithBindings.add(resolved);
|
|
146
|
+
visit(resolved);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
visit(entryFile);
|
|
151
|
+
|
|
152
|
+
const isolateFiles = new Set();
|
|
153
|
+
for (const file of seen) {
|
|
154
|
+
if (file !== entryFile && !importedWithBindings.has(file)) {
|
|
155
|
+
isolateFiles.add(file);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return isolateFiles;
|
|
160
|
+
}
|
|
161
|
+
|
|
116
162
|
function listTopLevelEntrypoints(scriptsDir) {
|
|
117
163
|
if (!existsSync(scriptsDir)) return [];
|
|
118
164
|
return readdirSync(scriptsDir, { withFileTypes: true })
|
|
@@ -134,7 +180,14 @@ function buildSingleEntrypoint({ cwd, entryFile, minify = false }) {
|
|
|
134
180
|
}
|
|
135
181
|
|
|
136
182
|
const processedFiles = new Set();
|
|
137
|
-
|
|
183
|
+
const isolateFiles = collectFilesToIsolate({ scriptsDir, entryFile });
|
|
184
|
+
let finalContent = processScriptFile({
|
|
185
|
+
scriptsDir,
|
|
186
|
+
filePath: entryFile,
|
|
187
|
+
processedFiles,
|
|
188
|
+
minify,
|
|
189
|
+
isolateFiles
|
|
190
|
+
});
|
|
138
191
|
finalContent = stripModuleSyntax(finalContent);
|
|
139
192
|
if (minify) finalContent = minifyScriptContent(finalContent);
|
|
140
193
|
|
package/src/lib/dev-runtime.js
CHANGED
|
@@ -5,6 +5,7 @@ import { isAbsolute, join, relative } from 'node:path';
|
|
|
5
5
|
import pc from 'picocolors';
|
|
6
6
|
import { readConfig } from './config.js';
|
|
7
7
|
import { buildScripts } from './build-scripts.js';
|
|
8
|
+
import { buildSchemas } from './schema-builder.js';
|
|
8
9
|
import { runShopify } from './shopify-cli.js';
|
|
9
10
|
|
|
10
11
|
function tagLabel(tag, color = (s) => s) {
|
|
@@ -260,6 +261,49 @@ export function serveAssets({ cwd = process.cwd(), includeThemeCheck = false } =
|
|
|
260
261
|
})
|
|
261
262
|
: null;
|
|
262
263
|
|
|
264
|
+
const schemasDir = join(cwd, '_schemas');
|
|
265
|
+
const hasSchemas = existsSync(schemasDir);
|
|
266
|
+
if (hasSchemas) {
|
|
267
|
+
try {
|
|
268
|
+
const result = buildSchemas({ cwd });
|
|
269
|
+
if (result.processed.length > 0) {
|
|
270
|
+
writeTaggedLine('schema', pc.green, `built ${result.processed.length} file(s) (initial)`);
|
|
271
|
+
}
|
|
272
|
+
if (result.errors.length > 0) {
|
|
273
|
+
for (const e of result.errors) {
|
|
274
|
+
writeTaggedLine('schema', pc.green, `error: ${e.section} — ${e.error}`, process.stderr);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
} catch (err) {
|
|
278
|
+
writeTaggedLine('schema', pc.green, `initial build failed: ${err.message}`, process.stderr);
|
|
279
|
+
}
|
|
280
|
+
} else {
|
|
281
|
+
writeTaggedLine('schema', pc.green, 'skipped (missing _schemas/)');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const schemasWatch = hasSchemas
|
|
285
|
+
? watchTree({
|
|
286
|
+
rootDir: schemasDir,
|
|
287
|
+
ignore: (p) => p.includes('node_modules') || p.includes('/.git/'),
|
|
288
|
+
debounceMs: 300,
|
|
289
|
+
onChange: () => {
|
|
290
|
+
try {
|
|
291
|
+
const result = buildSchemas({ cwd });
|
|
292
|
+
if (result.processed.length > 0) {
|
|
293
|
+
writeTaggedLine('schema', pc.green, `rebuilt ${result.processed.length} file(s)`);
|
|
294
|
+
}
|
|
295
|
+
if (result.errors.length > 0) {
|
|
296
|
+
for (const e of result.errors) {
|
|
297
|
+
writeTaggedLine('schema', pc.green, `error: ${e.section} — ${e.error}`, process.stderr);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
} catch (err) {
|
|
301
|
+
writeTaggedLine('schema', pc.green, `build failed: ${err.message}`, process.stderr);
|
|
302
|
+
}
|
|
303
|
+
},
|
|
304
|
+
})
|
|
305
|
+
: null;
|
|
306
|
+
|
|
263
307
|
let themeCheckRunning = false;
|
|
264
308
|
let themeCheckQueued = false;
|
|
265
309
|
const runThemeCheck = () => {
|
|
@@ -287,7 +331,8 @@ export function serveAssets({ cwd = process.cwd(), includeThemeCheck = false } =
|
|
|
287
331
|
p.includes('/assets/') ||
|
|
288
332
|
p.includes('/.git/') ||
|
|
289
333
|
p.includes('/_scripts/') ||
|
|
290
|
-
p.includes('/_styles/')
|
|
334
|
+
p.includes('/_styles/') ||
|
|
335
|
+
p.includes('/_schemas/'),
|
|
291
336
|
debounceMs: 800,
|
|
292
337
|
onChange: () => {
|
|
293
338
|
runThemeCheck();
|
|
@@ -304,12 +349,13 @@ export function serveAssets({ cwd = process.cwd(), includeThemeCheck = false } =
|
|
|
304
349
|
|
|
305
350
|
const cleanup = () => {
|
|
306
351
|
safeClose(scriptsWatch);
|
|
352
|
+
safeClose(schemasWatch);
|
|
307
353
|
safeClose(themeCheckWatch);
|
|
308
354
|
safeKill(tailwind);
|
|
309
355
|
safeKill(devMcp);
|
|
310
356
|
};
|
|
311
357
|
|
|
312
|
-
return { tailwind, devMcp, scriptsWatch, themeCheckWatch, cleanup };
|
|
358
|
+
return { tailwind, devMcp, scriptsWatch, schemasWatch, themeCheckWatch, cleanup };
|
|
313
359
|
}
|
|
314
360
|
|
|
315
361
|
export function serveAll({ cwd = process.cwd(), includeThemeCheck = false } = {}) {
|
|
@@ -355,6 +401,24 @@ export function lintAll({ cwd = process.cwd() } = {}) {
|
|
|
355
401
|
|
|
356
402
|
export function buildAll({ cwd = process.cwd() } = {}) {
|
|
357
403
|
const env = { ...process.env, NODE_ENV: 'production' };
|
|
404
|
+
|
|
405
|
+
let schemasOk = true;
|
|
406
|
+
try {
|
|
407
|
+
const schemaResult = buildSchemas({ cwd });
|
|
408
|
+
if (schemaResult.processed.length > 0) {
|
|
409
|
+
writeTaggedLine('schema', pc.green, `built ${schemaResult.processed.length} section(s)`);
|
|
410
|
+
}
|
|
411
|
+
if (schemaResult.errors.length > 0) {
|
|
412
|
+
for (const e of schemaResult.errors) {
|
|
413
|
+
writeTaggedLine('schema', pc.green, `error: ${e.section} — ${e.error}`, process.stderr);
|
|
414
|
+
}
|
|
415
|
+
schemasOk = false;
|
|
416
|
+
}
|
|
417
|
+
} catch (err) {
|
|
418
|
+
console.log(pc.red(`\n build-schemas failed: ${err.message}\n`));
|
|
419
|
+
schemasOk = false;
|
|
420
|
+
}
|
|
421
|
+
|
|
358
422
|
let scriptsOk = true;
|
|
359
423
|
try {
|
|
360
424
|
buildScripts({ cwd });
|
|
@@ -367,6 +431,6 @@ export function buildAll({ cwd = process.cwd() } = {}) {
|
|
|
367
431
|
env,
|
|
368
432
|
name: 'tailwind',
|
|
369
433
|
});
|
|
370
|
-
return { scriptsOk, tailwind };
|
|
434
|
+
return { schemasOk, scriptsOk, tailwind };
|
|
371
435
|
}
|
|
372
436
|
|