Skip to content

Commit

Permalink
Merge pull request brandingbrand#2680 from crherman7/feat/ENG-5152_cu…
Browse files Browse the repository at this point in the history
…stom_privacy_manifest_v13

feat(privacy-manifest): ENG-5152 allow custom privacy manifest
  • Loading branch information
NickBurkhartBB authored Apr 19, 2024
2 parents 4ac4aed + 94307ba commit 69437de
Show file tree
Hide file tree
Showing 11 changed files with 245 additions and 2 deletions.
8 changes: 8 additions & 0 deletions apps/docs/src/content/docs/guides/build.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ type IOSConfig = {
* bundleId: "com.app",
* displayName: "App",
* entitlementsFilePath: "./path/to/app.entitlements",
* privacyManifestPath: "./path/to/PrivacyInfo.xcprivacy",
* frameworks: {
* framework: "Sprite.framework",
* },
Expand Down Expand Up @@ -109,6 +110,13 @@ type IOSConfig = {
*/
entitlementsFilePath?: string | undefined;

/**
* Optional PrivacyInfo.xcprivacy path relative to the root of the project.
*
* https://developer.apple.com/documentation/bundleresources/privacy_manifest_files
*/
privacyManifestPath?: string | undefined;

/**
* Optional frameworks.
*/
Expand Down
18 changes: 18 additions & 0 deletions apps/docs/src/content/docs/packages/cli-kit.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,24 @@ const entitlementsPath = path.ios.entitlements;

##### Constant

###### `path.ios.privacyManifest`

Generates the absolute path to the iOS PrivacyInfo.xcprivacy.

**type:** `string`

##### Usage

Below is an illustrative example demonstrating the utilization of the `path.ios.privacyManifest` to generate the absolute path to the PrivacyInfo.xcprivacy.

```ts
import { path } from "@brandingbrand/code-cli-kit";

const privacyManifestPath = path.ios.privacyManifest;
```

##### Constant

###### `path.ios.nativeConstants`

Generates the absolute path to the iOS NativeConstants.m.
Expand Down
14 changes: 14 additions & 0 deletions packages/cli-kit/__tests__/path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,20 @@ describe("path", () => {
);
});

it("should have an ios.entitlements function that returns the path to ios/app/app.entitlements", () => {
const entitlementsPath = path.ios.entitlements;
expect(entitlementsPath).toEqual(
expect.stringMatching(/.*ios\/app\/app\.entitlements$/)
);
});

it("should have an ios.privacyManifest function that returns the path to ios/app/PrivacyInfo.xcprivacy", () => {
const privacyManifestPath = path.ios.privacyManifest;
expect(privacyManifestPath).toEqual(
expect.stringMatching(/.*ios\/app\/PrivacyInfo\.xcprivacy$/)
);
});

it("should have an ios.gemfile function that returns the path to ios/app/Gemfile", () => {
const gemfilePath = path.ios.gemfile;
expect(gemfilePath).toEqual(expect.stringMatching(/.*ios\/Gemfile$/));
Expand Down
11 changes: 11 additions & 0 deletions packages/cli-kit/src/lib/path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,17 @@ export default {
*/
entitlements: resolvePathFromProject("ios", "app", "app.entitlements"),

/**
* Retrieves the absolute path to the iOS PrivacyInfo.xcprivacy file.
*
* @returns {string} The absolute path to "ios/app/PrivacyInfo.xcprivacy".
*/
privacyManifest: resolvePathFromProject(
"ios",
"app",
"PrivacyInfo.xcprivacy"
),

/**
* Retrieves the absolute path to the iOS NativeConstants.m file.
*
Expand Down
8 changes: 8 additions & 0 deletions packages/cli-kit/src/schemas/build-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@ const IOSSchema = t.exact(
* bundleId: "com.app",
* displayName: "App",
* entitlementsFilePath: "./path/to/app.entitlements",
* privacyManifestPath: "./path/to/PrivacyInfo.xcprivacy"
* frameworks: {
* framework: "Sprite.framework",
* },
Expand Down Expand Up @@ -352,6 +353,13 @@ const IOSSchema = t.exact(
*/
entitlementsFilePath: t.string,

/**
* Optional PrivacyInfo.xcprivacy path relative to the root of the project.
*
* https://developer.apple.com/documentation/bundleresources/privacy_manifest_files
*/
privacyManifestPath: t.string,

/**
* Optional frameworks.
*
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/__tests__/app-entitlements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { type BuildConfig, fs, path } from "@brandingbrand/code-cli-kit";

import transformer from "../src/transformers/ios/app-entitlements";

describe("ios project.pbxproj transformers", () => {
describe("ios app.entitlements transformers", () => {
beforeEach(() => {
jest.resetAllMocks();
});
Expand Down
70 changes: 70 additions & 0 deletions packages/cli/__tests__/privacy-info-xcprivacy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/**
* @jest-environment-options {"requireTemplate": true, "fixtures": "privacy-info-xcprivacy_fixtures"}
*/

/// <reference types="@brandingbrand/code-jest-config" />

import { type BuildConfig, fs, path } from "@brandingbrand/code-cli-kit";

import transformer from "../src/transformers/ios/privacy-info-xcprivacy";

describe("ios PrivacyInfo.xcprivacy transformers", () => {
beforeEach(() => {
jest.resetAllMocks();
});

it("should not update PrivacyInfo.xcprivacy file", async () => {
const config = {
...__flagship_code_build_config,
} as BuildConfig;

const origionalContent = await fs.readFile(
path.ios.privacyManifest,
"utf-8"
);
await transformer.transform(config, {} as any);
const content = await fs.readFile(path.ios.privacyManifest, "utf-8");

expect(content).toEqual(origionalContent);
});

it("should update PrivacyInfo.xcprivacy file", async () => {
const config = {
...__flagship_code_build_config,
} as BuildConfig;

config.ios.privacyManifestPath = "./PrivacyInfo.xcprivacy";

const privacyManifestContent = await fs.readFile(
path.project.resolve("PrivacyInfo.xcprivacy"),
"utf-8"
);

await transformer.transform(config, {} as any);
const content = await fs.readFile(path.ios.privacyManifest, "utf-8");

expect(content).toEqual(privacyManifestContent);
});

it("should throw error for wrong PrivacyInfo.xcprivacy path", async () => {
const config = {
...__flagship_code_build_config,
} as BuildConfig;

config.ios.privacyManifestPath = "./blah/PrivacyInfo.xcprivacy";

const privacyManifestAbsolutePath = path.project.resolve(
config.ios.privacyManifestPath
);

const throwError = async () => {
await transformer.transform(config, {} as any);
};

await expect(throwError).rejects.toThrow(
new Error(
`[PrivacyInfoXCPrivacyTransformerError]: path to privacy manifest does not exist ${privacyManifestAbsolutePath}, please update privacyManifestPath to the correct path relative to the root of your React Native project.`
)
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyCollectedDataTypes</key>
<array>
</array>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>35F9.1</string>
</array>
</dict>
</array>
<key>NSPrivacyTracking</key>
<false/>
</dict>
</plist>
2 changes: 1 addition & 1 deletion packages/cli/src/transformers/ios/app-entitlements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { Transforms, defineTransformer } from "@/lib";
*/
export default defineTransformer<Transforms<string>>({
/**
* The name of the file to be transformed ("build.gradle").
* The name of the file to be transformed ("app.entitlements").
* @type {string}
*/
file: "app.entitlements",
Expand Down
5 changes: 5 additions & 0 deletions packages/cli/src/transformers/ios/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,8 @@ export { default as entitlements } from "./app-entitlements";
* Represents the AppDelegate.mm file transformers.
*/
export { default as appDelegate } from "./app-delegate-mm";

/**
* Represents the PrivacyInfo.xcprivacy file transformers.
*/
export { default as privacyInfo } from "./privacy-info-xcprivacy";
79 changes: 79 additions & 0 deletions packages/cli/src/transformers/ios/privacy-info-xcprivacy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import fs from "fs";

import {
type BuildConfig,
type PrebuildOptions,
withUTF8,
path,
string,
} from "@brandingbrand/code-cli-kit";

import { Transforms, defineTransformer } from "@/lib";

/**
* Defines a transformer for the iOS project's "PrivacyInfo.xcprivacy" file.
*
* @type {typeof defineTransformer<(content: string, config: BuildConfig) => string>} - The type of the transformer.
* @property {string} file - The name of the file to be transformed ("PrivacyInfo.xcprivacy").
* @property {Array<(content: string, config: BuildConfig) => string>} transforms - An array of transformer functions.
* @property {Function} transform - The main transform function that applies all specified transformations.
* @returns {Promise<string>} The updated content of the "PrivacyInfo.xcprivacy" file.
*/
export default defineTransformer<Transforms<string>>({
/**
* The name of the file to be transformed ("PrivacyInfo.xcprivacy").
* @type {string}
*/
file: "PrivacyInfo.xcprivacy",

/**
* An array of transformer functions to be applied to the "PrivacyInfo.xcprivacy" file.
* Each function receives the content of the file and the build configuration,
* and returns the updated content after applying specific transformations.
* @type {Array<(content: string, config: BuildConfig) => string>}
*/
transforms: [
/**
* Transformer for updating the dependencies in the "PrivacyInfo.xcprivacy" file.
* @param {string} content - The content of the file.
* @param {BuildConfig} config - The build configuration.
* @returns {string} - The updated content.
*/
(content: string, config: BuildConfig): string => {
const { privacyManifestPath } = config.ios;

if (!privacyManifestPath) return content;

const privacyManifestAbsolutePath =
path.project.resolve(privacyManifestPath);

if (!fs.existsSync(privacyManifestAbsolutePath)) {
throw new Error(
`[PrivacyInfoXCPrivacyTransformerError]: path to privacy manifest does not exist ${privacyManifestAbsolutePath}, please update privacyManifestPath to the correct path relative to the root of your React Native project.`
);
}

const privacyManifestContent = fs.readFileSync(
privacyManifestAbsolutePath,
"utf-8"
);

return string.replace(content, /[\s\S]*/m, privacyManifestContent);
},
],
/**
* The main transform function that applies all specified transformations to the "PrivacyInfo.xcprivacy" file.
* @param {BuildConfig} config - The build configuration.
* @returns {Promise<void>} - The updated content of the "PrivacyInfo.xcprivacy" file.
*/
transform: async function (
config: BuildConfig,
options: PrebuildOptions
): Promise<void> {
return withUTF8(path.ios.privacyManifest, (content: string) => {
return this.transforms.reduce((acc, curr) => {
return curr(acc, config, options);
}, content);
});
},
});

0 comments on commit 69437de

Please sign in to comment.