nitrogen 0.35.0 → 0.35.1-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/lib/autolinking/android/createHybridObjectInitializer.js +27 -18
  2. package/lib/autolinking/ios/createHybridObjectInitializer.js +28 -19
  3. package/lib/config/NitroConfig.d.ts +4 -1
  4. package/lib/config/NitroConfig.js +32 -0
  5. package/lib/config/NitroUserConfig.d.ts +133 -2
  6. package/lib/config/NitroUserConfig.js +95 -7
  7. package/lib/config/getConfig.js +29 -0
  8. package/lib/syntax/kotlin/FbjniHybridObject.js +2 -2
  9. package/lib/syntax/kotlin/KotlinCxxBridgedType.d.ts +2 -2
  10. package/lib/syntax/kotlin/KotlinCxxBridgedType.js +14 -16
  11. package/lib/syntax/kotlin/KotlinEnum.js +1 -1
  12. package/lib/syntax/kotlin/KotlinFunction.js +2 -2
  13. package/lib/syntax/kotlin/KotlinHybridObjectRegistration.js +2 -2
  14. package/lib/syntax/kotlin/KotlinStruct.js +1 -1
  15. package/lib/syntax/kotlin/KotlinVariant.js +2 -2
  16. package/lib/views/kotlin/KotlinHybridViewManager.js +4 -4
  17. package/lib/views/swift/SwiftHybridViewManager.js +3 -4
  18. package/package.json +2 -2
  19. package/src/autolinking/android/createHybridObjectInitializer.ts +30 -18
  20. package/src/autolinking/ios/createHybridObjectInitializer.ts +31 -19
  21. package/src/config/NitroConfig.ts +49 -1
  22. package/src/config/NitroUserConfig.ts +134 -10
  23. package/src/config/getConfig.ts +38 -0
  24. package/src/syntax/kotlin/FbjniHybridObject.ts +2 -2
  25. package/src/syntax/kotlin/KotlinCxxBridgedType.ts +16 -16
  26. package/src/syntax/kotlin/KotlinEnum.ts +1 -1
  27. package/src/syntax/kotlin/KotlinFunction.ts +2 -2
  28. package/src/syntax/kotlin/KotlinHybridObjectRegistration.ts +2 -2
  29. package/src/syntax/kotlin/KotlinStruct.ts +1 -1
  30. package/src/syntax/kotlin/KotlinVariant.ts +2 -2
  31. package/src/views/kotlin/KotlinHybridViewManager.ts +6 -4
  32. package/src/views/swift/SwiftHybridViewManager.ts +3 -4
