zinkee 0.1.40 → 0.1.41

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.
@@ -1,95 +1,64 @@
1
1
  # Publish to npm
2
2
 
3
- Simple release checklist for publishing a patch update of the `zinkee` CLI to the npm package registry.
3
+ The `Publish to npm` GitHub Actions workflow publishes new CLI versions when
4
+ they reach `main`. It uses npm trusted publishing with GitHub OIDC, so the
5
+ repository does not need an `NPM_TOKEN`.
4
6
 
5
- ## Prerequisites
7
+ ## One-time npm configuration
6
8
 
7
- - You are on the release branch with a clean working tree.
8
- - You have npm publish permissions for the `zinkee` package.
9
- - You are logged in to npm:
9
+ Configure the `zinkee` package on npmjs.com with this trusted publisher:
10
10
 
11
- ```bash
12
- npm whoami
13
- ```
14
-
15
- If needed:
16
-
17
- ```bash
18
- npm login
19
- ```
20
-
21
- ## 1. Verify the package
22
-
23
- Run the local checks before changing the package version:
24
-
25
- ```bash
26
- npm install
27
- npm run build
28
- npm run typecheck
29
- npm test
30
- ```
31
-
32
- Optionally inspect what would be published:
33
-
34
- ```bash
35
- npm pack --dry-run
36
- ```
37
-
38
- ## 2. Bump the patch version
39
-
40
- For a normal patch release, use:
11
+ - Provider: GitHub Actions
12
+ - Organization: `Zinkee-Engineering`
13
+ - Repository: `cli`
14
+ - Workflow filename: `npm-publish.yml`
15
+ - Environment: empty
16
+ - Allowed action: `npm publish`
41
17
 
42
- ```bash
43
- npm version patch
44
- ```
18
+ Enter only the workflow filename, not `.github/workflows/npm-publish.yml`.
45
19
 
46
- This updates `package.json` and `package-lock.json`, creates a git commit, and creates a version tag like `v0.1.8`.
20
+ ## Release flow
47
21
 
48
- Use the other npm version levels only when needed:
22
+ Every pull request that ships a user-visible change must update the package
23
+ version. For a patch release, run:
49
24
 
50
25
  ```bash
51
- npm version minor
52
- npm version major
26
+ npm version patch --no-git-tag-version
53
27
  ```
54
28
 
55
- ## 3. Publish to npm
56
-
57
- Build again after the version bump:
29
+ This updates `package.json` and `package-lock.json` without creating a commit or
30
+ tag. Include both files in the feature pull request and run:
58
31
 
59
32
  ```bash
33
+ npm ci
34
+ npm test
35
+ npm run typecheck
60
36
  npm run build
37
+ npm pack --dry-run
61
38
  ```
62
39
 
63
- Publish the package:
40
+ After the pull request is merged, `.github/workflows/npm-publish.yml`:
64
41
 
65
- ```bash
66
- npm publish
67
- ```
68
-
69
- If npm requires explicit public access, use:
42
+ 1. Checks whether the package version already exists on npm.
43
+ 2. Skips the publish successfully when the version already exists.
44
+ 3. Runs the full quality gate for a new version.
45
+ 4. Publishes the package with `npm publish --access public`.
70
46
 
71
- ```bash
72
- npm publish --access public
73
- ```
47
+ ## Verification
74
48
 
75
- ## 4. Push the release commit and tag
49
+ Check the workflow run and the published version:
76
50
 
77
51
  ```bash
78
- git push
79
- git push --tags
52
+ gh run list --workflow npm-publish.yml
53
+ npm view zinkee version
80
54
  ```
81
55
 
82
- ## 5. Confirm the published version
56
+ Optionally install the published version and exercise the CLI:
83
57
 
84
58
  ```bash
85
- npm view zinkee version
86
59
  npm install -g zinkee@latest
87
60
  zinkee --help
88
61
  ```
89
62
 
