Compare commits
197 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f013531d65 | |||
| cb659b1b11 | |||
|
|
e986f4ef9c | ||
|
|
27b8c9b890 | ||
|
|
dce5874400 | ||
| 1d4a8d5b87 | |||
| 1adf27c6a3 | |||
| 6489096ec6 | |||
| 830eb54f09 | |||
|
|
3319aec473 | ||
|
|
82e5ff2ae8 | ||
|
|
5365109ca8 | ||
|
|
6000e69886 | ||
|
|
6f0d538dd7 | ||
|
|
3b3bea56c4 | ||
|
|
1902340017 | ||
|
|
9e30f87a98 | ||
|
|
4b625e3517 | ||
|
|
058b87ee4d | ||
|
|
b4798e78cc | ||
|
|
e977303275 | ||
|
|
c391484cc8 | ||
|
|
1a01c532a1 | ||
|
|
c313f7712f | ||
|
|
46d46790b5 | ||
|
|
7afdaddd2b | ||
|
|
1cea8c6f0c | ||
|
|
c3f83f9028 | ||
|
|
1757654882 | ||
|
|
0a3e701ba5 | ||
|
|
9e0735c00d | ||
|
|
cef108db68 | ||
|
|
d0cc38d549 | ||
|
|
cff12f7182 | ||
|
|
36439bd5f4 | ||
|
|
058d080e9a | ||
|
|
9793383832 | ||
|
|
3735aa6ff8 | ||
|
|
714c3dc769 | ||
|
|
1db5cd456c | ||
|
|
2bf0ffc95f | ||
|
|
dbcf2d9bd5 | ||
|
|
12c8d80f4a | ||
|
|
9699318043 | ||
|
|
f9001ec7cd | ||
|
|
616483edbf | ||
|
|
bf3f1d5241 | ||
|
|
23565b5135 | ||
|
|
61f10310dc | ||
|
|
95d50125e4 | ||
|
|
121980c7e5 | ||
|
|
04de7b9745 | ||
|
|
fe621047ce | ||
|
|
717ea47735 | ||
|
|
4611a350e0 | ||
|
|
882c2ec57b | ||
|
|
a9258543bb | ||
|
|
8732cb9430 | ||
|
|
83e5b6026e | ||
|
|
462d6d5052 | ||
|
|
6b0437a6e3 | ||
|
|
205b7a4aea | ||
|
|
fdc559d70d | ||
|
|
d014856a3a | ||
|
|
fbaf13c7aa | ||
|
|
ca117805be | ||
|
|
ab9d66c8b6 | ||
|
|
20a70ae64d | ||
|
|
98bf396714 | ||
|
|
71d67f27d0 | ||
|
|
a3dc1a1bdc | ||
|
|
6fb109faf1 | ||
|
|
cbf300ffd3 | ||
|
|
678f1b1184 | ||
|
|
bd1dcef010 | ||
|
|
3f284dec55 | ||
|
|
855a6b8e26 | ||
|
|
944a542110 | ||
|
|
e436e1992e | ||
|
|
4324846736 | ||
|
|
c0fc0c868f | ||
|
|
cb3fcc9651 | ||
|
|
5e4b81f77a | ||
|
|
7c8d63ef28 | ||
|
|
97db6d1cc7 | ||
|
|
90135d8580 | ||
|
|
a3248d2a41 | ||
|
|
e39b27be7e | ||
|
|
9632cfa141 | ||
|
|
c3b141e77c | ||
|
|
4a05ae0cfd | ||
|
|
f024e00ca6 | ||
|
|
6f1f739428 | ||
|
|
184f272965 | ||
|
|
1a563a7917 | ||
|
|
98b4191fec | ||
|
|
a57d7aa926 | ||
|
|
2111c8fb2c | ||
|
|
6dd4a01145 | ||
|
|
83769b9265 | ||
|
|
53c7e6ba04 | ||
|
|
01673ac092 | ||
|
|
522812e126 | ||
|
|
3a4e7b6cd6 | ||
|
|
c3a27a6bcf | ||
|
|
d51a4a6641 | ||
|
|
4934920e10 | ||
|
|
c42ebca8d3 | ||
|
|
31fc648e50 | ||
|
|
d26c5c9757 | ||
|
|
49efb4f470 | ||
|
|
397efd790b | ||
|
|
b237b3639c | ||
|
|
23fec945a4 | ||
|
|
3ee8afb808 | ||
|
|
1a063acd91 | ||
|
|
d76791ebe6 | ||
|
|
50372294c1 | ||
|
|
2fb2e9d899 | ||
|
|
ebb7a2241f | ||
|
|
44eb76bed7 | ||
|
|
6fdfd57c11 | ||
|
|
0c675ec2db | ||
|
|
f243f1cd28 | ||
|
|
4ba3099d69 | ||
|
|
ca44fdd56f | ||
|
|
723e727f05 | ||
|
|
abe7a24be5 | ||
|
|
51393f13f3 | ||
|
|
3990de8932 | ||
|
|
5e9fbcf892 | ||
|
|
8fa0cbfa23 | ||
|
|
9bb3b9897b | ||
|
|
71599a8ea9 | ||
|
|
0015467f97 | ||
|
|
ae545ab62d | ||
|
|
79e8d37365 | ||
|
|
c4a2ce38da | ||
|
|
a341ef8981 | ||
|
|
c653d7c665 | ||
|
|
93b749eb79 | ||
|
|
d0a28cdc67 | ||
|
|
b19e15f181 | ||
|
|
f6eeaa7e07 | ||
|
|
c984d4f84d | ||
|
|
9dcfd149d8 | ||
|
|
6129c8571d | ||
|
|
be5b6c196c | ||
|
|
af2c35b03b | ||
|
|
34ea6b2821 | ||
|
|
4a2cad681d | ||
|
|
c615e37982 | ||
|
|
308aa0c45e | ||
|
|
b11b7d1d17 | ||
|
|
47e4bbca78 | ||
|
|
db154996f4 | ||
|
|
72b97f60f5 | ||
|
|
1d8568ade8 | ||
|
|
d713ea5b39 | ||
|
|
ec007aedec | ||
|
|
52f2b896c9 | ||
|
|
907421e3a1 | ||
|
|
017e27f475 | ||
|
|
ec1e0c3c84 | ||
|
|
b16084fad3 | ||
|
|
6cf1bb447b | ||
|
|
31556bfed6 | ||
|
|
84846b01a9 | ||
|
|
67e709b414 | ||
|
|
cf88dd89fc | ||
|
|
dbd7575f41 | ||
|
|
c0eaeefbe0 | ||
|
|
5d47f062f3 | ||
|
|
b3ccf3648f | ||
|
|
570ea2fb75 | ||
|
|
2291175c53 | ||
|
|
26741d5a89 | ||
|
|
327210f943 | ||
|
|
bb0f4a4568 | ||
|
|
d3c0850cb3 | ||
|
|
794d60b58a | ||
|
|
8a299c9bb2 | ||
|
|
ec657b4c4b | ||
|
|
90ec660963 | ||
|
|
aa88019582 | ||
|
|
c2e78dc452 | ||
|
|
910fd2b449 | ||
|
|
468b271928 | ||
|
|
9272efe2a8 | ||
|
|
6d136a1343 | ||
|
|
b44aace169 | ||
|
|
dc30200051 | ||
|
|
403bdb6856 | ||
|
|
8d358c321a | ||
|
|
4424b58fab | ||
|
|
b1d4910e28 | ||
|
|
828bf7ee45 |
|
|
@ -12,6 +12,13 @@ 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:
|
||||
|
|
|
|||
50
.github/actions/prepare-ios-build/action.yaml
vendored
|
|
@ -1,29 +1,61 @@
|
|||
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: latest-stable
|
||||
xcode-version: '26.1'
|
||||
|
||||
- name: ci/prepare-mobile-build
|
||||
uses: ./.github/actions/prepare-mobile-build
|
||||
|
||||
- name: ci/install-pods-dependencies
|
||||
- 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: |
|
||||
echo "::group::install-pods-dependencies"
|
||||
npm run ios-gems
|
||||
npm run pod-install
|
||||
echo "::endgroup::"
|
||||
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: Pods
|
||||
key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }}
|
||||
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-
|
||||
${{ 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::"
|
||||
|
|
|
|||
27
.github/actions/setup-intune-ssh-key/action.yaml
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# 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
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
# 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
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# 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"
|
||||
21
.github/workflows/build-android-beta.yml
vendored
|
|
@ -24,6 +24,9 @@ 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
|
||||
|
|
@ -58,3 +61,21 @@ jobs:
|
|||
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
|
||||
|
|
|
|||
19
.github/workflows/build-android-release.yml
vendored
|
|
@ -24,6 +24,9 @@ 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
|
||||
|
|
@ -58,3 +61,19 @@ jobs:
|
|||
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
|
||||
|
|
|
|||
62
.github/workflows/build-ios-beta.yml
vendored
|
|
@ -11,6 +11,7 @@ on:
|
|||
env:
|
||||
NODE_VERSION: 22.14.0
|
||||
TERM: xterm
|
||||
INTUNE_ENABLED: 'true'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
|
|
@ -22,16 +23,22 @@ jobs:
|
|||
uses: ./.github/actions/test
|
||||
|
||||
build-ios-simulator:
|
||||
runs-on: macos-14-large
|
||||
runs-on: macos-15-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:
|
||||
|
|
@ -49,26 +56,44 @@ jobs:
|
|||
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-14-large
|
||||
runs-on: macos-15-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/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/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/build-and-deploy-ios-beta
|
||||
env:
|
||||
|
|
@ -97,3 +122,20 @@ jobs:
|
|||
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
|
||||
|
|
|
|||
64
.github/workflows/build-ios-release.yml
vendored
|
|
@ -11,6 +11,7 @@ on:
|
|||
env:
|
||||
NODE_VERSION: 22.14.0
|
||||
TERM: xterm
|
||||
INTUNE_ENABLED: 'true'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
|
|
@ -22,30 +23,33 @@ jobs:
|
|||
uses: ./.github/actions/test
|
||||
|
||||
build-and-deploy-ios-release:
|
||||
runs-on: macos-14-large
|
||||
runs-on: macos-15-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
|
||||
|
||||
- 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
|
||||
with:
|
||||
intune-enabled: ${{ env.INTUNE_ENABLED }}
|
||||
intune-ssh-private-key: ${{ secrets.MM_MOBILE_INTUNE_DEPLOY_KEY }}
|
||||
|
||||
- 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 }}"
|
||||
|
|
@ -70,17 +74,40 @@ jobs:
|
|||
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-14-large
|
||||
runs-on: macos-15-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:
|
||||
|
|
@ -97,3 +124,18 @@ jobs:
|
|||
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
|
||||
|
|
|
|||
20
.github/workflows/build-pr.yml
vendored
|
|
@ -8,6 +8,7 @@ on:
|
|||
env:
|
||||
NODE_VERSION: 22.14.0
|
||||
TERM: xterm
|
||||
INTUNE_ENABLED: 'true'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
|
|
@ -22,7 +23,7 @@ jobs:
|
|||
uses: ./.github/actions/test
|
||||
|
||||
build-ios-pr:
|
||||
runs-on: macos-14-large
|
||||
runs-on: macos-15-large
|
||||
if: ${{ github.event.label.name == 'Build Apps for PR' || github.event.label.name == 'Build App for iOS' }}
|
||||
needs:
|
||||
- test
|
||||
|
|
@ -32,17 +33,16 @@ 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
|
||||
|
||||
- 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
|
||||
with:
|
||||
intune-enabled: ${{ env.INTUNE_ENABLED }}
|
||||
intune-ssh-private-key: ${{ secrets.MM_MOBILE_INTUNE_DEPLOY_KEY }}
|
||||
|
||||
- name: ci/build-ios-pr
|
||||
env:
|
||||
|
|
|
|||
2
.github/workflows/e2e-android-template.yml
vendored
|
|
@ -127,7 +127,7 @@ jobs:
|
|||
device_os_version: ${{ env.SDK_VERSION }}
|
||||
|
||||
e2e-android:
|
||||
name: android-detox-e2e-${{ matrix.runId }}-${{ matrix.deviceName }}-${{ matrix.deviceOsVersion }}
|
||||
name: machine-${{ matrix.runId }}-api-${{ matrix.deviceOsVersion }}
|
||||
runs-on: ubuntu-latest-8-cores
|
||||
continue-on-error: true
|
||||
timeout-minutes: 240
|
||||
|
|
|
|||
25
.github/workflows/e2e-detox-pr.yml
vendored
|
|
@ -1,12 +1,11 @@
|
|||
# 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: Detox E2E Tests PR
|
||||
name: E2E
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- feature_schedule_posts
|
||||
types:
|
||||
- labeled
|
||||
|
||||
|
|
@ -14,6 +13,9 @@ 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:
|
||||
if: contains(github.event.label.name, 'E2E iOS tests for PR')
|
||||
|
|
@ -46,7 +48,7 @@ jobs:
|
|||
|
||||
build-ios-simulator:
|
||||
if: contains(github.event.label.name, 'E2E iOS tests for PR')
|
||||
runs-on: macos-14
|
||||
runs-on: macos-26
|
||||
needs:
|
||||
- update-initial-status-ios
|
||||
steps:
|
||||
|
|
@ -55,21 +57,26 @@ jobs:
|
|||
|
||||
- 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
|
||||
run: bundle exec fastlane ios simulator --env ios.simulator skip_upload_to_s3_bucket:true
|
||||
working-directory: ./fastlane
|
||||
|
||||
- name: Upload iOS Simulator Build
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: ios-build-simulator-${{ github.run_id }}
|
||||
path: Mattermost-simulator-x86_64.app.zip
|
||||
path: Mattermost-simulator-*.app.zip
|
||||
|
||||
build-android-apk:
|
||||
runs-on: ubuntu-latest-8-cores
|
||||
|
|
@ -116,7 +123,7 @@ jobs:
|
|||
|
||||
run-ios-tests-on-pr:
|
||||
if: contains(github.event.label.name, 'E2E iOS tests for PR')
|
||||
name: iOS Mobile Tests on PR
|
||||
name: iOS
|
||||
uses: ./.github/workflows/e2e-ios-template.yml
|
||||
needs:
|
||||
- build-ios-simulator
|
||||
|
|
@ -128,7 +135,7 @@ jobs:
|
|||
|
||||
run-android-tests-on-pr:
|
||||
if: contains(github.event.label.name, 'E2E Android tests for PR')
|
||||
name: Android Mobile Tests on PR
|
||||
name: Android
|
||||
uses: ./.github/workflows/e2e-android-template.yml
|
||||
needs:
|
||||
- build-android-apk
|
||||
|
|
|
|||
8
.github/workflows/e2e-detox-release.yml
vendored
|
|
@ -5,6 +5,9 @@ on:
|
|||
branches:
|
||||
- release-*
|
||||
|
||||
env:
|
||||
INTUNE_ENABLED: 'true'
|
||||
|
||||
jobs:
|
||||
update-initial-status-ios:
|
||||
runs-on: ubuntu-22.04
|
||||
|
|
@ -33,7 +36,7 @@ jobs:
|
|||
status: pending
|
||||
|
||||
build-ios-simulator:
|
||||
runs-on: macos-14
|
||||
runs-on: macos-15
|
||||
needs:
|
||||
- update-initial-status-ios
|
||||
steps:
|
||||
|
|
@ -42,6 +45,9 @@ jobs:
|
|||
|
||||
- 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:
|
||||
|
|
|
|||
8
.github/workflows/e2e-detox-scheduled.yml
vendored
|
|
@ -4,6 +4,9 @@ on:
|
|||
schedule:
|
||||
- cron: "0 0 * * 4,5" # Wednesday and Thursday midnight
|
||||
|
||||
env:
|
||||
INTUNE_ENABLED: 'true'
|
||||
|
||||
jobs:
|
||||
update-initial-status-ios:
|
||||
runs-on: ubuntu-22.04
|
||||
|
|
@ -32,7 +35,7 @@ jobs:
|
|||
status: pending
|
||||
|
||||
build-ios-simulator:
|
||||
runs-on: macos-14
|
||||
runs-on: macos-15
|
||||
needs:
|
||||
- update-initial-status-ios
|
||||
steps:
|
||||
|
|
@ -41,6 +44,9 @@ jobs:
|
|||
|
||||
- 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:
|
||||
|
|
|
|||
180
.github/workflows/e2e-ios-template.yml
vendored
|
|
@ -38,12 +38,12 @@ on:
|
|||
description: "iPhone simulator name"
|
||||
required: false
|
||||
type: string
|
||||
default: "iPhone 15 Pro"
|
||||
default: "iPhone 17 Pro"
|
||||
ios_device_os_name:
|
||||
description: "iPhone simulator OS version"
|
||||
required: false
|
||||
type: string
|
||||
default: "iOS 17.2"
|
||||
default: "iOS 26.2"
|
||||
low_bandwidth_mode:
|
||||
description: "Enable low bandwidth mode"
|
||||
required: false
|
||||
|
|
@ -121,8 +121,8 @@ jobs:
|
|||
device_os_version: ${{ env.DEVICE_OS_VERSION }}
|
||||
|
||||
e2e-ios:
|
||||
name: ios-detox-e2e-${{ matrix.runId }}-${{ matrix.deviceName }}-${{ matrix.deviceOsVersion }}
|
||||
runs-on: macos-14
|
||||
name: machine-${{ matrix.runId }}-os-${{ matrix.deviceOsVersion }}
|
||||
runs-on: macos-15
|
||||
continue-on-error: true
|
||||
timeout-minutes: ${{ inputs.low_bandwidth_mode && 240 || 180 }}
|
||||
env:
|
||||
|
|
@ -173,6 +173,82 @@ jobs:
|
|||
- 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
|
||||
|
|
@ -182,35 +258,111 @@ jobs:
|
|||
|
||||
- name: Set .env with RUNNING_E2E=true
|
||||
run: |
|
||||
cat > .env <<EOF
|
||||
echo "RUNNING_E2E=true" >> .env
|
||||
echo "RUNNING_E2E=true" > .env
|
||||
|
||||
- name: Run Detox E2E Tests
|
||||
- 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 the server
|
||||
npm run start &
|
||||
sleep 120 # Wait for watchman to finish querying the files
|
||||
# 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 clean-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_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;
|
||||
|
||||
|
|
|
|||
20
.github/workflows/github-release.yml
vendored
|
|
@ -15,6 +15,7 @@ env:
|
|||
RELEASE_TAG: ${{ inputs.tag || github.ref_name }}
|
||||
SNYK_VERSION: "1.1297.2"
|
||||
CYCLONEDX_VERSION: "v0.27.2"
|
||||
INTUNE_ENABLED: 'true'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
|
|
@ -26,24 +27,23 @@ jobs:
|
|||
uses: ./.github/actions/test
|
||||
|
||||
build-ios-unsigned:
|
||||
runs-on: macos-14-large
|
||||
runs-on: macos-15-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
|
||||
|
||||
- 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
|
||||
with:
|
||||
intune-enabled: ${{ env.INTUNE_ENABLED }}
|
||||
intune-ssh-private-key: ${{ secrets.MM_MOBILE_INTUNE_DEPLOY_KEY }}
|
||||
|
||||
- name: ci/build-ios-unsigned
|
||||
env:
|
||||
|
|
|
|||
145
.github/workflows/validate-intune-clean.yml
vendored
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
---
|
||||
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)"
|
||||
304
.gitignore
vendored
|
|
@ -1,125 +1,199 @@
|
|||
assets/override
|
||||
dist
|
||||
build-ios
|
||||
*.zip
|
||||
server.PID
|
||||
mattermost.keystore
|
||||
tmp/
|
||||
.env
|
||||
env.d.ts
|
||||
*.apk
|
||||
*.aab
|
||||
*.ipa
|
||||
.tsbuildinfo.precommit
|
||||
# Sorted combined gitignore from .gitignore and .gitignore_my
|
||||
|
||||
*/**/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
|
||||
*.hmap
|
||||
*.xcuserstate
|
||||
project.xcworkspace
|
||||
ios/Pods
|
||||
.podinstall
|
||||
ios/.xcode.env.local
|
||||
|
||||
# Android/IntelliJ
|
||||
#
|
||||
.idea
|
||||
.gradle
|
||||
local.properties
|
||||
*.iml
|
||||
*.hprof
|
||||
.cxx/
|
||||
*.keystore
|
||||
!build/notice-file
|
||||
!debug.keystore
|
||||
android/app/bin
|
||||
android/app/build
|
||||
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
|
||||
!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
|
||||
**/fastlane/.env
|
||||
|
||||
# Sentry
|
||||
android/sentry.properties
|
||||
ios/sentry.properties
|
||||
|
||||
# Testing
|
||||
.nyc_output
|
||||
coverage
|
||||
.tmp
|
||||
|
||||
# E2E testing
|
||||
mattermost-license.txt
|
||||
*.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
|
||||
*.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/
|
||||
.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
|
||||
DerivedData
|
||||
Icon
|
||||
Network Trash Folder
|
||||
Session.vim
|
||||
Temporary Items
|
||||
[._]*.s[a-w][a-z]
|
||||
[._]s[a-w][a-z]
|
||||
__generated__
|
||||
android/app/bin
|
||||
android/app/build
|
||||
android/app/src/main/res/raw/*
|
||||
android/build
|
||||
android/sentry.properties
|
||||
assets/override
|
||||
bower_components
|
||||
buck-out/
|
||||
build-ios
|
||||
build/Release
|
||||
captures/
|
||||
coverage
|
||||
deploymentTargetDropDown.xml
|
||||
detox/artifacts
|
||||
detox/detox_pixel_*
|
||||
|
||||
# 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
|
||||
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*
|
||||
|
||||
# Android sounds
|
||||
android/app/src/main/res/raw/*
|
||||
.aider*
|
||||
# 새로 적용된 .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
|
||||
11
.gitmodules
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# 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
|
||||
3
.kilocode/rules/rules.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# 필수 준수 사항
|
||||
- agent-ops/rules/global.md 파일을 읽고 이에 대해 꼭 준수해서 진행해야한다.
|
||||
- global.md 내용을 모르면 추정하지 말고 먼저 해당 파일을 확인한다.
|
||||
1
.kilocode/system-prompt-architect
Normal file
|
|
@ -0,0 +1 @@
|
|||
-
|
||||
1
.kilocode/system-prompt-ask
Normal file
|
|
@ -0,0 +1 @@
|
|||
-
|
||||
1
.kilocode/system-prompt-code
Normal file
|
|
@ -0,0 +1 @@
|
|||
-
|
||||
1
.kilocode/system-prompt-debug
Normal file
|
|
@ -0,0 +1 @@
|
|||
-
|
||||
1
.kilocode/system-prompt-orchestrator
Normal file
|
|
@ -0,0 +1 @@
|
|||
-
|
||||
1
.kilocode/system-prompt-review
Normal file
|
|
@ -0,0 +1 @@
|
|||
-
|
||||
31
.vscode/launch.json
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"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
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
3
AGENTS.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# 필수 준수 사항
|
||||
- agent-ops/rules/global.md 파일을 읽고 이에 대해 꼭 준수해서 진행해야한다.
|
||||
- global.md 내용을 모르면 추정하지 말고 먼저 해당 파일을 확인한다.
|
||||
209
Agent-Ops-SKILL.md
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
---
|
||||
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 단계에서 불필요한 세부 구현까지 생성하지 않는다.
|
||||
172
CLAUDE-IOS-SIMULATOR.md
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
# 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
|
||||
3
CLAUDE.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# 필수 준수 사항
|
||||
- agent-ops/rules/global.md 파일을 읽고 이에 대해 꼭 준수해서 진행해야한다.
|
||||
- global.md 내용을 모르면 추정하지 말고 먼저 해당 파일을 확인한다.
|
||||
3
GEMINI.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# 필수 준수 사항
|
||||
- agent-ops/rules/global.md 파일을 읽고 이에 대해 꼭 준수해서 진행해야한다.
|
||||
- global.md 내용을 모르면 추정하지 말고 먼저 해당 파일을 확인한다.
|
||||
1
Gemfile
|
|
@ -1,3 +1,4 @@
|
|||
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.3.9)
|
||||
rexml (3.4.4)
|
||||
ruby-macho (2.5.1)
|
||||
securerandom (0.4.0)
|
||||
typhoeus (1.4.1)
|
||||
|
|
@ -107,6 +107,7 @@ PLATFORMS
|
|||
|
||||
DEPENDENCIES
|
||||
cocoapods (= 1.16.1)
|
||||
rexml (>= 3.4.2)
|
||||
|
||||
BUNDLED WITH
|
||||
2.5.11
|
||||
|
|
|
|||
124
NOTICE.txt
|
|
@ -20,6 +20,21 @@ 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
|
||||
|
|
@ -135,6 +150,42 @@ 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
|
||||
|
|
@ -1702,6 +1753,43 @@ 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
|
||||
|
|
@ -2437,6 +2525,42 @@ 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
|
||||
|
|
|
|||
36
README.md
|
|
@ -1,7 +1,7 @@
|
|||
# Mattermost Mobile v2
|
||||
|
||||
- **Minimum Server versions:** Current ESR version (10.5.0+)
|
||||
- **Supported iOS versions:** 15.1+
|
||||
- **Minimum Server versions:** Current ESR version (10.11.0+)
|
||||
- **Supported iOS versions:** 16.0+
|
||||
- **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,6 +12,34 @@ 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
|
||||
|
|
@ -19,7 +47,7 @@ We plan on releasing monthly updates with new features - check the [changelog](h
|
|||
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.mattermost.rnbeta)
|
||||
- [Android](https://play.google.com/apps/testing/com.tokilabs.mattermost)
|
||||
- [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:
|
||||
|
|
@ -31,7 +59,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.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 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 iOS, access the `Mattermost Beta` app page in TestFlight and click **Stop Testing**.
|
||||
|
||||
### Contribute Code
|
||||
|
|
|
|||
32
agent-ops/rules/_templates/domain-rule-template.md
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
---
|
||||
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 패턴을 따른다
|
||||
|
||||
## 금지 사항
|
||||
- <이 도메인에서 하면 안 되는 것>
|
||||
46
agent-ops/rules/domain/database/rules.md
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
---
|
||||
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 누락
|
||||
45
agent-ops/rules/domain/messaging/rules.md
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
---
|
||||
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()` 처리)
|
||||
49
agent-ops/rules/domain/products-agents/rules.md
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
---
|
||||
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()` 호출
|
||||
41
agent-ops/rules/domain/products-calls/rules.md
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
---
|
||||
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()` 호출
|
||||
53
agent-ops/rules/domain/ui/rules.md
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
---
|
||||
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 컴포넌트 내부에 인라인 삽입
|
||||
23
agent-ops/rules/global.md
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
---
|
||||
name: global
|
||||
description: 전 프로젝트 공통 규칙
|
||||
version: 1.0.0
|
||||
---
|
||||
|
||||
# Global Rules
|
||||
|
||||
## 응답 언어
|
||||
- 한국어로 응답한다.
|
||||
|
||||
## 기존 구조 우선
|
||||
- 새 파일 생성보다 기존 파일 수정을 우선한다.
|
||||
- 기존 패턴과 컨벤션을 따른다.
|
||||
|
||||
## 코드 변경 원칙
|
||||
- 코드 변경 전 관련 domain rule을 확인한다.
|
||||
- 요청 범위를 넘는 변경을 금지한다.
|
||||
- 불확실하면 단정하지 말고 후보로 제시한다.
|
||||
|
||||
## 작업 전 확인
|
||||
- `agent-ops/skills/router.md` 를 먼저 참조하여 필요한 skill 또는 domain rule만 로드한다.
|
||||
- 관련 rule / skill 만 최소로 로드한다.
|
||||
35
agent-ops/skills/_templates/skill-template.md
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
---
|
||||
name: <skill-name>
|
||||
description: <skill 한 줄 설명>
|
||||
version: 1.0.0
|
||||
---
|
||||
|
||||
# <Skill Name>
|
||||
|
||||
## 목적
|
||||
<이 skill이 해결하는 문제>
|
||||
|
||||
## 언제 호출할지
|
||||
- <이 skill을 사용해야 하는 상황>
|
||||
- <이 skill을 사용해야 하는 상황>
|
||||
|
||||
## 입력
|
||||
- `<param>`: <설명> (필수/선택)
|
||||
|
||||
## 먼저 확인할 것
|
||||
- [ ] <사전 확인 항목>
|
||||
- [ ] <사전 확인 항목>
|
||||
|
||||
## 실행 절차
|
||||
1. <단계>
|
||||
2. <단계>
|
||||
3. <단계>
|
||||
|
||||
## 출력 형식
|
||||
```
|
||||
<출력 예시>
|
||||
```
|
||||
|
||||
## 금지 사항
|
||||
- <하면 안 되는 것>
|
||||
- <하면 안 되는 것>
|
||||
48
agent-ops/skills/code-change/SKILL.md
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
---
|
||||
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 파일 수정 금지)
|
||||
96
agent-ops/skills/create-domain-rule/SKILL.md
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
---
|
||||
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 파일 생성만 담당한다
|
||||
83
agent-ops/skills/create-skill/SKILL.md
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
---
|
||||
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 에 여러 독립적인 책임을 묶지 않는다
|
||||
40
agent-ops/skills/find-domain/SKILL.md
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
---
|
||||
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까지 로드하지 않는다
|
||||
48
agent-ops/skills/router.md
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
---
|
||||
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로 먼저 판별한다.
|
||||
|
|
@ -104,7 +104,7 @@ android {
|
|||
ndkVersion rootProject.ext.ndkVersion
|
||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
namespace "com.mattermost.rnbeta"
|
||||
namespace "com.tokilabs.mattermost"
|
||||
|
||||
lintOptions {
|
||||
checkReleaseBuilds false
|
||||
|
|
@ -112,11 +112,11 @@ android {
|
|||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.mattermost.rnbeta"
|
||||
applicationId "com.tokilabs.mattermost"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 676
|
||||
versionName "2.33.0"
|
||||
versionCode 730
|
||||
versionName "2.38.0"
|
||||
testBuildType System.getProperty('testBuildType', 'debug')
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,104 +1,29 @@
|
|||
{
|
||||
"project_info": {
|
||||
"project_number": "184930218130",
|
||||
"firebase_url": "https://api-7231322553409637977-752355.firebaseio.com",
|
||||
"project_id": "api-7231322553409637977-752355",
|
||||
"storage_bucket": "api-7231322553409637977-752355.appspot.com"
|
||||
"project_number": "1047648748539",
|
||||
"project_id": "mattermost-6ac08",
|
||||
"storage_bucket": "mattermost-6ac08.firebasestorage.app"
|
||||
},
|
||||
"client": [
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:184930218130:android:a0e553d1a1f043b5",
|
||||
"mobilesdk_app_id": "1:1047648748539:android:818bf70bfbb3f9d070415e",
|
||||
"android_client_info": {
|
||||
"package_name": "com.mattermost.react.native"
|
||||
"package_name": "com.tokilabs.mattermost"
|
||||
}
|
||||
},
|
||||
"oauth_client": [
|
||||
{
|
||||
"client_id": "184930218130-8nahrspll1opff0uogtkh2qsv8coqngo.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
}
|
||||
],
|
||||
"oauth_client": [],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyCkZkU2ECVg-mmdCG5OoHHQXtKiENuvWPE"
|
||||
"current_key": "AIzaSyAW6j_oPl9MVcm93qS3JgaEPD5ywp-TzZ0"
|
||||
}
|
||||
],
|
||||
"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.mattermost.rnbeta;
|
||||
package com.tokilabs.mattermost;
|
||||
|
||||
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="mmauthbeta" />
|
||||
<data android:scheme="mmauth_tokilabs" />
|
||||
</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.mattermost.rnbeta.*;
|
||||
import com.tokilabs.mattermost.*;
|
||||
import com.mattermost.rnutils.helpers.NotificationHelper;
|
||||
import com.nozbe.watermelondb.WMDatabase;
|
||||
import com.mattermost.turbolog.TurboLog;
|
||||
|
|
|
|||
|
|
@ -135,21 +135,22 @@ class PushNotificationDataRunnable {
|
|||
threadsArray.add(thread)
|
||||
}
|
||||
for(i in 0 until it.size()) {
|
||||
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)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,12 +54,14 @@ fun insertCategoriesWithChannels(db: WMDatabase, orderCategories: ReadableMap) {
|
|||
val categories = orderCategories.getArray("categories") ?: return
|
||||
for (i in 0 until categories.size()) {
|
||||
val category = categories.getMap(i)
|
||||
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) }
|
||||
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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -67,19 +69,22 @@ fun insertCategoriesWithChannels(db: WMDatabase, orderCategories: ReadableMap) {
|
|||
fun insertChannelToDefaultCategory(db: WMDatabase, categoryChannels: ReadableArray) {
|
||||
try {
|
||||
for (i in 0 until categoryChannels.size()) {
|
||||
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)
|
||||
)
|
||||
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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} 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()) {
|
||||
threadsArray.add(it.getMap(i))
|
||||
it.getMap(i)?.let { map -> threadsArray.add(map) }
|
||||
}
|
||||
handleThreads(db, threadsArray, teamId)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,8 +10,10 @@ 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)
|
||||
if (earliest >= chunk.getDouble("earliest") || latest <= chunk.getDouble("latest")) {
|
||||
return chunk
|
||||
chunk?.let {
|
||||
if (earliest >= it.getDouble("earliest") || latest <= it.getDouble("latest")) {
|
||||
return it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -45,10 +47,12 @@ internal fun mergePostsInChannel(db: WMDatabase, existingChunks: ReadableArray,
|
|||
for (i in 0 until existingChunks.size()) {
|
||||
try {
|
||||
val chunk = existingChunks.getMap(i)
|
||||
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
|
||||
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
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
|
|
|
|||
|
|
@ -83,15 +83,17 @@ internal fun insertThreadParticipants(db: WMDatabase, threadId: String, particip
|
|||
for (i in 0 until participants.size()) {
|
||||
try {
|
||||
val participant = participants.getMap(i)
|
||||
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"))
|
||||
)
|
||||
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"))
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,38 +48,40 @@ fun getCurrentUserLocale(db: WMDatabase): String {
|
|||
fun handleUsers(db: WMDatabase, users: ReadableArray) {
|
||||
for (i in 0 until users.size()) {
|
||||
val user = users.getMap(i)
|
||||
val roles = user.getString("roles") ?: ""
|
||||
val isBot = try {
|
||||
user.getBoolean("is_bot")
|
||||
} catch (e: NoSuchKeyException) {
|
||||
false
|
||||
}
|
||||
user?.let { u ->
|
||||
val roles = u.getString("roles") ?: ""
|
||||
val isBot = try {
|
||||
u.getBoolean("is_bot")
|
||||
} catch (e: NoSuchKeyException) {
|
||||
false
|
||||
}
|
||||
|
||||
val lastPictureUpdate = try { user.getDouble("last_picture_update") } catch (e: NoSuchKeyException) { 0 }
|
||||
val lastPictureUpdate = try { u.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(
|
||||
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()
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,11 +26,13 @@ suspend fun PushNotificationDataRunnable.Companion.fetchMyChannel(db: WMDatabase
|
|||
"D" -> {
|
||||
profilesArray = fetchProfileInChannel(db, serverUrl, channelId)
|
||||
if ((profilesArray?.size() ?: 0) > 0) {
|
||||
val displayName = displayUsername(profilesArray!!.getMap(0), displayNameSetting)
|
||||
val data = Arguments.createMap()
|
||||
data.merge(channelData)
|
||||
data.putString("display_name", displayName)
|
||||
channelData = data
|
||||
profilesArray?.getMap(0)?.let { profile ->
|
||||
val displayName = displayUsername(profile, displayNameSetting)
|
||||
val data = Arguments.createMap()
|
||||
data.merge(channelData)
|
||||
data.putString("display_name", displayName)
|
||||
channelData = data
|
||||
}
|
||||
}
|
||||
}
|
||||
"G" -> {
|
||||
|
|
@ -117,8 +119,10 @@ private suspend fun PushNotificationDataRunnable.Companion.fetchProfileInChannel
|
|||
if (profilesArray != null) {
|
||||
for (i in 0 until profilesArray.size()) {
|
||||
val profile = profilesArray.getMap(i)
|
||||
if (profile.getString("id") != currentUserId) {
|
||||
result.pushMap(profile)
|
||||
profile?.let {
|
||||
if (it.getString("id") != currentUserId) {
|
||||
result.pushMap(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -152,7 +156,9 @@ private fun PushNotificationDataRunnable.Companion.displayGroupMessageName(profi
|
|||
val names = ArrayList<String>()
|
||||
for (i in 0 until profilesArray.size()) {
|
||||
val profile = profilesArray.getMap(i)
|
||||
names.add(displayUsername(profile, displayNameSetting))
|
||||
profile?.let {
|
||||
names.add(displayUsername(it, 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,10 +95,12 @@ internal suspend fun PushNotificationDataRunnable.Companion.fetchPosts(
|
|||
if (attachments != null) {
|
||||
for (i in 0 until attachments.size()) {
|
||||
val attachment = attachments.getMap(i)
|
||||
val pretext = attachment.getString("pretext")
|
||||
val text = attachment.getString("text")
|
||||
findNeededUsernames(pretext)
|
||||
findNeededUsernames(text)
|
||||
attachment?.let {
|
||||
val pretext = it.getString("pretext")
|
||||
val text = it.getString("text")
|
||||
findNeededUsernames(pretext)
|
||||
findNeededUsernames(text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -132,22 +134,23 @@ 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)
|
||||
}
|
||||
|
||||
val participantId = participant.getString("id")
|
||||
if (participantId != currentUserId && participantId != null) {
|
||||
if (!threadParticipantUserIds.contains(participantId) && !userIdsAlreadyLoaded.contains(participantId)) {
|
||||
threadParticipantUserIds.add(participantId)
|
||||
if (!threadParticipantUsers.containsKey(participantId)) {
|
||||
threadParticipantUsers[participantId] = p
|
||||
}
|
||||
}
|
||||
|
||||
if (!threadParticipantUsers.containsKey(participantId)) {
|
||||
threadParticipantUsers[participantId] = participant
|
||||
val username = p.getString("username")
|
||||
if (username != null && username != currentUsername && !threadParticipantUsernames.contains(username)) {
|
||||
threadParticipantUsernames.add(username)
|
||||
}
|
||||
}
|
||||
|
||||
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()) {
|
||||
list.add(users.getMap(i))
|
||||
users.getMap(i)?.let { list.add(it) }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.mattermost.rnbeta
|
||||
package com.tokilabs.mattermost
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
package com.mattermost.rnbeta
|
||||
package com.tokilabs.mattermost
|
||||
|
||||
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
|
||||
|
|
@ -37,6 +38,7 @@ class MainActivity : NavigationActivity() {
|
|||
setHWKeyboardConnected()
|
||||
lastOrientation = this.resources.configuration.orientation
|
||||
foldableObserver.onCreate()
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.mattermost.rnbeta
|
||||
package com.tokilabs.mattermost
|
||||
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.mattermost.rnbeta;
|
||||
package com.tokilabs.mattermost;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.mattermost.rnbeta;
|
||||
package com.tokilabs.mattermost;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.mattermost.rnbeta;
|
||||
package com.tokilabs.mattermost;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
|
Before Width: | Height: | Size: 787 KiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 239 KiB After Width: | Height: | Size: 334 KiB |
|
Before Width: | Height: | Size: 351 KiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 345 KiB |
|
Before Width: | Height: | Size: 730 KiB After Width: | Height: | Size: 2.6 MiB |
|
Before Width: | Height: | Size: 730 KiB After Width: | Height: | Size: 2.6 MiB |
|
Before Width: | Height: | Size: 730 KiB After Width: | Height: | Size: 2.6 MiB |
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 2.5 MiB |
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 2.5 MiB |
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 2.5 MiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 7.7 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 7.7 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 7.7 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 7.7 KiB |