diff --git a/src/__tests__/unit/tasks/mainActivityTask.spec.ts b/src/__tests__/unit/tasks/mainActivityTask.spec.ts new file mode 100644 index 0000000..520035d --- /dev/null +++ b/src/__tests__/unit/tasks/mainActivityTask.spec.ts @@ -0,0 +1,716 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +const { mockFs } = require('../../mocks/mockAll'); +const mock = jest.spyOn(require('../../../utils/stringSplice'), 'stringSplice'); + +import path from 'path'; +import { Constants } from '../../../constants'; +import { mainActivityTask, runTask } from '../../../tasks/mainActivityTask'; +import { MainActivityTaskType } from '../../../types/mod.types'; +import { mockPrompter } from '../../mocks/mockAll'; + +describe('mainActivityTask', () => { + it('should prepend text into empty body ', async () => { + let content = ''; + const task: MainActivityTaskType = { + type: 'main_activity', + actions: [ + { + block: 'test', + append: 'google();', + prepend: 'google();', + }, + ], + }; + content = await mainActivityTask({ + configPath: 'path/to/config', + task: task, + content, + packageName: 'test-package', + }); + content = await mainActivityTask({ + configPath: 'path/to/config', + task: task, + content, + packageName: 'test-package', + }); + expect(content).toEqual(` +test { + google(); +} +`); + }); + it('should prepend text into empty body without block', async () => { + let content = ''; + const task: MainActivityTaskType = { + type: 'main_activity', + actions: [ + { + append: 'google();', + prepend: 'google();', + }, + ], + }; + content = await mainActivityTask({ + configPath: 'path/to/config', + task: task, + content, + packageName: 'test-package', + }); + content = await mainActivityTask({ + configPath: 'path/to/config', + task: task, + content, + packageName: 'test-package', + }); + expect(content).toEqual(` +google(); +`); + }); + it('should skip insert when ifNotPresent exists', async () => { + const content = ` +buildscript { + ext { + jcenter(); + } +} +`; + const task: MainActivityTaskType = { + type: 'main_activity', + actions: [ + { + block: 'buildscript.ext', + ifNotPresent: 'jcenter', + append: 'google();', + }, + { + block: 'buildscript.ext', + ifNotPresent: 'jcenter', + prepend: 'google();', + }, + ], + }; + + await mainActivityTask({ + configPath: 'path/to/config', + task: task, + content, + packageName: 'test-package', + }); + expect(mockPrompter.log.message).toHaveBeenCalledWith( + expect.stringContaining('found existing ') + ); + }); + it('should prepend text into partial body ', async () => { + let content = ` +buildscript {} +`; + const task: MainActivityTaskType = { + type: 'main_activity', + actions: [ + { + block: 'buildscript.ext', + prepend: 'google();', + }, + ], + }; + + content = await mainActivityTask({ + configPath: 'path/to/config', + task: task, + content, + packageName: 'test-package', + }); + expect(content).toEqual(` +buildscript { + ext { + google(); + } +} +`); + }); + it('should prepend text into existing body ', async () => { + let content = ` +buildscript { + ext { + jcenter(); + } +} +`; + const task: MainActivityTaskType = { + type: 'main_activity', + actions: [ + { + block: 'buildscript.ext', + prepend: 'google();', + }, + ], + }; + + content = await mainActivityTask({ + configPath: 'path/to/config', + task, + content, + packageName: 'test-package', + }); + expect(content).toEqual(` +buildscript { + ext { + google(); + jcenter(); + } +} +`); + }); + it('should append text into existing body ', async () => { + let content = ` +buildscript { + ext { + jcenter(); + } +} +`; + const task: MainActivityTaskType = { + type: 'main_activity', + actions: [ + { + block: 'buildscript.ext', + append: 'google();', + }, + ], + }; + content = await mainActivityTask({ + configPath: 'path/to/config', + task: task, + content, + packageName: 'test-package', + }); + expect(content).toEqual(` +buildscript { + ext { + jcenter(); + google(); + } +} +`); + }); + it('should replace text with existing body ', async () => { + let content = ` +buildscript { + ext { + jcenter(); + jcenter(); + } +} +`; + const task: MainActivityTaskType = { + type: 'main_activity', + actions: [ + { + block: 'buildscript.ext', + search: 'enter', + replace: 'google();', + }, + ], + }; + content = await mainActivityTask({ + configPath: 'path/to/config', + task: task, + content, + packageName: 'test-package', + }); + expect(content).toEqual(` +buildscript { + ext { + google(); + jcenter(); + } +} +`); + }); + it('should append text exactly with existing body ', async () => { + let content = ` +buildscript { + ext { + jcenter(); + } +} +`; + const task: MainActivityTaskType = { + type: 'main_activity', + actions: [ + { + block: 'buildscript', + after: 'ext {', + before: '}', + search: 'jcenter();', + replace: 'jcenter();', + prepend: 'google1();', + append: 'google3();', + exact: true, + }, + ], + }; + content = await mainActivityTask({ + configPath: 'path/to/config', + task: task, + content, + packageName: 'test-package', + }); + expect(content).toEqual(` +buildscript { + ext { + google1();jcenter();google3(); + } +} +`); + }); + it('should replace all text with existing body ', async () => { + let content = ` +buildscript { + ext { + jcenter(); + jcenter(); + } +} +`; + const task: MainActivityTaskType = { + type: 'main_activity', + actions: [ + { + block: 'buildscript.ext', + search: { + regex: 'jcenter\\(\\);', + flags: 'g', + }, + replace: 'google();', + }, + ], + }; + content = await mainActivityTask({ + configPath: 'path/to/config', + task: task, + content, + packageName: 'test-package', + }); + expect(content).toEqual(` +buildscript { + ext { + google(); + google(); + } +} +`); + }); + it('should replace all text with existing body exactly', async () => { + let content = ` +buildscript { + ext { + jcenter(); // some comment + jcenter(); + } +} +`; + const task: MainActivityTaskType = { + type: 'main_activity', + actions: [ + { + block: 'buildscript.ext', + search: { + regex: 'jcenter\\(\\);', + flags: 'g', + }, + exact: true, + replace: 'google();', + }, + ], + }; + content = await mainActivityTask({ + configPath: 'path/to/config', + task: task, + content, + packageName: 'test-package', + }); + expect(content).toEqual(` +buildscript { + ext { + google(); // some comment + google(); + } +} +`); + }); + it('should skip replace when ifNotPresent exists', async () => { + const content = ` +buildscript { + ext { + jcenter(); + } +} +`; + const task: MainActivityTaskType = { + type: 'main_activity', + actions: [ + { + block: 'buildscript.ext', + ifNotPresent: 'jcenter', + search: 'jcenter();', + replace: 'google();', + }, + { + block: 'buildscript.ext', + ifNotPresent: 'jcenter', + search: 'jcenter();', + replace: 'google();', + }, + ], + }; + + await mainActivityTask({ + configPath: 'path/to/config', + task: task, + content, + packageName: 'test-package', + }); + expect(mockPrompter.log.message).toHaveBeenCalledWith( + expect.stringContaining('found existing ') + ); + }); + it('should insert text after point with comment', async () => { + let content = ` +buildscript { + ext { + test1; + test3; + } +} +`; + const task: MainActivityTaskType = { + type: 'main_activity', + actions: [ + { + block: 'buildscript.ext', + after: 'test1', + prepend: 'test2;', + comment: 'test comment', + }, + ], + }; + + content = await mainActivityTask({ + configPath: 'path/to/config', + task: task, + content, + packageName: 'test-package', + }); + expect(content).toContain(`test1; + // test comment + test2; + test3;`); + }); + it('should insert text when empty', async () => { + let content = ` +buildscript {} +`; + const task: MainActivityTaskType = { + type: 'main_activity', + actions: [ + { + block: 'buildscript.ext', + after: 'test1', + prepend: 'test2;', + }, + ], + }; + + content = await mainActivityTask({ + configPath: 'path/to/config', + task: task, + content, + packageName: 'test-package', + }); + expect(content).toContain(` +buildscript { + ext { + test2; + } +} +`); + }); + it('should insert text before point', async () => { + let content = ` +buildscript { + ext { + test1; + test3; + } +} +`; + const task: MainActivityTaskType = { + type: 'main_activity', + actions: [ + { + block: 'buildscript.ext', + before: { regex: '\n.*?test3;' }, + append: 'test2;', + }, + ], + }; + + content = await mainActivityTask({ + configPath: 'path/to/config', + task: task, + content, + packageName: 'test-package', + }); + expect(content).toContain(`test1; + test2; + test3;`); + }); + it('should skip if condition not met', async () => { + let content = ` +buildscript { + ext { + test1; + test3; + } +} +`; + const task: MainActivityTaskType = { + type: 'main_activity', + actions: [ + { + when: { test: 'random' }, + block: 'buildscript.ext', + before: { regex: '\n.*?test3;' }, + append: 'test2;', + }, + ], + }; + + content = await mainActivityTask({ + configPath: 'path/to/config', + task: task, + content, + packageName: 'test-package', + }); + expect(content).not.toContain(`test1; + test2; + test3;`); + }); + it('should throw when insertion point not found with strict', async () => { + const content = ` +buildscript { + ext { + test1; + test3; + } +} +`; + const taskInsertBefore: MainActivityTaskType = { + type: 'main_activity', + actions: [ + { + block: 'buildscript.ext', + before: 'random', + append: 'test2;', + strict: true, + }, + ], + }; + const taskInsertBeforeNonStrict: MainActivityTaskType = { + type: 'main_activity', + actions: [ + { + block: 'buildscript.ext', + before: 'random', + append: 'test2;', + }, + ], + }; + + await expect( + mainActivityTask({ + configPath: 'path/to/config', + task: taskInsertBefore, + content, + packageName: 'test-package', + }) + ).rejects.toThrowError('insertion point'); + await expect( + mainActivityTask({ + configPath: 'path/to/config', + task: taskInsertBeforeNonStrict, + content, + packageName: 'test-package', + }) + ).resolves.not.toThrowError('insertion point'); + const taskInsertAfter: MainActivityTaskType = { + type: 'main_activity', + actions: [ + { + block: 'buildscript.ext', + after: 'random', + prepend: 'test2;', + strict: true, + }, + ], + }; + + const taskInsertAfterNonStrict: MainActivityTaskType = { + type: 'main_activity', + actions: [ + { + block: 'buildscript.ext', + after: 'random', + prepend: 'test2;', + }, + ], + }; + + await expect( + mainActivityTask({ + configPath: 'path/to/config', + task: taskInsertAfter, + content, + packageName: 'test-package', + }) + ).rejects.toThrowError('insertion point'); + await expect( + mainActivityTask({ + configPath: 'path/to/config', + task: taskInsertAfterNonStrict, + content, + packageName: 'test-package', + }) + ).resolves.not.toThrowError('insertion point'); + }); + it('should throw when block could not be added', async () => { + const content = ''; + mock.mockImplementationOnce(content => content); + const task: MainActivityTaskType = { + type: 'main_activity', + actions: [ + { + block: 'buildscript.ext', + prepend: 'random;', + }, + ], + }; + + await expect( + mainActivityTask({ + configPath: 'path/to/config', + task: task, + content, + packageName: 'test-package', + }) + ).rejects.toThrowError('block could not be inserted'); + }); + + describe('runTask', () => { + it('should read and write main application file', async () => { + let content = ` +buildscript { + ext { + test1; + test3; + } +} +`; + const mainActivityPath = path.resolve( + __dirname, + `../../mock-project/android/app/src/main/java/com/test/${Constants.MAIN_ACTIVITY_JAVA_FILE_NAME}` + ); + mockFs.writeFileSync(mainActivityPath, content); + const task: MainActivityTaskType = { + type: 'main_activity', + actions: [ + { + block: 'buildscript.ext', + prepend: 'test2;', + }, + ], + }; + + await runTask({ + configPath: 'path/to/config', + task: task, + packageName: 'test-package', + }); + content = mockFs.readFileSync(mainActivityPath); + // @ts-ignore + expect(content).toContain(task.actions[0].prepend); + }); + it('should read and write main application kotlin file', async () => { + let content = ` +buildscript { + ext { + test1; + test3; + } +} +`; + const mainActivityPath = path.resolve( + __dirname, + `../../mock-project/android/app/src/main/java/com/test/${Constants.MAIN_ACTIVITY_KT_FILE_NAME}` + ); + mockFs.writeFileSync(mainActivityPath, content); + const task: MainActivityTaskType = { + type: 'main_activity', + lang: 'kotlin', + actions: [ + { + block: 'buildscript.ext', + prepend: 'test2;', + }, + ], + }; + + await runTask({ + configPath: 'path/to/config', + task: task, + packageName: 'test-package', + }); + content = mockFs.readFileSync(mainActivityPath); + // @ts-ignore + expect(content).toContain(task.actions[0].prepend); + }); + it('should throw when main application does not exist', async () => { + let task: MainActivityTaskType = { + type: 'main_activity', + actions: [ + { + block: 'buildscript.ext', + prepend: 'test2;', + }, + ], + }; + + await expect( + runTask({ + configPath: 'path/to/config', + task: task, + packageName: 'test-package', + }) + ).rejects.toThrowError('MainActivity.java file not found'); + task = { + type: 'main_activity', + lang: 'kotlin', + actions: [ + { + block: 'buildscript.ext', + prepend: 'test2;', + }, + ], + }; + + await expect( + runTask({ + configPath: 'path/to/config', + task: task, + packageName: 'test-package', + }) + ).rejects.toThrowError('MainActivity.kt file not found'); + }); + }); +}); diff --git a/src/__tests__/unit/utils/runTask.spec.ts b/src/__tests__/unit/utils/runTask.spec.ts index 03ad88e..f853a20 100644 --- a/src/__tests__/unit/utils/runTask.spec.ts +++ b/src/__tests__/unit/utils/runTask.spec.ts @@ -17,6 +17,9 @@ const mocks = { main_application: { runTask: jest.fn(), }, + main_activity: { + runTask: jest.fn(), + }, xcode: { runTask: jest.fn(), }, @@ -38,6 +41,7 @@ jest.mock('../../../tasks/plistTask', () => mocks.plist); jest.mock('../../../tasks/buildGradleTask', () => mocks.build_gradle); jest.mock('../../../tasks/settingsGradleTask', () => mocks.settings_gradle); jest.mock('../../../tasks/mainApplicationTask', () => mocks.main_application); +jest.mock('../../../tasks/mainActivityTask', () => mocks.main_activity); jest.mock('../../../tasks/xcode/xcodeTask', () => mocks.xcode); jest.mock('../../../tasks/androidManifestTask', () => mocks.android_manifest); jest.mock('../../../tasks/podFileTask', () => mocks.podfile); diff --git a/src/constants.ts b/src/constants.ts index 1b0327d..34d6500 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -16,6 +16,8 @@ export const Constants = { SETTINGS_GRADLE_FILE_NAME: 'settings.gradle', MAIN_APPLICATION_JAVA_FILE_NAME: 'MainApplication.java', MAIN_APPLICATION_KT_FILE_NAME: 'MainApplication.kt', + MAIN_ACTIVITY_JAVA_FILE_NAME: 'MainActivity.java', + MAIN_ACTIVITY_KT_FILE_NAME: 'MainActivity.kt', POD_FILE_NAME: 'Podfile', GITIGNORE_FILE_NAME: '.gitignore', XCODEPROJ_EXT: '.xcodeproj', diff --git a/src/schema/integrate.schema.json b/src/schema/integrate.schema.json index 23a629e..5bc49f7 100644 --- a/src/schema/integrate.schema.json +++ b/src/schema/integrate.schema.json @@ -489,6 +489,44 @@ ], "type": "object" }, + "MainActivityTaskType": { + "additionalProperties": false, + "properties": { + "actions": { + "items": { + "$ref": "#/definitions/ContentModifierType" + }, + "type": "array" + }, + "label": { + "type": "string" + }, + "lang": { + "$ref": "#/definitions/AndroidCodeType" + }, + "name": { + "type": "string" + }, + "postInfo": { + "$ref": "#/definitions/TextOrTitleMessage" + }, + "preInfo": { + "$ref": "#/definitions/TextOrTitleMessage" + }, + "type": { + "const": "main_activity", + "type": "string" + }, + "when": { + "$ref": "#/definitions/AnyObject" + } + }, + "required": [ + "actions", + "type" + ], + "type": "object" + }, "MainApplicationTaskType": { "additionalProperties": false, "properties": { @@ -553,6 +591,9 @@ { "$ref": "#/definitions/MainApplicationTaskType" }, + { + "$ref": "#/definitions/MainActivityTaskType" + }, { "$ref": "#/definitions/AndroidManifestTaskType" }, diff --git a/src/schema/upgrade.schema.json b/src/schema/upgrade.schema.json index bdc7729..d95214a 100644 --- a/src/schema/upgrade.schema.json +++ b/src/schema/upgrade.schema.json @@ -489,6 +489,44 @@ ], "type": "object" }, + "MainActivityTaskType": { + "additionalProperties": false, + "properties": { + "actions": { + "items": { + "$ref": "#/definitions/ContentModifierType" + }, + "type": "array" + }, + "label": { + "type": "string" + }, + "lang": { + "$ref": "#/definitions/AndroidCodeType" + }, + "name": { + "type": "string" + }, + "postInfo": { + "$ref": "#/definitions/TextOrTitleMessage" + }, + "preInfo": { + "$ref": "#/definitions/TextOrTitleMessage" + }, + "type": { + "const": "main_activity", + "type": "string" + }, + "when": { + "$ref": "#/definitions/AnyObject" + } + }, + "required": [ + "actions", + "type" + ], + "type": "object" + }, "MainApplicationTaskType": { "additionalProperties": false, "properties": { @@ -553,6 +591,9 @@ { "$ref": "#/definitions/MainApplicationTaskType" }, + { + "$ref": "#/definitions/MainActivityTaskType" + }, { "$ref": "#/definitions/AndroidManifestTaskType" }, diff --git a/src/tasks/mainActivityTask.ts b/src/tasks/mainActivityTask.ts new file mode 100644 index 0000000..c432082 --- /dev/null +++ b/src/tasks/mainActivityTask.ts @@ -0,0 +1,190 @@ +import fs from 'fs'; +import { globSync } from 'glob'; +import { Constants } from '../constants'; +import { + AndroidCodeType, + BlockContentType, + MainActivityTaskType, +} from '../types/mod.types'; +import { applyContentModification } from '../utils/applyContentModification'; +import { findClosingTagIndex } from '../utils/findClosingTagIndex'; +import { getErrMessage } from '../utils/getErrMessage'; +import { getProjectPath } from '../utils/getProjectPath'; +import { satisfies } from '../utils/satisfies'; +import { setState } from '../utils/setState'; +import { stringSplice } from '../utils/stringSplice'; +import { variables } from '../variables'; + +export async function mainActivityTask(args: { + configPath: string; + packageName: string; + content: string; + task: MainActivityTaskType; +}): Promise { + let { content } = args; + const { task, configPath, packageName } = args; + + for (const action of task.actions) { + variables.set('CONTENT', content); + if (action.when && !satisfies(variables.getStore(), action.when)) { + setState(action.name, { + state: 'skipped', + reason: 'when', + error: false, + }); + continue; + } + + setState(action.name, { + state: 'progress', + error: false, + }); + try { + content = await applyContentModification({ + action, + findOrCreateBlock, + configPath, + packageName, + content, + indentation: 2, + }); + + setState(action.name, { + state: 'done', + error: false, + }); + } catch (e) { + setState(action.name, { + state: 'error', + reason: getErrMessage(e), + error: true, + }); + throw e; + } + } + + return content; +} + +function findOrCreateBlock( + content: string, + block: string +): { + blockContent: BlockContentType; + content: string; +} { + let blockContent = { + start: 0, + end: content.length, + match: content, + space: '', + justCreated: false, + }; + + const blockPath = block.split('.'); + let contentOffset = 0; + + for (let i = 0; i < blockPath.length; i++) { + const matcherRegex = new RegExp(`^((\\s+)?)${blockPath[i]}\\s+\\{`, 'ms'); + let blockStart = matcherRegex.exec(blockContent.match); + + const justCreated = !blockStart; + if (!blockStart) { + const blockName = blockPath[i]; + // create block in block + const space = ' '.repeat(2 * i); + const previousSpace = ' '.repeat(Math.max(0, 2 * (i - 1))); + const newBlock = `${space}${blockName} {}`; + const codeToInsert = ` +${newBlock} +${previousSpace}`; + const contentLengthBeforeInsert = content.length; + content = stringSplice(content, blockContent.end, 0, codeToInsert); + if (codeToInsert.length && contentLengthBeforeInsert < content.length) { + blockContent.match += codeToInsert; + blockContent.end += codeToInsert.length; + blockStart = matcherRegex.exec(blockContent.match); + } + } + if (!blockStart) { + throw new Error('block could not be inserted, something wrong?'); + } + + const blockEndIndex = findClosingTagIndex( + content, + contentOffset + blockStart.index + blockStart[0].length + ); + const blockBody = content.substring( + contentOffset + blockStart.index + blockStart[0].length, + blockEndIndex + ); + blockContent = { + start: contentOffset + blockStart.index + blockStart[0].length, + end: blockEndIndex, + match: blockBody, + justCreated, + space: ' '.repeat(2 * i), + }; + contentOffset += blockStart.index + blockStart[0].length; + } + + return { + blockContent, + content, + }; +} + +function getMainActivityPath(lang?: AndroidCodeType) { + const projectPath = getProjectPath(); + + const mainActivityPath = globSync( + [ + projectPath, + 'android', + 'app', + 'src', + 'main', + 'java', + '**', + lang === 'kotlin' + ? Constants.MAIN_ACTIVITY_KT_FILE_NAME + : Constants.MAIN_ACTIVITY_JAVA_FILE_NAME, + ].join('/'), + { nodir: true } + )[0]; + if (!mainActivityPath) + throw new Error( + `MainActivity.${lang === 'kotlin' ? 'kt' : 'java'} file not found` + ); + return mainActivityPath; +} + +function readMainActivityContent(lang?: AndroidCodeType) { + const mainActivityPath = getMainActivityPath(lang); + return fs.readFileSync(mainActivityPath, 'utf-8'); +} + +function writeAppDelegateContent( + content: string, + lang?: AndroidCodeType +): void { + const mainActivityPath = getMainActivityPath(lang); + return fs.writeFileSync(mainActivityPath, content, 'utf-8'); +} + +export async function runTask(args: { + configPath: string; + packageName: string; + task: MainActivityTaskType; +}): Promise { + let content = readMainActivityContent(args.task.lang); + + content = await mainActivityTask({ + ...args, + content, + }); + + writeAppDelegateContent(content, args.task.lang); +} + +export const summary = 'MainActivity modification'; diff --git a/src/types/mod.types.ts b/src/types/mod.types.ts index 73aebd0..89a121d 100644 --- a/src/types/mod.types.ts +++ b/src/types/mod.types.ts @@ -177,6 +177,14 @@ export type MainApplicationTaskType = ModTaskBase & export type AndroidCodeType = 'java' | 'kotlin'; +// main activity + +export type MainActivityTaskType = ModTaskBase & + ActionsType & { + type: 'main_activity'; + lang?: AndroidCodeType; + }; + // android manifest task export type AndroidManifestTaskType = ModTaskBase & @@ -397,6 +405,7 @@ export type ModTask = | BuildGradleTaskType | SettingsGradleTaskType | MainApplicationTaskType + | MainActivityTaskType | AndroidManifestTaskType | StringsXmlTaskType | NotificationServiceTaskType diff --git a/src/utils/taskManager.ts b/src/utils/taskManager.ts index c8beb0e..a92a609 100644 --- a/src/utils/taskManager.ts +++ b/src/utils/taskManager.ts @@ -12,6 +12,7 @@ import * as prompt from '../tasks/promptTask'; import * as notification_service from '../tasks/notificationServiceTask'; import * as notification_view_controller from '../tasks/notificationViewControllerTask'; import * as main_application from '../tasks/mainApplicationTask'; +import * as main_activity from '../tasks/mainActivityTask'; import * as settings_gradle from '../tasks/settingsGradleTask'; import * as shell from '../tasks/shellTask'; import { ModTask } from '../types/mod.types'; @@ -30,6 +31,7 @@ const task: Record = { notification_service, notification_view_controller, main_application, + main_activity, settings_gradle, strings_xml, shell, diff --git a/website/docs/for-developers/guides/task-types/android-tasks/main-activity.md b/website/docs/for-developers/guides/task-types/android-tasks/main-activity.md new file mode 100644 index 0000000..1262313 --- /dev/null +++ b/website/docs/for-developers/guides/task-types/android-tasks/main-activity.md @@ -0,0 +1,65 @@ +--- +sidebar_position: 5 +title: MainActivity.java/kt +--- +# MainActivity Task Configuration (`main_activity`) +_Modify MainActivity java or kt file_ + +The `main_activity` task is designed to facilitate modifications to the `MainActivity` java or kotlin files in Android projects. It is the main entry file of the android application. This task provides the flexibility to make changes to different sections of the `MainActivity` java or kotlin file. + +## Task Properties + +| Property | Type | Description | +|:---------|:------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| type | "main_activity", required | Specifies the task type, which should be set to "main_activity" for this task. | +| name | string | An optional name for the task. If provided, the task state will be saved as a variable. Visit [Task and Action States](../../states) page to learn more. | +| label | string | An optional label or description for the task. | +| when | object | Visit [Conditional Tasks and Actions](../../when) page to learn how to execute task conditionally. | +| lang | "java" (default) or "kotlin" | Specifies the language of the file, distinguishing between the java and kt file. It helps determine which MainActivity file to modify during the configuration process. "java" modifies `MainActivity.java` file and "kotlin" modifies `MainActivity.kt` file. | +| actions | Array\<[Action](#action-properties)\>, required | An array of action items that define the modifications to be made in the file. Each action item contains the following fields: | + +## Action Properties + +### Common properties + +| Property | Type | Description | +|:-----------|:-------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| name | string | An optional name for the task. If provided, the task state will be saved as a variable. Visit [Task and Action States](../../states) page to learn more. | +| when | object | Visit [Conditional Tasks and Actions](../../when) page to learn how to execute action conditionally. | + +### Context reduction properties + +| Property | Type | Description | +|:-----------|:-------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| before | string or `{regex: string, flags: string}` | Text or code that is used to specify a point within the context where text should be inserted before. It can be a string or an object with a `regex` and `flags` field to perform a regex-based search. | +| after | string or `{regex: string, flags: string}` | Text or code that is used to specify a point within the context where text should be inserted after. It can be a string or an object with a `regex` and `flags` field to perform a regex-based search. | +| search | string or `{regex: string, flags: string}` | A string or object (with regex and flags) that narrows the context to a specific text within the method or file. | + +### Context modification properties + +| Property | Type | Description | +|:----------|:---------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| prepend | string or `{file: string}` | Text or code to prepend at the beginning of the specified context. It can be a string or an object with a `file` field that points to a file containing the code to prepend. | +| append | string or `{file: string}` | Text or code to append at the end of the specified context. It can be a string or an object with a `file` field that points to a file containing the code to append. | +| replace | string or `{file: string}` | Text or code to replace the entire specified context. It can be a string or an object with a `file` field that points to a file containing the code to replace. | + +### Other properties + +| Property | Type | Description | +|:---------------|:--------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| exact | boolean | A boolean flag that modifies the whitespace and new line management. | +| strict | boolean | Specifies the behavior of the `before` and `after` fields. If set to `true`, the task will throw an error if the text in the `before` or `after` field is not found in the context, otherwise, it will ignore the field. | +| ifNotPresent | string | Indicates that the task should only be executed if the specified text or code is not present within the specified context. | +| comment | string | An optional comment to add before the inserted code or text. The comment is purely informational and does not affect the code's functionality. | + +## Example + +```yaml +type: main_activity +label: "Adding import" +lang: "kotlin", +actions: + - prepend: import com.some.lib +``` + +In this example, the `main_activity` task adds an import to the file. diff --git a/website/package-lock.json b/website/package-lock.json index 1570d41..9480ad0 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -12781,12 +12781,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/search-insights": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.13.0.tgz", - "integrity": "sha512-Orrsjf9trHHxFRuo9/rzm0KIWmgzE8RMlZMzuhZOJ01Rnz3D0YBAe+V6473t6/H6c7irs6Lt48brULAiRWb3Vw==", - "peer": true - }, "node_modules/section-matter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", @@ -13823,19 +13817,6 @@ "is-typedarray": "^1.0.0" } }, - "node_modules/typescript": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", - "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==", - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",