@augeo/smelt 1.2.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.
Files changed (152) hide show
  1. package/.claude/settings.json +11 -0
  2. package/.github/workflows/verify.yml +64 -0
  3. package/.gitmodules +3 -0
  4. package/.prettierignore +25 -0
  5. package/.prettierrc.cjs +9 -0
  6. package/.zed/settings.json +21 -0
  7. package/AGENTS.md +232 -0
  8. package/LICENSE +21 -0
  9. package/README.md +266 -0
  10. package/biome.json +58 -0
  11. package/dist/cli.d.mts +1 -0
  12. package/dist/cli.mjs +350 -0
  13. package/dist/schema.d.mts +265 -0
  14. package/dist/schema.mjs +21 -0
  15. package/docs/TESTING.md +293 -0
  16. package/docs/assets-plan.md +197 -0
  17. package/docs/build-spec.md +466 -0
  18. package/docs/library-conversion-plan.md +419 -0
  19. package/example/.gitattributes +7 -0
  20. package/example/.shopifyignore +28 -0
  21. package/example/.theme-check.yml +7 -0
  22. package/example/blocks/_built--sections--hero--blocks--feature.liquid +52 -0
  23. package/example/config/settings_schema.json +10 -0
  24. package/example/layout/theme.liquid +25 -0
  25. package/example/locales/en.default.json +1 -0
  26. package/example/package-lock.json +51 -0
  27. package/example/package.json +20 -0
  28. package/example/sections/built--sections--hero.liquid +83 -0
  29. package/example/snippets/built--components--button.liquid +38 -0
  30. package/example/snippets/built--components--card.liquid +33 -0
  31. package/example/src/components/button/button.css +13 -0
  32. package/example/src/components/card/card.css +16 -0
  33. package/example/src/components/card/card.liquid +9 -0
  34. package/example/src/sections/hero/blocks/feature/feature.css +11 -0
  35. package/example/src/sections/hero/blocks/feature/feature.liquid +9 -0
  36. package/example/src/sections/hero/blocks/feature/feature.schema.ts +14 -0
  37. package/example/src/sections/hero/hero.css +15 -0
  38. package/example/src/sections/hero/hero.liquid +16 -0
  39. package/example/src/sections/hero/hero.schema.ts +26 -0
  40. package/example/src/sections/hero/hero.test.ts +43 -0
  41. package/example/src/utilities/labels.ts +5 -0
  42. package/example/templates/index.liquid +1 -0
  43. package/example/tsconfig.json +10 -0
  44. package/example/vitest.config.ts +6 -0
  45. package/lib/build/build.test.ts +475 -0
  46. package/lib/build/build.ts +314 -0
  47. package/lib/build/command.ts +27 -0
  48. package/lib/build/index.ts +1 -0
  49. package/lib/cli.ts +17 -0
  50. package/lib/dev/command.ts +25 -0
  51. package/lib/dev/index.ts +1 -0
  52. package/lib/dev/watch.ts +52 -0
  53. package/lib/resolver.test.ts +275 -0
  54. package/lib/resolver.ts +156 -0
  55. package/lib/schema.ts +37 -0
  56. package/package.json +59 -0
  57. package/scripts/codegen-schema.ts +66 -0
  58. package/src/components/button/button.css +13 -0
  59. package/src/components/button/button.liquid +5 -0
  60. package/src/components/button/button.ts +5 -0
  61. package/src/tsconfig.json +10 -0
  62. package/tests/example.test.ts +101 -0
  63. package/tsconfig.json +20 -0
  64. package/tsdown.config.ts +14 -0
  65. package/vendor/theme-liquid-docs/.gitattributes +10 -0
  66. package/vendor/theme-liquid-docs/.github/CODEOWNERS +1 -0
  67. package/vendor/theme-liquid-docs/.github/CODE_OF_CONDUCT.md +73 -0
  68. package/vendor/theme-liquid-docs/.github/ISSUE_TEMPLATE/bug_report.md +17 -0
  69. package/vendor/theme-liquid-docs/.github/ISSUE_TEMPLATE/feature_request.md +17 -0
  70. package/vendor/theme-liquid-docs/.github/dependabot.yaml +6 -0
  71. package/vendor/theme-liquid-docs/.github/workflows/ci.yml +33 -0
  72. package/vendor/theme-liquid-docs/.github/workflows/cla.yml +27 -0
  73. package/vendor/theme-liquid-docs/.github/workflows/shopify-dev-preview-automation.yml +86 -0
  74. package/vendor/theme-liquid-docs/.github/workflows/update-latest.yml +56 -0
  75. package/vendor/theme-liquid-docs/.prettierrc.json +16 -0
  76. package/vendor/theme-liquid-docs/.vscode/settings.json +28 -0
  77. package/vendor/theme-liquid-docs/LICENSE.md +7 -0
  78. package/vendor/theme-liquid-docs/README.md +48 -0
  79. package/vendor/theme-liquid-docs/ai/claude/CLAUDE.md +1485 -0
  80. package/vendor/theme-liquid-docs/ai/cursor/rules/assets.mdc +15 -0
  81. package/vendor/theme-liquid-docs/ai/cursor/rules/blocks.mdc +339 -0
  82. package/vendor/theme-liquid-docs/ai/cursor/rules/examples/block-example-group.mdc +103 -0
  83. package/vendor/theme-liquid-docs/ai/cursor/rules/examples/block-example-text.mdc +59 -0
  84. package/vendor/theme-liquid-docs/ai/cursor/rules/examples/section-example.mdc +61 -0
  85. package/vendor/theme-liquid-docs/ai/cursor/rules/examples/snippet-example.mdc +72 -0
  86. package/vendor/theme-liquid-docs/ai/cursor/rules/liquid.mdc +837 -0
  87. package/vendor/theme-liquid-docs/ai/cursor/rules/locales.mdc +100 -0
  88. package/vendor/theme-liquid-docs/ai/cursor/rules/localization.mdc +67 -0
  89. package/vendor/theme-liquid-docs/ai/cursor/rules/mcp.mdc +2 -0
  90. package/vendor/theme-liquid-docs/ai/cursor/rules/schemas.mdc +184 -0
  91. package/vendor/theme-liquid-docs/ai/cursor/rules/sections.mdc +84 -0
  92. package/vendor/theme-liquid-docs/ai/cursor/rules/settings-schema.mdc +51 -0
  93. package/vendor/theme-liquid-docs/ai/cursor/rules/snippets.mdc +119 -0
  94. package/vendor/theme-liquid-docs/ai/github/copilot-instructions.md +1485 -0
  95. package/vendor/theme-liquid-docs/ai/liquid.mdc +638 -0
  96. package/vendor/theme-liquid-docs/data/filters.json +6148 -0
  97. package/vendor/theme-liquid-docs/data/latest.json +2 -0
  98. package/vendor/theme-liquid-docs/data/objects.json +20594 -0
  99. package/vendor/theme-liquid-docs/data/shopify_system_translations.json +2586 -0
  100. package/vendor/theme-liquid-docs/data/tags.json +1276 -0
  101. package/vendor/theme-liquid-docs/package.json +20 -0
  102. package/vendor/theme-liquid-docs/schemas/manifest_schema.json +31 -0
  103. package/vendor/theme-liquid-docs/schemas/manifest_theme.json +19 -0
  104. package/vendor/theme-liquid-docs/schemas/manifest_theme_app_extension.json +10 -0
  105. package/vendor/theme-liquid-docs/schemas/theme/app_block_entry.json +13 -0
  106. package/vendor/theme-liquid-docs/schemas/theme/default_setting_values.json +24 -0
  107. package/vendor/theme-liquid-docs/schemas/theme/local_block_entry.json +25 -0
  108. package/vendor/theme-liquid-docs/schemas/theme/preset.json +72 -0
  109. package/vendor/theme-liquid-docs/schemas/theme/preset_blocks.json +91 -0
  110. package/vendor/theme-liquid-docs/schemas/theme/section.json +208 -0
  111. package/vendor/theme-liquid-docs/schemas/theme/setting.json +1413 -0
  112. package/vendor/theme-liquid-docs/schemas/theme/settings.json +10 -0
  113. package/vendor/theme-liquid-docs/schemas/theme/targetted_block_entry.json +15 -0
  114. package/vendor/theme-liquid-docs/schemas/theme/theme_block.json +91 -0
  115. package/vendor/theme-liquid-docs/schemas/theme/theme_block_entry.json +14 -0
  116. package/vendor/theme-liquid-docs/schemas/theme/theme_settings.json +83 -0
  117. package/vendor/theme-liquid-docs/schemas/theme/translations.json +63 -0
  118. package/vendor/theme-liquid-docs/schemas/update/update_extension_schema_v1.json +186 -0
  119. package/vendor/theme-liquid-docs/tests/fixtures/section-nested-blocks.json +18 -0
  120. package/vendor/theme-liquid-docs/tests/fixtures/section-schema-1.json +90 -0
  121. package/vendor/theme-liquid-docs/tests/fixtures/section-schema-2.json +201 -0
  122. package/vendor/theme-liquid-docs/tests/fixtures/section-schema-3.json +29 -0
  123. package/vendor/theme-liquid-docs/tests/fixtures/section-schema-4.json +315 -0
  124. package/vendor/theme-liquid-docs/tests/fixtures/section-schema-5.json +114 -0
  125. package/vendor/theme-liquid-docs/tests/fixtures/section-schema-6.json +63 -0
  126. package/vendor/theme-liquid-docs/tests/fixtures/section-schema-conditional-settings.json +145 -0
  127. package/vendor/theme-liquid-docs/tests/fixtures/section-schema-preset-blocks-as-hash.json +60 -0
  128. package/vendor/theme-liquid-docs/tests/fixtures/section-schema-static-block-preset.json +76 -0
  129. package/vendor/theme-liquid-docs/tests/fixtures/section-settings.json +34 -0
  130. package/vendor/theme-liquid-docs/tests/fixtures/theme-block-1.json +234 -0
  131. package/vendor/theme-liquid-docs/tests/fixtures/theme-block-2.json +253 -0
  132. package/vendor/theme-liquid-docs/tests/fixtures/theme-block-basics.json +48 -0
  133. package/vendor/theme-liquid-docs/tests/fixtures/theme-block-conditional-settings.json +202 -0
  134. package/vendor/theme-liquid-docs/tests/fixtures/theme-block-presets-as-hash.json +50 -0
  135. package/vendor/theme-liquid-docs/tests/fixtures/theme-block-settings.json +34 -0
  136. package/vendor/theme-liquid-docs/tests/fixtures/theme-settings-all-settings.json +313 -0
  137. package/vendor/theme-liquid-docs/tests/fixtures/theme-settings-dawn.json +1469 -0
  138. package/vendor/theme-liquid-docs/tests/fixtures/theme-settings-metadata.json +10 -0
  139. package/vendor/theme-liquid-docs/tests/fixtures/translations-1.json +14 -0
  140. package/vendor/theme-liquid-docs/tests/section.spec.ts +367 -0
  141. package/vendor/theme-liquid-docs/tests/test-constants.ts +58 -0
  142. package/vendor/theme-liquid-docs/tests/test-helpers.ts +104 -0
  143. package/vendor/theme-liquid-docs/tests/theme-settings/color_palette.spec.ts +184 -0
  144. package/vendor/theme-liquid-docs/tests/theme-settings/color_scheme_group.spec.ts +143 -0
  145. package/vendor/theme-liquid-docs/tests/theme-settings/general.spec.ts +192 -0
  146. package/vendor/theme-liquid-docs/tests/theme-settings/metaobject.spec.ts +94 -0
  147. package/vendor/theme-liquid-docs/tests/theme-settings/resource_list.spec.ts +58 -0
  148. package/vendor/theme-liquid-docs/tests/theme-settings/theme-metadata.spec.ts +59 -0
  149. package/vendor/theme-liquid-docs/tests/theme_block.spec.ts +266 -0
  150. package/vendor/theme-liquid-docs/tests/translations_schema.spec.ts +31 -0
  151. package/vendor/theme-liquid-docs/yarn.lock +543 -0
  152. package/vitest.config.ts +7 -0