90
- ## Notes
91
-
92
- - Do not manually edit the version unless there is a specific reason; prefer `npm version patch`.
93
- - If the publish fails after `npm version patch`, fix the issue and run `npm publish` again. Do not create another version bump unless the failed version was already published.
94
- - Keep stdout-sensitive CLI changes verified with `--json` before publishing.
95
-
63
+ Do not create another version bump after a failed publish unless npm confirms
64
+ that the failed version was already published.
@@ -0,0 +1,37 @@
1
+ # CLI pull request checks
2
+
3
+ ## Context
4
+
5
+ The CLI repository has no GitHub Actions workflow that validates pull
6
+ requests. Its documented local quality gate consists of tests, TypeScript type
7
+ checking, and a production build.
8
+
9
+ ## Decision
10
+
11
+ Add `.github/workflows/pr-checks.yml` and run it for pull requests targeting
12
+ `main`. The workflow will grant only `contents: read` permission and use one
13
+ Ubuntu job so dependencies are installed once.
14
+
15
+ The job will:
16
+
17
+ 1. Check out the repository.
18
+ 2. Set up Node.js 24 with npm dependency caching.
19
+ 3. Install the locked dependency graph with `npm ci`.
20
+ 4. Run `npm test`.
21
+ 5. Run `npm run typecheck`.
22
+ 6. Run `npm run build`.
23
+
24
+ Each quality check will be a separate step so GitHub identifies the failing
25
+ gate directly. A failure in any step will fail the job and prevent subsequent
26
+ steps from running.
27
+
28
+ ## Scope
29
+
30
+ The workflow will not publish packages, deploy artifacts, access secrets,
31
+ change versions, run on pushes, or test a Node.js version matrix.
32
+
33
+ ## Verification
34
+
35
+ Validate the workflow YAML and inspect its trigger, permissions, Node version,
36
+ dependency installation, and commands. Run the same three quality checks
37
+ locally from the CLI repository before completion.
package/package.json CHANGED
@@ -1,7 +1,11 @@
1
1
  {
2
2
  "name": "zinkee",
3
- "version": "0.1.40",
3
+ "version": "0.1.41",
4
4
  "description": "CLI for Zinkee API v2",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/Zinkee-Engineering/cli.git"
8
+ },
5
9
  "type": "module",
6
10
  "bin": {
7
11
  "zinkee": "dist/index.js"
@@ -611,6 +611,146 @@ describe("automations commands", () => {
611
611
  });
612
612
  });
613
613
 
