climaybe 3.0.4 → 3.0.6
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 +5 -3
- package/bin/version.txt +1 -1
- package/package.json +1 -1
- package/src/commands/add-store.js +13 -7
- package/src/commands/ensure-branches.js +18 -5
- package/src/commands/init.js +20 -13
- package/src/lib/git.js +22 -0
- package/src/lib/github-secrets.js +36 -10
package/README.md
CHANGED
|
@@ -304,14 +304,16 @@ Add the following secrets to your GitHub repository (or use **GitLab CI/CD varia
|
|
|
304
304
|
|
|
305
305
|
| Secret | Required | Description |
|
|
306
306
|
|--------|----------|-------------|
|
|
307
|
-
| `GEMINI_API_KEY` |
|
|
307
|
+
| `GEMINI_API_KEY` | Optional | Google Gemini API key for AI-generated release notes fallback |
|
|
308
308
|
| `SHOPIFY_STORE_URL` | Set from config | Store URL is set automatically from the store domain(s) you add during init (no prompt). |
|
|
309
|
-
| `SHOPIFY_THEME_ACCESS_TOKEN` |
|
|
309
|
+
| `SHOPIFY_THEME_ACCESS_TOKEN` | Optional* | Theme access token for preview workflows (needed only when you want preview theme publish/cleanup to run). |
|
|
310
310
|
| `SHOP_ACCESS_TOKEN` | Optional* | Required only when optional build workflows are enabled (Lighthouse) |
|
|
311
311
|
| `LHCI_GITHUB_APP_TOKEN` | Optional* | Required only when optional build workflows are enabled (Lighthouse) |
|
|
312
312
|
| `SHOP_PASSWORD` | Optional | Used by Lighthouse action when your store requires password auth |
|
|
313
313
|
|
|
314
|
-
**
|
|
314
|
+
**Prompting behavior:** During `climaybe init` (or `add-store`), every GitHub/GitLab secret prompt is skippable. Add values later in CI settings if you prefer.
|
|
315
|
+
|
|
316
|
+
**Store URL:** During `climaybe init` (or `add-store`), store URL secret(s) are set from your configured store domain(s); theme tokens are optional prompts.
|
|
315
317
|
|
|
316
318
|
**Multi-store:** Per-store secrets `SHOPIFY_STORE_URL_<ALIAS>` and `SHOPIFY_THEME_ACCESS_TOKEN_<ALIAS>` — the URL is set from config; you must provide the theme token per store. `<ALIAS>` is uppercase with hyphens as underscores (e.g. `voldt-norway` → `SHOPIFY_STORE_URL_VOLDT_NORWAY`). Preview and cleanup: for PRs targeting **main**, **staging**, or **develop** the workflows use the **default store** (from `config.default_store` or first in `config.stores`); for PRs targeting **staging-<alias>** or **live-<alias>** they use that store’s credentials. Set either the plain `SHOPIFY_*` secrets or the `_<ALIAS>` pair for each store you use in preview.
|
|
317
319
|
|
package/bin/version.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.0.
|
|
1
|
+
3.0.6
|
package/package.json
CHANGED
|
@@ -88,7 +88,13 @@ export async function addStoreCommand() {
|
|
|
88
88
|
: { check: isGlabAvailable, checkRemote: hasGitLabRemote, set: setGitLabVariable, name: 'GitLab' };
|
|
89
89
|
|
|
90
90
|
if (!setter.check()) {
|
|
91
|
-
console.log(
|
|
91
|
+
console.log(
|
|
92
|
+
pc.yellow(
|
|
93
|
+
ciHost === 'github'
|
|
94
|
+
? ' GitHub CLI is not available (tried gh and npx gh) or not logged in. Add secrets manually in repo Settings.'
|
|
95
|
+
: ` ${setter.name} CLI is not installed or not logged in. Add secrets manually in repo Settings.`
|
|
96
|
+
)
|
|
97
|
+
);
|
|
92
98
|
} else if (!setter.checkRemote()) {
|
|
93
99
|
console.log(pc.yellow(' No ' + setter.name + ' remote (origin). Add secrets manually after pushing.'));
|
|
94
100
|
} else {
|
|
@@ -116,22 +122,22 @@ export async function addStoreCommand() {
|
|
|
116
122
|
}
|
|
117
123
|
}
|
|
118
124
|
const total = secretsToPrompt.length;
|
|
119
|
-
console.log(pc.cyan(`\n Configure ${total} secret(s) for store "${store.alias}" (
|
|
125
|
+
console.log(pc.cyan(`\n Configure ${total} secret(s) for store "${store.alias}" (all optional).\n`));
|
|
120
126
|
for (let i = 0; i < secretsToPrompt.length; i++) {
|
|
121
127
|
const secret = secretsToPrompt[i];
|
|
122
128
|
const isThemeToken = secret.name === 'SHOPIFY_THEME_ACCESS_TOKEN' || secret.name.startsWith('SHOPIFY_THEME_ACCESS_TOKEN_');
|
|
123
129
|
if (isThemeToken && store.domain) {
|
|
124
|
-
// Theme tokens are
|
|
130
|
+
// Theme tokens are optional; if provided, keep prompting until valid + set.
|
|
125
131
|
while (true) {
|
|
126
132
|
const value = await promptSecretValue(secret, i, total);
|
|
127
133
|
if (!value) {
|
|
128
|
-
console.log(pc.
|
|
129
|
-
|
|
134
|
+
console.log(pc.dim(` Skipped ${secret.name}.`));
|
|
135
|
+
break;
|
|
130
136
|
}
|
|
131
137
|
const result = await validateThemeAccessToken(store.domain, value);
|
|
132
138
|
if (!result.ok) {
|
|
133
139
|
console.log(pc.red(` Token test failed: ${result.error}`));
|
|
134
|
-
console.log(pc.dim('
|
|
140
|
+
console.log(pc.dim(' Enter a valid token, or leave blank to skip.'));
|
|
135
141
|
continue;
|
|
136
142
|
}
|
|
137
143
|
console.log(pc.green(' Token validated against store.'));
|
|
@@ -142,7 +148,7 @@ export async function addStoreCommand() {
|
|
|
142
148
|
break;
|
|
143
149
|
} catch (err) {
|
|
144
150
|
console.log(pc.red(` Failed to set ${secret.name}: ${err.message}`));
|
|
145
|
-
console.log(pc.dim('
|
|
151
|
+
console.log(pc.dim(' Enter again to retry, or leave blank to skip.'));
|
|
146
152
|
}
|
|
147
153
|
}
|
|
148
154
|
continue;
|
|
@@ -6,6 +6,8 @@ import {
|
|
|
6
6
|
currentBranch,
|
|
7
7
|
ensureStagingBranch,
|
|
8
8
|
createStoreBranches,
|
|
9
|
+
hasOriginRemote,
|
|
10
|
+
pushBranchesToOrigin,
|
|
9
11
|
} from '../lib/git.js';
|
|
10
12
|
|
|
11
13
|
/**
|
|
@@ -36,15 +38,26 @@ export async function ensureBranchesCommand() {
|
|
|
36
38
|
console.log(pc.dim(` Mode: ${mode}-store (${aliases.length} store(s))\n`));
|
|
37
39
|
|
|
38
40
|
ensureStagingBranch();
|
|
41
|
+
const branchesToPush = ['staging'];
|
|
39
42
|
for (const alias of aliases) {
|
|
40
43
|
createStoreBranches(alias);
|
|
44
|
+
branchesToPush.push(`staging-${alias}`, `live-${alias}`);
|
|
41
45
|
}
|
|
42
46
|
|
|
43
47
|
console.log(pc.bold(pc.green('\n Branches ensured.\n')));
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
if (hasOriginRemote()) {
|
|
49
|
+
try {
|
|
50
|
+
pushBranchesToOrigin(branchesToPush);
|
|
51
|
+
console.log(pc.green(' Pushed ensured branches to origin.\n'));
|
|
52
|
+
} catch (err) {
|
|
53
|
+
console.log(pc.yellow(` Could not push branches automatically: ${err.message}`));
|
|
54
|
+
console.log(pc.dim(' Push them manually so CI can run:'));
|
|
55
|
+
console.log(pc.dim(' git push origin --all\n'));
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
console.log(pc.dim(' No origin remote found.'));
|
|
59
|
+
console.log(pc.dim(' Push them after adding a remote so CI can run:'));
|
|
60
|
+
console.log(pc.dim(' git remote add origin <url>'));
|
|
61
|
+
console.log(pc.dim(' git push origin --all\n'));
|
|
48
62
|
}
|
|
49
|
-
console.log(pc.dim(' Or push all at once: git push origin --all\n'));
|
|
50
63
|
}
|
package/src/commands/init.js
CHANGED
|
@@ -223,7 +223,13 @@ async function runInitFlow() {
|
|
|
223
223
|
|
|
224
224
|
if (!setter.check()) {
|
|
225
225
|
const installUrl = ciHost === 'github' ? 'https://cli.github.com/' : 'https://gitlab.com/gitlab-org/cli';
|
|
226
|
-
console.log(
|
|
226
|
+
console.log(
|
|
227
|
+
pc.yellow(
|
|
228
|
+
ciHost === 'github'
|
|
229
|
+
? ' GitHub CLI is not available (tried gh and npx gh) or not logged in.'
|
|
230
|
+
: ` ${setter.name} CLI is not installed or not logged in.`
|
|
231
|
+
)
|
|
232
|
+
);
|
|
227
233
|
console.log(pc.dim(` Install: ${installUrl} — then run ${ciHost === 'github' ? 'gh' : 'glab'} auth login`));
|
|
228
234
|
console.log(
|
|
229
235
|
pc.dim(
|
|
@@ -283,23 +289,24 @@ async function runInitFlow() {
|
|
|
283
289
|
|
|
284
290
|
if (isThemeToken) {
|
|
285
291
|
if (!storeUrl) {
|
|
286
|
-
console.log(pc.
|
|
287
|
-
continue;
|
|
292
|
+
console.log(pc.yellow(` Could not resolve store URL for ${secret.name}, skipping token validation.`));
|
|
288
293
|
}
|
|
289
|
-
// Theme tokens are
|
|
294
|
+
// Theme tokens are optional during setup; if provided, keep prompting until valid + set.
|
|
290
295
|
while (true) {
|
|
291
296
|
const value = await promptSecretValue(secret, i, totalToPrompt);
|
|
292
297
|
if (!value) {
|
|
293
|
-
console.log(pc.
|
|
294
|
-
|
|
298
|
+
console.log(pc.dim(` Skipped ${secret.name}.`));
|
|
299
|
+
break;
|
|
295
300
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
+
if (storeUrl) {
|
|
302
|
+
const result = await validateThemeAccessToken(storeUrl, value);
|
|
303
|
+
if (!result.ok) {
|
|
304
|
+
console.log(pc.red(` Token test failed: ${result.error}`));
|
|
305
|
+
console.log(pc.dim(' Enter a valid token, or leave blank to skip.'));
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
console.log(pc.green(' Token validated against store.'));
|
|
301
309
|
}
|
|
302
|
-
console.log(pc.green(' Token validated against store.'));
|
|
303
310
|
try {
|
|
304
311
|
await setter.set(secret.name, value);
|
|
305
312
|
console.log(pc.green(` Set ${secret.name}.`));
|
|
@@ -307,7 +314,7 @@ async function runInitFlow() {
|
|
|
307
314
|
break;
|
|
308
315
|
} catch (err) {
|
|
309
316
|
console.log(pc.red(` Failed to set ${secret.name}: ${err.message}`));
|
|
310
|
-
console.log(pc.dim('
|
|
317
|
+
console.log(pc.dim(' Enter again to retry, or leave blank to skip.'));
|
|
311
318
|
}
|
|
312
319
|
}
|
|
313
320
|
continue;
|
package/src/lib/git.js
CHANGED
|
@@ -107,6 +107,28 @@ export function ensureGitRepo(cwd = process.cwd()) {
|
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Check if origin remote exists.
|
|
112
|
+
*/
|
|
113
|
+
export function hasOriginRemote(cwd = process.cwd()) {
|
|
114
|
+
try {
|
|
115
|
+
exec('git remote get-url origin', cwd);
|
|
116
|
+
return true;
|
|
117
|
+
} catch {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Push branches to origin if remote exists.
|
|
124
|
+
*/
|
|
125
|
+
export function pushBranchesToOrigin(branches = [], cwd = process.cwd()) {
|
|
126
|
+
if (!branches.length) return true;
|
|
127
|
+
const unique = [...new Set(branches)];
|
|
128
|
+
exec(`git push origin ${unique.join(' ')}`, cwd);
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
|
|
110
132
|
/**
|
|
111
133
|
* Get the latest tag version (e.g. "1.2.3") from v* tags, or null if none.
|
|
112
134
|
* Sorts by version so v2.0.0 > v1.9.9.
|
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
import { spawn, spawnSync, execSync } from 'node:child_process';
|
|
2
2
|
import pc from 'picocolors';
|
|
3
3
|
|
|
4
|
+
function resolveGhInvocation(cwd = process.cwd()) {
|
|
5
|
+
const direct = spawnSync('gh', ['--version'], { cwd, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
6
|
+
if (direct.status === 0) return { command: 'gh', prefix: [] };
|
|
7
|
+
|
|
8
|
+
const viaNpx = spawnSync('npx', ['--yes', 'gh', '--version'], {
|
|
9
|
+
cwd,
|
|
10
|
+
encoding: 'utf-8',
|
|
11
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
12
|
+
});
|
|
13
|
+
if (viaNpx.status === 0) return { command: 'npx', prefix: ['--yes', 'gh'] };
|
|
14
|
+
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
|
|
4
18
|
/**
|
|
5
19
|
* Secret/variable definitions for CI (GitHub Actions or GitLab CI).
|
|
6
20
|
* condition: 'always' = required for core workflows; 'preview' | 'build' = only when that feature is enabled.
|
|
@@ -8,7 +22,7 @@ import pc from 'picocolors';
|
|
|
8
22
|
export const SECRET_DEFINITIONS = [
|
|
9
23
|
{
|
|
10
24
|
name: 'GEMINI_API_KEY',
|
|
11
|
-
required:
|
|
25
|
+
required: false,
|
|
12
26
|
condition: 'always',
|
|
13
27
|
description: 'Google Gemini API key for AI-generated changelogs on release',
|
|
14
28
|
whereToGet:
|
|
@@ -24,7 +38,7 @@ export const SECRET_DEFINITIONS = [
|
|
|
24
38
|
},
|
|
25
39
|
{
|
|
26
40
|
name: 'SHOPIFY_THEME_ACCESS_TOKEN',
|
|
27
|
-
required:
|
|
41
|
+
required: false,
|
|
28
42
|
condition: 'preview',
|
|
29
43
|
description: 'Theme access token so CI can push preview themes (password from Shopify Theme Access app)',
|
|
30
44
|
whereToGet:
|
|
@@ -60,8 +74,13 @@ export const SECRET_DEFINITIONS = [
|
|
|
60
74
|
*/
|
|
61
75
|
export function isGhAvailable() {
|
|
62
76
|
try {
|
|
63
|
-
|
|
64
|
-
return
|
|
77
|
+
const gh = resolveGhInvocation();
|
|
78
|
+
if (!gh) return false;
|
|
79
|
+
const result = spawnSync(gh.command, [...gh.prefix, 'auth', 'status'], {
|
|
80
|
+
encoding: 'utf-8',
|
|
81
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
82
|
+
});
|
|
83
|
+
return result.status === 0;
|
|
65
84
|
} catch {
|
|
66
85
|
return false;
|
|
67
86
|
}
|
|
@@ -99,10 +118,12 @@ export function getGitHubRepoSpec(cwd = process.cwd()) {
|
|
|
99
118
|
*/
|
|
100
119
|
export function listGitHubSecrets(cwd = process.cwd()) {
|
|
101
120
|
try {
|
|
121
|
+
const gh = resolveGhInvocation(cwd);
|
|
122
|
+
if (!gh) return [];
|
|
102
123
|
const repo = getGitHubRepoSpec(cwd);
|
|
103
|
-
const args = ['secret', 'list', '--json', 'name'];
|
|
124
|
+
const args = [...gh.prefix, 'secret', 'list', '--json', 'name'];
|
|
104
125
|
if (repo) args.push('-R', repo);
|
|
105
|
-
const result = spawnSync(
|
|
126
|
+
const result = spawnSync(gh.command, args, { cwd, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
106
127
|
const out = (result.stdout || '').trim();
|
|
107
128
|
const data = JSON.parse(out || '[]');
|
|
108
129
|
return Array.isArray(data) ? data.map((s) => s.name).filter(Boolean) : [];
|
|
@@ -131,10 +152,15 @@ export function listGitLabVariables(cwd = process.cwd()) {
|
|
|
131
152
|
*/
|
|
132
153
|
export function setSecret(name, value, cwd = process.cwd()) {
|
|
133
154
|
return new Promise((resolve, reject) => {
|
|
155
|
+
const gh = resolveGhInvocation(cwd);
|
|
156
|
+
if (!gh) {
|
|
157
|
+
reject(new Error('GitHub CLI is not available (tried gh and npx gh)'));
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
134
160
|
const repo = getGitHubRepoSpec(cwd);
|
|
135
|
-
const args = ['secret', 'set', name];
|
|
161
|
+
const args = [...gh.prefix, 'secret', 'set', name];
|
|
136
162
|
if (repo) args.push('-R', repo);
|
|
137
|
-
const child = spawn(
|
|
163
|
+
const child = spawn(gh.command, args, {
|
|
138
164
|
stdio: ['pipe', 'inherit', 'inherit'],
|
|
139
165
|
cwd,
|
|
140
166
|
});
|
|
@@ -295,7 +321,7 @@ export function getSecretsToPrompt({ enablePreviewWorkflows, enableBuildWorkflow
|
|
|
295
321
|
if (enablePreviewWorkflows) {
|
|
296
322
|
list.push({
|
|
297
323
|
name: `SHOPIFY_THEME_ACCESS_TOKEN_${suffix}`,
|
|
298
|
-
required:
|
|
324
|
+
required: false,
|
|
299
325
|
description: `Store ${store.alias}: Theme access token (password from Theme Access app)`,
|
|
300
326
|
whereToGet: 'Theme Access app in Shopify — the password it gives is the token for this store.',
|
|
301
327
|
});
|
|
@@ -338,7 +364,7 @@ export function getSecretsToPromptForNewStore(store) {
|
|
|
338
364
|
return [
|
|
339
365
|
{
|
|
340
366
|
name: `SHOPIFY_THEME_ACCESS_TOKEN_${suffix}`,
|
|
341
|
-
required:
|
|
367
|
+
required: false,
|
|
342
368
|
description: `Store ${store.alias}: Theme access token (password from Theme Access app)`,
|
|
343
369
|
whereToGet: 'Theme Access app in Shopify — the password it gives is the token for this store.',
|
|
344
370
|
},
|