@acidgreen-au/ag-cicd-cli 0.0.2 → 0.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 +40 -3
- package/dist/cli.mjs +259 -68
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
# ag-cicd-cli
|
|
1
|
+
# @acidgreen-au/ag-cicd-cli
|
|
2
2
|
|
|
3
3
|
CLI tools for Acidgreen CI/CD workflows. Provides commands for Shopify theme deployment, code quality checks, environment validation, and git automation.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
pnpm install -g ag-cicd-cli
|
|
8
|
+
pnpm install -g @acidgreen-au/ag-cicd-cli
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
Or use directly with npx:
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
pnpx ag-cicd-cli <command>
|
|
14
|
+
pnpx @acidgreen-au/ag-cicd-cli <command>
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
## Usage
|
|
@@ -30,3 +30,40 @@ ag <command> --help
|
|
|
30
30
|
|
|
31
31
|
- Node.js >= 24.0.0
|
|
32
32
|
- Shopify CLI (for theme commands)
|
|
33
|
+
|
|
34
|
+
## Contributing
|
|
35
|
+
|
|
36
|
+
This project uses [changesets](https://github.com/changesets/changesets) for version management and changelog generation.
|
|
37
|
+
|
|
38
|
+
### Adding a changeset
|
|
39
|
+
|
|
40
|
+
When making changes that should be released, add a changeset:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pnpm changeset
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Select the change type:
|
|
47
|
+
- **patch** - Bug fixes, documentation updates
|
|
48
|
+
- **minor** - New features (backwards compatible)
|
|
49
|
+
- **major** - Breaking changes
|
|
50
|
+
|
|
51
|
+
Write a summary describing your change. This will appear in the CHANGELOG.
|
|
52
|
+
|
|
53
|
+
### Release workflow
|
|
54
|
+
|
|
55
|
+
1. Make changes and add changeset(s) with your PR
|
|
56
|
+
2. Merge PR to main
|
|
57
|
+
3. CI runs `pnpm version` to consume changesets and bump version
|
|
58
|
+
4. CI commits version bump and creates git tag
|
|
59
|
+
5. CI publishes to npm on tag
|
|
60
|
+
|
|
61
|
+
### Manual release (maintainers)
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# Consume changesets, update version and CHANGELOG
|
|
65
|
+
pnpm version
|
|
66
|
+
|
|
67
|
+
# Build and publish to npm
|
|
68
|
+
pnpm release
|
|
69
|
+
```
|
package/dist/cli.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { existsSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { program } from "commander";
|
|
@@ -8,34 +8,17 @@ import prompts from "prompts";
|
|
|
8
8
|
import { parse, stringify } from "smol-toml";
|
|
9
9
|
import concurrently from "concurrently";
|
|
10
10
|
|
|
11
|
-
//#region src/
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
Used in CI/CD to automatically tag releases when package.json version
|
|
20
|
-
is bumped.
|
|
21
|
-
|
|
22
|
-
Exit Codes:
|
|
23
|
-
0 Tag exists, or was successfully created
|
|
24
|
-
1 Tag does not exist (when --create is not specified)
|
|
25
|
-
|
|
26
|
-
Examples:
|
|
27
|
-
$ ag check-tag
|
|
28
|
-
$ ag check-tag --create
|
|
29
|
-
$ ag check-tag --create --push
|
|
30
|
-
$ ag check-tag --create --push --remote origin
|
|
31
|
-
$ ag check-tag --prefix release-`;
|
|
32
|
-
function register$9(program$1) {
|
|
33
|
-
program$1.command("check-tag").description("Check if git tag exists for package.json version").addHelpText("after", helpText$9).option("-p, --package-path <path>", "Path to package.json", "package.json").option("--prefix <prefix>", "Tag prefix", "v").option("-c, --create", "Create the tag if it doesn't exist").option("--push", "Push the tag to remote (requires --create)").option("-r, --remote <remote>", "Remote to push to", "origin").action(checkTagCommand);
|
|
34
|
-
}
|
|
35
|
-
function getVersionFromPackage(packagePath) {
|
|
36
|
-
const content = readFileSync(packagePath, "utf-8");
|
|
37
|
-
return JSON.parse(content).version;
|
|
11
|
+
//#region src/utils/git.ts
|
|
12
|
+
/**
|
|
13
|
+
* Configure git user for commits
|
|
14
|
+
*/
|
|
15
|
+
function configureGit(name, email) {
|
|
16
|
+
execSync(`git config user.name "${name}"`, { stdio: "pipe" });
|
|
17
|
+
execSync(`git config user.email "${email}"`, { stdio: "pipe" });
|
|
38
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Sync local tags with remote (prune deleted, fetch new)
|
|
21
|
+
*/
|
|
39
22
|
function syncTagsFromRemote(remote) {
|
|
40
23
|
execSync(`git fetch "${remote}" --prune --prune-tags --force`, {
|
|
41
24
|
encoding: "utf-8",
|
|
@@ -46,6 +29,9 @@ function syncTagsFromRemote(remote) {
|
|
|
46
29
|
]
|
|
47
30
|
});
|
|
48
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Check if a git tag exists
|
|
34
|
+
*/
|
|
49
35
|
function tagExists(tag) {
|
|
50
36
|
try {
|
|
51
37
|
execSync(`git rev-parse --verify "refs/tags/${tag}"`, {
|
|
@@ -61,6 +47,9 @@ function tagExists(tag) {
|
|
|
61
47
|
return false;
|
|
62
48
|
}
|
|
63
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Create an annotated git tag
|
|
52
|
+
*/
|
|
64
53
|
function createTag(tag, message) {
|
|
65
54
|
execSync(`git tag -a "${tag}" -m "${message}"`, {
|
|
66
55
|
encoding: "utf-8",
|
|
@@ -71,6 +60,9 @@ function createTag(tag, message) {
|
|
|
71
60
|
]
|
|
72
61
|
});
|
|
73
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Push a specific tag to remote
|
|
65
|
+
*/
|
|
74
66
|
function pushTag(tag, remote) {
|
|
75
67
|
execSync(`git push "${remote}" "${tag}"`, {
|
|
76
68
|
encoding: "utf-8",
|
|
@@ -81,15 +73,103 @@ function pushTag(tag, remote) {
|
|
|
81
73
|
]
|
|
82
74
|
});
|
|
83
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Check if there are staged or unstaged changes
|
|
78
|
+
*/
|
|
79
|
+
function hasChanges() {
|
|
80
|
+
try {
|
|
81
|
+
const status = execSync("git status --porcelain", {
|
|
82
|
+
encoding: "utf-8",
|
|
83
|
+
stdio: [
|
|
84
|
+
"pipe",
|
|
85
|
+
"pipe",
|
|
86
|
+
"pipe"
|
|
87
|
+
]
|
|
88
|
+
});
|
|
89
|
+
const hasUncommittedChanges = status.trim().length > 0;
|
|
90
|
+
if (hasUncommittedChanges) {
|
|
91
|
+
console.log("Changes detected:");
|
|
92
|
+
console.log(status);
|
|
93
|
+
}
|
|
94
|
+
return hasUncommittedChanges;
|
|
95
|
+
} catch {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Stage all changes and commit with message
|
|
101
|
+
* @param message - Commit message
|
|
102
|
+
* @param skipHooks - Skip pre-commit and commit-msg hooks (for CI releases)
|
|
103
|
+
* Returns false if no changes to commit
|
|
104
|
+
*/
|
|
105
|
+
function commitChanges(message, skipHooks = false) {
|
|
106
|
+
if (!hasChanges()) return false;
|
|
107
|
+
execSync("git add -A", { stdio: "inherit" });
|
|
108
|
+
const noVerify = skipHooks ? " --no-verify" : "";
|
|
109
|
+
try {
|
|
110
|
+
execSync(`git commit -m "${message}"${noVerify}`, { stdio: "inherit" });
|
|
111
|
+
return true;
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.error("Git commit failed:", error instanceof Error ? error.message : String(error));
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Push to remote with optional branch refspec (for detached HEAD in CI)
|
|
119
|
+
* Pushes commit and tags separately so [skip ci] doesn't affect tag pipeline
|
|
120
|
+
*/
|
|
121
|
+
function pushToRemote(remote, branch) {
|
|
122
|
+
execSync(`git push "${remote}" ${branch ? `HEAD:refs/heads/${branch}` : "HEAD"}`, { stdio: "inherit" });
|
|
123
|
+
execSync(`git push "${remote}" --tags`, { stdio: "inherit" });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
//#endregion
|
|
127
|
+
//#region src/commands/check-tag.ts
|
|
128
|
+
const helpText$10 = `
|
|
129
|
+
Details:
|
|
130
|
+
Reads the version from package.json and checks if a corresponding git tag
|
|
131
|
+
exists. Optionally creates and pushes the tag if it doesn't exist.
|
|
132
|
+
|
|
133
|
+
When --remote is specified, local tags are synced with remote before checking.
|
|
134
|
+
|
|
135
|
+
Used in CI/CD to automatically tag releases when package.json version
|
|
136
|
+
is bumped.
|
|
137
|
+
|
|
138
|
+
Exit Codes:
|
|
139
|
+
0 Tag exists, or was successfully created
|
|
140
|
+
1 Tag does not exist (when --create is not specified)
|
|
141
|
+
|
|
142
|
+
Examples:
|
|
143
|
+
$ ag check-tag
|
|
144
|
+
$ ag check-tag --create
|
|
145
|
+
$ ag check-tag --create --push
|
|
146
|
+
$ ag check-tag --create --push --remote origin
|
|
147
|
+
$ ag check-tag --prefix release-
|
|
148
|
+
|
|
149
|
+
GitLab CI/CD Authentication:
|
|
150
|
+
To push tags from GitLab CI/CD, you need to authenticate with the remote.
|
|
151
|
+
|
|
152
|
+
Create a token with write_repository scope in Settings > Access Tokens,
|
|
153
|
+
then add it as a masked CI/CD variable (e.g., GITLAB_TOKEN).
|
|
154
|
+
|
|
155
|
+
$ ag check-tag --create --push \\
|
|
156
|
+
--remote "https://oauth2:\${GITLAB_TOKEN}@\${CI_SERVER_HOST}/\${CI_PROJECT_PATH}.git"`;
|
|
157
|
+
function register$10(program$1) {
|
|
158
|
+
program$1.command("check-tag").description("Check if git tag exists for package.json version").addHelpText("after", helpText$10).option("-p, --package-path <path>", "Path to package.json", "package.json").option("--prefix <prefix>", "Tag prefix", "v").option("-c, --create", "Create the tag if it doesn't exist").option("--push", "Push the tag to remote (requires --create)").option("-r, --remote <remote>", "Remote to push to", "origin").action(checkTagCommand);
|
|
159
|
+
}
|
|
160
|
+
function getVersionFromPackage(packagePath) {
|
|
161
|
+
const content = readFileSync(packagePath, "utf-8");
|
|
162
|
+
return JSON.parse(content).version;
|
|
163
|
+
}
|
|
84
164
|
function checkTag(options) {
|
|
85
165
|
const packagePath = options.packagePath ?? "package.json";
|
|
86
166
|
const prefix = options.prefix ?? "v";
|
|
87
167
|
const remote = options.remote ?? "origin";
|
|
88
168
|
if (options.remote) syncTagsFromRemote(remote);
|
|
89
|
-
const version = getVersionFromPackage(packagePath);
|
|
90
|
-
const tag = `${prefix}${version}`;
|
|
169
|
+
const version$1 = getVersionFromPackage(packagePath);
|
|
170
|
+
const tag = `${prefix}${version$1}`;
|
|
91
171
|
if (tagExists(tag)) return {
|
|
92
|
-
version,
|
|
172
|
+
version: version$1,
|
|
93
173
|
tag,
|
|
94
174
|
exists: true,
|
|
95
175
|
created: false,
|
|
@@ -97,16 +177,16 @@ function checkTag(options) {
|
|
|
97
177
|
message: `Tag ${tag} already exists`
|
|
98
178
|
};
|
|
99
179
|
if (!options.create) return {
|
|
100
|
-
version,
|
|
180
|
+
version: version$1,
|
|
101
181
|
tag,
|
|
102
182
|
exists: false,
|
|
103
183
|
created: false,
|
|
104
184
|
pushed: false,
|
|
105
185
|
message: `Tag ${tag} does not exist`
|
|
106
186
|
};
|
|
107
|
-
createTag(tag, `Release ${version}`);
|
|
187
|
+
createTag(tag, `Release ${version$1}`);
|
|
108
188
|
if (!options.push) return {
|
|
109
|
-
version,
|
|
189
|
+
version: version$1,
|
|
110
190
|
tag,
|
|
111
191
|
exists: false,
|
|
112
192
|
created: true,
|
|
@@ -115,7 +195,7 @@ function checkTag(options) {
|
|
|
115
195
|
};
|
|
116
196
|
pushTag(tag, remote);
|
|
117
197
|
return {
|
|
118
|
-
version,
|
|
198
|
+
version: version$1,
|
|
119
199
|
tag,
|
|
120
200
|
exists: false,
|
|
121
201
|
created: true,
|
|
@@ -284,7 +364,7 @@ const ALL_CHECKS = [
|
|
|
284
364
|
"theme-check",
|
|
285
365
|
"tsc"
|
|
286
366
|
];
|
|
287
|
-
const helpText$
|
|
367
|
+
const helpText$9 = `
|
|
288
368
|
Checks:
|
|
289
369
|
biome Runs Biome linter for TypeScript/JavaScript
|
|
290
370
|
prettier Checks file formatting with Prettier
|
|
@@ -299,8 +379,8 @@ Examples:
|
|
|
299
379
|
$ ag codequality
|
|
300
380
|
$ ag codequality --checks biome tsc
|
|
301
381
|
$ ag codequality --output gl-code-quality.json --path ./theme`;
|
|
302
|
-
function register$
|
|
303
|
-
program$1.command("codequality").description("Run code quality checks and output GitLab-compatible JSON").addHelpText("after", helpText$
|
|
382
|
+
function register$9(program$1) {
|
|
383
|
+
program$1.command("codequality").description("Run code quality checks and output GitLab-compatible JSON").addHelpText("after", helpText$9).option("-o, --output <file>", "Output JSON file path", "codequality.json").option("-p, --path <path>", "Theme directory path", "theme").option("-c, --checks <checks...>", "Specific checks to run (biome, prettier, theme-check, tsc)").action(codequality);
|
|
304
384
|
}
|
|
305
385
|
function codequality(options) {
|
|
306
386
|
const { output, path, checks = ALL_CHECKS } = options;
|
|
@@ -337,7 +417,7 @@ function codequality(options) {
|
|
|
337
417
|
|
|
338
418
|
//#endregion
|
|
339
419
|
//#region src/commands/commit-msg.ts
|
|
340
|
-
const helpText$
|
|
420
|
+
const helpText$8 = `
|
|
341
421
|
Details:
|
|
342
422
|
Validates that commit messages follow the required format:
|
|
343
423
|
<PREFIX>-<NUMBER> <message>
|
|
@@ -356,8 +436,8 @@ Exit Codes:
|
|
|
356
436
|
Examples:
|
|
357
437
|
$ ag commit-msg --file .git/COMMIT_EDITMSG
|
|
358
438
|
$ ag commit-msg -f .git/COMMIT_EDITMSG --prefix PROJ`;
|
|
359
|
-
function register$
|
|
360
|
-
program$1.command("commit-msg").description("Validate commit message format for git hooks").addHelpText("after", helpText$
|
|
439
|
+
function register$8(program$1) {
|
|
440
|
+
program$1.command("commit-msg").description("Validate commit message format for git hooks").addHelpText("after", helpText$8).requiredOption("-f, --file <file>", "Path to commit message file").option("-p, --prefix <prefix>", "JIRA project prefix", "AIR").action(commitMsg);
|
|
361
441
|
}
|
|
362
442
|
function validateCommitMsg(options) {
|
|
363
443
|
const { file, prefix } = options;
|
|
@@ -418,7 +498,7 @@ function commitMsg(options) {
|
|
|
418
498
|
|
|
419
499
|
//#endregion
|
|
420
500
|
//#region src/commands/config-shopify.ts
|
|
421
|
-
const helpText$
|
|
501
|
+
const helpText$7 = `
|
|
422
502
|
Details:
|
|
423
503
|
Creates or updates a shopify.theme.toml configuration file.
|
|
424
504
|
|
|
@@ -442,8 +522,8 @@ const DEFAULT_IGNORES = [
|
|
|
442
522
|
"public/*"
|
|
443
523
|
];
|
|
444
524
|
const DEFAULT_PATH = "theme";
|
|
445
|
-
function register$
|
|
446
|
-
program$1.command("config:shopify").description("Create or update shopify.theme.toml configuration").addHelpText("after", helpText$
|
|
525
|
+
function register$7(program$1) {
|
|
526
|
+
program$1.command("config:shopify").description("Create or update shopify.theme.toml configuration").addHelpText("after", helpText$7).argument("[name]", "Environment name (default: 'default')").option("-f, --force", "Overwrite existing environment").action(configShopify);
|
|
447
527
|
}
|
|
448
528
|
function loadConfig(configPath) {
|
|
449
529
|
if (!existsSync(configPath)) return { environments: {} };
|
|
@@ -530,7 +610,7 @@ const defaultIgnores = [
|
|
|
530
610
|
|
|
531
611
|
//#endregion
|
|
532
612
|
//#region src/commands/deploy.ts
|
|
533
|
-
const helpText$
|
|
613
|
+
const helpText$6 = `
|
|
534
614
|
Details:
|
|
535
615
|
Pushes theme files to a specific Shopify theme ID. Use this for deploying
|
|
536
616
|
to staging or production themes where you know the target theme ID.
|
|
@@ -547,8 +627,8 @@ Environment:
|
|
|
547
627
|
Examples:
|
|
548
628
|
$ ag deploy --theme-id 123456789
|
|
549
629
|
$ ag deploy -t 123456789 --path ./theme`;
|
|
550
|
-
function register$
|
|
551
|
-
program$1.command("deploy").description("Deploy theme to an existing Shopify theme by ID").addHelpText("after", helpText$
|
|
630
|
+
function register$6(program$1) {
|
|
631
|
+
program$1.command("deploy").description("Deploy theme to an existing Shopify theme by ID").addHelpText("after", helpText$6).requiredOption("-t, --theme-id <id>", "Target Shopify theme ID").option("-p, --path <path>", "Theme directory path", "theme").action(deploy);
|
|
552
632
|
}
|
|
553
633
|
function deploy(options) {
|
|
554
634
|
const { themeId, path } = options;
|
|
@@ -562,7 +642,7 @@ function deploy(options) {
|
|
|
562
642
|
|
|
563
643
|
//#endregion
|
|
564
644
|
//#region src/commands/deploy-review.ts
|
|
565
|
-
const helpText$
|
|
645
|
+
const helpText$5 = `
|
|
566
646
|
Details:
|
|
567
647
|
Creates or updates an unpublished theme for reviewing branch changes.
|
|
568
648
|
Theme is named "AG Preview: <branch>" for easy identification.
|
|
@@ -584,8 +664,8 @@ Environment:
|
|
|
584
664
|
Examples:
|
|
585
665
|
$ ag deploy:review --branch feature/new-header
|
|
586
666
|
$ ag deploy:review -b $CI_COMMIT_REF_NAME`;
|
|
587
|
-
function register$
|
|
588
|
-
program$1.command("deploy:review").description("Deploy a review app theme for the current branch").addHelpText("after", helpText$
|
|
667
|
+
function register$5(program$1) {
|
|
668
|
+
program$1.command("deploy:review").description("Deploy a review app theme for the current branch").addHelpText("after", helpText$5).requiredOption("-b, --branch <branch>", "Git branch name").option("-p, --path <path>", "Theme directory path", "theme").action(deployReview);
|
|
589
669
|
}
|
|
590
670
|
function deployReview(options) {
|
|
591
671
|
const { branch, path } = options;
|
|
@@ -613,7 +693,7 @@ function deployReview(options) {
|
|
|
613
693
|
|
|
614
694
|
//#endregion
|
|
615
695
|
//#region src/commands/dev.ts
|
|
616
|
-
const helpText$
|
|
696
|
+
const helpText$4 = `
|
|
617
697
|
Details:
|
|
618
698
|
Starts both Vite (for frontend asset compilation) and Shopify theme dev
|
|
619
699
|
servers in parallel. All additional arguments are passed to shopify theme dev.
|
|
@@ -629,8 +709,8 @@ Examples:
|
|
|
629
709
|
$ ag dev
|
|
630
710
|
$ ag dev -- --store my-store
|
|
631
711
|
$ ag dev -- --theme 123456789`;
|
|
632
|
-
function register$
|
|
633
|
-
program$1.command("dev").description("Start Vite and Shopify theme dev servers concurrently").addHelpText("after", helpText$
|
|
712
|
+
function register$4(program$1) {
|
|
713
|
+
program$1.command("dev").description("Start Vite and Shopify theme dev servers concurrently").addHelpText("after", helpText$4).allowUnknownOption().allowExcessArguments().action(dev);
|
|
634
714
|
}
|
|
635
715
|
async function dev(_options, command) {
|
|
636
716
|
const { result } = concurrently([{
|
|
@@ -654,7 +734,7 @@ async function dev(_options, command) {
|
|
|
654
734
|
|
|
655
735
|
//#endregion
|
|
656
736
|
//#region src/commands/release.ts
|
|
657
|
-
const helpText$
|
|
737
|
+
const helpText$3 = `
|
|
658
738
|
Details:
|
|
659
739
|
Creates a new theme for production releases. Clones the current live theme
|
|
660
740
|
to preserve any theme editor customizations, then pushes your code changes.
|
|
@@ -675,8 +755,8 @@ Environment:
|
|
|
675
755
|
Examples:
|
|
676
756
|
$ ag release --name v1.2.3
|
|
677
757
|
$ ag release -n $CI_COMMIT_TAG`;
|
|
678
|
-
function register$
|
|
679
|
-
program$1.command("release").description("Create a release theme by cloning live and pushing changes").addHelpText("after", helpText$
|
|
758
|
+
function register$3(program$1) {
|
|
759
|
+
program$1.command("release").description("Create a release theme by cloning live and pushing changes").addHelpText("after", helpText$3).requiredOption("-n, --name <name>", "Release name (usually git tag)").option("-p, --path <path>", "Theme directory path", "theme").action(release);
|
|
680
760
|
}
|
|
681
761
|
function release(options) {
|
|
682
762
|
const { name, path } = options;
|
|
@@ -694,7 +774,7 @@ function release(options) {
|
|
|
694
774
|
|
|
695
775
|
//#endregion
|
|
696
776
|
//#region src/commands/stop-review.ts
|
|
697
|
-
const helpText$
|
|
777
|
+
const helpText$2 = `
|
|
698
778
|
Details:
|
|
699
779
|
Finds and deletes the unpublished theme named "AG Preview: <branch>".
|
|
700
780
|
Typically called when a merge request is closed or merged.
|
|
@@ -709,8 +789,8 @@ Environment:
|
|
|
709
789
|
Examples:
|
|
710
790
|
$ ag stop:review --branch feature/new-header
|
|
711
791
|
$ ag stop:review -b $CI_COMMIT_REF_NAME`;
|
|
712
|
-
function register$
|
|
713
|
-
program$1.command("stop:review").description("Delete the review app theme for a branch").addHelpText("after", helpText$
|
|
792
|
+
function register$2(program$1) {
|
|
793
|
+
program$1.command("stop:review").description("Delete the review app theme for a branch").addHelpText("after", helpText$2).requiredOption("-b, --branch <branch>", "Git branch name").action(stopReview);
|
|
714
794
|
}
|
|
715
795
|
function stopReview(options) {
|
|
716
796
|
const { branch } = options;
|
|
@@ -726,7 +806,7 @@ function stopReview(options) {
|
|
|
726
806
|
|
|
727
807
|
//#endregion
|
|
728
808
|
//#region src/commands/validate-env.ts
|
|
729
|
-
const helpText = `
|
|
809
|
+
const helpText$1 = `
|
|
730
810
|
Contexts:
|
|
731
811
|
all Check all variables for all contexts (default)
|
|
732
812
|
deploy Check variables for theme deployment
|
|
@@ -744,8 +824,8 @@ Examples:
|
|
|
744
824
|
$ ag validate-env
|
|
745
825
|
$ ag validate-env --context deploy
|
|
746
826
|
$ ag validate-env --vars SHOPIFY_FLAG_STORE CUSTOM_VAR`;
|
|
747
|
-
function register(program$1) {
|
|
748
|
-
program$1.command("validate-env").description("Validate required environment variables are set").addHelpText("after", helpText).option("-c, --context <context>", "Validation context (deploy, review, release, codequality, all)").option("-v, --vars <vars...>", "Specific variables to check").action(validateEnv);
|
|
827
|
+
function register$1(program$1) {
|
|
828
|
+
program$1.command("validate-env").description("Validate required environment variables are set").addHelpText("after", helpText$1).option("-c, --context <context>", "Validation context (deploy, review, release, codequality, all)").option("-v, --vars <vars...>", "Specific variables to check").action(validateEnv);
|
|
749
829
|
}
|
|
750
830
|
const commonVars = [{
|
|
751
831
|
name: "SHOPIFY_CLI_THEME_TOKEN",
|
|
@@ -828,22 +908,133 @@ function validateEnv(options) {
|
|
|
828
908
|
console.log("All required environment variables are set.");
|
|
829
909
|
}
|
|
830
910
|
|
|
911
|
+
//#endregion
|
|
912
|
+
//#region src/commands/version.ts
|
|
913
|
+
const helpText = `
|
|
914
|
+
Details:
|
|
915
|
+
Automates the release versioning workflow using changesets.
|
|
916
|
+
|
|
917
|
+
1. Checks for pending changesets in .changeset/
|
|
918
|
+
2. If changesets exist:
|
|
919
|
+
- Runs "changeset version" to bump version and update CHANGELOG
|
|
920
|
+
- Commits the changes
|
|
921
|
+
- Creates a git tag
|
|
922
|
+
- Optionally pushes to remote
|
|
923
|
+
3. If no changesets:
|
|
924
|
+
- Falls back to check-tag behavior (creates tag if version is untagged)
|
|
925
|
+
|
|
926
|
+
Used in CI/CD to automate releases when changesets are merged.
|
|
927
|
+
|
|
928
|
+
Environment:
|
|
929
|
+
GITLAB_TOKEN GitLab API token for pushing (when using HTTPS remote)
|
|
930
|
+
|
|
931
|
+
Examples:
|
|
932
|
+
$ ag version
|
|
933
|
+
$ ag version --push
|
|
934
|
+
$ ag version --push --remote origin
|
|
935
|
+
$ ag version --push --remote "https://oauth2:\${GITLAB_TOKEN}@gitlab.com/org/repo.git"
|
|
936
|
+
$ ag version --push --branch main # Required for detached HEAD (CI environments)`;
|
|
937
|
+
function register(program$1) {
|
|
938
|
+
program$1.command("version").description("Version packages using changesets and create git tag").addHelpText("after", helpText).option("--push", "Push commits and tags to remote").option("-r, --remote <remote>", "Git remote to push to", "origin").option("-b, --branch <branch>", "Branch to push to (required for detached HEAD in CI)").option("--git-user-name <name>", "Git user name for commits", "CI").option("--git-user-email <email>", "Git user email for commits", "ci@localhost").action(versionCommand);
|
|
939
|
+
}
|
|
940
|
+
function hasChangesets() {
|
|
941
|
+
try {
|
|
942
|
+
return readdirSync(".changeset").filter((f) => f.endsWith(".md") && f !== "README.md").length > 0;
|
|
943
|
+
} catch {
|
|
944
|
+
return false;
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
function getPackageVersion() {
|
|
948
|
+
const content = readFileSync("package.json", "utf-8");
|
|
949
|
+
return JSON.parse(content).version;
|
|
950
|
+
}
|
|
951
|
+
function runChangesetVersion() {
|
|
952
|
+
execSync("pnpm changeset version", { stdio: "inherit" });
|
|
953
|
+
}
|
|
954
|
+
function version(options) {
|
|
955
|
+
const remote = options.remote ?? "origin";
|
|
956
|
+
if (options.push && options.remote) {
|
|
957
|
+
console.log("Syncing tags from remote...");
|
|
958
|
+
syncTagsFromRemote(remote);
|
|
959
|
+
}
|
|
960
|
+
if (!hasChangesets()) {
|
|
961
|
+
console.log("No changesets found, checking if tag exists...");
|
|
962
|
+
const checkTagResult = checkTag({
|
|
963
|
+
create: true,
|
|
964
|
+
push: options.push,
|
|
965
|
+
remote: options.push ? remote : void 0
|
|
966
|
+
});
|
|
967
|
+
return {
|
|
968
|
+
hadChangesets: false,
|
|
969
|
+
version: checkTagResult.version,
|
|
970
|
+
tag: checkTagResult.tag,
|
|
971
|
+
committed: false,
|
|
972
|
+
tagged: checkTagResult.created,
|
|
973
|
+
pushed: checkTagResult.pushed,
|
|
974
|
+
message: checkTagResult.message
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
console.log("Found changesets, versioning packages...");
|
|
978
|
+
configureGit(options.gitUserName ?? "CI", options.gitUserEmail ?? "ci@localhost");
|
|
979
|
+
runChangesetVersion();
|
|
980
|
+
const newVersion = getPackageVersion();
|
|
981
|
+
const tag = `v${newVersion}`;
|
|
982
|
+
console.log(`Version bumped to ${newVersion}`);
|
|
983
|
+
const committed = commitChanges(`chore: release ${tag}`, true);
|
|
984
|
+
if (committed) console.log(`Committed version changes`);
|
|
985
|
+
else console.log("No changes to commit");
|
|
986
|
+
const tagAlreadyExists = tagExists(tag);
|
|
987
|
+
if (!committed && !tagAlreadyExists) throw new Error(`No changes to commit but tag ${tag} doesn't exist. This may indicate changesets were consumed in a previous failed run. Please check if package.json version matches the expected release.`);
|
|
988
|
+
let tagCreated = false;
|
|
989
|
+
if (!tagAlreadyExists) {
|
|
990
|
+
createTag(tag, `Release ${newVersion}`);
|
|
991
|
+
tagCreated = true;
|
|
992
|
+
console.log(`Created tag ${tag}`);
|
|
993
|
+
} else console.log(`Tag ${tag} already exists, skipping`);
|
|
994
|
+
let pushed = false;
|
|
995
|
+
if (options.push) {
|
|
996
|
+
console.log(`Pushing to ${remote}...`);
|
|
997
|
+
pushToRemote(remote, options.branch);
|
|
998
|
+
pushed = true;
|
|
999
|
+
console.log("Pushed successfully");
|
|
1000
|
+
}
|
|
1001
|
+
return {
|
|
1002
|
+
hadChangesets: true,
|
|
1003
|
+
version: newVersion,
|
|
1004
|
+
tag,
|
|
1005
|
+
committed,
|
|
1006
|
+
tagged: tagCreated || tagAlreadyExists,
|
|
1007
|
+
pushed,
|
|
1008
|
+
message: `Released ${tag}`
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
function versionCommand(options) {
|
|
1012
|
+
try {
|
|
1013
|
+
const result = version(options);
|
|
1014
|
+
console.log(result.message);
|
|
1015
|
+
} catch (error) {
|
|
1016
|
+
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
1017
|
+
process.exit(1);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
|
|
831
1021
|
//#endregion
|
|
832
1022
|
//#region src/cli.ts
|
|
833
1023
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
834
1024
|
const pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
|
|
835
1025
|
const binName = Object.keys(pkg.bin)[0];
|
|
836
1026
|
program.name(binName).description("Acidgreen CI/CD CLI tools for Shopify theme development").version(pkg.version);
|
|
837
|
-
register$
|
|
1027
|
+
register$9(program);
|
|
1028
|
+
register$6(program);
|
|
838
1029
|
register$5(program);
|
|
839
|
-
register$4(program);
|
|
840
|
-
register$1(program);
|
|
841
1030
|
register$2(program);
|
|
842
|
-
register(program);
|
|
843
1031
|
register$3(program);
|
|
1032
|
+
register$1(program);
|
|
1033
|
+
register$4(program);
|
|
1034
|
+
register$8(program);
|
|
1035
|
+
register$10(program);
|
|
844
1036
|
register$7(program);
|
|
845
|
-
register
|
|
846
|
-
register$6(program);
|
|
1037
|
+
register(program);
|
|
847
1038
|
program.parse();
|
|
848
1039
|
|
|
849
1040
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@acidgreen-au/ag-cicd-cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "Acidgreen CI/CD CLI tools",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@biomejs/biome": "^2.3.10",
|
|
28
|
+
"@changesets/cli": "^2.29.8",
|
|
28
29
|
"@types/node": "24.10.4",
|
|
29
30
|
"@types/prompts": "^2.4.9",
|
|
30
31
|
"husky": "^9.1.7",
|
|
@@ -48,6 +49,7 @@
|
|
|
48
49
|
"biome:check": "biome check",
|
|
49
50
|
"biome:ci": "biome ci",
|
|
50
51
|
"typecheck": "tsc",
|
|
51
|
-
"precommit": "pnpm run biome:ci && pnpm run typecheck"
|
|
52
|
+
"precommit": "pnpm run biome:ci && pnpm run typecheck",
|
|
53
|
+
"changeset": "changeset"
|
|
52
54
|
}
|
|
53
55
|
}
|