climaybe 2.0.0 → 2.2.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
@@ -231,12 +231,33 @@ Enabled via `climaybe init` prompt (`Enable preview + cleanup workflows?`; defau
231
231
 
232
232
  Enabled via `climaybe init` prompt (`Enable build + Lighthouse workflows?`; default: yes).
233
233
 
234
+ When enabled, `init` validates required theme files and exits with an error if any are missing:
235
+ - `_scripts/main.js`
236
+ - `_styles/main.css`
237
+ - `assets/`
238
+ - `release-notes.md`
239
+
240
+ `climaybe` auto-installs the shared build script at `.climaybe/build-scripts.js` during workflow scaffolding.
241
+
234
242
  | Workflow | Trigger | What it does |
235
243
  |----------|---------|-------------|
236
244
  | `build-pipeline.yml` | Push to any branch | Runs reusable build and Lighthouse checks (when required secrets exist) |
237
245
  | `reusable-build.yml` | workflow_call | Runs Node build + Tailwind compile, then commits compiled assets when changed |
238
246
  | `create-release.yml` | Push tag `v*` | Builds release archive and creates GitHub Release using `release-notes.md` |
239
247
 
248
+ ### Optional theme dev kit package
249
+
250
+ During `climaybe init`, you can enable the Electric Maybe theme dev kit (default: yes). This installs local
251
+ dev scripts/config defaults (`nodemon.json`, `.theme-check.yml`, `.shopifyignore`, `.prettierrc`,
252
+ `.lighthouserc.js`), merges matching `package.json` scripts/dependencies, appends a managed `.gitignore` block,
253
+ and optionally adds `.vscode/tasks.json` (default: yes).
254
+
255
+ If these files already exist, `init` warns that they will be replaced.
256
+
257
+ You can install/update this later with:
258
+
259
+ `climaybe add-dev-kit` (or `climaybe theme add-dev-kit`)
260
+
240
261
  ## Versioning
241
262
 
242
263
  - **Version format**: Always three-part (e.g. `v3.2.0`). No version in code or PR title; the system infers from tags.
package/bin/version.txt CHANGED
@@ -1 +1 @@
1
- 2.0.0
1
+ 2.2.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "climaybe",
3
- "version": "2.0.0",
3
+ "version": "2.2.0",
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": {
@@ -0,0 +1,36 @@
1
+ import prompts from 'prompts';
2
+ import pc from 'picocolors';
3
+ import { readConfig, writeConfig } from '../lib/config.js';
4
+ import { getDevKitExistingFiles, scaffoldThemeDevKit } from '../lib/theme-dev-kit.js';
5
+ import { promptVSCodeDevTasks } from '../lib/prompts.js';
6
+
7
+ export async function addDevKitCommand() {
8
+ console.log(pc.bold('\n climaybe — Add theme dev kit\n'));
9
+
10
+ const includeVSCodeTasks = await promptVSCodeDevTasks();
11
+ const existing = getDevKitExistingFiles({ includeVSCodeTasks });
12
+ if (existing.length > 0) {
13
+ console.log(pc.yellow(' Some dev kit files already exist and will be replaced:'));
14
+ for (const path of existing) console.log(pc.yellow(` - ${path}`));
15
+ const { ok } = await prompts({
16
+ type: 'confirm',
17
+ name: 'ok',
18
+ message: 'Replace these files?',
19
+ initial: true,
20
+ });
21
+ if (!ok) {
22
+ console.log(pc.dim(' Cancelled.\n'));
23
+ return;
24
+ }
25
+ }
26
+
27
+ const config = readConfig() || {};
28
+ scaffoldThemeDevKit({
29
+ includeVSCodeTasks,
30
+ defaultStoreDomain: config.default_store || '',
31
+ });
32
+ writeConfig({ dev_kit: true, vscode_tasks: includeVSCodeTasks });
33
+
34
+ console.log(pc.green(' Theme dev kit installed.'));
35
+ console.log(pc.dim(' Added scripts/configs for local serve/watch/lint and ignore defaults.\n'));
36
+ }
@@ -4,6 +4,8 @@ import {
4
4
  promptStoreLoop,
5
5
  promptPreviewWorkflows,
6
6
  promptBuildWorkflows,
7
+ promptDevKit,
8
+ promptVSCodeDevTasks,
7
9
  promptCommitlint,
8
10
  promptCursorSkills,
9
11
  promptConfigureCISecrets,
@@ -17,6 +19,8 @@ import { scaffoldWorkflows } from '../lib/workflows.js';
17
19
  import { createStoreDirectories } from '../lib/store-sync.js';
18
20
  import { scaffoldCommitlint } from '../lib/commit-tooling.js';
19
21
  import { scaffoldCursorBundle } from '../lib/cursor-bundle.js';
22
+ import { getMissingBuildWorkflowRequirements, getBuildScriptRelativePath } from '../lib/build-workflows.js';
23
+ import { getDevKitExistingFiles, scaffoldThemeDevKit } from '../lib/theme-dev-kit.js';
20
24
  import {
21
25
  isGhAvailable,
22
26
  hasGitHubRemote,
@@ -42,11 +46,27 @@ async function runInitFlow() {
42
46
  const mode = stores.length > 1 ? 'multi' : 'single';
43
47
  const enablePreviewWorkflows = await promptPreviewWorkflows();
44
48
  const enableBuildWorkflows = await promptBuildWorkflows();
49
+ const enableDevKit = await promptDevKit();
50
+ const enableVSCodeTasks = enableDevKit ? await promptVSCodeDevTasks() : false;
45
51
  const enableCommitlint = await promptCommitlint();
46
52
  const enableCursorSkills = await promptCursorSkills();
47
53
 
48
54
  console.log(pc.dim(`\n Mode: ${mode}-store (${stores.length} store(s))`));
49
55
 
56
+ if (enableBuildWorkflows) {
57
+ const missingBuildFiles = getMissingBuildWorkflowRequirements();
58
+ if (missingBuildFiles.length > 0) {
59
+ console.log(pc.red('\n Build workflows are enabled, but required files are missing:'));
60
+ for (const req of missingBuildFiles) {
61
+ const expected = req.kind === 'dir' ? `${req.path}/` : req.path;
62
+ console.log(pc.red(` - ${expected}`));
63
+ }
64
+ console.log(pc.dim(`\n climaybe will auto-install ${getBuildScriptRelativePath()} during workflow scaffolding.`));
65
+ console.log(pc.dim(' Add the missing files above, then run init again.\n'));
66
+ return;
67
+ }
68
+ }
69
+
50
70
  // 2. Build config
51
71
  const config = {
52
72
  project_type: 'theme',
@@ -54,6 +74,8 @@ async function runInitFlow() {
54
74
  default_store: stores[0].domain,
55
75
  preview_workflows: enablePreviewWorkflows,
56
76
  build_workflows: enableBuildWorkflows,
77
+ dev_kit: enableDevKit,
78
+ vscode_tasks: enableVSCodeTasks,
57
79
  commitlint: enableCommitlint,
58
80
  cursor_skills: enableCursorSkills,
59
81
  stores: {},
@@ -107,6 +129,19 @@ async function runInitFlow() {
107
129
  }
108
130
  }
109
131
 
132
+ if (enableDevKit) {
133
+ const existing = getDevKitExistingFiles({ includeVSCodeTasks: enableVSCodeTasks });
134
+ if (existing.length > 0) {
135
+ console.log(pc.yellow(' Theme dev kit will replace existing files:'));
136
+ for (const path of existing) console.log(pc.yellow(` - ${path}`));
137
+ }
138
+ scaffoldThemeDevKit({
139
+ includeVSCodeTasks: enableVSCodeTasks,
140
+ defaultStoreDomain: stores[0]?.domain || '',
141
+ });
142
+ console.log(pc.green(' Theme dev kit installed (scripts/watch/lint + ignore defaults).'));
143
+ }
144
+
110
145
  // Done
111
146
  console.log(pc.bold(pc.green('\n Setup complete!\n')));
112
147
 
@@ -122,6 +157,10 @@ async function runInitFlow() {
122
157
  }
123
158
  console.log(pc.dim(` Preview workflows: ${enablePreviewWorkflows ? 'enabled' : 'disabled'}`));
124
159
  console.log(pc.dim(` Build workflows: ${enableBuildWorkflows ? 'enabled' : 'disabled'}`));
160
+ console.log(pc.dim(` Theme dev kit: ${enableDevKit ? 'enabled' : 'disabled'}`));
161
+ if (enableDevKit) {
162
+ console.log(pc.dim(` VS Code tasks: ${enableVSCodeTasks ? 'enabled' : 'disabled'}`));
163
+ }
125
164
  console.log(pc.dim(` commitlint + Husky: ${enableCommitlint ? 'enabled' : 'disabled'}`));
126
165
  console.log(pc.dim(` Cursor rules + skills: ${enableCursorSkills ? 'installed' : 'skipped'}`));
127
166
 
package/src/index.js CHANGED
@@ -7,6 +7,7 @@ import { updateWorkflowsCommand } from './commands/update-workflows.js';
7
7
  import { ensureBranchesCommand } from './commands/ensure-branches.js';
8
8
  import { setupCommitlintCommand } from './commands/setup-commitlint.js';
9
9
  import { addCursorSkillCommand } from './commands/add-cursor-skill.js';
10
+ import { addDevKitCommand } from './commands/add-dev-kit.js';
10
11
  import { appInitCommand } from './commands/app-init.js';
11
12
 
12
13
  /**
@@ -41,6 +42,11 @@ function registerThemeCommands(cmd) {
41
42
  .description('Sync root JSON files back to a store directory')
42
43
  .action(syncCommand);
43
44
 
45
+ cmd
46
+ .command('add-dev-kit')
47
+ .description('Install/update local theme dev kit files (scripts, lint, ignores, optional VS Code tasks)')
48
+ .action(addDevKitCommand);
49
+
44
50
  cmd
45
51
  .command('update-workflows')
46
52
  .description('Refresh GitHub Actions workflows from latest bundled templates')
@@ -0,0 +1,66 @@
1
+ import { copyFileSync, existsSync, mkdirSync, rmSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { readFileSync, writeFileSync } from 'node:fs';
5
+
6
+ const SCRIPT_SOURCE = fileURLToPath(new URL('../workflows/build/build-scripts.js', import.meta.url));
7
+ const SCRIPT_TARGET = '.climaybe/build-scripts.js';
8
+ const SCRIPT_SHIM_TARGET = 'build-scripts.js';
9
+ const SCRIPT_SHIM_CONTENT = `// Auto-generated by climaybe. Keep for npm scripts compatibility.
10
+ // Delegates to the bundled implementation in .climaybe/.
11
+ const { buildScripts } = require('./.climaybe/build-scripts.js');
12
+
13
+ if (require.main === module) {
14
+ buildScripts();
15
+ }
16
+
17
+ module.exports = { buildScripts };
18
+ `;
19
+
20
+ const REQUIRED_PATHS = [
21
+ { path: '_scripts/main.js', kind: 'file' },
22
+ { path: '_styles/main.css', kind: 'file' },
23
+ { path: 'assets', kind: 'dir' },
24
+ { path: 'release-notes.md', kind: 'file' },
25
+ ];
26
+
27
+ function targetScriptPath(cwd = process.cwd()) {
28
+ return join(cwd, SCRIPT_TARGET);
29
+ }
30
+
31
+ export function installBuildScript(cwd = process.cwd()) {
32
+ const target = targetScriptPath(cwd);
33
+ mkdirSync(join(cwd, '.climaybe'), { recursive: true });
34
+ copyFileSync(SCRIPT_SOURCE, target);
35
+ const shimTarget = join(cwd, SCRIPT_SHIM_TARGET);
36
+ if (!existsSync(shimTarget)) {
37
+ writeFileSync(shimTarget, SCRIPT_SHIM_CONTENT, 'utf-8');
38
+ }
39
+ return target;
40
+ }
41
+
42
+ export function removeBuildScript(cwd = process.cwd()) {
43
+ const target = targetScriptPath(cwd);
44
+ if (existsSync(target)) rmSync(target);
45
+ const shimTarget = join(cwd, SCRIPT_SHIM_TARGET);
46
+ if (!existsSync(shimTarget)) return;
47
+ const content = readFileSync(shimTarget, 'utf-8');
48
+ if (content === SCRIPT_SHIM_CONTENT) {
49
+ rmSync(shimTarget);
50
+ }
51
+ }
52
+
53
+ export function getMissingBuildWorkflowRequirements(cwd = process.cwd()) {
54
+ const missing = [];
55
+ for (const req of REQUIRED_PATHS) {
56
+ const abs = join(cwd, req.path);
57
+ if (!existsSync(abs)) {
58
+ missing.push(req);
59
+ }
60
+ }
61
+ return missing;
62
+ }
63
+
64
+ export function getBuildScriptRelativePath() {
65
+ return SCRIPT_TARGET;
66
+ }
@@ -148,6 +148,35 @@ export async function promptBuildWorkflows() {
148
148
  return !!enableBuildWorkflows;
149
149
  }
150
150
 
151
+ /**
152
+ * Ask whether to scaffold the local theme dev kit files (scripts, lint, ignores, editor tasks).
153
+ */
154
+ export async function promptDevKit() {
155
+ const { enableDevKit } = await prompts({
156
+ type: 'confirm',
157
+ name: 'enableDevKit',
158
+ message:
159
+ 'Install Electric Maybe theme dev kit? (local scripts/watch/lint configs, ignores, and optional VS Code tasks)',
160
+ initial: true,
161
+ });
162
+
163
+ return !!enableDevKit;
164
+ }
165
+
166
+ /**
167
+ * Ask whether to scaffold VS Code tasks for local serve/watch.
168
+ */
169
+ export async function promptVSCodeDevTasks() {
170
+ const { enableVSCodeTasks } = await prompts({
171
+ type: 'confirm',
172
+ name: 'enableVSCodeTasks',
173
+ message: 'Add VS Code tasks.json to auto-run Shopify + Tailwind local dev tasks?',
174
+ initial: true,
175
+ });
176
+
177
+ return !!enableVSCodeTasks;
178
+ }
179
+
151
180
  /**
152
181
  * Ask whether to set up commitlint + Husky (conventional commits enforced on git commit).
153
182
  */
@@ -0,0 +1,199 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { join, dirname } from 'node:path';
3
+ import { readPkg, writePkg } from './config.js';
4
+
5
+ const DEV_KIT_FILES = {
6
+ 'nodemon.json': `{
7
+ "watch": ["_scripts"],
8
+ "ext": "js",
9
+ "exec": "npm run scripts:build --silent",
10
+ "quiet": true,
11
+ "no-colours": true,
12
+ "ignore": ["node_modules/**/*", "assets/**/*", "**/*.min.js"],
13
+ "delay": "500",
14
+ "polling": false,
15
+ "legacyWatch": false,
16
+ "restartable": "rs"
17
+ }
18
+ `,
19
+ '.theme-check.yml': `root: .
20
+
21
+ extends: :nothing
22
+
23
+ ignore:
24
+ - node_modules/*
25
+ - _styles/
26
+ `,
27
+ '.shopifyignore': `_styles
28
+ _scripts
29
+ .cursorrules
30
+ .config
31
+ .backups
32
+ .github
33
+ .vscode
34
+ node_modules
35
+ .gitignore
36
+ LICENSE
37
+ package.json
38
+ package-lock.json
39
+ yarn-error.log
40
+ yarn.lock
41
+ *.md
42
+ `,
43
+ '.prettierrc': `{
44
+ "tabWidth": 2,
45
+ "useTabs": false,
46
+ "plugins": ["@shopify/prettier-plugin-liquid"]
47
+ }
48
+ `,
49
+ '.lighthouserc.js': `module.exports = {
50
+ ci: {
51
+ collect: {
52
+ url: ['http://localhost:9292'],
53
+ startServerCommand: 'shopify theme serve',
54
+ startServerReadyPattern: 'Preview your theme',
55
+ startServerReadyTimeout: 60000,
56
+ },
57
+ assert: {
58
+ assertions: {
59
+ 'categories:performance': ['warn', { minScore: 0.9 }],
60
+ 'categories:accessibility': ['warn', { minScore: 0.9 }],
61
+ 'categories:best-practices': ['warn', { minScore: 0.9 }],
62
+ 'categories:seo': ['warn', { minScore: 0.9 }],
63
+ },
64
+ },
65
+ upload: {
66
+ target: 'temporary-public-storage',
67
+ },
68
+ },
69
+ };
70
+ `,
71
+ };
72
+
73
+ const VSCODE_TASKS_FILE = '.vscode/tasks.json';
74
+ const VSCODE_TASKS_CONTENT = `{
75
+ "version": "2.0.0",
76
+ "tasks": [
77
+ {
78
+ "label": "Shopify",
79
+ "type": "shell",
80
+ "command": "yarn shopify:serve",
81
+ "isBackground": true,
82
+ "presentation": {
83
+ "echo": true,
84
+ "reveal": "always",
85
+ "focus": true,
86
+ "panel": "new",
87
+ "group": "develop",
88
+ "showReuseMessage": false,
89
+ "clear": true
90
+ },
91
+ "problemMatcher": []
92
+ },
93
+ {
94
+ "label": "Tailwind",
95
+ "type": "shell",
96
+ "command": "yarn tailwind:watch",
97
+ "isBackground": true,
98
+ "presentation": {
99
+ "echo": true,
100
+ "reveal": "always",
101
+ "focus": false,
102
+ "panel": "new",
103
+ "group": "develop",
104
+ "showReuseMessage": false,
105
+ "clear": true
106
+ },
107
+ "problemMatcher": []
108
+ },
109
+ {
110
+ "label": "Run Both Consoles",
111
+ "dependsOn": ["Shopify", "Tailwind"],
112
+ "runOptions": {
113
+ "runOn": "folderOpen"
114
+ }
115
+ }
116
+ ]
117
+ }
118
+ `;
119
+
120
+ const GITIGNORE_BLOCK = `# climaybe: theme dev kit (managed)
121
+ .vscode
122
+ assets/style.css
123
+ assets/index.js
124
+ .shopify
125
+ .vercel
126
+ `;
127
+
128
+ const PACKAGE_MERGES = {
129
+ scripts: {
130
+ 'shopify:serve': 'shopify theme dev --theme-editor-sync --store=$npm_package_config_store',
131
+ 'shopify:populate': 'shopify populate --store=$npm_package_config_store',
132
+ 'scripts:build': 'node build-scripts.js',
133
+ 'scripts:watch': 'nodemon',
134
+ 'tailwind:watch':
135
+ `concurrently --kill-others --max-restarts 3 "NODE_ENV=production NODE_OPTIONS='--max-old-space-size=512' ` +
136
+ `npx @tailwindcss/cli -i _styles/main.css -o assets/style.css --watch" "NODE_OPTIONS='--max-old-space-size=256' ` +
137
+ `npm run scripts:watch" "npx -y @shopify/dev-mcp@latest"`,
138
+ 'tailwind:build': 'NODE_ENV=production npx @tailwindcss/cli -i _styles/main.css -o assets/style.css --minify && npm run scripts:build',
139
+ 'lint:liquid': 'shopify theme check',
140
+ 'lint:js': 'eslint ./assets/*.js --config .config/eslint.config.mjs',
141
+ 'lint:css': 'node_modules/.bin/stylelint ./assets/*.css --config .config/.stylelintrc.json',
142
+ release: 'node .sys/scripts/release.js',
143
+ },
144
+ devDependencies: {
145
+ '@shopify/prettier-plugin-liquid': '^1.6.3',
146
+ '@tailwindcss/cli': '^4.1.17',
147
+ concurrently: '^8.2.2',
148
+ nodemon: '^3.0.2',
149
+ prettier: '^3.4.2',
150
+ stylelint: '^16.9.0',
151
+ eslint: '^9.11.0',
152
+ },
153
+ };
154
+
155
+ function ensureParent(path) {
156
+ mkdirSync(dirname(path), { recursive: true });
157
+ }
158
+
159
+ function mergeGitignore(cwd = process.cwd()) {
160
+ const path = join(cwd, '.gitignore');
161
+ if (!existsSync(path)) {
162
+ writeFileSync(path, GITIGNORE_BLOCK, 'utf-8');
163
+ return;
164
+ }
165
+ const current = readFileSync(path, 'utf-8');
166
+ if (current.includes('# climaybe: theme dev kit (managed)')) return;
167
+ const next = `${current.trimEnd()}\n\n${GITIGNORE_BLOCK}`;
168
+ writeFileSync(path, `${next}\n`, 'utf-8');
169
+ }
170
+
171
+ function mergePackageJson(defaultStoreDomain = '', cwd = process.cwd()) {
172
+ const pkg = readPkg(cwd) || { name: 'shopify-theme', version: '1.0.0', private: true };
173
+ pkg.config = { ...(pkg.config || {}) };
174
+ if (!pkg.config.store && defaultStoreDomain) pkg.config.store = defaultStoreDomain;
175
+ pkg.scripts = { ...(pkg.scripts || {}), ...PACKAGE_MERGES.scripts };
176
+ pkg.devDependencies = { ...(pkg.devDependencies || {}), ...PACKAGE_MERGES.devDependencies };
177
+ writePkg(pkg, cwd);
178
+ }
179
+
180
+ export function getDevKitExistingFiles({ includeVSCodeTasks = true, cwd = process.cwd() } = {}) {
181
+ const paths = Object.keys(DEV_KIT_FILES);
182
+ if (includeVSCodeTasks) paths.push(VSCODE_TASKS_FILE);
183
+ return paths.filter((p) => existsSync(join(cwd, p)));
184
+ }
185
+
186
+ export function scaffoldThemeDevKit({ includeVSCodeTasks = true, defaultStoreDomain = '', cwd = process.cwd() } = {}) {
187
+ for (const [rel, content] of Object.entries(DEV_KIT_FILES)) {
188
+ const dest = join(cwd, rel);
189
+ ensureParent(dest);
190
+ writeFileSync(dest, content, 'utf-8');
191
+ }
192
+ if (includeVSCodeTasks) {
193
+ const dest = join(cwd, VSCODE_TASKS_FILE);
194
+ ensureParent(dest);
195
+ writeFileSync(dest, VSCODE_TASKS_CONTENT, 'utf-8');
196
+ }
197
+ mergeGitignore(cwd);
198
+ mergePackageJson(defaultStoreDomain, cwd);
199
+ }
@@ -2,6 +2,7 @@ import { existsSync, mkdirSync, readdirSync, copyFileSync, rmSync } from 'node:f
2
2
  import { join, dirname } from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
4
  import pc from 'picocolors';
5
+ import { installBuildScript, removeBuildScript } from './build-workflows.js';
5
6
 
6
7
  const __dirname = dirname(fileURLToPath(import.meta.url));
7
8
  const TEMPLATES_DIR = join(__dirname, '..', 'workflows');
@@ -109,6 +110,9 @@ export function scaffoldWorkflows(mode = 'single', options = {}, cwd = process.c
109
110
  for (const f of listYmls(buildDir)) {
110
111
  copyWorkflow(buildDir, f, dest);
111
112
  }
113
+ installBuildScript(cwd);
114
+ } else {
115
+ removeBuildScript(cwd);
112
116
  }
113
117
 
114
118
  const total = readdirSync(dest).filter((f) => f.endsWith('.yml')).length;
@@ -0,0 +1,77 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const ROOT_DIR = process.cwd();
4
+
5
+ function extractImports(content) {
6
+ const importRegex = /import\s+['"]([^'"]+)['"];?/g;
7
+ const imports = [];
8
+ let match;
9
+
10
+ while ((match = importRegex.exec(content)) !== null) {
11
+ imports.push(match[1]);
12
+ }
13
+
14
+ return imports;
15
+ }
16
+
17
+ function processScriptFile(filePath, processedFiles = new Set()) {
18
+ if (processedFiles.has(filePath)) {
19
+ return '';
20
+ }
21
+
22
+ processedFiles.add(filePath);
23
+
24
+ const fullPath = path.join(ROOT_DIR, '_scripts', filePath);
25
+
26
+ if (!fs.existsSync(fullPath)) {
27
+ console.warn(`Warning: File ${filePath} not found`);
28
+ return '';
29
+ }
30
+
31
+ let content = fs.readFileSync(fullPath, 'utf8');
32
+ const imports = extractImports(content);
33
+
34
+ let importedContent = '';
35
+ for (const importPath of imports) {
36
+ importedContent += processScriptFile(importPath, processedFiles);
37
+ }
38
+
39
+ content = content.replace(/import\s+['"][^'"]+['"];?\s*/g, '');
40
+
41
+ if (process.env.NODE_ENV === 'production') {
42
+ content = content.replace(/\/\*\*[\s\S]*?\*\//g, '');
43
+ content = content.replace(/^\s*\*.*$/gm, '');
44
+ content = content.replace(/console\.(log|warn|error)\([^)]*\);?\s*/g, '');
45
+ content = content.replace(/^\s*\n/gm, '');
46
+ }
47
+
48
+ return importedContent + '\n' + content;
49
+ }
50
+
51
+ function buildScripts() {
52
+ try {
53
+ if (global.gc) global.gc();
54
+
55
+ const mainPath = path.join(ROOT_DIR, '_scripts', 'main.js');
56
+ fs.readFileSync(mainPath, 'utf8');
57
+
58
+ const processedFiles = new Set();
59
+ const finalContent = processScriptFile('main.js', processedFiles);
60
+ const outputPath = path.join(ROOT_DIR, 'assets', 'index.js');
61
+ fs.writeFileSync(outputPath, finalContent.trim() + '\n');
62
+
63
+ const fileCount = processedFiles.size;
64
+ console.log(`✅ Scripts built (${fileCount} files)`);
65
+ processedFiles.clear();
66
+ if (global.gc) global.gc();
67
+ } catch (error) {
68
+ console.error('❌ Build error:', error.message);
69
+ process.exit(1);
70
+ }
71
+ }
72
+
73
+ if (require.main === module) {
74
+ buildScripts();
75
+ }
76
+
77
+ module.exports = { buildScripts };
@@ -28,7 +28,7 @@ jobs:
28
28
  run: npm ci
29
29
 
30
30
  - name: Build scripts
31
- run: node build-scripts.js
31
+ run: node .climaybe/build-scripts.js
32
32
 
33
33
  - name: Run Tailwind build
34
34
  id: build