@acidgreen-au/ag-cicd-cli 0.0.1 → 0.0.2

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 +32 -0
  2. package/dist/cli.mjs +315 -49
  3. package/package.json +10 -8
package/README.md ADDED
@@ -0,0 +1,32 @@
1
+ # ag-cicd-cli
2
+
3
+ CLI tools for Acidgreen CI/CD workflows. Provides commands for Shopify theme deployment, code quality checks, environment validation, and git automation.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm install -g ag-cicd-cli
9
+ ```
10
+
11
+ Or use directly with npx:
12
+
13
+ ```bash
14
+ pnpx ag-cicd-cli <command>
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ```bash
20
+ ag --help
21
+ ```
22
+
23
+ For help with a specific command:
24
+
25
+ ```bash
26
+ ag <command> --help
27
+ ```
28
+
29
+ ## Requirements
30
+
31
+ - Node.js >= 24.0.0
32
+ - Shopify CLI (for theme commands)
package/dist/cli.mjs CHANGED
@@ -1,13 +1,51 @@
1
1
  #!/usr/bin/env node
2
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
2
5
  import { program } from "commander";
3
- import { execSync, spawn } from "node:child_process";
4
- import { readFileSync, writeFileSync } from "node:fs";
6
+ import { execSync } from "node:child_process";
7
+ import prompts from "prompts";
8
+ import { parse, stringify } from "smol-toml";
9
+ import concurrently from "concurrently";
5
10
 
6
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
+ }
7
35
  function getVersionFromPackage(packagePath) {
8
36
  const content = readFileSync(packagePath, "utf-8");
9
37
  return JSON.parse(content).version;
10
38
  }
