Skip to content

Conversation

@fengtianze
Copy link
Collaborator

@fengtianze fengtianze commented Jan 11, 2026

Summary by CodeRabbit

  • Refactor

    • Redesigned tags input to an item-based flow with improved input confirmation and readonly/tag ordering behavior.
  • Bug Fixes

    • Fixed tag add/remove, duplicate/empty handling and validation so form values reflect user actions reliably.
  • Tests

    • Updated tests and test setup to assert form-level outcomes, stabilize async flows and suppress noisy CSS errors.
  • Chores

    • Hardened release/publish scripts and CI workflows; normalized package repository metadata and added a changeset.

✏️ Tip: You can customize this high-level summary in your review settings.

@changeset-bot
Copy link

changeset-bot bot commented Jan 11, 2026

🦋 Changeset detected

Latest commit: 7d0913e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@alauda/ui Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link

coderabbitai bot commented Jan 11, 2026

Caution

Review failed

The pull request is closed.

Walkthrough

Replaces string[] tags with TagItem[] (value + isInputting), refactors tag creation/confirmation/removal and template rendering, updates tests to assert form values, switches Jest to zone test environment and suppresses specific CSS errors, and adjusts release workflows/scripts and package repository metadata.

Changes

Cohort / File(s) Summary
Tags Input Component Refactor
src/input/tags-input/tags-input.component.ts, src/input/tags-input/tags-input.component.html
Convert internal model from string[] to TagItem[] ({ value, isInputting }); make component standalone; rewrite input/confirm/remove flows (confirmInput, confirmInputtingItem, removeInputtingItem, sortByReadonly); template renders items via item.isInputting gating; add confirmedTags getter and map valueIn/modelOut.
Tests Updated
src/input/tags-input-form.component.spec.ts, src/input/tags-input/tags-input.component.spec.ts
Remove reliance on internal component state; assert host form control values; add extra fixture.detectChanges()/tick() around inputs; adjust expectations for empty/duplicate tags and async validators.
Jest Setup
jest.setup.ts
Use zone-specific setup (setupZoneTestEnv()); suppress JSDOM CSS parsing console errors; retain matchMedia and ResizeObserver mocks.
Release Workflows
.github/workflows/release*.yml
Add job permissions (contents/id-token), set registry-url: 'https://registry.npmjs.org' for Node setup, switch release env var to NODE_AUTH_TOKEN (still sourced from same secret).
Release Script
scripts/release.sh
Accept NODE_AUTH_TOKEN or NPM_TOKEN; remove in-script npm auth config; enforce non-latest tag handling; keep npm publish --provenance --access public.
Package Manifest
package.json
Change repository field from string to object { "type": "git", "url": "https://github.com/ui.git" } (structured repo metadata).
Changeset
.changeset/nasty-cherries-double.md
Add changeset noting patch release and "feat: optimize tags input".

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Input as Input Element
    participant Component as TagsInputComponent
    participant Form as FormControl

    User->>Input: type characters
    Input->>Component: onInput(value)
    Component->>Component: create/ensure TagItem(isInputting=true)
    Component->>Form: emitModel(TagItem[])

    User->>Input: press Enter / blur
    Input->>Component: onKeyDown / onBlur
    Component->>Component: confirmInput() -> set isInputting=false
    Component->>Form: emitModel(TagItem[])

    User->>Component: click remove icon
    Component->>Component: onRemove(index) -> remove TagItem
    Component->>Form: emitModel(TagItem[])
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • yangxiaolang
  • JounQin

Poem

🐰
I hopped through values, soft and bright,
Tagged each thought from day to night.
Flags of input now blink and stay,
Tests applaud and pipelines play.
A tiny rabbit loves the new way!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'feat: optimize tags input' is vague and lacks specificity about what optimization was performed. Provide a more specific title that describes the actual change, such as 'feat: refactor tags input to track inputting state' or 'feat: add TagItem internal model to tags input'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a570d99 and 7d0913e.

