Skip to content

Commit 42177c3

Browse files
authored
feat: ability to embed NativeScript into host Swift and Kotlin projects (#5803)
1 parent 9e9773f commit 42177c3

38 files changed

+2876
-1145
lines changed

lib/bootstrap.ts

+2
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,8 @@ injector.requireCommand("build|vision", "./commands/build");
199199
injector.requireCommand("build|visionos", "./commands/build");
200200
injector.requireCommand("deploy", "./commands/deploy");
201201

202+
injector.requireCommand("embed", "./commands/embedding/embed");
203+
202204
injector.require("testExecutionService", "./services/test-execution-service");
203205
injector.requireCommand("dev-test|android", "./commands/test");
204206
injector.requireCommand("dev-test|ios", "./commands/test");

lib/commands/add-platform.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import { injector } from "../common/yok";
1212

1313
export class AddPlatformCommand
1414
extends ValidatePlatformCommandBase
15-
implements ICommand {
15+
implements ICommand
16+
{
1617
public allowedParameters: ICommandParameter[] = [];
1718

1819
constructor(

lib/commands/embedding/embed.ts

+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import { ICommand, ICommandParameter } from "../../common/definitions/commands";
2+
import { injector } from "../../common/yok";
3+
import { PrepareCommand } from "../prepare";
4+
import { PrepareController } from "../../controllers/prepare-controller";
5+
import { IOptions, IPlatformValidationService } from "../../declarations";
6+
import { IProjectConfigService, IProjectData } from "../../definitions/project";
7+
import { IPlatformsDataService } from "../../definitions/platform";
8+
import { PrepareDataService } from "../../services/prepare-data-service";
9+
import { IMigrateController } from "../../definitions/migrate";
10+
import { resolve } from "path";
11+
import { IFileSystem } from "../../common/declarations";
12+
import { color } from "../../color";
13+
14+
export class EmbedCommand extends PrepareCommand implements ICommand {
15+
constructor(
16+
public $options: IOptions,
17+
public $prepareController: PrepareController,
18+
public $platformValidationService: IPlatformValidationService,
19+
public $projectData: IProjectData,
20+
public $platformCommandParameter: ICommandParameter,
21+
public $platformsDataService: IPlatformsDataService,
22+
public $prepareDataService: PrepareDataService,
23+
public $migrateController: IMigrateController,
24+
25+
private $logger: ILogger,
26+
private $fs: IFileSystem,
27+
private $projectConfigService: IProjectConfigService
28+
) {
29+
super(
30+
$options,
31+
$prepareController,
32+
$platformValidationService,
33+
$projectData,
34+
$platformCommandParameter,
35+
$platformsDataService,
36+
$prepareDataService,
37+
$migrateController
38+
);
39+
}
40+
41+
private resolveHostProjectPath(hostProjectPath: string): string {
42+
if (hostProjectPath.charAt(0) === ".") {
43+
// resolve relative to the project dir
44+
const projectDir = this.$projectData.projectDir;
45+
return resolve(projectDir, hostProjectPath);
46+
}
47+
48+
return resolve(hostProjectPath);
49+
}
50+
51+
public async execute(args: string[]): Promise<void> {
52+
const hostProjectPath = args[1];
53+
const resolvedHostProjectPath =
54+
this.resolveHostProjectPath(hostProjectPath);
55+
56+
if (!this.$fs.exists(resolvedHostProjectPath)) {
57+
this.$logger.error(
58+
`The host project path ${color.yellow(
59+
hostProjectPath
60+
)} (resolved to: ${color.yellow.dim(
61+
resolvedHostProjectPath
62+
)}) does not exist.`
63+
);
64+
return;
65+
}
66+
67+
this.$options["hostProjectPath"] = resolvedHostProjectPath;
68+
if (args.length > 2) {
69+
this.$options["hostProjectModuleName"] = args[2];
70+
}
71+
72+
return super.execute(args);
73+
}
74+
75+
public async canExecute(args: string[]): Promise<boolean> {
76+
const canSuperExecute = await super.canExecute(args);
77+
78+
if (!canSuperExecute) {
79+
return false;
80+
}
81+
82+
// args[0] is the platform
83+
// args[1] is the path to the host project
84+
// args[2] is the host project module name
85+
86+
const platform = args[0].toLowerCase();
87+
88+
// also allow these to be set in the nativescript.config.ts
89+
if (!args[1]) {
90+
const hostProjectPath = this.getEmbedConfigForKey(
91+
"hostProjectPath",
92+
platform
93+
);
94+
if (hostProjectPath) {
95+
args[1] = hostProjectPath;
96+
}
97+
}
98+
99+
if (!args[2]) {
100+
const hostProjectModuleName = this.getEmbedConfigForKey(
101+
"hostProjectModuleName",
102+
platform
103+
);
104+
if (hostProjectModuleName) {
105+
args[2] = hostProjectModuleName;
106+
}
107+
}
108+
109+
console.log(args);
110+
111+
if (args.length < 2) {
112+
return false;
113+
}
114+
115+
return true;
116+
}
117+
118+
private getEmbedConfigForKey(key: string, platform: string) {
119+
// get the embed.<platform>.<key> value, or fallback to embed.<key> value
120+
return this.$projectConfigService.getValue(
121+
`embed.${platform}.${key}`,
122+
this.$projectConfigService.getValue(`embed.${key}`)
123+
);
124+
}
125+
}
126+
127+
injector.registerCommand("embed", EmbedCommand);

lib/commands/native-add.ts

+6-5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { ICommandParameter, ICommand } from "../common/definitions/commands";
44
import { IErrors } from "../common/declarations";
55
import * as path from "path";
66
import { injector } from "../common/yok";
7+
import { capitalizeFirstLetter } from "../common/utils";
78
import { EOL } from "os";
89

910
export class NativeAddCommand implements ICommand {
@@ -142,7 +143,9 @@ class ${classSimpleName} {
142143
fs.mkdirSync(packagePath, { recursive: true });
143144
fs.writeFileSync(filePath, fileContent);
144145
this.$logger.info(
145-
`${extension} file '${filePath}' generated successfully.`
146+
`${capitalizeFirstLetter(
147+
extension
148+
)} file '${filePath}' generated successfully.`
146149
);
147150
}
148151

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

162165
if (useKotlin === "false") {
163166
this.$errors.failWithHelp(
164-
"The useKotlin property is set to false. Stopping processing."
167+
"The useKotlin property is set to false. Stopping processing. Kotlin must be enabled in gradle.properties to use."
165168
);
166169
return false;
167170
}
168171

169172
if (useKotlin === "true") {
170-
this.$logger.warn(
171-
'gradle.properties already contains "useKotlin=true".'
172-
);
173173
return true;
174174
}
175175
} else {
@@ -367,6 +367,7 @@ export class NativeAddSwiftCommand extends NativeAddSingleCommand {
367367

368368
const content = `import Foundation;
369369
import os;
370+
370371
@objc class ${className}: NSObject {
371372
@objc func logMessage() {
372373
os_log("Hello from ${className} class!")

lib/commands/prepare.ts

+16-9
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ import { injector } from "../common/yok";
1111

1212
export class PrepareCommand
1313
extends ValidatePlatformCommandBase
14-
implements ICommand {
14+
implements ICommand
15+
{
1516
public allowedParameters = [this.$platformCommandParameter];
1617

1718
public dashedOptions = {
@@ -21,17 +22,23 @@ export class PrepareCommand
2122
hasSensitiveValue: false,
2223
},
2324
hmr: { type: OptionType.Boolean, default: false, hasSensitiveValue: false },
25+
26+
whatever: {
27+
type: OptionType.Boolean,
28+
default: false,
29+
hasSensitiveValue: false,
30+
},
2431
};
2532

2633
constructor(
27-
$options: IOptions,
28-
private $prepareController: PrepareController,
29-
$platformValidationService: IPlatformValidationService,
30-
$projectData: IProjectData,
31-
private $platformCommandParameter: ICommandParameter,
32-
$platformsDataService: IPlatformsDataService,
33-
private $prepareDataService: PrepareDataService,
34-
private $migrateController: IMigrateController
34+
public $options: IOptions,
35+
public $prepareController: PrepareController,
36+
public $platformValidationService: IPlatformValidationService,
37+
public $projectData: IProjectData,
38+
public $platformCommandParameter: ICommandParameter,
39+
public $platformsDataService: IPlatformsDataService,
40+
public $prepareDataService: PrepareDataService,
41+
public $migrateController: IMigrateController
3542
) {
3643
super(
3744
$options,

lib/common/utils.ts

+8
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,12 @@ export class Utils implements IUtils {
2828
return timeout * 1000;
2929
}
3030
}
31+
32+
export function capitalizeFirstLetter(value: string) {
33+
if (!value) {
34+
return "";
35+
}
36+
return value.charAt(0).toUpperCase() + value.slice(1);
37+
}
38+
3139
injector.register("utils", Utils);

lib/controllers/platform-controller.ts

+21-13
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,13 @@ export class PlatformController implements IPlatformController {
6262

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

65-
const installedPlatformVersion = await this.$addPlatformService.addPlatformSafe(
66-
projectData,
67-
platformData,
68-
packageToInstall,
69-
addPlatformData
70-
);
65+
const installedPlatformVersion =
66+
await this.$addPlatformService.addPlatformSafe(
67+
projectData,
68+
platformData,
69+
packageToInstall,
70+
addPlatformData
71+
);
7172

7273
this.$fs.ensureDirectoryExists(
7374
path.join(projectData.platformsDir, platform)
@@ -80,7 +81,8 @@ export class PlatformController implements IPlatformController {
8081
);
8182
const commentHeader = "# App configuration";
8283
const appPath = projectData.getAppDirectoryRelativePath();
83-
const appResourcesPath = projectData.getAppResourcesRelativeDirectoryPath();
84+
const appResourcesPath =
85+
projectData.getAppResourcesRelativeDirectoryPath();
8486

8587
let gradlePropertiesContents = "";
8688
if (this.$fs.exists(gradlePropertiesPath)) {
@@ -114,6 +116,12 @@ export class PlatformController implements IPlatformController {
114116
addPlatformData: IAddPlatformData,
115117
projectData?: IProjectData
116118
): Promise<void> {
119+
if (addPlatformData.hostProjectPath) {
120+
this.$logger.trace(
121+
"Not adding platform because --hostProjectPath is provided."
122+
);
123+
return;
124+
}
117125
const [platform] = addPlatformData.platform.toLowerCase().split("@");
118126

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

162170
if (!desiredRuntimePackage.version) {
163171
// if no version is explicitly added, then we use the latest
164-
desiredRuntimePackage.version = await this.$packageInstallationManager.getLatestCompatibleVersion(
165-
desiredRuntimePackage.name
166-
);
172+
desiredRuntimePackage.version =
173+
await this.$packageInstallationManager.getLatestCompatibleVersion(
174+
desiredRuntimePackage.name
175+
);
167176
}
168177
// const currentPlatformData = this.$projectDataService.getNSValue(projectData.projectDir, platformData.frameworkPackageName);
169178
// version = (currentPlatformData && currentPlatformData.version) ||
@@ -186,9 +195,8 @@ export class PlatformController implements IPlatformController {
186195

187196
const shouldAddNativePlatform =
188197
!nativePrepare || !nativePrepare.skipNativePrepare;
189-
const prepareInfo = this.$projectChangesService.getPrepareInfo(
190-
platformData
191-
);
198+
const prepareInfo =
199+
this.$projectChangesService.getPrepareInfo(platformData);
192200
const requiresNativePlatformAdd =
193201
prepareInfo &&
194202
prepareInfo.nativePlatformStatus ===

lib/controllers/prepare-controller.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
TrackActionNames,
2323
WEBPACK_COMPILATION_COMPLETE,
2424
} from "../constants";
25-
import { IWatchIgnoreListService } from "../declarations";
25+
import { IOptions, IWatchIgnoreListService } from "../declarations";
2626
import {
2727
INodeModulesDependenciesBuilder,
2828
IPlatformController,
@@ -59,6 +59,7 @@ export class PrepareController extends EventEmitter {
5959
public $hooksService: IHooksService,
6060
private $fs: IFileSystem,
6161
private $logger: ILogger,
62+
private $options: IOptions,
6263
private $mobileHelper: Mobile.IMobileHelper,
6364
private $nodeModulesDependenciesBuilder: INodeModulesDependenciesBuilder,
6465
private $platformsDataService: IPlatformsDataService,
@@ -138,6 +139,7 @@ export class PrepareController extends EventEmitter {
138139
prepareData,
139140
projectData
140141
);
142+
141143
await this.trackRuntimeVersion(prepareData.platform, projectData);
142144

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

431+
/**
432+
* TODO: move this logic to the webpack side of things - WIP and deprecate here with a webpack version check...
433+
*/
429434
public async writeRuntimePackageJson(
430435
projectData: IProjectData,
431436
platformData: IPlatformData
@@ -478,9 +483,9 @@ export class PrepareController extends EventEmitter {
478483
} else {
479484
packagePath = path.join(
480485
platformData.projectRoot,
481-
"app",
486+
this.$options.hostProjectModuleName,
482487
"src",
483-
"main",
488+
this.$options.hostProjectPath ? "nativescript" : "main",
484489
"assets",
485490
"app",
486491
"package.json"

lib/data/build-data.ts

+4
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export class IOSBuildData extends BuildData implements IiOSBuildData {
3131
public mobileProvisionData: any;
3232
public buildForAppStore: boolean;
3333
public iCloudContainerEnvironment: string;
34+
public hostProjectPath: string;
3435

3536
constructor(projectDir: string, platform: string, data: any) {
3637
super(projectDir, platform, data);
@@ -40,6 +41,7 @@ export class IOSBuildData extends BuildData implements IiOSBuildData {
4041
this.mobileProvisionData = data.mobileProvisionData;
4142
this.buildForAppStore = data.buildForAppStore;
4243
this.iCloudContainerEnvironment = data.iCloudContainerEnvironment;
44+
this.hostProjectPath = data.hostProjectPath;
4345
}
4446
}
4547

@@ -51,6 +53,7 @@ export class AndroidBuildData extends BuildData {
5153
public androidBundle: boolean;
5254
public gradlePath: string;
5355
public gradleArgs: string;
56+
public hostProjectPath: string;
5457

5558
constructor(projectDir: string, platform: string, data: any) {
5659
super(projectDir, platform, data);
@@ -62,5 +65,6 @@ export class AndroidBuildData extends BuildData {
6265
this.androidBundle = data.androidBundle || data.aab;
6366
this.gradlePath = data.gradlePath;
6467
this.gradleArgs = data.gradleArgs;
68+
this.hostProjectPath = data.hostProjectPath;
6569
}
6670
}

0 commit comments

Comments
 (0)