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 +21 -0
- package/bin/version.txt +1 -1
- package/package.json +1 -1
- package/src/commands/add-dev-kit.js +36 -0
- package/src/commands/init.js +39 -0
- package/src/index.js +6 -0
- package/src/lib/build-workflows.js +66 -0
- package/src/lib/prompts.js +29 -0
- package/src/lib/theme-dev-kit.js +199 -0
- package/src/lib/workflows.js +4 -0
- package/src/workflows/build/build-scripts.js +77 -0
- package/src/workflows/build/reusable-build.yml +1 -1
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.
|
|
1
|
+
2.2.0
|
package/package.json
CHANGED
|
@@ -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
|
+
}
|
package/src/commands/init.js
CHANGED
|
@@ -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
|
+
}
|
package/src/lib/prompts.js
CHANGED
|
@@ -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
|
+
}
|
package/src/lib/workflows.js
CHANGED
|
@@ -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 };
|