@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.
Files changed (3) hide show
  1. package/README.md +40 -3
  2. package/dist/cli.mjs +259 -68
  3. 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/commands/check-tag.ts
12
- const helpText$9 = `
13
- Details:
14
- Reads the version from package.json and checks if a corresponding git tag
15
- exists. Optionally creates and pushes the tag if it doesn't exist.
16
-
17
- When --remote is specified, local tags are synced with remote before checking.
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$8 = `
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$8(program$1) {
303
- program$1.command("codequality").description("Run code quality checks and output GitLab-compatible JSON").addHelpText("after", helpText$8).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);
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$7 = `
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$7(program$1) {
360
- program$1.command("commit-msg").description("Validate commit message format for git hooks").addHelpText("after", helpText$7).requiredOption("-f, --file <file>", "Path to commit message file").option("-p, --prefix <prefix>", "JIRA project prefix", "AIR").action(commitMsg);
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$6 = `
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$6(program$1) {
446
- program$1.command("config:shopify").description("Create or update shopify.theme.toml configuration").addHelpText("after", helpText$6).argument("[name]", "Environment name (default: 'default')").option("-f, --force", "Overwrite existing environment").action(configShopify);
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$5 = `
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$5(program$1) {
551
- program$1.command("deploy").description("Deploy theme to an existing Shopify theme by ID").addHelpText("after", helpText$5).requiredOption("-t, --theme-id <id>", "Target Shopify theme ID").option("-p, --path <path>", "Theme directory path", "theme").action(deploy);
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$4 = `
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$4(program$1) {
588
- program$1.command("deploy:review").description("Deploy a review app theme for the current branch").addHelpText("after", helpText$4).requiredOption("-b, --branch <branch>", "Git branch name").option("-p, --path <path>", "Theme directory path", "theme").action(deployReview);
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$3 = `
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$3(program$1) {
633
- program$1.command("dev").description("Start Vite and Shopify theme dev servers concurrently").addHelpText("after", helpText$3).allowUnknownOption().allowExcessArguments().action(dev);
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$2 = `
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$2(program$1) {
679
- program$1.command("release").description("Create a release theme by cloning live and pushing changes").addHelpText("after", helpText$2).requiredOption("-n, --name <name>", "Release name (usually git tag)").option("-p, --path <path>", "Theme directory path", "theme").action(release);
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$1 = `
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$1(program$1) {
713
- program$1.command("stop:review").description("Delete the review app theme for a branch").addHelpText("after", helpText$1).requiredOption("-b, --branch <branch>", "Git branch name").action(stopReview);
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$8(program);
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$9(program);
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.2",
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
  }