climaybe 2.2.1 → 2.2.3

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
@@ -25,6 +25,8 @@ npm install -D climaybe
25
25
 
26
26
  Run commands with `npx climaybe` (or add scripts to your `package.json`).
27
27
 
28
+ When a newer `climaybe` is available, the CLI can prompt at startup to update. Press **Enter** to accept the update (`npm install -g climaybe@latest`) or type `n` to skip.
29
+
28
30
  ## Quick Start (theme)
29
31
 
30
32
  ```bash
@@ -234,8 +236,10 @@ Enabled via `climaybe init` prompt (`Enable build + Lighthouse workflows?`; defa
234
236
  When enabled, `init` validates required theme files and exits with an error if any are missing:
235
237
  - `_scripts/main.js`
236
238
  - `_styles/main.css`
239
+
240
+ `init` auto-creates:
237
241
  - `assets/`
238
- - `release-notes.md`
242
+ - `release-notes.md` (starter template)
239
243
 
240
244
  `climaybe` auto-installs the shared build script at `.climaybe/build-scripts.js` during workflow scaffolding.
241
245
 
package/bin/cli.js CHANGED
@@ -5,6 +5,7 @@ import { createRequire } from 'node:module';
5
5
  import { dirname, join } from 'node:path';
6
6
  import { fileURLToPath } from 'node:url';
7
7
  import { run } from '../src/index.js';
8
+ import { maybeOfferCliUpdate } from '../src/lib/update-notifier.js';
8
9
 
9
10
  const require = createRequire(import.meta.url);
10
11
  const binDir = dirname(fileURLToPath(import.meta.url));
@@ -19,4 +20,6 @@ if (existsSync(versionFile)) {
19
20
  version = require(join(packageDir, 'package.json')).version;
20
21
  }
21
22
 
23
+ const packageName = require(join(packageDir, 'package.json')).name || 'climaybe';
24
+ await maybeOfferCliUpdate({ packageName, currentVersion: version, packageDir });
22
25
  run(process.argv, version, packageDir);
package/bin/version.txt CHANGED
@@ -1 +1 @@
1
- 2.2.1
1
+ 2.2.3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "climaybe",
3
- "version": "2.2.1",
3
+ "version": "2.2.3",
4
4
  "description": "Shopify CLI — theme CI/CD (workflows, branches, stores) and app repo helpers; also: commitlint and Cursor rules/skills",
5
5
  "type": "module",
6
6
  "bin": {
@@ -19,7 +19,7 @@ import { scaffoldWorkflows } from '../lib/workflows.js';
19
19
  import { createStoreDirectories } from '../lib/store-sync.js';
20
20
  import { scaffoldCommitlint } from '../lib/commit-tooling.js';
21
21
  import { scaffoldCursorBundle } from '../lib/cursor-bundle.js';
22
- import { getMissingBuildWorkflowRequirements, getBuildScriptRelativePath } from '../lib/build-workflows.js';
22
+ import { getMissingBuildWorkflowRequirements, getBuildScriptRelativePath, ensureBuildWorkflowDefaults } from '../lib/build-workflows.js';
23
23
  import { getDevKitExistingFiles, scaffoldThemeDevKit } from '../lib/theme-dev-kit.js';
24
24
  import {
25
25
  isGhAvailable,
@@ -54,6 +54,7 @@ async function runInitFlow() {
54
54
  console.log(pc.dim(`\n Mode: ${mode}-store (${stores.length} store(s))`));
55
55
 
56
56
  if (enableBuildWorkflows) {
57
+ ensureBuildWorkflowDefaults();
57
58
  const missingBuildFiles = getMissingBuildWorkflowRequirements();
58
59
  if (missingBuildFiles.length > 0) {
59
60
  console.log(pc.red('\n Build workflows are enabled, but required files are missing:'));
@@ -1,7 +1,6 @@
1
- import { copyFileSync, existsSync, mkdirSync, rmSync } from 'node:fs';
1
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
- import { readFileSync, writeFileSync } from 'node:fs';
5
4
 
6
5
  const SCRIPT_SOURCE = fileURLToPath(new URL('../workflows/build/build-scripts.js', import.meta.url));
7
6
  const SCRIPT_TARGET = '.climaybe/build-scripts.js';
@@ -20,8 +19,18 @@ module.exports = { buildScripts };
20
19
  const REQUIRED_PATHS = [
21
20
  { path: '_scripts/main.js', kind: 'file' },
22
21
  { path: '_styles/main.css', kind: 'file' },
22
+ ];
23
+
24
+ const DEFAULTS = [
23
25
  { path: 'assets', kind: 'dir' },
24
- { path: 'release-notes.md', kind: 'file' },
26
+ {
27
+ path: 'release-notes.md',
28
+ kind: 'file',
29
+ content: `# Release Notes
30
+
31
+ - describe key changes for this release
32
+ `,
33
+ },
25
34
  ];
26
35
 
27
36
  function targetScriptPath(cwd = process.cwd()) {
@@ -61,6 +70,18 @@ export function getMissingBuildWorkflowRequirements(cwd = process.cwd()) {
61
70
  return missing;
62
71
  }
63
72
 
73
+ export function ensureBuildWorkflowDefaults(cwd = process.cwd()) {
74
+ for (const entry of DEFAULTS) {
75
+ const abs = join(cwd, entry.path);
76
+ if (existsSync(abs)) continue;
77
+ if (entry.kind === 'dir') {
78
+ mkdirSync(abs, { recursive: true });
79
+ continue;
80
+ }
81
+ writeFileSync(abs, entry.content || '', 'utf-8');
82
+ }
83
+ }
84
+
64
85
  export function getBuildScriptRelativePath() {
65
86
  return SCRIPT_TARGET;
66
87
  }
@@ -0,0 +1,81 @@
1
+ import { execSync } from 'node:child_process';
2
+ import { stdin as input, stdout as output } from 'node:process';
3
+ import { createInterface } from 'node:readline/promises';
4
+ import pc from 'picocolors';
5
+
6
+ const NPM_REGISTRY_BASE = 'https://registry.npmjs.org';
7
+ const DEFAULT_TIMEOUT_MS = 1200;
8
+
9
+ function parseSemver(version) {
10
+ const match = String(version || '').trim().match(/^v?(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/);
11
+ if (!match) return null;
12
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
13
+ }
14
+
15
+ export function isVersionGreater(candidate, current) {
16
+ const a = parseSemver(candidate);
17
+ const b = parseSemver(current);
18
+ if (!a || !b) return false;
19
+ if (a[0] !== b[0]) return a[0] > b[0];
20
+ if (a[1] !== b[1]) return a[1] > b[1];
21
+ return a[2] > b[2];
22
+ }
23
+
24
+ async function fetchLatestVersion(packageName, timeoutMs = DEFAULT_TIMEOUT_MS) {
25
+ const controller = new AbortController();
26
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
27
+ try {
28
+ const response = await fetch(`${NPM_REGISTRY_BASE}/${encodeURIComponent(packageName)}/latest`, {
29
+ signal: controller.signal,
30
+ headers: { accept: 'application/json' },
31
+ });
32
+ if (!response.ok) return null;
33
+ const json = await response.json();
34
+ return typeof json.version === 'string' ? json.version : null;
35
+ } catch {
36
+ return null;
37
+ } finally {
38
+ clearTimeout(timeout);
39
+ }
40
+ }
41
+
42
+ function canPromptForUpdate() {
43
+ return Boolean(input.isTTY && output.isTTY && process.env.CI !== 'true');
44
+ }
45
+
46
+ function runGlobalUpdate(packageName) {
47
+ execSync(`npm install -g ${packageName}@latest`, { stdio: 'inherit' });
48
+ }
49
+
50
+ export async function maybeOfferCliUpdate({
51
+ packageName,
52
+ currentVersion,
53
+ packageDir,
54
+ timeoutMs = DEFAULT_TIMEOUT_MS,
55
+ } = {}) {
56
+ if (!packageName || !currentVersion || !canPromptForUpdate()) return;
57
+
58
+ const latestVersion = await fetchLatestVersion(packageName, timeoutMs);
59
+ if (!latestVersion || !isVersionGreater(latestVersion, currentVersion)) return;
60
+
61
+ console.log(pc.yellow(`\nA newer ${packageName} is available: ${currentVersion} -> ${latestVersion}`));
62
+ console.log(pc.dim('Press Enter to update now, or type "n" to continue without updating.'));
63
+
64
+ const rl = createInterface({ input, output });
65
+ try {
66
+ const answer = (await rl.question('Update now? [Y/n] ')).trim().toLowerCase();
67
+ const shouldUpdate = answer === '' || answer === 'y' || answer === 'yes';
68
+ if (!shouldUpdate) return;
69
+
70
+ try {
71
+ runGlobalUpdate(packageName);
72
+ console.log(pc.green(`Updated ${packageName} to latest. Restarting command...`));
73
+ process.chdir(packageDir || process.cwd());
74
+ } catch (err) {
75
+ console.log(pc.red('Update failed. Continuing with current version.'));
76
+ if (err?.message) console.log(pc.dim(err.message));
77
+ }
78
+ } finally {
79
+ rl.close();
80
+ }
81
+ }
@@ -3,7 +3,7 @@ const path = require('path');
3
3
  const ROOT_DIR = process.cwd();
4
4
 
5
5
  function extractImports(content) {
6
- const importRegex = /import\s+['"]([^'"]+)['"];?/g;
6
+ const importRegex = /^\s*import\s+(?:[^'"\n;]+?\s+from\s+)?['"]([^'"]+)['"]\s*;?\s*$/gm;
7
7
  const imports = [];
8
8
  let match;
9
9
 
@@ -36,7 +36,7 @@ function processScriptFile(filePath, processedFiles = new Set()) {
36
36
  importedContent += processScriptFile(importPath, processedFiles);
37
37
  }
38
38
 
39
- content = content.replace(/import\s+['"][^'"]+['"];?\s*/g, '');
39
+ content = content.replace(/^\s*import\s+(?:[^'"\n;]+?\s+from\s+)?['"][^'"]+['"]\s*;?\s*$/gm, '');
40
40
 
41
41
  if (process.env.NODE_ENV === 'production') {
42
42
  content = content.replace(/\/\*\*[\s\S]*?\*\//g, '');