614
+ it("accepts the field:comparator literal form for no-value comparators", async () => {
615
+ const configPath = createApiConfig();
616
+ const output = createStdoutBuffer();
617
+ const fetchMock = createFetchMock();
618
+ fetchMock.mockResolvedValueOnce(jsonResponse({ ok: true }));
619
+ const program = buildAutomationsProgram({ configPath, stdout: output.stdout });
620
+
621
+ await program.parseAsync([
622
+ "node",
623
+ "zinkee",
624
+ "--json",
625
+ "automations",
626
+ "trigger",
627
+ "set",
628
+ "automation-1",
629
+ "--trigger-type",
630
+ "record_changed",
631
+ "--trigger-schema",
632
+ "schema-1",
633
+ "--trigger-field",
634
+ "field-1",
635
+ "--condition",
636
+ "field-1:chg",
637
+ ]);
638
+
639
+ expect(JSON.parse(String(fetchMock.mock.calls[0]?.[1]?.body))).toEqual({
640
+ type: "record_changed",
641
+ config: {
642
+ schemaId: "schema-1",
643
+ fieldIds: ["field-1"],
644
+ conditions: [
645
+ {
646
+ field: "field-1",
647
+ comparator: "chg",
648
+ values: [],
649
+ },
650
+ ],
651
+ },
652
+ });
653
+ });
654
+
655
+ it("accepts empty condition-json values for no-value comparators", async () => {
656
+ const configPath = createApiConfig();
657
+ const output = createStdoutBuffer();
658
+ const fetchMock = createFetchMock();
659
+ fetchMock.mockResolvedValueOnce(jsonResponse({ ok: true }));
660
+ const program = buildAutomationsProgram({ configPath, stdout: output.stdout });
661
+
662
+ await program.parseAsync([
663
+ "node",
664
+ "zinkee",
665
+ "--json",
666
+ "automations",
667
+ "trigger",
668
+ "set",
669
+ "automation-1",
670
+ "--trigger-type",
671
+ "record_changed",
672
+ "--trigger-schema",
673
+ "schema-1",
674
+ "--trigger-field",
675
+ "field-1",
676
+ "--condition-json",
677
+ '{"field":"field-1","comparator":"ie","values":[]}',
678
+ ]);
679
+
680
+ expect(JSON.parse(String(fetchMock.mock.calls[0]?.[1]?.body))).toEqual({
681
+ type: "record_changed",
682
+ config: {
683
+ schemaId: "schema-1",
684
+ fieldIds: ["field-1"],
685
+ conditions: [
686
+ {
687
+ field: "field-1",
688
+ comparator: "ie",
689
+ values: [],
690
+ },
691
+ ],
692
+ },
693
+ });
694
+ });
695
+
696
+ it("rejects the field:comparator literal form for comparators that need a value", async () => {
697
+ const configPath = createApiConfig();
698
+ const output = createStdoutBuffer();
699
+ const fetchMock = createFetchMock();
700
+ const program = buildAutomationsProgram({ configPath, stdout: output.stdout });
701
+
702
+ await expect(
703
+ program.parseAsync([
704
+ "node",
705
+ "zinkee",
706
+ "--json",
707
+ "automations",
708
+ "trigger",
709
+ "set",
710
+ "automation-1",
711
+ "--trigger-type",
712
+ "record_changed",
713
+ "--trigger-schema",
714
+ "schema-1",
715
+ "--trigger-field",
716
+ "field-1",
717
+ "--condition",
718
+ "field-1:eq",
719
+ ]),
720
+ ).rejects.toThrowError(/field:comparator for comparators without value/);
721
+
722
+ expect(fetchMock).not.toHaveBeenCalled();
723
+ });
724
+
725
+ it("rejects empty condition-json values for comparators that need a value", async () => {
726
+ const configPath = createApiConfig();
727
+ const output = createStdoutBuffer();
728
+ const fetchMock = createFetchMock();
729
+ const program = buildAutomationsProgram({ configPath, stdout: output.stdout });
730
+
731
+ await expect(
732
+ program.parseAsync([
733
+ "node",
734
+ "zinkee",
735
+ "--json",
736
+ "automations",
737
+ "trigger",
738
+ "set",
739
+ "automation-1",
740
+ "--trigger-type",
741
+ "record_changed",
742
+ "--trigger-schema",
743
+ "schema-1",
744
+ "--trigger-field",
745
+ "field-1",
746
+ "--condition-json",
747
+ '{"field":"field-1","comparator":"eq","values":[]}',
748
+ ]),
749
+ ).rejects.toThrowError(/non-empty "values" array/);
750
+
751
+ expect(fetchMock).not.toHaveBeenCalled();
752
+ });
753
+
614
754
  it("rejects source-based trigger conditions", async () => {
615
755
  const configPath = createApiConfig();
616
756
  const output = createStdoutBuffer();
@@ -450,15 +450,47 @@ function readJsonObjectOption(
450
450
  ) as JsonObject;
451
451
  }
452
452
 
453
+ const NO_VALUE_COMPARATORS = new Set([
454
+ "chg",
455
+ "changes",
456
+ "ie",
457
+ "is_empty",
458
+ "ine",
459
+ "is_not_empty",
460
+ ]);
461
+
462
+ function isNoValueComparator(comparator: string): boolean {
463
+ return NO_VALUE_COMPARATORS.has(comparator.toLowerCase());
464
+ }
465
+
466
+ function conditionFormatError(input: string): CliError {
467
+ return new CliError(
468
+ "invalid_cli_usage",
469
+ `Expected condition in the form field:comparator:value, or field:comparator for comparators without value (chg, ie, ine), received "${input}".`,
470
+ );
471
+ }
472
+
453
473
  function parseCondition(input: string): ConditionSpec {
454
474
  const firstSeparator = input.indexOf(":");
455
475
  const secondSeparator = input.indexOf(":", firstSeparator + 1);
456
476
 
457
- if (firstSeparator <= 0 || secondSeparator <= firstSeparator + 1) {
458
- throw new CliError(
459
- "invalid_cli_usage",
460
- `Expected condition in the form field:comparator:value, received "${input}".`,
461
- );
477
+ if (firstSeparator <= 0) {
478
+ throw conditionFormatError(input);
479
+ }
480
+
481
+ if (secondSeparator === -1) {
482
+ const field = input.slice(0, firstSeparator).trim();
483
+ const comparator = input.slice(firstSeparator + 1).trim();
484
+
485
+ if (!field || !comparator || !isNoValueComparator(comparator)) {
486
+ throw conditionFormatError(input);
487
+ }
488
+
489
+ return { field, comparator, values: [] };
490
+ }
491
+
492
+ if (secondSeparator <= firstSeparator + 1) {
493
+ throw conditionFormatError(input);
462
494
  }
463
495
 
464
496
  const field = input.slice(0, firstSeparator).trim();
@@ -466,10 +498,7 @@ function parseCondition(input: string): ConditionSpec {
466
498
  const value = input.slice(secondSeparator + 1).trim();
467
499
 
468
500
  if (!field || !comparator || !value) {
469
- throw new CliError(
470
- "invalid_cli_usage",
471
- `Expected condition in the form field:comparator:value, received "${input}".`,
472
- );
501
+ throw conditionFormatError(input);
473
502
  }
474
503
 
475
504
  return {
@@ -683,10 +712,11 @@ function normalizeAutomationConditions(
683
712
  const comparator = typeof condition.comparator === "string" ? condition.comparator.trim() : "";
684
713
  const values = condition.values;
685
714
 
686
- if (!field || !comparator || !Array.isArray(values) || values.length === 0) {
715
+ const allowsEmptyValues = isNoValueComparator(comparator);
716
+ if (!field || !comparator || !Array.isArray(values) || (values.length === 0 && !allowsEmptyValues)) {
687
717
  throw new CliError(
688
718
  "invalid_cli_usage",
689
- `Expected ${label} ${index + 1} to contain "field", "comparator", and a non-empty "values" array.`,
719
+ `Expected ${label} ${index + 1} to contain "field", "comparator", and a non-empty "values" array (comparators without value, e.g. chg/ie/ine, accept an empty array).`,
690
720
  );
691
721
  }
692
722
 
@@ -1329,7 +1359,7 @@ function addTriggerPayloadOptions(command: Command): Command {
1329
1359
  )
1330
1360
  .option(
1331
1361
  "--condition <field:comparator:value>",
1332
- "trigger condition",
1362
+ "trigger condition (use field:comparator for comparators without value: chg, ie, ine)",
1333
1363
  collectValues,
1334
1364
  [],
1335
1365
  )
@@ -1368,7 +1398,12 @@ function addActionPayloadOptions(command: Command): Command {
1368
1398
  .option("--target-schema <uuid>", "target schema id")
1369
1399
  .option("--map <field=value>", "field mapping", collectValues, [])
1370
1400
  .option("--map-json <field=json>", "JSON field mapping", collectValues, [])
1371
- .option("--condition <field:comparator:value>", "action condition", collectValues, [])
1401
+ .option(
1402
+ "--condition <field:comparator:value>",
1403
+ "action condition (use field:comparator for comparators without value: ie, ine)",
1404
+ collectValues,
1405
+ [],
1406
+ )
1372
1407
  .option("--condition-json <json>", "action condition as JSON", collectValues, [])
1373
1408
  .option("--use-trigger-record", "use the triggering record")
1374
1409
  .option("--method <method>", "HTTP method")
@@ -1,70 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(npm test *)",
5
- "Bash(npm run *)",
6
- "Bash(pnpm typecheck *)",
7
- "Bash(pnpm test *)",
8
- "Bash(npx tsc *)",
9
- "Read(//home/guillermo/dev/z2-backend/api/src/main/java/com/zinkee/api/domain/model/v2/log/**)",
10
- "Read(//home/guillermo/dev/z2-backend/**)",
11
- "Bash(grep *)",
12
- "Bash(npx vitest *)",
13
- "Bash(npx tsx *)",
14
- "Bash(node dist/index.js --help)",
15
- "Bash(gh repo *)",
16
- "WebFetch(domain:github.com)",
17
- "Bash(git config *)",
18
- "Read(//home/guillermo/.config/**)",
19
- "Read(//home/guillermo/**)",
20
- "Bash(curl *)",
21
- "Bash(node dist/index.js --show-completion zsh)",
22
- "Bash(node *)",
23
- "Bash(_ZINKEE_COMPLETE=zsh_complete COMP_WORDS=\"zinkee\" COMP_CWORD=1 node *)",
24
- "Bash(_ZINKEE_COMPLETE=zsh_complete COMP_WORDS=\"zinkee schemas\" COMP_CWORD=2 node *)",
25
- "Bash(_ZINKEE_COMPLETE=zsh_complete COMP_WORDS=\"zinkee schemas fields\" COMP_CWORD=3 node dist/index.js)",
26
- "Bash(_ZINKEE_COMPLETE=zsh_complete COMP_WORDS=\"zinkee schemas fields cr\" COMP_CWORD=3 node dist/index.js)",
27
- "Bash(_ZINKEE_COMPLETE=zsh_complete COMP_WORDS=\"zinkee schemas list --j\" COMP_CWORD=3 node dist/index.js)",
28
- "Bash(rm -rf /tmp/zinkee-install)",
29
- "Bash(mkdir -p /tmp/zinkee-install)",
30
- "Bash(HOME=/tmp/zinkee-install SHELL=/bin/zsh node dist/index.js --install-completion)",
31
- "Read(//tmp/**)",
32
- "Read(//tmp/zinkee-install/**)",
33
- "Bash(rm -rf /tmp/zinkee-install-bash)",
34
- "Bash(mkdir -p /tmp/zinkee-install-bash)",
35
- "Bash(HOME=/tmp/zinkee-install-bash SHELL=/bin/bash node dist/index.js --install-completion)",
36
- "Bash(rm -rf /tmp/zinkee-install-fish)",
37
- "Bash(mkdir -p /tmp/zinkee-install-fish)",
38
- "Bash(_ZINKEE_COMPLETE=zsh_complete node dist/index.js)",
39
- "Bash(echo \"exit: $?\")",
40
- "Bash(_ZINKEE_COMPLETE=zsh_complete COMP_WORDS=\"zinkee schemas\" COMP_CWORD=abc node dist/index.js)",
41
- "Bash(_ZINKEE_COMPLETE=zsh_complete COMP_WORDS=\"zinkee config show --foo --bar baz quux ?\" COMP_CWORD=10 node dist/index.js)",
42
- "Bash(git -C /home/guillermo/dev/cli status)",
43
- "Bash(git -C /home/guillermo/dev/cli branch --show-current)",
44
- "Bash(git -C /home/guillermo/dev/cli diff --stat)",
45
- "Bash(git fetch *)",
46
- "Bash(pnpm exec *)",
47
- "Bash(echo \"=== exit $? \\(grep\\) ===\")",
48
- "Bash(grep -rn \"resourceType\" src/)",
49
- "Bash(grep -rn \"automation-folders/\\\\${\\\\|/automations:move\\\\|{folderId}/automations\" src/)",
50
- "Bash(grep -rn \"navigation resources move.*--type\\\\|resourceType\" docs/ README.md)",
51
- "Bash(zinkee --version)",
52
- "Bash(zinkee --json profiles list)",
53
- "Bash(zinkee displays *)",
54
- "Bash(chmod +x /tmp/audit_search.sh)",
55
- "Bash(/tmp/audit_search.sh)",
56
- "Bash(head -60 CLAUDE.md)",
57
- "Bash(head -40 AGENTS.md)",
58
- "Bash(xargs cat)",
59
- "Bash(./gradlew :api:test --tests \"com.zinkee.api.service.v2.data.DataV2TranslatorTest\" --tests \"com.zinkee.api.service.v2.data.DataV2ServiceTest\" --tests \"com.zinkee.api.domain.model.v2.data.DataV2PatchRecordRequestTest\")",
60
- "Bash(./gradlew :api:test --tests \"com.zinkee.api.service.v2.data.DataV2ServiceTest\" --tests \"com.zinkee.api.domain.model.v2.data.DataV2PatchRecordRequestTest\")",
61
- "Bash(./gradlew :api:spotlessApply)",
62
- "Bash(./gradlew :api:test :api:spotbugsMain spotlessCheck)",
63
- "Bash(./gradlew :api:spotbugsMain)",
64
- "Bash(./gradlew spotlessCheck)",
65
- "Bash(git --no-pager diff --stat)",
66
- "Bash(git --no-pager diff -- src/commands/records.ts src/api/records.ts)",
67
- "Bash(echo \"===EXIT: $?===\")"
68
- ]
69
- }
70
- }