climaybe 1.8.2 → 1.9.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
@@ -1,38 +1,54 @@
1
1
  # climaybe
2
2
 
3
- Shopify CI/CD CLI — scaffolds GitHub Actions workflows, branch strategy, and store config for single-store and multi-store theme repositories.
3
+ Shopify CLI for **theme CI/CD** (GitHub Actions, branches, multi-store config) and light **app repo** setup. Same install works in theme or app repositories.
4
4
 
5
- **Commit linting and AI-assisted commits are available as optional setup steps:**
5
+ **Commit linting and Cursor bundle (optional in both flows):**
6
6
 
7
- - **Conventional commit linting:** During `climaybe init`, you can choose to automatically install and configure [commitlint](https://commitlint.js.org/) and [Husky](https://typicode.github.io/husky) to enforce [Conventional Commits](https://www.conventionalcommits.org/) in your theme repository.
8
- - **Cursor rules + skills:** You can opt in to installing Electric Maybe’s bundled [Cursor](https://cursor.com/) project rules and agent skills under `.cursor/rules/` and `.cursor/skills/` (Liquid, JS, a11y, commits, changelog, Linear, etc.).
7
+ - **Conventional commit linting:** During `climaybe theme init` or `climaybe app init`, you can install [commitlint](https://commitlint.js.org/) and [Husky](https://typicode.github.io/husky) for [Conventional Commits](https://www.conventionalcommits.org/).
8
+ - **Cursor rules + skills:** Opt in to Electric Maybe’s bundled [Cursor](https://cursor.com/) rules and skills under `.cursor/rules/` and `.cursor/skills/` (themes, JS, a11y, commits, changelog, Linear, etc.).
9
9
 
10
- Both options streamline commit message quality and team workflows but are fully optional during setup.
10
+ ## Command layout (Shopify CLI–style)
11
11
 
12
- ## Install
12
+ - **`climaybe theme <command>`** — canonical commands for theme repos (workflows, stores, branches).
13
+ - **Same commands at the top level** — `climaybe init` is the same as `climaybe theme init` (backward compatible).
14
+ - **`climaybe app init`** — app repos only: writes `project_type: "app"` in `package.json` config, optional commitlint + Cursor bundle. Does **not** install theme GitHub Actions or store/branch setup.
15
+ - **`climaybe setup-commitlint`** and **`climaybe add-cursor`** — always at the top level (stack-agnostic).
16
+
17
+ Theme-only commands refuse to run when `package.json` → `config.project_type` is **`app`**.
13
18
 
14
- Install in your theme repo (project-only, no global install):
19
+ ## Install
15
20
 
16
21
  ```bash
17
- cd your-shopify-theme-repo
22
+ cd your-shopify-theme-repo # or app repo
18
23
  npm install -D climaybe
19
24
  ```
20
25
 
21
26
  Run commands with `npx climaybe` (or add scripts to your `package.json`).
22
27
 
23
- ## Quick Start
28
+ ## Quick Start (theme)
24
29
 
25
30
  ```bash
26
31
  cd your-shopify-theme-repo
27
32
  npm install -D climaybe
28
33
  npx climaybe init
34
+ # equivalent: npx climaybe theme init
29
35
  ```
30
36
 
31
37
  The interactive setup will ask for your store URL(s) and configure everything automatically.
32
38
 
39
+ ## Quick Start (app)
40
+
41
+ ```bash
42
+ cd your-shopify-app-repo
43
+ npm install -D climaybe
44
+ npx climaybe app init
45
+ ```
46
+
47
+ Installs optional commitlint/Husky and Cursor rules/skills only. Use [Shopify CLI](https://shopify.dev/docs/api/shopify-cli) for app development and deployment.
48
+
33
49
  ## Commands
34
50
 
35
- ### `climaybe init`
51
+ ### `climaybe init` / `climaybe theme init`
36
52
 
37
53
  Interactive setup that configures your repo for CI/CD.
38
54
 
@@ -49,7 +65,11 @@ Interactive setup that configures your repo for CI/CD.
49
65
  11. Creates git branches and store directories (multi-store)
50
66
  12. Optionally installs commitlint, Husky, and the Cursor bundle (rules + skills)
51
67
 
52
- ### `climaybe add-store`
68
+ ### `climaybe app init`
69
+
70
+ Interactive setup for a **Shopify app** repository: optional commitlint + Husky, optional Cursor bundle, and `project_type: "app"` in `package.json` `config`. No theme workflows, stores, or staging/live branches.
71
+
72
+ ### `climaybe add-store` / `climaybe theme add-store`
53
73
 
54
74
  Add a new store to an existing setup.
55
75
 
@@ -62,7 +82,7 @@ npx climaybe add-store
62
82
  - Creates `stores/<alias>/` directory structure
63
83
  - If store count goes from 1 to 2+, automatically migrates from single to multi-store mode
64
84
 
65
- ### `climaybe switch <alias>`
85
+ ### `climaybe switch <alias>` / `climaybe theme switch`
66
86
 
67
87
  Switch your local dev environment to a specific store (multi-store only).
68
88
 
@@ -72,7 +92,7 @@ npx climaybe switch voldt-norway
72
92
 
73
93
  Copies `stores/<alias>/` JSON files to the repo root so you can preview that store locally.
74
94
 
75
- ### `climaybe sync [alias]`
95
+ ### `climaybe sync [alias]` / `climaybe theme sync`
76
96
 
77
97
  Sync root JSON files back to a store directory (multi-store only).
78
98
 
@@ -82,7 +102,7 @@ npx climaybe sync voldt-norway
82
102
 
83
103
  If no alias is given, syncs to the default store.
84
104
 
85
- ### `climaybe ensure-branches`
105
+ ### `climaybe ensure-branches` / `climaybe theme ensure-branches`
86
106
 
87
107
  Create missing `staging` and per-store branches (`staging-<alias>`, `live-<alias>`) from your current branch (usually `main`). Use when the repo only has `main` (e.g. after a fresh clone) so the main → staging-&lt;store&gt; sync can run.
88
108
 
@@ -91,7 +111,7 @@ npx climaybe ensure-branches
91
111
  git push origin --all
92
112
  ```
93
113
 
94
- ### `climaybe update-workflows`
114
+ ### `climaybe update-workflows` / `climaybe theme update-workflows`
95
115
 
96
116
  Refresh GitHub Actions workflows from the latest bundled templates.
97
117
 
package/bin/version.txt CHANGED
@@ -1 +1 @@
1
- 1.8.2
1
+ 1.9.0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "climaybe",
3
- "version": "1.8.2",
4
- "description": "Shopify CI/CD CLI — scaffolds workflows, branch strategy, and store config for single-store and multi-store theme repos",
3
+ "version": "1.9.0",
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": {
7
7
  "climaybe": "bin/cli.js"
@@ -1,6 +1,14 @@
1
1
  import pc from 'picocolors';
2
2
  import { promptNewStore, promptConfigureCISecrets, promptUpdateExistingSecrets, promptSecretValue, promptTestThemeToken } from '../lib/prompts.js';
3
- import { readConfig, addStoreToConfig, getStoreAliases, getMode, isPreviewWorkflowsEnabled, isBuildWorkflowsEnabled } from '../lib/config.js';
3
+ import {
4
+ readConfig,
5
+ addStoreToConfig,
6
+ getStoreAliases,
7
+ getMode,
8
+ isPreviewWorkflowsEnabled,
9
+ isBuildWorkflowsEnabled,
10
+ } from '../lib/config.js';
11
+ import { requireThemeProject } from '../lib/theme-guard.js';
4
12
  import { createStoreBranches } from '../lib/git.js';
5
13
  import { scaffoldWorkflows } from '../lib/workflows.js';
6
14
  import { createStoreDirectories } from '../lib/store-sync.js';
@@ -21,10 +29,12 @@ import {
21
29
  export async function addStoreCommand() {
22
30
  console.log(pc.bold('\n climaybe — Add Store\n'));
23
31
 
32
+ if (!requireThemeProject()) return;
33
+
24
34
  // Guard: config must exist
25
35
  const config = readConfig();
26
36
  if (!config?.stores) {
27
- console.log(pc.red(' No climaybe config found. Run "climaybe init" first.\n'));
37
+ console.log(pc.red(' No climaybe config found. Run "climaybe theme init" (or "climaybe init") first.\n'));
28
38
  return;
29
39
  }
30
40
 
@@ -0,0 +1,89 @@
1
+ import prompts from 'prompts';
2
+ import pc from 'picocolors';
3
+ import { promptCommitlint, promptCursorSkills } from '../lib/prompts.js';
4
+ import {
5
+ readConfig,
6
+ writeConfig,
7
+ getProjectType,
8
+ isThemeProjectForAppInit,
9
+ } from '../lib/config.js';
10
+ import { ensureGitRepo, ensureInitialCommit } from '../lib/git.js';
11
+ import { scaffoldCommitlint } from '../lib/commit-tooling.js';
12
+ import { scaffoldCursorBundle } from '../lib/cursor-bundle.js';
13
+
14
+ /**
15
+ * Minimal Shopify app repo setup: commitlint, Cursor bundle, project_type in config.
16
+ * No theme stores, branches, or GitHub Actions theme workflows.
17
+ */
18
+ async function runAppInitFlow() {
19
+ const enableCommitlint = await promptCommitlint();
20
+ const enableCursorSkills = await promptCursorSkills();
21
+
22
+ const config = {
23
+ project_type: 'app',
24
+ commitlint: enableCommitlint,
25
+ cursor_skills: enableCursorSkills,
26
+ };
27
+
28
+ writeConfig(config, process.cwd(), { defaultPackageName: 'shopify-app' });
29
+ console.log(pc.green(' Updated package.json config.'));
30
+
31
+ ensureGitRepo();
32
+ ensureInitialCommit();
33
+
34
+ if (enableCommitlint) {
35
+ console.log(pc.dim(' Setting up commitlint + Husky...'));
36
+ if (scaffoldCommitlint()) {
37
+ console.log(pc.green(' commitlint + Husky installed (conventional commits enforced on git commit).'));
38
+ } else {
39
+ console.log(pc.yellow(' commitlint setup failed or skipped (run npm install manually).'));
40
+ }
41
+ }
42
+ if (enableCursorSkills) {
43
+ const cursorOk = scaffoldCursorBundle();
44
+ if (cursorOk) {
45
+ console.log(pc.green(' Electric Maybe Cursor rules + skills → .cursor/rules, .cursor/skills'));
46
+ } else {
47
+ console.log(pc.yellow(' Cursor bundle not found in package (skipped).'));
48
+ }
49
+ }
50
+
51
+ console.log(pc.bold(pc.green('\n App setup complete!\n')));
52
+ console.log(pc.dim(' commitlint + Husky: ' + (enableCommitlint ? 'enabled' : 'disabled')));
53
+ console.log(pc.dim(' Cursor rules + skills: ' + (enableCursorSkills ? 'installed' : 'skipped')));
54
+ console.log(pc.dim('\n Next steps:'));
55
+ console.log(pc.dim(' Use Shopify CLI (`shopify app dev`, etc.) for app development.'));
56
+ console.log(pc.dim(' Theme CI/CD workflows are not installed; add your own deployment as needed.\n'));
57
+ }
58
+
59
+ export async function appInitCommand() {
60
+ console.log(pc.bold('\n climaybe — Shopify app setup\n'));
61
+
62
+ if (isThemeProjectForAppInit()) {
63
+ console.log(
64
+ pc.red(
65
+ ' This repo looks like a theme project (stores in config or project_type: theme).'
66
+ )
67
+ );
68
+ console.log(pc.dim(' Use: npx climaybe theme init (or: npx climaybe init)\n'));
69
+ return;
70
+ }
71
+
72
+ const existing = readConfig();
73
+ const hasConfig = existing != null && typeof existing === 'object';
74
+
75
+ if (hasConfig && getProjectType() === 'app') {
76
+ const { reinit } = await prompts({
77
+ type: 'confirm',
78
+ name: 'reinit',
79
+ message: 'This repo already has climaybe app config. Re-run setup (merge config and reinstall optional tooling)?',
80
+ initial: false,
81
+ });
82
+ if (!reinit) {
83
+ console.log(pc.dim(' Use "climaybe setup-commitlint" or "climaybe add-cursor" to refresh tooling.\n'));
84
+ return;
85
+ }
86
+ }
87
+
88
+ await runAppInitFlow();
89
+ }
@@ -1,5 +1,6 @@
1
1
  import pc from 'picocolors';
2
2
  import { readConfig } from '../lib/config.js';
3
+ import { requireThemeProject } from '../lib/theme-guard.js';
3
4
  import {
4
5
  isGitRepo,
5
6
  currentBranch,
@@ -14,9 +15,11 @@ import {
14
15
  export async function ensureBranchesCommand() {
15
16
  console.log(pc.bold('\n climaybe — Ensure Branches\n'));
16
17
 
18
+ if (!requireThemeProject()) return;
19
+
17
20
  const config = readConfig();
18
21
  if (!config?.stores) {
19
- console.log(pc.red(' No climaybe config found. Run "climaybe init" first.\n'));
22
+ console.log(pc.red(' No climaybe config found. Run "climaybe theme init" (or "climaybe init") first.\n'));
20
23
  return;
21
24
  }
22
25
 
@@ -11,7 +11,7 @@ import {
11
11
  promptSecretValue,
12
12
  promptTestThemeToken,
13
13
  } from '../lib/prompts.js';
14
- import { readConfig, writeConfig } from '../lib/config.js';
14
+ import { readConfig, writeConfig, getProjectType } from '../lib/config.js';
15
15
  import { ensureGitRepo, ensureInitialCommit, ensureStagingBranch, createStoreBranches, getSuggestedTagForRelease } from '../lib/git.js';
16
16
  import { scaffoldWorkflows } from '../lib/workflows.js';
17
17
  import { createStoreDirectories } from '../lib/store-sync.js';
@@ -49,6 +49,7 @@ async function runInitFlow() {
49
49
 
50
50
  // 2. Build config
51
51
  const config = {
52
+ project_type: 'theme',
52
53
  port: 9295,
53
54
  default_store: stores[0].domain,
54
55
  preview_workflows: enablePreviewWorkflows,
@@ -236,7 +237,14 @@ async function runInitFlow() {
236
237
  }
237
238
 
238
239
  export async function initCommand() {
239
- console.log(pc.bold('\n climaybe — Shopify CI/CD Setup\n'));
240
+ console.log(pc.bold('\n climaybe — Shopify theme CI/CD setup\n'));
241
+
242
+ if (getProjectType() === 'app') {
243
+ console.log(pc.red(' This repo is configured as a Shopify app (project_type: app).'));
244
+ console.log(pc.dim(' Use: npx climaybe app init'));
245
+ console.log(pc.dim(' To switch to theme CI/CD, remove or edit package.json → config first.\n'));
246
+ return;
247
+ }
240
248
 
241
249
  const existing = readConfig();
242
250
  const hasConfig = existing != null && typeof existing === 'object';
@@ -249,9 +257,9 @@ export async function initCommand() {
249
257
  initial: false,
250
258
  });
251
259
  if (!reinit) {
252
- console.log(pc.dim(' Use "climaybe add-store" to add more stores.'));
253
- console.log(pc.dim(' Use "climaybe update-workflows" to refresh workflows.'));
254
- console.log(pc.dim(' Use "climaybe reinit" to reinitialize from scratch.\n'));
260
+ console.log(pc.dim(' Use "climaybe theme add-store" (or "climaybe add-store") to add more stores.'));
261
+ console.log(pc.dim(' Use "climaybe theme update-workflows" (or "climaybe update-workflows") to refresh workflows.'));
262
+ console.log(pc.dim(' Use "climaybe theme reinit" (or "climaybe reinit") to reinitialize from scratch.\n'));
255
263
  return;
256
264
  }
257
265
  }
@@ -260,6 +268,13 @@ export async function initCommand() {
260
268
  }
261
269
 
262
270
  export async function reinitCommand() {
263
- console.log(pc.bold('\n climaybe — Reinitialize CI/CD Setup\n'));
271
+ console.log(pc.bold('\n climaybe — Reinitialize theme CI/CD setup\n'));
272
+
273
+ if (getProjectType() === 'app') {
274
+ console.log(pc.red(' This repo is a Shopify app (project_type: app).'));
275
+ console.log(pc.dim(' Use: npx climaybe app init\n'));
276
+ return;
277
+ }
278
+
264
279
  await runInitFlow();
265
280
  }
@@ -1,10 +1,13 @@
1
1
  import pc from 'picocolors';
2
2
  import { getStoreAliases, getMode } from '../lib/config.js';
3
3
  import { storesToRoot } from '../lib/store-sync.js';
4
+ import { requireThemeProject } from '../lib/theme-guard.js';
4
5
 
5
6
  export async function switchCommand(alias) {
6
7
  console.log(pc.bold('\n climaybe — Switch Store\n'));
7
8
 
9
+ if (!requireThemeProject()) return;
10
+
8
11
  const mode = getMode();
9
12
  if (mode !== 'multi') {
10
13
  console.log(pc.yellow(' Switch is only available in multi-store mode.\n'));
@@ -22,6 +25,6 @@ export async function switchCommand(alias) {
22
25
  if (ok) {
23
26
  console.log(pc.bold(pc.green(`\n Switched to store: ${alias}\n`)));
24
27
  console.log(pc.dim(' Root JSON files now reflect this store\'s data.'));
25
- console.log(pc.dim(' Use "climaybe sync" to write changes back.\n'));
28
+ console.log(pc.dim(' Use "climaybe theme sync" (or "climaybe sync") to write changes back.\n'));
26
29
  }
27
30
  }
@@ -1,10 +1,13 @@
1
1
  import pc from 'picocolors';
2
2
  import { getStoreAliases, getMode, readConfig } from '../lib/config.js';
3
3
  import { rootToStores } from '../lib/store-sync.js';
4
+ import { requireThemeProject } from '../lib/theme-guard.js';
4
5
 
5
6
  export async function syncCommand(alias) {
6
7
  console.log(pc.bold('\n climaybe — Sync to Store\n'));
7
8
 
9
+ if (!requireThemeProject()) return;
10
+
8
11
  const mode = getMode();
9
12
  if (mode !== 'multi') {
10
13
  console.log(pc.yellow(' Sync is only available in multi-store mode.\n'));
@@ -22,7 +25,7 @@ export async function syncCommand(alias) {
22
25
 
23
26
  if (!alias) {
24
27
  console.log(pc.red(' No alias provided and no default store found.'));
25
- console.log(pc.dim(` Usage: climaybe sync <alias>`));
28
+ console.log(pc.dim(` Usage: climaybe theme sync <alias>`));
26
29
  console.log(pc.dim(` Available: ${aliases.join(', ')}\n`));
27
30
  return;
28
31
  }
@@ -1,13 +1,16 @@
1
1
  import pc from 'picocolors';
2
2
  import { getMode, isBuildWorkflowsEnabled, isPreviewWorkflowsEnabled, readConfig } from '../lib/config.js';
3
3
  import { scaffoldWorkflows } from '../lib/workflows.js';
4
+ import { requireThemeProject } from '../lib/theme-guard.js';
4
5
 
5
6
  export async function updateWorkflowsCommand() {
6
7
  console.log(pc.bold('\n climaybe — Update Workflows\n'));
7
8
 
9
+ if (!requireThemeProject()) return;
10
+
8
11
  const config = readConfig();
9
12
  if (!config?.stores) {
10
- console.log(pc.red(' No climaybe config found. Run "climaybe init" first.\n'));
13
+ console.log(pc.red(' No climaybe config found. Run "climaybe theme init" (or "climaybe init") first.\n'));
11
14
  return;
12
15
  }
13
16
 
package/src/index.js CHANGED
@@ -7,57 +7,79 @@ 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 { appInitCommand } from './commands/app-init.js';
10
11
 
11
12
  /**
12
- * Create the CLI program (for testing and for run).
13
- * @param {string} [version] - Version string (from bin/cli.js when run as CLI; from package.json in tests).
14
- * @param {string} [packageDir] - Package root dir (shown with --version so user can see which install is running).
13
+ * Register theme CI/CD commands on a Commander instance (root or `theme` subgroup).
14
+ * @param {import('commander').Command} cmd
15
15
  */
16
- export function createProgram(version = '0.0.0', packageDir = '') {
17
- const program = new Command();
18
- const versionDisplay = packageDir ? `${version}\n from: ${packageDir}` : version;
19
-
20
- program
21
- .name('climaybe')
22
- .description('Shopify CI/CD CLI — scaffolds workflows, branch strategy, and store config')
23
- .version(versionDisplay);
24
-
25
- program
16
+ function registerThemeCommands(cmd) {
17
+ cmd
26
18
  .command('init')
27
19
  .description('Initialize CI/CD setup for a Shopify theme repo')
28
20
  .action(initCommand);
29
21
 
30
- program
22
+ cmd
31
23
  .command('reinit')
32
- .description('Reinitialize CI/CD setup (removes existing config and re-scaffolds workflows)')
24
+ .description('Reinitialize theme CI/CD (re-scaffold workflows from templates)')
33
25
  .action(reinitCommand);
34
26
 
35
- program
27
+ cmd
36
28
  .command('add-store')
37
29
  .description('Add a new store to an existing multi-store config')
38
30
  .action(addStoreCommand);
39
31
 
40
- program
32
+ cmd
41
33
  .command('switch')
42
34
  .argument('<alias>', 'Store alias to switch to')
43
35
  .description('Switch local dev environment to a specific store')
44
36
  .action(switchCommand);
45
37
 
46
- program
38
+ cmd
47
39
  .command('sync')
48
40
  .argument('[alias]', 'Store alias to sync to (defaults to active store)')
49
41
  .description('Sync root JSON files back to a store directory')
50
42
  .action(syncCommand);
51
43
 
52
- program
44
+ cmd
53
45
  .command('update-workflows')
54
46
  .description('Refresh GitHub Actions workflows from latest bundled templates')
55
47
  .action(updateWorkflowsCommand);
56
48
 
57
- program
49
+ cmd
58
50
  .command('ensure-branches')
59
51
  .description('Create missing staging and per-store branches from current HEAD (then push)')
60
52
  .action(ensureBranchesCommand);
53
+ }
54
+
55
+ /**
56
+ * Create the CLI program (for testing and for run).
57
+ * @param {string} [version] - Version string (from bin/cli.js when run as CLI; from package.json in tests).
58
+ * @param {string} [packageDir] - Package root dir (shown with --version so user can see which install is running).
59
+ */
60
+ export function createProgram(version = '0.0.0', packageDir = '') {
61
+ const program = new Command();
62
+ const versionDisplay = packageDir ? `${version}\n from: ${packageDir}` : version;
63
+
64
+ program
65
+ .name('climaybe')
66
+ .description(
67
+ 'Shopify CLI — theme CI/CD (workflows, branches, stores) and app repo helpers (commitlint, Cursor bundle)'
68
+ )
69
+ .version(versionDisplay);
70
+
71
+ const theme = program
72
+ .command('theme')
73
+ .description('Shopify theme CI/CD: workflows, branches, and multi-store config');
74
+
75
+ registerThemeCommands(theme);
76
+ registerThemeCommands(program);
77
+
78
+ const app = program.command('app').description('Shopify app repo helpers (no theme workflows)');
79
+ app
80
+ .command('init')
81
+ .description('Set up commitlint, Cursor rules/skills, and project_type: app in package.json')
82
+ .action(appInitCommand);
61
83
 
62
84
  program
63
85
  .command('setup-commitlint')
package/src/lib/config.js CHANGED
@@ -39,11 +39,20 @@ export function readConfig(cwd = process.cwd()) {
39
39
  return pkg?.config ?? null;
40
40
  }
41
41
 
42
+ /**
43
+ * @typedef {object} WriteConfigOptions
44
+ * @property {string} [defaultPackageName] - When creating package.json, set `name` (default: shopify-theme).
45
+ */
46
+
42
47
  /**
43
48
  * Write/merge the climaybe config section into package.json.
44
49
  * Creates package.json if it doesn't exist.
50
+ * @param {object} config
51
+ * @param {string} [cwd]
52
+ * @param {WriteConfigOptions} [options]
45
53
  */
46
- export function writeConfig(config, cwd = process.cwd()) {
54
+ export function writeConfig(config, cwd = process.cwd(), options = {}) {
55
+ const defaultPackageName = options.defaultPackageName ?? 'shopify-theme';
47
56
  let pkg = readPkg(cwd);
48
57
  if (!pkg) {
49
58
  let version = '1.0.0';
@@ -54,7 +63,7 @@ export function writeConfig(config, cwd = process.cwd()) {
54
63
  // not a git repo or no tags
55
64
  }
56
65
  pkg = {
57
- name: 'shopify-theme',
66
+ name: defaultPackageName,
58
67
  version,
59
68
  private: true,
60
69
  config: {},
@@ -64,6 +73,34 @@ export function writeConfig(config, cwd = process.cwd()) {
64
73
  writePkg(pkg, cwd);
65
74
  }
66
75
 
76
+ /**
77
+ * Resolved project kind for guards and init flows.
78
+ * - `app` only when config explicitly sets project_type: app.
79
+ * - Otherwise `theme` (including missing config and legacy theme repos without project_type).
80
+ * @param {string} [cwd]
81
+ * @returns {'theme' | 'app'}
82
+ */
83
+ export function getProjectType(cwd = process.cwd()) {
84
+ const config = readConfig(cwd);
85
+ if (config?.project_type === 'app') return 'app';
86
+ return 'theme';
87
+ }
88
+
89
+ /**
90
+ * True if this repo is clearly a theme project (explicit project_type or legacy stores).
91
+ * Used to refuse `app init` on theme repos.
92
+ * @param {string} [cwd]
93
+ */
94
+ export function isThemeProjectForAppInit(cwd = process.cwd()) {
95
+ const config = readConfig(cwd);
96
+ if (!config) return false;
97
+ if (config.project_type === 'theme') return true;
98
+ if (config.stores && typeof config.stores === 'object' && Object.keys(config.stores).length > 0) {
99
+ return true;
100
+ }
101
+ return false;
102
+ }
103
+
67
104
  /**
68
105
  * Get the list of store aliases from config.
69
106
  */
@@ -0,0 +1,13 @@
1
+ import pc from 'picocolors';
2
+ import { getProjectType } from './config.js';
3
+
4
+ /**
5
+ * @param {string} [cwd]
6
+ * @returns {boolean} false if the repo is an app project (caller should return early).
7
+ */
8
+ export function requireThemeProject(cwd = process.cwd()) {
9
+ if (getProjectType(cwd) !== 'app') return true;
10
+ console.log(pc.red(' This command is for theme repos only. This project has project_type: app.'));
11
+ console.log(pc.dim(' Use climaybe app init for app setup; theme stores and workflows do not apply here.\n'));
12
+ return false;
13
+ }