Skip to content

feat: add support for android widgets#5971

Open
triniwiz wants to merge 6 commits into
mainfrom
feat/android-widgets
Open

feat: add support for android widgets#5971
triniwiz wants to merge 6 commits into
mainfrom
feat/android-widgets

Conversation

@triniwiz

@triniwiz triniwiz commented Feb 20, 2026

Copy link
Copy Markdown
Member

Enables ns widget android

Summary by CodeRabbit

  • New Features

    • Added Android support for widget creation, including an interactive setup flow that generates the needed app resources and app registration entries.
    • Widget commands now support both iOS and Android platforms.
  • Documentation

    • Updated the widget guide to describe platform-based usage and added a note about platform-specific command limitations.

@triniwiz triniwiz marked this pull request as ready for review February 24, 2026 20:54
@NathanWalker NathanWalker added this to the 9.1 milestone Jun 9, 2026
@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds Android support to the ns widget command by introducing a new WidgetAndroidCommand class that generates Android widget resources, Kotlin provider classes, and updates AndroidManifest.xml. It registers the new command route in the dependency injector and updates documentation to be platform-generic.

Changes

Android Widget Command

Layer / File(s) Summary
WidgetAndroidCommand implementation and wiring
lib/commands/widget.ts, lib/bootstrap.ts
Adds WidgetAndroidCommand extending WidgetCommand with an interactive prompt collecting widget metadata (name, description, update interval, resize mode, dimensions, layout, features), generates/updates Android XML string and widget-info resources, creates a Kotlin AppWidgetProvider class file, inserts a marker-guarded <receiver> block into AndroidManifest.xml (throwing if the manifest is missing), and registers widget|android alongside existing widget and widget|ios routes in the injector.
Man page documentation update
docs/man_pages/project/configuration/widget.md
Generalizes the widget man page with conditional Jekyll front-matter, a platform-parameterized ns widget <Platform> usage table, and an HTML-only note restricting ns widget ios to macOS.

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
Loading

Poem

A rabbit hops with code in paw,
Android widgets, no more flaw!
Manifest markers, XML neat,
Kotlin classes, review complete.
🐰📱 Hop hop, ship it, sweet!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding Android widget support.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 win

Use h2 headings under the page title.

The page jumps from # ns widget straight 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

📥 Commits

Reviewing files that changed from the base of the PR and between 00ba99a and bcb0496.

📒 Files selected for processing (3)
  • docs/man_pages/project/configuration/widget.md
  • lib/bootstrap.ts
  • lib/commands/widget.ts

Comment thread lib/commands/widget.ts
Comment on lines +953 to +955
public async execute(args: string[]): Promise<void> {
this.startPrompt(args);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 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 -n

Repository: 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 -n

Repository: 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.

Suggested change
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.

Comment thread lib/commands/widget.ts
Comment on lines +1062 to +1076
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";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 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.

Suggested change
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.

Comment thread lib/commands/widget.ts
Comment on lines +1140 to +1145
const content = `<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="${name}_widget_description">${description}</string>
</resources>${EOL}`;

fs.writeFileSync(widgetsStringsInfoPath, content);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 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

Comment thread lib/commands/widget.ts
Comment on lines +1260 to +1281
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>`,
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 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.ts

Repository: 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.

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