climaybe 3.3.0 → 3.4.0

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 CHANGED
@@ -250,6 +250,8 @@ When enabled, builds are **resilient**:
250
250
  - `init` may offer to create entrypoints; **default answer is No**.
251
251
  - Script bundling preserves comments/spacing and emits bundles only for root entry files (files imported by other top-level `_scripts/*.js` are inlined, not emitted separately).
252
252
  - Script bundles are written to `assets/*.js` (readable by default; use `climaybe build-scripts --minify` if you want minified output).
253
+ - `assets/*.js`, `assets/style.css`, and the injected `{% schema %}` blocks are **generated outputs** — edit the source in `_scripts/`, `_styles/`, and `_schemas/` instead, since the watcher/build regenerate (and overwrite) the outputs on save and in CI.
254
+ - **Orphan cleanup:** when `_scripts/` is in use, a full build (`climaybe build`, `climaybe build-scripts`, and the `serve` watcher) deletes any `assets/*.js` that no longer has a matching `_scripts/` source. Add new JS via a top-level `_scripts/<name>.js` entry, not by hand-editing `assets/`. Liquid-processed `*.js.liquid` assets and non-JS files are left untouched; targeted single-entry builds (`build-scripts <entry>`) skip pruning.
253
255
  - Live minified `assets/*` changes are intentionally excluded from hotfix backports to `main` (no branch-specific `.gitignore` split required).
254
256
 
255
257
  Build workflows install deps with `npm ci` and run `npx --no-install climaybe build-scripts` plus `npx --no-install climaybe build`, so CI uses lockfile-pinned versions (no `@latest` drift).
