feat: add support for android widgets#5971
Conversation
📝 WalkthroughWalkthroughThis PR adds Android support to the ChangesAndroid Widget Command
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant WidgetAndroidCommand
participant AndroidResources
participant AndroidManifest
User->>WidgetAndroidCommand: ns widget android
WidgetAndroidCommand->>User: prompt for widget metadata
User-->>WidgetAndroidCommand: name, description, layout, features
WidgetAndroidCommand->>AndroidResources: write/update strings.xml, widget_info.xml
WidgetAndroidCommand->>AndroidResources: create Kotlin AppWidgetProvider class
WidgetAndroidCommand->>AndroidManifest: insert receiver block with markers
AndroidManifest-->>WidgetAndroidCommand: confirm insertion or skip if markers exist
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
docs/man_pages/project/configuration/widget.md (1)
8-22: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winUse h2 headings under the page title.
The page jumps from
# ns widgetstraight to###, which trips MD001 in this file.Suggested fix
-### Description +## Description @@ -### Commands +## Commands @@ -### Command Limitations +## Command Limitations🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/man_pages/project/configuration/widget.md` around lines 8 - 22, The page currently skips from the title to third-level headings, which triggers the markdown heading-order check. Update the headings in this widget manual page so the sections like Description, Commands, and Command Limitations use h2 under the page title, and keep the existing structure intact in the widget documentation template.Source: Linters/SAST tools
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@lib/commands/widget.ts`:
- Around line 1140-1145: The widget generation flow is interpolating
prompt-derived values directly into XML, Kotlin, and filesystem paths, which can
break output or allow path traversal. Update the logic around fs.writeFileSync,
widgetsStringsInfoPath, and the code that builds widget source/package paths to
validate and sanitize description, widgetPackageName, and widgetClassName before
use. Escape XML content for strings resources, normalize package/class names to
valid Kotlin identifiers, and reject or strip path separators so generated files
stay within the intended main/java subtree.
- Around line 1062-1076: The `widget.ts` prompt flow overwrites `result` before
`initialLayout` is read, so the earlier layout answer is lost and the code
always falls back to the default. Fix the `prompts.prompt` sequence in the
widget generator by capturing the `initialLayout` value immediately after the
first prompt (before the second `widgetFeatures` prompt reassigns `result`), and
keep the `widgetFeatures` handling separate so both values are preserved.
- Around line 1260-1281: The generated manifest wiring in widgetXml is
incomplete: the receiver in widget.ts needs the boot and upgrade paths fully
declared. Update the manifest generation around the widget receiver so it also
adds the RECEIVE_BOOT_COMPLETED permission when BOOT_COMPLETED is included, and
add an intent-filter for android.intent.action.MY_PACKAGE_REPLACED alongside the
existing APPWIDGET_UPDATE handling. Use the widgetXml/manifestContent
replacement block to locate and adjust the receiver registration.
- Around line 953-955: The widget command’s execute method returns before the
prompt flow finishes, because startPrompt is not awaited. Update execute in
widget.ts so it awaits startPrompt(args), ensuring commands-service waits for
the full prompt/generation chain and surfaces downstream errors from the widget
command.
---
Outside diff comments:
In `@docs/man_pages/project/configuration/widget.md`:
- Around line 8-22: The page currently skips from the title to third-level
headings, which triggers the markdown heading-order check. Update the headings
in this widget manual page so the sections like Description, Commands, and
Command Limitations use h2 under the page title, and keep the existing structure
intact in the widget documentation template.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: ec7d5e78-a622-4b3e-9a0c-d53c4818ad1f
📒 Files selected for processing (3)
docs/man_pages/project/configuration/widget.mdlib/bootstrap.tslib/commands/widget.ts
| public async execute(args: string[]): Promise<void> { | ||
| this.startPrompt(args); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="lib/commands/widget.ts"
echo "== line count =="
wc -l "$FILE"
echo
echo "== execute/startPrompt context =="
grep -n -A8 -B8 -E 'async execute\(|startPrompt\(' "$FILE"Repository: NativeScript/nativescript-cli
Length of output: 2426
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find similar async execute patterns and whether they await internal async helpers.
rg -n "public async execute\([^)]*\): Promise<void> \{\s*(await )?this\.[A-Za-z0-9_]+\(" lib/commands -U
echo
echo "== widget startPrompt body =="
sed -n '978,1140p' lib/commands/widget.ts | cat -nRepository: NativeScript/nativescript-cli
Length of output: 7209
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== execute call sites =="
rg -n "\.execute\(" lib src test -g '!**/node_modules/**'
echo
echo "== command runner / dispatcher references =="
rg -n "canExecute\(|executeCore\(|runCommand|dispatch|command.*execute" lib src test -g '!**/node_modules/**'Repository: NativeScript/nativescript-cli
Length of output: 50387
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== commands service context =="
sed -n '90,125p' lib/common/services/commands-service.ts | cat -n
echo
echo "== widget command context =="
sed -n '949,985p' lib/commands/widget.ts | cat -nRepository: NativeScript/nativescript-cli
Length of output: 2741
Await startPrompt in execute().
commands-service.ts awaits command.execute(...), but this method returns before the prompt/generation chain finishes, so the widget command can complete early and miss downstream errors.
Suggested fix
public async execute(args: string[]): Promise<void> {
- this.startPrompt(args);
+ await this.startPrompt(args);
}📝 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.
| public async execute(args: string[]): Promise<void> { | |
| this.startPrompt(args); | |
| } | |
| public async execute(args: string[]): Promise<void> { | |
| await this.startPrompt(args); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/commands/widget.ts` around lines 953 - 955, The widget command’s execute
method returns before the prompt flow finishes, because startPrompt is not
awaited. Update execute in widget.ts so it awaits startPrompt(args), ensuring
commands-service waits for the full prompt/generation chain and surfaces
downstream errors from the widget command.
| result = await prompts.prompt({ | ||
| type: "text", | ||
| name: "initialLayout", | ||
| message: `What initial layout would you like for this widget? (Default is 'ns_remote_views_linear_layout' which is an empty linear layout. You can customize this with your own custom layout)`, | ||
| }); | ||
|
|
||
| result = await prompts.prompt({ | ||
| type: "text", | ||
| name: "widgetFeatures", | ||
| message: `Enable responsive layout features for this widget? (Default is 'Y')`, | ||
| }); | ||
|
|
||
| const widgetFeatures = result.widgetFeatures || "Y"; | ||
|
|
||
| const initialLayout = result.initialLayout || "ns_remote_views_linear_layout"; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Read initialLayout before reusing result.
Line 1076 runs after result has been overwritten by the widget-features prompt, so a custom layout is always discarded and the generator always falls back to ns_remote_views_linear_layout.
Suggested fix
result = await prompts.prompt({
type: "text",
name: "initialLayout",
message: `What initial layout would you like for this widget? (Default is 'ns_remote_views_linear_layout' which is an empty linear layout. You can customize this with your own custom layout)`,
});
+const initialLayout = result.initialLayout || "ns_remote_views_linear_layout";
+
result = await prompts.prompt({
type: "text",
name: "widgetFeatures",
message: `Enable responsive layout features for this widget? (Default is 'Y')`,
});
const widgetFeatures = result.widgetFeatures || "Y";
-
-const initialLayout = result.initialLayout || "ns_remote_views_linear_layout";📝 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.
| result = await prompts.prompt({ | |
| type: "text", | |
| name: "initialLayout", | |
| message: `What initial layout would you like for this widget? (Default is 'ns_remote_views_linear_layout' which is an empty linear layout. You can customize this with your own custom layout)`, | |
| }); | |
| result = await prompts.prompt({ | |
| type: "text", | |
| name: "widgetFeatures", | |
| message: `Enable responsive layout features for this widget? (Default is 'Y')`, | |
| }); | |
| const widgetFeatures = result.widgetFeatures || "Y"; | |
| const initialLayout = result.initialLayout || "ns_remote_views_linear_layout"; | |
| result = await prompts.prompt({ | |
| type: "text", | |
| name: "initialLayout", | |
| message: `What initial layout would you like for this widget? (Default is 'ns_remote_views_linear_layout' which is an empty linear layout. You can customize this with your own custom layout)`, | |
| }); | |
| const initialLayout = result.initialLayout || "ns_remote_views_linear_layout"; | |
| result = await prompts.prompt({ | |
| type: "text", | |
| name: "widgetFeatures", | |
| message: `Enable responsive layout features for this widget? (Default is 'Y')`, | |
| }); | |
| const widgetFeatures = result.widgetFeatures || "Y"; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/commands/widget.ts` around lines 1062 - 1076, The `widget.ts` prompt flow
overwrites `result` before `initialLayout` is read, so the earlier layout answer
is lost and the code always falls back to the default. Fix the `prompts.prompt`
sequence in the widget generator by capturing the `initialLayout` value
immediately after the first prompt (before the second `widgetFeatures` prompt
reassigns `result`), and keep the `widgetFeatures` handling separate so both
values are preserved.
| const content = `<?xml version="1.0" encoding="utf-8"?> | ||
| <resources> | ||
| <string name="${name}_widget_description">${description}</string> | ||
| </resources>${EOL}`; | ||
|
|
||
| fs.writeFileSync(widgetsStringsInfoPath, content); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Validate and escape prompt-derived values before writing files.
description, widgetPackageName, and widgetClassName are interpolated raw into XML, Kotlin, and path segments. Inputs like Weather & Clock, com.example-widget, or foo/bar will generate broken resources/source, and slash-containing names can escape the intended main/java subtree.
Also applies to: 1206-1224, 1260-1264
🧰 Tools
🪛 ast-grep (0.44.0)
[warning] 1144-1144: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.writeFileSync(widgetsStringsInfoPath, content)
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').
(detect-non-literal-fs-filename-typescript)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/commands/widget.ts` around lines 1140 - 1145, The widget generation flow
is interpolating prompt-derived values directly into XML, Kotlin, and filesystem
paths, which can break output or allow path traversal. Update the logic around
fs.writeFileSync, widgetsStringsInfoPath, and the code that builds widget
source/package paths to validate and sanitize description, widgetPackageName,
and widgetClassName before use. Escape XML content for strings resources,
normalize package/class names to valid Kotlin identifiers, and reject or strip
path separators so generated files stay within the intended main/java subtree.
Source: Linters/SAST tools
| const widgetXml = ` | ||
| ${widgetMarkerStart} | ||
| <receiver | ||
| android:name="${packageName}.${widgetClassName}" | ||
| android:exported="true"> | ||
| <intent-filter> | ||
| <action android:name="android.intent.action.BOOT_COMPLETED" /> | ||
| </intent-filter> | ||
| <intent-filter> | ||
| <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> | ||
| </intent-filter> | ||
| <meta-data | ||
| android:name="android.appwidget.provider" | ||
| android:resource="@xml/ns_${name}_widget_info" /> | ||
| </receiver> | ||
| ${widgetMarkerEnd}`; | ||
|
|
||
| // Insert before </application> | ||
| manifestContent = manifestContent.replace( | ||
| "</application>", | ||
| `${widgetXml}\n </application>`, | ||
| ); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Map the relevant file first
ast-grep outline lib/commands/widget.ts --view expanded || true
# Read the relevant section around the reported lines
sed -n '1200,1320p' lib/commands/widget.ts
# Search for boot/app update wiring and related permissions in the codebase
rg -n "RECEIVE_BOOT_COMPLETED|MY_PACKAGE_REPLACED|BOOT_COMPLETED|APPWIDGET_UPDATE|android:exported|appwidget.provider" lib . -g '!**/node_modules/**' -g '!**/dist/**' -g '!**/build/**'Repository: NativeScript/nativescript-cli
Length of output: 4918
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Search for any manifest permission insertion or package-replaced handling in the repo
rg -n "RECEIVE_BOOT_COMPLETED|MY_PACKAGE_REPLACED|BOOT_COMPLETED|ACTION_MY_PACKAGE_REPLACED|PACKAGE_REPLACED|onReceive\\(|AppWidgetProvider" lib . -g '!**/node_modules/**' -g '!**/dist/**' -g '!**/build/**'
# Read the widget info generation section for surrounding intent assumptions
sed -n '1150,1210p' lib/commands/widget.tsRepository: NativeScript/nativescript-cli
Length of output: 2351
Add the missing reboot/update wiring in the generated manifest. BOOT_COMPLETED also needs android.permission.RECEIVE_BOOT_COMPLETED, and the receiver should handle android.intent.action.MY_PACKAGE_REPLACED for app upgrades. Without both, widgets can fail to recover after reboot or update.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/commands/widget.ts` around lines 1260 - 1281, The generated manifest
wiring in widgetXml is incomplete: the receiver in widget.ts needs the boot and
upgrade paths fully declared. Update the manifest generation around the widget
receiver so it also adds the RECEIVE_BOOT_COMPLETED permission when
BOOT_COMPLETED is included, and add an intent-filter for
android.intent.action.MY_PACKAGE_REPLACED alongside the existing
APPWIDGET_UPDATE handling. Use the widgetXml/manifestContent replacement block
to locate and adjust the receiver registration.
Enables
ns widget androidSummary by CodeRabbit
New Features
Documentation