Compare commits
13 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
884d70ed24 | ||
|
|
f8d2716ad7 | ||
|
|
776a656667 | ||
|
|
f830338640 | ||
|
|
ce66105d18 | ||
|
|
f488718b51 | ||
|
|
d17516e8c7 | ||
|
|
dba4aef5fd | ||
|
|
a807f3a528 | ||
|
|
f9b609c5d1 | ||
|
|
6d80244974 | ||
|
|
747982afcb | ||
|
|
7872b619b2 |
115
.eslintrc.json
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
{
|
||||
"extends": [
|
||||
"./eslint/eslint-mattermost",
|
||||
"./eslint/eslint-react",
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:react-hooks/recommended"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": [
|
||||
"@typescript-eslint",
|
||||
"import"
|
||||
],
|
||||
"settings": {
|
||||
"react": {
|
||||
"pragma": "React",
|
||||
"version": "17.0"
|
||||
}
|
||||
},
|
||||
"env": {
|
||||
"jest": true
|
||||
},
|
||||
"globals": {
|
||||
"__DEV__": true
|
||||
},
|
||||
"rules": {
|
||||
"eol-last": ["error", "always"],
|
||||
"global-require": 0,
|
||||
"no-undefined": 0,
|
||||
"no-shadow": "off",
|
||||
"react/display-name": [2, { "ignoreTranspilerName": false }],
|
||||
"react/jsx-filename-extension": 0,
|
||||
"react-hooks/exhaustive-deps": "warn",
|
||||
"camelcase": [
|
||||
0,
|
||||
{
|
||||
"properties": "never"
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/ban-types": 0,
|
||||
"@typescript-eslint/no-non-null-assertion": 0,
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
2,
|
||||
{
|
||||
"vars": "all",
|
||||
"args": "after-used"
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/no-shadow": ["error"],
|
||||
"@typescript-eslint/no-explicit-any": "warn",
|
||||
"no-use-before-define": "off",
|
||||
"@typescript-eslint/no-use-before-define": 0,
|
||||
"@typescript-eslint/no-var-requires": 0,
|
||||
"@typescript-eslint/explicit-function-return-type": 0,
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
"no-underscore-dangle": "off",
|
||||
"indent": [2, 4, {"SwitchCase": 1}],
|
||||
"key-spacing": [2, {
|
||||
"singleLine": {
|
||||
"beforeColon": false,
|
||||
"afterColon": true
|
||||
}}],
|
||||
"@typescript-eslint/member-delimiter-style": 2,
|
||||
"@typescript-eslint/no-unsafe-declaration-merging": "off",
|
||||
"import/order": [
|
||||
2,
|
||||
{
|
||||
"groups": ["builtin", "external", "parent", "sibling", "index", "type"],
|
||||
"newlines-between": "always",
|
||||
"pathGroups": [
|
||||
{
|
||||
"pattern": "{@(@actions|@app|@assets|@calls|@client|@components|@constants|@context|@database|@helpers|@hooks|@init|@managers|@queries|@screens|@selectors|@share|@store|@telemetry|@typings|@test|@utils)/**,@(@constants|@i18n|@store|@websocket)}",
|
||||
"group": "external",
|
||||
"position": "after"
|
||||
},
|
||||
{
|
||||
"pattern": "app/**",
|
||||
"group": "parent",
|
||||
"position": "before"
|
||||
}
|
||||
],
|
||||
"alphabetize": {
|
||||
"order": "asc",
|
||||
"caseInsensitive": true
|
||||
},
|
||||
"pathGroupsExcludedImportTypes": ["type"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.test.js", "*.test.jsx"],
|
||||
"env": {
|
||||
"jest": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["detox/e2e/**"],
|
||||
"globals": {
|
||||
"by": true,
|
||||
"detox": true,
|
||||
"device": true,
|
||||
"element": true,
|
||||
"waitFor": true
|
||||
},
|
||||
"rules": {
|
||||
"func-names": 0,
|
||||
"import/no-unresolved": 0,
|
||||
"max-nested-callbacks": 0,
|
||||
"no-process-env": 0,
|
||||
"no-unused-expressions": 0
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
18
.github/actions/generate-specs/split-tests.js
vendored
|
|
@ -52,22 +52,20 @@ class Specs {
|
|||
}
|
||||
|
||||
generateSplits() {
|
||||
const chunkSize = Math.floor(this.rawFiles.length / this.parallelism);
|
||||
let remainder = this.rawFiles.length % this.parallelism;
|
||||
const chunkSize = Math.ceil(this.rawFiles.length / this.parallelism);
|
||||
let runNo = 1;
|
||||
let start = 0;
|
||||
|
||||
for (let i = 0; i < this.parallelism; i++) {
|
||||
let end = start + chunkSize + (remainder > 0 ? 1 : 0);
|
||||
const fileGroup = this.rawFiles.slice(start, end).join(' ');
|
||||
for (let i = 0; i < this.rawFiles.length; i += chunkSize) {
|
||||
const end = Math.min(i + chunkSize, this.rawFiles.length);
|
||||
const fileGroup = this.rawFiles.slice(i, end).join(' ');
|
||||
const specFileGroup = new SpecGroup(runNo.toString(), fileGroup, this.deviceInfo);
|
||||
this.groupedFiles.push(specFileGroup);
|
||||
|
||||
start = end;
|
||||
runNo++;
|
||||
if (remainder > 0) {
|
||||
remainder--;
|
||||
if (end === this.rawFiles.length) {
|
||||
break;
|
||||
}
|
||||
|
||||
runNo++;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,13 +12,6 @@ runs:
|
|||
- name: ci/prepare-mobile-build
|
||||
uses: ./.github/actions/prepare-mobile-build
|
||||
|
||||
- name: ci/apply-16kb-pagesize-patch
|
||||
shell: bash
|
||||
run: |
|
||||
echo "::group::Apply 16KB page size compatibility patch"
|
||||
npm run apply-16kb-pagesize-patch
|
||||
echo "::endgroup::"
|
||||
|
||||
- name: ci/setup-java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
|
|
|
|||
53
.github/actions/prepare-ios-build/action.yaml
vendored
|
|
@ -1,61 +1,24 @@
|
|||
name: prepare-ios-build
|
||||
description: Action to prepare environment for ios build
|
||||
|
||||
inputs:
|
||||
intune-enabled:
|
||||
description: 'Enable Intune MAM features (requires Xcode 26.1+)'
|
||||
required: false
|
||||
default: 'false'
|
||||
intune-ssh-private-key:
|
||||
description: 'SSH private key for Intune submodule repository access (required if intune-enabled is true)'
|
||||
required: false
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: ci/setup-xcode
|
||||
uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd # v1.6.0
|
||||
with:
|
||||
xcode-version: '26.1'
|
||||
|
||||
- name: ci/prepare-mobile-build
|
||||
uses: ./.github/actions/prepare-mobile-build
|
||||
|
||||
- name: ci/setup-intune
|
||||
if: inputs.intune-enabled == 'true'
|
||||
uses: ./.github/actions/setup-intune
|
||||
with:
|
||||
ssh-private-key: ${{ inputs.intune-ssh-private-key }}
|
||||
|
||||
- name: Get Intune submodule commit hash
|
||||
if: inputs.intune-enabled == 'true'
|
||||
id: intune-hash
|
||||
shell: bash
|
||||
run: |
|
||||
if [ -e "libraries/@mattermost/intune/.git" ]; then
|
||||
INTUNE_HASH=$(cd libraries/@mattermost/intune && git rev-parse --short HEAD)
|
||||
echo "hash=${INTUNE_HASH}" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "hash=none" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Cache Pods
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
with:
|
||||
path: |
|
||||
ios/Pods
|
||||
libraries/@mattermost/intune/ios/Frameworks
|
||||
key: ${{ runner.os }}-pods-v4-intune-${{ inputs.intune-enabled }}-${{ steps.intune-hash.outputs.hash }}-${{ hashFiles('ios/Podfile.lock') }}-${{ github.ref_name }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pods-v4-intune-${{ inputs.intune-enabled }}-${{ steps.intune-hash.outputs.hash }}-${{ hashFiles('ios/Podfile.lock') }}-
|
||||
|
||||
- name: ci/install-pods-dependencies
|
||||
shell: bash
|
||||
env:
|
||||
INTUNE_ENABLED: ${{ inputs.intune-enabled }}
|
||||
run: |
|
||||
echo "::group::install-pods-dependencies"
|
||||
echo "INTUNE_ENABLED=$INTUNE_ENABLED"
|
||||
npm run ios-gems
|
||||
npm run pod-install
|
||||
echo "::endgroup::"
|
||||
|
||||
- name: Cache Pods
|
||||
uses: actions/cache@0c907a75c2c80ebcb7f088228285e798b750cf8f # v4.2.1
|
||||
with:
|
||||
path: Pods
|
||||
key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pods-
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ runs:
|
|||
using: composite
|
||||
steps:
|
||||
# The required ruby version is mentioned in '.ruby-version'
|
||||
- uses: ruby/setup-ruby@cb0fda56a307b8c78d38320cd40d9eb22a3bf04e # v1.242.0
|
||||
- uses: ruby/setup-ruby@32110d4e311bd8996b2a82bf2a43b714ccc91777 # v1.221.0
|
||||
|
||||
- name: ci/setup-fastlane-dependencies
|
||||
shell: bash
|
||||
|
|
@ -16,7 +16,7 @@ runs:
|
|||
working-directory: ./fastlane
|
||||
|
||||
- name: Cache Ruby gems
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
uses: actions/cache@0c907a75c2c80ebcb7f088228285e798b750cf8f # v4.2.1
|
||||
with:
|
||||
path: vendor/bundle
|
||||
key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ runs:
|
|||
using: composite
|
||||
steps:
|
||||
- name: ci/setup-node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: "npm"
|
||||
|
|
@ -22,7 +22,7 @@ runs:
|
|||
echo "::endgroup::"
|
||||
|
||||
- name: Cache Node.js modules
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
uses: actions/cache@0c907a75c2c80ebcb7f088228285e798b750cf8f # v4.2.1
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||
|
|
|
|||
27
.github/actions/setup-intune-ssh-key/action.yaml
vendored
|
|
@ -1,27 +0,0 @@
|
|||
# Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
# See LICENSE.txt for license information.
|
||||
|
||||
name: Setup Intune SSH Key
|
||||
description: Configure SSH private key for accessing Intune submodule repository
|
||||
|
||||
inputs:
|
||||
ssh-private-key:
|
||||
description: 'SSH private key for Intune submodule repository access'
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Configure Intune submodule SSH key
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
chmod 700 ~/.ssh
|
||||
|
||||
# Configure Intune submodule SSH key (separate from main repo key)
|
||||
INTUNE_KEY_PATH="${HOME}/.ssh/intune_submodule_ed25519"
|
||||
echo -e '${{ inputs.ssh-private-key }}' > ${INTUNE_KEY_PATH}
|
||||
chmod 0600 ${INTUNE_KEY_PATH}
|
||||
ssh-keygen -y -f ${INTUNE_KEY_PATH} > ${INTUNE_KEY_PATH}.pub
|
||||
|
||||
echo "✅ Intune submodule SSH key configured"
|
||||
60
.github/actions/setup-intune/action.yaml
vendored
|
|
@ -1,60 +0,0 @@
|
|||
# Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
# See LICENSE.txt for license information.
|
||||
|
||||
name: Setup Intune
|
||||
description: Initialize Intune submodule and link module for internal builds with MAM features
|
||||
|
||||
inputs:
|
||||
ssh-private-key:
|
||||
description: 'SSH private key for Intune submodule repository access'
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Configure SSH for Intune submodule
|
||||
uses: ./.github/actions/setup-intune-ssh-key
|
||||
with:
|
||||
ssh-private-key: ${{ inputs.ssh-private-key }}
|
||||
|
||||
- name: Initialize Intune submodule
|
||||
shell: bash
|
||||
env:
|
||||
GIT_SSH_COMMAND: ssh -i $HOME/.ssh/intune_submodule_ed25519 -o IdentitiesOnly=yes -o StrictHostKeyChecking=no
|
||||
run: |
|
||||
echo "::group::Initialize Intune submodule"
|
||||
# Check if submodule is already initialized
|
||||
if [ -e "libraries/@mattermost/intune/.git" ]; then
|
||||
echo "ℹ️ Intune submodule already initialized, updating..."
|
||||
git submodule update --remote libraries/@mattermost/intune
|
||||
else
|
||||
echo "📥 Initializing Intune submodule..."
|
||||
git submodule update --init --recursive libraries/@mattermost/intune
|
||||
fi
|
||||
|
||||
# Display submodule info
|
||||
cd libraries/@mattermost/intune
|
||||
echo "📦 Intune submodule initialized:"
|
||||
echo " Branch: $(git rev-parse --abbrev-ref HEAD)"
|
||||
echo " Commit: $(git rev-parse --short HEAD)"
|
||||
echo " Remote: $(git remote get-url origin)"
|
||||
cd ../..
|
||||
|
||||
echo "✅ Intune submodule ready"
|
||||
echo "::endgroup::"
|
||||
|
||||
- name: Link Intune module to node_modules
|
||||
shell: bash
|
||||
run: |
|
||||
echo "::group::Link Intune module"
|
||||
# Install the module without saving to package.json
|
||||
npm install --install-links --no-save ./libraries/@mattermost/intune
|
||||
|
||||
# Verify symlink was created
|
||||
if [ -L "node_modules/@mattermost/intune" ] || [ -d "node_modules/@mattermost/intune" ]; then
|
||||
echo "✅ Intune module linked to node_modules/@mattermost/intune"
|
||||
else
|
||||
echo "❌ Failed to link Intune module"
|
||||
exit 1
|
||||
fi
|
||||
echo "::endgroup::"
|
||||
23
.github/actions/setup-ssh-key/action.yaml
vendored
|
|
@ -1,23 +0,0 @@
|
|||
# Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
# See LICENSE.txt for license information.
|
||||
|
||||
name: Setup SSH Key
|
||||
description: Configure SSH private key for accessing private repositories
|
||||
|
||||
inputs:
|
||||
ssh-private-key:
|
||||
description: 'SSH private key for repository access'
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Configure SSH private key
|
||||
shell: bash
|
||||
run: |
|
||||
SSH_KEY_PATH=~/.ssh/id_ed25519
|
||||
mkdir -p ~/.ssh
|
||||
echo -e '${{ inputs.ssh-private-key }}' > ${SSH_KEY_PATH}
|
||||
chmod 0600 ${SSH_KEY_PATH}
|
||||
ssh-keygen -y -f ${SSH_KEY_PATH} > ${SSH_KEY_PATH}.pub
|
||||
echo "✅ SSH key configured"
|
||||
146
.github/actions/test-coverage/action.yml
vendored
|
|
@ -1,146 +0,0 @@
|
|||
# This action is used to test the coverage of the mobile repo
|
||||
# It will download the coverage result from the main branch and compare it with the current branch
|
||||
# If the coverage is lower than the main branch (1% or more), it will post a warning along with
|
||||
# the coverage report to the PR.
|
||||
# If this action is run on the main branch, it will upload the coverage result to the main branch
|
||||
# It will also generate a run id and cache it, so that the next time the action is run in the PR,
|
||||
# it will use the cached run id to download the coverage result from the main branch
|
||||
|
||||
name: test-coverage
|
||||
description: Test coverage tracking for mobile repo
|
||||
|
||||
inputs:
|
||||
run_id:
|
||||
description: The run id to use to download the coverage result
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: ci/prepare-node-deps
|
||||
uses: ./.github/actions/prepare-node-deps
|
||||
|
||||
- name: ci/get-last-run-id
|
||||
if: github.event_name == 'pull_request'
|
||||
id: get-last-run-id
|
||||
uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684
|
||||
continue-on-error: true
|
||||
with:
|
||||
path: run-id.txt
|
||||
key: last-run-id-${{ inputs.run_id }}
|
||||
restore-keys: |
|
||||
last-run-id-
|
||||
|
||||
- name: ci/set-pr-condition
|
||||
if: github.event_name == 'pull_request'
|
||||
shell: bash
|
||||
run: |
|
||||
echo "::group::set-pr-condition"
|
||||
if [ -f "run-id.txt" ]; then
|
||||
echo "HAS_MAIN_RUN_ID=true" >> $GITHUB_ENV
|
||||
echo "LAST_RUN_ID=$(cat run-id.txt)" >> $GITHUB_ENV
|
||||
fi
|
||||
echo "::endgroup::"
|
||||
|
||||
- name: ci/download-main-coverage
|
||||
if: env.HAS_MAIN_RUN_ID == 'true'
|
||||
continue-on-error: true
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: test-coverage-result-${{ env.LAST_RUN_ID }}
|
||||
path: main-coverage/
|
||||
github-token: ${{ github.token }}
|
||||
run-id: ${{ env.LAST_RUN_ID }}
|
||||
|
||||
- name: ci/check-coverage-download
|
||||
if: env.HAS_MAIN_RUN_ID == 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
echo "::group::check-coverage-download"
|
||||
if [ -f "main-coverage/coverage-summary.json" ]; then
|
||||
echo "HAS_COVERAGE_FROM_MAIN=true" >> $GITHUB_ENV
|
||||
fi
|
||||
echo "::endgroup::"
|
||||
|
||||
- name: ci/read-coverage
|
||||
if: env.HAS_COVERAGE_FROM_MAIN == 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
echo "::group::read-coverage"
|
||||
./scripts/read-coverage.sh ./main-coverage/coverage-summary.json
|
||||
echo "::endgroup::"
|
||||
|
||||
- name: ci/run-tests-with-coverage
|
||||
shell: bash
|
||||
run: |
|
||||
echo "::group::run-tests"
|
||||
npm run test:ci:coverage
|
||||
echo "::endgroup::"
|
||||
|
||||
- name: ci/compare-coverage
|
||||
if: env.HAS_COVERAGE_FROM_MAIN == 'true'
|
||||
id: compare-coverage
|
||||
shell: bash
|
||||
run: |
|
||||
echo "::group::compare-coverage"
|
||||
echo "IS_FORK=false" >> $GITHUB_ENV
|
||||
output=$(./scripts/compare-coverage.sh \
|
||||
./main-coverage \
|
||||
./coverage \
|
||||
${{ github.event.pull_request.number }})
|
||||
echo "report<<EOF" >> $GITHUB_ENV
|
||||
echo "$output" >> $GITHUB_ENV
|
||||
echo "EOF" >> $GITHUB_ENV
|
||||
if [ "${{ github.event.pull_request.head.repo.fork }}" = "true" ]; then
|
||||
echo "IS_FORK=true" >> $GITHUB_ENV
|
||||
echo "::warning::PR is from a fork. Coverage report will be displayed in the workflow summary."
|
||||
echo "### Coverage Report from Fork" >> $GITHUB_STEP_SUMMARY
|
||||
echo "$output" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
echo "::endgroup::"
|
||||
|
||||
- name: ci/post-coverage-report
|
||||
if: env.HAS_COVERAGE_FROM_MAIN == 'true' && env.IS_FORK == 'false'
|
||||
uses: thollander/actions-comment-pull-request@v3
|
||||
with:
|
||||
message: ${{ env.report }}
|
||||
comment-tag: coverage-report
|
||||
create-if-not-exists: true
|
||||
|
||||
- name: ci/exit-on-test-coverage-failure
|
||||
if: env.HAS_COVERAGE_FROM_MAIN == 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
echo "::group::exit-on-test-coverage-failure"
|
||||
exit ${{ steps.compare-coverage.outputs.status }}
|
||||
echo "::endgroup::"
|
||||
|
||||
- name: ci/upload-coverage
|
||||
if: github.ref_name == 'main'
|
||||
id: upload-coverage
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-coverage-result-${{ inputs.run_id }}
|
||||
path: coverage/coverage-summary.json
|
||||
overwrite: true
|
||||
github-token: ${{ github.token }}
|
||||
|
||||
- name: ci/set-upload-success
|
||||
if: github.ref_name == 'main' && steps.upload-coverage.outcome == 'success'
|
||||
shell: bash
|
||||
run: echo "UPLOAD_SUCCESS=true" >> $GITHUB_ENV
|
||||
|
||||
- name: ci/generate-run-id-file
|
||||
if: env.UPLOAD_SUCCESS == 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
echo "::group::generate-last-run-id"
|
||||
echo "${{ inputs.run_id }}" > run-id.txt
|
||||
echo "::endgroup::"
|
||||
|
||||
- name: ci/cache-run-id-file
|
||||
if: env.UPLOAD_SUCCESS == 'true'
|
||||
uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684
|
||||
with:
|
||||
path: run-id.txt
|
||||
key: last-run-id-${{ inputs.run_id }}
|
||||
6
.github/actions/test/action.yaml
vendored
|
|
@ -13,16 +13,12 @@ runs:
|
|||
echo "::group::check-styles"
|
||||
npm run check
|
||||
echo "::endgroup::"
|
||||
|
||||
- name: ci/run-tests
|
||||
# main and PR will run test:coverage
|
||||
if: startsWith(github.ref_name, 'release-')
|
||||
shell: bash
|
||||
run: |
|
||||
echo "::group::run-tests"
|
||||
npm run test:ci
|
||||
npm test
|
||||
echo "::endgroup::"
|
||||
|
||||
- name: ci/check-i18n
|
||||
shell: bash
|
||||
run: |
|
||||
|
|
|
|||
18
.github/dependabot.yaml
vendored
|
|
@ -1,18 +0,0 @@
|
|||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directories:
|
||||
- "/.github/workflows"
|
||||
- "/.github/actions/**/*"
|
||||
reviewers:
|
||||
- "mattermost/core-build-engineers"
|
||||
open-pull-requests-limit: 5
|
||||
groups:
|
||||
Github Actions updates:
|
||||
applies-to: version-updates
|
||||
dependency-type: production
|
||||
schedule:
|
||||
# Check for updates to GitHub Actions every week
|
||||
day: "monday"
|
||||
time: "09:00"
|
||||
interval: "weekly"
|
||||
25
.github/workflows/build-android-beta.yml
vendored
|
|
@ -8,7 +8,7 @@ on:
|
|||
- build-beta-android-[0-9]+
|
||||
|
||||
env:
|
||||
NODE_VERSION: 22.14.0
|
||||
NODE_VERSION: 20.13.1
|
||||
TERM: xterm
|
||||
|
||||
jobs:
|
||||
|
|
@ -24,9 +24,6 @@ jobs:
|
|||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- test
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
steps:
|
||||
- name: ci/checkout-repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
|
@ -57,25 +54,7 @@ jobs:
|
|||
working-directory: ./fastlane
|
||||
|
||||
- name: ci/upload-android-beta-build
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: android-build-beta-${{ github.run_id }}
|
||||
path: "*.apk"
|
||||
|
||||
# New AWS bucket deployment steps
|
||||
- name: Configure AWS credentials new bucket
|
||||
id: configure-aws-credentials-new-bucket
|
||||
uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 # v5.1.1
|
||||
with:
|
||||
aws-region: us-east-1
|
||||
role-to-assume: ${{ secrets.MM_MOBILE_RELEASE_AWS_ROLE_TO_ASSUME }}
|
||||
|
||||
- name: ci/upload-android-beta-new-bucket
|
||||
env:
|
||||
AWS_NEW_BUCKET_NAME: "${{ secrets.MM_MOBILE_RELEASE_AWS_NEW_BUCKET_NAME }}"
|
||||
MATTERMOST_WEBHOOK_URL: "${{ secrets.MM_MOBILE_BETA_MATTERMOST_WEBHOOK_URL }}"
|
||||
run: |
|
||||
echo "::group::Upload to new S3 bucket"
|
||||
bundle exec fastlane android upload_artifacts_to_s3 --env android.beta
|
||||
echo "::endgroup::"
|
||||
working-directory: ./fastlane
|
||||
|
|
|
|||
23
.github/workflows/build-android-release.yml
vendored
|
|
@ -8,7 +8,7 @@ on:
|
|||
- build-release-android-[0-9]+
|
||||
|
||||
env:
|
||||
NODE_VERSION: 22.14.0
|
||||
NODE_VERSION: 20.13.1
|
||||
TERM: xterm
|
||||
|
||||
jobs:
|
||||
|
|
@ -24,9 +24,6 @@ jobs:
|
|||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- test
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
steps:
|
||||
- name: ci/checkout-repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
|
@ -57,23 +54,7 @@ jobs:
|
|||
working-directory: ./fastlane
|
||||
|
||||
- name: ci/upload-android-release-build
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: android-build-release-${{ github.run_id }}
|
||||
path: "*.apk"
|
||||
|
||||
- name: Configure AWS credentials new bucket
|
||||
uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 # v5.1.1
|
||||
with:
|
||||
aws-region: us-east-1
|
||||
role-to-assume: ${{ secrets.MM_MOBILE_RELEASE_AWS_ROLE_TO_ASSUME }}
|
||||
|
||||
- name: ci/upload-android-release-new-bucket
|
||||
env:
|
||||
AWS_NEW_BUCKET_NAME: "${{ secrets.MM_MOBILE_RELEASE_AWS_NEW_BUCKET_NAME }}"
|
||||
MATTERMOST_WEBHOOK_URL: "${{ secrets.MM_MOBILE_RELEASE_MATTERMOST_WEBHOOK_URL }}"
|
||||
run: |
|
||||
echo "::group::Upload to new S3 bucket"
|
||||
bundle exec fastlane android upload_artifacts_to_s3 --env android.release
|
||||
echo "::endgroup::"
|
||||
working-directory: ./fastlane
|
||||
|
|
|
|||
68
.github/workflows/build-ios-beta.yml
vendored
|
|
@ -9,9 +9,8 @@ on:
|
|||
- build-beta-sim-[0-9]+
|
||||
|
||||
env:
|
||||
NODE_VERSION: 22.14.0
|
||||
NODE_VERSION: 20.13.1
|
||||
TERM: xterm
|
||||
INTUNE_ENABLED: 'true'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
|
|
@ -23,22 +22,16 @@ jobs:
|
|||
uses: ./.github/actions/test
|
||||
|
||||
build-ios-simulator:
|
||||
runs-on: macos-15-large
|
||||
runs-on: macos-14-large
|
||||
if: ${{ !contains(github.ref_name, 'beta-ios') }}
|
||||
needs:
|
||||
- test
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
steps:
|
||||
- name: ci/checkout-repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: ci/prepare-ios-build
|
||||
uses: ./.github/actions/prepare-ios-build
|
||||
with:
|
||||
intune-enabled: ${{ env.INTUNE_ENABLED }}
|
||||
intune-ssh-private-key: ${{ secrets.MM_MOBILE_INTUNE_DEPLOY_KEY }}
|
||||
|
||||
- name: ci/build-ios-simulator
|
||||
env:
|
||||
|
|
@ -51,49 +44,31 @@ jobs:
|
|||
working-directory: ./fastlane
|
||||
|
||||
- name: ci/upload-ios-pr-simulator
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: ios-build-simulator-${{ github.run_id }}
|
||||
path: Mattermost-simulator-x86_64.app.zip
|
||||
|
||||
# Logic for new bucket upload
|
||||
- name: Configure AWS credentials new bucket
|
||||
uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 # v5.1.1
|
||||
with:
|
||||
aws-region: us-east-1
|
||||
role-to-assume: ${{ secrets.MM_MOBILE_RELEASE_AWS_ROLE_TO_ASSUME }}
|
||||
|
||||
- name: ci/upload-ios-simulator-new-bucket
|
||||
env:
|
||||
TAG: "${{ github.ref_name }}"
|
||||
AWS_NEW_BUCKET_NAME: "${{ secrets.MM_MOBILE_RELEASE_AWS_NEW_BUCKET_NAME }}"
|
||||
MATTERMOST_WEBHOOK_URL: "${{ secrets.MM_MOBILE_BETA_MATTERMOST_WEBHOOK_URL }}"
|
||||
GITHUB_TOKEN: "${{ secrets.MM_MOBILE_GITHUB_TOKEN }}"
|
||||
run: bundle exec fastlane ios upload_artifacts_to_s3 --env ios.simulator output_file:"Mattermost-simulator-x86_64.app.zip"
|
||||
working-directory: ./fastlane
|
||||
|
||||
build-and-deploy-ios-beta:
|
||||
runs-on: macos-15-large
|
||||
runs-on: macos-14-large
|
||||
if: ${{ !contains(github.ref_name, 'beta-sim') }}
|
||||
needs:
|
||||
- test
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
steps:
|
||||
- name: ci/checkout-repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: ci/setup-ssh-key
|
||||
uses: ./.github/actions/setup-ssh-key
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.MM_MOBILE_PRIVATE_DEPLOY_KEY }}
|
||||
- name: ci/output-ssh-private-key
|
||||
shell: bash
|
||||
run: |
|
||||
SSH_KEY_PATH=~/.ssh/id_ed25519
|
||||
mkdir -p ~/.ssh
|
||||
echo -e '${{ secrets.MM_MOBILE_PRIVATE_DEPLOY_KEY }}' > ${SSH_KEY_PATH}
|
||||
chmod 0600 ${SSH_KEY_PATH}
|
||||
ssh-keygen -y -f ${SSH_KEY_PATH} > ${SSH_KEY_PATH}.pub
|
||||
|
||||
- name: ci/prepare-ios-build
|
||||
uses: ./.github/actions/prepare-ios-build
|
||||
with:
|
||||
intune-enabled: ${{ env.INTUNE_ENABLED }}
|
||||
intune-ssh-private-key: ${{ secrets.MM_MOBILE_INTUNE_DEPLOY_KEY }}
|
||||
|
||||
- name: ci/build-and-deploy-ios-beta
|
||||
env:
|
||||
|
|
@ -118,24 +93,7 @@ jobs:
|
|||
working-directory: ./fastlane
|
||||
|
||||
- name: ci/upload-ios-beta-build
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: ios-build-beta-${{ github.run_id }}
|
||||
path: "*.ipa"
|
||||
|
||||
# Logic for new bucket upload
|
||||
- name: Configure AWS credentials new bucket
|
||||
uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 # v5.1.1
|
||||
with:
|
||||
aws-region: us-east-1
|
||||
role-to-assume: ${{ secrets.MM_MOBILE_RELEASE_AWS_ROLE_TO_ASSUME }}
|
||||
|
||||
- name: ci/upload-ios-beta-new-bucket
|
||||
env:
|
||||
AWS_NEW_BUCKET_NAME: "${{ secrets.MM_MOBILE_RELEASE_AWS_NEW_BUCKET_NAME }}"
|
||||
MATTERMOST_WEBHOOK_URL: "${{ secrets.MM_MOBILE_BETA_MATTERMOST_WEBHOOK_URL }}"
|
||||
run: |
|
||||
echo "::group::Upload to new S3 bucket"
|
||||
bundle exec fastlane ios upload_artifacts_to_s3 --env ios.beta
|
||||
echo "::endgroup::"
|
||||
working-directory: ./fastlane
|
||||
|
|
|
|||
70
.github/workflows/build-ios-release.yml
vendored
|
|
@ -9,9 +9,8 @@ on:
|
|||
- build-release-sim-[0-9]+
|
||||
|
||||
env:
|
||||
NODE_VERSION: 22.14.0
|
||||
NODE_VERSION: 20.13.1
|
||||
TERM: xterm
|
||||
INTUNE_ENABLED: 'true'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
|
|
@ -23,33 +22,30 @@ jobs:
|
|||
uses: ./.github/actions/test
|
||||
|
||||
build-and-deploy-ios-release:
|
||||
runs-on: macos-15-large
|
||||
runs-on: macos-14-large
|
||||
if: ${{ !contains(github.ref_name, 'release-sim') }}
|
||||
needs:
|
||||
- test
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
steps:
|
||||
- name: ci/checkout-repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: ci/setup-ssh-key
|
||||
uses: ./.github/actions/setup-ssh-key
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.MM_MOBILE_PRIVATE_DEPLOY_KEY }}
|
||||
|
||||
- name: ci/prepare-ios-build
|
||||
uses: ./.github/actions/prepare-ios-build
|
||||
with:
|
||||
intune-enabled: ${{ env.INTUNE_ENABLED }}
|
||||
intune-ssh-private-key: ${{ secrets.MM_MOBILE_INTUNE_DEPLOY_KEY }}
|
||||
|
||||
- name: ci/output-ssh-private-key
|
||||
shell: bash
|
||||
run: |
|
||||
SSH_KEY_PATH=~/.ssh/id_ed25519
|
||||
mkdir -p ~/.ssh
|
||||
echo -e '${{ secrets.MM_MOBILE_PRIVATE_DEPLOY_KEY }}' > ${SSH_KEY_PATH}
|
||||
chmod 0600 ${SSH_KEY_PATH}
|
||||
ssh-keygen -y -f ${SSH_KEY_PATH} > ${SSH_KEY_PATH}.pub
|
||||
|
||||
- name: ci/build-and-deploy-ios-release
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: "${{ secrets.MM_MOBILE_RELEASE_AWS_ACCESS_KEY_ID }}"
|
||||
AWS_SECRET_ACCESS_KEY: "${{ secrets.MM_MOBILE_RELEASE_AWS_SECRET_ACCESS_KEY }}"
|
||||
AWS_NEW_BUCKET_NAME: "${{ secrets.MM_MOBILE_RELEASE_AWS_NEW_BUCKET_NAME }}"
|
||||
MATTERMOST_WEBHOOK_URL: "${{ secrets.MM_MOBILE_RELEASE_MATTERMOST_WEBHOOK_URL }}"
|
||||
FASTLANE_TEAM_ID: "${{ secrets.MM_MOBILE_FASTLANE_TEAM_ID }}"
|
||||
IOS_API_ISSUER_ID: "${{ secrets.MM_MOBILE_IOS_API_ISSUER_ID }}"
|
||||
|
|
@ -69,45 +65,22 @@ jobs:
|
|||
working-directory: ./fastlane
|
||||
|
||||
- name: ci/upload-ios-release-build
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: ios-build-release-${{ github.run_id }}
|
||||
path: "*.ipa"
|
||||
|
||||
# Upload artifacts to new bucket
|
||||
- name: Configure AWS credentials new bucket
|
||||
uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 # v5.1.1
|
||||
with:
|
||||
aws-region: us-east-1
|
||||
role-to-assume: ${{ secrets.MM_MOBILE_RELEASE_AWS_ROLE_TO_ASSUME }}
|
||||
|
||||
- name: ci/upload-ios-release-to-new-bucket
|
||||
env:
|
||||
AWS_NEW_BUCKET_NAME: "${{ secrets.MM_MOBILE_RELEASE_AWS_NEW_BUCKET_NAME }}"
|
||||
MATTERMOST_WEBHOOK_URL: "${{ secrets.MM_MOBILE_RELEASE_MATTERMOST_WEBHOOK_URL }}"
|
||||
run: |
|
||||
echo "::group::Build"
|
||||
bundle exec fastlane ios upload_artifacts_to_s3 --env ios.release
|
||||
echo "::endgroup::"
|
||||
working-directory: ./fastlane
|
||||
|
||||
build-ios-simulator:
|
||||
runs-on: macos-15-large
|
||||
runs-on: macos-14-large
|
||||
if: ${{ !contains(github.ref_name , 'release-ios') }}
|
||||
needs:
|
||||
- test
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
steps:
|
||||
- name: ci/checkout-repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: ci/prepare-ios-build
|
||||
uses: ./.github/actions/prepare-ios-build
|
||||
with:
|
||||
intune-enabled: ${{ env.INTUNE_ENABLED }}
|
||||
intune-ssh-private-key: ${{ secrets.MM_MOBILE_INTUNE_DEPLOY_KEY }}
|
||||
|
||||
- name: ci/build-ios-simulator
|
||||
env:
|
||||
|
|
@ -120,22 +93,7 @@ jobs:
|
|||
working-directory: ./fastlane
|
||||
|
||||
- name: ci/upload-ios-pr-simulator
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: ios-build-simulator-${{ github.run_id }}
|
||||
path: Mattermost-simulator-x86_64.app.zip
|
||||
|
||||
# Logic for new bucket upload
|
||||
- name: Configure AWS credentials new bucket
|
||||
uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 # v5.1.1
|
||||
with:
|
||||
aws-region: us-east-1
|
||||
role-to-assume: ${{ secrets.MM_MOBILE_RELEASE_AWS_ROLE_TO_ASSUME }}
|
||||
|
||||
- name: ci/upload-ios-simulator-new-bucket
|
||||
env:
|
||||
TAG: "${{ github.ref_name }}"
|
||||
AWS_NEW_BUCKET_NAME: "${{ secrets.MM_MOBILE_RELEASE_AWS_NEW_BUCKET_NAME }}"
|
||||
MATTERMOST_WEBHOOK_URL: "${{ secrets.MM_MOBILE_BETA_MATTERMOST_WEBHOOK_URL }}"
|
||||
run: bundle exec fastlane ios simulator --env ios.simulator output_file:"Mattermost-simulator-x86_64.app.zip"
|
||||
working-directory: ./fastlane
|
||||
|
|
|
|||
26
.github/workflows/build-pr.yml
vendored
|
|
@ -6,9 +6,8 @@ on:
|
|||
- labeled
|
||||
|
||||
env:
|
||||
NODE_VERSION: 22.14.0
|
||||
NODE_VERSION: 20.13.1
|
||||
TERM: xterm
|
||||
INTUNE_ENABLED: 'true'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
|
|
@ -23,7 +22,7 @@ jobs:
|
|||
uses: ./.github/actions/test
|
||||
|
||||
build-ios-pr:
|
||||
runs-on: macos-15-large
|
||||
runs-on: macos-14-large
|
||||
if: ${{ github.event.label.name == 'Build Apps for PR' || github.event.label.name == 'Build App for iOS' }}
|
||||
needs:
|
||||
- test
|
||||
|
|
@ -33,16 +32,17 @@ jobs:
|
|||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: ci/setup-ssh-key
|
||||
uses: ./.github/actions/setup-ssh-key
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.MM_MOBILE_PRIVATE_DEPLOY_KEY }}
|
||||
|
||||
- name: ci/prepare-ios-build
|
||||
uses: ./.github/actions/prepare-ios-build
|
||||
with:
|
||||
intune-enabled: ${{ env.INTUNE_ENABLED }}
|
||||
intune-ssh-private-key: ${{ secrets.MM_MOBILE_INTUNE_DEPLOY_KEY }}
|
||||
|
||||
- name: ci/output-ssh-private-key
|
||||
shell: bash
|
||||
run: |
|
||||
SSH_KEY_PATH=~/.ssh/id_ed25519
|
||||
mkdir -p ~/.ssh
|
||||
echo -e '${{ secrets.MM_MOBILE_PRIVATE_DEPLOY_KEY }}' > ${SSH_KEY_PATH}
|
||||
chmod 0600 ${SSH_KEY_PATH}
|
||||
ssh-keygen -y -f ${SSH_KEY_PATH} > ${SSH_KEY_PATH}.pub
|
||||
|
||||
- name: ci/build-ios-pr
|
||||
env:
|
||||
|
|
@ -60,7 +60,7 @@ jobs:
|
|||
working-directory: ./fastlane
|
||||
|
||||
- name: ci/upload-ios-pr-build
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: ios-build-pr-${{ github.run_id }}
|
||||
path: "*.ipa"
|
||||
|
|
@ -94,7 +94,7 @@ jobs:
|
|||
working-directory: ./fastlane
|
||||
|
||||
- name: ci/upload-android-pr-build
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: android-build-pr-${{ github.run_id }}
|
||||
path: "*.apk"
|
||||
|
|
|
|||
10
.github/workflows/ci.yml
vendored
|
|
@ -7,11 +7,8 @@ on:
|
|||
- 'release*'
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
env:
|
||||
NODE_VERSION: 22.14.0
|
||||
NODE_VERSION: 20.13.1
|
||||
TERM: xterm
|
||||
|
||||
jobs:
|
||||
|
|
@ -22,8 +19,3 @@ jobs:
|
|||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: ci/test
|
||||
uses: ./.github/actions/test
|
||||
- name: ci/test-coverage
|
||||
if: github.event_name == 'pull_request' || github.ref_name == 'main'
|
||||
uses: ./.github/actions/test-coverage
|
||||
with:
|
||||
run_id: ${{ github.run_id }}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ jobs:
|
|||
run: echo '${{ inputs.CMT_MATRIX }}' > instance-details.json
|
||||
|
||||
- name: cmt/upload-instance-details
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: instance-details.json
|
||||
path: instance-details.json
|
||||
|
|
@ -45,7 +45,7 @@ jobs:
|
|||
needs:
|
||||
- calculate-commit-hash
|
||||
steps:
|
||||
- uses: mattermost/actions/delivery/update-commit-status@d5174b860704729f4c14ef8489ae075742bfa08a
|
||||
- uses: mattermost/actions/delivery/update-commit-status@746563b58e737a17a8ceb00b84a813b9e6e1b236
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
|
|
@ -91,7 +91,7 @@ jobs:
|
|||
working-directory: ./fastlane
|
||||
|
||||
- name: Upload iOS Simulator Build
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: ios-build-simulator-${{ github.run_id }}
|
||||
path: Mattermost-simulator-x86_64.app.zip
|
||||
|
|
|
|||
73
.github/workflows/e2e-android-detox.yml
vendored
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
name: Detox E2E Android Tests PR
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
types:
|
||||
- labeled
|
||||
|
||||
concurrency:
|
||||
group: "${{ github.workflow }}-${{ github.event.pull_request.number }}-${{ github.event.label.name }}"
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build-android-apk:
|
||||
if: github.event.label.name == 'E2E Android tests for PR'
|
||||
runs-on: ubuntu-latest-8-cores
|
||||
env:
|
||||
ORG_GRADLE_PROJECT_jvmargs: -Xmx8g
|
||||
steps:
|
||||
- name: Prune Docker to free up space
|
||||
run: docker system prune -af
|
||||
|
||||
- name: Remove npm Temporary Files
|
||||
run: |
|
||||
rm -rf ~/.npm/_cacache
|
||||
|
||||
- name: ci/checkout-repo
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: ci/prepare-android-build
|
||||
uses: ./.github/actions/prepare-android-build
|
||||
env:
|
||||
STORE_FILE: "${{ secrets.MM_MOBILE_STORE_FILE }}"
|
||||
STORE_ALIAS: "${{ secrets.MM_MOBILE_STORE_ALIAS }}"
|
||||
STORE_PASSWORD: "${{ secrets.MM_MOBILE_STORE_PASSWORD }}"
|
||||
MATTERMOST_BUILD_GH_TOKEN: "${{ secrets.MATTERMOST_BUILD_GH_TOKEN }}"
|
||||
|
||||
- name: Install Dependencies
|
||||
run: sudo apt-get clean && sudo apt-get update && sudo apt-get install -y default-jdk
|
||||
|
||||
- name: Cache Gradle dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.gradle/caches/modules-2/
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
|
||||
restore-keys: ${{ runner.os }}-gradle-
|
||||
|
||||
- name: Validate Gradle wrapper
|
||||
uses: gradle/actions/wrapper-validation@v3
|
||||
|
||||
- name: Inject Detox settings
|
||||
run: cd detox && npm run e2e:android-inject-settings
|
||||
|
||||
- name: Update minSdkVersion for react-native-image-picker
|
||||
run: |
|
||||
sed -i 's/minSdkVersion 21/minSdkVersion 23/' ./node_modules/react-native-image-picker/android/build.gradle
|
||||
cat ./node_modules/react-native-image-picker/android/build.gradle | grep minSdkVersion
|
||||
|
||||
- name: Detox build
|
||||
run: |
|
||||
cd detox
|
||||
npm install
|
||||
npm install -g detox-cli
|
||||
npm run e2e:android-build
|
||||
|
||||
- name: ci/upload-android-pr-build
|
||||
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
|
||||
with:
|
||||
name: android-build-apk-${{ github.run_id }}
|
||||
path: "android/app/build/outputs/apk/**/app-*.apk"
|
||||
70
.github/workflows/e2e-android-template.yml
vendored
|
|
@ -52,7 +52,7 @@ on:
|
|||
description: "Android API level"
|
||||
required: false
|
||||
type: string
|
||||
default: "34"
|
||||
default: "33"
|
||||
outputs:
|
||||
STATUS:
|
||||
value: ${{ jobs.generate-report.outputs.STATUS }}
|
||||
|
|
@ -127,10 +127,10 @@ jobs:
|
|||
device_os_version: ${{ env.SDK_VERSION }}
|
||||
|
||||
e2e-android:
|
||||
name: machine-${{ matrix.runId }}-api-${{ matrix.deviceOsVersion }}
|
||||
name: android-detox-e2e-${{ matrix.runId }}-${{ matrix.deviceName }}-${{ matrix.deviceOsVersion }}
|
||||
runs-on: ubuntu-latest-8-cores
|
||||
continue-on-error: true
|
||||
timeout-minutes: 240
|
||||
timeout-minutes: 150
|
||||
env:
|
||||
ANDROID_HOME: /usr/local/lib/android/sdk
|
||||
ANDROID_SDK_ROOT: /usr/local/lib/android/sdk
|
||||
|
|
@ -170,12 +170,6 @@ jobs:
|
|||
cd detox
|
||||
npm install
|
||||
|
||||
- name: Set .env with RUNNING_E2E=true
|
||||
run: |
|
||||
cat > .env <<EOF
|
||||
RUNNING_E2E=true
|
||||
EOF
|
||||
|
||||
- name: Create destination path
|
||||
run: mkdir -p android/app/build
|
||||
|
||||
|
|
@ -200,34 +194,23 @@ jobs:
|
|||
|
||||
- name: Accept Android licenses
|
||||
run: |
|
||||
mkdir -p ~/.android
|
||||
yes | sdkmanager --licenses > sdk_licenses_output.txt || true
|
||||
|
||||
# Check if "All SDK package licenses accepted" appears in output
|
||||
if grep -q "All SDK package licenses accepted" sdk_licenses_output.txt; then
|
||||
echo "✅ All licenses accepted successfully."
|
||||
else
|
||||
echo "❌ Licenses not fully accepted."
|
||||
cat sdk_licenses_output.txt
|
||||
exit 1
|
||||
fi
|
||||
yes | sdkmanager --licenses || true
|
||||
|
||||
- name: Install Android SDK components
|
||||
- name: Install Android system image
|
||||
run: |
|
||||
yes | sdkmanager --install "platform-tools" "emulator" "platforms;android-34" "system-images;android-34;default;x86_64" "system-images;android-34;google_apis;x86_64"
|
||||
env:
|
||||
JAVA_HOME: ${{ env.JAVA_HOME_17_X64 }}
|
||||
|
||||
sdkmanager "system-images;android-34;default;x86_64"
|
||||
sdkmanager "platform-tools" "emulator"
|
||||
|
||||
- name: Create and run Android Emulator
|
||||
run: |
|
||||
cd detox
|
||||
chmod +x ./create_android_emulator.sh
|
||||
CI=true ./create_android_emulator.sh ${{ env.SDK_VERSION }} ${{ env.AVD_NAME }} ${{ matrix.specs }}
|
||||
./create_android_emulator.sh ${{ env.SDK_VERSION }} ${{ env.AVD_NAME }} ${{ matrix.specs }}
|
||||
continue-on-error: true # We want to run all the tests
|
||||
|
||||
- name: Upload Android Test Report
|
||||
if: always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: android-results-${{ needs.generate-specs.outputs.workflow_hash }}-${{ matrix.runId }}
|
||||
path: detox/artifacts/
|
||||
|
|
@ -310,36 +293,3 @@ jobs:
|
|||
echo "" >> ${GITHUB_STEP_SUMMARY}
|
||||
echo "You can check the full report [here](${{ steps.set-url.outputs.TARGET_URL }})" >> ${GITHUB_STEP_SUMMARY}
|
||||
echo "There was **${{ steps.summary.outputs.PERCENTAGE }}%** success rate." >> ${GITHUB_STEP_SUMMARY}
|
||||
|
||||
- name: Comment report on the PR
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const prNumber = context.payload.pull_request.number;
|
||||
|
||||
const commentBody = `**Android E2E Test Report**: ${process.env.MOBILE_SHA} | ${process.env.PERCENTAGE}% (${process.env.PASSES}/${process.env.TOTAL}) | [full report](${process.env.TARGET_URL})
|
||||
| Tests | Passed ✅ | Failed ❌ | Skipped ⏭️ | Errors ⚠️ |
|
||||
|:---:|:---:|:---:|:---:|:---:|
|
||||
| ${process.env.TOTAL} | ${process.env.PASSES} | ${process.env.FAILURES} | ${process.env.SKIPPED} | ${process.env.ERRORS} |
|
||||
`;
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber,
|
||||
body: commentBody,
|
||||
});
|
||||
env:
|
||||
STATUS: ${{ steps.determine-status.outputs.STATUS }}
|
||||
FAILURES: ${{ steps.summary.outputs.FAILURES }}
|
||||
PASSES: ${{ steps.summary.outputs.PASSES }}
|
||||
SKIPPED: ${{ steps.summary.outputs.SKIPPED }}
|
||||
TOTAL: ${{ steps.summary.outputs.TOTAL }}
|
||||
ERRORS: ${{ steps.summary.outputs.ERRORS }}
|
||||
PERCENTAGE: ${{ steps.summary.outputs.PERCENTAGE }}
|
||||
BUILD_ID: ${{ needs.generate-specs.outputs.build_id }}
|
||||
RUN_TYPE: ${{ inputs.run-type }}
|
||||
MOBILE_REF: ${{ needs.generate-specs.outputs.mobile_ref }}
|
||||
MOBILE_SHA: ${{ needs.generate-specs.outputs.mobile_sha }}
|
||||
TARGET_URL: ${{ steps.set-url.outputs.TARGET_URL }}
|
||||
|
|
|
|||
118
.github/workflows/e2e-detox-pr.yml
vendored
|
|
@ -1,6 +1,6 @@
|
|||
# Can be used to run Detox E2E tests on pull requests for the Mattermost mobile app with low bandwidth
|
||||
# by using 'E2E iOS tests for PR (LBW 1)' instead.
|
||||
name: E2E
|
||||
name: Detox E2E Tests PR
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
|
@ -13,28 +13,10 @@ concurrency:
|
|||
group: "${{ github.workflow }}-${{ github.event.pull_request.number }}-${{ github.event.label.name }}"
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
INTUNE_ENABLED: 'true'
|
||||
|
||||
jobs:
|
||||
update-initial-status-ios:
|
||||
update-initial-status:
|
||||
runs-on: ubuntu-22.04
|
||||
if: contains(github.event.label.name, 'E2E iOS tests for PR')
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: mattermost/actions/delivery/update-commit-status@main
|
||||
if: contains(github.event.label.name, 'E2E iOS tests for PR')
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
with:
|
||||
repository_full_name: ${{ github.repository }}
|
||||
commit_sha: ${{ github.event.pull_request.head.sha }}
|
||||
context: e2e/detox-ios-tests
|
||||
description: Detox iOS tests for mattermost mobile app have started ...
|
||||
status: pending
|
||||
|
||||
update-initial-status-android:
|
||||
runs-on: ubuntu-22.04
|
||||
if: contains(github.event.label.name, 'E2E Android tests for PR')
|
||||
steps:
|
||||
- uses: mattermost/actions/delivery/update-commit-status@main
|
||||
env:
|
||||
|
|
@ -42,41 +24,35 @@ jobs:
|
|||
with:
|
||||
repository_full_name: ${{ github.repository }}
|
||||
commit_sha: ${{ github.event.pull_request.head.sha }}
|
||||
context: e2e/detox-android-tests
|
||||
description: Detox Android tests for mattermost mobile app have started ...
|
||||
context: e2e/detox-tests
|
||||
description: Detox tests for mattermost mobile app have started ...
|
||||
status: pending
|
||||
|
||||
build-ios-simulator:
|
||||
if: contains(github.event.label.name, 'E2E iOS tests for PR')
|
||||
runs-on: macos-26
|
||||
runs-on: macos-14
|
||||
needs:
|
||||
- update-initial-status-ios
|
||||
- update-initial-status
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Prepare iOS Build
|
||||
uses: ./.github/actions/prepare-ios-build
|
||||
with:
|
||||
intune-enabled: ${{ env.INTUNE_ENABLED }}
|
||||
intune-ssh-private-key: ${{ secrets.MM_MOBILE_INTUNE_DEPLOY_KEY }}
|
||||
|
||||
- name: Set .env with RUNNING_E2E=true
|
||||
run: |
|
||||
echo "RUNNING_E2E=true" > .env
|
||||
|
||||
- name: Build iOS Simulator
|
||||
env:
|
||||
TAG: "${{ github.event.pull_request.head.sha }}"
|
||||
AWS_ACCESS_KEY_ID: "${{ secrets.MM_MOBILE_BETA_AWS_ACCESS_KEY_ID }}"
|
||||
AWS_SECRET_ACCESS_KEY: "${{ secrets.MM_MOBILE_BETA_AWS_SECRET_ACCESS_KEY }}"
|
||||
GITHUB_TOKEN: "${{ secrets.MM_MOBILE_GITHUB_TOKEN }}"
|
||||
run: bundle exec fastlane ios simulator --env ios.simulator skip_upload_to_s3_bucket:true
|
||||
run: bundle exec fastlane ios simulator --env ios.simulator
|
||||
working-directory: ./fastlane
|
||||
|
||||
- name: Upload iOS Simulator Build
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: ios-build-simulator-${{ github.run_id }}
|
||||
path: Mattermost-simulator-*.app.zip
|
||||
path: Mattermost-simulator-x86_64.app.zip
|
||||
|
||||
build-android-apk:
|
||||
runs-on: ubuntu-latest-8-cores
|
||||
|
|
@ -112,42 +88,28 @@ jobs:
|
|||
cd detox
|
||||
npm install
|
||||
npm install -g detox-cli
|
||||
npm run e2e:android-inject-settings
|
||||
npm run e2e:android-build
|
||||
|
||||
- name: Upload Android Build
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: android-build-files-${{ github.run_id }}
|
||||
path: "android/app/build/**/*"
|
||||
|
||||
run-ios-tests-on-pr:
|
||||
if: contains(github.event.label.name, 'E2E iOS tests for PR')
|
||||
name: iOS
|
||||
uses: ./.github/workflows/e2e-ios-template.yml
|
||||
name: iOS Mobile Tests on PR
|
||||
uses: ./.github/workflows/e2e-detox-template.yml
|
||||
needs:
|
||||
- build-ios-simulator
|
||||
with:
|
||||
run-ios-tests: true
|
||||
run-type: "PR"
|
||||
MOBILE_VERSION: ${{ github.event.pull_request.head.sha }}
|
||||
low_bandwidth_mode: ${{ contains(github.event.label.name,'LBW') && true || false }}
|
||||
secrets: inherit
|
||||
|
||||
run-android-tests-on-pr:
|
||||
if: contains(github.event.label.name, 'E2E Android tests for PR')
|
||||
name: Android
|
||||
uses: ./.github/workflows/e2e-android-template.yml
|
||||
needs:
|
||||
- build-android-apk
|
||||
with:
|
||||
run-android-tests: true
|
||||
run-type: "PR"
|
||||
MOBILE_VERSION: ${{ github.event.pull_request.head.sha }}
|
||||
secrets: inherit
|
||||
|
||||
update-final-status-ios:
|
||||
update-final-status:
|
||||
runs-on: ubuntu-22.04
|
||||
if: contains(github.event.label.name, 'E2E iOS tests for PR')
|
||||
needs:
|
||||
- run-ios-tests-on-pr
|
||||
steps:
|
||||
|
|
@ -157,39 +119,23 @@ jobs:
|
|||
with:
|
||||
repository_full_name: ${{ github.repository }}
|
||||
commit_sha: ${{ github.event.pull_request.head.sha }}
|
||||
context: e2e/detox-ios-tests
|
||||
context: e2e/detox-tests
|
||||
description: Completed with ${{ needs.run-ios-tests-on-pr.outputs.FAILURES }} failures
|
||||
status: ${{ needs.run-ios-tests-on-pr.outputs.STATUS }}
|
||||
target_url: ${{ needs.run-ios-tests-on-pr.outputs.TARGET_URL }}
|
||||
|
||||
update-final-status-android:
|
||||
runs-on: ubuntu-22.04
|
||||
if: contains(github.event.label.name, 'E2E Android tests for PR')
|
||||
needs:
|
||||
- run-android-tests-on-pr
|
||||
steps:
|
||||
- uses: mattermost/actions/delivery/update-commit-status@main
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
with:
|
||||
repository_full_name: ${{ github.repository }}
|
||||
commit_sha: ${{ github.event.pull_request.head.sha }}
|
||||
context: e2e/detox-android-tests
|
||||
description: Completed with ${{ needs.run-android-tests-on-pr.outputs.FAILURES }} failures
|
||||
status: ${{ needs.run-android-tests-on-pr.outputs.STATUS }}
|
||||
target_url: ${{ needs.run-android-tests-on-pr.outputs.TARGET_URL }}
|
||||
|
||||
e2e-remove-ios-label:
|
||||
e2e-remove-label:
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- run-ios-tests-on-pr
|
||||
steps:
|
||||
- name: e2e/remove-label-from-pr
|
||||
uses: actions/github-script@e7aeb8c663f696059ebb5f9ab1425ed2ef511bdb # v7.0.1
|
||||
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||
continue-on-error: true # Label might have been removed manually
|
||||
with:
|
||||
script: |
|
||||
const iosLabel = 'E2E iOS tests for PR';
|
||||
const androidLabel = 'E2E Android tests for PR';
|
||||
context.payload.pull_request.labels.forEach(label => {
|
||||
if (label.name.includes(iosLabel)) {
|
||||
github.rest.issues.removeLabel({
|
||||
|
|
@ -200,25 +146,3 @@ jobs:
|
|||
});
|
||||
}
|
||||
});
|
||||
|
||||
e2e-remove-android-label:
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- run-android-tests-on-pr
|
||||
steps:
|
||||
- name: e2e/remove-label-from-pr
|
||||
uses: actions/github-script@e7aeb8c663f696059ebb5f9ab1425ed2ef511bdb # v7.0.1
|
||||
continue-on-error: true # Label might have been removed manually
|
||||
with:
|
||||
script: |
|
||||
const androidLabel = 'E2E Android tests for PR';
|
||||
context.payload.pull_request.labels.forEach(label => {
|
||||
if (label.name.includes(androidLabel)) {
|
||||
github.rest.issues.removeLabel({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
name: label.name,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
|||
68
.github/workflows/e2e-detox-release.yml
vendored
|
|
@ -5,11 +5,8 @@ on:
|
|||
branches:
|
||||
- release-*
|
||||
|
||||
env:
|
||||
INTUNE_ENABLED: 'true'
|
||||
|
||||
jobs:
|
||||
update-initial-status-ios:
|
||||
update-initial-status:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: mattermost/actions/delivery/update-commit-status@main
|
||||
|
|
@ -18,36 +15,20 @@ jobs:
|
|||
with:
|
||||
repository_full_name: ${{ github.repository }}
|
||||
commit_sha: ${{ github.sha }}
|
||||
context: e2e/detox-ios-tests
|
||||
description: Detox iOS tests for mattermost mobile app have started ...
|
||||
status: pending
|
||||
|
||||
update-initial-status-android:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: mattermost/actions/delivery/update-commit-status@main
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
with:
|
||||
repository_full_name: ${{ github.repository }}
|
||||
commit_sha: ${{ github.sha }}
|
||||
context: e2e/detox-android-tests
|
||||
description: Detox Android tests for mattermost mobile app have started ...
|
||||
context: e2e/detox-tests
|
||||
description: Detox tests for mattermost mobile app have started ...
|
||||
status: pending
|
||||
|
||||
build-ios-simulator:
|
||||
runs-on: macos-15
|
||||
runs-on: macos-14
|
||||
needs:
|
||||
- update-initial-status-ios
|
||||
- update-initial-status
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Prepare iOS Build
|
||||
uses: ./.github/actions/prepare-ios-build
|
||||
with:
|
||||
intune-enabled: ${{ env.INTUNE_ENABLED }}
|
||||
intune-ssh-private-key: ${{ secrets.MM_MOBILE_INTUNE_DEPLOY_KEY }}
|
||||
|
||||
- name: Build iOS Simulator
|
||||
env:
|
||||
|
|
@ -59,7 +40,7 @@ jobs:
|
|||
working-directory: ./fastlane
|
||||
|
||||
- name: Upload iOS Simulator Build
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: ios-build-simulator-${{ github.run_id }}
|
||||
path: Mattermost-simulator-x86_64.app.zip
|
||||
|
|
@ -109,35 +90,24 @@ jobs:
|
|||
npm run e2e:android-build
|
||||
|
||||
- name: Upload Android Build
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: android-build-files-${{ github.run_id }}
|
||||
path: "android/app/build/**/*"
|
||||
|
||||
run-ios-tests-on-release:
|
||||
name: iOS Mobile Tests on Release
|
||||
uses: ./.github/workflows/e2e-ios-template.yml
|
||||
uses: ./.github/workflows/e2e-detox-template.yml
|
||||
needs:
|
||||
- build-ios-simulator
|
||||
with:
|
||||
run-ios-tests: true
|
||||
run-type: "RELEASE"
|
||||
record_tests_in_zephyr: 'true'
|
||||
MOBILE_VERSION: ${{ github.ref }}
|
||||
secrets: inherit
|
||||
|
||||
run-android-tests-on-release:
|
||||
name: Android Mobile Tests on Release
|
||||
uses: ./.github/workflows/e2e-android-template.yml
|
||||
needs:
|
||||
- build-android-apk
|
||||
with:
|
||||
run-android-tests: true
|
||||
run-type: "RELEASE"
|
||||
record_tests_in_zephyr: 'true'
|
||||
MOBILE_VERSION: ${{ github.ref }}
|
||||
secrets: inherit
|
||||
|
||||
update-final-status-ios:
|
||||
update-final-status:
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- run-ios-tests-on-release
|
||||
|
|
@ -148,23 +118,7 @@ jobs:
|
|||
with:
|
||||
repository_full_name: ${{ github.repository }}
|
||||
commit_sha: ${{ github.sha }}
|
||||
context: e2e/detox-ios-tests
|
||||
context: e2e/detox-tests
|
||||
description: Completed with ${{ needs.run-ios-tests-on-release.outputs.FAILURES }} failures
|
||||
status: ${{ needs.run-ios-tests-on-release.outputs.STATUS }}
|
||||
target_url: ${{ needs.run-ios-tests-on-release.outputs.TARGET_URL }}
|
||||
|
||||
update-final-status-android:
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- run-android-tests-on-release
|
||||
steps:
|
||||
- uses: mattermost/actions/delivery/update-commit-status@main
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
with:
|
||||
repository_full_name: ${{ github.repository }}
|
||||
commit_sha: ${{ github.sha }}
|
||||
context: e2e/detox-android-tests
|
||||
description: Completed with ${{ needs.run-android-tests-on-release.outputs.FAILURES }} failures
|
||||
status: ${{ needs.run-android-tests-on-release.outputs.STATUS }}
|
||||
target_url: ${{ needs.run-android-tests-on-release.outputs.TARGET_URL }}
|
||||
|
|
|
|||
68
.github/workflows/e2e-detox-scheduled.yml
vendored
|
|
@ -4,11 +4,8 @@ on:
|
|||
schedule:
|
||||
- cron: "0 0 * * 4,5" # Wednesday and Thursday midnight
|
||||
|
||||
env:
|
||||
INTUNE_ENABLED: 'true'
|
||||
|
||||
jobs:
|
||||
update-initial-status-ios:
|
||||
update-initial-status:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: mattermost/actions/delivery/update-commit-status@main
|
||||
|
|
@ -17,36 +14,20 @@ jobs:
|
|||
with:
|
||||
repository_full_name: ${{ github.repository }}
|
||||
commit_sha: ${{ github.sha }}
|
||||
context: e2e/detox-ios-tests
|
||||
description: Detox iOS tests for mattermost mobile app have started ...
|
||||
status: pending
|
||||
|
||||
update-initial-status-android:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: mattermost/actions/delivery/update-commit-status@main
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
with:
|
||||
repository_full_name: ${{ github.repository }}
|
||||
commit_sha: ${{ github.sha }}
|
||||
context: e2e/detox-android-tests
|
||||
description: Detox Android tests for mattermost mobile app have started ...
|
||||
context: e2e/detox-tests
|
||||
description: Detox tests for mattermost mobile app have started ...
|
||||
status: pending
|
||||
|
||||
build-ios-simulator:
|
||||
runs-on: macos-15
|
||||
runs-on: macos-14
|
||||
needs:
|
||||
- update-initial-status-ios
|
||||
- update-initial-status
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Prepare iOS Build
|
||||
uses: ./.github/actions/prepare-ios-build
|
||||
with:
|
||||
intune-enabled: ${{ env.INTUNE_ENABLED }}
|
||||
intune-ssh-private-key: ${{ secrets.MM_MOBILE_INTUNE_DEPLOY_KEY }}
|
||||
|
||||
- name: Build iOS Simulator
|
||||
env:
|
||||
|
|
@ -58,7 +39,7 @@ jobs:
|
|||
working-directory: ./fastlane
|
||||
|
||||
- name: Upload iOS Simulator Build
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: ios-build-simulator-${{ github.run_id }}
|
||||
path: Mattermost-simulator-x86_64.app.zip
|
||||
|
|
@ -108,35 +89,24 @@ jobs:
|
|||
npm run e2e:android-build
|
||||
|
||||
- name: Upload Android Build
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: android-build-files-${{ github.run_id }}
|
||||
path: "android/app/build/**/*"
|
||||
|
||||
run-ios-tests-on-main-scheduled:
|
||||
name: iOS Mobile Tests on Main (Scheduled)
|
||||
uses: ./.github/workflows/e2e-ios-template.yml
|
||||
uses: ./.github/workflows/e2e-detox-template.yml
|
||||
needs:
|
||||
- build-ios-simulator
|
||||
with:
|
||||
run-ios-tests: true
|
||||
run-type: "MAIN"
|
||||
record_tests_in_zephyr: 'true'
|
||||
MOBILE_VERSION: ${{ github.ref }}
|
||||
secrets: inherit
|
||||
|
||||
run-android-tests-on-main-scheduled:
|
||||
name: Android Mobile Tests on Main (Scheduled)
|
||||
uses: ./.github/workflows/e2e-android-template.yml
|
||||
needs:
|
||||
- build-android-apk
|
||||
with:
|
||||
run-android-tests: true
|
||||
run-type: "MAIN"
|
||||
record_tests_in_zephyr: 'true'
|
||||
MOBILE_VERSION: ${{ github.ref }}
|
||||
secrets: inherit
|
||||
|
||||
update-final-status-ios:
|
||||
update-final-status:
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- run-ios-tests-on-main-scheduled
|
||||
|
|
@ -147,23 +117,7 @@ jobs:
|
|||
with:
|
||||
repository_full_name: ${{ github.repository }}
|
||||
commit_sha: ${{ github.sha }}
|
||||
context: e2e/detox-ios-tests
|
||||
context: e2e/detox-tests
|
||||
description: Completed with ${{ needs.run-ios-tests-on-main-scheduled.outputs.FAILURES }} failures
|
||||
status: ${{ needs.run-ios-tests-on-main-scheduled.outputs.STATUS }}
|
||||
target_url: ${{ needs.run-ios-tests-on-main-scheduled.outputs.TARGET_URL }}
|
||||
|
||||
update-final-status-android:
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- run-android-tests-on-main-scheduled
|
||||
steps:
|
||||
- uses: mattermost/actions/delivery/update-commit-status@main
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
with:
|
||||
repository_full_name: ${{ github.repository }}
|
||||
commit_sha: ${{ github.sha }}
|
||||
context: e2e/detox-android-tests
|
||||
description: Completed with ${{ needs.run-android-tests-on-main-scheduled.outputs.FAILURES }} failures
|
||||
status: ${{ needs.run-android-tests-on-main-scheduled.outputs.STATUS }}
|
||||
target_url: ${{ needs.run-android-tests-on-main-scheduled.outputs.TARGET_URL }}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
name: Detox iOS E2E Tests Template
|
||||
name: Detox E2E Tests Template
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
|
@ -20,6 +20,10 @@ on:
|
|||
required: false
|
||||
default: ${{ github.head_ref || github.ref }}
|
||||
type: string
|
||||
run-ios-tests:
|
||||
description: "Run iOS tests"
|
||||
required: true
|
||||
type: boolean
|
||||
run-type:
|
||||
type: string
|
||||
required: false
|
||||
|
|
@ -38,12 +42,12 @@ on:
|
|||
description: "iPhone simulator name"
|
||||
required: false
|
||||
type: string
|
||||
default: "iPhone 17 Pro"
|
||||
default: "iPhone 15 Pro"
|
||||
ios_device_os_name:
|
||||
description: "iPhone simulator OS version"
|
||||
required: false
|
||||
type: string
|
||||
default: "iOS 26.2"
|
||||
default: "iOS 17.4"
|
||||
low_bandwidth_mode:
|
||||
description: "Enable low bandwidth mode"
|
||||
required: false
|
||||
|
|
@ -80,12 +84,11 @@ env:
|
|||
TEST_CYCLE_LINK_PREFIX: ${{ secrets.MM_MOBILE_E2E_TEST_CYCLE_LINK_PREFIX }}
|
||||
WEBHOOK_URL: ${{ secrets.MM_MOBILE_E2E_WEBHOOK_URL }}
|
||||
FAILURE_MESSAGE: "Something has failed"
|
||||
IOS: "true"
|
||||
RUNNING_E2E: "true"
|
||||
|
||||
jobs:
|
||||
generate-specs:
|
||||
runs-on: ubuntu-22.04
|
||||
if: ${{ inputs.run-ios-tests }}
|
||||
outputs:
|
||||
specs: ${{ steps.generate-specs.outputs.specs }}
|
||||
build_id: ${{ steps.resolve-device.outputs.BUILD_ID }}
|
||||
|
|
@ -121,10 +124,11 @@ jobs:
|
|||
device_os_version: ${{ env.DEVICE_OS_VERSION }}
|
||||
|
||||
e2e-ios:
|
||||
name: machine-${{ matrix.runId }}-os-${{ matrix.deviceOsVersion }}
|
||||
runs-on: macos-15
|
||||
name: ios-detox-e2e-${{ matrix.runId }}-${{ matrix.deviceName }}-${{ matrix.deviceOsVersion }}
|
||||
if: ${{ inputs.run-ios-tests }}
|
||||
runs-on: macos-14
|
||||
continue-on-error: true
|
||||
timeout-minutes: ${{ inputs.low_bandwidth_mode && 240 || 180 }}
|
||||
timeout-minutes: ${{ inputs.low_bandwidth_mode && 80 || 40 }}
|
||||
env:
|
||||
IOS: true
|
||||
needs:
|
||||
|
|
@ -153,10 +157,7 @@ jobs:
|
|||
path: mobile-artifacts
|
||||
|
||||
- name: Unzip iOS Simulator Build
|
||||
run: |
|
||||
unzip -o mobile-artifacts/*.zip -d mobile-artifacts/
|
||||
# delete zip file
|
||||
rm -f mobile-artifacts/*.zip
|
||||
run: unzip -o mobile-artifacts/*.zip -d mobile-artifacts/
|
||||
|
||||
- name: Prepare Low Bandwidth Environment
|
||||
id: prepare-low-bandwidth
|
||||
|
|
@ -170,85 +171,12 @@ jobs:
|
|||
upload_speed: "3300"
|
||||
latency: "500"
|
||||
|
||||
- name: Start React Native Metro Server
|
||||
run: npm run start &
|
||||
|
||||
- name: Install Detox Dependencies
|
||||
run: cd detox && npm i
|
||||
|
||||
- name: Pre-boot iOS Simulator
|
||||
run: |
|
||||
set -e
|
||||
|
||||
echo "Looking for simulator: ${{ env.DEVICE_NAME }} with ${{ env.DEVICE_OS_VERSION }}"
|
||||
|
||||
# Find or create the simulator
|
||||
SIMULATOR_ID=$(xcrun simctl list devices | grep "${{ env.DEVICE_NAME }}" | grep "${{ env.DEVICE_OS_VERSION }}" | head -1 | grep -oE '([0-9A-F-]{36})' || true)
|
||||
|
||||
if [ -z "$SIMULATOR_ID" ]; then
|
||||
echo "Simulator not found. Creating simulator ${{ env.DEVICE_NAME }} with ${{ env.DEVICE_OS_VERSION }}"
|
||||
|
||||
# Get device type (use exact match with parentheses to avoid partial matches)
|
||||
DEVICE_TYPE=$(xcrun simctl list devicetypes | grep "${{ env.DEVICE_NAME }} (" | head -1 | awk -F'[()]' '{print $(NF-1)}')
|
||||
if [ -z "$DEVICE_TYPE" ]; then
|
||||
echo "Error: Could not find device type for ${{ env.DEVICE_NAME }}"
|
||||
exit 1
|
||||
fi
|
||||
echo "Found device type: $DEVICE_TYPE"
|
||||
|
||||
# Get runtime (extract the identifier after the last " - ")
|
||||
RUNTIME=$(xcrun simctl list runtimes | grep "${{ env.DEVICE_OS_VERSION }}" | head -1 | sed 's/.* - \(.*\)/\1/')
|
||||
if [ -z "$RUNTIME" ]; then
|
||||
echo "Error: Could not find runtime for ${{ env.DEVICE_OS_VERSION }}"
|
||||
exit 1
|
||||
fi
|
||||
echo "Found runtime: $RUNTIME"
|
||||
|
||||
# Create simulator
|
||||
SIMULATOR_ID=$(xcrun simctl create "CI-${{ env.DEVICE_NAME }}" "$DEVICE_TYPE" "$RUNTIME")
|
||||
echo "Created simulator with ID: $SIMULATOR_ID"
|
||||
else
|
||||
echo "Found existing simulator: $SIMULATOR_ID"
|
||||
fi
|
||||
|
||||
# Boot simulator first to initialize directory structure
|
||||
echo "Initializing simulator directories (first boot)..."
|
||||
xcrun simctl boot "$SIMULATOR_ID"
|
||||
xcrun simctl bootstatus "$SIMULATOR_ID"
|
||||
|
||||
# Shut down to apply password autofill restrictions
|
||||
echo "Shutting down to apply password autofill restrictions..."
|
||||
xcrun simctl shutdown "$SIMULATOR_ID"
|
||||
sleep 3
|
||||
|
||||
echo "Disabling password autofill (simulator is shut down)"
|
||||
|
||||
# Ensure the directory exists (should now exist after first boot)
|
||||
SETTINGS_DIR="$HOME/Library/Developer/CoreSimulator/Devices/$SIMULATOR_ID/data/Containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles/Library/ConfigurationProfiles"
|
||||
mkdir -p "$SETTINGS_DIR"
|
||||
|
||||
# Use the JavaScript utility to disable password autofill
|
||||
cd detox
|
||||
node utils/disable_ios_autofill.js --simulator-id "$SIMULATOR_ID"
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "⚠️ Failed to disable password autofill"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd ..
|
||||
|
||||
echo "Booting simulator with password autofill disabled..."
|
||||
xcrun simctl boot "$SIMULATOR_ID"
|
||||
|
||||
# Wait for boot to complete
|
||||
xcrun simctl bootstatus "$SIMULATOR_ID"
|
||||
|
||||
# Open Simulator.app in background to speed up UI rendering
|
||||
open -a Simulator --args -CurrentDeviceUDID "$SIMULATOR_ID"
|
||||
|
||||
# Give Simulator.app time to fully initialize UI (iOS 26.2 needs more time)
|
||||
sleep 5
|
||||
|
||||
echo "SIMULATOR_ID=$SIMULATOR_ID" >> $GITHUB_ENV
|
||||
|
||||
- name: Start Proxy
|
||||
if: ${{ inputs.low_bandwidth_mode }}
|
||||
id: start-proxy
|
||||
|
|
@ -256,119 +184,31 @@ jobs:
|
|||
with:
|
||||
test_server_url: ${{ env.SITE_1_URL }}
|
||||
|
||||
- name: Set .env with RUNNING_E2E=true
|
||||
- name: Run Detox E2E Tests
|
||||
continue-on-error: true # Label might have been removed manually
|
||||
run: |
|
||||
echo "RUNNING_E2E=true" > .env
|
||||
|
||||
- name: Verify Simulator Health
|
||||
run: |
|
||||
echo "Verifying simulator health before running tests..."
|
||||
|
||||
# Check if simulator is still responsive
|
||||
if ! xcrun simctl bootstatus "$SIMULATOR_ID" -b 2>/dev/null; then
|
||||
echo "⚠️ Simulator not responsive, attempting recovery..."
|
||||
|
||||
# Try to shutdown and reboot
|
||||
xcrun simctl shutdown "$SIMULATOR_ID" || true
|
||||
sleep 3
|
||||
xcrun simctl boot "$SIMULATOR_ID"
|
||||
xcrun simctl bootstatus "$SIMULATOR_ID"
|
||||
fi
|
||||
|
||||
echo "✅ Simulator is healthy and ready"
|
||||
|
||||
- name: Start Metro Bundler and Run Detox E2E Tests
|
||||
continue-on-error: true # We want to run all the tests
|
||||
run: |
|
||||
# Start Metro bundler in background
|
||||
npm run start > metro.log 2>&1 &
|
||||
METRO_PID=$!
|
||||
echo "Metro PID: $METRO_PID"
|
||||
echo "METRO_PID=$METRO_PID" >> $GITHUB_ENV
|
||||
|
||||
# Wait for Metro to be ready with health check endpoint polling
|
||||
timeout=120
|
||||
elapsed=0
|
||||
echo "Waiting for Metro bundler to be ready..."
|
||||
while [ $elapsed -lt $timeout ]; do
|
||||
# Try health check endpoint first (more reliable)
|
||||
if curl -s http://localhost:8081/status 2>/dev/null | grep -q "packager-status:running"; then
|
||||
echo "✅ Metro bundler is ready after ${elapsed}s (verified via health check)"
|
||||
break
|
||||
# Fallback to log checking
|
||||
elif grep -q "Fast - Scalable - Integrated" metro.log 2>/dev/null; then
|
||||
echo "✅ Metro bundler is ready after ${elapsed}s (verified via logs)"
|
||||
break
|
||||
fi
|
||||
|
||||
# Show progress every 10 seconds
|
||||
if [ $((elapsed % 10)) -eq 0 ] && [ $elapsed -gt 0 ]; then
|
||||
echo "Still waiting for Metro... (${elapsed}s elapsed)"
|
||||
fi
|
||||
|
||||
sleep 2
|
||||
elapsed=$((elapsed + 2))
|
||||
done
|
||||
|
||||
if [ $elapsed -ge $timeout ]; then
|
||||
echo "⚠️ Warning: Metro bundler may not be fully ready after ${timeout}s, proceeding anyway"
|
||||
echo "Metro process status:"
|
||||
ps aux | grep -E "metro|node.*start" | grep -v grep || echo "Metro process not found"
|
||||
echo ""
|
||||
echo "Last 30 lines of metro.log:"
|
||||
tail -30 metro.log
|
||||
else
|
||||
echo "Metro is serving on http://localhost:8081"
|
||||
# Give Metro extra time to fully initialize for iOS 26.2 (React Native bridge needs more time)
|
||||
echo "Allowing Metro to fully stabilize..."
|
||||
sleep 8
|
||||
fi
|
||||
|
||||
cd detox
|
||||
npm run detox:config-gen
|
||||
npm run e2e:ios-test -- ${{ matrix.specs }}
|
||||
env:
|
||||
DETOX_DISABLE_HIERARCHY_DUMP: "YES"
|
||||
DETOX_DISABLE_SCREENSHOT_TRACKING: "YES"
|
||||
DETOX_LOGLEVEL: "debug"
|
||||
DETOX_DEVICE_TYPE: ${{ env.DEVICE_NAME }}
|
||||
DETOX_OS_VERSION: ${{ env.DEVICE_OS_VERSION }}
|
||||
LOW_BANDWIDTH_MODE: ${{ inputs.low_bandwidth_mode }}
|
||||
|
||||
- name: Cleanup Simulator State
|
||||
if: always()
|
||||
run: |
|
||||
echo "Cleaning up simulator state after tests..."
|
||||
|
||||
# Stop Metro bundler if still running
|
||||
if [ ! -z "$METRO_PID" ]; then
|
||||
echo "Stopping Metro bundler (PID: $METRO_PID)..."
|
||||
kill -9 "$METRO_PID" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Terminate app gracefully
|
||||
if [ ! -z "$SIMULATOR_ID" ]; then
|
||||
echo "Terminating app on simulator..."
|
||||
xcrun simctl terminate "$SIMULATOR_ID" com.tokilabs.mattermost 2>/dev/null || true
|
||||
sleep 2
|
||||
fi
|
||||
|
||||
echo "✅ Simulator cleanup complete"
|
||||
|
||||
- name: reset network settings
|
||||
if: ${{ inputs.low_bandwidth_mode || failure() }}
|
||||
run: |
|
||||
networksetup -setwebproxystate Ethernet "off"
|
||||
networksetup -setsecurewebproxystate Ethernet "off"
|
||||
|
||||
if (sudo pfctl -q -sa | grep 'Status: Enabled') then sudo pfctl -d; fi
|
||||
if (command -v pm2 &> /dev/null) then pm2 stop mitmdump; fi
|
||||
if (sudo pfctl -q -sa | grep 'Status: Enabled') then sudo pfctl -d; fi
|
||||
if (command -v pm2 &> /dev/null) then pm2 stop mitmdump; fi
|
||||
|
||||
sleep 5;
|
||||
|
||||
- name: Upload mitmdump Flow Output
|
||||
if: ${{ inputs.low_bandwidth_mode }}
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: ios-mitmdump-flow-output-${{ needs.generate-specs.outputs.workflow_hash }}-${{ matrix.runId }}
|
||||
path: |
|
||||
|
|
@ -377,13 +217,14 @@ jobs:
|
|||
|
||||
- name: Upload iOS Test Report
|
||||
if: always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: ios-results-${{ needs.generate-specs.outputs.workflow_hash }}-${{ matrix.runId }}
|
||||
path: detox/artifacts/
|
||||
|
||||
generate-report:
|
||||
runs-on: ubuntu-22.04
|
||||
if: ${{ inputs.run-ios-tests}}
|
||||
needs:
|
||||
- generate-specs
|
||||
- e2e-ios
|
||||
|
|
@ -400,7 +241,7 @@ jobs:
|
|||
- name: ci/prepare-node-deps
|
||||
uses: ./.github/actions/prepare-node-deps
|
||||
|
||||
- name: Download iOS Artifacts
|
||||
- name: Download All Artifacts
|
||||
uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4
|
||||
with:
|
||||
path: detox/artifacts/
|
||||
|
|
@ -421,6 +262,7 @@ jobs:
|
|||
env:
|
||||
DETOX_AWS_ACCESS_KEY_ID: ${{ secrets.MM_MOBILE_DETOX_AWS_ACCESS_KEY_ID }}
|
||||
DETOX_AWS_SECRET_ACCESS_KEY: ${{ secrets.MM_MOBILE_DETOX_AWS_SECRET_ACCESS_KEY }}
|
||||
IOS: ${{ inputs.run-ios-tests }}
|
||||
BUILD_ID: ${{ needs.generate-specs.outputs.build_id }}
|
||||
REPORT_PATH: ${{ steps.s3.outputs.path }}
|
||||
## These are needed for the MM Webhook report
|
||||
|
|
@ -459,36 +301,3 @@ jobs:
|
|||
echo "" >> ${GITHUB_STEP_SUMMARY}
|
||||
echo "You can check the full report [here](${{ steps.set-url.outputs.TARGET_URL }})" >> ${GITHUB_STEP_SUMMARY}
|
||||
echo "There was **${{ steps.summary.outputs.PERCENTAGE }}%** success rate." >> ${GITHUB_STEP_SUMMARY}
|
||||
|
||||
- name: Comment report on the PR
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const prNumber = context.payload.pull_request.number;
|
||||
|
||||
const commentBody = `**iOS E2E Test Report**: ${process.env.MOBILE_SHA} | ${process.env.PERCENTAGE}% (${process.env.PASSES}/${process.env.TOTAL}) | [full report](${process.env.TARGET_URL})
|
||||
| Tests | Passed ✅ | Failed ❌ | Skipped ⏭️ | Errors ⚠️ |
|
||||
|:---:|:---:|:---:|:---:|:---:|
|
||||
| ${process.env.TOTAL} | ${process.env.PASSES} | ${process.env.FAILURES} | ${process.env.SKIPPED} | ${process.env.ERRORS} |
|
||||
`;
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber,
|
||||
body: commentBody,
|
||||
});
|
||||
env:
|
||||
STATUS: ${{ steps.determine-status.outputs.STATUS }}
|
||||
FAILURES: ${{ steps.summary.outputs.FAILURES }}
|
||||
PASSES: ${{ steps.summary.outputs.PASSES }}
|
||||
SKIPPED: ${{ steps.summary.outputs.SKIPPED }}
|
||||
TOTAL: ${{ steps.summary.outputs.TOTAL }}
|
||||
ERRORS: ${{ steps.summary.outputs.ERRORS }}
|
||||
PERCENTAGE: ${{ steps.summary.outputs.PERCENTAGE }}
|
||||
BUILD_ID: ${{ needs.generate-specs.outputs.build_id }}
|
||||
RUN_TYPE: ${{ inputs.run-type }}
|
||||
MOBILE_REF: ${{ needs.generate-specs.outputs.mobile_ref }}
|
||||
MOBILE_SHA: ${{ needs.generate-specs.outputs.mobile_sha }}
|
||||
TARGET_URL: ${{ steps.set-url.outputs.TARGET_URL }}
|
||||
165
.github/workflows/github-release.yml
vendored
|
|
@ -4,18 +4,6 @@ on:
|
|||
push:
|
||||
tags:
|
||||
- v[0-9]+.[0-9]+.[0-9]+*
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: "Release tag (e.g., v2.10.0)"
|
||||
required: true
|
||||
type: string
|
||||
|
||||
env:
|
||||
RELEASE_TAG: ${{ inputs.tag || github.ref_name }}
|
||||
SNYK_VERSION: "1.1297.2"
|
||||
CYCLONEDX_VERSION: "v0.27.2"
|
||||
INTUNE_ENABLED: 'true'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
|
|
@ -27,54 +15,38 @@ jobs:
|
|||
uses: ./.github/actions/test
|
||||
|
||||
build-ios-unsigned:
|
||||
runs-on: macos-15-large
|
||||
runs-on: macos-14-large
|
||||
needs:
|
||||
- test
|
||||
steps:
|
||||
- name: ci/checkout-repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: ci/setup-ssh-key
|
||||
uses: ./.github/actions/setup-ssh-key
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.MM_MOBILE_PRIVATE_DEPLOY_KEY }}
|
||||
|
||||
- name: ci/prepare-ios-build
|
||||
uses: ./.github/actions/prepare-ios-build
|
||||
with:
|
||||
intune-enabled: ${{ env.INTUNE_ENABLED }}
|
||||
intune-ssh-private-key: ${{ secrets.MM_MOBILE_INTUNE_DEPLOY_KEY }}
|
||||
|
||||
- name: ci/output-ssh-private-key
|
||||
shell: bash
|
||||
run: |
|
||||
SSH_KEY_PATH=~/.ssh/id_ed25519
|
||||
mkdir -p ~/.ssh
|
||||
echo -e '${{ secrets.MM_MOBILE_PRIVATE_DEPLOY_KEY }}' > ${SSH_KEY_PATH}
|
||||
chmod 0600 ${SSH_KEY_PATH}
|
||||
ssh-keygen -y -f ${SSH_KEY_PATH} > ${SSH_KEY_PATH}.pub
|
||||
|
||||
- name: ci/build-ios-unsigned
|
||||
env:
|
||||
TAG: "${{ env.RELEASE_TAG }}"
|
||||
TAG: "${{ github.ref_name }}"
|
||||
GITHUB_TOKEN: "${{ secrets.MM_MOBILE_GITHUB_TOKEN }}"
|
||||
run: bundle exec fastlane ios unsigned
|
||||
working-directory: ./fastlane
|
||||
|
||||
- name: ci/upload-ios-unsigned
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
path: Mattermost-unsigned.ipa
|
||||
name: Mattermost-unsigned.ipa
|
||||
|
||||
- name: ci/install-snyk
|
||||
run: npm install -g snyk@${{ env.SNYK_VERSION }}
|
||||
|
||||
- name: ci/generate-ios-sbom
|
||||
env:
|
||||
SNYK_TOKEN: "${{ secrets.SNYK_TOKEN }}"
|
||||
run: |
|
||||
snyk sbom --format=cyclonedx1.6+json --json-file-output=../sbom-ios.json --all-projects
|
||||
working-directory: ./ios
|
||||
shell: bash
|
||||
|
||||
- name: ci/upload-ios-sbom
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
path: sbom-ios.json
|
||||
name: sbom-ios.json
|
||||
|
||||
build-android-unsigned:
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
|
|
@ -90,133 +62,34 @@ jobs:
|
|||
|
||||
- name: ci/build-android-beta
|
||||
env:
|
||||
TAG: "${{ env.RELEASE_TAG }}"
|
||||
TAG: "${{ github.ref_name }}"
|
||||
GITHUB_TOKEN: "${{ secrets.MM_MOBILE_GITHUB_TOKEN }}"
|
||||
run: bundle exec fastlane android unsigned
|
||||
working-directory: ./fastlane
|
||||
|
||||
- name: ci/upload-android-unsigned-build
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
path: Mattermost-unsigned.apk
|
||||
name: Mattermost-unsigned.apk
|
||||
|
||||
- name: ci/install-snyk
|
||||
run: npm install -g snyk@${{ env.SNYK_VERSION }}
|
||||
|
||||
- name: ci/generate-android-sbom
|
||||
env:
|
||||
SNYK_TOKEN: "${{ secrets.SNYK_TOKEN }}"
|
||||
run: |
|
||||
snyk sbom --format=cyclonedx1.6+json --all-projects --json-file-output=../sbom-android.json
|
||||
working-directory: ./android
|
||||
shell: bash
|
||||
|
||||
- name: ci/upload-android-sbom
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
path: sbom-android.json
|
||||
name: sbom-android.json
|
||||
|
||||
generate-consolidated-sbom:
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- build-ios-unsigned
|
||||
- build-android-unsigned
|
||||
steps:
|
||||
- name: ci/checkout-repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: ci/download-sboms
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
pattern: sbom-*.json
|
||||
path: ${{ github.workspace }}
|
||||
merge-multiple: true
|
||||
|
||||
- name: ci/install-snyk
|
||||
run: npm install -g snyk@${{ env.SNYK_VERSION }}
|
||||
|
||||
- name: ci/setup-cyclonedx-cli
|
||||
run: |
|
||||
set -e
|
||||
CYCLONEDX_BINARY="cyclonedx-linux-x64"
|
||||
CYCLONEDX_URL="https://github.com/CycloneDX/cyclonedx-cli/releases/download/${{ env.CYCLONEDX_VERSION }}/${CYCLONEDX_BINARY}"
|
||||
|
||||
# Download with better error handling and retry
|
||||
echo "Downloading CycloneDX CLI ${{ env.CYCLONEDX_VERSION }}..."
|
||||
curl -sSfL --retry 3 --retry-delay 5 "${CYCLONEDX_URL}" -o cyclonedx
|
||||
|
||||
# Verify the binary is executable and not corrupted
|
||||
if [ ! -s cyclonedx ]; then
|
||||
echo "Error: Downloaded file is empty or corrupted"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Make executable and move to PATH
|
||||
chmod +x cyclonedx
|
||||
sudo mv cyclonedx /usr/local/bin/
|
||||
|
||||
# Verify installation
|
||||
cyclonedx --version
|
||||
|
||||
- name: ci/generate-consolidated-sbom
|
||||
env:
|
||||
SNYK_TOKEN: "${{ secrets.SNYK_TOKEN }}"
|
||||
SBOM_FILENAME: "sbom-${{ github.event.repository.name }}-${{ env.RELEASE_TAG }}.json"
|
||||
run: |
|
||||
# Check if required SBOM files are available
|
||||
if [ ! -f "sbom-android.json" ]; then
|
||||
echo "Error: sbom-android.json not found. Android SBOM generation may have failed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "sbom-ios.json" ]; then
|
||||
echo "Error: sbom-ios.json not found. iOS SBOM generation may have failed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "All required SBOM files are available. Proceeding with consolidation..."
|
||||
|
||||
# Generate top-level SBOM
|
||||
snyk sbom --format=cyclonedx1.6+json --json-file-output=sbom-top-level.json
|
||||
|
||||
# Consolidate SBOMs
|
||||
cyclonedx merge \
|
||||
--input-files "sbom-top-level.json" "sbom-android.json" "sbom-ios.json" \
|
||||
--input-format=json \
|
||||
--output-file="$SBOM_FILENAME" \
|
||||
--output-format=json \
|
||||
--output-version=v1_6
|
||||
|
||||
# Validate the consolidated SBOM
|
||||
cyclonedx validate --input-file="$SBOM_FILENAME"
|
||||
shell: bash
|
||||
|
||||
- name: ci/upload-consolidated-sbom
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
path: sbom-${{ github.event.repository.name }}-${{ env.RELEASE_TAG }}.json
|
||||
name: sbom-${{ github.event.repository.name }}-${{ env.RELEASE_TAG }}.json
|
||||
|
||||
release:
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- build-ios-unsigned
|
||||
- build-android-unsigned
|
||||
- generate-consolidated-sbom
|
||||
steps:
|
||||
- name: ci/checkout-repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- uses: ruby/setup-ruby@cb0fda56a307b8c78d38320cd40d9eb22a3bf04e # v1.242.0
|
||||
- uses: ruby/setup-ruby@32110d4e311bd8996b2a82bf2a43b714ccc91777 # v1.221.0
|
||||
|
||||
- name: release/setup-fastlane-dependencies
|
||||
run: bundle install
|
||||
working-directory: ./fastlane
|
||||
|
||||
- name: ci/download-artifacts
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
|
||||
with:
|
||||
path: ${{ github.workspace }}
|
||||
merge-multiple: true
|
||||
|
|
@ -226,9 +99,3 @@ jobs:
|
|||
GITHUB_TOKEN: "${{ secrets.MM_MOBILE_GITHUB_TOKEN }}"
|
||||
run: bundle exec fastlane github
|
||||
working-directory: ./fastlane
|
||||
|
||||
- name: release/upload-sbom-to-release
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ secrets.MM_MOBILE_GITHUB_TOKEN }}"
|
||||
run: |
|
||||
gh release upload "${{ env.RELEASE_TAG }}" "sbom-${{ github.event.repository.name }}-${{ env.RELEASE_TAG }}.json"
|
||||
|
|
|
|||
145
.github/workflows/validate-intune-clean.yml
vendored
|
|
@ -1,145 +0,0 @@
|
|||
---
|
||||
name: Validate Intune Clean
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'ios/Podfile'
|
||||
- 'ios/Podfile.lock'
|
||||
- 'package.json'
|
||||
- 'package-lock.json'
|
||||
- 'ios/Mattermost/Info.plist'
|
||||
- 'ios/Mattermost/Mattermost.entitlements'
|
||||
- 'ios/Mattermost.xcodeproj/project.pbxproj'
|
||||
|
||||
jobs:
|
||||
validate-podfile-lock:
|
||||
name: Validate Podfile.lock is OSS-only
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: ci/checkout-repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Check Podfile.lock for Intune references
|
||||
run: |
|
||||
if grep -q "mattermost-intune\|IntuneMAMSwift" ios/Podfile.lock; then
|
||||
echo ""
|
||||
echo "❌ ERROR: Podfile.lock contains Intune dependencies"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
echo "The committed Podfile.lock should only contain OSS dependencies."
|
||||
echo "Please restore the OSS version:"
|
||||
echo ""
|
||||
echo " git checkout main -- ios/Podfile.lock"
|
||||
echo ""
|
||||
echo "Or run: npm run intune:disable"
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Podfile.lock is clean (OSS-only)"
|
||||
|
||||
- name: Check for package.json Intune reference
|
||||
run: |
|
||||
# Check only dependencies and devDependencies sections (not scripts)
|
||||
if jq -e '.dependencies."@mattermost/intune" // .devDependencies."@mattermost/intune"' package.json > /dev/null 2>&1; then
|
||||
echo ""
|
||||
echo "❌ ERROR: package.json contains @mattermost/intune dependency"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
echo "The Intune library should NOT be in package.json dependencies."
|
||||
echo "It is installed via --no-save flag during internal builds only."
|
||||
echo ""
|
||||
echo "The 'intune:link' script is OK - only dependencies are checked."
|
||||
echo ""
|
||||
echo "Please remove the @mattermost/intune entry from dependencies."
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ package.json is clean (no Intune in dependencies)"
|
||||
|
||||
- name: Check for package-lock.json Intune reference
|
||||
run: |
|
||||
if grep -q "@mattermost/intune" package-lock.json 2>/dev/null; then
|
||||
echo ""
|
||||
echo "❌ ERROR: package-lock.json contains @mattermost/intune"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
echo "The Intune library should NOT be in package-lock.json."
|
||||
echo ""
|
||||
echo "Please run: npm install (without INTUNE_ENABLED set)"
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ package-lock.json is clean (no Intune reference)"
|
||||
|
||||
- name: Check Info.plist for Intune configuration
|
||||
run: |
|
||||
if grep -q "IntuneMAMSettings\|msauth\.com\.microsoft\.intunemam\|mattermost-intunemam\|mmauthbeta-intunemam\|intunemam-mtd\|msauthv2\|msauthv3" ios/Mattermost/Info.plist; then
|
||||
echo ""
|
||||
echo "❌ ERROR: Info.plist contains Intune configuration"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
echo "The Info.plist should NOT contain Intune-specific settings."
|
||||
echo "Intune configuration is applied by Fastlane during internal builds only."
|
||||
echo ""
|
||||
echo "Please restore the OSS version:"
|
||||
echo ""
|
||||
echo " git checkout main -- ios/Mattermost/Info.plist"
|
||||
echo ""
|
||||
echo "Or run: npm run intune:disable"
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Info.plist is clean (no Intune configuration)"
|
||||
|
||||
- name: Check entitlements for Intune keychain groups
|
||||
run: |
|
||||
if grep -q "com\.microsoft\.adalcache\|com\.microsoft\.intune\.mam\|\.intunemam" ios/Mattermost/Mattermost.entitlements 2>/dev/null; then
|
||||
echo ""
|
||||
echo "❌ ERROR: Mattermost.entitlements contains Intune keychain groups"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
echo "The entitlements should NOT contain Intune-specific keychain groups."
|
||||
echo "Intune keychain groups are added by Fastlane during internal builds only."
|
||||
echo ""
|
||||
echo "Please restore the OSS version:"
|
||||
echo ""
|
||||
echo " git checkout main -- ios/Mattermost/Mattermost.entitlements"
|
||||
echo ""
|
||||
echo "Or run: npm run intune:disable"
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Mattermost.entitlements is clean (no Intune keychain groups)"
|
||||
|
||||
- name: Check project.pbxproj for Intune frameworks
|
||||
run: |
|
||||
if grep -q "MSAL\|IntuneMAM\|IntuneMAMSwift" ios/Mattermost.xcodeproj/project.pbxproj 2>/dev/null; then
|
||||
echo ""
|
||||
echo "❌ ERROR: project.pbxproj contains Intune framework references"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
echo "The project.pbxproj should NOT contain Intune framework references."
|
||||
echo "Intune frameworks (MSAL, IntuneMAM, IntuneMAMSwift) are added by Fastlane during internal builds only."
|
||||
echo ""
|
||||
echo "Please restore the OSS version:"
|
||||
echo ""
|
||||
echo " git checkout main -- ios/Mattermost.xcodeproj/project.pbxproj"
|
||||
echo ""
|
||||
echo "Or run: npm run intune:disable"
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ project.pbxproj is clean (no Intune framework references)"
|
||||
301
.gitignore
vendored
|
|
@ -1,199 +1,124 @@
|
|||
# Sorted combined gitignore from .gitignore and .gitignore_my
|
||||
|
||||
!build/notice-file
|
||||
!debug.keystore
|
||||
!default.mode1v3
|
||||
!default.mode2v3
|
||||
!default.pbxuser
|
||||
!default.perspectivev3
|
||||
!gradle-wrapper.jar
|
||||
!gradle-wrapper.properties
|
||||
!libraries/@mattermost/intune/.gitkeep
|
||||
!src/**/build/
|
||||
**/build/
|
||||
**/fastlane/.env
|
||||
**/fastlane/Preview.html
|
||||
**/fastlane/report.xml
|
||||
**/fastlane/screenshots
|
||||
**/fastlane/test_output
|
||||
*.aab
|
||||
*.apk
|
||||
*.hmap
|
||||
*.hprof
|
||||
*.iml
|
||||
*.ipa
|
||||
*.jks
|
||||
*.jsbundle
|
||||
*.keystore
|
||||
*.lcov
|
||||
*.log
|
||||
*.mattermost-license
|
||||
*.mode1v3
|
||||
*.mode2v3
|
||||
*.moved-aside
|
||||
*.pbxuser
|
||||
*.perspectivev3
|
||||
*.pid
|
||||
*.pid.lock
|
||||
*.seed
|
||||
*.tgz
|
||||
*.tsbuildinfo
|
||||
*.un~
|
||||
*.xccheckout
|
||||
*.xcscmblueprint
|
||||
*.xcuserstate
|
||||
assets/override
|
||||
dist
|
||||
build-ios
|
||||
*.zip
|
||||
*/**/compass-icons.ttf
|
||||
*~
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
.AppleDouble
|
||||
.DS_Store
|
||||
.DocumentRevisions-V100
|
||||
.LSOverride
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trash-*
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
._*
|
||||
.aider*
|
||||
.apdisk
|
||||
.buckconfig.local
|
||||
.buckd/
|
||||
.buckversion
|
||||
.bundle
|
||||
.cache
|
||||
.cache/
|
||||
.classpath
|
||||
.claude/settings.local.json
|
||||
.com.apple.timemachine.donotpresent
|
||||
.cxx/
|
||||
.directory
|
||||
.docusaurus
|
||||
.dynamodb/
|
||||
server.PID
|
||||
mattermost.keystore
|
||||
tmp/
|
||||
.env
|
||||
.env.development.local
|
||||
.env.local
|
||||
.env.production.local
|
||||
.env.test.local
|
||||
.eslintcache
|
||||
.expo
|
||||
.externalNativeBuild/
|
||||
.fakebuckversion
|
||||
.fseventsd
|
||||
.fuse_hidden*
|
||||
.fusebox/
|
||||
.gradle
|
||||
.gradle/
|
||||
.gradletasknamecache
|
||||
.grunt
|
||||
.idea
|
||||
.idea/
|
||||
.lock-wscript
|
||||
.metro-health-check*
|
||||
.netrwhist
|
||||
.next
|
||||
.nfs*
|
||||
.node_repl_history
|
||||
.npm
|
||||
.npminstall
|
||||
.nuxt
|
||||
.nyc_output
|
||||
.parcel-cache
|
||||
.pnp.*
|
||||
.pnpm-debug.log*
|
||||
.podinstall
|
||||
.project
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
.scannerwork
|
||||
.serverless/
|
||||
.settings
|
||||
.stylelintcache
|
||||
.temp
|
||||
.tern-port
|
||||
.tmp
|
||||
.tsbuildinfo.precommit
|
||||
.vscode/*
|
||||
!.vscode/launch.json
|
||||
!.vscode/settings.json
|
||||
.vscode-test
|
||||
.vuepress/dist
|
||||
.yarn-integrity
|
||||
.yarn/build-state.yml
|
||||
.yarn/cache
|
||||
.yarn/install-state.gz
|
||||
.yarn/unplugged
|
||||
.yarninstall
|
||||
env.d.ts
|
||||
*.apk
|
||||
*.aab
|
||||
*.ipa
|
||||
|
||||
*/**/compass-icons.ttf
|
||||
|
||||
# OSX
|
||||
#
|
||||
.DS_Store
|
||||
|
||||
# Xcode
|
||||
#
|
||||
ios/build/*
|
||||
*.pbxuser
|
||||
!default.pbxuser
|
||||
*.mode1v3
|
||||
!default.mode1v3
|
||||
*.mode2v3
|
||||
!default.mode2v3
|
||||
*.perspectivev3
|
||||
!default.perspectivev3
|
||||
xcuserdata
|
||||
*.xccheckout
|
||||
*.moved-aside
|
||||
DerivedData
|
||||
Icon
|
||||
Network Trash Folder
|
||||
Session.vim
|
||||
Temporary Items
|
||||
[._]*.s[a-w][a-z]
|
||||
[._]s[a-w][a-z]
|
||||
__generated__
|
||||
*.hmap
|
||||
*.xcuserstate
|
||||
project.xcworkspace
|
||||
ios/Pods
|
||||
.podinstall
|
||||
ios/.xcode.env.local
|
||||
|
||||
# Android/IntelliJ
|
||||
#
|
||||
.idea
|
||||
.gradle
|
||||
local.properties
|
||||
*.iml
|
||||
*.hprof
|
||||
.cxx/
|
||||
*.keystore
|
||||
!debug.keystore
|
||||
android/app/bin
|
||||
android/app/build
|
||||
android/app/src/main/res/raw/*
|
||||
android/build
|
||||
.settings
|
||||
.project
|
||||
.classpath
|
||||
|
||||
# node.js
|
||||
#
|
||||
node_modules/
|
||||
npm-debug.log
|
||||
.npminstall
|
||||
yarn-error.log
|
||||
.yarninstall
|
||||
|
||||
# Vim
|
||||
[._]*.s[a-w][a-z]
|
||||
[._]s[a-w][a-z]
|
||||
*.un~
|
||||
Session.vim
|
||||
.netrwhist
|
||||
*~
|
||||
tags
|
||||
|
||||
# fastlane
|
||||
#
|
||||
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
|
||||
# screenshots whenever they are needed.
|
||||
# For more information about the recommended setup visit:
|
||||
# https://docs.fastlane.tools/best-practices/source-control/
|
||||
|
||||
**/fastlane/report.xml
|
||||
**/fastlane/Preview.html
|
||||
**/fastlane/screenshots
|
||||
**/fastlane/test_output
|
||||
**/fastlane/.env
|
||||
|
||||
# Sentry
|
||||
android/sentry.properties
|
||||
assets/override
|
||||
bower_components
|
||||
buck-out/
|
||||
build-ios
|
||||
build/Release
|
||||
captures/
|
||||
ios/sentry.properties
|
||||
|
||||
# Testing
|
||||
.nyc_output
|
||||
coverage
|
||||
deploymentTargetDropDown.xml
|
||||
.tmp
|
||||
|
||||
# E2E testing
|
||||
mattermost-license.txt
|
||||
*.mattermost-license
|
||||
detox/artifacts
|
||||
detox/detox_pixel_*
|
||||
dist
|
||||
env.d.ts
|
||||
google-services.json
|
||||
gradle-app.setting
|
||||
ios/.xcode.env.local
|
||||
ios/Pods
|
||||
ios/build/*
|
||||
ios/sentry.properties
|
||||
jspm_packages/
|
||||
lerna-debug.log*
|
||||
lib-cov
|
||||
libraries/**/**/.build
|
||||
libraries/**/**/build
|
||||
libraries/@mattermost/intune/*
|
||||
local.properties
|
||||
logs
|
||||
mattermost-license.txt
|
||||
mattermost.keystore
|
||||
misc.xml
|
||||
node_modules/
|
||||
node_modules/@mattermost/intune
|
||||
npm-debug.log
|
||||
npm-debug.log*
|
||||
out
|
||||
output.json
|
||||
pids
|
||||
project.xcworkspace
|
||||
render.experimental.xml
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
server.PID
|
||||
tags
|
||||
tmp/
|
||||
web_modules/
|
||||
xcuserdata
|
||||
xcuserdata/
|
||||
yarn-debug.log*
|
||||
yarn-error.log
|
||||
yarn-error.log*
|
||||
|
||||
# 새로 적용된 .gitignore 규칙에 의해 무시되었으나, 기존 프로젝트에서 관리(Tracking)되고 있던 필수 파일들 예외 처리
|
||||
!android/app/debug.keystore
|
||||
!android/app/google-services.json
|
||||
!build/notice-file/Readme.md
|
||||
!build/notice-file/config.yaml
|
||||
!env.d.ts
|
||||
!fastlane/screenshots/README.txt
|
||||
# Bundle artifact
|
||||
*.jsbundle
|
||||
.bundle
|
||||
|
||||
#editor-settings
|
||||
.vscode
|
||||
.scannerwork
|
||||
launch.json
|
||||
|
||||
# Notice.txt generation
|
||||
!build/notice-file
|
||||
|
||||
# Temporary files created by Metro to check the health of the file watcher
|
||||
.metro-health-check*
|
||||
|
||||
libraries/**/**/build
|
||||
libraries/**/**/.build
|
||||
|
||||
# Android sounds
|
||||
android/app/src/main/res/raw/*
|
||||
.aider*
|
||||
|
|
|
|||
11
.gitmodules
vendored
|
|
@ -1,11 +0,0 @@
|
|||
# Intune MAM library submodule (private repository - internal builds only)
|
||||
# To pin to a specific tag/commit:
|
||||
# cd libraries/@mattermost/intune
|
||||
# git checkout <tag-or-commit>
|
||||
# cd ../..
|
||||
# git add libraries/@mattermost/intune
|
||||
# git commit -m "Update Intune submodule to <version>"
|
||||
|
||||
[submodule "libraries/@mattermost/intune"]
|
||||
path = libraries/@mattermost/intune
|
||||
url = git@github.com:mattermost/mattermost-mobile-intune.git
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
# 필수 준수 사항
|
||||
- agent-ops/rules/global.md 파일을 읽고 이에 대해 꼭 준수해서 진행해야한다.
|
||||
- global.md 내용을 모르면 추정하지 말고 먼저 해당 파일을 확인한다.
|
||||
|
|
@ -1 +0,0 @@
|
|||
-
|
||||
|
|
@ -1 +0,0 @@
|
|||
-
|
||||
|
|
@ -1 +0,0 @@
|
|||
-
|
||||
|
|
@ -1 +0,0 @@
|
|||
-
|
||||
|
|
@ -1 +0,0 @@
|
|||
-
|
||||
|
|
@ -1 +0,0 @@
|
|||
-
|
||||
|
|
@ -1 +1 @@
|
|||
22.14.0
|
||||
20.13.1
|
||||
2
.nvmrc
|
|
@ -1 +1 @@
|
|||
22.14.0
|
||||
20.13.1
|
||||
|
|
|
|||
31
.vscode/launch.json
vendored
|
|
@ -1,31 +0,0 @@
|
|||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Metro: Start",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"runtimeExecutable": "bash",
|
||||
"runtimeArgs": [
|
||||
"-c",
|
||||
"lsof -ti:8081 | xargs kill -9 2>/dev/null; npm start"
|
||||
],
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "neverOpen"
|
||||
},
|
||||
{
|
||||
"name": "Android: Run App",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"runtimeExecutable": "bash",
|
||||
"runtimeArgs": [
|
||||
"-c",
|
||||
"adb devices | grep -q emulator || (emulator -avd Pixel_7_Pro &); adb -e wait-for-device && npm run android -- --deviceId emulator-5554"
|
||||
],
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "neverOpen"
|
||||
}
|
||||
]
|
||||
}
|
||||
17
.vscode/settings.json
vendored
|
|
@ -1,17 +0,0 @@
|
|||
{
|
||||
"react-native.packager.port": 8081,
|
||||
"terminal.integrated.env.osx": {
|
||||
"NVM_DIR": "${env:HOME}/.nvm",
|
||||
"JAVA_HOME": "/opt/homebrew/opt/openjdk@17",
|
||||
"ANDROID_HOME": "${env:HOME}/Library/Android/sdk",
|
||||
"ANDROID_SDK_ROOT": "${env:HOME}/Library/Android/sdk"
|
||||
},
|
||||
"terminal.integrated.profiles.osx": {
|
||||
"zsh": {
|
||||
"path": "/bin/zsh",
|
||||
"args": ["-l"]
|
||||
}
|
||||
},
|
||||
"terminal.integrated.defaultProfile.osx": "zsh",
|
||||
"npm.scriptExplorerAction": "run"
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
# 필수 준수 사항
|
||||
- agent-ops/rules/global.md 파일을 읽고 이에 대해 꼭 준수해서 진행해야한다.
|
||||
- global.md 내용을 모르면 추정하지 말고 먼저 해당 파일을 확인한다.
|
||||
|
|
@ -1,209 +0,0 @@
|
|||
---
|
||||
name: scaffold-agent-ops
|
||||
description: 프로젝트 상태를 판별하고 에이전트 진입 파일과 agent-ops 기본 스캐폴드를 생성하기 위한 초기 규칙
|
||||
version: 1.0.0
|
||||
---
|
||||
|
||||
# 목적
|
||||
|
||||
이 스킬은 프로젝트에 Agent-Ops 구조가 없거나 불완전할 때,
|
||||
현재 프로젝트 상태를 분석하여 다음을 세팅한다.
|
||||
|
||||
1. 에이전트 진입 파일
|
||||
2. agent-ops 기본 폴더 구조
|
||||
3. global rule
|
||||
4. domain rule 템플릿
|
||||
5. skill router
|
||||
6. skill 템플릿
|
||||
7. 초기 domain / skill 세팅
|
||||
|
||||
이 스킬은 과도한 초기 생성보다
|
||||
"현재 프로젝트에 맞는 최소 스캐폴드" 생성을 우선한다.
|
||||
|
||||
# 핵심 원칙
|
||||
|
||||
- 기존 구조를 우선한다.
|
||||
- 새 파일 생성은 꼭 필요한 최소 범위로 제한한다.
|
||||
- 에이전트별 md는 얇은 진입점으로 둔다.
|
||||
- 실제 규칙은 agent-ops 아래로 모은다.
|
||||
- 도메인은 발명하지 말고 현재 구조에서 발견한다.
|
||||
- 처음에는 핵심 도메인만 생성한다.
|
||||
- 반복되는 작업만 초기 skill로 만든다.
|
||||
- 불확실한 내용은 후보로 제시한다.
|
||||
|
||||
# 상태 판별
|
||||
|
||||
## 신규 프로젝트
|
||||
다음 조건이 많으면 신규 프로젝트로 본다.
|
||||
|
||||
- 코드/폴더 구조가 단순하다
|
||||
- 도메인 경계가 아직 약하다
|
||||
- Agent-Ops 관련 파일이 없다
|
||||
- 반복 작업 패턴이 아직 적다
|
||||
|
||||
## 운영중 프로젝트
|
||||
다음 조건이 많으면 운영중 프로젝트로 본다.
|
||||
|
||||
- 모듈/폴더/패키지 경계가 보인다
|
||||
- 반복 작업이 드러난다
|
||||
- 기존 규칙/문서/관례의 흔적이 있다
|
||||
- 핵심 책임 경계가 식별된다
|
||||
|
||||
# 생성 대상
|
||||
|
||||
이 스킬은 아래 구조를 기준으로 scaffold를 제안하거나 생성한다.
|
||||
|
||||
- CLAUDE.md
|
||||
- GEMINI.md
|
||||
- .cursorrules
|
||||
- agent-ops/rules/global.md
|
||||
- agent-ops/rules/_templates/domain-rule-template.md
|
||||
- agent-ops/rules/domain/<domain>/rules.md
|
||||
- agent-ops/skills/router.md
|
||||
- agent-ops/skills/_templates/skill-template.md
|
||||
|
||||
필요 시 아래 초기 skill도 함께 제안한다.
|
||||
|
||||
- agent-ops/skills/find-domain/SKILL.md
|
||||
- agent-ops/skills/code-change/SKILL.md
|
||||
|
||||
# 에이전트 진입 파일 원칙
|
||||
|
||||
CLAUDE.md, GEMINI.md, .cursorrules 는 공통적으로 얇게 유지한다.
|
||||
|
||||
각 파일에는 아래만 둔다.
|
||||
|
||||
- 응답 언어
|
||||
- 기존 구조 우선
|
||||
- 새 파일 생성보다 기존 수정 우선
|
||||
- 작업 전 agent-ops/skills/router.md 참조
|
||||
- 관련 rule / skill 만 최소 로드
|
||||
- 세부 규칙은 agent-ops 아래 파일을 따른다
|
||||
|
||||
각 에이전트 진입 파일에 프로젝트별 상세 규칙을 중복 작성하지 않는다.
|
||||
|
||||
# Rule 구조 원칙
|
||||
|
||||
## global.md
|
||||
전 프로젝트 공통 규칙만 둔다.
|
||||
|
||||
포함 항목:
|
||||
- 응답 언어
|
||||
- 기존 구조 우선
|
||||
- 새 파일 생성보다 기존 수정 우선
|
||||
- 코드 변경 전 관련 domain rule 확인
|
||||
- 요청 범위를 넘는 변경 금지
|
||||
- 불확실하면 단정하지 말고 후보 제시
|
||||
- 플랫폼 특화 규칙은 global에 두지 않음
|
||||
|
||||
## domain rule
|
||||
각 도메인 rules.md 는 아래만 둔다.
|
||||
|
||||
- 목적 / 책임
|
||||
- 포함 경로
|
||||
- 제외 경로
|
||||
- 주요 구성 요소
|
||||
- 유지할 패턴
|
||||
- 다른 도메인과의 경계
|
||||
- 금지 사항
|
||||
|
||||
# DDD 기준
|
||||
|
||||
도메인은 아래 기준으로 분류한다.
|
||||
|
||||
## Core Domain
|
||||
핵심 가치와 주요 유즈케이스를 담당하는 영역
|
||||
|
||||
## Supporting Domain
|
||||
핵심 도메인을 지원하는 영역
|
||||
|
||||
## Generic / Common
|
||||
여러 도메인이 공통으로 사용하는 범용 영역
|
||||
|
||||
도메인은 반드시 실제 폴더, 모듈, 패키지, 네임스페이스, 책임 경계와 연결되어야 한다.
|
||||
|
||||
# Skill 구조 원칙
|
||||
|
||||
## router.md
|
||||
router.md 는 작업 유형에 따라 필요한 skill 또는 domain rule만 로드하게 한다.
|
||||
|
||||
최소 라우팅 축:
|
||||
- 구조 분석 / 스캐폴드
|
||||
- 도메인 판별
|
||||
- 코드 변경
|
||||
- 흐름 추적
|
||||
- 문서/릴리즈 정리
|
||||
|
||||
처음부터 너무 많은 분기를 만들지 않는다.
|
||||
|
||||
## skill-template.md
|
||||
각 skill 은 아래 형식을 따른다.
|
||||
|
||||
- 목적
|
||||
- 언제 호출할지
|
||||
- 입력
|
||||
- 먼저 확인할 것
|
||||
- 실행 절차
|
||||
- 출력 형식
|
||||
- 금지 사항
|
||||
|
||||
# 신규 프로젝트 처리
|
||||
|
||||
신규 프로젝트일 때는 템플릿 중심으로 세팅한다.
|
||||
|
||||
1. 언어/구조를 확인한다.
|
||||
2. 에이전트 진입 파일 3개를 얇게 생성한다.
|
||||
3. agent-ops/rules/global.md 생성
|
||||
4. domain-rule-template.md 생성
|
||||
5. skills/router.md 생성
|
||||
6. skill-template.md 생성
|
||||
7. 핵심 domain placeholder 2~4개만 제안
|
||||
8. 초기 skill 은 최소 2개만 제안한다.
|
||||
|
||||
신규 프로젝트에서는 domain rules 를 과도하게 채우지 않는다.
|
||||
|
||||
# 운영중 프로젝트 처리
|
||||
|
||||
운영중 프로젝트일 때는 현재 구조 기반으로 자동 채움한다.
|
||||
|
||||
1. 구조를 분석한다.
|
||||
2. Core / Supporting / Generic 도메인 후보를 식별한다.
|
||||
3. 실제 경로를 반영한 domain rules 초안을 제안한다.
|
||||
4. 반복 작업 기반으로 필요한 초기 skill 을 제안한다.
|
||||
5. router.md 에 기본 라우팅을 채운다.
|
||||
6. 신규 프로젝트보다 한 단계 더 구체적으로 scaffold 한다.
|
||||
|
||||
운영중 프로젝트에서는 실제 코드 구조에 맞는 domain / skill 을 일부 채워 넣는다.
|
||||
|
||||
# 출력 형식
|
||||
|
||||
## 상태 판별
|
||||
- Agent-Ops 상태: 없음 / 부분 적용 / 운영중
|
||||
- 프로젝트 상태: 신규 / 운영중
|
||||
- 근거: 짧게 요약
|
||||
|
||||
## 스캐폴드 계획
|
||||
- 생성할 파일
|
||||
- 바로 채울 파일
|
||||
- placeholder 로 둘 파일
|
||||
|
||||
## 도메인 제안
|
||||
- Core Domain
|
||||
- Supporting Domain
|
||||
- Generic / Common
|
||||
|
||||
## 초기 skill 제안
|
||||
- skill 이름
|
||||
- 필요한 이유
|
||||
|
||||
## 주의사항
|
||||
- 지금 만들지 말아야 할 것
|
||||
- 아직 확정하면 안 되는 것
|
||||
|
||||
# 금지
|
||||
|
||||
- 플랫폼 전용 규칙을 기본 scaffold 에 넣지 않는다.
|
||||
- agent 별 md 에 상세 규칙을 중복 작성하지 않는다.
|
||||
- 실제 구조보다 앞선 추상 구조를 강요하지 않는다.
|
||||
- 처음부터 많은 domain / skill 을 만들지 않는다.
|
||||
- scaffold 단계에서 불필요한 세부 구현까지 생성하지 않는다.
|
||||
|
|
@ -1,172 +0,0 @@
|
|||
# iOS Simulator Setup Guide for AI Agents
|
||||
|
||||
This guide documents the process for setting up and running the Mattermost Mobile app on an iOS simulator.
|
||||
|
||||
## Prerequisites Check
|
||||
|
||||
Before starting, verify these are installed:
|
||||
|
||||
```bash
|
||||
# Check Xcode
|
||||
xcodebuild -version
|
||||
|
||||
# Check CocoaPods
|
||||
pod --version
|
||||
|
||||
# Check Node.js
|
||||
node --version
|
||||
|
||||
# Check npm dependencies
|
||||
test -d node_modules && echo "node_modules exists" || echo "Run: npm install"
|
||||
```
|
||||
|
||||
## Step 1: iOS Simulator Runtime
|
||||
|
||||
Check if iOS simulators are available:
|
||||
|
||||
```bash
|
||||
xcrun simctl list devices available
|
||||
```
|
||||
|
||||
If no devices are listed or you see "Install Started", you need to download the iOS platform:
|
||||
|
||||
```bash
|
||||
xcodebuild -downloadPlatform iOS
|
||||
```
|
||||
|
||||
**Note:** This downloads ~8GB and takes several minutes. The command runs in the foreground and shows progress.
|
||||
|
||||
## Step 2: CocoaPods Installation
|
||||
|
||||
### Critical: Set UTF-8 Encoding
|
||||
|
||||
CocoaPods requires UTF-8 encoding. Always set this before running pod commands:
|
||||
|
||||
```bash
|
||||
export LANG=en_US.UTF-8
|
||||
export LC_ALL=en_US.UTF-8
|
||||
```
|
||||
|
||||
### Install Pods (Apple Silicon Mac)
|
||||
|
||||
```bash
|
||||
cd ios
|
||||
RCT_NEW_ARCH_ENABLED=0 arch -x86_64 pod install
|
||||
```
|
||||
|
||||
### Common CocoaPods Issues
|
||||
|
||||
#### Issue: "Unicode Normalization not appropriate for ASCII-8BIT"
|
||||
**Solution:** Set UTF-8 encoding as shown above.
|
||||
|
||||
#### Issue: Podfile.lock version conflicts
|
||||
**Solution:** Clean install:
|
||||
```bash
|
||||
cd ios
|
||||
rm -rf Pods Podfile.lock build
|
||||
export LANG=en_US.UTF-8 && export LC_ALL=en_US.UTF-8
|
||||
RCT_NEW_ARCH_ENABLED=0 arch -x86_64 pod install
|
||||
```
|
||||
|
||||
#### Issue: GitHub SSH timeouts during pod install
|
||||
**Symptom:** Errors like "Connection to github.com port 22: Operation timed out"
|
||||
|
||||
**Solution:** Temporarily configure git to use HTTPS instead of SSH:
|
||||
```bash
|
||||
git config --global url."https://github.com/".insteadOf git@github.com:
|
||||
```
|
||||
|
||||
**IMPORTANT:** Revert this after pod install completes, or the user won't be able to push:
|
||||
```bash
|
||||
git config --global --unset url.https://github.com/.insteadof
|
||||
```
|
||||
|
||||
**Warning:** Never leave git URL rewrites in place without informing the user!
|
||||
|
||||
## Step 3: Start Metro Bundler (if not already running)
|
||||
|
||||
```bash
|
||||
npm start
|
||||
```
|
||||
|
||||
Wait for "Dev server ready" message before proceeding.
|
||||
|
||||
## Step 4: Run iOS App (if not already built)
|
||||
|
||||
### Using npm script
|
||||
```bash
|
||||
npm run ios -- --simulator="iPhone 17 Pro"
|
||||
```
|
||||
|
||||
### Or specify a different simulator
|
||||
List available simulators first:
|
||||
```bash
|
||||
xcrun simctl list devices available | grep -E "iPhone|iPad"
|
||||
```
|
||||
|
||||
Then run with your chosen device:
|
||||
```bash
|
||||
npm run ios -- --simulator="iPhone 16e"
|
||||
```
|
||||
|
||||
## Build Times
|
||||
|
||||
- **First build:** 10-30 minutes (compiles all native code)
|
||||
- **Subsequent builds:** Much faster (incremental)
|
||||
- **JS/TS changes:** ~3 seconds hot reload (no native rebuild needed)
|
||||
|
||||
## Troubleshooting Build Failures
|
||||
|
||||
### Getting Better Error Messages
|
||||
|
||||
If `npm run ios` fails, run xcodebuild directly for clearer errors:
|
||||
|
||||
```bash
|
||||
xcodebuild -workspace ios/Mattermost.xcworkspace \
|
||||
-configuration Debug \
|
||||
-scheme Mattermost \
|
||||
-destination 'platform=iOS Simulator,name=iPhone 17 Pro' \
|
||||
2>&1 | grep -E "error:|fatal error"
|
||||
```
|
||||
|
||||
### Clean Build
|
||||
|
||||
If builds fail mysteriously, try a clean build:
|
||||
|
||||
```bash
|
||||
# Clean Xcode derived data
|
||||
rm -rf ~/Library/Developer/Xcode/DerivedData/Mattermost-*
|
||||
|
||||
# Clean and reinstall pods
|
||||
cd ios
|
||||
rm -rf Pods build
|
||||
export LANG=en_US.UTF-8 && export LC_ALL=en_US.UTF-8
|
||||
RCT_NEW_ARCH_ENABLED=0 arch -x86_64 pod install
|
||||
```
|
||||
|
||||
### Opening in Xcode
|
||||
|
||||
For complex build issues, open in Xcode for better debugging:
|
||||
|
||||
```bash
|
||||
open ios/Mattermost.xcworkspace
|
||||
```
|
||||
|
||||
Then select your simulator target and press Cmd+B to build.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
```bash
|
||||
# Full setup sequence (Apple Silicon)
|
||||
export LANG=en_US.UTF-8 && export LC_ALL=en_US.UTF-8
|
||||
cd ios && RCT_NEW_ARCH_ENABLED=0 arch -x86_64 pod install && cd ..
|
||||
npm start &
|
||||
sleep 10
|
||||
npm run ios -- --simulator="iPhone 17 Pro"
|
||||
```
|
||||
|
||||
## Do NOT Modify
|
||||
|
||||
- **Git configuration:** Never permanently change git URL rewrites without reverting
|
||||
- **Podfile:** Don't add modular_headers to pods unless you understand the implications
|
||||
- **New Architecture:** Keep `RCT_NEW_ARCH_ENABLED=0` as the project uses the old architecture
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
# 필수 준수 사항
|
||||
- agent-ops/rules/global.md 파일을 읽고 이에 대해 꼭 준수해서 진행해야한다.
|
||||
- global.md 내용을 모르면 추정하지 말고 먼저 해당 파일을 확인한다.
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
# 필수 준수 사항
|
||||
- agent-ops/rules/global.md 파일을 읽고 이에 대해 꼭 준수해서 진행해야한다.
|
||||
- global.md 내용을 모르면 추정하지 말고 먼저 해당 파일을 확인한다.
|
||||
1
Gemfile
|
|
@ -1,4 +1,3 @@
|
|||
source "https://rubygems.org"
|
||||
|
||||
gem "cocoapods", "1.16.1"
|
||||
gem "rexml", ">= 3.4.2"
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ GEM
|
|||
netrc (0.11.0)
|
||||
nkf (0.2.0)
|
||||
public_suffix (4.0.7)
|
||||
rexml (3.4.4)
|
||||
rexml (3.3.9)
|
||||
ruby-macho (2.5.1)
|
||||
securerandom (0.4.0)
|
||||
typhoeus (1.4.1)
|
||||
|
|
@ -107,7 +107,6 @@ PLATFORMS
|
|||
|
||||
DEPENDENCIES
|
||||
cocoapods (= 1.16.1)
|
||||
rexml (>= 3.4.2)
|
||||
|
||||
BUNDLED WITH
|
||||
2.5.11
|
||||
|
|
|
|||
268
NOTICE.txt
|
|
@ -20,21 +20,6 @@ A spec-compliant polyfill for Intl.DateTimeFormat fully tested by the official E
|
|||
|
||||
* LICENSE: MIT
|
||||
|
||||
---
|
||||
|
||||
## @formatjs/intl-displaynames
|
||||
|
||||
This product contains '@formatjs/intl-displaynames' by Linjie Ding.
|
||||
|
||||
Polyfill for: https://tc39.es/proposal-intl-displaynames
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/formatjs/formatjs
|
||||
|
||||
* LICENSE: MIT
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
## @formatjs/intl-getcanonicallocales
|
||||
|
|
@ -102,21 +87,6 @@ A spec-compliant polyfill for Intl.PluralRules fully tested by the official ECMA
|
|||
|
||||
* LICENSE: MIT
|
||||
|
||||
---
|
||||
|
||||
## @formatjs/intl-relativetimeformat
|
||||
|
||||
This product contains '@formatjs/intl-relativetimeformat' by Long Ho.
|
||||
|
||||
Formats JavaScript dates to relative time strings.
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/formatjs/formatjs
|
||||
|
||||
* LICENSE: MIT
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
## @gorhom/bottom-sheet
|
||||
|
|
@ -150,42 +120,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## @gorhom/portal
|
||||
|
||||
This product contains '@gorhom/portal' by Mo Gorhom.
|
||||
|
||||
A simplified portal implementation for ⭕️ React Native ⭕️
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/gorhom/react-native-portal#readme
|
||||
|
||||
* LICENSE: MIT
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Mo Gorhom
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## @mattermost/calls
|
||||
|
|
@ -309,39 +243,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## @mattermost/secure-pdf-viewer
|
||||
|
||||
This product contains '@mattermost/secure-pdf-viewer' by Mattermost.
|
||||
|
||||
Securely view PDF documents without allowing them to be downloaded to their devices.
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/mattermost/secure-pdf-viewer
|
||||
|
||||
* LICENSE: MIT
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Mattermost, Inc.
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -796,28 +697,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|||
SOFTWARE.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## @shopify/flash-list
|
||||
|
||||
This product contains '@shopify/flash-list' by shopify.
|
||||
|
||||
FlashList is a more performant FlatList replacement
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://shopify.github.io/flash-list/
|
||||
|
||||
* LICENSE: MIT
|
||||
|
||||
Copyright 2022-present, Shopify Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## @stream-io/flat-list-mvcp
|
||||
|
|
@ -1753,43 +1632,6 @@ Lightweight fuzzy-search
|
|||
limitations under the License.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## grapheme-splitter
|
||||
|
||||
This product contains 'grapheme-splitter' by Orlin Georgiev.
|
||||
|
||||
A JavaScript library that breaks strings into their individual user-perceived characters. It supports emojis!
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/orling/grapheme-splitter
|
||||
|
||||
* LICENSE: MIT
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Orlin Georgiev
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
## html-entities
|
||||
|
|
@ -1824,43 +1666,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|||
THE SOFTWARE.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## js-sha256
|
||||
|
||||
This product contains 'js-sha256' by Chen, Yi-Cyuan.
|
||||
|
||||
A simple SHA-256 / SHA-224 hash function for JavaScript supports UTF-8 encoding.
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/emn178/js-sha256
|
||||
|
||||
* LICENSE: MIT
|
||||
|
||||
Copyright (c) 2014-2025 Chen, Yi-Cyuan
|
||||
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## mime-db
|
||||
|
|
@ -1988,42 +1793,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## prismjs
|
||||
|
||||
This product contains 'prismjs' by Lea Verou.
|
||||
|
||||
Lightweight, robust, elegant syntax highlighting. A spin-off project from Dabblet.
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/PrismJS/prism#readme
|
||||
|
||||
* LICENSE: MIT
|
||||
|
||||
MIT LICENSE
|
||||
|
||||
Copyright (c) 2012 Lea Verou
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## react
|
||||
|
|
@ -2525,42 +2294,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|||
SOFTWARE.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## react-native-keyboard-controller
|
||||
|
||||
This product contains 'react-native-keyboard-controller' by Kiryl Ziusko.
|
||||
|
||||
Keyboard manager which works in identical way on both iOS and Android
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://kirillzyusko.github.io/react-native-keyboard-controller/
|
||||
|
||||
* LICENSE: MIT
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Kiryl Ziusko
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## react-native-keychain
|
||||
|
|
@ -3346,4 +3079,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
|
|
|||
36
README.md
|
|
@ -1,7 +1,7 @@
|
|||
# Mattermost Mobile v2
|
||||
|
||||
- **Minimum Server versions:** Current ESR version (10.11.0+)
|
||||
- **Supported iOS versions:** 16.0+
|
||||
- **Minimum Server versions:** Current ESR version (9.11.0+)
|
||||
- **Supported iOS versions:** 15.1+
|
||||
- **Supported Android versions:** 7.0+
|
||||
|
||||
Mattermost is an open source Slack-alternative used by thousands of companies around the world in 21 languages. Learn more at [https://mattermost.com](https://mattermost.com).
|
||||
|
|
@ -12,34 +12,6 @@ We plan on releasing monthly updates with new features - check the [changelog](h
|
|||
|
||||
**Important:** If you self-compile the Mattermost Mobile apps you also need to deploy your own [Mattermost Push Notification Service](https://github.com/mattermost/mattermost-push-proxy/releases).
|
||||
|
||||
# Android 16KB Page Size Support
|
||||
|
||||
**Temporary Requirement:** To comply with Google Play's 16KB page size requirement for Android devices, this project includes a compatibility patch that must be applied before building for Android.
|
||||
|
||||
### When to Apply the Patch
|
||||
|
||||
- **CI/CD Builds:** The patch is automatically applied in GitHub Actions for Android builds
|
||||
- **Local Development:** If you're building Android locally and encounter 16KB page size related issues, run:
|
||||
|
||||
```bash
|
||||
npm run apply-16kb-pagesize-patch
|
||||
```
|
||||
|
||||
This script will:
|
||||
1. Update package dependencies to compatible versions
|
||||
2. Apply necessary code changes for 16KB page size support
|
||||
3. Update patch files for modified dependencies
|
||||
4. Regenerate `package-lock.json`
|
||||
|
||||
### ⚠️ Important Warnings
|
||||
|
||||
- **DO NOT commit the changes** applied by this patch to the repository
|
||||
- **These changes will break iOS builds** if committed
|
||||
- The patch is designed to be applied only during Android CI builds
|
||||
- For local development, revert all changes after building Android
|
||||
|
||||
**Note:** This is a temporary solution until all dependencies natively support 16KB page sizes.
|
||||
|
||||
# How to Contribute
|
||||
|
||||
### Testing
|
||||
|
|
@ -47,7 +19,7 @@ This script will:
|
|||
To help with testing app updates before they're released, you can:
|
||||
|
||||
1. Sign up to be a beta tester
|
||||
- [Android](https://play.google.com/apps/testing/com.tokilabs.mattermost)
|
||||
- [Android](https://play.google.com/apps/testing/com.mattermost.rnbeta)
|
||||
- [iOS](https://testflight.apple.com/join/Q7Rx7K9P) - Open this link from your iOS device
|
||||
2. Install the `Mattermost Beta` app. New updates in the Beta app are released periodically. You will receive a notification when the new updates are available.
|
||||
3. File any bugs you find by filing a [GitHub issue](https://github.com/mattermost/mattermost-mobile/issues) with:
|
||||
|
|
@ -59,7 +31,7 @@ To help with testing app updates before they're released, you can:
|
|||
- Join the [Native Mobile Apps channel](https://community.mattermost.com/core/channels/native-mobile-apps) to see what's new and discuss feedback with other contributors and the core team
|
||||
|
||||
You can leave the Beta testing program at any time:
|
||||
- On Android, [click this link](https://play.google.com/apps/testing/com.tokilabs.mattermost) while logged in with your Google Play email address used to opt-in for the Beta program, then click **Leave the program**.
|
||||
- On Android, [click this link](https://play.google.com/apps/testing/com.mattermost.rnbeta) while logged in with your Google Play email address used to opt-in for the Beta program, then click **Leave the program**.
|
||||
- On iOS, access the `Mattermost Beta` app page in TestFlight and click **Stop Testing**.
|
||||
|
||||
### Contribute Code
|
||||
|
|
|
|||
|
|
@ -1,32 +0,0 @@
|
|||
---
|
||||
name: <domain-name>
|
||||
description: <도메인 한 줄 설명>
|
||||
type: core | supporting | generic
|
||||
---
|
||||
|
||||
# <Domain Name>
|
||||
|
||||
## 목적 / 책임
|
||||
<이 도메인이 담당하는 핵심 책임>
|
||||
|
||||
## 포함 경로
|
||||
- `app/<path>/`
|
||||
- `app/actions/local/<file>.ts`
|
||||
- `app/actions/remote/<file>.ts`
|
||||
|
||||
## 제외 경로
|
||||
- <이 도메인에 속하지 않는 경로>
|
||||
|
||||
## 주요 구성 요소
|
||||
- **<component>**: <역할>
|
||||
- **<component>**: <역할>
|
||||
|
||||
## 유지할 패턴
|
||||
- <이 도메인에서 따라야 할 패턴>
|
||||
|
||||
## 다른 도메인과의 경계
|
||||
- **database 도메인**: DB 조작은 database 도메인 패턴을 따른다
|
||||
- **network 도메인**: API 호출은 ClientMix 패턴을 따른다
|
||||
|
||||
## 금지 사항
|
||||
- <이 도메인에서 하면 안 되는 것>
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
---
|
||||
name: database
|
||||
description: WatermelonDB 이중 DB 시스템 - 앱 DB와 서버별 DB
|
||||
type: supporting
|
||||
---
|
||||
|
||||
# Database Domain
|
||||
|
||||
## 목적 / 책임
|
||||
WatermelonDB 기반 이중 데이터베이스를 관리한다. DatabaseManager 싱글톤, operator, 스키마, 모델을 담당한다.
|
||||
|
||||
## 포함 경로
|
||||
- `app/database/` (전체)
|
||||
- `app/database/manager/` - DatabaseManager 싱글톤
|
||||
- `app/database/models/app/` - 앱 전역 모델 (서버 목록 등)
|
||||
- `app/database/models/server/` - 서버별 모델 (채널, 유저, 포스트 등)
|
||||
- `app/database/operator/` - 데이터 쓰기 operator
|
||||
- `app/database/schema/` - DB 스키마 정의
|
||||
- `docs/database/` - DB 스키마 문서
|
||||
|
||||
## 제외 경로
|
||||
- `app/queries/` (Query Layer는 별도 - 여기서 DB를 읽는다)
|
||||
|
||||
## 주요 구성 요소
|
||||
- **DatabaseManager**: 모든 DB 인스턴스 관리 싱글톤 (`app/database/manager/index.ts`)
|
||||
- **App Database**: 서버 목록 등 전역 앱 상태 (하나만 존재)
|
||||
- **Server Database**: 서버별 채널/유저/포스트 데이터 (서버당 하나)
|
||||
- **Operators**: transformer/handler/comparator 패턴으로 데이터 쓰기
|
||||
- **Schema**: 테이블 정의 및 마이그레이션
|
||||
|
||||
## 유지할 패턴
|
||||
- DB 경로: iOS는 App Group, Android는 `${documentDirectory}/databases/`
|
||||
- Operator를 통해 batch 작업으로 데이터 쓰기
|
||||
- Sync handler는 create/update/delete 전체 라이프사이클 처리 필수
|
||||
- 스키마 변경 시 반드시 버전 번호 증가 + 문서(`docs/database/`) 업데이트
|
||||
- Query Layer(`app/queries/`)는 `query*`, `observe*`, `get*`, `prepare*` 패턴
|
||||
|
||||
## 다른 도메인과의 경계
|
||||
- **모든 도메인**: DB 직접 접근보다 Query Layer(`app/queries/`) 사용 권장
|
||||
- **network 도메인**: remote action이 API 응답을 operator에 전달
|
||||
|
||||
## 금지 사항
|
||||
- 스키마 변경 후 `docs/database/` 문서 미업데이트
|
||||
- Sync handler에서 stale 레코드 삭제 누락 (`prepareDestroyPermanently()` 필수)
|
||||
- 테스트에서 `extraLokiOptions: {autosave: false}` 누락 (메모리 누수)
|
||||
- 테스트 후 `DatabaseManager.destroyServerDatabase()` cleanup 누락
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
---
|
||||
name: messaging
|
||||
description: 채널, 포스트, 스레드, 드래프트 등 핵심 메시징 기능
|
||||
type: core
|
||||
---
|
||||
|
||||
# Messaging Domain
|
||||
|
||||
## 목적 / 책임
|
||||
Mattermost의 핵심 메시징 기능: 채널, 포스트, 스레드, 리액션, 드래프트, 예약 포스트를 담당한다.
|
||||
|
||||
## 포함 경로
|
||||
- `app/actions/local/channel.ts`, `post.ts`, `thread.ts`, `draft.ts`, `reactions.ts`, `scheduled_post.ts`
|
||||
- `app/actions/remote/channel.ts`, `post.ts`, `thread.ts`, `reactions.ts`, `search.ts`
|
||||
- `app/queries/servers/channel.ts`, `post.ts`, `thread.ts`, `drafts.ts`, `scheduled_post.ts`, `reaction.ts`
|
||||
- `app/database/models/server/channel*.ts`, `post.ts`, `thread*.ts`, `draft.ts`, `reaction.ts`, `scheduled_post.ts`
|
||||
- `app/screens/channel/`, `app/screens/thread/`, `app/screens/edit_post/`
|
||||
- `app/components/post_list/`, `app/components/post_draft/`
|
||||
|
||||
## 제외 경로
|
||||
- `app/products/` (products는 각 product 도메인)
|
||||
- `app/actions/remote/user.ts` (user 도메인)
|
||||
|
||||
## 주요 구성 요소
|
||||
- **Channel**: 채널 CRUD, 멤버십, 카테고리 관리
|
||||
- **Post**: 포스트 생성/수정/삭제, 파일 첨부, 리액션
|
||||
- **Thread**: 스레드 팔로우, 언급, 참여자 관리
|
||||
- **Draft**: 임시저장, 예약 포스트
|
||||
- **Search**: 채널/포스트 검색
|
||||
|
||||
## 유지할 패턴
|
||||
- Remote action: `fetchXxx()` → API 호출 → operator로 DB 저장 → `{error}` 반환
|
||||
- Local action: DB 조작만 수행
|
||||
- Sync handler는 create/update/delete 전체 라이프사이클을 처리한다
|
||||
- `fetchMissingProfilesByIds()` 사용 - 직접 `client.getProfilesByIds()` 호출 금지
|
||||
|
||||
## 다른 도메인과의 경계
|
||||
- **database 도메인**: WatermelonDB 스키마 변경 시 `docs/database/server/server.md` 업데이트 필요
|
||||
- **network 도메인**: API 호출은 `client/rest.ts`의 ClientMix 패턴 사용
|
||||
- **ui 도메인**: 화면 렌더링은 ui 도메인, 데이터 로직은 이 도메인
|
||||
|
||||
## 금지 사항
|
||||
- `console.error()` / `console.log()` 직접 사용 (→ `logError()`, `logDebug()` 사용)
|
||||
- `TouchableOpacity` 사용 (→ `Pressable` 사용)
|
||||
- DB sync handler에서 삭제 누락 (stale 레코드는 `prepareDestroyPermanently()` 처리)
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
---
|
||||
name: products-agents
|
||||
description: AI 에이전트 기능 - WebSocket 스트리밍, AI 봇, 툴 호출
|
||||
type: core
|
||||
---
|
||||
|
||||
# Products/Agents Domain
|
||||
|
||||
## 목적 / 책임
|
||||
AI 에이전트 기능을 담당한다. WebSocket 스트리밍, AI 봇 관리, 툴 호출/승인, 어노테이션을 처리한다.
|
||||
|
||||
## 포함 경로
|
||||
- `app/products/agents/` (전체)
|
||||
- `app/products/agents/actions/`
|
||||
- `app/products/agents/client/rest.ts`
|
||||
- `app/products/agents/components/`
|
||||
- `app/products/agents/constants.ts`
|
||||
- `app/products/agents/hooks/`
|
||||
- `app/products/agents/screens/`
|
||||
- `app/products/agents/store/`
|
||||
- `app/products/agents/types/`
|
||||
- `app/products/agents/websocket/`
|
||||
|
||||
## 제외 경로
|
||||
- `app/actions/remote/` (messaging 도메인)
|
||||
- `app/products/calls/` (calls 도메인)
|
||||
|
||||
## 주요 구성 요소
|
||||
- **WebSocket Handler**: `custom_mattermost-ai_postupdate` 이벤트 처리 (start, message, reasoning, tool_call, annotation, end)
|
||||
- **Streaming Store**: 스트리밍 중 ephemeral 상태 관리
|
||||
- **AI Bot**: AI 봇 목록 조회 및 DB 동기화 (`handleAIBots`)
|
||||
- **Tool Call**: 툴 호출 승인/거절 처리
|
||||
- **Client (ClientMix)**: `app/products/agents/client/rest.ts`
|
||||
|
||||
## 유지할 패턴
|
||||
- **WebSocket 스트리밍**: `ENDED` 이벤트에서 로컬 스트리밍 상태를 반드시 초기화 (stale state 방지)
|
||||
- **ClientMix 패턴**: API 호출은 `client/rest.ts` 내에서만 수행, action에서 직접 `doFetch()` 금지
|
||||
- **PostType 추가 시**: `types/api/posts.d.ts`의 `PostType` union에 타입 추가
|
||||
- **Sync handler**: create/update/delete 모두 처리 (`prepareDestroyPermanently()` 필수)
|
||||
|
||||
## 다른 도메인과의 경계
|
||||
- **messaging 도메인**: 포스트 데이터는 `POST_EDITED` WebSocket 이벤트로 수신
|
||||
- **database 도메인**: Agents 전용 DB 모델 사용
|
||||
- **network 도메인**: ClientMix를 통해 NetworkManager와 연결
|
||||
|
||||
## 금지 사항
|
||||
- 툴 호출 상태 변경을 별도 WebSocket 이벤트로 처리 (→ `POST_EDITED`로 수신)
|
||||
- `ENDED` 이벤트 후 스트리밍 상태 미초기화
|
||||
- action 파일에서 직접 `client.doFetch()` 호출
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
---
|
||||
name: products-calls
|
||||
description: 음성/화상 통화 기능
|
||||
type: core
|
||||
---
|
||||
|
||||
# Products/Calls Domain
|
||||
|
||||
## 목적 / 책임
|
||||
음성/화상 통화 기능을 담당한다. 통화 연결, 상태 관리, UI를 처리한다.
|
||||
|
||||
## 포함 경로
|
||||
- `app/products/calls/` (전체)
|
||||
- `app/products/calls/actions/`
|
||||
- `app/products/calls/client/rest.ts`
|
||||
- `app/products/calls/components/`
|
||||
- `app/products/calls/constants.ts`
|
||||
- `app/products/calls/hooks/`
|
||||
- `app/products/calls/screens/`
|
||||
- `app/products/calls/store/`
|
||||
- `app/products/calls/types/`
|
||||
|
||||
## 제외 경로
|
||||
- `app/products/agents/` (agents 도메인)
|
||||
- `app/products/playbooks/` (playbooks 도메인)
|
||||
|
||||
## 주요 구성 요소
|
||||
- **Call Connection**: WebRTC 기반 통화 연결 관리
|
||||
- **Call State**: Ephemeral store로 통화 상태 관리
|
||||
- **Client (ClientMix)**: `app/products/calls/client/rest.ts`
|
||||
|
||||
## 유지할 패턴
|
||||
- **ClientMix 패턴**: API 호출은 `client/rest.ts` 내에서만 수행
|
||||
- **Ephemeral Store**: 통화 상태는 ephemeral store로 관리 (DB 아님)
|
||||
|
||||
## 다른 도메인과의 경계
|
||||
- **messaging 도메인**: 채널 정보는 messaging 도메인 쿼리 사용
|
||||
- **network 도메인**: ClientMix를 통해 NetworkManager와 연결
|
||||
|
||||
## 금지 사항
|
||||
- action 파일에서 직접 `client.doFetch()` 호출
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
---
|
||||
name: ui
|
||||
description: 컴포넌트, 스크린, 네비게이션, 스타일링
|
||||
type: generic
|
||||
---
|
||||
|
||||
# UI Domain
|
||||
|
||||
## 목적 / 책임
|
||||
React Native UI 컴포넌트, 스크린, 네비게이션, 스타일링을 담당한다.
|
||||
|
||||
## 포함 경로
|
||||
- `app/components/` - 재사용 컴포넌트
|
||||
- `app/screens/` - 화면 단위 컴포넌트
|
||||
- `app/hooks/` - 공통 React hooks
|
||||
- `app/utils/` - UI 유틸리티
|
||||
- `share_extension/` - Android 공유 확장 (React Navigation 사용)
|
||||
|
||||
## 제외 경로
|
||||
- `app/products/*/components/` (각 product 도메인)
|
||||
- `app/actions/` (비즈니스 로직은 messaging/product 도메인)
|
||||
|
||||
## 주요 구성 요소
|
||||
- **Navigation**: `react-native-navigation` v7 (메인 앱), `React Navigation` (share extension)
|
||||
- **Components**: 60+ 재사용 컴포넌트
|
||||
- **Screens**: 63+ 화면 컴포넌트 (각각 독립 등록)
|
||||
- **Themes**: `PREFERENCES.THEMES` 상수 사용
|
||||
|
||||
## 유지할 패턴
|
||||
- `Pressable` 사용 (→ `TouchableOpacity` 금지), 항상 pressed 피드백 추가
|
||||
- `typography()` 사용 (→ raw `fontSize`/`fontWeight`/`fontFamily` 금지)
|
||||
- `makeStyleSheetFromTheme` 사용 시 `StyleSheet.create` 불필요
|
||||
- `getStyleSheet` 는 파일 상단(imports 이후)에 위치
|
||||
- 정적 객체(hitSlop 등)는 모듈 레벨 상수로 추출
|
||||
- `FormattedText` 사용 (→ `<Text>{intl.formatMessage(...)}</Text>` 지양)
|
||||
- `useServerUrl()` hook 사용 (→ `serverUrl` prop 전달 지양)
|
||||
- `usePreventDoubleTap` hook 사용 (버튼 중복 클릭 방지)
|
||||
- 리스트 렌더링: 인라인 화살표 함수 금지, 자식 컴포넌트에서 ID 받아 처리
|
||||
- `useDidMount` (→ `useEffect(cb, [])` 대신), `useInitialValue` (→ `useMemo(f, [])` 대신)
|
||||
|
||||
## 다른 도메인과의 경계
|
||||
- **messaging/product 도메인**: UI는 데이터 표시만, 비즈니스 로직은 해당 도메인
|
||||
- **database 도메인**: `observe*` 쿼리를 통해 반응형 데이터 구독
|
||||
|
||||
## 금지 사항
|
||||
- `TouchableOpacity` 사용
|
||||
- raw `fontSize`, `fontWeight`, `fontFamily` 스타일 직접 사용
|
||||
- `Object.hasOwn()` 사용 (ES2022+, RN 미지원 → `'key' in obj` 사용)
|
||||
- `Animated` API 사용 (→ `react-native-reanimated` 사용)
|
||||
- `new URL()` 사용 (→ `urlParse` 사용)
|
||||
- `Linking.openURL()` 직접 사용 (→ `tryOpenURL()` 사용)
|
||||
- `ActivityIndicator` 직접 사용 (→ `<Loading>` 컴포넌트 사용)
|
||||
- 커스텀 React 컴포넌트를 Markdown 컴포넌트 내부에 인라인 삽입
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
---
|
||||
name: global
|
||||
description: 전 프로젝트 공통 규칙
|
||||
version: 1.0.0
|
||||
---
|
||||
|
||||
# Global Rules
|
||||
|
||||
## 응답 언어
|
||||
- 한국어로 응답한다.
|
||||
|
||||
## 기존 구조 우선
|
||||
- 새 파일 생성보다 기존 파일 수정을 우선한다.
|
||||
- 기존 패턴과 컨벤션을 따른다.
|
||||
|
||||
## 코드 변경 원칙
|
||||
- 코드 변경 전 관련 domain rule을 확인한다.
|
||||
- 요청 범위를 넘는 변경을 금지한다.
|
||||
- 불확실하면 단정하지 말고 후보로 제시한다.
|
||||
|
||||
## 작업 전 확인
|
||||
- `agent-ops/skills/router.md` 를 먼저 참조하여 필요한 skill 또는 domain rule만 로드한다.
|
||||
- 관련 rule / skill 만 최소로 로드한다.
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
---
|
||||
name: <skill-name>
|
||||
description: <skill 한 줄 설명>
|
||||
version: 1.0.0
|
||||
---
|
||||
|
||||
# <Skill Name>
|
||||
|
||||
## 목적
|
||||
<이 skill이 해결하는 문제>
|
||||
|
||||
## 언제 호출할지
|
||||
- <이 skill을 사용해야 하는 상황>
|
||||
- <이 skill을 사용해야 하는 상황>
|
||||
|
||||
## 입력
|
||||
- `<param>`: <설명> (필수/선택)
|
||||
|
||||
## 먼저 확인할 것
|
||||
- [ ] <사전 확인 항목>
|
||||
- [ ] <사전 확인 항목>
|
||||
|
||||
## 실행 절차
|
||||
1. <단계>
|
||||
2. <단계>
|
||||
3. <단계>
|
||||
|
||||
## 출력 형식
|
||||
```
|
||||
<출력 예시>
|
||||
```
|
||||
|
||||
## 금지 사항
|
||||
- <하면 안 되는 것>
|
||||
- <하면 안 되는 것>
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
---
|
||||
name: code-change
|
||||
description: 코드 변경 절차 - 읽기 → 도메인 rule 확인 → 변경 → 검증
|
||||
version: 1.0.0
|
||||
---
|
||||
|
||||
# Code Change
|
||||
|
||||
## 목적
|
||||
코드 변경 시 도메인 규칙을 준수하고 기존 패턴을 따른다.
|
||||
|
||||
## 언제 호출할지
|
||||
- 새 기능을 추가할 때
|
||||
- 버그를 수정할 때
|
||||
- 기존 코드를 수정할 때
|
||||
|
||||
## 입력
|
||||
- `변경 대상`: 파일 경로 또는 기능 설명 (필수)
|
||||
- `변경 내용`: 무엇을 어떻게 변경할지 (필수)
|
||||
|
||||
## 먼저 확인할 것
|
||||
- [ ] `find-domain` skill로 대상 도메인 판별
|
||||
- [ ] 해당 domain rule 로드 (`agent-ops/rules/domain/<domain>/rules.md`)
|
||||
- [ ] 변경할 파일을 Read tool로 먼저 읽기
|
||||
- [ ] 기존 패턴과 컨벤션 파악
|
||||
|
||||
## 실행 절차
|
||||
1. 대상 파일 읽기 (Read tool 사용)
|
||||
2. 도메인 rule의 "유지할 패턴"과 "금지 사항" 확인
|
||||
3. 변경 범위를 요청 범위로 제한 (추가 리팩터링 금지)
|
||||
4. 변경 적용 (Edit tool 우선, 전면 재작성은 Write tool)
|
||||
5. TypeScript 타입 확인 (`npm run tsc`)
|
||||
6. 린팅 확인 (`npm run fix`)
|
||||
|
||||
## 출력 형식
|
||||
변경 후:
|
||||
```
|
||||
변경 파일: <path>
|
||||
변경 내용: <요약>
|
||||
확인 필요: <있으면 명시>
|
||||
```
|
||||
|
||||
## 금지 사항
|
||||
- 파일을 읽기 전에 변경하지 않는다
|
||||
- 요청 범위를 넘는 리팩터링, 주석 추가, 타입 어노테이션 추가
|
||||
- 도메인 rule의 금지 사항 위반
|
||||
- `console.log()` / `console.error()` 직접 사용
|
||||
- 다른 언어 파일 수정 (`en.json` 외 i18n 파일 수정 금지)
|
||||
|
|
@ -1,96 +0,0 @@
|
|||
---
|
||||
name: create-domain-rule
|
||||
description: 새로운 도메인 rules.md 파일을 생성하기 위한 범용 스킬
|
||||
version: 1.0.0
|
||||
---
|
||||
|
||||
# Create Domain Rule
|
||||
|
||||
## 목적
|
||||
|
||||
`agent-ops/rules/domain/<domain-name>/rules.md` 파일을 올바른 형식으로 생성한다.
|
||||
실제 프로젝트 폴더 구조를 분석하여 경로와 구성 요소를 자동으로 채운다.
|
||||
생성 후 router.md 의 코드 변경 라우팅 테이블에 항목을 추가한다.
|
||||
|
||||
## 언제 호출할지
|
||||
|
||||
- 새로운 도메인 영역의 코드를 처음 변경하기 전에
|
||||
- 기존 domain rule이 없는 경로에 대해 규칙이 필요할 때
|
||||
- 사용자가 특정 도메인의 rule 파일을 만들어 달라고 요청할 때
|
||||
- agent-ops 초기 scaffold 이후 도메인을 추가 확장할 때
|
||||
|
||||
## 입력
|
||||
|
||||
- `domain-name`: 생성할 도메인 이름, kebab-case (필수)
|
||||
- `domain-type`: `core` / `supporting` / `generic` 중 하나 (필수)
|
||||
- `path-hints`: 이 도메인에 해당하는 경로 힌트 목록 (선택, 없으면 자동 탐색)
|
||||
|
||||
## 먼저 확인할 것
|
||||
|
||||
- [ ] `agent-ops/rules/domain/<domain-name>/rules.md` 가 이미 존재하는지 확인
|
||||
- [ ] `agent-ops/rules/_templates/domain-rule-template.md` 를 읽어 최신 템플릿 형식 파악
|
||||
- [ ] `agent-ops/rules/domain/` 하위 기존 domain rule 목록을 확인하여 중복·유사 도메인 여부 판단
|
||||
|
||||
## 실행 절차
|
||||
|
||||
1. **중복 확인**
|
||||
- `agent-ops/rules/domain/<domain-name>/` 폴더 존재 여부 확인
|
||||
- 책임이 겹치는 기존 domain rule 이 있으면 사용자에게 알리고 중단한다
|
||||
|
||||
2. **경로 탐색**
|
||||
- `path-hints` 가 제공된 경우: 해당 경로를 기준으로 폴더 구조 확인
|
||||
- `path-hints` 가 없는 경우: `domain-name` 과 연관된 폴더명·파일명으로 프로젝트를 탐색
|
||||
- 실제로 존재하는 경로만 포함 경로에 기재한다
|
||||
- 존재하지 않는 경로는 기재하지 않는다
|
||||
|
||||
3. **도메인 분석**
|
||||
- 탐색된 경로의 주요 파일·모듈을 읽어 아래를 도출한다
|
||||
- 도메인의 목적 / 책임 한 줄 요약
|
||||
- 포함 경로 목록
|
||||
- 제외 경로 (인접 도메인과 겹칠 수 있는 경로)
|
||||
- 주요 구성 요소 (파일, 모듈, 클래스, 함수 등)
|
||||
- 유지할 패턴 (네이밍 규칙, 아키텍처 패턴 등)
|
||||
- 다른 도메인과의 경계
|
||||
- 금지 사항
|
||||
|
||||
4. **rules.md 생성**
|
||||
- 경로: `agent-ops/rules/domain/<domain-name>/rules.md`
|
||||
- `domain-rule-template.md` 형식을 따른다
|
||||
- 실제 코드에서 확인된 내용만 기재한다
|
||||
- 불확실한 항목은 `<!-- TODO: 확인 필요 -->` 주석으로 남긴다
|
||||
|
||||
5. **router.md 업데이트**
|
||||
- `agent-ops/skills/router.md` 를 읽는다
|
||||
- 코드 변경 섹션의 경로-도메인 라우팅 테이블에 아래 형식으로 추가한다
|
||||
```
|
||||
| `<path-pattern>` | `agent-ops/rules/domain/<domain-name>/rules.md` |
|
||||
```
|
||||
- 경로 패턴은 실제 포함 경로를 기준으로 작성한다
|
||||
- 기존 테이블 항목을 삭제하거나 재정렬하지 않는다
|
||||
|
||||
6. **결과 보고**
|
||||
- 생성한 파일 경로
|
||||
- router.md 에 추가한 항목
|
||||
- 불확실하여 TODO로 남긴 항목 (해당 시)
|
||||
|
||||
## 출력 형식
|
||||
|
||||
```
|
||||
## 생성 완료
|
||||
|
||||
- Domain Rule 경로: agent-ops/rules/domain/<domain-name>/rules.md
|
||||
- 도메인 유형: <core | supporting | generic>
|
||||
- router.md 추가 경로 패턴: <path-pattern>
|
||||
|
||||
## TODO 항목 (확인 필요)
|
||||
- <불확실하여 직접 확인이 필요한 항목> (해당 시)
|
||||
```
|
||||
|
||||
## 금지 사항
|
||||
|
||||
- 이미 존재하는 rules.md 를 덮어쓰지 않는다
|
||||
- 실제 존재하지 않는 경로를 포함 경로에 넣지 않는다
|
||||
- 추측으로 패턴이나 금지 사항을 채우지 않는다 — 확인된 내용만 기재한다
|
||||
- router.md 의 기존 항목을 삭제하거나 재정렬하지 않는다
|
||||
- 하나의 domain rule 에 여러 독립 도메인의 책임을 묶지 않는다
|
||||
- 코드 변경을 수행하지 않는다 — 이 skill 은 rule 파일 생성만 담당한다
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
---
|
||||
name: create-skill
|
||||
description: 새로운 SKILL.md 파일을 생성하기 위한 범용 스킬
|
||||
version: 1.0.0
|
||||
---
|
||||
|
||||
# Create Skill
|
||||
|
||||
## 목적
|
||||
|
||||
`agent-ops/skills/<skill-name>/SKILL.md` 파일을 올바른 형식으로 생성한다.
|
||||
기존 skill-template.md 를 기반으로, 요청 목적에 맞는 내용을 채워 넣는다.
|
||||
생성 후 router.md 에 라우팅 항목을 추가한다.
|
||||
|
||||
## 언제 호출할지
|
||||
|
||||
- 새로운 반복 작업 패턴이 생겨 skill로 정의해야 할 때
|
||||
- 기존 skill이 없는 작업 유형을 처음 수행하기 전에
|
||||
- 사용자가 특정 작업을 skill로 만들어 달라고 요청할 때
|
||||
|
||||
## 입력
|
||||
|
||||
- `skill-name`: 생성할 skill 이름, kebab-case (필수)
|
||||
- `purpose`: 이 skill이 해결하는 문제 한 줄 요약 (필수)
|
||||
- `trigger-cases`: 이 skill을 호출해야 하는 상황 목록 (선택)
|
||||
|
||||
## 먼저 확인할 것
|
||||
|
||||
- [ ] `agent-ops/skills/` 하위에 동일 이름의 디렉터리가 이미 존재하는지 확인
|
||||
- [ ] `agent-ops/skills/router.md` 에 이미 유사한 라우팅 항목이 있는지 확인
|
||||
- [ ] `agent-ops/skills/_templates/skill-template.md` 를 읽어 최신 템플릿 형식 파악
|
||||
|
||||
## 실행 절차
|
||||
|
||||
1. **중복 확인**
|
||||
- `agent-ops/skills/<skill-name>/` 폴더 존재 여부 확인
|
||||
- 기능이 겹치는 기존 skill이 있으면 사용자에게 알리고 중단한다
|
||||
|
||||
2. **목적 분석**
|
||||
- `purpose` 와 `trigger-cases` 를 바탕으로 아래 항목을 도출한다
|
||||
- 언제 호출할지 (2~4개)
|
||||
- 필요한 입력 파라미터
|
||||
- 사전 확인 항목
|
||||
- 실행 절차 (3~7단계)
|
||||
- 출력 형식
|
||||
- 금지 사항
|
||||
|
||||
3. **SKILL.md 생성**
|
||||
- 경로: `agent-ops/skills/<skill-name>/SKILL.md`
|
||||
- `skill-template.md` 형식을 따른다
|
||||
- 프로젝트 특화 내용보다 범용 절차를 우선한다
|
||||
- 절차는 구체적이되 지나치게 세부 구현을 기술하지 않는다
|
||||
|
||||
4. **router.md 업데이트**
|
||||
- `agent-ops/skills/router.md` 를 읽는다
|
||||
- 이 skill이 속할 라우팅 축(구조 분석/코드 변경/흐름 추적 등)을 판단한다
|
||||
- 해당 축에 `→ agent-ops/skills/<skill-name>/SKILL.md` 항목을 추가한다
|
||||
- 기존 라우팅 구조를 깨지 않는다
|
||||
|
||||
5. **결과 보고**
|
||||
- 생성한 파일 경로
|
||||
- router.md 에 추가한 항목
|
||||
- 이 skill이 다루지 않는 범위(필요 시)
|
||||
|
||||
## 출력 형식
|
||||
|
||||
```
|
||||
## 생성 완료
|
||||
|
||||
- SKILL 경로: agent-ops/skills/<skill-name>/SKILL.md
|
||||
- router.md 추가 항목: <라우팅 축> → <skill-name>
|
||||
|
||||
## 주의사항 (해당 시)
|
||||
- <이 skill이 다루지 않는 범위 또는 주의할 점>
|
||||
```
|
||||
|
||||
## 금지 사항
|
||||
|
||||
- 이미 존재하는 skill 을 덮어쓰지 않는다
|
||||
- 프로젝트 특화 경로(예: `app/screens/`)를 skill 본문에 하드코딩하지 않는다
|
||||
- skill 생성과 무관한 코드 파일을 수정하지 않는다
|
||||
- router.md 의 기존 항목을 삭제하거나 재정렬하지 않는다
|
||||
- 하나의 skill 에 여러 독립적인 책임을 묶지 않는다
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
---
|
||||
name: find-domain
|
||||
description: 파일 경로 또는 기능으로 해당 도메인을 판별한다
|
||||
version: 1.0.0
|
||||
---
|
||||
|
||||
# Find Domain
|
||||
|
||||
## 목적
|
||||
변경하거나 분석할 코드가 어느 도메인에 속하는지 판별한다.
|
||||
|
||||
## 언제 호출할지
|
||||
- 어떤 파일을 수정해야 하는지 불분명할 때
|
||||
- 기능이 어느 도메인에 속하는지 모를 때
|
||||
- 여러 도메인에 걸친 변경이 필요한지 파악할 때
|
||||
|
||||
## 입력
|
||||
- `경로 또는 기능 설명`: 파일 경로, 컴포넌트 이름, 또는 기능 설명 (필수)
|
||||
|
||||
## 먼저 확인할 것
|
||||
- [ ] `agent-ops/skills/router.md`의 경로 패턴 테이블 확인
|
||||
- [ ] CLAUDE.md의 TypeScript Path Aliases 섹션 확인
|
||||
|
||||
## 실행 절차
|
||||
1. 입력이 파일 경로면 → router.md 테이블과 매칭
|
||||
2. 입력이 기능 설명이면 → 관련 파일을 Glob/Grep으로 탐색
|
||||
3. 도메인 후보를 식별하고 해당 domain rule 로드
|
||||
4. 여러 도메인에 걸친 경우 → 주 도메인 + 영향 도메인 명시
|
||||
|
||||
## 출력 형식
|
||||
```
|
||||
도메인: <domain-name>
|
||||
근거: <판별 근거>
|
||||
관련 rule: agent-ops/rules/domain/<domain>/rules.md
|
||||
영향 도메인: <있으면 명시>
|
||||
```
|
||||
|
||||
## 금지 사항
|
||||
- 도메인을 단정하지 않고 불확실하면 후보를 제시한다
|
||||
- 관련 없는 domain rule까지 로드하지 않는다
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
---
|
||||
name: router
|
||||
description: 작업 유형별 skill / domain rule 라우팅
|
||||
version: 1.0.0
|
||||
---
|
||||
|
||||
# Skill Router
|
||||
|
||||
작업 유형에 따라 필요한 skill 또는 domain rule만 로드한다.
|
||||
|
||||
## 라우팅 축
|
||||
|
||||
### 구조 분석 / 스캐폴드
|
||||
→ `agent-ops/skills/find-domain/SKILL.md`
|
||||
→ `agent-ops/rules/global.md`
|
||||
|
||||
### 새 Skill 생성
|
||||
→ `agent-ops/skills/create-skill/SKILL.md`
|
||||
|
||||
### 새 Domain Rule 생성
|
||||
→ `agent-ops/skills/create-domain-rule/SKILL.md`
|
||||
|
||||
### 도메인 판별
|
||||
→ `agent-ops/skills/find-domain/SKILL.md`
|
||||
|
||||
### 코드 변경
|
||||
→ `agent-ops/skills/code-change/SKILL.md`
|
||||
→ 변경 대상 경로에 해당하는 domain rule 로드
|
||||
|
||||
| 경로 패턴 | Domain Rule |
|
||||
|---|---|
|
||||
| `app/products/agents/` | `agent-ops/rules/domain/products-agents/rules.md` |
|
||||
| `app/products/calls/` | `agent-ops/rules/domain/products-calls/rules.md` |
|
||||
| `app/database/`, `app/queries/` | `agent-ops/rules/domain/database/rules.md` |
|
||||
| `app/components/`, `app/screens/`, `app/hooks/` | `agent-ops/rules/domain/ui/rules.md` |
|
||||
| `app/actions/`, 채널/포스트/스레드 관련 | `agent-ops/rules/domain/messaging/rules.md` |
|
||||
|
||||
### 흐름 추적
|
||||
→ 흐름에 관련된 domain rule 로드
|
||||
→ `agent-ops/skills/find-domain/SKILL.md`로 경로 판별 후 진행
|
||||
|
||||
### 문서 / 릴리즈 정리
|
||||
→ `agent-ops/rules/global.md`
|
||||
→ `docs/` 폴더 직접 확인
|
||||
|
||||
## 원칙
|
||||
- 필요한 rule만 로드한다. 전체를 로드하지 않는다.
|
||||
- 불확실한 도메인은 `find-domain` skill로 먼저 판별한다.
|
||||
|
|
@ -70,7 +70,6 @@ if (System.getenv("SENTRY_ENABLED") == "true") {
|
|||
* and want to have separate APKs to upload to the Play Store
|
||||
*/
|
||||
def enableSeparateBuildPerCPUArchitecture = project.hasProperty('separateApk') ? project.property('separateApk').toBoolean() : false
|
||||
def enableUniversalBuild = project.hasProperty('universalApk') ? project.property('universalApk').toBoolean() : false
|
||||
|
||||
/**
|
||||
* Set this to true to Run Proguard on Release builds to minify the Java bytecode.
|
||||
|
|
@ -104,7 +103,7 @@ android {
|
|||
ndkVersion rootProject.ext.ndkVersion
|
||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
namespace "com.tokilabs.mattermost"
|
||||
namespace "com.mattermost.rnbeta"
|
||||
|
||||
lintOptions {
|
||||
checkReleaseBuilds false
|
||||
|
|
@ -112,11 +111,11 @@ android {
|
|||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.tokilabs.mattermost"
|
||||
applicationId "com.mattermost.rnbeta"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 730
|
||||
versionName "2.38.0"
|
||||
versionCode 613
|
||||
versionName "2.26.0"
|
||||
testBuildType System.getProperty('testBuildType', 'debug')
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
}
|
||||
|
|
@ -141,7 +140,7 @@ android {
|
|||
abi {
|
||||
reset()
|
||||
enable enableSeparateBuildPerCPUArchitecture
|
||||
universalApk enableUniversalBuild // If true, also generate a universal APK
|
||||
universalApk enableSeparateBuildPerCPUArchitecture // If true, also generate a universal APK
|
||||
include (*reactNativeArchitectures())
|
||||
}
|
||||
}
|
||||
|
|
@ -203,23 +202,16 @@ dependencies {
|
|||
implementation jscFlavor
|
||||
}
|
||||
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3'
|
||||
implementation 'androidx.appcompat:appcompat:1.7.0'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'com.google.android.material:material:1.12.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
implementation "com.google.firebase:firebase-messaging:$firebaseVersion"
|
||||
androidTestImplementation('com.wix:detox:+')
|
||||
androidTestImplementation 'androidx.test:core:1.6.1'
|
||||
androidTestImplementation 'androidx.test:runner:1.6.2'
|
||||
androidTestImplementation 'androidx.test:core:1.6.0'
|
||||
androidTestImplementation 'androidx.test:runner:1.5.2'
|
||||
androidTestImplementation 'com.wix:detox:20.26.2'
|
||||
|
||||
// For animated GIF support
|
||||
implementation 'com.facebook.fresco:animated-gif:3.6.0'
|
||||
|
||||
// For WebP support, including animated WebP
|
||||
implementation 'com.facebook.fresco:animated-webp:3.6.0'
|
||||
implementation 'com.facebook.fresco:webpsupport:3.6.0'
|
||||
|
||||
implementation project(':reactnativenotifications')
|
||||
implementation project(':watermelondb-jsi')
|
||||
|
||||
|
|
@ -232,16 +224,16 @@ dependencies {
|
|||
|
||||
configurations.all {
|
||||
resolutionStrategy {
|
||||
force 'androidx.test:core:1.6.1'
|
||||
force 'androidx.test:core:1.6.0'
|
||||
eachDependency { DependencyResolveDetails details ->
|
||||
if (details.requested.name == 'play-services-base') {
|
||||
details.useTarget group: details.requested.group, name: details.requested.name, version: '18.5.0'
|
||||
}
|
||||
if (details.requested.name == 'play-services-tasks') {
|
||||
details.useTarget group: details.requested.group, name: details.requested.name, version: '18.2.0'
|
||||
}
|
||||
if (details.requested.name == 'play-services-tasks') {
|
||||
details.useTarget group: details.requested.group, name: details.requested.name, version: '18.0.2'
|
||||
}
|
||||
if (details.requested.name == 'play-services-basement') {
|
||||
details.useTarget group: details.requested.group, name: details.requested.name, version: '18.5.0'
|
||||
details.useTarget group: details.requested.group, name: details.requested.name, version: '18.2.0'
|
||||
}
|
||||
if (details.requested.name == 'okhttp') {
|
||||
details.useTarget group: details.requested.group, name: details.requested.name, version: '4.12.0'
|
||||
|
|
|
|||
|
|
@ -1,29 +1,104 @@
|
|||
{
|
||||
"project_info": {
|
||||
"project_number": "1047648748539",
|
||||
"project_id": "mattermost-6ac08",
|
||||
"storage_bucket": "mattermost-6ac08.firebasestorage.app"
|
||||
"project_number": "184930218130",
|
||||
"firebase_url": "https://api-7231322553409637977-752355.firebaseio.com",
|
||||
"project_id": "api-7231322553409637977-752355",
|
||||
"storage_bucket": "api-7231322553409637977-752355.appspot.com"
|
||||
},
|
||||
"client": [
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:1047648748539:android:818bf70bfbb3f9d070415e",
|
||||
"mobilesdk_app_id": "1:184930218130:android:a0e553d1a1f043b5",
|
||||
"android_client_info": {
|
||||
"package_name": "com.tokilabs.mattermost"
|
||||
"package_name": "com.mattermost.react.native"
|
||||
}
|
||||
},
|
||||
"oauth_client": [],
|
||||
"oauth_client": [
|
||||
{
|
||||
"client_id": "184930218130-8nahrspll1opff0uogtkh2qsv8coqngo.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
}
|
||||
],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyAW6j_oPl9MVcm93qS3JgaEPD5ywp-TzZ0"
|
||||
"current_key": "AIzaSyCkZkU2ECVg-mmdCG5OoHHQXtKiENuvWPE"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"analytics_service": {
|
||||
"status": 2
|
||||
},
|
||||
"appinvite_service": {
|
||||
"status": 1,
|
||||
"other_platform_oauth_client": []
|
||||
},
|
||||
"ads_service": {
|
||||
"status": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:184930218130:android:c7debfa7ea3f75a7",
|
||||
"android_client_info": {
|
||||
"package_name": "com.mattermost.rnbeta"
|
||||
}
|
||||
},
|
||||
"oauth_client": [
|
||||
{
|
||||
"client_id": "184930218130-8nahrspll1opff0uogtkh2qsv8coqngo.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
}
|
||||
],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyCkZkU2ECVg-mmdCG5OoHHQXtKiENuvWPE"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"analytics_service": {
|
||||
"status": 2
|
||||
},
|
||||
"appinvite_service": {
|
||||
"status": 1,
|
||||
"other_platform_oauth_client": []
|
||||
},
|
||||
"ads_service": {
|
||||
"status": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:184930218130:android:2db4058a5b5d6506",
|
||||
"android_client_info": {
|
||||
"package_name": "com.mattermost.rn"
|
||||
}
|
||||
},
|
||||
"oauth_client": [
|
||||
{
|
||||
"client_id": "184930218130-8nahrspll1opff0uogtkh2qsv8coqngo.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
}
|
||||
],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyCkZkU2ECVg-mmdCG5OoHHQXtKiENuvWPE"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"analytics_service": {
|
||||
"status": 2
|
||||
},
|
||||
"appinvite_service": {
|
||||
"status": 1,
|
||||
"other_platform_oauth_client": []
|
||||
},
|
||||
"ads_service": {
|
||||
"status": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"configuration_version": "1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tokilabs.mattermost;
|
||||
package com.mattermost.rnbeta;
|
||||
|
||||
import com.wix.detox.Detox;
|
||||
import com.wix.detox.config.DetoxConfig;
|
||||
|
|
@ -77,7 +77,7 @@
|
|||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="mmauth_tokilabs" />
|
||||
<data android:scheme="mmauthbeta" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<service android:name=".NotificationDismissService"
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ import androidx.core.app.Person;
|
|||
import androidx.core.app.RemoteInput;
|
||||
import androidx.core.graphics.drawable.IconCompat;
|
||||
|
||||
import com.tokilabs.mattermost.*;
|
||||
import com.mattermost.rnbeta.*;
|
||||
import com.mattermost.rnutils.helpers.NotificationHelper;
|
||||
import com.nozbe.watermelondb.WMDatabase;
|
||||
import com.mattermost.turbolog.TurboLog;
|
||||
|
|
|
|||
|
|
@ -135,22 +135,21 @@ class PushNotificationDataRunnable {
|
|||
threadsArray.add(thread)
|
||||
}
|
||||
for(i in 0 until it.size()) {
|
||||
it.getMap(i)?.let { thread ->
|
||||
val threadId = thread.getString("id")
|
||||
if (threadId != null) {
|
||||
if (threadIds.contains(threadId)) {
|
||||
// replace the values for participants and is_following
|
||||
val index = threadsArray.indexOfFirst { el -> el.getString("id") == threadId }
|
||||
val prev = threadsArray[index]
|
||||
val merge = Arguments.createMap()
|
||||
merge.merge(prev)
|
||||
merge.putBoolean("is_following", thread.getBoolean("is_following"))
|
||||
merge.putArray("participants", thread.getArray("participants"))
|
||||
threadsArray[index] = merge
|
||||
} else {
|
||||
threadsArray.add(thread)
|
||||
threadIds.add(threadId)
|
||||
}
|
||||
val thread = it.getMap(i)
|
||||
val threadId = thread.getString("id")
|
||||
if (threadId != null) {
|
||||
if (threadIds.contains(threadId)) {
|
||||
// replace the values for participants and is_following
|
||||
val index = threadsArray.indexOfFirst { el -> el.getString("id") == threadId }
|
||||
val prev = threadsArray[index]
|
||||
val merge = Arguments.createMap()
|
||||
merge.merge(prev)
|
||||
merge.putBoolean("is_following", thread.getBoolean("is_following"))
|
||||
merge.putArray("participants", thread.getArray("participants"))
|
||||
threadsArray[index] = merge
|
||||
} else {
|
||||
threadsArray.add(thread)
|
||||
threadIds.add(threadId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,10 @@ import java.util.UUID
|
|||
|
||||
class RandomId {
|
||||
companion object {
|
||||
private const val alphabet = "0123456789abcdefghijklmnopqrstuvwxyz"
|
||||
private const val alphabetLength = alphabet.length
|
||||
private const val idLength = 16
|
||||
|
||||
fun generate(): String {
|
||||
return UUID.randomUUID().toString()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,14 +54,12 @@ fun insertCategoriesWithChannels(db: WMDatabase, orderCategories: ReadableMap) {
|
|||
val categories = orderCategories.getArray("categories") ?: return
|
||||
for (i in 0 until categories.size()) {
|
||||
val category = categories.getMap(i)
|
||||
category?.let { cat ->
|
||||
val id = cat.getString("id")
|
||||
val teamId = cat.getString("team_id")
|
||||
val channelIds = cat.getArray("channel_ids")
|
||||
insertCategory(db, cat)
|
||||
if (id != null && teamId != null) {
|
||||
channelIds?.let { insertCategoryChannels(db, id, teamId, it) }
|
||||
}
|
||||
val id = category.getString("id")
|
||||
val teamId = category.getString("team_id")
|
||||
val channelIds = category.getArray("channel_ids")
|
||||
insertCategory(db, category)
|
||||
if (id != null && teamId != null) {
|
||||
channelIds?.let { insertCategoryChannels(db, id, teamId, it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -69,22 +67,19 @@ fun insertCategoriesWithChannels(db: WMDatabase, orderCategories: ReadableMap) {
|
|||
fun insertChannelToDefaultCategory(db: WMDatabase, categoryChannels: ReadableArray) {
|
||||
try {
|
||||
for (i in 0 until categoryChannels.size()) {
|
||||
categoryChannels.getMap(i)?.let { cc ->
|
||||
val id = cc.getString("id")
|
||||
val categoryId = cc.getString("category_id")
|
||||
val channelId = cc.getString("channel_id")
|
||||
if (id != null && categoryId != null && channelId != null) {
|
||||
val count = countByColumn(db, "CategoryChannel", "category_id", categoryId)
|
||||
db.execute(
|
||||
"""
|
||||
INSERT INTO CategoryChannel
|
||||
(id, category_id, channel_id, sort_order, _changed, _status)
|
||||
VALUES (?, ?, ?, ?, '', 'created')
|
||||
""".trimIndent(),
|
||||
arrayOf(id, categoryId, channelId, if (count > 0) count + 1 else count)
|
||||
)
|
||||
}
|
||||
}
|
||||
val cc = categoryChannels.getMap(i)
|
||||
val id = cc.getString("id")
|
||||
val categoryId = cc.getString("category_id")
|
||||
val channelId = cc.getString("channel_id")
|
||||
val count = countByColumn(db, "CategoryChannel", "category_id", categoryId)
|
||||
db.execute(
|
||||
"""
|
||||
INSERT INTO CategoryChannel
|
||||
(id, category_id, channel_id, sort_order, _changed, _status)
|
||||
VALUES (?, ?, ?, ?, '', 'created')
|
||||
""".trimIndent(),
|
||||
arrayOf(id, categoryId, channelId, if (count > 0) count + 1 else count)
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ internal fun DatabaseHelper.saveToDatabase(db: WMDatabase, data: ReadableMap, te
|
|||
data.getArray("threads")?.let {
|
||||
val threadsArray = ArrayList<ReadableMap>()
|
||||
for (i in 0 until it.size()) {
|
||||
it.getMap(i)?.let { map -> threadsArray.add(map) }
|
||||
threadsArray.add(it.getMap(i))
|
||||
}
|
||||
handleThreads(db, threadsArray, teamId)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,10 +10,8 @@ import com.nozbe.watermelondb.WMDatabase
|
|||
internal fun findPostInChannel(chunks: ReadableArray, earliest: Double, latest: Double): ReadableMap? {
|
||||
for (i in 0 until chunks.size()) {
|
||||
val chunk = chunks.getMap(i)
|
||||
chunk?.let {
|
||||
if (earliest >= it.getDouble("earliest") || latest <= it.getDouble("latest")) {
|
||||
return it
|
||||
}
|
||||
if (earliest >= chunk.getDouble("earliest") || latest <= chunk.getDouble("latest")) {
|
||||
return chunk
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -47,12 +45,10 @@ internal fun mergePostsInChannel(db: WMDatabase, existingChunks: ReadableArray,
|
|||
for (i in 0 until existingChunks.size()) {
|
||||
try {
|
||||
val chunk = existingChunks.getMap(i)
|
||||
chunk?.let {
|
||||
if (newChunk.getDouble("earliest") <= it.getDouble("earliest") &&
|
||||
newChunk.getDouble("latest") >= it.getDouble("latest")) {
|
||||
db.execute("DELETE FROM PostsInChannel WHERE id = ?", arrayOf(it.getString("id")))
|
||||
return
|
||||
}
|
||||
if (newChunk.getDouble("earliest") <= chunk.getDouble("earliest") &&
|
||||
newChunk.getDouble("latest") >= chunk.getDouble("latest")) {
|
||||
db.execute("DELETE FROM PostsInChannel WHERE id = ?", arrayOf(chunk.getString("id")))
|
||||
break
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
|
|
|
|||
|
|
@ -83,17 +83,15 @@ internal fun insertThreadParticipants(db: WMDatabase, threadId: String, particip
|
|||
for (i in 0 until participants.size()) {
|
||||
try {
|
||||
val participant = participants.getMap(i)
|
||||
participant?.let {
|
||||
val id = RandomId.generate()
|
||||
db.execute(
|
||||
"""
|
||||
INSERT INTO ThreadParticipant
|
||||
(id, thread_id, user_id, _changed, _status)
|
||||
VALUES (?, ?, ?, '', 'created')
|
||||
""".trimIndent(),
|
||||
arrayOf(id, threadId, it.getString("id"))
|
||||
)
|
||||
}
|
||||
val id = RandomId.generate()
|
||||
db.execute(
|
||||
"""
|
||||
INSERT INTO ThreadParticipant
|
||||
(id, thread_id, user_id, _changed, _status)
|
||||
VALUES (?, ?, ?, '', 'created')
|
||||
""".trimIndent(),
|
||||
arrayOf(id, threadId, participant.getString("id"))
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,40 +48,38 @@ fun getCurrentUserLocale(db: WMDatabase): String {
|
|||
fun handleUsers(db: WMDatabase, users: ReadableArray) {
|
||||
for (i in 0 until users.size()) {
|
||||
val user = users.getMap(i)
|
||||
user?.let { u ->
|
||||
val roles = u.getString("roles") ?: ""
|
||||
val isBot = try {
|
||||
u.getBoolean("is_bot")
|
||||
} catch (e: NoSuchKeyException) {
|
||||
false
|
||||
}
|
||||
val roles = user.getString("roles") ?: ""
|
||||
val isBot = try {
|
||||
user.getBoolean("is_bot")
|
||||
} catch (e: NoSuchKeyException) {
|
||||
false
|
||||
}
|
||||
|
||||
val lastPictureUpdate = try { u.getDouble("last_picture_update") } catch (e: NoSuchKeyException) { 0 }
|
||||
val lastPictureUpdate = try { user.getDouble("last_picture_update") } catch (e: NoSuchKeyException) { 0 }
|
||||
|
||||
try {
|
||||
db.execute(
|
||||
"""
|
||||
INSERT INTO User (id, auth_service, update_at, delete_at, email, first_name, is_bot, is_guest,
|
||||
last_name, last_picture_update, locale, nickname, position, roles, status, username, notify_props,
|
||||
props, timezone, _changed, _status)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, '', 'created')
|
||||
""".trimIndent(),
|
||||
arrayOf(
|
||||
u.getString("id"),
|
||||
u.getString("auth_service"), u.getDouble("update_at"), u.getDouble("delete_at"),
|
||||
u.getString("email"), u.getString("first_name"), isBot,
|
||||
roles.contains("system_guest"), u.getString("last_name"), lastPictureUpdate,
|
||||
u.getString("locale"), u.getString("nickname"), u.getString("position"),
|
||||
roles, "", u.getString("username"), "{}",
|
||||
ReadableMapUtils.toJSONObject(u.getMap("props")
|
||||
?: Arguments.createMap()).toString(),
|
||||
ReadableMapUtils.toJSONObject(u.getMap("timezone")
|
||||
?: Arguments.createMap()).toString(),
|
||||
)
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
try {
|
||||
db.execute(
|
||||
"""
|
||||
INSERT INTO User (id, auth_service, update_at, delete_at, email, first_name, is_bot, is_guest,
|
||||
last_name, last_picture_update, locale, nickname, position, roles, status, username, notify_props,
|
||||
props, timezone, _changed, _status)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, '', 'created')
|
||||
""".trimIndent(),
|
||||
arrayOf(
|
||||
user.getString("id"),
|
||||
user.getString("auth_service"), user.getDouble("update_at"), user.getDouble("delete_at"),
|
||||
user.getString("email"), user.getString("first_name"), isBot,
|
||||
roles.contains("system_guest"), user.getString("last_name"), lastPictureUpdate,
|
||||
user.getString("locale"), user.getString("nickname"), user.getString("position"),
|
||||
roles, "", user.getString("username"), "{}",
|
||||
ReadableMapUtils.toJSONObject(user.getMap("props")
|
||||
?: Arguments.createMap()).toString(),
|
||||
ReadableMapUtils.toJSONObject(user.getMap("timezone")
|
||||
?: Arguments.createMap()).toString(),
|
||||
)
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,13 +26,11 @@ suspend fun PushNotificationDataRunnable.Companion.fetchMyChannel(db: WMDatabase
|
|||
"D" -> {
|
||||
profilesArray = fetchProfileInChannel(db, serverUrl, channelId)
|
||||
if ((profilesArray?.size() ?: 0) > 0) {
|
||||
profilesArray?.getMap(0)?.let { profile ->
|
||||
val displayName = displayUsername(profile, displayNameSetting)
|
||||
val data = Arguments.createMap()
|
||||
data.merge(channelData)
|
||||
data.putString("display_name", displayName)
|
||||
channelData = data
|
||||
}
|
||||
val displayName = displayUsername(profilesArray!!.getMap(0), displayNameSetting)
|
||||
val data = Arguments.createMap()
|
||||
data.merge(channelData)
|
||||
data.putString("display_name", displayName)
|
||||
channelData = data
|
||||
}
|
||||
}
|
||||
"G" -> {
|
||||
|
|
@ -119,10 +117,8 @@ private suspend fun PushNotificationDataRunnable.Companion.fetchProfileInChannel
|
|||
if (profilesArray != null) {
|
||||
for (i in 0 until profilesArray.size()) {
|
||||
val profile = profilesArray.getMap(i)
|
||||
profile?.let {
|
||||
if (it.getString("id") != currentUserId) {
|
||||
result.pushMap(it)
|
||||
}
|
||||
if (profile.getString("id") != currentUserId) {
|
||||
result.pushMap(profile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -156,9 +152,7 @@ private fun PushNotificationDataRunnable.Companion.displayGroupMessageName(profi
|
|||
val names = ArrayList<String>()
|
||||
for (i in 0 until profilesArray.size()) {
|
||||
val profile = profilesArray.getMap(i)
|
||||
profile?.let {
|
||||
names.add(displayUsername(it, displayNameSetting))
|
||||
}
|
||||
names.add(displayUsername(profile, displayNameSetting))
|
||||
}
|
||||
|
||||
return names.sortedWith { s1, s2 ->
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ internal suspend fun PushNotificationDataRunnable.Companion.fetchPosts(
|
|||
val userIdsAlreadyLoaded = mutableListOf<String>()
|
||||
if (loadedProfiles != null) {
|
||||
for (i in 0 until loadedProfiles.size()) {
|
||||
loadedProfiles.getMap(i)?.getString("id")?.let { userIdsAlreadyLoaded.add(it) }
|
||||
loadedProfiles.getMap(i).getString("id")?.let { userIdsAlreadyLoaded.add(it) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -95,12 +95,10 @@ internal suspend fun PushNotificationDataRunnable.Companion.fetchPosts(
|
|||
if (attachments != null) {
|
||||
for (i in 0 until attachments.size()) {
|
||||
val attachment = attachments.getMap(i)
|
||||
attachment?.let {
|
||||
val pretext = it.getString("pretext")
|
||||
val text = it.getString("text")
|
||||
findNeededUsernames(pretext)
|
||||
findNeededUsernames(text)
|
||||
}
|
||||
val pretext = attachment.getString("pretext")
|
||||
val text = attachment.getString("text")
|
||||
findNeededUsernames(pretext)
|
||||
findNeededUsernames(text)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -134,23 +132,22 @@ internal suspend fun PushNotificationDataRunnable.Companion.fetchPosts(
|
|||
participants?.let {
|
||||
for (i in 0 until it.size()) {
|
||||
val participant = it.getMap(i)
|
||||
participant?.let { p ->
|
||||
val participantId = p.getString("id")
|
||||
if (participantId != currentUserId && participantId != null) {
|
||||
if (!threadParticipantUserIds.contains(participantId) && !userIdsAlreadyLoaded.contains(participantId)) {
|
||||
threadParticipantUserIds.add(participantId)
|
||||
}
|
||||
|
||||
if (!threadParticipantUsers.containsKey(participantId)) {
|
||||
threadParticipantUsers[participantId] = p
|
||||
}
|
||||
val participantId = participant.getString("id")
|
||||
if (participantId != currentUserId && participantId != null) {
|
||||
if (!threadParticipantUserIds.contains(participantId) && !userIdsAlreadyLoaded.contains(participantId)) {
|
||||
threadParticipantUserIds.add(participantId)
|
||||
}
|
||||
|
||||
val username = p.getString("username")
|
||||
if (username != null && username != currentUsername && !threadParticipantUsernames.contains(username)) {
|
||||
threadParticipantUsernames.add(username)
|
||||
if (!threadParticipantUsers.containsKey(participantId)) {
|
||||
threadParticipantUsers[participantId] = participant
|
||||
}
|
||||
}
|
||||
|
||||
val username = participant.getString("username")
|
||||
if (username != null && username != currentUsername && !threadParticipantUsernames.contains(username)) {
|
||||
threadParticipantUsernames.add(username)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,6 +56,6 @@ internal suspend fun PushNotificationDataRunnable.Companion.fetchNeededUsers(ser
|
|||
|
||||
internal fun PushNotificationDataRunnable.Companion.addUsersToList(users: ReadableArray, list: ArrayList<Any>) {
|
||||
for (i in 0 until users.size()) {
|
||||
users.getMap(i)?.let { list.add(it) }
|
||||
list.add(users.getMap(i))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tokilabs.mattermost
|
||||
package com.mattermost.rnbeta
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
|
|
@ -105,7 +105,7 @@ class CustomPushNotification(
|
|||
CustomPushNotificationHelper.PUSH_TYPE_MESSAGE, CustomPushNotificationHelper.PUSH_TYPE_SESSION -> {
|
||||
val currentActivityName = mAppLifecycleFacade.runningReactContext?.currentActivity?.componentName?.className ?: ""
|
||||
TurboLog.i("ReactNative", currentActivityName)
|
||||
if (!mAppLifecycleFacade.isAppVisible() || !currentActivityName.contains("MainActivity")) {
|
||||
if (!mAppLifecycleFacade.isAppVisible() || currentActivityName != "MainActivity") {
|
||||
var createSummary = type == CustomPushNotificationHelper.PUSH_TYPE_MESSAGE
|
||||
if (type == CustomPushNotificationHelper.PUSH_TYPE_MESSAGE) {
|
||||
channelId?.let {
|
||||
|
|
@ -1,9 +1,8 @@
|
|||
package com.tokilabs.mattermost
|
||||
package com.mattermost.rnbeta
|
||||
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import android.view.KeyEvent
|
||||
import androidx.core.view.WindowCompat
|
||||
import com.facebook.react.ReactActivityDelegate
|
||||
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint
|
||||
import com.facebook.react.defaults.DefaultReactActivityDelegate
|
||||
|
|
@ -15,7 +14,6 @@ import expo.modules.ReactActivityDelegateWrapper
|
|||
class MainActivity : NavigationActivity() {
|
||||
private var HWKeyboardConnected = false
|
||||
private val foldableObserver = FoldableObserver.getInstance(this)
|
||||
private var lastOrientation: Int = Configuration.ORIENTATION_UNDEFINED
|
||||
|
||||
/**
|
||||
* Returns the name of the main component registered from JavaScript. This is used to schedule
|
||||
|
|
@ -36,9 +34,7 @@ class MainActivity : NavigationActivity() {
|
|||
super.onCreate(null)
|
||||
setContentView(R.layout.launch_screen)
|
||||
setHWKeyboardConnected()
|
||||
lastOrientation = this.resources.configuration.orientation
|
||||
foldableObserver.onCreate()
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
|
|
@ -58,11 +54,6 @@ class MainActivity : NavigationActivity() {
|
|||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
val newOrientation = newConfig.orientation
|
||||
if (newOrientation != lastOrientation) {
|
||||
lastOrientation = newOrientation
|
||||
foldableObserver.handleWindowLayoutInfo()
|
||||
}
|
||||
if (newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO) {
|
||||
HWKeyboardConnected = true
|
||||
} else if (newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES) {
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tokilabs.mattermost
|
||||
package com.mattermost.rnbeta
|
||||
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tokilabs.mattermost;
|
||||
package com.mattermost.rnbeta;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tokilabs.mattermost;
|
||||
package com.mattermost.rnbeta;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tokilabs.mattermost;
|
||||
package com.mattermost.rnbeta;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 787 KiB |
|
Before Width: | Height: | Size: 334 KiB After Width: | Height: | Size: 239 KiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 351 KiB |
|
Before Width: | Height: | Size: 345 KiB After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 2.6 MiB After Width: | Height: | Size: 730 KiB |
|
Before Width: | Height: | Size: 2.6 MiB After Width: | Height: | Size: 730 KiB |
|
Before Width: | Height: | Size: 2.6 MiB After Width: | Height: | Size: 730 KiB |
|
Before Width: | Height: | Size: 2.5 MiB After Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 2.5 MiB After Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 2.5 MiB After Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 3.9 KiB |