-
-
Notifications
You must be signed in to change notification settings - Fork 204
feat: add support for android widgets #5971
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
6999d1f
84990c1
094e8aa
cdd7ffa
469dafa
bcb0496
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -937,6 +937,352 @@ declare class AppleWidgetUtils extends NSObject { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export class WidgetAndroidCommand extends WidgetCommand { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| constructor( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $projectData: IProjectData, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $projectConfigService: IProjectConfigService, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $logger: ILogger, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $errors: IErrors, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| super($projectData, $projectConfigService, $logger, $errors); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public async canExecute(args: string[]): Promise<boolean> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public async execute(args: string[]): Promise<void> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.startPrompt(args); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private toAndroidFriendlyResourceName(name: string): string { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return name | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .trim() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .toLowerCase() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .replace(/[^a-z0-9_]/g, "_") // replace anything not a-z, 0-9, or _ with _ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .replace(/_{2,}/g, "_") // collapse multiple underscores | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .replace(/^[0-9_]+/, ""); // strip leading digits or underscores | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private toAndroidClassName(name: string): string { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return name | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .trim() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .toLowerCase() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .replace(/[^a-z0-9_]/g, "_") // normalize to resource name first | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .replace(/_{2,}/g, "_") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .replace(/^[0-9_]+/, "") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .split("_") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .join(""); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private async startPrompt(args: string[]) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let result = await prompts.prompt({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type: "text", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: "name", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| message: `What name would you like for this widget? (Default is 'widget')`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const rawName = (result.name || "widget").toLowerCase(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const name = this.toAndroidFriendlyResourceName(rawName); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| result = await prompts.prompt({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type: "text", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: "description", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| message: `What description would you like for this widget? (Default is '')`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const description = result.description || ""; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| result = await prompts.prompt({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type: "number", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: "updateInterval", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| message: `What update interval would you like for this widget? (Default is 900000 ms or 15 mins)`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const updateInterval = result.updateInterval || 900000; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| result = await prompts.prompt({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type: "select", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: "resizeMode", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| message: `What type of resizing would you like for this widget?`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| choices: [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| title: "Horizontal", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| description: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "This will allow the widget to resize horizontally on the Home Screen", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value: 0, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| title: "Vertical", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| description: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "This will allow the widget to resize vertically on the Home Screen", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value: 1, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| title: "Horizontal and Vertical", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| description: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "This will allow the widget to resize both horizontally and vertically on the Home Screen", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value: 2, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| initial: 2, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let resizeMode = "horizontal|vertical"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| switch (result.resizeMode) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case 0: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| resizeMode = "horizontal"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case 1: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| resizeMode = "vertical"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case 2: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| resizeMode = "horizontal|vertical"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| result = await prompts.prompt({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type: "text", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: "minWidth", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| message: `What minimum width would you like for this widget? (Default is '50dp')`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const minWidth = result.minWidth || "50dp"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| result = await prompts.prompt({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type: "text", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: "minHeight", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| message: `What minimum height would you like for this widget? (Default is '50dp')`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const minHeight = result.minHeight || "50dp"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+1062
to
+1076
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Read Line 1076 runs after 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
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const bundleId = this.$projectConfigService.getValue(`id`, ""); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| result = await prompts.prompt({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type: "text", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: "widgetPackageName", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| message: `What package name would you like to use for this widget? (Default is ${bundleId})`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const widgetPackageName = result.widgetPackageName || bundleId; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| result = await prompts.prompt({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type: "text", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: "widgetClassName", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| message: `What class name would you like to use for this widget? (Default is ${this.toAndroidClassName(name)}WidgetProvider)`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const widgetClassName = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| result.widgetClassName || | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `${this.toAndroidClassName(rawName)}WidgetProvider`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await this.generateWidgetDescriptionResource(name, description); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await this.generateWidgetInfo( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| resizeMode, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| minWidth, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| minHeight, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| initialLayout, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| widgetFeatures === "N" ? false : true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await this.generateWidget( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| widgetPackageName, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| widgetClassName, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| updateInterval, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await this.generateAndroidManifest( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| widgetPackageName, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| widgetClassName, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private async generateWidgetDescriptionResource( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: string, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| description: string, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const appResourcePath = this.$projectData.appResourcesDirectoryPath; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const widgetsStringsInfoPath = path.join( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| appResourcePath, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Android", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "src", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "main", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "res", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "values", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `ns_widgets_strings_info.xml`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!fs.existsSync(widgetsStringsInfoPath)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fs.mkdirSync(path.dirname(widgetsStringsInfoPath), { recursive: true }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const content = `<?xml version="1.0" encoding="utf-8"?> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <resources> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <string name="${name}_widget_description">${description}</string> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </resources>${EOL}`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fs.writeFileSync(widgetsStringsInfoPath, content); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+1140
to
+1145
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
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. (detect-non-literal-fs-filename-typescript) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const content = fs.readFileSync(widgetsStringsInfoPath).toString(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (content.indexOf(`${name}_widget_description`) === -1) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const updatedContent = content.replace( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "</resources>", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ` <string name="${name}_widget_description">${description}</string>\n</resources>`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fs.writeFileSync(widgetsStringsInfoPath, updatedContent); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private async generateWidgetInfo( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: string, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| resizeMode: string, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| minWidth: string, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| minHeight: string, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| initialLayout: string, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| enableWidgetFeatures: boolean, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const appResourcePath = this.$projectData.appResourcesDirectoryPath; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const widgetInfoPath = path.join( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| appResourcePath, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Android", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "src", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "main", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "res", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "xml", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `ns_${name}_widget_info.xml`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!fs.existsSync(widgetInfoPath)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fs.mkdirSync(path.dirname(widgetInfoPath), { recursive: true }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const content = `<?xml version="1.0" encoding="utf-8"?> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <appwidget-provider | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| xmlns:android="http://schemas.android.com/apk/res/android" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| android:description="@string/${name}_widget_description" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| android:initialLayout="@layout/${initialLayout}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| android:minWidth="${minWidth}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| android:minHeight="${minHeight}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| android:resizeMode="${resizeMode}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| android:updatePeriodMillis="0" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| android:widgetCategory="home_screen" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ${enableWidgetFeatures ? 'android:widgetFeatures="reconfigurable|configuration_optional"' : ""}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <meta-data | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| android:name="org.nativescript.widgets.MANAGED_WIDGET" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| android:value="true"/> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </appwidget-provider>${EOL}`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fs.writeFileSync(widgetInfoPath, content); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private async generateWidget( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| packageName: string, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| widgetClassName: string, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| updateInterval: number, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const appResourcePath = this.$projectData.appResourcesDirectoryPath; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const widgetPath = path.join( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| appResourcePath, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Android", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "src", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "main", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "java", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| packageName.replace(/\./g, "/"), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `${widgetClassName}.kt`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!fs.existsSync(widgetPath)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fs.mkdirSync(path.dirname(widgetPath), { recursive: true }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const content = `package ${packageName} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.nativescript.widgets.AppWidgetProvider | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| class ${widgetClassName} : AppWidgetProvider() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| override val interval = ${updateInterval}L | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ${EOL}`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fs.writeFileSync(widgetPath, content); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private async generateAndroidManifest( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: string, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| packageName: string, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| widgetClassName: string, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const appResourcePath = this.$projectData.appResourcesDirectoryPath; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const mainManifestPath = path.join( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| appResourcePath, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Android", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "src", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "main", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "AndroidManifest.xml", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!fs.existsSync(mainManifestPath)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new Error("Main AndroidManifest.xml not found"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let manifestContent = fs.readFileSync(mainManifestPath, "utf-8"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const widgetMarkerStart = `<!-- BEGIN NativeScript Widget: ${name} -->`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const widgetMarkerEnd = `<!-- END NativeScript Widget: ${name} -->`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Check if widget already exists | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (manifestContent.includes(widgetMarkerStart)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.log(`Widget ${name} already exists in manifest, skipping...`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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>`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+1260
to
+1281
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.tsRepository: NativeScript/nativescript-cli Length of output: 2351 Add the missing reboot/update wiring in the generated manifest. 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fs.writeFileSync(mainManifestPath, manifestContent); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| injector.registerCommand(["widget"], WidgetCommand); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| injector.registerCommand(["widget|ios"], WidgetIOSCommand); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| injector.registerCommand(["widget|android"], WidgetAndroidCommand); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
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:
Repository: NativeScript/nativescript-cli
Length of output: 2426
🏁 Script executed:
Repository: NativeScript/nativescript-cli
Length of output: 7209
🏁 Script executed:
Repository: NativeScript/nativescript-cli
Length of output: 50387
🏁 Script executed:
Repository: NativeScript/nativescript-cli
Length of output: 2741
Await
startPromptinexecute().commands-service.tsawaitscommand.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
🤖 Prompt for AI Agents