package/bin/version.txt CHANGED
@@ -1 +1 @@
1
- 3.3.0
1
+ 3.4.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "climaybe",
3
- "version": "3.3.0",
3
+ "version": "3.4.0",
4
4
  "description": "Shopify CLI by Electric Maybe for theme CI/CD workflows, branch orchestration, app setup, and dev tooling",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,7 +9,7 @@ export async function buildScriptsCommand(opts = {}) {
9
9
  try {
10
10
  const minify = opts.minify === true;
11
11
  if (global.gc) global.gc();
12
- const { bundles } = buildScripts({ cwd: process.cwd(), minify });
12
+ const { bundles, removed } = buildScripts({ cwd: process.cwd(), minify });
13
13
  if (!bundles || bundles.length === 0) {
14
14
  console.log(pc.yellow(' No _scripts/*.js entrypoints found; nothing to build.'));
15
15
  return;
@@ -20,6 +20,12 @@ export async function buildScriptsCommand(opts = {}) {
20
20
  for (const b of bundles) {
21
21
  console.log(pc.dim(` - ${b.entryFile} → ${b.outputPath}`));
22
22
  }
23
+ if (removed && removed.length > 0) {
24
+ console.log(pc.yellow(` Removed ${removed.length} orphan asset(s) with no _scripts source:`));
25
+ for (const name of removed) {
26
+ console.log(pc.dim(` - assets/${name}`));
27
+ }
28
+ }
23
29
  if (global.gc) global.gc();
24
30
  } catch (err) {
25
31
  console.log(pc.red(` Build error: ${err.message}`));
@@ -8,6 +8,7 @@ alwaysApply: true
8
8
 
9
9
  When you perform any of the following, **read and apply** the corresponding rule file from `.cursor/rules/`:
10
10
 
11
+ - **Editing `assets/*.js` or `assets/style.css`, build outputs vs source, where to edit JS/CSS** → `project-overview.mdc` (Build Outputs vs Source — edit `_scripts/`/`_styles/`, never the generated `assets/` files)
11
12
  - **Git commits, commit messages** → `commit-rules.mdc`
12
13
  - **Accessibility, a11y, focus, WCAG, UI behavior** → `accessibility-rules.mdc`
13
14
  - **JavaScript, web components, _scripts/, *.js** → `javascript-standards.mdc`
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  description: Professional JavaScript standards for Electric Maybe Shopify Theme. All JavaScript files must follow these patterns for consistency, performance, and maintainability.
3
3
  globs:
4
- - "**/*.js"
4
+ - "_scripts/**/*.js"
5
5
  - "sections/*.liquid"
6
6
  - "snippets/*.liquid"
7
7
  alwaysApply: false
@@ -9,6 +9,8 @@ alwaysApply: false
9
9
 
10
10
  # JavaScript Development Standards
11
11
 
12
+ > **Source vs build output:** Edit JavaScript in `_scripts/`. The CLI bundles `_scripts/*.js` into `assets/*.js` (`main.js` → `assets/index.js`) on every save and in CI, so `assets/*.js` is a **generated artifact** — never edit it directly, your changes will be overwritten. See `project-overview.mdc` (Build Outputs vs Source).
13
+
12
14
  ## Core Principles
13
15
 
14
16
  - **Zero external dependencies** - Use native browser APIs
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  description: JavaScript component refactor tasks and improvement tracking
3
3
  globs:
4
- - "**/*.js"
4
+ - "_scripts/**/*.js"
5
5
  alwaysApply: false
6
6
  ---
7
7
  # JavaScript Component Refactor Tasks
@@ -55,8 +55,10 @@ Use `_scripts/electric-modal.js` as the gold standard for all web components.
55
55
 
56
56
  ## File Organization
57
57
  ```
58
- _scripts/ # Web components and utilities
59
- assets/ # Compiled CSS/JS and static assets
58
+ _scripts/ # Source: web components and JS utilities (edit here)
59
+ _styles/ # Source: Tailwind/CSS entrypoint (_styles/main.css)
60
+ _schemas/ # Source: section/block schema definitions
61
+ assets/ # GENERATED build outputs + static assets (do not hand-edit generated files)
60
62
  sections/ # Liquid section files
61
63
  snippets/ # Reusable Liquid snippets
62
64
  templates/ # Page templates
@@ -64,6 +66,22 @@ locales/ # Translation files
64
66
  config/ # Theme settings
65
67
  ```
66
68
 
69
+ ## Build Outputs vs Source
70
+
71
+ `assets/` contains both static assets and **generated** build outputs. The CLI (`climaybe serve` / `climaybe build`) compiles sources into `assets/` and regenerates them on every save and in CI. Always edit the **source**, never the generated output:
72
+
73
+ | Source (edit this) | Generated output (do NOT edit) |
74
+ | --- | --- |
75
+ | `_scripts/main.js` | `assets/index.js` |
76
+ | `_scripts/<name>.js` (top-level entry) | `assets/<name>.js` |
77
+ | `_styles/main.css` | `assets/style.css` |
78
+ | `_schemas/<name>.js` / `.json` | injected `{% schema %}` block in `sections/*.liquid` & `blocks/*.liquid` |
79
+
80
+ Notes:
81
+ - JS bundling follows `import` statements: files imported by a top-level `_scripts/*.js` entry are **inlined** into that bundle, not emitted separately.
82
+ - Any manual edit to `assets/index.js`, `assets/*.js`, or `assets/style.css` is **overwritten** on the next save (watcher) or build. Make JS changes in `_scripts/`, CSS in `_styles/`, and schema changes in `_schemas/`.
83
+ - **Orphan cleanup:** on a full build (`climaybe build` / `climaybe build-scripts`, and the `serve` watcher), any `assets/*.js` that the current `_scripts/` build does not produce is **deleted** as a stale artifact. To add a new JS asset, create a top-level `_scripts/<name>.js` entry — do not drop a hand-written `.js` into `assets/` (it will be removed). Liquid-processed assets (`*.js.liquid`) and non-JS files are never touched.
84
+
67
85
  ## Code Review Requirements
68
86
  All code must pass the checklist in `javascript-standards.mdc` before merge.
69
87
 
@@ -3,13 +3,14 @@ description: Tailwind CSS and theme token standards for Liquid and theme styles.
3
3
  globs:
4
4
  - "**/*.liquid"
5
5
  - "_styles/**/*.css"
6
- - "assets/*.css"
7
6
  alwaysApply: false
8
7
  ---
9
8
  # Tailwind CSS Development Rules
10
9
 
11
10
  Apply when editing Liquid templates or theme CSS that use Tailwind classes or theme tokens. Use semantic tokens (surface, subtle, emphasis, accent, text-primary, border-secondary) from this project's `@theme`; see `_styles/02_base/02.02_colors.css`.
12
11
 
12
+ > **Source vs build output:** Edit CSS in `_styles/` (entrypoint `_styles/main.css`). The CLI compiles it into `assets/style.css` on every save and in CI, so `assets/style.css` is a **generated artifact** — never edit it directly, your changes will be overwritten. See `project-overview.mdc` (Build Outputs vs Source).
13
+
13
14
  ## Core Principles
14
15
 
15
16
  ### 1. Static Class Generation
@@ -1,4 +1,4 @@
1
- import { existsSync, readFileSync, writeFileSync, readdirSync, mkdirSync } from 'node:fs';
1
+ import { existsSync, readFileSync, writeFileSync, readdirSync, mkdirSync, unlinkSync } from 'node:fs';
2
2
  import { join, basename, dirname, normalize } from 'node:path';
3
3
 
4
4
  function extractImportRecords(content) {
@@ -161,6 +161,30 @@ function collectFilesToIsolate({ scriptsDir, entryFile }) {
161
161
  return isolateFiles;
162
162
  }
163
163
 
164
+ function removeOrphanScriptAssets({ cwd, keepNames }) {
165
+ // Delete assets/*.js that this build did not produce. The Electric Maybe build
166
+ // model treats assets/*.js as generated output of _scripts/, so any *.js without
167
+ // a matching bundle output is stale and should be removed. Only plain *.js files
168
+ // are considered — Liquid-processed assets (e.g. *.js.liquid) are left untouched.
169
+ const assetsDir = join(cwd, 'assets');
170
+ if (!existsSync(assetsDir)) return [];
171
+
172
+ const removed = [];
173
+ for (const dirent of readdirSync(assetsDir, { withFileTypes: true })) {
174
+ if (!dirent.isFile()) continue;
175
+ const name = dirent.name;
176
+ if (!name.endsWith('.js')) continue;
177
+ if (keepNames.has(name)) continue;
178
+ try {
179
+ unlinkSync(join(assetsDir, name));
180
+ removed.push(name);
181
+ } catch {
182
+ // Best effort: a failed unlink shouldn't break the build.
183
+ }
184
+ }
185
+ return removed;
186
+ }
187
+
164
188
  function listTopLevelEntrypoints(scriptsDir) {
165
189
  if (!existsSync(scriptsDir)) return [];
166
190
  return readdirSync(scriptsDir, { withFileTypes: true })
@@ -234,9 +258,18 @@ export function buildScripts({ cwd = process.cwd(), entry = null, minify = false
234
258
  }
235
259
  }
236
260
  if (entrypoints.length === 0) {
237
- return { bundles: [] };
261
+ return { bundles: [], removed: [] };
238
262
  }
239
263
  const bundles = entrypoints.map((entryFile) => buildSingleEntrypoint({ cwd, entryFile, minify }));
240
- return { bundles };
264
+
265
+ // Only prune orphans on a full build (no explicit entry). A targeted single-entry
266
+ // build must not delete the other bundles' outputs.
267
+ let removed = [];
268
+ if (!entry) {
269
+ const keepNames = new Set(bundles.map((b) => basename(b.outputPath)));
270
+ removed = removeOrphanScriptAssets({ cwd, keepNames });
271
+ }
272
+
273
+ return { bundles, removed };
241
274
  }
242
275
 
@@ -253,8 +253,11 @@ export function serveAssets({ cwd = process.cwd(), includeThemeCheck = false } =
253
253
  const scriptsDir = join(cwd, '_scripts');
254
254
  if (existsSync(scriptsDir)) {
255
255
  try {
256
- buildScripts({ cwd });
256
+ const { removed } = buildScripts({ cwd });
257
257
  writeTaggedLine('scripts', pc.yellow, 'built (initial)');
258
+ if (removed?.length) {
259
+ writeTaggedLine('scripts', pc.yellow, `removed ${removed.length} orphan asset(s): ${removed.join(', ')}`);
260
+ }
258
261
  } catch (err) {
259
262
  writeTaggedLine('scripts', pc.yellow, `initial build failed: ${err.message}`, process.stderr);
260
263
  }
@@ -268,8 +271,11 @@ export function serveAssets({ cwd = process.cwd(), includeThemeCheck = false } =
268
271
  debounceMs: 300,
269
272
  onChange: () => {
270
273
  try {
271
- buildScripts({ cwd });
274
+ const { removed } = buildScripts({ cwd });
272
275
  writeTaggedLine('scripts', pc.yellow, 'rebuilt');
276
+ if (removed?.length) {
277
+ writeTaggedLine('scripts', pc.yellow, `removed ${removed.length} orphan asset(s): ${removed.join(', ')}`);
278
+ }
273
279
  } catch (err) {
274
280
  writeTaggedLine('scripts', pc.yellow, `build failed: ${err.message}`, process.stderr);
275
281
  }
@@ -449,7 +455,10 @@ export function buildAll({ cwd = process.cwd() } = {}) {
449
455
 
450
456
  let scriptsOk = true;
451
457
  try {
452
- buildScripts({ cwd });
458
+ const { removed } = buildScripts({ cwd });
459
+ if (removed?.length) {
460
+ writeTaggedLine('scripts', pc.yellow, `removed ${removed.length} orphan asset(s): ${removed.join(', ')}`);
461
+ }
453
462
  } catch (err) {
454
463
  console.log(pc.red(`\n build-scripts failed: ${err.message}\n`));
455
464
  scriptsOk = false;