@@ -0,0 +1,419 @@
1
+ ---
2
+ status: draft
3
+ ---
4
+
5
+ # Converting Smelt to a Library + CLI
6
+
7
+ Plan for turning this repo from "a Shopify theme that happens to have a custom
8
+ build script" into "an npm package that other Shopify themes install and use."
9
+ Companion to [`build-spec.md`](./build-spec.md) — the build spec describes the
10
+ compiler's behavior; this doc describes the path from where we are to publishing
11
+ it as a tool.
12
+
13
+ ## Vision
14
+
15
+ Today, Smelt _is_ a theme. After this work:
16
+
17
+ - Smelt is published as **`@augeo/smelt`** (`npm i -D @augeo/smelt`), matching
18
+ the scope used by
19
+ [`@augeo/assay`](https://www.npmjs.com/package/@augeo/assay).
20
+ - It exposes a **CLI** (`npx smelt build`, `npx smelt dev`).
21
+ - It ships a **baseline component layer** that consumers can use as-is.
22
+ - Consumer themes have their own `src/` that **layers on top** of Smelt's
23
+ baseline — files in the consumer layer shadow matching baseline files.
24
+ - This repo keeps a working `example/` consumer theme that exercises the package
25
+ end-to-end (mirrors how [`assay`](https://github.com/seanhealy/assay) is
26
+ organized).
27
+
28
+ The build engine's _behavior_ doesn't change much. The walker, render rewriter,
29
+ esbuild bundle, CSS/JS inlining, banner, and indent handling all transfer almost
30
+ as-is. The new pieces are **layer resolution**, **CLI packaging**, and the
31
+ **inside-out reorganization** of this repo.
32
+
33
+ ## Design Decisions
34
+
35
+ All five confirmed during planning. Listed here so the rationale lives next to
36
+ the decision and is easy to revisit if a future constraint forces a rethink.
37
+
38
+ ### 1. Shadowing is per-file, not per-component
39
+
40
+ If a consumer provides only `button.css`, they get Smelt's `button.liquid` +
41
+ `button.ts` with their CSS swapped in. Each of `<name>.{liquid,ts,css,test.ts}`
42
+ resolves independently across layers.
43
+
44
+ - **Why:** maximally flexible; lets consumers tweak just styling without forking
45
+ the logic.
46
+ - **Cost:** harder to reason about than "you own the whole component." Need
47
+ clear error messages when a shadow drifts (e.g. consumer's CSS references a
48
+ class Smelt's liquid no longer renders).
49
+
50
+ ### 2. `@/` and `./` resolve against the **merged** layer tree
51
+
52
+ When Smelt's `tabs.liquid` does `{% render '@/components/icon' %}`, it resolves
53
+ through the layered tree — so a consumer's icon shadow is picked up. Same for
54
+ esbuild's resolution of `@/utilities/...` imports.
55
+
56
+ - **Why:** without this, layering is just "drop in alternates" rather than true
57
+ overrides — the baseline can't be visibly customized through its
58
+ collaborators.
59
+ - **Cost:** consumers can break Smelt internals by shadowing a utility
60
+ carelessly. Mitigation: discipline + clear documentation about which surface
61
+ is considered stable (same model as Gatsby component shadowing or Material UI
62
+ theme overrides).
63
+
64
+ ### 3. We ship **source**, not built artifacts
65
+
66
+ The published package contains raw `.liquid` / `.ts` / `.css` for the baseline
67
+ layer. The consumer's `smelt build` is what compiles them.
68
+
69
+ - **Why:** required for layering to work at the source level. If we shipped
70
+ pre-built `built--*.liquid`, consumers couldn't shadow a baseline component's
71
+ CSS in isolation.
72
+ - **Cost:** consumer build time scales with Smelt's component count. Acceptable
73
+ for now; revisit if pain.
74
+
75
+ ### 4. One published package, not split
76
+
77
+ `@augeo/smelt` ships the CLI **and** the baseline components together. No
78
+ `@augeo/smelt-cli` + `@augeo/smelt-baseline` split yet.
79
+
80
+ - **Why:** split is cheap to do later if a reason emerges (e.g. alternate
81
+ baselines, plugin authors who want just the CLI). No reason today.
82
+ - **Cost:** baseline component changes force a new version of the CLI package
83
+ too. Acceptable until baseline surface grows.
84
+
85
+ ### 5. Optional `smelt.config.ts`, minimal surface
86
+
87
+ A config file exists but is optional. For v1 it has exactly one field: `layers`.
88
+ Everything else stays convention until proven necessary.
89
+
90
+ ```ts
91
+ // smelt.config.ts
92
+ import { defineConfig } from "@augeo/smelt";
93
+
94
+ export default defineConfig({
95
+ layers: ["./src", "@augeo/smelt"],
96
+ });
97
+ ```
98
+
99
+ If the file doesn't exist, the resolver defaults to `["./src", "@augeo/smelt"]`
100
+ — first wins. Output dirs, build options, etc. stay hardcoded for now.
101
+
102
+ - **Why:** consumers will eventually want custom layer ordering (multiple
103
+ baselines, brand overrides, plugin layers). The config makes this expressible
104
+ without arg-parsing gymnastics. Limiting v1 to `layers` alone keeps the door
105
+ open without committing to a sprawling surface.
106
+ - **Cost:** another file format to maintain — small. `defineConfig` is a pure
107
+ type-identity helper (same pattern as Vite, Vitest, etc.).
108
+ - **Future:** add `output`, `extensions`, build options when a real consumer
109
+ needs them.
110
+
111
+ ## CLI Surface
112
+
113
+ | Command | What it does |
114
+ | ------------- | ------------------------------------------------------------------ |
115
+ | `smelt build` | One-shot build of all layers → consumer's flat output directories |
116
+ | `smelt dev` | Watch mode: rebuild affected components on source file change |
117
+ | `smelt init` | (Future) Scaffold a new consumer theme repo with sensible defaults |
118
+
119
+ **`smelt dev` is build-watch only** — it rebuilds affected components on source
120
+ change and exits on Ctrl-C. Consumer composes it with the Shopify CLI in their
121
+ own `package.json`:
122
+
123
+ ```jsonc
124
+ {
125
+ "scripts": {
126
+ "dev": "concurrently 'smelt dev' 'shopify theme dev'",
127
+ },
128
+ }
129
+ ```
130
+
131
+ Rationale: keeps Smelt from having a runtime dependency on Shopify CLI being
132
+ installed, and matches how Vite / Next / etc handle their `dev` command (they
133
+ don't try to run framework-adjacent servers for you).
134
+
135
+ ## Architecture Changes
136
+
137
+ Most of the engine survives intact. What changes:
138
+
139
+ | Area | Before | After conversion |
140
+ | ---------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------- |
141
+ | Entry point | `src/scripts/build.ts` runs immediately | `lib/cli.ts` defines commands via [citty](https://github.com/unjs/citty), calls library API |
142
+ | Roots | Hardcoded `ROOT = resolve(".")`, `SRC = …` | `resolveContext()` figures out consumer cwd + layer paths |
143
+ | Component walker | Walks one tree | Walks N layer trees, per-file merge by priority |
144
+ | Render rewriter | Looks up in single component map | Looks up in merged component map |
145
+ | esbuild | `tsconfig` points at this repo's tsconfig | Uses consumer's tsconfig if present; alias plugin resolves `@/` per layer |
146
+ | Output dirs | Hardcoded `sections/`, etc. | Resolved relative to consumer cwd |
147
+ | Watch mode | Doesn't exist | New: incremental rebuild on file change in any layer |
148
+ | Dev mode | `npm run dev` = shopify CLI only | `smelt dev` = build watcher; consumer composes with `shopify theme dev` |
149
+ | Banner | `GENERATED FROM src/...` | `GENERATED FROM <layer-tagged source path>` |
150
+
151
+ ## Phased Execution Plan
152
+
153
+ Each phase is a coherent chunk that can be verified, reviewed, and merged
154
+ independently. We don't need to do them all in one shot — they're sized so the
155
+ repo stays useful between phases.
156
+
157
+ ### Phase 1 — Inside-out reorganization (minimal engine touchups)
158
+
159
+ Move all theme content into `example/` and reframe the root as a future library,
160
+ without touching the engine architecturally. The build script gets ~4 lines of
161
+ hardcoded path updates so it keeps working against the new layout — proper
162
+ context-driven refactor happens in Phase 2. After this phase, `example/` is the
163
+ test bed for everything that follows (mirroring how assay uses its own
164
+ `example/`).
165
+
166
+ Deliverables:
167
+
168
+ - Create directories: `example/`, `lib/` (placeholder for Phase 2),
169
+ `components/` (placeholder for Phase 3), `dist/` (gitignored)
170
+ - Move theme content into `example/`:
171
+ - Output dirs: `sections/`, `snippets/`, `blocks/`
172
+ - Theme files: `layout/`, `templates/`, `config/`, `locales/`, `assets/`
173
+ - Theme config: `.theme-check.yml`, `.shopifyignore`
174
+ - Test config: `vitest.config.ts`
175
+ - Move demo component: `src/components/button/` →
176
+ `example/src/components/button/`
177
+ - Patch `src/scripts/build.ts`: change `SRC` to point at `example/src`, prefix
178
+ output dir constants with `example/`. ~4 line change; engine is still
179
+ hardcoded but works against the new layout.
180
+ - Update root `package.json`:
181
+ - Strip theme scripts (`dev`, `push`, `pull`, `theme:check`, `theme:fix`)
182
+ - Keep `build` (still at root for now; outputs into `example/`)
183
+ - Create `example/package.json`:
184
+ - Theme scripts (cwd=`example/`): `dev`, `push`, `pull`, `theme:check`,
185
+ `theme:fix`
186
+ - `build`: `tsx ../src/scripts/build.ts` (interim — Phase 2 makes this clean)
187
+ - `test`: vitest against `example/`'s built output
188
+ - Update `.gitattributes` linguist-generated patterns to use the `example/`
189
+ prefix
190
+ - Update CI workflow: lint/typecheck at root, then `cd example` for build,
191
+ tests, theme-check, and the `git status` diff guard
192
+ - Update docs:
193
+ - `AGENTS.md` — restructure to reflect the library-root + consumer-example
194
+ layout. Note that `example/` is the integration test bed.
195
+ - `docs/build-spec.md` — reframe the worked example as "in the consumer's
196
+ repo." Pipeline behavior description unchanged.
197
+ - `README.md` — minor update to reflect theme commands now live in `example/`.
198
+
199
+ Verification: `npm run build` at root produces files in `example/`; from
200
+ `example/`: `npm test && npm run theme:check` succeeds and the built output
201
+ matches today's root-as-theme output (after the move).
202
+
203
+ ### Phase 2 — Engine refactor + citty CLI + `file:..` link
204
+
205
+ Pull the build script out of `src/scripts/` and into `lib/` as a properly
206
+ context-driven library, exposed through a citty CLI compiled to a real bin via
207
+ tsdown. After this phase, the engine no longer assumes anything about "where the
208
+ consumer's theme lives" — it accepts a `BuildContext`, **and `example/` consumes
209
+ it like a real npm consumer** via `"@augeo/smelt": "file:.."`. This catches bin
210
+ / shebang / entry-point bugs early without waiting for full packaging in
211
+ Phase 4.
212
+
213
+ **Types policy:** no dedicated `types.ts` file. Define types alongside the code
214
+ that primarily uses them — if a second module needs the same type, it imports
215
+ from the defining module. Keeps things easy to mutate; revisit only if genuine
216
+ ambiguity emerges.
217
+
218
+ **Bundler policy:** tsdown owns _library packaging_ (the CLI bin, and later the
219
+ programmatic exports + d.ts). esbuild stays for _per-component runtime bundling_
220
+ inside the build engine. Two bundlers, two distinct concerns; not a swap.
221
+
222
+ Deliverables:
223
+
224
+ - `lib/build.ts` — exports `async function build(context: BuildContext)`.
225
+ `BuildContext` lives in this file.
226
+ - `lib/cli.ts` — defines commands via [citty](https://github.com/unjs/citty),
227
+ dispatches to `build` (only command for now). Picked over cac/Cliffy/oclif for
228
+ deep TS inference, lightweight footprint, and active maintenance in the UnJS
229
+ ecosystem.
230
+ - `tsdown.config.ts` — bundles `lib/cli.ts` → `dist/cli.mjs` with a
231
+ `#!/usr/bin/env node` shebang. (Grows in Phase 4 to add `dist/index.js` +
232
+ d.ts + exports map.)
233
+ - Delete `src/scripts/` entirely (no shim — its one caller, `example/`'s `build`
234
+ script, gets repointed via the bin)
235
+ - Root `package.json` (mirrors assay's script conventions):
236
+ - Add `bin.smelt` → `./dist/cli.mjs`
237
+ - `build` script: `tsdown` (this package's artifact = `dist/cli.mjs`)
238
+ - `build:watch` script: `tsdown --watch` (iteration ergonomic)
239
+ - `verify` script: `lint:fix && typecheck` — fast root quality gate
240
+ - `test` script: `npm run build && npm test --prefix example` — integration
241
+ chain
242
+ - `prepare` script: `npm run build` (keeps `npm install` ergonomic; needed
243
+ because the example's bin resolution depends on `dist/cli.mjs` existing)
244
+ - `prepublishOnly` script: `npm run build` (publish-time safety)
245
+ - Add `citty` to `dependencies`; add `tsdown` to `devDependencies`
246
+ - `example/package.json` (intentionally consumer-shaped):
247
+ - `devDependencies`: `"@augeo/smelt": "file:.."`
248
+ - `build` script: `smelt build` (resolves through the linked bin)
249
+ - `test` script: `typecheck → build → vitest → theme:check` — example's full
250
+ check, called by root's `npm test`
251
+ - Run `npm install` in `example/` once to set up the symlink
252
+ - Update `tsconfig.json` `include` from `src/**/*.ts` → `lib/**/*.ts`
253
+ - Update doc references (`AGENTS.md`, `docs/build-spec.md`) to the new
254
+ `lib/build.ts` path
255
+ - `npm run verify` at root and `npm run verify` in `example/` both pass; diff
256
+ check confirms no output drift
257
+
258
+ Verification: from `example/`, `npx smelt build` (via `npm run build`) produces
259
+ byte-identical output to Phase 1 (modulo any intentional banner reformat).
260
+ Resolution flows `example/node_modules/@augeo/smelt` → symlink → repo root, so
261
+ edits to `lib/cli.ts` rebuild `dist/cli.mjs` on next example build and are
262
+ picked up automatically.
263
+
264
+ ### Phase 3 — Layer resolution
265
+
266
+ Introduce the `Layer` concept and per-file merging. Build engine accepts an
267
+ ordered list of layer paths; walker merges; render rewriter and esbuild resolver
268
+ consult the merged tree. With `example/` in place since Phase 1, this is
269
+ exercised against a real consumer setup rather than synthetic scaffolding.
270
+
271
+ Deliverables:
272
+
273
+ - `lib/resolver.ts` — walks N layers, returns merged component list. Each
274
+ `ResolvedComponent` has `liquid`/`ts`/`css`/`test` slots that independently
275
+ track which layer they came from. New types (`Layer`, `ComponentSource`,
276
+ `ResolvedComponent`, `ComponentType`) live here; `lib/build/build.ts` imports
277
+ them.
278
+ - `BuildContext` drops `cwd` and gains `layers: Layer[]`. Output dirs are always
279
+ relative (`sections/`, `snippets/`, `blocks/`), resolved against the first
280
+ layer (the consumer root).
281
+ - `lib/build/build.ts` walker rewritten to consume the merged list from the
282
+ resolver (no more local `walkSrc`).
283
+ - `lib/build/command.ts` uses `import.meta.url` + `fileURLToPath` to locate the
284
+ published package's root (the second layer); the consumer's layer is
285
+ `process.cwd()`. Default layers: `[<consumer>, "@augeo/smelt"]`.
286
+ - Promote the button into the root `src/components/button/` baseline so there's
287
+ something to shadow. Drop a `button.css` shadow into
288
+ `example/src/components/button/` to demonstrate partial override.
289
+ - esbuild plugin for `@/...` resolution against the merged layer tree —
290
+ **deferred until a real component uses `@/...` TS imports**. The demo button
291
+ doesn't, so this is YAGNI for now.
292
+ - Root `vitest.config.ts` (matches assay's pattern) picks up
293
+ `lib/**/*.test.ts` + `src/**/*.test.ts`. Root `test` script chains
294
+ `vitest run` between the lib build and example's tests.
295
+ - `lib/resolver.test.ts` — colocated unit tests covering baseline-only, partial
296
+ CSS shadow, full consumer shadow, first-wins ordering, disjoint layers, and
297
+ dead-code (consumer slot with no liquid anchor) scenarios via `mkdtemp`
298
+ fixtures.
299
+ - `src/components/button/button.test.ts` — assay-style functional test (renders,
300
+ accepts `label` arg, applies CSS class). **Deferred** until the baseline has a
301
+ built target vitest can render against; the integration test in `example/`
302
+ already verifies the button compiles end-to-end.
303
+
304
+ Verification: in `example/`, the built button uses the consumer's CSS
305
+ (`background: #f3c724`, pill-shaped) while the `liquid` and `ts` slots come from
306
+ baseline (`@augeo/smelt/src/components/button/...`, visible in the banner).
307
+
308
+ ### Phase 4 — Packaging
309
+
310
+ Make the library a real publishable npm package. The CLI bin already comes out
311
+ of tsdown (set up in Phase 2); this phase grows the same tsdown config to also
312
+ emit a programmatic entry point and adds the `package.json` metadata an npm
313
+ consumer needs.
314
+
315
+ Deliverables:
316
+
317
+ - Grow `tsdown.config.ts` to add a second entry: `lib/build.ts` →
318
+ `dist/index.js` with `.d.ts`
319
+ - `package.json` updates:
320
+ - `exports` map for `dist/index.js` (programmatic use) and the bin
321
+ - `files: ["dist", "components"]`
322
+ - `peerDependencies`: nothing yet (we bundle esbuild)
323
+ - `dependencies`: esbuild, citty (already moved out of devDeps in Phase 2 for
324
+ citty; confirm esbuild placement)
325
+ - `prepublishOnly` script that runs `npm run lib:build` to ensure `dist/` is
326
+ fresh
327
+ - `npm pack` round-trip: install the tarball into `example/` (instead of
328
+ `file:..`) and confirm it still builds. Catches missing-`files`-entry and
329
+ shebang-permission bugs that `file:..` masks.
330
+
331
+ Verification: `npm pack` produces a tarball;
332
+ `cd example && npm install ../smelt-0.0.0.tgz && npx smelt build` works
333
+ end-to-end against the packed artifact (not just `file:..`). After verification,
334
+ restore the `file:..` link so day-to-day dev keeps working live.
335
+
336
+ ### Phase 5 — CI and verify chain
337
+
338
+ CI now verifies two things: the library compiles cleanly, and the example
339
+ consumer builds correctly with no drift.
340
+
341
+ Deliverables:
342
+
343
+ - Workflow `.github/workflows/verify.yml`:
344
+ - lint, typecheck, library unit tests, library build (`tsdown`)
345
+ - then `cd example`, `npm install`, `npx smelt build`, vitest
346
+ - finally `git diff --exit-code` to catch stale built files in
347
+ `example/sections`, `example/snippets`, `example/blocks`
348
+ - Root `npm run verify` mirrors CI: lint:fix → typecheck → test → build → cd
349
+ example && verify
350
+ - `.claude/settings.json` adjusted for the new script set
351
+
352
+ Verification: open a draft PR with a deliberately stale built file in
353
+ `example/`; CI should fail on the diff check.
354
+
355
+ ### Phase 6 — First publish + first real consumer
356
+
357
+ Go from "works locally" to "is the tool we use."
358
+
359
+ Deliverables:
360
+
361
+ - Publish `@augeo/smelt` 0.1.0
362
+ - Pick one of the Augeo themes (or coverlet, if we're feeling ambitious) to
363
+ migrate as the first real consumer
364
+ - Document the migration path in `docs/`
365
+
366
+ Verification: real-world consumer's CI is green using the published package.
367
+
368
+ ## Documentation Updates
369
+
370
+ Done as part of the relevant phase, listed here for visibility:
371
+
372
+ - `AGENTS.md` (Phase 1) — restructure to reflect the library-root +
373
+ consumer-example layout. Will get a second pass in Phase 4 to add a section
374
+ pointing consumers at the README / `docs/consumer-guide.md`.
375
+ - `README.md` (Phase 4) — rewrite as "what Smelt is, how to install, how to
376
+ use." Move the current command table to `example/README.md`.
377
+ - `docs/build-spec.md` (Phase 1) — reframe the worked example as "in the
378
+ consumer's repo, where Smelt was installed." The pipeline behavior description
379
+ is unchanged.
380
+ - `docs/consumer-guide.md` (Phase 4, new) — install, write components, shadow
381
+ baseline, run dev/build/test. The new user-facing doc.
382
+ - `docs/library-architecture.md` (Phase 3, new, optional) — internal notes on
383
+ layer resolution and the engine API. Lives next to `build-spec.md`.
384
+
385
+ ## Open Questions / Risks
386
+
387
+ - **Shadowing footguns.** A consumer shadowing a utility used by multiple
388
+ baseline components creates a non-local change. Error messages need to make
389
+ this obvious. May need a `smelt explain` command long-term (shows resolution
390
+ for a given component).
391
+ - **Tests-against-built-output composes with layers how?** Today vitest runs
392
+ against `./sections`, `./snippets`, `./blocks` — those contain built output
393
+ from BOTH baseline + consumer post-merge. So tests work the same way. But:
394
+ should consumers be able to write tests for baseline components without
395
+ copy-pasting them? Probably yes; defer until use case appears.
396
+ - **What happens when a consumer references a baseline component via
397
+ `@/components/button` but doesn't shadow it?** Resolver finds it in the
398
+ baseline layer, build emits `snippets/built--components--button.liquid` into
399
+ the consumer's output dirs. ✓ same naming as if consumer had written it.
400
+ Banner notes the file came from `smelt/components/button/`.
401
+
402
+ ## Out of Scope (For This Conversion)
403
+
404
+ Things we explicitly defer beyond the scope of this conversion:
405
+
406
+ - **Plugin system.** No third-party extension points yet. Layers cover the
407
+ customization story for v1.
408
+ - **Multiple baseline packages.** v1 has exactly one baseline (the library's own
409
+ `components/`). Consumers can't mix `smelt-baseline-a` + `smelt-baseline-b`.
410
+ Adding that is a layered version of layering and can wait.
411
+ - **`props.ts` runtime validation.** Already deferred in `build-spec.md`. Still
412
+ deferred here.
413
+ - **Workspace conversion.** Single package is the right call until a reason
414
+ emerges to split. Triggers documented in [conversation history / future ADR].
415
+ - **Component documentation site.** Out of scope; baseline components are
416
+ documented via their own colocated comments + a future `docs/components.md`
417
+ catalog.
418
+ - **Migration tooling for existing themes.** v1 consumer migration is manual.
419
+ Tooling (e.g. `smelt migrate-from-dawn`) is way out of scope.
@@ -0,0 +1,7 @@
1
+ # Compiler output is committed (Shopify imports the theme from git) but
2
+ # GitHub should collapse it in PR diffs and exclude it from language stats.
3
+ sections/built--*.liquid linguist-generated=true
4
+ snippets/built--*.liquid linguist-generated=true
5
+ blocks/built--*.liquid linguist-generated=true
6
+ blocks/_built--*.liquid linguist-generated=true
7
+ assets/built--* linguist-generated=true
@@ -0,0 +1,28 @@
1
+ .git/
2
+ .github/
3
+ .gitignore
4
+ .shopifyignore
5
+ .claude/
6
+ .theme-check.yml
7
+
8
+ node_modules/
9
+ package.json
10
+ package-lock.json
11
+ biome.json
12
+ tsconfig.json
13
+ .prettierrc.cjs
14
+ .prettierignore
15
+
16
+ # Authoring tree + dev surface — never uploaded to Shopify.
17
+ src/
18
+ tests/
19
+ scripts/
20
+ docs/
21
+ dist/
22
+
23
+ README.md
24
+ AGENTS.md
25
+ CLAUDE.md
26
+
27
+ __screenshots__/
28
+ .vitest-attachments/
@@ -0,0 +1,7 @@
1
+ extends: theme-check:recommended
2
+ ignore:
3
+ - node_modules/**
4
+ - src/**
5
+ - tests/**
6
+ - scripts/**
7
+ - dist/**
@@ -0,0 +1,52 @@
1
+ {%- comment -%}
2
+ GENERATED FROM consumer/src/sections/hero/blocks/feature/feature.liquid — do not edit this file directly.
3
+ Edit the source and run `npm run build`.
4
+ {%- endcomment -%}
5
+
6
+ {%- liquid
7
+ assign title = block.settings.title | default: 'Feature'
8
+ assign body = block.settings.body | default: ''
9
+ -%}
10
+
11
+ <li class="tr-feature" {{ block.shopify_attributes }}>
12
+ <strong class="tr-feature__title">{{ title }}</strong>
13
+ <p class="tr-feature__body">{{ body }}</p>
14
+ </li>
15
+
16
+ {% schema %}
17
+ {
18
+ "name": "Feature",
19
+ "settings": [
20
+ {
21
+ "type": "text",
22
+ "id": "title",
23
+ "label": "Title",
24
+ "default": "Fast"
25
+ },
26
+ {
27
+ "type": "textarea",
28
+ "id": "body",
29
+ "label": "Body"
30
+ }
31
+ ],
32
+ "presets": [
33
+ {
34
+ "name": "Feature"
35
+ }
36
+ ]
37
+ }
38
+ {% endschema %}
39
+
40
+ {% stylesheet %}
41
+ .tr-feature {
42
+ margin: 0.5rem 0;
43
+ }
44
+
45
+ .tr-feature__title {
46
+ display: block;
47
+ }
48
+
49
+ .tr-feature__body {
50
+ margin: 0.25rem 0 0;
51
+ }
52
+ {% endstylesheet %}
@@ -0,0 +1,10 @@
1
+ [
2
+ {
3
+ "name": "theme_info",
4
+ "theme_name": "Smelt",
5
+ "theme_version": "0.0.0",
6
+ "theme_author": "Sean Healy",
7
+ "theme_documentation_url": "https://github.com/AugeoCorp/smelt",
8
+ "theme_support_email": "support@example.com"
9
+ }
10
+ ]
@@ -0,0 +1,25 @@
1
+ <!doctype html>
2
+ <html lang="{{ request.locale.iso_code }}">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <link rel="canonical" href="{{ canonical_url }}">
7
+ <title>
8
+ {{ page_title }}
9
+ {%- if current_tags %}
10
+ &ndash; tagged "{{ current_tags | join: ', ' }}"
11
+ {%- endif -%}
12
+ {%- if current_page != 1 %} &ndash; Page {{ current_page }}{% endif -%}
13
+ {%- unless page_title contains shop.name %}
14
+ &ndash; {{ shop.name -}}
15
+ {%- endunless -%}
16
+ </title>
17
+ {%- if page_description -%}
18
+ <meta name="description" content="{{ page_description | escape }}">
19
+ {%- endif -%}
20
+ {{ content_for_header }}
21
+ </head>
22
+ <body class="template-{{ template.name | handle }}">
23
+ {{ content_for_layout }}
24
+ </body>
25
+ </html>
@@ -0,0 +1 @@
1
+ {}
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "smelt-example",
3
+ "version": "0.0.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "smelt-example",
9
+ "version": "0.0.0",
10
+ "devDependencies": {
11
+ "@augeo/smelt": "file:.."
12
+ }
13
+ },
14
+ "..": {
15
+ "name": "@augeo/smelt",
16
+ "version": "1.2.0",
17
+ "dev": true,
18
+ "license": "MIT",
19
+ "dependencies": {
20
+ "chokidar": "^5.0.0",
21
+ "citty": "^0.2.2",
22
+ "esbuild": "^0.28.0"
23
+ },
24
+ "bin": {
25
+ "smelt": "dist/cli.mjs"
26
+ },
27
+ "devDependencies": {
28
+ "@augeo/assay": "^1.3.0",
29
+ "@biomejs/biome": "^2.4.16",
30
+ "@shopify/cli": "^3",
31
+ "@shopify/prettier-plugin-liquid": "^1",
32
+ "@types/node": "^25.9.1",
33
+ "@vitest/browser-playwright": "^4.1.7",
34
+ "json-schema-to-typescript": "^15.0.4",
35
+ "prettier": "^3",
36
+ "tsdown": "^0.22.1",
37
+ "tsx": "^4.22.3",
38
+ "typescript": "^6.0.3",
39
+ "vitest": "^4.1.7"
40
+ },
41
+ "engines": {
42
+ "node": "24.11.0",
43
+ "npm": "11.6.2"
44
+ }
45
+ },
46
+ "node_modules/@augeo/smelt": {
47
+ "resolved": "..",
48
+ "link": true
49
+ }
50
+ }
51
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "smelt-example",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "description": "Example Shopify theme consuming Smelt",
6
+ "type": "module",
7
+ "scripts": {
8
+ "dev": "shopify theme dev",
9
+ "push": "shopify theme push",
10
+ "pull": "shopify theme pull",
11
+ "theme:check": "shopify theme check",
12
+ "theme:fix": "shopify theme check --auto-correct",
13
+ "typecheck": "tsc --noEmit",
14
+ "build": "smelt build",
15
+ "test": "npm run typecheck && npm run build && vitest run --passWithNoTests && npm run theme:check"
16
+ },
17
+ "devDependencies": {
18
+ "@augeo/smelt": "file:.."
19
+ }
20
+ }