diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index bffaf7abf5..7d9cea5e18 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -470,4 +470,5 @@ injector.requireCommand( ], "./commands/native-add" ); +injector.requireCommand(["widget", "widget|ios"], "./commands/widget"); require("./key-commands/bootstrap"); diff --git a/lib/commands/config.ts b/lib/commands/config.ts index 80839a1cfa..70592ceb7f 100644 --- a/lib/commands/config.ts +++ b/lib/commands/config.ts @@ -31,6 +31,7 @@ export class ConfigListCommand implements ICommand { .map((key) => { return ( color.green(`${indent()}${key}: `) + + // @ts-ignore this.getValueString(value[key], depth + 1) ); }) diff --git a/lib/commands/widget.ts b/lib/commands/widget.ts new file mode 100644 index 0000000000..70e750b57a --- /dev/null +++ b/lib/commands/widget.ts @@ -0,0 +1,936 @@ +import { IProjectConfigService, IProjectData } from "../definitions/project"; +import * as fs from "fs"; +import * as prompts from "prompts"; +import { ICommandParameter, ICommand } from "../common/definitions/commands"; +import { IErrors } from "../common/declarations"; +import * as path from "path"; +import * as plist from "plist"; +import { injector } from "../common/yok"; +import { capitalizeFirstLetter } from "../common/utils"; +import { EOL } from "os"; +import { SupportedConfigValues } from "../tools/config-manipulation/config-transformer"; + +export class WidgetCommand implements ICommand { + public allowedParameters: ICommandParameter[] = []; + + constructor( + protected $projectData: IProjectData, + protected $projectConfigService: IProjectConfigService, + protected $logger: ILogger, + protected $errors: IErrors + ) { + this.$projectData.initializeProjectData(); + } + + public async execute(args: string[]): Promise { + this.failWithUsage(); + + return Promise.resolve(); + } + + protected failWithUsage(): void { + this.$errors.failWithHelp("Usage: ns widget ios"); + } + public async canExecute(args: string[]): Promise { + this.failWithUsage(); + return false; + } + + protected getIosSourcePathBase() { + const resources = this.$projectData.getAppResourcesDirectoryPath(); + return path.join(resources, "iOS", "src"); + } +} +export class WidgetIOSCommand extends WidgetCommand { + constructor( + $projectData: IProjectData, + $projectConfigService: IProjectConfigService, + $logger: ILogger, + $errors: IErrors + ) { + super($projectData, $projectConfigService, $logger, $errors); + } + public async canExecute(args: string[]): Promise { + return true; + } + + public async execute(args: string[]): Promise { + this.startPrompt(args); + } + + 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 name = (result.name || "widget").toLowerCase(); + + result = await prompts.prompt({ + type: "select", + name: "value", + message: `What type of widget would you like?`, + choices: [ + { + title: "Live Activity", + description: + "This will create a Live Activity that will display on the iOS Lock Screen.", + value: 0, + }, + { + title: "Live Activity with Home Screen Widget", + description: + "This will create a Live Activity that will display on the iOS Lock Screen with an optional Widget.", + value: 1, + }, + { + title: "Home Screen Widget", + description: "This will create just a Home Screen Widget.", + value: 2, + }, + ], + initial: 1, + }); + + const bundleId = this.$projectConfigService.getValue(`id`, ""); + + switch (result.value) { + case 0: + this.$logger.info("TODO"); + break; + case 1: + await this.generateSharedWidgetPackage( + this.$projectData.projectDir, + name + ); + this.generateWidget( + this.$projectData.projectDir, + name, + bundleId, + result.value + ); + this.generateAppleUtility(this.$projectData.projectDir, name, bundleId); + break; + case 2: + this.$logger.info("TODO"); + break; + } + } + + private async generateSharedWidgetPackage(projectDir: string, name: string) { + const sharedWidgetDir = "Shared_Resources/iOS/SharedWidget"; + const sharedWidgetPath = path.join(projectDir, sharedWidgetDir); + const sharedWidgetSourceDir = "Sources/SharedWidget"; + const sharedWidgetPackagePath = path.join( + projectDir, + `${sharedWidgetDir}/Package.swift` + ); + const sharedWidgetSourcePath = path.join( + sharedWidgetPath, + `${sharedWidgetSourceDir}/${capitalizeFirstLetter(name)}Model.swift` + ); + const gitIgnorePath = path.join(projectDir, ".gitignore"); + + if (!fs.existsSync(sharedWidgetPackagePath)) { + fs.mkdirSync(sharedWidgetPath, { recursive: true }); + fs.mkdirSync(path.join(sharedWidgetPath, sharedWidgetSourceDir), { + recursive: true, + }); + + let content = `// swift-tools-version:5.9 +import PackageDescription + +let package = Package( + name: "SharedWidget", + platforms: [ + .iOS(.v13) + ], + products: [ + .library( + name: "SharedWidget", + targets: ["SharedWidget"]) + ], + dependencies: [ + // Dependencies declare other packages that this package depends on. + ], + targets: [ + .target( + name: "SharedWidget", + dependencies: [] + ) + ] +)${EOL}`; + + fs.writeFileSync(sharedWidgetPackagePath, content); + + content = `import ActivityKit +import WidgetKit + +public struct ${capitalizeFirstLetter(name)}Model: ActivityAttributes { + public typealias DeliveryStatus = ContentState + + public struct ContentState: Codable, Hashable { + // Dynamic stateful properties about your activity go here! + public var message: String + public var deliveryTime: Double + + public init(message: String, deliveryTime: Double) { + self.message = message + self.deliveryTime = deliveryTime + } + } + + // Fixed non-changing properties about your activity go here! + public var numberOfPizzas: Int + public var totalAmount: String + + public init(numberOfPizzas: Int, totalAmount: String) { + self.numberOfPizzas = numberOfPizzas + self.totalAmount = totalAmount + } +}${EOL}`; + + fs.writeFileSync(sharedWidgetSourcePath, content); + + // update spm package + const configData = this.$projectConfigService.readConfig(projectDir); + if (!configData.ios) { + configData.ios = {}; + } + if (!configData.ios.SPMPackages) { + configData.ios.SPMPackages = []; + } + const spmPackages = configData.ios.SPMPackages; + const sharedWidgetPackage = spmPackages?.find( + (p) => p.name === "SharedWidget" + ); + if (!sharedWidgetPackage) { + spmPackages.push({ + name: "SharedWidget", + libs: ["SharedWidget"], + path: "./Shared_Resources/iOS/SharedWidget", + // @ts-ignore + targets: [name], + }); + } else { + // add target if needed + if (!sharedWidgetPackage.targets?.includes(name)) { + sharedWidgetPackage.targets.push(name); + } + } + + configData.ios.SPMPackages = spmPackages; + await this.$projectConfigService.setValue( + "", // root + configData as { [key: string]: SupportedConfigValues } + ); + + if (fs.existsSync(gitIgnorePath)) { + const gitIgnore = fs.readFileSync(gitIgnorePath, { + encoding: "utf-8", + }); + const swiftBuildIgnore = `# Swift +.build +.swiftpm`; + if (gitIgnore.indexOf(swiftBuildIgnore) === -1) { + content = `${gitIgnore}${EOL}${swiftBuildIgnore}${EOL}`; + fs.writeFileSync(gitIgnorePath, content); + } + } + + console.log(`\nCreated Shared Resources: ${sharedWidgetDir}.\n`); + } + } + + private generateWidget( + projectDir: string, + name: string, + bundleId: string, + type: number + ): void { + const appResourcePath = this.$projectData.appResourcesDirectoryPath; + const capitalName = capitalizeFirstLetter(name); + const appInfoPlistPath = path.join(appResourcePath, "iOS", "Info.plist"); + const extensionDir = path.join(appResourcePath, "iOS", "extensions"); + const widgetPath = path.join(extensionDir, name); + const extensionProvisionPath = path.join(extensionDir, `provisioning.json`); + const extensionsInfoPath = path.join(widgetPath, `Info.plist`); + const extensionsPrivacyPath = path.join( + widgetPath, + `PrivacyInfo.xcprivacy` + ); + const extensionsConfigPath = path.join(widgetPath, `extension.json`); + const entitlementsPath = path.join(widgetPath, `${name}.entitlements`); + const widgetBundlePath = path.join( + widgetPath, + `${capitalName}Bundle.swift` + ); + const widgetHomeScreenPath = path.join( + widgetPath, + `${capitalName}HomeScreenWidget.swift` + ); + const widgetLiveActivityPath = path.join( + widgetPath, + `${capitalName}LiveActivity.swift` + ); + // const appIntentPath = path.join(widgetPath, `AppIntent.swift`); + // const widgetLockScreenControlPath = path.join( + // widgetPath, + // `${capitalName}LockScreenControl.swift` + // ); + const appEntitlementsPath = path.join( + appResourcePath, + "iOS", + "app.entitlements" + ); + + if (!fs.existsSync(extensionsConfigPath)) { + fs.mkdirSync(widgetPath, { recursive: true }); + + let content = ` + + + + NSExtension + + NSExtensionPointIdentifier + com.apple.widgetkit-extension + + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1.0 + +${EOL}`; + + fs.writeFileSync(extensionsInfoPath, content); + + content = ` + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + + + NSPrivacyCollectedDataTypes + + NSPrivacyTracking + + +${EOL}`; + + fs.writeFileSync(extensionsPrivacyPath, content); + + // TODO: can add control (lock screen custom control icon handler) in future + // ${[1, 2].includes(type) ? capitalName + "LockScreenControl()" : ""} + + content = `import WidgetKit +import SwiftUI + +@main +struct ${capitalName}Bundle: SwiftUI.WidgetBundle { + var body: some Widget { + ${[1, 2].includes(type) ? capitalName + "HomeScreenWidget()" : ""} + ${[0, 1].includes(type) ? capitalName + "LiveActivity()" : ""} + } +}${EOL}`; + + fs.writeFileSync(widgetBundlePath, content); + + if ([0, 1].includes(type)) { + content = `import ActivityKit +import SwiftUI +import WidgetKit +import Foundation +import SharedWidget +import os + +struct ${capitalName}LiveActivity: Widget { + + var body: some WidgetConfiguration { + ActivityConfiguration(for: ${capitalName}Model.self) { context in + + LockScreenView(message: context.state.message, deliveryTime: context.state.deliveryTime) + .activityBackgroundTint(Color.black) + .activitySystemActionForegroundColor(Color.white) + + } dynamicIsland: { context in + DynamicIsland { + DynamicIslandExpandedRegion(.leading) { + Image(systemName: context.state.deliveryTime >= 0 ? "car.side.arrowtriangle.up.fill" : "face.smiling.inverse") + .resizable() + .scaledToFit() + .frame(width: 50, height: 50) + .foregroundColor(context.state.deliveryTime >= 0 ? Color.green : Color.blue) + } + DynamicIslandExpandedRegion(.trailing) { + if (context.state.deliveryTime >= 0) { + ZStack { + ProgressView(value: context.state.deliveryTime, total: 60) + .progressViewStyle(.circular) + .tint(Color.green) + .frame(width: 75, height: 75) + Text("\\(formatter.string(for: context.state.deliveryTime) ?? "") mins") + .font(.system(size: 11)) + .foregroundStyle(.white) + }.frame(width: 75, height: 75) + } else { + Image(systemName: "checkmark.circle.fill") + .resizable() + .scaledToFit() + .frame(width: 50, height: 50) + .foregroundColor(.blue) + } + } + DynamicIslandExpandedRegion(.bottom) { + Text("\\(context.state.message)") + } + } compactLeading: { + Image(systemName: context.state.deliveryTime >= 0 ? "car.side.arrowtriangle.up.fill" : "face.smiling.inverse") + .resizable() + .scaledToFit() + .frame(width: 20, height: 20) + .foregroundColor(context.state.deliveryTime >= 0 ? .green : .blue) + } compactTrailing: { + Image(systemName: context.state.deliveryTime >= 0 ? "timer.circle.fill" : "checkmark.circle.fill") + .resizable() + .scaledToFit() + .frame(width: 20, height: 20) + .foregroundColor(context.state.deliveryTime >= 0 ? .green : .blue) + } minimal: { + Text(context.state.message).font(.system(size: 12)) + } + .widgetURL(URL(string: "http://www.apple.com")) + .keylineTint(Color.red) + } + } + + private let formatter: NumberFormatter = { + let formatter = NumberFormatter() + formatter.maximumFractionDigits = 0 + formatter.minimumFractionDigits = 0 + return formatter + }() +} + +struct LockScreenView: View { + @State private var message = "" + @State private var deliveryTime: Double = 0 + // for console debugging + let logger = Logger(subsystem: "${bundleId}.${name}", category: "Widget") + + var body: some View { + ZStack { + LinearGradient( + gradient: Gradient(colors: [Color.gray.opacity(0.3), Color.black]), + startPoint: .top, + endPoint: .bottom + ) + VStack { + Spacer() + Image(systemName: deliveryTime >= 0 ? "car.side.arrowtriangle.up.fill" : "face.smiling.inverse") + .resizable() + .scaledToFit() + .frame(width: 50, height: 50) + .foregroundColor(deliveryTime >= 0 ? .green : .blue) + Spacer() + Text("\\(message)") + .foregroundStyle(.white) + Spacer() + } + }.frame(maxWidth: .infinity, maxHeight: .infinity) + } + + init(message: String = "", deliveryTime: Double = 0) { + _message = State(initialValue: message) + _deliveryTime = State(initialValue: deliveryTime) + + // Logs the deliveryTime at init for debugging purposes if needed + logger.log("deliveryTime: \\(deliveryTime)") + } +}${EOL}`; + + fs.writeFileSync(widgetLiveActivityPath, content); + } + + if ([1, 2].includes(type)) { + content = `import SwiftUI +import WidgetKit + +/** + * Widget data shared between the app and the widget extension. + */ +struct WidgetData: Codable { + let pizzas: [String] + let orderTime: Double + let delivered: Bool +} + +struct Provider: TimelineProvider { + + func placeholder(in context: Context) -> SimpleEntry { + SimpleEntry(date: Date(), pizza: "Pepperoni", delivered: false, orderTime: Date()) + } + + func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) { + let entry = SimpleEntry(date: Date(), pizza: "Pepperoni", delivered: false, orderTime: Date()) + completion(entry) + } + + func getTimeline(in context: Context, completion: @escaping @Sendable (Timeline) -> ()) { + var entries: [SimpleEntry] = [] + + if let sharedDefaults = UserDefaults(suiteName: "group.${bundleId}") { + let currentDate = Date() + if let jsonString = sharedDefaults.string(forKey: "widgetData") { + if let jsonData = jsonString.data(using: .utf8) { + do { + let widgetData = try JSONDecoder().decode(WidgetData.self, from: jsonData) + let pizzas = widgetData.pizzas + let orderTime = Date(timeIntervalSince1970: widgetData.orderTime/1000) + let delivered = widgetData.delivered + + // Generate a timeline of entries 1 second apart, starting from the current date. + for secondOffset in 0.. WidgetRelevances { +// // Generate a list containing the contexts this widget is relevant in. +// } +} + +struct SimpleEntry: TimelineEntry { + let date: Date + let pizza: String + let delivered: Bool + let orderTime: Date? +} + +struct WidgetView: View { + @Environment(\\.widgetFamily) var widgetFamily + var entry: Provider.Entry + + var body: some View { + VStack { + if (entry.pizza != "") { + Spacer() + Image(systemName: entry.delivered ? "face.smiling.inverse" : "car.side") + .resizable() + .scaledToFit() + .frame(width: iconSize(for: widgetFamily), height: iconSize(for: widgetFamily)) + .foregroundColor(entry.delivered ? .blue : .green) + Spacer() + if (entry.delivered) { + Text("Pizza Delivered!") + .font(.system(size: fontSize(for: widgetFamily), weight: .bold)) + .foregroundStyle(.white) + } else { + HStack(spacing: 4) { + Text("Ordered:") + .font(.system(size: fontSize(for: widgetFamily))) + .foregroundStyle(.white) + Text(entry.orderTime!, style: .time) + .font(.system(size: fontSize(for: widgetFamily), weight: .bold)) + .foregroundStyle(.white) + } + HStack(spacing: 4) { + Text("Pizza:") + .font(.system(size: fontSize(for: widgetFamily))) + .foregroundStyle(.white) + Text(entry.pizza) + .font(.system(size: fontSize(for: widgetFamily), weight: .bold)) + .foregroundStyle(.white) + } + } + Spacer() + } else { + Spacer() + Image(systemName: "car.side.rear.open") + .resizable() + .scaledToFit() + .frame(width: iconSize(for: widgetFamily), height: iconSize(for: widgetFamily)) + .foregroundColor(.gray) + Spacer() + Text("Awaiting orders...") + .foregroundStyle(.white) + Spacer() + } + }.frame(maxWidth: .infinity, maxHeight: .infinity) + } + + private func iconSize(for family: WidgetFamily) -> CGFloat { + switch family { + case .systemSmall: + return 65 + case .systemMedium: + return 85 + case .systemLarge: + return 150 + default: + return 65 + } + } + + private func fontSize(for family: WidgetFamily) -> CGFloat { + switch family { + case .systemSmall: + return 12 + case .systemMedium: + return 14 + case .systemLarge: + return 18 + default: + return 14 + } + } +} + +@available(iOSApplicationExtension 17.0, *) +struct ${capitalName}HomeScreenWidget: Widget { + let kind: String = "widget" + + var body: some WidgetConfiguration { + StaticConfiguration(kind: kind, provider: Provider()) { entry in + WidgetView(entry: entry) + .containerBackground(for: .widget) { + LinearGradient( + gradient: Gradient(colors: [Color.black.opacity(0.6), Color.black]), + startPoint: .top, + endPoint: .bottom + ) + } + } + .configurationDisplayName("${capitalName} Widget") + .description("${capitalName} delivery service.") + } +} + +#Preview(as: .systemSmall) { + ${capitalName}HomeScreenWidget() +} timeline: { + SimpleEntry(date: .now, pizza: "Pepperoni", delivered: false, orderTime: Date()) + SimpleEntry(date: .now, pizza: "Hawaiian", delivered: false, orderTime: Date()) +}${EOL}`; + fs.writeFileSync(widgetHomeScreenPath, content); + } + + content = `{ + "${bundleId}.${name}": "{set-your-provision-profile-id}" +}`; + + fs.writeFileSync(extensionProvisionPath, content); + + content = ` + + + + com.apple.security.application-groups + + group.${bundleId} + + +${EOL}`; + + fs.writeFileSync(entitlementsPath, content); + + if (fs.existsSync(appInfoPlistPath)) { + const appSupportLiveActivity = "NSSupportsLiveActivities"; + const appInfoPlist = plist.parse( + fs.readFileSync(appInfoPlistPath, { + encoding: "utf-8", + }) + ) as plist.PlistObject; + + if (!appInfoPlist[appSupportLiveActivity]) { + // @ts-ignore + appInfoPlist[appSupportLiveActivity] = true; + const appPlist = plist.build(appInfoPlist); + fs.writeFileSync(appInfoPlistPath, appPlist); + } + } + + const appGroupKey = "com.apple.security.application-groups"; + if (fs.existsSync(appEntitlementsPath)) { + const appEntitlementsPlist = plist.parse( + fs.readFileSync(appEntitlementsPath, { + encoding: "utf-8", + }) + ) as plist.PlistObject; + + if (!appEntitlementsPlist[appGroupKey]) { + // @ts-ignore + appEntitlementsPlist[appGroupKey] = [`group.${bundleId}`]; + const appEntitlements = plist.build(appEntitlementsPlist); + console.log("appentitlement:", appEntitlements); + fs.writeFileSync(appEntitlementsPath, appEntitlements); + } + } else { + content = ` + + + + com.apple.security.application-groups + + group.${bundleId} + + +${EOL}`; + fs.writeFileSync(appEntitlementsPath, content); + } + + content = `{ + "frameworks": [ + "SwiftUI.framework", + "WidgetKit.framework" + ], + "targetBuildConfigurationProperties": { + "ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME": "AccentColor", + "ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME": "WidgetBackground", + "CLANG_ANALYZER_NONNULL": "YES", + "CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION": "YES_AGGRESSIVE", + "CLANG_CXX_LANGUAGE_STANDARD": "\\"gnu++20\\"", + "CLANG_ENABLE_OBJC_WEAK": "YES", + "CLANG_WARN_DOCUMENTATION_COMMENTS": "YES", + "CLANG_WARN_UNGUARDED_AVAILABILITY": "YES_AGGRESSIVE", + "CURRENT_PROJECT_VERSION": 1, + "GCC_C_LANGUAGE_STANDARD": "gnu11", + "GCC_WARN_UNINITIALIZED_AUTOS": "YES_AGGRESSIVE", + "GENERATE_INFOPLIST_FILE": "YES", + "INFOPLIST_KEY_CFBundleDisplayName": "widget", + "INFOPLIST_KEY_NSHumanReadableCopyright": "\\"Copyright © All rights reserved.\\"", + "IPHONEOS_DEPLOYMENT_TARGET": 18.0, + "MARKETING_VERSION": "1.0", + "MTL_FAST_MATH": "YES", + "PRODUCT_NAME": "widget", + "SWIFT_EMIT_LOC_STRINGS": "YES", + "SWIFT_VERSION": "5.0", + "TARGETED_DEVICE_FAMILY": "\\"1,2\\"", + "MTL_ENABLE_DEBUG_INFO": "NO", + "SWIFT_OPTIMIZATION_LEVEL": "\\"-O\\"", + "COPY_PHASE_STRIP": "NO", + "SWIFT_COMPILATION_MODE": "wholemodule", + "CODE_SIGN_ENTITLEMENTS": "../../App_Resources/iOS/extensions/${name}/${name}.entitlements" + }, + "targetNamedBuildConfigurationProperties": { + "debug": { + "DEBUG_INFORMATION_FORMAT": "dwarf", + "GCC_PREPROCESSOR_DEFINITIONS": "(\\"DEBUG=1\\",\\"$(inherited)\\",)", + "MTL_ENABLE_DEBUG_INFO": "INCLUDE_SOURCE", + "SWIFT_ACTIVE_COMPILATION_CONDITIONS": "DEBUG", + "SWIFT_OPTIMIZATION_LEVEL": "\\"-Onone\\"" + }, + "release": { + "CODE_SIGN_STYLE": "Manual", + "MTL_ENABLE_DEBUG_INFO": "NO", + "SWIFT_OPTIMIZATION_LEVEL": "\\"-O\\"", + "COPY_PHASE_STRIP": "NO", + "SWIFT_COMPILATION_MODE": "wholemodule" + } + } +}${EOL}`; + + fs.writeFileSync(extensionsConfigPath, content); + + console.log( + `🚀 Your widget is now ready to develop: App_Resources/iOS/extensions/${name}.\n` + ); + console.log( + `Followup steps:\n +- Check App_Resources/iOS/build.xcconfig uses IPHONEOS_DEPLOYMENT_TARGET=17 or higher. +- Update App_Resources/iOS/extensions/provisioning.json with your profile id. +- Customize App_Resources/iOS/extensions/${name}/${capitalizeFirstLetter( + name + )}LiveActivity.swift for your display. +- Customize Shared_Resources/iOS/SharedWidget/Sources/SharedWidget/${capitalizeFirstLetter( + name + )}Model.swift for your data. +` + ); + } + + // if (fs.existsSync(filePath)) { + // this.$errors.failWithHelp(`Error: File '${filePath}' already exists.`); + // return; + // } + } + + private generateAppleUtility( + projectDir: string, + name: string, + bundleId: string + ): void { + const capitalName = capitalizeFirstLetter(name); + const appResourcePath = this.$projectData.appResourcesDirectoryPath; + const appResourceSrcPath = path.join(appResourcePath, "iOS", "src"); + const appleUtilityPath = path.join( + appResourceSrcPath, + `AppleWidgetUtils.swift` + ); + const referenceTypesPath = path.join(projectDir, "references.d.ts"); + + if (!fs.existsSync(appleUtilityPath)) { + fs.mkdirSync(appResourceSrcPath, { recursive: true }); + } + if (!fs.existsSync(appleUtilityPath)) { + } + + let content = `import Foundation +import UIKit +import ActivityKit +import WidgetKit +import SharedWidget + +@objcMembers +public class AppleWidgetUtils: NSObject { + + // Live Activity Handling + public static func startActivity(_ data: NSDictionary) { + if ActivityAuthorizationInfo().areActivitiesEnabled { + let numberOfPizzas = data.object(forKey: "numberOfPizzas") as! Int + let totalAmount = data.object(forKey: "totalAmount") as! String + let attrs = ${capitalName}Model(numberOfPizzas: numberOfPizzas, totalAmount: totalAmount) + + let message = data.object(forKey: "message") as! String + let deliveryTime = data.object(forKey: "deliveryTime") as! Double + let initialStatus = ${capitalName}Model.DeliveryStatus( + message: message, deliveryTime: deliveryTime) + let content = ActivityContent(state: initialStatus, staleDate: nil) + + do { + let activity = try Activity<${capitalName}Model>.request( + attributes: attrs, + content: content, + pushType: nil) + print("Requested a Live Activity \\(activity.id)") + } catch (let error) { + print("Error requesting Live Activity \\(error.localizedDescription)") + } + } + } + public static func updateActivity(_ data: NSDictionary) { + if ActivityAuthorizationInfo().areActivitiesEnabled { + Task { + let message = data.object(forKey: "message") as! String + let deliveryTime = data.object(forKey: "deliveryTime") as! Double + let status = ${capitalName}Model.DeliveryStatus( + message: message, deliveryTime: deliveryTime) + let content = ActivityContent(state: status, staleDate: nil) + + for activity in Activity<${capitalName}Model>.activities { + await activity.update(content) + } + } + } + } + public static func cancelActivity(_ data: NSDictionary) { + if ActivityAuthorizationInfo().areActivitiesEnabled { + Task { + let message = data.object(forKey: "message") as! String + let status = ${capitalName}Model.DeliveryStatus( + message: message, deliveryTime: 0) + let content = ActivityContent(state: status, staleDate: nil) + + for activity in Activity<${capitalName}Model>.activities { + await activity.end(content, dismissalPolicy: .immediate) + } + } + } + } + public static func getData(key: String) -> String? { + guard let sharedDefaults = UserDefaults(suiteName: "group.${bundleId}") else { + return nil + } + return sharedDefaults.object(forKey: key) as? String + } + public static func updateData(key: String, _ data: String) { + guard let sharedDefaults = UserDefaults(suiteName: "group.${bundleId}") else { + return + } + sharedDefaults.set(data, forKey: key) + sharedDefaults.synchronize() + } + public static func removeData(key: String) { + guard let sharedDefaults = UserDefaults(suiteName: "group.${bundleId}") else { + return + } + sharedDefaults.removeObject(forKey: key) + sharedDefaults.synchronize() + } + + // Home Screen Widget Handling + public static func updateWidget() { + if #available(iOS 14.0, *) { + Task.detached(priority: .userInitiated) { + WidgetCenter.shared.reloadAllTimelines() + } + } + } +}${EOL}`; + + fs.writeFileSync(appleUtilityPath, content); + + content = `/** + * Customize for your own Apple Widget Data + */ +declare interface AppleWidgetModelData { + numberOfPizzas: number; + totalAmount: string; + driverName: string; + deliveryTime: number; +} +declare class AppleWidgetUtils extends NSObject { + static startActivity(data: AppleWidgetModelData): void; + static updateActivity( + data: Pick + ): void; + static cancelActivity(data: Pick): void; + static updateWidget(): void; + static updateDataWithKey(key: string, data: string): void; + static getDataWithKey(key: string): string; + static removeDataWithKey(key: string): void; +}${EOL}`; + + if (!fs.existsSync(referenceTypesPath)) { + const references = `/// +/// ${EOL}${content}`; + fs.writeFileSync(referenceTypesPath, references); + } else { + const references = fs.readFileSync(referenceTypesPath, { + encoding: "utf-8", + }); + if (references?.indexOf("AppleWidgetUtils") === -1) { + content = `${references.toString()}${EOL}${content}`; + fs.writeFileSync(referenceTypesPath, content); + } + } + } +} + +injector.registerCommand(["widget"], WidgetCommand); +injector.registerCommand(["widget|ios"], WidgetIOSCommand); diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index 7589ae9c09..9e13e752f1 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -101,8 +101,34 @@ interface INsConfigPlaform { id?: string; } +interface IOSSPMPackageBase { + name: string; + libs: string[]; + /** + * Optional: If you have more targets (like widgets for example) + * you can list their names here to include the Swift Package with them + */ + targets?: string[]; +} + +export interface IOSRemoteSPMPackage extends IOSSPMPackageBase { + repositoryURL: string; + version: string; +} + +export interface IOSLocalSPMPackage extends IOSSPMPackageBase { + path: string; +} + +export type IOSSPMPackage = IOSRemoteSPMPackage | IOSLocalSPMPackage; + interface INsConfigIOS extends INsConfigPlaform { discardUncaughtJsExceptions?: boolean; + /** + * Swift Package Manager + * List packages to be included in the iOS build. + */ + SPMPackages?: Array; } interface INSConfigVisionOS extends INsConfigIOS {} diff --git a/lib/services/project-config-service.ts b/lib/services/project-config-service.ts index df655a281b..b86db515a1 100644 --- a/lib/services/project-config-service.ts +++ b/lib/services/project-config-service.ts @@ -255,7 +255,7 @@ export default { this.writeDefaultConfig(this.projectHelper.projectDir); } - if (typeof value === "object") { + if (!Array.isArray(value) && typeof value === "object") { let allSuccessful = true; for (const prop of this.flattenObjectToPaths(value)) { @@ -298,7 +298,7 @@ export default { this.$logger.error(`Failed to update config.` + error); } finally { // verify config is updated correctly - if (this.getValue(key) !== value) { + if (!Array.isArray(this.getValue(key)) && this.getValue(key) !== value) { this.$logger.error( `${EOL}Failed to update ${ hasTSConfig ? CONFIG_FILE_NAME_TS : CONFIG_FILE_NAME_JS @@ -465,7 +465,15 @@ You may add \`nsconfig.json\` to \`.gitignore\` as the CLI will regenerate it as ): Array<{ key: string; value: any }> { const toPath = (key: any) => [basePath, key].filter(Boolean).join("."); return Object.keys(obj).reduce((all: any, key) => { - if (typeof obj[key] === "object") { + if (Array.isArray(obj[key])) { + return [ + ...all, + { + key: toPath(key), + value: obj[key], // Preserve arrays as they are + }, + ]; + } else if (typeof obj[key] === "object" && obj[key] !== null) { return [...all, ...this.flattenObjectToPaths(obj[key], toPath(key))]; } return [ diff --git a/lib/tools/config-manipulation/config-transformer.ts b/lib/tools/config-manipulation/config-transformer.ts index 77d4bd4946..e7a8e8a52f 100644 --- a/lib/tools/config-manipulation/config-transformer.ts +++ b/lib/tools/config-manipulation/config-transformer.ts @@ -21,7 +21,8 @@ export type SupportedConfigValues = | string | number | boolean - | { [key: string]: SupportedConfigValues }; + | { [key: string]: SupportedConfigValues } + | any[]; export interface IConfigTransformer { /** @@ -167,11 +168,18 @@ export class ConfigTransformer implements IConfigTransformer { return this.addProperty(key, value, this.getDefaultExportValue()); } - private createInitializer(value: SupportedConfigValues | {}): string { + private createInitializer(value: SupportedConfigValues): any { if (typeof value === "string") { return `'${value}'`; } else if (typeof value === "number" || typeof value === "boolean") { return `${value}`; + } else if (Array.isArray(value)) { + return `[${value.map((v) => this.createInitializer(v)).join(", ")}]`; + } else if (typeof value === "object" && value !== null) { + const properties = Object.entries(value) + .map(([key, val]) => `${key}: ${this.createInitializer(val)}`) + .join(", "); + return `{ ${properties} }`; } return `{}`; } diff --git a/package-lock.json b/package-lock.json index 577163ad1d..e616914561 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,7 +46,7 @@ "minimatch": "7.4.2", "mkdirp": "2.1.6", "mute-stream": "1.0.0", - "nativescript-dev-xcode": "0.8.0", + "nativescript-dev-xcode": "0.8.1", "open": "8.4.2", "ora": "5.4.1", "pacote": "15.1.1", @@ -2701,6 +2701,18 @@ "integrity": "sha512-LEyx4aLEC3x6T0UguF6YILf+ntvmOaWsVfENmIW0E9H09vKlLDGelMjjSm0jkDHALj8A8quZ/HapKNigzwge+Q==", "license": "ISC" }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/acorn": { "version": "8.14.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", @@ -3578,10 +3590,9 @@ } }, "node_modules/call-bind-apply-helpers": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", - "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", - "dev": true, + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -4924,7 +4935,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -5021,7 +5031,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5031,7 +5040,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5041,7 +5049,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -5050,6 +5057,21 @@ "node": ">= 0.4" } }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es6-promise": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-0.1.2.tgz", @@ -5161,6 +5183,15 @@ "node": ">=0.10.0" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/eventemitter2": { "version": "0.4.14", "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", @@ -5175,6 +5206,15 @@ "dev": true, "license": "MIT" }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/execa": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", @@ -5292,9 +5332,9 @@ } }, "node_modules/exponential-backoff": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz", - "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.2.tgz", + "integrity": "sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA==", "license": "Apache-2.0" }, "node_modules/extend": { @@ -5388,9 +5428,9 @@ "license": "MIT" }, "node_modules/fastq": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz", - "integrity": "sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.0.tgz", + "integrity": "sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==", "license": "ISC", "dependencies": { "reusify": "^1.0.4" @@ -5533,9 +5573,9 @@ } }, "node_modules/flatted": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", - "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "license": "ISC" }, "node_modules/follow-redirects": { @@ -5623,13 +5663,14 @@ } }, "node_modules/form-data": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", - "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", "mime-types": "^2.1.12" }, "engines": { @@ -5791,7 +5832,6 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -5927,7 +5967,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -6259,7 +6298,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -7220,7 +7258,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -7229,6 +7266,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", @@ -8821,6 +8873,7 @@ "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.", "dev": true, "license": "MIT" }, @@ -9175,7 +9228,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -10153,9 +10205,9 @@ } }, "node_modules/nativescript-dev-xcode": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/nativescript-dev-xcode/-/nativescript-dev-xcode-0.8.0.tgz", - "integrity": "sha512-NenQo5L57dJfsG8UHFqxMp7wokXVc8oGPagBT8FBTpebxb0GyVzxHwDhAzKHwk2Oex5AvjQKGlp2iOwRwxn9Xw==", + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/nativescript-dev-xcode/-/nativescript-dev-xcode-0.8.1.tgz", + "integrity": "sha512-AIHoah4ZEo8CUC6xb7CX0dWTKHcsoO4DL+nYVwIESVd2XQspE1pzSRMmar9/maz4rxbPeFFTEN7eknqn69+aVg==", "license": "Apache-2.0", "dependencies": { "simple-plist": "1.3.1", @@ -10749,9 +10801,9 @@ } }, "node_modules/object-inspect": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", - "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "dev": true, "license": "MIT", "engines": { @@ -12725,12 +12777,12 @@ } }, "node_modules/readable-web-to-node-stream": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz", - "integrity": "sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.4.tgz", + "integrity": "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw==", "license": "MIT", "dependencies": { - "readable-stream": "^3.6.0" + "readable-stream": "^4.7.0" }, "engines": { "node": ">=8" @@ -12740,6 +12792,46 @@ "url": "https://github.com/sponsors/Borewit" } }, + "node_modules/readable-web-to-node-stream/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/readable-web-to-node-stream/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/readdir-glob": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", @@ -14156,9 +14248,9 @@ } }, "node_modules/socks": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", - "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.4.tgz", + "integrity": "sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==", "license": "MIT", "dependencies": { "ip-address": "^9.0.5", diff --git a/package.json b/package.json index ea641ea7d4..dbce28080f 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ "minimatch": "7.4.2", "mkdirp": "2.1.6", "mute-stream": "1.0.0", - "nativescript-dev-xcode": "0.8.0", + "nativescript-dev-xcode": "0.8.1", "open": "8.4.2", "ora": "5.4.1", "pacote": "15.1.1",