@@ -19,25 +19,34 @@ export function createHybridObjectIntializer() {
19
19
  const cppRegistrations = [];
20
20
  const cppDefinitions = [];
21
21
  for (const hybridObjectName of Object.keys(autolinkedHybridObjects)) {
22
- const config = autolinkedHybridObjects[hybridObjectName];
23
- if (config?.cpp != null) {
24
- // Autolink a C++ HybridObject!
25
- const { cppCode, requiredImports } = createCppHybridObjectRegistration({
26
- hybridObjectName: hybridObjectName,
27
- cppClassName: config.cpp,
28
- });
29
- cppHybridObjectImports.push(...requiredImports);
30
- cppRegistrations.push(cppCode);
22
+ const implementation = NitroConfig.current.getAndroidAutolinkedImplementation(hybridObjectName);
23
+ if (implementation == null) {
24
+ continue;
31
25
  }
32
- if (config?.kotlin != null) {
33
- // Autolink a Kotlin HybridObject through JNI/C++!
34
- const { cppCode, cppDefinition, requiredImports } = createJNIHybridObjectRegistration({
35
- hybridObjectName: hybridObjectName,
36
- jniClassName: config.kotlin,
37
- });
38
- cppHybridObjectImports.push(...requiredImports);
39
- cppDefinitions.push(cppDefinition);
40
- cppRegistrations.push(cppCode);
26
+ switch (implementation.language) {
27
+ case 'cpp': {
28
+ // Autolink a C++ HybridObject!
29
+ const { cppCode, requiredImports } = createCppHybridObjectRegistration({
30
+ hybridObjectName: hybridObjectName,
31
+ cppClassName: implementation.implementationClassName,
32
+ });
33
+ cppHybridObjectImports.push(...requiredImports);
34
+ cppRegistrations.push(cppCode);
35
+ break;
36
+ }
37
+ case 'kotlin': {
38
+ // Autolink a Kotlin HybridObject through JNI/C++!
39
+ const { cppCode, cppDefinition, requiredImports } = createJNIHybridObjectRegistration({
40
+ hybridObjectName: hybridObjectName,
41
+ jniClassName: implementation.implementationClassName,
42
+ });
43
+ cppHybridObjectImports.push(...requiredImports);
44
+ cppDefinitions.push(cppDefinition);
45
+ cppRegistrations.push(cppCode);
46
+ break;
47
+ }
48
+ default:
49
+ throw new Error(`The HybridObject "${hybridObjectName}" cannot be autolinked on Android - Language "${implementation.language}" is not supported on Android!`);
41
50
  }
42
51
  }
43
52
  const buildingWithDefinition = getBuildingWithGeneratedCmakeDefinition();
@@ -15,26 +15,35 @@ export function createHybridObjectIntializer() {
15
15
  const cppImports = [];
16
16
  let containsSwiftObjects = false;
17
17
  for (const hybridObjectName of Object.keys(autolinkedHybridObjects)) {
18
- const config = autolinkedHybridObjects[hybridObjectName];
19
- if (config?.cpp != null) {
20
- // Autolink a C++ HybridObject!
21
- const { cppCode, requiredImports } = createCppHybridObjectRegistration({
22
- hybridObjectName: hybridObjectName,
23
- cppClassName: config.cpp,
24
- });
25
- cppImports.push(...requiredImports);
26
- cppRegistrations.push(cppCode);
18
+ const implementation = NitroConfig.current.getIosAutolinkedImplementation(hybridObjectName);
19
+ if (implementation == null) {
20
+ continue;
27
21
  }
28
- if (config?.swift != null) {
29
- // Autolink a Swift HybridObject!
30
- containsSwiftObjects = true;
31
- const { cppCode, requiredImports, swiftRegistrationMethods } = createSwiftHybridObjectRegistration({
32
- hybridObjectName: hybridObjectName,
33
- swiftClassName: config.swift,
34
- });
35
- cppImports.push(...requiredImports);
36
- cppRegistrations.push(cppCode);
37
- swiftRegistrations.push(swiftRegistrationMethods);
22
+ switch (implementation.language) {
23
+ case 'cpp': {
24
+ // Autolink a C++ HybridObject!
25
+ const { cppCode, requiredImports } = createCppHybridObjectRegistration({
26
+ hybridObjectName: hybridObjectName,
27
+ cppClassName: implementation.implementationClassName,
28
+ });
29
+ cppImports.push(...requiredImports);
30
+ cppRegistrations.push(cppCode);
31
+ break;
32
+ }
33
+ case 'swift': {
34
+ // Autolink a Swift HybridObject!
35
+ containsSwiftObjects = true;
36
+ const { cppCode, requiredImports, swiftRegistrationMethods } = createSwiftHybridObjectRegistration({
37
+ hybridObjectName: hybridObjectName,
38
+ swiftClassName: implementation.implementationClassName,
39
+ });
40
+ cppImports.push(...requiredImports);
41
+ cppRegistrations.push(cppCode);
42
+ swiftRegistrations.push(swiftRegistrationMethods);
43
+ break;
44
+ }
45
+ default:
46
+ throw new Error(`The HybridObject "${hybridObjectName}" cannot be autolinked on iOS - Language "${implementation.language}" is not supported on iOS!`);
38
47
  }
39
48
  }
40
49
  if (cppRegistrations.length === 0) {
@@ -1,4 +1,4 @@
1
- import type { NitroUserConfig } from './NitroUserConfig.js';
1
+ import type { AutolinkingAndroidImplementation, AutolinkingIOSImplementation, AutolinkingPlatformImplementation, NitroUserConfig } from './NitroUserConfig.js';
2
2
  /**
3
3
  * Represents the properly parsed `nitro.json` config of the current executing directory.
4
4
  */
@@ -40,6 +40,9 @@ export declare class NitroConfig {
40
40
  * Those will be generated and default-constructed.
41
41
  */
42
42
  getAutolinkedHybridObjects(): NitroUserConfig['autolinking'];
43
+ getPlatformAutolinkedImplementation(hybridObjectName: string, platform: string): AutolinkingPlatformImplementation | undefined;
44
+ getIosAutolinkedImplementation(hybridObjectName: string): AutolinkingIOSImplementation | undefined;
45
+ getAndroidAutolinkedImplementation(hybridObjectName: string): AutolinkingAndroidImplementation | undefined;
43
46
  /**
44
47
  * Get the paths that will be ignored when loading the TypeScript project.
45
48
  * In most cases, this just contains `node_modules/`.
@@ -88,6 +88,38 @@ export class NitroConfig {
88
88
  getAutolinkedHybridObjects() {
89
89
  return this.config.autolinking;
90
90
  }
91
+ getPlatformAutolinkedImplementation(hybridObjectName, platform) {
92
+ const objectConfig = this.config.autolinking[hybridObjectName];
93
+ if (objectConfig == null) {
94
+ return undefined;
95
+ }
96
+ // "all" applies to every platform and should automatically include
97
+ // future platforms without having to expand explicit key lists here.
98
+ if (objectConfig.all != null) {
99
+ return objectConfig.all;
100
+ }
101
+ return objectConfig[platform];
102
+ }
103
+ getIosAutolinkedImplementation(hybridObjectName) {
104
+ const objectConfig = this.config.autolinking[hybridObjectName];
105
+ if (objectConfig == null) {
106
+ return undefined;
107
+ }
108
+ if (objectConfig.all != null) {
109
+ return objectConfig.all;
110
+ }
111
+ return objectConfig.ios;
112
+ }
113
+ getAndroidAutolinkedImplementation(hybridObjectName) {
114
+ const objectConfig = this.config.autolinking[hybridObjectName];
115
+ if (objectConfig == null) {
116
+ return undefined;
117
+ }
118
+ if (objectConfig.all != null) {
119
+ return objectConfig.all;
120
+ }
121
+ return objectConfig.android;
122
+ }
91
123
  /**
92
124
  * Get the paths that will be ignored when loading the TypeScript project.
93
125
  * In most cases, this just contains `node_modules/`.
@@ -1,4 +1,87 @@
1
1
  import { z } from 'zod';
2
+ declare const autolinkingAllImplementationSchema: z.ZodObject<{
3
+ language: z.ZodLiteral<"cpp">;
4
+ implementationClassName: z.ZodString;
5
+ }, z.core.$strip>;
6
+ declare const autolinkingIOSImplementationSchema: z.ZodObject<{
7
+ language: z.ZodEnum<{
8
+ cpp: "cpp";
9
+ swift: "swift";
10
+ }>;
11
+ implementationClassName: z.ZodString;
12
+ }, z.core.$strip>;
13
+ declare const autolinkingAndroidImplementationSchema: z.ZodObject<{
14
+ language: z.ZodEnum<{
15
+ cpp: "cpp";
16
+ kotlin: "kotlin";
17
+ }>;
18
+ implementationClassName: z.ZodString;
19
+ }, z.core.$strip>;
20
+ declare const autolinkingPlatformImplementationSchema: z.ZodObject<{
21
+ language: z.ZodEnum<{
22
+ cpp: "cpp";
23
+ swift: "swift";
24
+ kotlin: "kotlin";
25
+ }>;
26
+ implementationClassName: z.ZodString;
27
+ }, z.core.$strip>;
28
+ declare const autolinkingHybridObjectSchema: z.ZodUnion<readonly [z.ZodObject<{
29
+ all: z.ZodOptional<z.ZodObject<{
30
+ language: z.ZodLiteral<"cpp">;
31
+ implementationClassName: z.ZodString;
32
+ }, z.core.$strip>>;
33
+ ios: z.ZodOptional<z.ZodObject<{
34
+ language: z.ZodEnum<{
35
+ cpp: "cpp";
36
+ swift: "swift";
37
+ }>;
38
+ implementationClassName: z.ZodString;
39
+ }, z.core.$strip>>;
40
+ android: z.ZodOptional<z.ZodObject<{
41
+ language: z.ZodEnum<{
42
+ cpp: "cpp";
43
+ kotlin: "kotlin";
44
+ }>;
45
+ implementationClassName: z.ZodString;
46
+ }, z.core.$strip>>;
47
+ }, z.core.$catchall<z.ZodObject<{
48
+ language: z.ZodEnum<{
49
+ cpp: "cpp";
50
+ swift: "swift";
51
+ kotlin: "kotlin";
52
+ }>;
53
+ implementationClassName: z.ZodString;
54
+ }, z.core.$strip>>>, z.ZodPipe<z.ZodObject<{
55
+ cpp: z.ZodOptional<z.ZodString>;
56
+ swift: z.ZodOptional<z.ZodString>;
57
+ kotlin: z.ZodOptional<z.ZodString>;
58
+ }, z.core.$strict>, z.ZodTransform<{
59
+ [x: string]: {
60
+ language: "cpp" | "swift" | "kotlin";
61
+ implementationClassName: string;
62
+ };
63
+ all?: {
64
+ language: "cpp";
65
+ implementationClassName: string;
66
+ } | undefined;
67
+ ios?: {
68
+ language: "cpp" | "swift";
69
+ implementationClassName: string;
70
+ } | undefined;
71
+ android?: {
72
+ language: "cpp" | "kotlin";
73
+ implementationClassName: string;
74
+ } | undefined;
75
+ }, {
76
+ cpp?: string | undefined;
77
+ swift?: string | undefined;
78
+ kotlin?: string | undefined;
79
+ }>>]>;
80
+ export type AutolinkingAllImplementation = z.infer<typeof autolinkingAllImplementationSchema>;
81
+ export type AutolinkingIOSImplementation = z.infer<typeof autolinkingIOSImplementationSchema>;
82
+ export type AutolinkingAndroidImplementation = z.infer<typeof autolinkingAndroidImplementationSchema>;
83
+ export type AutolinkingPlatformImplementation = z.infer<typeof autolinkingPlatformImplementationSchema>;
84
+ export type AutolinkingHybridObject = z.infer<typeof autolinkingHybridObjectSchema>;
2
85
  export declare const NitroUserConfigSchema: z.ZodObject<{
3
86
  cxxNamespace: z.ZodArray<z.ZodString>;
4
87
  ios: z.ZodObject<{
@@ -8,11 +91,58 @@ export declare const NitroUserConfigSchema: z.ZodObject<{
8
91
  androidNamespace: z.ZodArray<z.ZodString>;
9
92
  androidCxxLibName: z.ZodString;
10
93
  }, z.core.$strip>;
11
- autolinking: z.ZodRecord<z.ZodString, z.ZodObject<{
94
+ autolinking: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodObject<{
95
+ all: z.ZodOptional<z.ZodObject<{
96
+ language: z.ZodLiteral<"cpp">;
97
+ implementationClassName: z.ZodString;
98
+ }, z.core.$strip>>;
99
+ ios: z.ZodOptional<z.ZodObject<{
100
+ language: z.ZodEnum<{
101
+ cpp: "cpp";
102
+ swift: "swift";
103
+ }>;
104
+ implementationClassName: z.ZodString;
105
+ }, z.core.$strip>>;
106
+ android: z.ZodOptional<z.ZodObject<{
107
+ language: z.ZodEnum<{
108
+ cpp: "cpp";
109
+ kotlin: "kotlin";
110
+ }>;
111
+ implementationClassName: z.ZodString;
112
+ }, z.core.$strip>>;
113
+ }, z.core.$catchall<z.ZodObject<{
114
+ language: z.ZodEnum<{
115
+ cpp: "cpp";
116
+ swift: "swift";
117
+ kotlin: "kotlin";
118
+ }>;
119
+ implementationClassName: z.ZodString;
120
+ }, z.core.$strip>>>, z.ZodPipe<z.ZodObject<{
12
121
  cpp: z.ZodOptional<z.ZodString>;
13
122
  swift: z.ZodOptional<z.ZodString>;
14
123
  kotlin: z.ZodOptional<z.ZodString>;
15
- }, z.core.$strip>>;
124
+ }, z.core.$strict>, z.ZodTransform<{
125
+ [x: string]: {
126
+ language: "cpp" | "swift" | "kotlin";
127
+ implementationClassName: string;
128
+ };
129
+ all?: {
130
+ language: "cpp";
131
+ implementationClassName: string;
132
+ } | undefined;
133
+ ios?: {
134
+ language: "cpp" | "swift";
135
+ implementationClassName: string;
136
+ } | undefined;
137
+ android?: {
138
+ language: "cpp" | "kotlin";
139
+ implementationClassName: string;
140
+ } | undefined;
141
+ }, {
142
+ cpp?: string | undefined;
143
+ swift?: string | undefined;
144
+ kotlin?: string | undefined;
145
+ }>>]>>;
16
146
  ignorePaths: z.ZodOptional<z.ZodArray<z.ZodString>>;
17
147
  gitAttributesGeneratedFlag: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
18
148
  }, z.core.$strip>;
@@ -20,3 +150,4 @@ export declare const NitroUserConfigSchema: z.ZodObject<{
20
150
  * Represents the structure of a `nitro.json` config file.
21
151
  */
22
152
  export type NitroUserConfig = z.infer<typeof NitroUserConfigSchema>;
153
+ export {};
@@ -5,6 +5,98 @@ const isNotReservedKeyword = (val) => !['core', 'nitro', 'NitroModules'].include
5
5
  const isReservedKeywordError = {
6
6
  message: `This value is reserved and cannot be used!`,
7
7
  };
8
+ const autolinkingLanguageSchema = z.enum(['cpp', 'swift', 'kotlin']);
9
+ const autolinkingAllImplementationSchema = z.object({
10
+ language: z.literal('cpp'),
11
+ implementationClassName: z.string(),
12
+ });
13
+ const autolinkingIOSImplementationSchema = z.object({
14
+ language: z.enum(['cpp', 'swift']),
15
+ implementationClassName: z.string(),
16
+ });
17
+ const autolinkingAndroidImplementationSchema = z.object({
18
+ language: z.enum(['cpp', 'kotlin']),
19
+ implementationClassName: z.string(),
20
+ });
21
+ const autolinkingPlatformImplementationSchema = z.object({
22
+ language: autolinkingLanguageSchema,
23
+ implementationClassName: z.string(),
24
+ });
25
+ const autolinkingModernHybridObjectSchema = z
26
+ .object({
27
+ all: autolinkingAllImplementationSchema.optional(),
28
+ ios: autolinkingIOSImplementationSchema.optional(),
29
+ android: autolinkingAndroidImplementationSchema.optional(),
30
+ })
31
+ .catchall(autolinkingPlatformImplementationSchema)
32
+ .superRefine((value, ctx) => {
33
+ const hasAll = value.all != null;
34
+ const platformCount = Object.keys(value).filter((key) => key !== 'all').length;
35
+ if (hasAll && platformCount > 0) {
36
+ ctx.addIssue({
37
+ code: 'custom',
38
+ message: '"all" cannot be combined with platform-specific entries.',
39
+ });
40
+ }
41
+ if (!hasAll && platformCount === 0) {
42
+ ctx.addIssue({
43
+ code: 'custom',
44
+ message: 'Each autolinking entry must declare either "all" or at least one platform.',
45
+ });
46
+ }
47
+ });
48
+ const autolinkingLegacyHybridObjectSchema = z
49
+ .object({
50
+ cpp: z.string().optional(),
51
+ swift: z.string().optional(),
52
+ kotlin: z.string().optional(),
53
+ })
54
+ .strict()
55
+ .superRefine((value, ctx) => {
56
+ const hasCpp = value.cpp != null;
57
+ const hasSwift = value.swift != null;
58
+ const hasKotlin = value.kotlin != null;
59
+ if (!hasCpp && !hasSwift && !hasKotlin) {
60
+ ctx.addIssue({
61
+ code: z.ZodIssueCode.custom,
62
+ message: 'Each autolinking entry must declare at least one implementation.',
63
+ });
64
+ }
65
+ if (hasCpp && (hasSwift || hasKotlin)) {
66
+ ctx.addIssue({
67
+ code: z.ZodIssueCode.custom,
68
+ message: 'Legacy autolinking entries cannot mix "cpp" with "swift"/"kotlin".',
69
+ });
70
+ }
71
+ });
72
+ function normalizeLegacyAutolinkingHybridObject(value) {
73
+ if (value.cpp != null) {
74
+ return {
75
+ all: {
76
+ language: 'cpp',
77
+ implementationClassName: value.cpp,
78
+ },
79
+ };
80
+ }
81
+ const normalized = {};
82
+ if (value.swift != null) {
83
+ normalized.ios = {
84
+ language: 'swift',
85
+ implementationClassName: value.swift,
86
+ };
87
+ }
88
+ if (value.kotlin != null) {
89
+ normalized.android = {
90
+ language: 'kotlin',
91
+ implementationClassName: value.kotlin,
92
+ };
93
+ }
94
+ return normalized;
95
+ }
96
+ const autolinkingHybridObjectSchema = z.union([
97
+ autolinkingModernHybridObjectSchema,
98
+ autolinkingLegacyHybridObjectSchema.transform(normalizeLegacyAutolinkingHybridObject),
99
+ ]);
8
100
  export const NitroUserConfigSchema = z.object({
9
101
  /**
10
102
  * Represents the C++ namespace of the module that will be generated.
@@ -65,14 +157,10 @@ export const NitroUserConfigSchema = z.object({
65
157
  * Configures the code that gets generated for autolinking (registering)
66
158
  * Hybrid Object constructors.
67
159
  *
68
- * Each class listed here needs to have a default constructor/initializer that takes
69
- * zero arguments.
160
+ * Each class listed here needs to have a default constructor/initializer
161
+ * that takes zero arguments.
70
162
  */
71
- autolinking: z.record(z.string(), z.object({
72
- cpp: z.string().optional(),
73
- swift: z.string().optional(),
74
- kotlin: z.string().optional(),
75
- })),
163
+ autolinking: z.record(z.string(), autolinkingHybridObjectSchema),
76
164
  /**
77
165
  * A list of paths relative to the project directory that should be ignored by nitrogen.
78
166
  * Nitrogen will not look for `.nitro.ts` files in these directories.
@@ -2,6 +2,34 @@ import { ZodError } from 'zod';
2
2
  import fs from 'fs';
3
3
  import { NitroUserConfigSchema, } from './NitroUserConfig.js';
4
4
  import chalk from 'chalk';
5
+ function isObject(value) {
6
+ return typeof value === 'object' && value != null && !Array.isArray(value);
7
+ }
8
+ function getLegacyAutolinkingEntries(config) {
9
+ if (!isObject(config))
10
+ return [];
11
+ const autolinking = config.autolinking;
12
+ if (!isObject(autolinking))
13
+ return [];
14
+ const entries = [];
15
+ for (const [hybridObjectName, entry] of Object.entries(autolinking)) {
16
+ if (!isObject(entry))
17
+ continue;
18
+ const hasLegacySyntax = 'cpp' in entry || 'swift' in entry || 'kotlin' in entry;
19
+ if (hasLegacySyntax) {
20
+ entries.push(hybridObjectName);
21
+ }
22
+ }
23
+ return entries;
24
+ }
25
+ // TODO: Remove this once users have migrated to new `nitro.json`
26
+ function logLegacyAutolinkingDeprecation(config) {
27
+ const legacyEntries = getLegacyAutolinkingEntries(config);
28
+ if (legacyEntries.length === 0)
29
+ return;
30
+ console.warn(chalk.yellow(`Warning: nitro.json uses deprecated autolinking syntax ("cpp"/"swift"/"kotlin") for [${legacyEntries.join(', ')}]. ` +
31
+ `Use "all"/"ios"/"android" entries with { language, implementationClassName } instead.`));
32
+ }
5
33
  function readFile(configPath) {
6
34
  try {
7
35
  return fs.readFileSync(configPath, 'utf8');
@@ -52,6 +80,7 @@ function parseConfig(json) {
52
80
  });
53
81
  }
54
82
  try {
83
+ logLegacyAutolinkingDeprecation(object);
55
84
  return NitroUserConfigSchema.parse(object);
56
85
  }
57
86
  catch (error) {
@@ -84,11 +84,11 @@ namespace ${cxxNamespace} {
84
84
  class ${name.JHybridTSpec}: public virtual ${name.HybridTSpec}, public virtual ${cppBaseClass} {
85
85
  public:
86
86
  struct JavaPart: public jni::JavaClass<JavaPart, ${javaPartBaseClass}> {
87
- static auto constexpr kJavaDescriptor = "L${jniClassDescriptor};";
87
+ static constexpr auto kJavaDescriptor = "L${jniClassDescriptor};";
88
88
  std::shared_ptr<${name.JHybridTSpec}> get${name.JHybridTSpec}();
89
89
  };
90
90
  struct CxxPart: public jni::HybridClass<CxxPart, ${cxxPartBaseClass}> {
91
- static auto constexpr kJavaDescriptor = "L${cxxPartJniClassDescriptor};";
91
+ static constexpr auto kJavaDescriptor = "L${cxxPartJniClassDescriptor};";
92
92
  static jni::local_ref<jhybriddata> initHybrid(jni::alias_ref<jhybridobject> jThis);
93
93
  static void registerNatives();
94
94
  using HybridBase::HybridBase;
@@ -8,8 +8,8 @@ export declare class KotlinCxxBridgedType implements BridgedType<'kotlin', 'c++'
8
8
  get hasType(): boolean;
9
9
  get canBePassedByReference(): boolean;
10
10
  get needsSpecialHandling(): boolean;
11
- getRequiredImports(language: Language): SourceImport[];
12
- getExtraFiles(): SourceFile[];
11
+ getRequiredImports(language: Language, visited?: Set<Type>): SourceImport[];
12
+ getExtraFiles(visited?: Set<Type>): SourceFile[];
13
13
  asJniReferenceType(referenceType?: 'alias' | 'local' | 'global'): string;
14
14
  getTypeCode(language: 'kotlin' | 'c++', isBoxed?: boolean): string;
15
15
  parse(parameterName: string, from: 'c++' | 'kotlin', to: 'kotlin' | 'c++', inLanguage: 'kotlin' | 'c++'): string;
@@ -56,7 +56,10 @@ export class KotlinCxxBridgedType {
56
56
  // no special handling needed
57
57
  return false;
58
58
  }
59
- getRequiredImports(language) {
59
+ getRequiredImports(language, visited = new Set()) {
60
+ if (visited.has(this.type))
61
+ return [];
62
+ visited.add(this.type);
60
63
  const imports = this.type.getRequiredImports(language);
61
64
  if (language === 'c++') {
62
65
  // All C++ imports we need for the JNI bridge
@@ -168,17 +171,16 @@ export class KotlinCxxBridgedType {
168
171
  }
169
172
  // Recursively look into referenced types (e.g. the `T` of a `optional<T>`, or `T` of a `T[]`)
170
173
  const referencedTypes = getReferencedTypes(this.type);
171
- referencedTypes.forEach((t) => {
172
- if (t === this.type) {
173
- // break a recursion - we already know this type
174
- return;
175
- }
174
+ for (const t of referencedTypes) {
176
175
  const bridged = new KotlinCxxBridgedType(t);
177
- imports.push(...bridged.getRequiredImports(language));
178
- });
176
+ imports.push(...bridged.getRequiredImports(language, visited));
177
+ }
179
178
  return imports;
180
179
  }
181
- getExtraFiles() {
180
+ getExtraFiles(visited = new Set()) {
181
+ if (visited.has(this.type))
182
+ return [];
183
+ visited.add(this.type);
182
184
  const files = [];
183
185
  switch (this.type.kind) {
184
186
  case 'enum':
@@ -204,14 +206,10 @@ export class KotlinCxxBridgedType {
204
206
  }
205
207
  // Recursively look into referenced types (e.g. the `T` of a `optional<T>`, or `T` of a `T[]`)
206
208
  const referencedTypes = getReferencedTypes(this.type);
207
- referencedTypes.forEach((t) => {
208
- if (t === this.type) {
209
- // break a recursion - we already know this type
210
- return;
211
- }
209
+ for (const t of referencedTypes) {
212
210
  const bridged = new KotlinCxxBridgedType(t);
213
- files.push(...bridged.getExtraFiles());
214
- });
211
+ files.push(...bridged.getExtraFiles(visited));
212
+ }
215
213
  return files;
216
214
  }
217
215
  asJniReferenceType(referenceType = 'alias') {
@@ -44,7 +44,7 @@ namespace ${cxxNamespace} {
44
44
  */
45
45
  struct J${enumType.enumName} final: public jni::JavaClass<J${enumType.enumName}> {
46
46
  public:
47
- static auto constexpr kJavaDescriptor = "L${jniClassDescriptor};";
47
+ static constexpr auto kJavaDescriptor = "L${jniClassDescriptor};";
48
48
 
49
49
  public:
50
50
  /**
@@ -180,7 +180,7 @@ namespace ${cxxNamespace} {
180
180
  */
181
181
  struct J${name}: public jni::JavaClass<J${name}> {
182
182
  public:
183
- static auto constexpr kJavaDescriptor = "L${jniInterfaceDescriptor};";
183
+ static constexpr auto kJavaDescriptor = "L${jniInterfaceDescriptor};";
184
184
 
185
185
  public:
186
186
  /**
@@ -215,7 +215,7 @@ namespace ${cxxNamespace} {
215
215
  }
216
216
 
217
217
  public:
218
- static auto constexpr kJavaDescriptor = "L${jniClassDescriptor};";
218
+ static constexpr auto kJavaDescriptor = "L${jniClassDescriptor};";
219
219
  static void registerNatives() {
220
220
  registerHybrid({makeNativeMethod("invoke_cxx", J${name}_cxx::invoke_cxx)});
221
221
  }
@@ -14,9 +14,9 @@ export function createJNIHybridObjectRegistration({ hybridObjectName, jniClassNa
14
14
  ],
15
15
  cppDefinition: `
16
16
  struct ${JHybridTSpec}Impl: public jni::JavaClass<${JHybridTSpec}Impl, ${JHybridTSpec}::JavaPart> {
17
- static auto constexpr kJavaDescriptor = "L${jniNamespace};";
17
+ static constexpr auto kJavaDescriptor = "L${jniNamespace};";
18
18
  static std::shared_ptr<${JHybridTSpec}> create() {
19
- static auto constructorFn = javaClassStatic()->getConstructor<${JHybridTSpec}Impl::javaobject()>();
19
+ static const auto constructorFn = javaClassStatic()->getConstructor<${JHybridTSpec}Impl::javaobject()>();
20
20
  jni::local_ref<${JHybridTSpec}::JavaPart> javaPart = javaClassStatic()->newObject(constructorFn);
21
21
  return javaPart->get${JHybridTSpec}();
22
22
  }
@@ -96,7 +96,7 @@ namespace ${cxxNamespace} {
96
96
  */
97
97
  struct J${structType.structName} final: public jni::JavaClass<J${structType.structName}> {
98
98
  public:
99
- static auto constexpr kJavaDescriptor = "L${jniClassDescriptor};";
99
+ static constexpr auto kJavaDescriptor = "L${jniClassDescriptor};";
100
100
 
101
101
  public:
102
102
  /**
@@ -127,7 +127,7 @@ if (isInstanceOf(${namespace}::${innerName}::javaClassStatic())) {
127
127
  return `
128
128
  class ${innerName} final: public jni::JavaClass<${innerName}, J${kotlinName}> {
129
129
  public:
130
- static auto constexpr kJavaDescriptor = "L${descriptor};";
130
+ static constexpr auto kJavaDescriptor = "L${descriptor};";
131
131
 
132
132
  [[nodiscard]] ${bridge.asJniReferenceType('local')} getValue() const {
133
133
  static const auto field = javaClassStatic()->getField<${bridge.getTypeCode('c++')}>("value");
@@ -160,7 +160,7 @@ namespace ${cxxNamespace} {
160
160
  */
161
161
  class J${kotlinName}: public jni::JavaClass<J${kotlinName}> {
162
162
  public:
163
- static auto constexpr kJavaDescriptor = "L${jniClassDescriptor};";
163
+ static constexpr auto kJavaDescriptor = "L${jniClassDescriptor};";
164
164
 
165
165
  ${indent(cppCreateFuncs.join('\n'), ' ')}
166
166
 
@@ -11,11 +11,11 @@ export function createKotlinHybridViewManager(spec) {
11
11
  const { JHybridTSpec, HybridTSpec } = getHybridObjectName(spec.name);
12
12
  const { manager, stateClassName, component, propsClassName, descriptorClassName, } = getViewComponentNames(spec);
13
13
  const stateUpdaterName = `${stateClassName}Updater`;
14
- const autolinking = spec.config.getAutolinkedHybridObjects();
15
- const viewImplementation = autolinking[spec.name]?.kotlin;
16
- if (viewImplementation == null) {
17
- throw new Error(`Cannot create Kotlin HybridView ViewManager for ${spec.name} - it is not autolinked in nitro.json!`);
14
+ const implementation = spec.config.getAndroidAutolinkedImplementation(spec.name);
15
+ if (implementation?.language !== 'kotlin') {
16
+ throw new Error(`Cannot create Kotlin HybridView ViewManager for ${spec.name} - it must be autolinked with a Kotlin Android implementation in nitro.json!`);
18
17
  }
18
+ const viewImplementation = implementation.implementationClassName;
19
19
  const viewManagerCode = `
20
20
  ${createFileMetadataString(`${manager}.kt`)}
21
21
 
@@ -11,10 +11,9 @@ export function createSwiftHybridViewManager(spec) {
11
11
  const swiftNamespace = spec.config.getIosModuleName();
12
12
  const { HybridTSpec, HybridTSpecSwift, HybridTSpecCxx } = getHybridObjectName(spec.name);
13
13
  const { component, descriptorClassName, propsClassName } = getViewComponentNames(spec);
14
- const autolinking = spec.config.getAutolinkedHybridObjects();
15
- const viewImplementation = autolinking[spec.name]?.swift;
16
- if (viewImplementation == null) {
17
- throw new Error(`Cannot create Swift HybridView ViewManager for ${spec.name} - it is not autolinked in nitro.json!`);
14
+ const implementation = spec.config.getIosAutolinkedImplementation(spec.name);
15
+ if (implementation?.language !== 'swift') {
16
+ throw new Error(`Cannot create Swift HybridView ViewManager for ${spec.name} - it must be autolinked with a Swift iOS implementation in nitro.json!`);
18
17
  }
19
18
  const propAssignments = spec.properties.map((p) => {
20
19
  const name = escapeCppName(p.name);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nitrogen",
3
- "version": "0.35.0",
3
+ "version": "0.35.1-beta.0",
4
4
  "description": "The code-generator for react-native-nitro-modules.",
5
5
  "main": "lib/index",
6
6
  "types": "lib/index.d.ts",
@@ -35,7 +35,7 @@
35
35
  },
36
36
  "dependencies": {
37
37
  "chalk": "^5.3.0",
38
- "react-native-nitro-modules": "^0.35.0",
38
+ "react-native-nitro-modules": "^0.35.1-beta.0",
39
39
  "ts-morph": "^27.0.0",
40
40
  "yargs": "^18.0.0",
41
41
  "zod": "^4.0.5"
@@ -28,27 +28,39 @@ export function createHybridObjectIntializer(): SourceFile[] {
28
28
  const cppRegistrations: string[] = []
29
29
  const cppDefinitions: string[] = []
30
30
  for (const hybridObjectName of Object.keys(autolinkedHybridObjects)) {
31
- const config = autolinkedHybridObjects[hybridObjectName]
32
-
33
- if (config?.cpp != null) {
34
- // Autolink a C++ HybridObject!
35
- const { cppCode, requiredImports } = createCppHybridObjectRegistration({
36
- hybridObjectName: hybridObjectName,
37
- cppClassName: config.cpp,
38
- })
39
- cppHybridObjectImports.push(...requiredImports)
40
- cppRegistrations.push(cppCode)
31
+ const implementation =
32
+ NitroConfig.current.getAndroidAutolinkedImplementation(hybridObjectName)
33
+ if (implementation == null) {
34
+ continue
41
35
  }
42
- if (config?.kotlin != null) {
43
- // Autolink a Kotlin HybridObject through JNI/C++!
44
- const { cppCode, cppDefinition, requiredImports } =
45
- createJNIHybridObjectRegistration({
36
+
37
+ switch (implementation.language) {
38
+ case 'cpp': {
39
+ // Autolink a C++ HybridObject!
40
+ const { cppCode, requiredImports } = createCppHybridObjectRegistration({
46
41
  hybridObjectName: hybridObjectName,
47
- jniClassName: config.kotlin,
42
+ cppClassName: implementation.implementationClassName,
48
43
  })
49
- cppHybridObjectImports.push(...requiredImports)
50
- cppDefinitions.push(cppDefinition)
51
- cppRegistrations.push(cppCode)
44
+ cppHybridObjectImports.push(...requiredImports)
45
+ cppRegistrations.push(cppCode)
46
+ break
47
+ }
48
+ case 'kotlin': {
49
+ // Autolink a Kotlin HybridObject through JNI/C++!
50
+ const { cppCode, cppDefinition, requiredImports } =
51
+ createJNIHybridObjectRegistration({
52
+ hybridObjectName: hybridObjectName,
53
+ jniClassName: implementation.implementationClassName,
54
+ })
55
+ cppHybridObjectImports.push(...requiredImports)
56
+ cppDefinitions.push(cppDefinition)
57
+ cppRegistrations.push(cppCode)
58
+ break
59
+ }
60
+ default:
61
+ throw new Error(
62
+ `The HybridObject "${hybridObjectName}" cannot be autolinked on Android - Language "${implementation.language}" is not supported on Android!`
63
+ )
52
64
  }
53
65
  }
54
66
 
@@ -23,28 +23,40 @@ export function createHybridObjectIntializer(): [ObjcFile, SwiftFile] | [] {
23
23
  const cppImports: SourceImport[] = []
24
24
  let containsSwiftObjects = false
25
25
  for (const hybridObjectName of Object.keys(autolinkedHybridObjects)) {
26
- const config = autolinkedHybridObjects[hybridObjectName]
27
-
28
- if (config?.cpp != null) {
29
- // Autolink a C++ HybridObject!
30
- const { cppCode, requiredImports } = createCppHybridObjectRegistration({
31
- hybridObjectName: hybridObjectName,
32
- cppClassName: config.cpp,
33
- })
34
- cppImports.push(...requiredImports)
35
- cppRegistrations.push(cppCode)
26
+ const implementation =
27
+ NitroConfig.current.getIosAutolinkedImplementation(hybridObjectName)
28
+ if (implementation == null) {
29
+ continue
36
30
  }
37
- if (config?.swift != null) {
38
- // Autolink a Swift HybridObject!
39
- containsSwiftObjects = true
40
- const { cppCode, requiredImports, swiftRegistrationMethods } =
41
- createSwiftHybridObjectRegistration({
31
+
32
+ switch (implementation.language) {
33
+ case 'cpp': {
34
+ // Autolink a C++ HybridObject!
35
+ const { cppCode, requiredImports } = createCppHybridObjectRegistration({
42
36
  hybridObjectName: hybridObjectName,
43
- swiftClassName: config.swift,
37
+ cppClassName: implementation.implementationClassName,
44
38
  })
45
- cppImports.push(...requiredImports)
46
- cppRegistrations.push(cppCode)
47
- swiftRegistrations.push(swiftRegistrationMethods)
39
+ cppImports.push(...requiredImports)
40
+ cppRegistrations.push(cppCode)
41
+ break
42
+ }
43
+ case 'swift': {
44
+ // Autolink a Swift HybridObject!
45
+ containsSwiftObjects = true
46
+ const { cppCode, requiredImports, swiftRegistrationMethods } =
47
+ createSwiftHybridObjectRegistration({
48
+ hybridObjectName: hybridObjectName,
49
+ swiftClassName: implementation.implementationClassName,
50
+ })
51
+ cppImports.push(...requiredImports)
52
+ cppRegistrations.push(cppCode)
53
+ swiftRegistrations.push(swiftRegistrationMethods)
54
+ break
55
+ }
56
+ default:
57
+ throw new Error(
58
+ `The HybridObject "${hybridObjectName}" cannot be autolinked on iOS - Language "${implementation.language}" is not supported on iOS!`
59
+ )
48
60
  }
49
61
  }
50
62
 
@@ -1,6 +1,11 @@
1
1
  import chalk from 'chalk'
2
2
  import { readUserConfig } from './getConfig.js'
3
- import type { NitroUserConfig } from './NitroUserConfig.js'
3
+ import type {
4
+ AutolinkingAndroidImplementation,
5
+ AutolinkingIOSImplementation,
6
+ AutolinkingPlatformImplementation,
7
+ NitroUserConfig,
8
+ } from './NitroUserConfig.js'
4
9
 
5
10
  const CXX_BASE_NAMESPACE = ['margelo', 'nitro']
6
11
  const ANDROID_BASE_NAMESPACE = ['com', 'margelo', 'nitro']
@@ -109,6 +114,49 @@ export class NitroConfig {
109
114
  return this.config.autolinking
110
115
  }
111
116
 
117
+ getPlatformAutolinkedImplementation(
118
+ hybridObjectName: string,
119
+ platform: string
120
+ ): AutolinkingPlatformImplementation | undefined {
121
+ const objectConfig = this.config.autolinking[hybridObjectName]
122
+ if (objectConfig == null) {
123
+ return undefined
124
+ }
125
+
126
+ // "all" applies to every platform and should automatically include
127
+ // future platforms without having to expand explicit key lists here.
128
+ if (objectConfig.all != null) {
129
+ return objectConfig.all
130
+ }
131
+ return objectConfig[platform]
132
+ }
133
+
134
+ getIosAutolinkedImplementation(
135
+ hybridObjectName: string
136
+ ): AutolinkingIOSImplementation | undefined {
137
+ const objectConfig = this.config.autolinking[hybridObjectName]
138
+ if (objectConfig == null) {
139
+ return undefined
140
+ }
141
+ if (objectConfig.all != null) {
142
+ return objectConfig.all
143
+ }
144
+ return objectConfig.ios
145
+ }
146
+
147
+ getAndroidAutolinkedImplementation(
148
+ hybridObjectName: string
149
+ ): AutolinkingAndroidImplementation | undefined {
150
+ const objectConfig = this.config.autolinking[hybridObjectName]
151
+ if (objectConfig == null) {
152
+ return undefined
153
+ }
154
+ if (objectConfig.all != null) {
155
+ return objectConfig.all
156
+ }
157
+ return objectConfig.android
158
+ }
159
+
112
160
  /**
113
161
  * Get the paths that will be ignored when loading the TypeScript project.
114
162
  * In most cases, this just contains `node_modules/`.
@@ -9,6 +9,137 @@ const isReservedKeywordError = {
9
9
  message: `This value is reserved and cannot be used!`,
10
10
  }
11
11
 
12
+ const autolinkingLanguageSchema = z.enum(['cpp', 'swift', 'kotlin'])
13
+
14
+ const autolinkingAllImplementationSchema = z.object({
15
+ language: z.literal('cpp'),
16
+ implementationClassName: z.string(),
17
+ })
18
+
19
+ const autolinkingIOSImplementationSchema = z.object({
20
+ language: z.enum(['cpp', 'swift']),
21
+ implementationClassName: z.string(),
22
+ })
23
+
24
+ const autolinkingAndroidImplementationSchema = z.object({
25
+ language: z.enum(['cpp', 'kotlin']),
26
+ implementationClassName: z.string(),
27
+ })
28
+
29
+ const autolinkingPlatformImplementationSchema = z.object({
30
+ language: autolinkingLanguageSchema,
31
+ implementationClassName: z.string(),
32
+ })
33
+
34
+ const autolinkingModernHybridObjectSchema = z
35
+ .object({
36
+ all: autolinkingAllImplementationSchema.optional(),
37
+ ios: autolinkingIOSImplementationSchema.optional(),
38
+ android: autolinkingAndroidImplementationSchema.optional(),
39
+ })
40
+ .catchall(autolinkingPlatformImplementationSchema)
41
+ .superRefine((value, ctx) => {
42
+ const hasAll = value.all != null
43
+ const platformCount = Object.keys(value).filter(
44
+ (key) => key !== 'all'
45
+ ).length
46
+
47
+ if (hasAll && platformCount > 0) {
48
+ ctx.addIssue({
49
+ code: 'custom',
50
+ message: '"all" cannot be combined with platform-specific entries.',
51
+ })
52
+ }
53
+
54
+ if (!hasAll && platformCount === 0) {
55
+ ctx.addIssue({
56
+ code: 'custom',
57
+ message:
58
+ 'Each autolinking entry must declare either "all" or at least one platform.',
59
+ })
60
+ }
61
+ })
62
+
63
+ const autolinkingLegacyHybridObjectSchema = z
64
+ .object({
65
+ cpp: z.string().optional(),
66
+ swift: z.string().optional(),
67
+ kotlin: z.string().optional(),
68
+ })
69
+ .strict()
70
+ .superRefine((value, ctx) => {
71
+ const hasCpp = value.cpp != null
72
+ const hasSwift = value.swift != null
73
+ const hasKotlin = value.kotlin != null
74
+
75
+ if (!hasCpp && !hasSwift && !hasKotlin) {
76
+ ctx.addIssue({
77
+ code: z.ZodIssueCode.custom,
78
+ message:
79
+ 'Each autolinking entry must declare at least one implementation.',
80
+ })
81
+ }
82
+
83
+ if (hasCpp && (hasSwift || hasKotlin)) {
84
+ ctx.addIssue({
85
+ code: z.ZodIssueCode.custom,
86
+ message:
87
+ 'Legacy autolinking entries cannot mix "cpp" with "swift"/"kotlin".',
88
+ })
89
+ }
90
+ })
91
+
92
+ function normalizeLegacyAutolinkingHybridObject(
93
+ value: z.infer<typeof autolinkingLegacyHybridObjectSchema>
94
+ ): z.infer<typeof autolinkingModernHybridObjectSchema> {
95
+ if (value.cpp != null) {
96
+ return {
97
+ all: {
98
+ language: 'cpp',
99
+ implementationClassName: value.cpp,
100
+ },
101
+ }
102
+ }
103
+
104
+ const normalized: z.infer<typeof autolinkingModernHybridObjectSchema> = {}
105
+ if (value.swift != null) {
106
+ normalized.ios = {
107
+ language: 'swift',
108
+ implementationClassName: value.swift,
109
+ }
110
+ }
111
+ if (value.kotlin != null) {
112
+ normalized.android = {
113
+ language: 'kotlin',
114
+ implementationClassName: value.kotlin,
115
+ }
116
+ }
117
+ return normalized
118
+ }
119
+
120
+ const autolinkingHybridObjectSchema = z.union([
121
+ autolinkingModernHybridObjectSchema,
122
+ autolinkingLegacyHybridObjectSchema.transform(
123
+ normalizeLegacyAutolinkingHybridObject
124
+ ),
125
+ ])
126
+
127
+ export type AutolinkingAllImplementation = z.infer<
128
+ typeof autolinkingAllImplementationSchema
129
+ >
130
+ export type AutolinkingIOSImplementation = z.infer<
131
+ typeof autolinkingIOSImplementationSchema
132
+ >
133
+ export type AutolinkingAndroidImplementation = z.infer<
134
+ typeof autolinkingAndroidImplementationSchema
135
+ >
136
+ export type AutolinkingPlatformImplementation = z.infer<
137
+ typeof autolinkingPlatformImplementationSchema
138
+ >
139
+ export type AutolinkingHybridObject = z.infer<
140
+ typeof autolinkingHybridObjectSchema
141
+ >
142
+
12
143
  export const NitroUserConfigSchema = z.object({
13
144
  /**
14
145
  * Represents the C++ namespace of the module that will be generated.
@@ -74,17 +205,10 @@ export const NitroUserConfigSchema = z.object({
74
205
  * Configures the code that gets generated for autolinking (registering)
75
206
  * Hybrid Object constructors.
76
207
  *
77
- * Each class listed here needs to have a default constructor/initializer that takes
78
- * zero arguments.
208
+ * Each class listed here needs to have a default constructor/initializer
209
+ * that takes zero arguments.
79
210
  */
80
- autolinking: z.record(
81
- z.string(),
82
- z.object({
83
- cpp: z.string().optional(),
84
- swift: z.string().optional(),
85
- kotlin: z.string().optional(),
86
- })
87
- ),
211
+ autolinking: z.record(z.string(), autolinkingHybridObjectSchema),
88
212
  /**
89
213
  * A list of paths relative to the project directory that should be ignored by nitrogen.
90
214
  * Nitrogen will not look for `.nitro.ts` files in these directories.
@@ -6,6 +6,43 @@ import {
6
6
  } from './NitroUserConfig.js'
7
7
  import chalk from 'chalk'
8
8
 
9
+ function isObject(value: unknown): value is Record<string, unknown> {
10
+ return typeof value === 'object' && value != null && !Array.isArray(value)
11
+ }
12
+
13
+ function getLegacyAutolinkingEntries(config: unknown): string[] {
14
+ if (!isObject(config)) return []
15
+
16
+ const autolinking = config.autolinking
17
+ if (!isObject(autolinking)) return []
18
+
19
+ const entries: string[] = []
20
+ for (const [hybridObjectName, entry] of Object.entries(autolinking)) {
21
+ if (!isObject(entry)) continue
22
+
23
+ const hasLegacySyntax =
24
+ 'cpp' in entry || 'swift' in entry || 'kotlin' in entry
25
+ if (hasLegacySyntax) {
26
+ entries.push(hybridObjectName)
27
+ }
28
+ }
29
+
30
+ return entries
31
+ }
32
+
33
+ // TODO: Remove this once users have migrated to new `nitro.json`
34
+ function logLegacyAutolinkingDeprecation(config: unknown): void {
35
+ const legacyEntries = getLegacyAutolinkingEntries(config)
36
+ if (legacyEntries.length === 0) return
37
+
38
+ console.warn(
39
+ chalk.yellow(
40
+ `Warning: nitro.json uses deprecated autolinking syntax ("cpp"/"swift"/"kotlin") for [${legacyEntries.join(', ')}]. ` +
41
+ `Use "all"/"ios"/"android" entries with { language, implementationClassName } instead.`
42
+ )
43
+ )
44
+ }
45
+
9
46
  function readFile(configPath: string): string {
10
47
  try {
11
48
  return fs.readFileSync(configPath, 'utf8')
@@ -58,6 +95,7 @@ function parseConfig(json: string): NitroUserConfig {
58
95
  }
59
96
 
60
97
  try {
98
+ logLegacyAutolinkingDeprecation(object)
61
99
  return NitroUserConfigSchema.parse(object)
62
100
  } catch (error) {
63
101
  if (error instanceof ZodError) {
@@ -103,11 +103,11 @@ namespace ${cxxNamespace} {
103
103
  class ${name.JHybridTSpec}: public virtual ${name.HybridTSpec}, public virtual ${cppBaseClass} {
104
104
  public:
105
105
  struct JavaPart: public jni::JavaClass<JavaPart, ${javaPartBaseClass}> {
106
- static auto constexpr kJavaDescriptor = "L${jniClassDescriptor};";
106
+ static constexpr auto kJavaDescriptor = "L${jniClassDescriptor};";
107
107
  std::shared_ptr<${name.JHybridTSpec}> get${name.JHybridTSpec}();
108
108
  };
109
109
  struct CxxPart: public jni::HybridClass<CxxPart, ${cxxPartBaseClass}> {
110
- static auto constexpr kJavaDescriptor = "L${cxxPartJniClassDescriptor};";
110
+ static constexpr auto kJavaDescriptor = "L${cxxPartJniClassDescriptor};";
111
111
  static jni::local_ref<jhybriddata> initHybrid(jni::alias_ref<jhybridobject> jThis);
112
112
  static void registerNatives();
113
113
  using HybridBase::HybridBase;
@@ -66,7 +66,13 @@ export class KotlinCxxBridgedType implements BridgedType<'kotlin', 'c++'> {
66
66
  return false
67
67
  }
68
68
 
69
- getRequiredImports(language: Language): SourceImport[] {
69
+ getRequiredImports(
70
+ language: Language,
71
+ visited: Set<Type> = new Set()
72
+ ): SourceImport[] {
73
+ if (visited.has(this.type)) return []
74
+ visited.add(this.type)
75
+
70
76
  const imports = this.type.getRequiredImports(language)
71
77
 
72
78
  if (language === 'c++') {
@@ -179,19 +185,17 @@ export class KotlinCxxBridgedType implements BridgedType<'kotlin', 'c++'> {
179
185
 
180
186
  // Recursively look into referenced types (e.g. the `T` of a `optional<T>`, or `T` of a `T[]`)
181
187
  const referencedTypes = getReferencedTypes(this.type)
182
- referencedTypes.forEach((t) => {
183
- if (t === this.type) {
184
- // break a recursion - we already know this type
185
- return
186
- }
188
+ for (const t of referencedTypes) {
187
189
  const bridged = new KotlinCxxBridgedType(t)
188
- imports.push(...bridged.getRequiredImports(language))
189
- })
190
+ imports.push(...bridged.getRequiredImports(language, visited))
191
+ }
190
192
 
191
193
  return imports
192
194
  }
193
195
 
194
- getExtraFiles(): SourceFile[] {
196
+ getExtraFiles(visited: Set<Type> = new Set()): SourceFile[] {
197
+ if (visited.has(this.type)) return []
198
+ visited.add(this.type)
195
199
  const files: SourceFile[] = []
196
200
 
197
201
  switch (this.type.kind) {
@@ -219,14 +223,10 @@ export class KotlinCxxBridgedType implements BridgedType<'kotlin', 'c++'> {
219
223
 
220
224
  // Recursively look into referenced types (e.g. the `T` of a `optional<T>`, or `T` of a `T[]`)
221
225
  const referencedTypes = getReferencedTypes(this.type)
222
- referencedTypes.forEach((t) => {
223
- if (t === this.type) {
224
- // break a recursion - we already know this type
225
- return
226
- }
226
+ for (const t of referencedTypes) {
227
227
  const bridged = new KotlinCxxBridgedType(t)
228
- files.push(...bridged.getExtraFiles())
229
- })
228
+ files.push(...bridged.getExtraFiles(visited))
229
+ }
230
230
 
231
231
  return files
232
232
  }
@@ -53,7 +53,7 @@ namespace ${cxxNamespace} {
53
53
  */
54
54
  struct J${enumType.enumName} final: public jni::JavaClass<J${enumType.enumName}> {
55
55
  public:
56
- static auto constexpr kJavaDescriptor = "L${jniClassDescriptor};";
56
+ static constexpr auto kJavaDescriptor = "L${jniClassDescriptor};";
57
57
 
58
58
  public:
59
59
  /**
@@ -201,7 +201,7 @@ namespace ${cxxNamespace} {
201
201
  */
202
202
  struct J${name}: public jni::JavaClass<J${name}> {
203
203
  public:
204
- static auto constexpr kJavaDescriptor = "L${jniInterfaceDescriptor};";
204
+ static constexpr auto kJavaDescriptor = "L${jniInterfaceDescriptor};";
205
205
 
206
206
  public:
207
207
  /**
@@ -236,7 +236,7 @@ namespace ${cxxNamespace} {
236
236
  }
237
237
 
238
238
  public:
239
- static auto constexpr kJavaDescriptor = "L${jniClassDescriptor};";
239
+ static constexpr auto kJavaDescriptor = "L${jniClassDescriptor};";
240
240
  static void registerNatives() {
241
241
  registerHybrid({makeNativeMethod("invoke_cxx", J${name}_cxx::invoke_cxx)});
242
242
  }
@@ -40,9 +40,9 @@ export function createJNIHybridObjectRegistration({
40
40
  ],
41
41
  cppDefinition: `
42
42
  struct ${JHybridTSpec}Impl: public jni::JavaClass<${JHybridTSpec}Impl, ${JHybridTSpec}::JavaPart> {
43
- static auto constexpr kJavaDescriptor = "L${jniNamespace};";
43
+ static constexpr auto kJavaDescriptor = "L${jniNamespace};";
44
44
  static std::shared_ptr<${JHybridTSpec}> create() {
45
- static auto constructorFn = javaClassStatic()->getConstructor<${JHybridTSpec}Impl::javaobject()>();
45
+ static const auto constructorFn = javaClassStatic()->getConstructor<${JHybridTSpec}Impl::javaobject()>();
46
46
  jni::local_ref<${JHybridTSpec}::JavaPart> javaPart = javaClassStatic()->newObject(constructorFn);
47
47
  return javaPart->get${JHybridTSpec}();
48
48
  }
@@ -117,7 +117,7 @@ namespace ${cxxNamespace} {
117
117
  */
118
118
  struct J${structType.structName} final: public jni::JavaClass<J${structType.structName}> {
119
119
  public:
120
- static auto constexpr kJavaDescriptor = "L${jniClassDescriptor};";
120
+ static constexpr auto kJavaDescriptor = "L${jniClassDescriptor};";
121
121
 
122
122
  public:
123
123
  /**
@@ -145,7 +145,7 @@ if (isInstanceOf(${namespace}::${innerName}::javaClassStatic())) {
145
145
  return `
146
146
  class ${innerName} final: public jni::JavaClass<${innerName}, J${kotlinName}> {
147
147
  public:
148
- static auto constexpr kJavaDescriptor = "L${descriptor};";
148
+ static constexpr auto kJavaDescriptor = "L${descriptor};";
149
149
 
150
150
  [[nodiscard]] ${bridge.asJniReferenceType('local')} getValue() const {
151
151
  static const auto field = javaClassStatic()->getField<${bridge.getTypeCode('c++')}>("value");
@@ -180,7 +180,7 @@ namespace ${cxxNamespace} {
180
180
  */
181
181
  class J${kotlinName}: public jni::JavaClass<J${kotlinName}> {
182
182
  public:
183
- static auto constexpr kJavaDescriptor = "L${jniClassDescriptor};";
183
+ static constexpr auto kJavaDescriptor = "L${jniClassDescriptor};";
184
184
 
185
185
  ${indent(cppCreateFuncs.join('\n'), ' ')}
186
186
 
@@ -28,13 +28,15 @@ export function createKotlinHybridViewManager(
28
28
  descriptorClassName,
29
29
  } = getViewComponentNames(spec)
30
30
  const stateUpdaterName = `${stateClassName}Updater`
31
- const autolinking = spec.config.getAutolinkedHybridObjects()
32
- const viewImplementation = autolinking[spec.name]?.kotlin
33
- if (viewImplementation == null) {
31
+ const implementation = spec.config.getAndroidAutolinkedImplementation(
32
+ spec.name
33
+ )
34
+ if (implementation?.language !== 'kotlin') {
34
35
  throw new Error(
35
- `Cannot create Kotlin HybridView ViewManager for ${spec.name} - it is not autolinked in nitro.json!`
36
+ `Cannot create Kotlin HybridView ViewManager for ${spec.name} - it must be autolinked with a Kotlin Android implementation in nitro.json!`
36
37
  )
37
38
  }
39
+ const viewImplementation = implementation.implementationClassName
38
40
 
39
41
  const viewManagerCode = `
40
42
  ${createFileMetadataString(`${manager}.kt`)}
@@ -28,11 +28,10 @@ export function createSwiftHybridViewManager(
28
28
  )
29
29
  const { component, descriptorClassName, propsClassName } =
30
30
  getViewComponentNames(spec)
31
- const autolinking = spec.config.getAutolinkedHybridObjects()
32
- const viewImplementation = autolinking[spec.name]?.swift
33
- if (viewImplementation == null) {
31
+ const implementation = spec.config.getIosAutolinkedImplementation(spec.name)
32
+ if (implementation?.language !== 'swift') {
34
33
  throw new Error(
35
- `Cannot create Swift HybridView ViewManager for ${spec.name} - it is not autolinked in nitro.json!`
34
+ `Cannot create Swift HybridView ViewManager for ${spec.name} - it must be autolinked with a Swift iOS implementation in nitro.json!`
36
35
  )
37
36
  }
38
37