Skip to content

Commit

Permalink
feat: ability to embed NativeScript into host Swift and Kotlin projec…
Browse files Browse the repository at this point in the history
…ts (#5803)
  • Loading branch information
NathanWalker authored Jul 11, 2024
1 parent 9e9773f commit 42177c3
Show file tree
Hide file tree
Showing 38 changed files with 2,876 additions and 1,145 deletions.
2 changes: 2 additions & 0 deletions lib/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@ injector.requireCommand("build|vision", "./commands/build");
injector.requireCommand("build|visionos", "./commands/build");
injector.requireCommand("deploy", "./commands/deploy");

injector.requireCommand("embed", "./commands/embedding/embed");

injector.require("testExecutionService", "./services/test-execution-service");
injector.requireCommand("dev-test|android", "./commands/test");
injector.requireCommand("dev-test|ios", "./commands/test");
Expand Down
3 changes: 2 additions & 1 deletion lib/commands/add-platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import { injector } from "../common/yok";

export class AddPlatformCommand
extends ValidatePlatformCommandBase
implements ICommand {
implements ICommand
{
public allowedParameters: ICommandParameter[] = [];

constructor(
Expand Down
127 changes: 127 additions & 0 deletions lib/commands/embedding/embed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { ICommand, ICommandParameter } from "../../common/definitions/commands";
import { injector } from "../../common/yok";
import { PrepareCommand } from "../prepare";
import { PrepareController } from "../../controllers/prepare-controller";
import { IOptions, IPlatformValidationService } from "../../declarations";
import { IProjectConfigService, IProjectData } from "../../definitions/project";
import { IPlatformsDataService } from "../../definitions/platform";
import { PrepareDataService } from "../../services/prepare-data-service";
import { IMigrateController } from "../../definitions/migrate";
import { resolve } from "path";
import { IFileSystem } from "../../common/declarations";
import { color } from "../../color";

export class EmbedCommand extends PrepareCommand implements ICommand {
constructor(
public $options: IOptions,
public $prepareController: PrepareController,
public $platformValidationService: IPlatformValidationService,
public $projectData: IProjectData,
public $platformCommandParameter: ICommandParameter,
public $platformsDataService: IPlatformsDataService,
public $prepareDataService: PrepareDataService,
public $migrateController: IMigrateController,

private $logger: ILogger,
private $fs: IFileSystem,
private $projectConfigService: IProjectConfigService
) {
super(
$options,
$prepareController,
$platformValidationService,
$projectData,
$platformCommandParameter,
$platformsDataService,
$prepareDataService,
$migrateController
);
}

private resolveHostProjectPath(hostProjectPath: string): string {
if (hostProjectPath.charAt(0) === ".") {
// resolve relative to the project dir
const projectDir = this.$projectData.projectDir;
return resolve(projectDir, hostProjectPath);
}

return resolve(hostProjectPath);
}

public async execute(args: string[]): Promise<void> {
const hostProjectPath = args[1];
const resolvedHostProjectPath =
this.resolveHostProjectPath(hostProjectPath);

if (!this.$fs.exists(resolvedHostProjectPath)) {
this.$logger.error(
`The host project path ${color.yellow(
hostProjectPath
)} (resolved to: ${color.yellow.dim(
resolvedHostProjectPath
)}) does not exist.`
);
return;
}

this.$options["hostProjectPath"] = resolvedHostProjectPath;
if (args.length > 2) {
this.$options["hostProjectModuleName"] = args[2];
}

return super.execute(args);
}

public async canExecute(args: string[]): Promise<boolean> {
const canSuperExecute = await super.canExecute(args);

if (!canSuperExecute) {
return false;
}

// args[0] is the platform
// args[1] is the path to the host project
// args[2] is the host project module name

const platform = args[0].toLowerCase();

// also allow these to be set in the nativescript.config.ts
if (!args[1]) {
const hostProjectPath = this.getEmbedConfigForKey(
"hostProjectPath",
platform
);
if (hostProjectPath) {
args[1] = hostProjectPath;
}
}

if (!args[2]) {
const hostProjectModuleName = this.getEmbedConfigForKey(
"hostProjectModuleName",
platform
);
if (hostProjectModuleName) {
args[2] = hostProjectModuleName;
}
}

console.log(args);

if (args.length < 2) {
return false;
}

return true;
}

private getEmbedConfigForKey(key: string, platform: string) {
// get the embed.<platform>.<key> value, or fallback to embed.<key> value
return this.$projectConfigService.getValue(
`embed.${platform}.${key}`,
this.$projectConfigService.getValue(`embed.${key}`)
);
}
}

injector.registerCommand("embed", EmbedCommand);
11 changes: 6 additions & 5 deletions lib/commands/native-add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ICommandParameter, ICommand } from "../common/definitions/commands";
import { IErrors } from "../common/declarations";
import * as path from "path";
import { injector } from "../common/yok";
import { capitalizeFirstLetter } from "../common/utils";
import { EOL } from "os";

export class NativeAddCommand implements ICommand {
Expand Down Expand Up @@ -142,7 +143,9 @@ class ${classSimpleName} {
fs.mkdirSync(packagePath, { recursive: true });
fs.writeFileSync(filePath, fileContent);
this.$logger.info(
`${extension} file '${filePath}' generated successfully.`
`${capitalizeFirstLetter(
extension
)} file '${filePath}' generated successfully.`
);
}

Expand All @@ -161,15 +164,12 @@ class ${classSimpleName} {

if (useKotlin === "false") {
this.$errors.failWithHelp(
"The useKotlin property is set to false. Stopping processing."
"The useKotlin property is set to false. Stopping processing. Kotlin must be enabled in gradle.properties to use."
);
return false;
}

if (useKotlin === "true") {
this.$logger.warn(
'gradle.properties already contains "useKotlin=true".'
);
return true;
}
} else {
Expand Down Expand Up @@ -367,6 +367,7 @@ export class NativeAddSwiftCommand extends NativeAddSingleCommand {

const content = `import Foundation;
import os;
@objc class ${className}: NSObject {
@objc func logMessage() {
os_log("Hello from ${className} class!")
Expand Down
25 changes: 16 additions & 9 deletions lib/commands/prepare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import { injector } from "../common/yok";

export class PrepareCommand
extends ValidatePlatformCommandBase
implements ICommand {
implements ICommand
{
public allowedParameters = [this.$platformCommandParameter];

public dashedOptions = {
Expand All @@ -21,17 +22,23 @@ export class PrepareCommand
hasSensitiveValue: false,
},
hmr: { type: OptionType.Boolean, default: false, hasSensitiveValue: false },

whatever: {
type: OptionType.Boolean,
default: false,
hasSensitiveValue: false,
},
};

constructor(
$options: IOptions,
private $prepareController: PrepareController,
$platformValidationService: IPlatformValidationService,
$projectData: IProjectData,
private $platformCommandParameter: ICommandParameter,
$platformsDataService: IPlatformsDataService,
private $prepareDataService: PrepareDataService,
private $migrateController: IMigrateController
public $options: IOptions,
public $prepareController: PrepareController,
public $platformValidationService: IPlatformValidationService,
public $projectData: IProjectData,
public $platformCommandParameter: ICommandParameter,
public $platformsDataService: IPlatformsDataService,
public $prepareDataService: PrepareDataService,
public $migrateController: IMigrateController
) {
super(
$options,
Expand Down
8 changes: 8 additions & 0 deletions lib/common/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,12 @@ export class Utils implements IUtils {
return timeout * 1000;
}
}

export function capitalizeFirstLetter(value: string) {
if (!value) {
return "";
}
return value.charAt(0).toUpperCase() + value.slice(1);
}

injector.register("utils", Utils);
34 changes: 21 additions & 13 deletions lib/controllers/platform-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,13 @@ export class PlatformController implements IPlatformController {

this.$logger.trace("Determined package to install is", packageToInstall);

const installedPlatformVersion = await this.$addPlatformService.addPlatformSafe(
projectData,
platformData,
packageToInstall,
addPlatformData
);
const installedPlatformVersion =
await this.$addPlatformService.addPlatformSafe(
projectData,
platformData,
packageToInstall,
addPlatformData
);

this.$fs.ensureDirectoryExists(
path.join(projectData.platformsDir, platform)
Expand All @@ -80,7 +81,8 @@ export class PlatformController implements IPlatformController {
);
const commentHeader = "# App configuration";
const appPath = projectData.getAppDirectoryRelativePath();
const appResourcesPath = projectData.getAppResourcesRelativeDirectoryPath();
const appResourcesPath =
projectData.getAppResourcesRelativeDirectoryPath();

let gradlePropertiesContents = "";
if (this.$fs.exists(gradlePropertiesPath)) {
Expand Down Expand Up @@ -114,6 +116,12 @@ export class PlatformController implements IPlatformController {
addPlatformData: IAddPlatformData,
projectData?: IProjectData
): Promise<void> {
if (addPlatformData.hostProjectPath) {
this.$logger.trace(
"Not adding platform because --hostProjectPath is provided."
);
return;
}
const [platform] = addPlatformData.platform.toLowerCase().split("@");

projectData ??= this.$projectDataService.getProjectData(
Expand Down Expand Up @@ -161,9 +169,10 @@ export class PlatformController implements IPlatformController {

if (!desiredRuntimePackage.version) {
// if no version is explicitly added, then we use the latest
desiredRuntimePackage.version = await this.$packageInstallationManager.getLatestCompatibleVersion(
desiredRuntimePackage.name
);
desiredRuntimePackage.version =
await this.$packageInstallationManager.getLatestCompatibleVersion(
desiredRuntimePackage.name
);
}
// const currentPlatformData = this.$projectDataService.getNSValue(projectData.projectDir, platformData.frameworkPackageName);
// version = (currentPlatformData && currentPlatformData.version) ||
Expand All @@ -186,9 +195,8 @@ export class PlatformController implements IPlatformController {

const shouldAddNativePlatform =
!nativePrepare || !nativePrepare.skipNativePrepare;
const prepareInfo = this.$projectChangesService.getPrepareInfo(
platformData
);
const prepareInfo =
this.$projectChangesService.getPrepareInfo(platformData);
const requiresNativePlatformAdd =
prepareInfo &&
prepareInfo.nativePlatformStatus ===
Expand Down
11 changes: 8 additions & 3 deletions lib/controllers/prepare-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
TrackActionNames,
WEBPACK_COMPILATION_COMPLETE,
} from "../constants";
import { IWatchIgnoreListService } from "../declarations";
import { IOptions, IWatchIgnoreListService } from "../declarations";
import {
INodeModulesDependenciesBuilder,
IPlatformController,
Expand Down Expand Up @@ -59,6 +59,7 @@ export class PrepareController extends EventEmitter {
public $hooksService: IHooksService,
private $fs: IFileSystem,
private $logger: ILogger,
private $options: IOptions,
private $mobileHelper: Mobile.IMobileHelper,
private $nodeModulesDependenciesBuilder: INodeModulesDependenciesBuilder,
private $platformsDataService: IPlatformsDataService,
Expand Down Expand Up @@ -138,6 +139,7 @@ export class PrepareController extends EventEmitter {
prepareData,
projectData
);

await this.trackRuntimeVersion(prepareData.platform, projectData);

this.$logger.info("Preparing project...");
Expand Down Expand Up @@ -426,6 +428,9 @@ export class PrepareController extends EventEmitter {
return patterns;
}

/**
* TODO: move this logic to the webpack side of things - WIP and deprecate here with a webpack version check...
*/
public async writeRuntimePackageJson(
projectData: IProjectData,
platformData: IPlatformData
Expand Down Expand Up @@ -478,9 +483,9 @@ export class PrepareController extends EventEmitter {
} else {
packagePath = path.join(
platformData.projectRoot,
"app",
this.$options.hostProjectModuleName,
"src",
"main",
this.$options.hostProjectPath ? "nativescript" : "main",
"assets",
"app",
"package.json"
Expand Down
4 changes: 4 additions & 0 deletions lib/data/build-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export class IOSBuildData extends BuildData implements IiOSBuildData {
public mobileProvisionData: any;
public buildForAppStore: boolean;
public iCloudContainerEnvironment: string;
public hostProjectPath: string;

constructor(projectDir: string, platform: string, data: any) {
super(projectDir, platform, data);
Expand All @@ -40,6 +41,7 @@ export class IOSBuildData extends BuildData implements IiOSBuildData {
this.mobileProvisionData = data.mobileProvisionData;
this.buildForAppStore = data.buildForAppStore;
this.iCloudContainerEnvironment = data.iCloudContainerEnvironment;
this.hostProjectPath = data.hostProjectPath;
}
}

Expand All @@ -51,6 +53,7 @@ export class AndroidBuildData extends BuildData {
public androidBundle: boolean;
public gradlePath: string;
public gradleArgs: string;
public hostProjectPath: string;

constructor(projectDir: string, platform: string, data: any) {
super(projectDir, platform, data);
Expand All @@ -62,5 +65,6 @@ export class AndroidBuildData extends BuildData {
this.androidBundle = data.androidBundle || data.aab;
this.gradlePath = data.gradlePath;
this.gradleArgs = data.gradleArgs;
this.hostProjectPath = data.hostProjectPath;
}
}
Loading

0 comments on commit 42177c3

Please sign in to comment.