39
+ function syncTagsFromRemote(remote) {
40
+ execSync(`git fetch "${remote}" --prune --prune-tags --force`, {
41
+ encoding: "utf-8",
42
+ stdio: [
43
+ "pipe",
44
+ "pipe",
45
+ "pipe"
46
+ ]
47
+ });
48
+ }
11
49
  function tagExists(tag) {
12
50
  try {
13
51
  execSync(`git rev-parse --verify "refs/tags/${tag}"`, {
@@ -47,6 +85,7 @@ function checkTag(options) {
47
85
  const packagePath = options.packagePath ?? "package.json";
48
86
  const prefix = options.prefix ?? "v";
49
87
  const remote = options.remote ?? "origin";
88
+ if (options.remote) syncTagsFromRemote(remote);
50
89
  const version = getVersionFromPackage(packagePath);
51
90
  const tag = `${prefix}${version}`;
52
91
  if (tagExists(tag)) return {
@@ -245,6 +284,24 @@ const ALL_CHECKS = [
245
284
  "theme-check",
246
285
  "tsc"
247
286
  ];
287
+ const helpText$8 = `
288
+ Checks:
289
+ biome Runs Biome linter for TypeScript/JavaScript
290
+ prettier Checks file formatting with Prettier
291
+ theme-check Runs Shopify Theme Check for Liquid files
292
+ tsc Runs TypeScript compiler for type errors
293
+
294
+ Output:
295
+ Generates a JSON file compatible with GitLab Code Quality reports.
296
+ Each issue includes severity, file path, and line number.
297
+
298
+ Examples:
299
+ $ ag codequality
300
+ $ ag codequality --checks biome tsc
301
+ $ 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);
304
+ }
248
305
  function codequality(options) {
249
306
  const { output, path, checks = ALL_CHECKS } = options;
250
307
  const allIssues = [];
@@ -280,6 +337,28 @@ function codequality(options) {
280
337
 
281
338
  //#endregion
282
339
  //#region src/commands/commit-msg.ts
340
+ const helpText$7 = `
341
+ Details:
342
+ Validates that commit messages follow the required format:
343
+ <PREFIX>-<NUMBER> <message>
344
+
345
+ When the branch contains a ticket ID, the commit message must reference
346
+ the same ticket. Branches without ticket IDs (main, development, etc.) skip
347
+ this check.
348
+
349
+ Designed to be called from .husky/commit-msg hook:
350
+ ag commit-msg --file "$1"
351
+
352
+ Exit Codes:
353
+ 0 Commit message is valid
354
+ 1 Commit message is invalid (blocks commit)
355
+
356
+ Examples:
357
+ $ ag commit-msg --file .git/COMMIT_EDITMSG
358
+ $ 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);
361
+ }
283
362
  function validateCommitMsg(options) {
284
363
  const { file, prefix } = options;
285
364
  const ticketRegex = /* @__PURE__ */ new RegExp(`^${prefix}-[0-9]+`);
@@ -337,6 +416,82 @@ function commitMsg(options) {
337
416
  process.exit(0);
338
417
  }
339
418
 
419
+ //#endregion
420
+ //#region src/commands/config-shopify.ts
421
+ const helpText$6 = `
422
+ Details:
423
+ Creates or updates a shopify.theme.toml configuration file.
424
+
425
+ Without a name argument: Creates the default environment configuration.
426
+ With a name argument: Adds a new named environment, copying path and
427
+ ignore settings from the default environment.
428
+
429
+ Prompts for:
430
+ - Store name (required): Your myshopify.com store URL
431
+ - Theme ID (optional): Target theme ID for deployment
432
+
433
+ Examples:
434
+ $ ag config:shopify # Create/update default environment
435
+ $ ag config:shopify production # Add production environment
436
+ $ ag config:shopify staging --force # Overwrite staging environment`;
437
+ const DEFAULT_IGNORES = [
438
+ "templates/*.json",
439
+ "locales/*.json",
440
+ "config/settings_data.json",
441
+ "src/*",
442
+ "public/*"
443
+ ];
444
+ 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);
447
+ }
448
+ function loadConfig(configPath) {
449
+ if (!existsSync(configPath)) return { environments: {} };
450
+ return parse(readFileSync(configPath, "utf-8"));
451
+ }
452
+ function saveConfig(configPath, config) {
453
+ writeFileSync(configPath, stringify(config));
454
+ }
455
+ async function configShopify(name, options) {
456
+ const configPath = "shopify.theme.toml";
457
+ const envName = name ?? "default";
458
+ const config = loadConfig(configPath);
459
+ if (!config.environments) config.environments = {};
460
+ if (config.environments[envName] && !options.force) {
461
+ console.error(`Error: Environment '${envName}' already exists. Use --force to overwrite.`);
462
+ process.exit(1);
463
+ }
464
+ if (envName !== "default" && !config.environments.default) {
465
+ console.error("Error: Default environment must be created first. Run 'ag config:shopify' without arguments.");
466
+ process.exit(1);
467
+ }
468
+ const response = await prompts([{
469
+ type: "text",
470
+ name: "store",
471
+ message: "Store name (e.g., my-store.myshopify.com)",
472
+ validate: (value) => value.length > 0 || "Store name is required"
473
+ }, {
474
+ type: "text",
475
+ name: "themeId",
476
+ message: "Theme ID (optional, press Enter to skip)"
477
+ }], { onCancel: () => {
478
+ console.log("\nCancelled.");
479
+ process.exit(0);
480
+ } });
481
+ const defaultEnv = config.environments.default;
482
+ const path = defaultEnv?.path ?? DEFAULT_PATH;
483
+ const ignore = defaultEnv?.ignore ?? DEFAULT_IGNORES;
484
+ const envConfig = {
485
+ store: response.store,
486
+ path,
487
+ ignore
488
+ };
489
+ if (response.themeId) envConfig.theme = response.themeId;
490
+ config.environments[envName] = envConfig;
491
+ saveConfig(configPath, config);
492
+ console.log(`\n${existsSync(configPath) ? "Updated" : "Created"} ${configPath} with '${envName}' environment`);
493
+ }
494
+
340
495
  //#endregion
341
496
  //#region src/utils/shopify.ts
342
497
  function listThemes() {
@@ -375,6 +530,26 @@ const defaultIgnores = [
375
530
 
376
531
  //#endregion
377
532
  //#region src/commands/deploy.ts
533
+ const helpText$5 = `
534
+ Details:
535
+ Pushes theme files to a specific Shopify theme ID. Use this for deploying
536
+ to staging or production themes where you know the target theme ID.
537
+
538
+ Writes deployment info to deploy.env:
539
+ - PREVIEW_URL: Theme preview URL
540
+ - EDITOR_URL: Theme editor URL
541
+ - THEME_ID: Deployed theme ID
542
+
543
+ Environment:
544
+ SHOPIFY_CLI_THEME_TOKEN Shopify CLI authentication token
545
+ SHOPIFY_FLAG_STORE Shopify store URL
546
+
547
+ Examples:
548
+ $ ag deploy --theme-id 123456789
549
+ $ 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);
552
+ }
378
553
  function deploy(options) {
379
554
  const { themeId, path } = options;
380
555
  const themeIdNum = parseInt(themeId, 10);
@@ -387,6 +562,31 @@ function deploy(options) {
387
562
 
388
563
  //#endregion
389
564
  //#region src/commands/deploy-review.ts
565
+ const helpText$4 = `
566
+ Details:
567
+ Creates or updates an unpublished theme for reviewing branch changes.
568
+ Theme is named "AG Preview: <branch>" for easy identification.
569
+
570
+ On first deploy: Clones the live theme as a starting point.
571
+ On subsequent deploys: Updates the existing preview theme.
572
+
573
+ Writes deployment info to deploy.env:
574
+ - PREVIEW_URL: Theme preview URL
575
+ - EDITOR_URL: Theme editor URL
576
+ - THEME_ID: Preview theme ID
577
+ - EXISTING_THEME_ID: ID if theme already existed
578
+
579
+ Environment:
580
+ SHOPIFY_CLI_THEME_TOKEN Shopify CLI authentication token
581
+ SHOPIFY_FLAG_STORE Shopify store URL
582
+ CI_COMMIT_REF_NAME Git branch name (GitLab CI)
583
+
584
+ Examples:
585
+ $ ag deploy:review --branch feature/new-header
586
+ $ 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);
589
+ }
390
590
  function deployReview(options) {
391
591
  const { branch, path } = options;
392
592
  const themeName = `AG Preview: ${branch}`;
@@ -413,48 +613,71 @@ function deployReview(options) {
413
613
 
414
614
  //#endregion
415
615
  //#region src/commands/dev.ts
416
- function dev(_options, command) {
417
- const themeArgs = command.args;
418
- console.log("Starting development servers...");
419
- const vite = spawn("pnpm", ["exec", "vite"], {
420
- stdio: "inherit",
421
- shell: true
422
- });
423
- const shopify = spawn("pnpm", [
424
- "exec",
425
- "shopify",
426
- "theme",
427
- "dev",
428
- ...themeArgs
429
- ], {
430
- stdio: "inherit",
431
- shell: true
432
- });
433
- const cleanup = () => {
434
- vite.kill();
435
- shopify.kill();
436
- process.exit(0);
437
- };
438
- process.on("SIGINT", cleanup);
439
- process.on("SIGTERM", cleanup);
440
- vite.on("close", (code) => {
441
- if (code !== 0) {
442
- console.error(`Vite exited with code ${code}`);
443
- shopify.kill();
444
- process.exit(code ?? 1);
445
- }
446
- });
447
- shopify.on("close", (code) => {
448
- if (code !== 0) {
449
- console.error(`Shopify theme dev exited with code ${code}`);
450
- vite.kill();
451
- process.exit(code ?? 1);
452
- }
616
+ const helpText$3 = `
617
+ Details:
618
+ Starts both Vite (for frontend asset compilation) and Shopify theme dev
619
+ servers in parallel. All additional arguments are passed to shopify theme dev.
620
+
621
+ Handles graceful shutdown of both processes on Ctrl+C or if either exits.
622
+
623
+ Arguments:
624
+ Any arguments after -- are passed directly to 'shopify theme dev'.
625
+ Common options include --store, --theme, --live-reload, etc.
626
+ Run \`shopify theme dev -h\` for full list of options.
627
+
628
+ Examples:
629
+ $ ag dev
630
+ $ ag dev -- --store my-store
631
+ $ 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);
634
+ }
635
+ async function dev(_options, command) {
636
+ const { result } = concurrently([{
637
+ command: "pnpm exec vite",
638
+ name: "vite",
639
+ prefixColor: "cyan"
640
+ }, {
641
+ command: `shopify theme dev ${command.args.join(" ")}`,
642
+ name: "shopify",
643
+ prefixColor: "magenta"
644
+ }], {
645
+ killOthers: ["failure", "success"],
646
+ restartTries: 0
453
647
  });
648
+ try {
649
+ await result;
650
+ } catch {
651
+ process.exit(1);
652
+ }
454
653
  }
455
654
 
456
655
  //#endregion
457
656
  //#region src/commands/release.ts
657
+ const helpText$2 = `
658
+ Details:
659
+ Creates a new theme for production releases. Clones the current live theme
660
+ to preserve any theme editor customizations, then pushes your code changes.
661
+
662
+ Theme is named "AG Release: <name>" for easy identification.
663
+
664
+ Writes deployment info to deploy.env:
665
+ - PREVIEW_URL: Release theme preview URL
666
+ - EDITOR_URL: Release theme editor URL
667
+ - THEME_ID: New release theme ID
668
+ - PUBLISHED_THEME_ID: Current live theme ID (for rollback)
669
+
670
+ Environment:
671
+ SHOPIFY_CLI_THEME_TOKEN Shopify CLI authentication token
672
+ SHOPIFY_FLAG_STORE Shopify store URL
673
+ CI_COMMIT_TAG Git tag (GitLab CI)
674
+
675
+ Examples:
676
+ $ ag release --name v1.2.3
677
+ $ 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);
680
+ }
458
681
  function release(options) {
459
682
  const { name, path } = options;
460
683
  const themeName = `AG Release: ${name}`;
@@ -471,6 +694,24 @@ function release(options) {
471
694
 
472
695
  //#endregion
473
696
  //#region src/commands/stop-review.ts
697
+ const helpText$1 = `
698
+ Details:
699
+ Finds and deletes the unpublished theme named "AG Preview: <branch>".
700
+ Typically called when a merge request is closed or merged.
701
+
702
+ Safe to run even if the theme doesn't exist.
703
+
704
+ Environment:
705
+ SHOPIFY_CLI_THEME_TOKEN Shopify CLI authentication token
706
+ SHOPIFY_FLAG_STORE Shopify store URL
707
+ CI_COMMIT_REF_NAME Git branch name (GitLab CI)
708
+
709
+ Examples:
710
+ $ ag stop:review --branch feature/new-header
711
+ $ 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);
714
+ }
474
715
  function stopReview(options) {
475
716
  const { branch } = options;
476
717
  const themeName = `AG Preview: ${branch}`;
@@ -485,6 +726,27 @@ function stopReview(options) {
485
726
 
486
727
  //#endregion
487
728
  //#region src/commands/validate-env.ts
729
+ const helpText = `
730
+ Contexts:
731
+ all Check all variables for all contexts (default)
732
+ deploy Check variables for theme deployment
733
+ review Check variables for review app deployment
734
+ release Check variables for release deployment
735
+ codequality Check variables for code quality checks
736
+
737
+ Details:
738
+ Validates that required environment variables are present before
739
+ running CI/CD jobs. Exits with code 1 if any required vars are missing.
740
+
741
+ Token values are redacted in output for security.
742
+
743
+ Examples:
744
+ $ ag validate-env
745
+ $ ag validate-env --context deploy
746
+ $ 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);
749
+ }
488
750
  const commonVars = [{
489
751
  name: "SHOPIFY_CLI_THEME_TOKEN",
490
752
  required: true,
@@ -568,16 +830,20 @@ function validateEnv(options) {
568
830
 
569
831
  //#endregion
570
832
  //#region src/cli.ts
571
- program.name("agci").description("Acidgreen CI/CD CLI tools").version("1.0.0");
572
- program.command("codequality").description("Run code quality checks and combine output").option("-o, --output <file>", "Output file", "codequality.json").option("-p, --path <path>", "Theme path", "theme").option("-c, --checks <checks...>", "Checks to run (biome, prettier, theme-check, tsc)").action(codequality);
573
- program.command("deploy:review").description("Deploy a review app for the current branch").requiredOption("-b, --branch <branch>", "Branch name (CI_COMMIT_REF_NAME)").option("-p, --path <path>", "Theme path", "theme").action(deployReview);
574
- program.command("stop:review").description("Stop and delete a review app").requiredOption("-b, --branch <branch>", "Branch name (CI_COMMIT_REF_NAME)").action(stopReview);
575
- program.command("deploy").description("Deploy to a specific theme").requiredOption("-t, --theme-id <id>", "Theme ID (SHOPIFY_THEME_ID)").option("-p, --path <path>", "Theme path", "theme").action(deploy);
576
- program.command("release").description("Create a release by cloning live theme and pushing").requiredOption("-n, --name <name>", "Release name/tag").option("-p, --path <path>", "Theme path", "theme").action(release);
577
- program.command("validate-env").description("Validate required environment variables are set").option("-c, --context <context>", "Context to validate (deploy, review, release, codequality, all)").option("-v, --vars <vars...>", "Specific environment variables to check").action(validateEnv);
578
- program.command("dev").description("Start Vite and Shopify theme dev servers concurrently").allowUnknownOption().allowExcessArguments().action(dev);
579
- program.command("commit-msg").description("Validate commit message format (for git hooks)").requiredOption("-f, --file <file>", "Path to commit message file").option("-p, --prefix <prefix>", "JIRA project prefix", "AIR").action(commitMsg);
580
- program.command("check-tag").description("Check if git tag exists for package.json version").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 does not exist").option("--push", "Push the tag to remote (requires --create)").option("-r, --remote <remote>", "Remote to push to", "origin").action(checkTagCommand);
833
+ const __dirname = dirname(fileURLToPath(import.meta.url));
834
+ const pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
835
+ const binName = Object.keys(pkg.bin)[0];
836
+ program.name(binName).description("Acidgreen CI/CD CLI tools for Shopify theme development").version(pkg.version);
837
+ register$8(program);
838
+ register$5(program);
839
+ register$4(program);
840
+ register$1(program);
841
+ register$2(program);
842
+ register(program);
843
+ register$3(program);
844
+ register$7(program);
845
+ register$9(program);
846
+ register$6(program);
581
847
  program.parse();
582
848
 
583
849
  //#endregion
package/package.json CHANGED
@@ -1,14 +1,10 @@
1
1
  {
2
2
  "name": "@acidgreen-au/ag-cicd-cli",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "Acidgreen CI/CD CLI tools",
5
- "repository": {
6
- "type": "git",
7
- "url": "https://gitlab.com/acidgreen/internal/agcicd.git"
8
- },
9
5
  "type": "module",
10
6
  "bin": {
11
- "agci": "./dist/cli.mjs"
7
+ "ag": "./dist/cli.mjs"
12
8
  },
13
9
  "main": "./dist/cli.mjs",
14
10
  "types": "./dist/cli.d.mts",
@@ -17,14 +13,20 @@
17
13
  "./package.json": "./package.json"
18
14
  },
19
15
  "files": [
20
- "dist"
16
+ "dist",
17
+ "package.json",
18
+ "README.md"
21
19
  ],
22
20
  "dependencies": {
23
- "commander": "13.1.0"
21
+ "commander": "13.1.0",
22
+ "concurrently": "^9.2.1",
23
+ "prompts": "^2.4.2",
24
+ "smol-toml": "^1.6.0"
24
25
  },
25
26
  "devDependencies": {
26
27
  "@biomejs/biome": "^2.3.10",
27
28
  "@types/node": "24.10.4",
29
+ "@types/prompts": "^2.4.9",
28
30
  "husky": "^9.1.7",
29
31
  "tsdown": "^0.18.2",
30
32
  "typescript": "^5.9.3",