📒 Files selected for processing (1)
  • .changeset/nasty-cherries-double.md

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @src/input/tags-input/tags-input.component.html:
- Line 12: The @for block uses an invalid track expression "track index"; change
the track clause to use the implicit $index variable (i.e., "track $index") and,
if you need a local alias, keep the let binding "let index = $index" so the loop
becomes @for (item of model; track $index; let index = $index) {; update the
@for declaration accordingly.

In @src/input/tags-input/tags-input.component.ts:
- Around line 272-288: The current valueIn method uses index-based matching to
preserve isInputting which breaks when the incoming array is reordered/modified;
update valueIn in tags-input.component (function valueIn, referencing model,
isInputting, clearInput, sortByReadonly) to preserve editing state by matching
existing model entries by value (or a unique id if available) instead of by
index, and add unit tests covering external reordering, removal, and addition of
tags while editing to validate the new behavior.
🧹 Nitpick comments (1)
src/input/tags-input/tags-input.component.ts (1)

211-243: Complex input state management - consider extraction.

The logic correctly manages the inputting tag state and clears stale validation errors when the user modifies input. However, the method handles multiple concerns (state updates, error clearing, width calculation).

Consider extracting the inputting item state update logic (lines 215-223) into a helper method like updateInputtingItem(value: string) to improve readability.

♻️ Optional refactor to extract inputting item logic
  onInput() {
    const value = this.inputRef.nativeElement.value;
-   const lastItem = this.model[this.model.length - 1];
-
-   if (lastItem?.isInputting) {
-     this.model = value
-       ? [...this.model.slice(0, -1), { value, isInputting: true }]
-       : this.model.slice(0, -1);
-   } else if (value) {
-     this.model = [...this.model, { value, isInputting: true }];
-   }
+   this.updateInputtingItem(value);

    this.emitModel(this.model);

    if (
      this.controlContainer?.control?.errors?.[INPUT_ERROR_KEY] &&
      Object.keys(this.controlContainer.control.errors).length === 1
    ) {
      this.controlContainer.control.setErrors(null);
    }

    requestAnimationFrame(() => {
      if (value.length) {
        this.renderer.setStyle(
          this.inputRef.nativeElement,
          'width',
          this.inputValueMirror.nativeElement.scrollWidth + 'px',
        );
      } else {
        this.renderer.removeStyle(this.inputRef.nativeElement, 'width');
      }
    });
  }
+
+ private updateInputtingItem(value: string) {
+   const lastItem = this.model[this.model.length - 1];
+   
+   if (lastItem?.isInputting) {
+     this.model = value
+       ? [...this.model.slice(0, -1), { value, isInputting: true }]
+       : this.model.slice(0, -1);
+   } else if (value) {
+     this.model = [...this.model, { value, isInputting: true }];
+   }
+ }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2832cff and fad72ce.

📒 Files selected for processing (2)
  • src/input/tags-input/tags-input.component.html
  • src/input/tags-input/tags-input.component.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Release Beta
  • GitHub Check: Unit Test
  • GitHub Check: Redirect rules - aui
  • GitHub Check: Header rules - aui
  • GitHub Check: Pages changed - aui
🔇 Additional comments (8)
src/input/tags-input/tags-input.component.html (1)

13-25: LGTM!

The conditional rendering of confirmed tags (filtering out isInputting items) aligns well with the refactored data model. The references to item.value are consistent throughout the template.

src/input/tags-input/tags-input.component.ts (7)

37-54: LGTM!

The TagItem interface cleanly separates confirmed tags from in-progress input, and the standalone component configuration is correct.


64-64: LGTM!

The dual type parameters elegantly separate the external API (string[]) from the internal model (TagItem[]), and the confirmedTags getter provides a clean way to access only the confirmed tags for validation.

Also applies to: 141-143


202-209: LGTM!

The method correctly handles the TagItem model, properly checking target.value against readonlyTags.


245-270: LGTM!

The keyboard interaction (Backspace/Enter) and blur handling correctly delegate to the confirmInput method, maintaining consistent validation behavior.


290-311: LGTM!

The modelOut transformation is straightforward, and sortByReadonly efficiently uses a Set for O(1) readonly tag lookup while maintaining the desired order (readonly tags first).


313-325: LGTM!

The helper methods cleanly handle the two input confirmation outcomes (removal vs. confirmation) with proper cleanup and model emission.


327-351: LGTM!

The confirmation flow correctly validates against confirmedTags (excluding inputting items) and properly handles both synchronous and asynchronous validation with the take(1) pattern.

>
{{ tag }}
</aui-tag>
@for (item of model; track index; let index = $index) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Angular @for track syntax correct usage documentation

💡 Result:

Short answer — use the @for block with a required track expression inside the parentheses:

Syntax examples

  • Track by item property (recommended for dynamic lists):
    @for (item of items; track item.id) {

  • {{ item.name }}
  • }
  • Track by index (for static lists):
    @for (item of items; track $index) {

  • {{ item }}
  • }

Notes

  • The track expression is required for @for and should uniquely identify each item to minimize DOM operations.
  • Inside the block you can use contextual variables like $index, $first, $last, $even, $odd and alias them with let.
  • You may add an @empty block that renders when the collection is empty.

References

  • @for API docs. [1]
  • Control flow guide (explanation of track and examples). [2]

Sources:
[1] https://angular.dev/api/core/%40for
[2] https://angular.dev/guide/templates/control-flow


Fix the track expression syntax for Angular @for block.

The track index syntax is incorrect. The track expression must reference $index directly, not a plain index identifier. The correct syntax is track $index.

If you want to alias $index as index for use within the block, use:

@for (item of model; track $index; let index = $index) {

The track expression is evaluated before let bindings, so only implicit variables like $index are available in the track clause.

🤖 Prompt for AI Agents
In @src/input/tags-input/tags-input.component.html at line 12, The @for block
uses an invalid track expression "track index"; change the track clause to use
the implicit $index variable (i.e., "track $index") and, if you need a local
alias, keep the let binding "let index = $index" so the loop becomes @for (item
of model; track $index; let index = $index) {; update the @for declaration
accordingly.

@fengtianze fengtianze force-pushed the feat/optimize-tags-input branch from fad72ce to 8be091a Compare January 12, 2026 03:51
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/input/tags-input/tags-input.component.html (1)

12-12: Consider tracking by item.value instead of index for better performance.

When items are added or removed from the middle of the list, tracking by index causes Angular to re-render more elements than necessary. If tag values are unique, tracking by value avoids unnecessary DOM operations.

♻️ Suggested improvement
-  @for (item of model; let index = $index; track index) {
+  @for (item of model; let index = $index; track item.value) {
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fad72ce and 8be091a.

📒 Files selected for processing (2)
  • src/input/tags-input/tags-input.component.html
  • src/input/tags-input/tags-input.component.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/input/tags-input/tags-input.component.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Unit Test
🔇 Additional comments (1)
src/input/tags-input/tags-input.component.html (1)

13-25: LGTM!

The conditional rendering logic correctly filters out items in the "inputting" state, and the tag bindings properly reference item.value from the new TagItem structure.

@fengtianze fengtianze force-pushed the feat/optimize-tags-input branch from 8be091a to 0b95847 Compare January 12, 2026 05:47
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @src/input/tags-input/tags-input.component.ts:
- Around line 272-288: In valueIn, the current index-based preservation of
isInputting can misalign when this.model differs in length/order; update valueIn
to map incoming v to TagItem by finding the corresponding entry in this.model by
value (e.g., locate this.model.find(m => m.value === value)) and use its
isInputting if found, otherwise false; after building items keep the existing
check that calls clearInput() when no item.isInputting, and return the result
through sortByReadonly—refer to valueIn, this.model, clearInput, sortByReadonly,
and TagItem when making the change.
- Around line 343-350: The code watches this.inputControl.pending and then on
statusChanges calls syncControlStatus() and confirmInputtingItem() if valid, but
it doesn’t handle the case when the async validator resolves to invalid; update
the subscription in the pending branch to check this.inputControl.valid and call
confirmInputtingItem() as now, and add an else branch that removes the
transient/inputting item from the model (the same model mutated by
confirmInputtingItem()) — you can call the existing removal logic or a helper
(e.g., removeInputtingItem or the code path that cancels input) after
syncControlStatus() when this.inputControl.valid is false to ensure the
inputting item is not left indefinitely.
🧹 Nitpick comments (2)
src/input/tags-input/tags-input.component.html (1)

12-26: Consider using a stable identity for track instead of index.

Using track index can cause incorrect DOM reuse when items are removed or reordered (e.g., when readonly tags are sorted to the front). Consider tracking by item.value or a unique identifier for more predictable behavior.

♻️ Suggested change
-  @for (item of model; let index = $index; track index) {
+  @for (item of model; let index = $index; track item.value) {
src/input/tags-input/tags-input.component.ts (1)

211-243: Complex input handling logic is correct but could benefit from extraction.

The onInput method handles multiple concerns: updating the inputting item, emitting the model, clearing errors, and resizing the input. Consider extracting the error-clearing logic (lines 225-230) into a separate method for clarity.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8be091a and 0b95847.

📒 Files selected for processing (5)
  • jest.setup.ts
  • src/input/tags-input/tags-input-form.component.spec.ts
  • src/input/tags-input/tags-input.component.html
  • src/input/tags-input/tags-input.component.spec.ts
  • src/input/tags-input/tags-input.component.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Unit Test
🔇 Additional comments (5)
jest.setup.ts (1)

1-15: LGTM! Jest setup modernization with appropriate error suppression.

The migration to setupZoneTestEnv() aligns with current jest-preset-angular patterns. The CSS parsing error suppression is well-scoped and preserves other console errors.

src/input/tags-input/tags-input.component.spec.ts (1)

37-47: LGTM! Test now correctly simulates real user input flow.

The addition of dispatchEvent(new Event('input')) before the Enter keydown accurately reflects actual user behavior where the input value must first be captured before confirmation.

src/input/tags-input/tags-input-form.component.spec.ts (1)

139-155: Verify: allowEmpty=true test expects empty array for empty input.

Both allowEmpty=false and allowEmpty=true tests result in the same empty array. This appears intentional since an empty input with no actual text shouldn't create a tag, but please confirm this matches the expected behavior for allowEmpty=true.

src/input/tags-input/tags-input.component.ts (2)

37-40: LGTM! Clean interface definition for internal tag state.

The TagItem interface clearly encapsulates the tag value and its inputting state, providing a clean abstraction for the component's internal model.


53-54: LGTM! Standalone component migration with proper imports.

The component is correctly converted to standalone with TagComponent imported. The extended generic type CommonFormControl<string[], TagItem[]> properly separates external value type from internal model type.

Also applies to: 63-65

Comment on lines +272 to +288
protected override valueIn(v: string[]): TagItem[] {
const tags = v || [];

const items = tags.map((value, index) => ({
value,
isInputting:
this.model?.[index]?.value === value
? this.model[index].isInputting
: false,
}));

if (!items.some(item => item.isInputting)) {
this.clearInput();
}

return this.sortByReadonly(items);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Potential issue with valueIn state mapping.

The logic at lines 278-280 attempts to preserve isInputting state by matching against this.model[index], but this.model may have a different length or order than the incoming v array (e.g., after sorting, removal, or external value changes). This could lead to incorrect state preservation.

🔧 Consider matching by value instead of index
  protected override valueIn(v: string[]): TagItem[] {
    const tags = v || [];
+   const modelMap = new Map(this.model?.map(item => [item.value, item]) ?? []);

    const items = tags.map((value, index) => ({
      value,
-     isInputting:
-       this.model?.[index]?.value === value
-         ? this.model[index].isInputting
-         : false,
+     isInputting: modelMap.get(value)?.isInputting ?? false,
    }));

    if (!items.some(item => item.isInputting)) {
      this.clearInput();
    }

    return this.sortByReadonly(items);
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
protected override valueIn(v: string[]): TagItem[] {
const tags = v || [];
const items = tags.map((value, index) => ({
value,
isInputting:
this.model?.[index]?.value === value
? this.model[index].isInputting
: false,
}));
if (!items.some(item => item.isInputting)) {
this.clearInput();
}
return this.sortByReadonly(items);
}
protected override valueIn(v: string[]): TagItem[] {
const tags = v || [];
const modelMap = new Map(this.model?.map(item => [item.value, item]) ?? []);
const items = tags.map((value, index) => ({
value,
isInputting: modelMap.get(value)?.isInputting ?? false,
}));
if (!items.some(item => item.isInputting)) {
this.clearInput();
}
return this.sortByReadonly(items);
}
🤖 Prompt for AI Agents
In @src/input/tags-input/tags-input.component.ts around lines 272 - 288, In
valueIn, the current index-based preservation of isInputting can misalign when
this.model differs in length/order; update valueIn to map incoming v to TagItem
by finding the corresponding entry in this.model by value (e.g., locate
this.model.find(m => m.value === value)) and use its isInputting if found,
otherwise false; after building items keep the existing check that calls
clearInput() when no item.isInputting, and return the result through
sortByReadonly—refer to valueIn, this.model, clearInput, sortByReadonly, and
TagItem when making the change.

Comment on lines 343 to 350
} else if (this.inputControl.pending) {
// PENDING 后只会变为 VALID 或 INVALID 的决议状态
this.inputControl.statusChanges.pipe(take(1)).subscribe(_ => {
this.syncControlStatus();
if (this.inputControl.valid) {
this.emitValue(this.model.concat(value));
this.confirmInputtingItem();
}
});
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing handling for async validator failure.

When the async validator resolves to invalid, the inputting item remains in the model indefinitely. Consider adding an else branch to handle the invalid case by removing the inputting item.

🐛 Proposed fix
      this.inputControl.statusChanges.pipe(take(1)).subscribe(_ => {
        this.syncControlStatus();
        if (this.inputControl.valid) {
          this.confirmInputtingItem();
+       } else {
+         this.removeInputtingItem();
        }
      });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} else if (this.inputControl.pending) {
// PENDING 后只会变为 VALID 或 INVALID 的决议状态
this.inputControl.statusChanges.pipe(take(1)).subscribe(_ => {
this.syncControlStatus();
if (this.inputControl.valid) {
this.emitValue(this.model.concat(value));
this.confirmInputtingItem();
}
});
}
} else if (this.inputControl.pending) {
this.inputControl.statusChanges.pipe(take(1)).subscribe(_ => {
this.syncControlStatus();
if (this.inputControl.valid) {
this.confirmInputtingItem();
} else {
this.removeInputtingItem();
}
});
}
🤖 Prompt for AI Agents
In @src/input/tags-input/tags-input.component.ts around lines 343 - 350, The
code watches this.inputControl.pending and then on statusChanges calls
syncControlStatus() and confirmInputtingItem() if valid, but it doesn’t handle
the case when the async validator resolves to invalid; update the subscription
in the pending branch to check this.inputControl.valid and call
confirmInputtingItem() as now, and add an else branch that removes the
transient/inputting item from the model (the same model mutated by
confirmInputtingItem()) — you can call the existing removal logic or a helper
(e.g., removeInputtingItem or the code path that cancels input) after
syncControlStatus() when this.inputControl.valid is false to ensure the
inputting item is not left indefinitely.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @.github/workflows/release-prod.yml:
- Around line 10-12: The workflow currently sets permissions to "contents: read"
which prevents the job from doing git push; update the permissions block so
"contents" is set to "write" (keep id-token as write) so the step that runs git
push --follow-tags origin "$PUBLISH_BRANCH" can succeed; locate the permissions
block in the release-prod.yml and change the contents permission to write.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0b95847 and 00270c9.

📒 Files selected for processing (4)
  • .github/workflows/release-beta.yml
  • .github/workflows/release-prod.yml
  • .github/workflows/release.yml
  • scripts/release.sh
🔇 Additional comments (6)
scripts/release.sh (1)

12-13: LGTM!

The switch from npm set to direct .npmrc writes is a reliable approach for CI environments and works well with npm provenance. The token interpolation and registry configuration are correct.

.github/workflows/release.yml (2)

8-11: LGTM!

The permissions block is correctly configured. id-token: write is required for npm provenance attestations, and contents: write / pull-requests: write are needed for the changesets action to create release PRs and push tags.


45-46: LGTM!

NODE_AUTH_TOKEN works with actions/setup-node's registry configuration, and NPM_CONFIG_PROVENANCE: true enables provenance attestations. Both are correctly set alongside NPM_TOKEN (used by the release script).

.github/workflows/release-beta.yml (2)

7-9: LGTM!

Permissions are appropriately scoped: contents: read for checkout and id-token: write for npm provenance. Beta releases don't need write access to contents since there's no git push operation.


29-49: LGTM!

The inline release flow is clean and consistent with the other workflows. The --provenance --access public flags are correctly applied for npm attestation support.

One consideration: this workflow triggers on all PRs to master. If fork PRs shouldn't publish beta releases, ensure secrets.NPM_TOKEN is not available to fork PRs (which is the default GitHub behavior for secrets).

.github/workflows/release-prod.yml (1)

37-57: Build and release flow looks good otherwise.

The inline release logic correctly handles version injection, conditional git push, and npm publish with provenance. The NODE_AUTH_TOKEN is properly configured for registry authentication.

@fengtianze fengtianze force-pushed the feat/optimize-tags-input branch from 00270c9 to e87e42a Compare January 12, 2026 06:36
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
.github/workflows/release-prod.yml (1)

37-38: Remove the redundant yarn build from either the workflow or release.sh.

The Build step (line 38) is executed before the Release step (line 41) calls scripts/release.sh, which also runs yarn build (line 26). This causes the project to be built twice during prod releases.

Either:

  • Remove the build call from release.sh if the workflow will always run it beforehand, or
  • Remove the explicit Build step from the workflow if release.sh should remain self-contained.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 00270c9 and e87e42a.

📒 Files selected for processing (6)
  • .github/workflows/release-beta.yml
  • .github/workflows/release-prod.yml
  • .github/workflows/release.yml
  • package.json
  • scripts/npm-tag.js
  • scripts/release.sh
✅ Files skipped from review due to trivial changes (1)
  • package.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • .github/workflows/release-beta.yml
🧰 Additional context used
🧬 Code graph analysis (1)
scripts/npm-tag.js (2)
scripts/publish-version.js (1)
  • version (5-5)
scripts/publish-branch.js (1)
  • version (3-3)
🔇 Additional comments (10)
scripts/npm-tag.js (1)

3-10: Good defensive handling for undefined version.

The fallback logic prevents crashes when no version is provided. One minor note: the error message mentions only PUBLISH_VERSION but the value could also come from process.argv[2]. Consider a more generic message like "Version is undefined" for clarity.

scripts/release.sh (3)

5-5: LGTM!

Correctly passes $PUBLISH_VERSION to the npm-tag script, aligning with the updated script that now prioritizes CLI arguments.


12-13: LGTM on the auth configuration approach.

Writing the token directly to ~/.npmrc is a valid approach for CI environments. The ephemeral nature of CI runners mitigates persistence concerns.


32-32: Good addition of --provenance and --access public.

The --provenance flag enables npm provenance attestation (requires id-token: write permission, which is correctly configured in the workflows). The --access public flag ensures scoped packages are published publicly.

.github/workflows/release.yml (3)

8-11: Permissions correctly configured for provenance and changesets.

  • id-token: write — Required for npm provenance attestation
  • contents: write — Required for changesets to push commits/tags
  • pull-requests: write — Required for changesets to create release PRs

29-29: Good addition of registry-url.

Setting the registry URL in actions/setup-node ensures proper authentication configuration and is required when using NODE_AUTH_TOKEN.


45-46: LGTM on the token and provenance configuration.

NODE_AUTH_TOKEN is the expected environment variable for actions/setup-node authentication. NPM_CONFIG_PROVENANCE: true enables provenance attestation at the npm config level, complementing the --provenance flag in release.sh.

.github/workflows/release-prod.yml (3)

10-12: Permissions appropriately scoped.

Using contents: read (instead of write in release.yml) is correct here since the prod release workflow doesn't need to push commits or create PRs.


27-27: LGTM on registry-url addition.

Consistent with the other workflow files.


44-45: LGTM on authentication setup.

Both NODE_AUTH_TOKEN and NPM_TOKEN are set, ensuring compatibility with both actions/setup-node authentication and the manual .npmrc configuration in release.sh.

@fengtianze fengtianze force-pushed the feat/optimize-tags-input branch from e87e42a to 0b1cad1 Compare January 12, 2026 06:48
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
scripts/release.sh (1)

7-11: Error message inconsistent with the updated condition.

The condition now checks for both NODE_AUTH_TOKEN and NPM_TOKEN, but the error message only mentions NPM_TOKEN. Consider updating the message for clarity.

📝 Suggested improvement
 if [ "$NODE_AUTH_TOKEN" = "" ] && [ "$NPM_TOKEN" = "" ]; then
-  echo "NPM_TOKEN is not available on PR from forked repository!"
+  echo "NODE_AUTH_TOKEN/NPM_TOKEN is not available on PR from forked repository!"
   echo "If you're a member of Alauda, just checkout a new branch instead."
   exit 0
 fi
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e87e42a and 0b1cad1.

📒 Files selected for processing (4)
  • .github/workflows/release-beta.yml
  • .github/workflows/release-prod.yml
  • .github/workflows/release.yml
  • scripts/release.sh
🚧 Files skipped from review as they are similar to previous changes (2)
  • .github/workflows/release.yml
  • .github/workflows/release-beta.yml
🔇 Additional comments (4)
.github/workflows/release-prod.yml (2)

13-15: LGTM! Correct permissions for npm provenance.

The id-token: write permission is required for OIDC-based npm provenance attestation, and contents: write is needed for pushing tags. This aligns with the --provenance flag added to the publish command in scripts/release.sh.


26-26: LGTM! Proper npm authentication setup.

Using registry-url with actions/setup-node automatically configures .npmrc to use NODE_AUTH_TOKEN for authentication, which is the recommended pattern for npm publishing in GitHub Actions.

Also applies to: 40-40

scripts/release.sh (2)

13-15: Good safety guard for latest tag.

Preventing accidental publishing to the latest tag via this script is a sensible safeguard.


29-29: LGTM! Provenance and public access flags.

The --provenance flag enables npm package provenance attestation (requires npm 9.5.0+), and --access public is required for publishing scoped packages publicly. Both are appropriate additions for this scoped package (@alauda/ui).

@fengtianze fengtianze force-pushed the feat/optimize-tags-input branch from 0b1cad1 to e3a91a4 Compare January 12, 2026 07:25
@fengtianze fengtianze force-pushed the feat/optimize-tags-input branch from e3a91a4 to a570d99 Compare January 12, 2026 07:28
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @package.json:
- Around line 5-8: The repository.url in package.json is incorrect (currently
"https://github.com/alauda/alauda-ui.git") and must match this repo to satisfy
the npm provenance check; update the "repository" -> "url" value to the correct
GitHub URL for this project (e.g., "https://github.com/alauda/ui.git") so the
package.json repository metadata points to the actual repository.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0b1cad1 and e3a91a4.

📒 Files selected for processing (5)
  • .github/workflows/release-beta.yml
  • .github/workflows/release-prod.yml
  • .github/workflows/release.yml
  • package.json
  • scripts/release.sh
🚧 Files skipped from review as they are similar to previous changes (2)
  • .github/workflows/release.yml
  • .github/workflows/release-prod.yml
🧰 Additional context used
🪛 GitHub Actions: Release beta
package.json

[error] 1-1: npm publish failed with E422 Unprocessable Entity. Provenance check failed: repository.url in package.json (git+https://github.com/alauda/alauda-ui.git) does not match expected provenance repository URL (https://github.com/alauda/ui). Please fix repository.url to match provenance or update provenance configuration.

🔇 Additional comments (6)
.github/workflows/release-beta.yml (3)

10-12: LGTM!

The id-token: write permission is correctly added to enable OIDC token generation required for npm provenance. Combined with contents: read, this follows the principle of least privilege.


18-23: LGTM!

Adding registry-url is required for actions/setup-node to properly configure npm authentication. This works in conjunction with NODE_AUTH_TOKEN for provenance-enabled publishing.


28-32: LGTM!

Using NODE_AUTH_TOKEN is the correct approach when registry-url is configured in actions/setup-node. The action automatically sets up .npmrc to use this environment variable for authentication.

scripts/release.sh (3)

7-11: LGTM!

Good backward compatibility by accepting either NODE_AUTH_TOKEN (used by actions/setup-node with registry-url) or the legacy NPM_TOKEN. The graceful exit with code 0 for forked PRs is appropriate to allow CI checks to pass without publishing.


13-21: LGTM!

Explicitly blocking latest tag publication via this script is a good safeguard to prevent accidental production releases.


29-29: LGTM!

The --provenance flag enables npm provenance for supply chain security, and --access public is required for scoped packages. These flags align with the workflow permission changes (id-token: write).

Comment on lines 5 to 8
"repository": {
"type": "git",
"url": "https://github.com/alauda/alauda-ui.git"
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Repository URL mismatch causing pipeline failure.

The repository.url points to alauda/alauda-ui but this PR is in alauda/ui. This mismatch is causing the npm provenance check to fail with E422, as indicated by the pipeline error.

🔧 Proposed fix
   "repository": {
     "type": "git",
-    "url": "https://github.com/alauda/alauda-ui.git"
+    "url": "https://github.com/alauda/ui.git"
   },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"repository": {
"type": "git",
"url": "https://github.com/alauda/alauda-ui.git"
},
"repository": {
"type": "git",
"url": "https://github.com/alauda/ui.git"
},
🤖 Prompt for AI Agents
In @package.json around lines 5 - 8, The repository.url in package.json is
incorrect (currently "https://github.com/alauda/alauda-ui.git") and must match
this repo to satisfy the npm provenance check; update the "repository" -> "url"
value to the correct GitHub URL for this project (e.g.,
"https://github.com/alauda/ui.git") so the package.json repository metadata
points to the actual repository.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @scripts/release.sh:
- Around line 7-11: The error message in the conditional that checks [
"$NODE_AUTH_TOKEN" = "" ] && [ "$NPM_TOKEN" = "" ] only mentions NPM_TOKEN;
update the echo(s) in that if block to clearly state that neither
NODE_AUTH_TOKEN nor NPM_TOKEN is available (for example: "Neither
NODE_AUTH_TOKEN nor NPM_TOKEN is available on PRs from forked repositories") and
keep the existing guidance line; modify the echo lines in the same if block so
the message reflects both token options.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0b1cad1 and a570d99.

📒 Files selected for processing (5)
  • .github/workflows/release-beta.yml
  • .github/workflows/release-prod.yml
  • .github/workflows/release.yml
  • package.json
  • scripts/release.sh
🚧 Files skipped from review as they are similar to previous changes (2)
  • .github/workflows/release-beta.yml
  • .github/workflows/release-prod.yml
🔇 Additional comments (5)
.github/workflows/release.yml (3)

12-14: LGTM! Proper permissions for npm provenance.

The id-token: write permission enables OIDC token generation for npm provenance attestation, which improves supply chain security. Combined with contents: write for release operations, these are the minimal permissions needed.


22-27: LGTM! Registry URL correctly configured.

Setting registry-url is required for actions/setup-node to generate the .npmrc file that references NODE_AUTH_TOKEN for authentication.


32-42: LGTM! Correct authentication setup for npm publishing.

NODE_AUTH_TOKEN is the environment variable that actions/setup-node expects when registry-url is configured. This aligns with the standard GitHub Actions pattern for npm authentication.

package.json (1)

5-8: LGTM!

The structured repository format is cleaner and more explicit. The URL correctly reflects the current repository location.

scripts/release.sh (1)

29-29: Good addition of --provenance for supply chain security.

The --provenance flag links the published package to its source repository and build environment, improving supply chain transparency. The script is correctly configured to run only from GitHub Actions workflows with the required id-token: write permission, ensuring the flag operates as intended.

Comment on lines +7 to 11
if [ "$NODE_AUTH_TOKEN" = "" ] && [ "$NPM_TOKEN" = "" ]; then
echo "NPM_TOKEN is not available on PR from forked repository!"
echo "If you're a member of Alauda, just checkout a new branch instead."
exit 0
else
npm set //registry.npmjs.org/:_authToken "$NPM_TOKEN"
fi
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Error message should mention both token options.

The condition now checks for both NODE_AUTH_TOKEN and NPM_TOKEN, but the error message only references NPM_TOKEN. This could confuse contributors who set NODE_AUTH_TOKEN but still see the error.

Proposed fix
 if [ "$NODE_AUTH_TOKEN" = "" ] && [ "$NPM_TOKEN" = "" ]; then
-  echo "NPM_TOKEN is not available on PR from forked repository!"
+  echo "NODE_AUTH_TOKEN or NPM_TOKEN is not available on PR from forked repository!"
   echo "If you're a member of Alauda, just checkout a new branch instead."
   exit 0
 fi
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if [ "$NODE_AUTH_TOKEN" = "" ] && [ "$NPM_TOKEN" = "" ]; then
echo "NPM_TOKEN is not available on PR from forked repository!"
echo "If you're a member of Alauda, just checkout a new branch instead."
exit 0
else
npm set //registry.npmjs.org/:_authToken "$NPM_TOKEN"
fi
if [ "$NODE_AUTH_TOKEN" = "" ] && [ "$NPM_TOKEN" = "" ]; then
echo "NODE_AUTH_TOKEN or NPM_TOKEN is not available on PR from forked repository!"
echo "If you're a member of Alauda, just checkout a new branch instead."
exit 0
fi
🤖 Prompt for AI Agents
In @scripts/release.sh around lines 7 - 11, The error message in the conditional
that checks [ "$NODE_AUTH_TOKEN" = "" ] && [ "$NPM_TOKEN" = "" ] only mentions
NPM_TOKEN; update the echo(s) in that if block to clearly state that neither
NODE_AUTH_TOKEN nor NPM_TOKEN is available (for example: "Neither
NODE_AUTH_TOKEN nor NPM_TOKEN is available on PRs from forked repositories") and
keep the existing guidance line; modify the echo lines in the same if block so
the message reflects both token options.

@fengtianze fengtianze merged commit ee1e6a8 into master Jan 13, 2026
6 of 7 checks passed
@fengtianze fengtianze deleted the feat/optimize-tags-input branch January 13, 2026 04:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants