├── .gitattributes
├── .github
└── workflows
│ └── tests.yml
├── .gitignore
├── .prettierrc.js
├── .watchmanconfig
├── LICENSE
├── README.md
├── app.json
├── babel.config.js
├── index.js
├── macos
├── .gitignore
├── Podfile
├── Podfile.lock
├── cidemon-iOS
│ ├── AppDelegate.h
│ ├── AppDelegate.m
│ ├── Base.lproj
│ │ └── LaunchScreen.xib
│ ├── Images.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ └── Contents.json
│ ├── Info.plist
│ └── main.m
├── cidemon-macOS
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ ├── Contents.json
│ │ │ ├── Icon-1024.png
│ │ │ ├── Icon-128.png
│ │ │ ├── Icon-16.png
│ │ │ ├── Icon-16@2x.png
│ │ │ ├── Icon-256.png
│ │ │ ├── Icon-257.png
│ │ │ ├── Icon-32.png
│ │ │ ├── Icon-32@2x.png
│ │ │ ├── Icon-512.png
│ │ │ └── Icon-513.png
│ │ ├── Contents.json
│ │ ├── copy.imageset
│ │ │ ├── Contents.json
│ │ │ └── copy.pdf
│ │ ├── failed.imageset
│ │ │ ├── Contents.json
│ │ │ ├── Group 106.png
│ │ │ ├── Group 106@2x.png
│ │ │ ├── Group 106@3x.png
│ │ │ ├── Group 98.png
│ │ │ ├── Group 98@2x.png
│ │ │ └── Group 98@3x.png
│ │ ├── failed_passed.imageset
│ │ │ ├── Contents.json
│ │ │ ├── Group 101.png
│ │ │ ├── Group 101@2x.png
│ │ │ ├── Group 101@3x.png
│ │ │ ├── Group 109.png
│ │ │ ├── Group 109@2x.png
│ │ │ └── Group 109@3x.png
│ │ ├── initial.imageset
│ │ │ ├── Contents.json
│ │ │ ├── Group 107.png
│ │ │ ├── Group 107@2x.png
│ │ │ ├── Group 107@3x.png
│ │ │ ├── Group 99.png
│ │ │ ├── Group 99@2x.png
│ │ │ └── Group 99@3x.png
│ │ ├── passed.imageset
│ │ │ ├── Contents.json
│ │ │ ├── Group 104.png
│ │ │ ├── Group 104@2x.png
│ │ │ ├── Group 104@3x.png
│ │ │ ├── Group 96.png
│ │ │ ├── Group 96@2x.png
│ │ │ └── Group 96@3x.png
│ │ ├── pending.imageset
│ │ │ ├── Contents.json
│ │ │ ├── Group 105.png
│ │ │ ├── Group 105@2x.png
│ │ │ ├── Group 105@3x.png
│ │ │ ├── Group 97.png
│ │ │ ├── Group 97@2x.png
│ │ │ └── Group 97@3x.png
│ │ ├── pending_failed.imageset
│ │ │ ├── Contents.json
│ │ │ ├── Group 103.png
│ │ │ ├── Group 103@2x.png
│ │ │ ├── Group 103@3x.png
│ │ │ ├── Group 95.png
│ │ │ ├── Group 95@2x.png
│ │ │ └── Group 95@3x.png
│ │ ├── pending_failed_passed.imageset
│ │ │ ├── Contents.json
│ │ │ ├── Group 102.png
│ │ │ ├── Group 102@2x-1.png
│ │ │ ├── Group 102@2x.png
│ │ │ ├── Group 110.png
│ │ │ ├── Group 110@3x-1.png
│ │ │ └── Group 110@3x.png
│ │ ├── pending_passed.imageset
│ │ │ ├── Contents.json
│ │ │ ├── Group 100.png
│ │ │ ├── Group 100@2x.png
│ │ │ ├── Group 100@3x.png
│ │ │ ├── Group 108.png
│ │ │ ├── Group 108@2x.png
│ │ │ └── Group 108@3x.png
│ │ ├── simple_failed.imageset
│ │ │ ├── Contents.json
│ │ │ ├── Union.png
│ │ │ ├── Union@2x.png
│ │ │ └── Union@3x.png
│ │ ├── simple_initial.imageset
│ │ │ ├── Contents.json
│ │ │ ├── Union-3.png
│ │ │ ├── Union-4.png
│ │ │ ├── Union@2x-3.png
│ │ │ ├── Union@2x-4.png
│ │ │ ├── Union@3x-3.png
│ │ │ └── Union@3x-4.png
│ │ ├── simple_passed.imageset
│ │ │ ├── Contents.json
│ │ │ ├── Union-2.png
│ │ │ ├── Union@2x-2.png
│ │ │ └── Union@3x-2.png
│ │ └── simple_pending.imageset
│ │ │ ├── Contents.json
│ │ │ ├── Union-1.png
│ │ │ ├── Union@2x-1.png
│ │ │ └── Union@3x-1.png
│ ├── Base.lproj
│ │ └── Main.storyboard
│ ├── CIDemonNative.m
│ ├── CIDemonNative.swift
│ ├── Info.plist
│ ├── cidemon-macOS-Bridging-Header.h
│ ├── cidemon.entitlements
│ └── lib
│ │ ├── MyNSWindowDelegate.swift
│ │ ├── ReactNativeBridge.swift
│ │ └── ReactViewController.swift
├── cidemon.xcodeproj
│ ├── project.pbxproj
│ └── xcshareddata
│ │ └── xcschemes
│ │ ├── cidemon-iOS.xcscheme
│ │ └── cidemon-macOS.xcscheme
└── cidemon.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ ├── IDEWorkspaceChecks.plist
│ └── swiftpm
│ └── Package.resolved
├── metro.config.js
├── package.json
├── scripts
└── bumpVersion.sh
├── src
├── App.tsx
├── Assets.ts
├── Root.store.ts
├── Route.tsx
├── Theme.ts
├── assets
│ ├── icons
│ │ ├── appcenter.png
│ │ ├── bitrise.png
│ │ ├── circleci.png
│ │ ├── github.png
│ │ ├── github_bar.png
│ │ ├── github_bar@2x.png
│ │ ├── github_bar@3x.png
│ │ ├── gitlab.png
│ │ ├── logo.png
│ │ └── travisci.png
│ └── image
│ │ └── profile.jpeg
├── component
│ ├── Divider.component.tsx
│ ├── EmptyNodes.component.tsx
│ ├── NodeRow.component.tsx
│ ├── Row.component.tsx
│ ├── Spacer.component.tsx
│ ├── TempoButton.component.tsx
│ └── index.ts
├── container
│ ├── AddToken.container.tsx
│ ├── GeneralConfig.container.tsx
│ ├── GithubActionsConfig.container.tsx
│ ├── IgnoreConfig.container.tsx
│ ├── NodeList.container.tsx
│ ├── Toast.container.tsx
│ └── index.ts
├── lib
│ ├── CIDemonNative.ts
│ ├── DtoMapper.test.ts
│ ├── DtoMappings.ts
│ ├── __mocks__
│ │ └── CIDemonNative.ts
│ ├── constants.ts
│ ├── generalUtils.ts
│ ├── hooks.ts
│ └── index.ts
├── model
│ ├── dto
│ │ ├── AppcenterBranch.dto.ts
│ │ ├── AppcenterRepo.dto.ts
│ │ ├── Bitrise.dto.ts
│ │ ├── CircleciRepo.dto.ts
│ │ ├── Github.dto.ts
│ │ ├── Gitlab.dto.ts
│ │ ├── TravisBranches.dto.ts
│ │ └── TravisRepos.dto.ts
│ └── index.ts
├── store
│ ├── Api.store.ts
│ ├── Node.store.test.ts
│ ├── Node.store.ts
│ ├── Ping.store.ts
│ ├── UI.store.ts
│ ├── __mocks__
│ │ └── Api.store.ts
│ └── index.ts
├── styles.json
├── tailwind.ts
└── typings.d.ts
├── styles.json
├── tailwind.config.js
├── tsconfig.json
└── yarn.lock
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.pbxproj -text
2 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: CI Demon Tests
2 | on: push
3 |
4 | jobs:
5 | tests:
6 | runs-on: ubuntu-latest
7 |
8 | steps:
9 | - name: Cancel Previous Runs
10 | uses: styfle/cancel-workflow-action@0.6.0
11 | with:
12 | access_token: ${{ github.token }}
13 | - uses: actions/checkout@v2
14 | - name: Use Node.js 12.x
15 | uses: actions/setup-node@v2.1.2
16 | with:
17 | node-version: 15.x
18 | - name: Install Dependencies
19 | run: yarn
20 | - name: Run tests
21 | run: yarn test
22 | # - name: Run tests
23 | # uses: ospfranco/gh-jester@v1.0.13
24 | # with:
25 | # post-comment: true
26 | # env:
27 | # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
28 | # GITHUB_CONTEXT: ${{ toJson(github) }}
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OSX
2 | #
3 | .DS_Store
4 |
5 | # Xcode
6 | #
7 | build/
8 | *.pbxuser
9 | !default.pbxuser
10 | *.mode1v3
11 | !default.mode1v3
12 | *.mode2v3
13 | !default.mode2v3
14 | *.perspectivev3
15 | !default.perspectivev3
16 | xcuserdata
17 | *.xccheckout
18 | *.moved-aside
19 | DerivedData
20 | *.hmap
21 | *.ipa
22 | *.xcuserstate
23 |
24 | # Android/IntelliJ
25 | #
26 | build/
27 | .idea
28 | .gradle
29 | local.properties
30 | *.iml
31 |
32 | # node.js
33 | #
34 | node_modules/
35 | npm-debug.log
36 | yarn-error.log
37 |
38 | # BUCK
39 | buck-out/
40 | \.buckd/
41 | *.keystore
42 | !debug.keystore
43 |
44 | # fastlane
45 | #
46 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
47 | # screenshots whenever they are needed.
48 | # For more information about the recommended setup visit:
49 | # https://docs.fastlane.tools/best-practices/source-control/
50 |
51 | */fastlane/report.xml
52 | */fastlane/Preview.html
53 | */fastlane/screenshots
54 |
55 | # Bundle artifact
56 | *.jsbundle
57 |
58 | # CocoaPods
59 | /ios/Pods/
60 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | bracketSpacing: false,
3 | jsxBracketSameLine: true,
4 | singleQuote: true,
5 | trailingComma: 'all',
6 | };
7 |
--------------------------------------------------------------------------------
/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2021 Oscar Franco
2 |
3 | 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:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | 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.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
CI Demon
2 |
3 | A macOS menu bar application to monitor your CI jobs
4 |
5 | 
6 |
7 |
20 |
21 |
22 |
23 | MacOS menu bar app that aggregates information and notifies you when your CI builds or deployments are broken (or restored), it shows you all the relevant information in a single native app, without having to wait for entire web apps to load.
24 |
25 | Developed with react-native for macOS, if you know typescript you can easily contribute to it, if you want to add support for more providers, it is fairly straightforward, your CI needs to provide an API and you need only to write a few typescript functions.
26 |
27 | ## Supported providers
28 |
29 | CI Demon currently supports the following CI providers:
30 |
31 | - Github Checks
32 | - CircleCI
33 | - TravisCI
34 | - Gitlab SaaS & self-managed
35 | - AppCenter
36 | - Bitrise
37 |
38 | ## Health Checks
39 |
40 | CI Demon can also create http ping checks for you to make sure your deployment is still running, feature is really simple at the moment, send request -> check for OK response.
41 |
42 | ## All features
43 |
44 | - Desktop notifications on failures/restorations
45 | - Everything is stored encrypted in the macOS keychain
46 | - Absolutely **NO** tracking
47 | - Not a SaaS, it's a native macOS app
48 | - Filter branches/builds by Regex
49 | - Trigger rebuilds for builds
50 | - Track Github PRs and/or branches
51 | - Natively share a job to your team mates
52 | - Filter Gitlab projects by visibility settings
53 | ## License
54 |
55 | MIT License
56 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cidemon",
3 | "displayName": "cidemon"
4 | }
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ['module:metro-react-native-babel-preset'],
3 | plugins: [
4 | [
5 | `module-resolver`,
6 | {
7 | root: [`./src`],
8 | extensions: [`.js`, `.ts`, `.tsx`, `.ios.js`, `.android.js`],
9 | },
10 | ],
11 | ],
12 | };
13 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import {AppRegistry} from 'react-native';
2 | import {App} from './src/App';
3 | import {name as appName} from './app.json';
4 | // import {NativeModules} from 'react-native';
5 |
6 | // NativeModules.DevSettings.setIsSecondaryClickToShowDevMenuEnabled(true);
7 |
8 | AppRegistry.registerComponent(appName, () => App);
9 |
--------------------------------------------------------------------------------
/macos/.gitignore:
--------------------------------------------------------------------------------
1 | # CocoaPods
2 | Pods/
3 |
--------------------------------------------------------------------------------
/macos/Podfile:
--------------------------------------------------------------------------------
1 | require_relative '../node_modules/react-native-macos/scripts/react_native_pods'
2 | require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
3 |
4 | target 'cidemon-macOS' do
5 | platform :macos, '10.14'
6 | use_native_modules!
7 | use_react_native!(
8 | :path => '../node_modules/react-native-macos',
9 |
10 | # To use Hermes, install the `hermes-engine-darwin` npm package, e.g.:
11 | # $ yarn add 'hermes-engine-darwin@~0.5.3'
12 | #
13 | # Then enable this option:
14 | # :hermes_enabled => true
15 | )
16 |
17 | # Pods specifically for macOS target
18 | pod 'KeychainAccess'
19 | end
20 |
21 | target 'cidemon-iOS' do
22 | platform :ios, '10'
23 | use_native_modules!
24 | use_react_native!(:path => '../node_modules/react-native-macos')
25 |
26 | # Enables Flipper.
27 | #
28 | # Note that if you have use_frameworks! enabled, Flipper will not work and
29 | # you should disable these next few lines.
30 | # use_flipper!
31 | # post_install do |installer|
32 | # flipper_post_install(installer)
33 | # end
34 |
35 | # Pods specifically for iOS target
36 | end
37 |
--------------------------------------------------------------------------------
/macos/cidemon-iOS/AppDelegate.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | @interface AppDelegate : UIResponder
5 |
6 | @property (nonatomic, strong) UIWindow *window;
7 |
8 | @end
9 |
--------------------------------------------------------------------------------
/macos/cidemon-iOS/AppDelegate.m:
--------------------------------------------------------------------------------
1 | #import "AppDelegate.h"
2 |
3 | #import
4 | #import
5 | #import
6 |
7 | @implementation AppDelegate
8 |
9 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
10 | {
11 | RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
12 | RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
13 | moduleName:@"cidemon"
14 | initialProperties:nil];
15 |
16 | rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
17 |
18 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
19 | UIViewController *rootViewController = [UIViewController new];
20 | rootViewController.view = rootView;
21 | self.window.rootViewController = rootViewController;
22 | [self.window makeKeyAndVisible];
23 | return YES;
24 | }
25 |
26 | - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
27 | {
28 | #if DEBUG
29 | return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
30 | #else
31 | return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
32 | #endif
33 | }
34 |
35 | @end
36 |
--------------------------------------------------------------------------------
/macos/cidemon-iOS/Base.lproj/LaunchScreen.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
21 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/macos/cidemon-iOS/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "29x29",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "29x29",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "40x40",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "40x40",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "60x60",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "60x60",
31 | "scale" : "3x"
32 | }
33 | ],
34 | "info" : {
35 | "version" : 1,
36 | "author" : "xcode"
37 | }
38 | }
--------------------------------------------------------------------------------
/macos/cidemon-iOS/Images.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/macos/cidemon-iOS/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | $(PRODUCT_NAME)
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | 1
25 | LSRequiresIPhoneOS
26 |
27 | NSAppTransportSecurity
28 |
29 | NSAllowsArbitraryLoads
30 |
31 | NSExceptionDomains
32 |
33 | localhost
34 |
35 | NSExceptionAllowsInsecureHTTPLoads
36 |
37 |
38 |
39 |
40 | NSLocationWhenInUseUsageDescription
41 |
42 | UILaunchStoryboardName
43 | LaunchScreen
44 | UIRequiredDeviceCapabilities
45 |
46 | armv7
47 |
48 | UISupportedInterfaceOrientations
49 |
50 | UIInterfaceOrientationPortrait
51 | UIInterfaceOrientationLandscapeLeft
52 | UIInterfaceOrientationLandscapeRight
53 |
54 | UIViewControllerBasedStatusBarAppearance
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/macos/cidemon-iOS/main.m:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | #import "AppDelegate.h"
4 |
5 | int main(int argc, char * argv[]) {
6 | @autoreleasepool {
7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/macos/cidemon-macOS/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import Cocoa
3 |
4 | @NSApplicationMain
5 | class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDelegate {
6 | var myWindowDelegate: MyNSWindowDelegate!
7 | var popover: NSWindow!
8 | var window: NSWindow!
9 | var statusBarItem: NSStatusItem!
10 | var reactNativeBridge: ReactNativeBridge!
11 | var controller: ReactViewController!
12 |
13 | func applicationDidFinishLaunching(_ aNotification: Notification) {
14 | reactNativeBridge = ReactNativeBridge()
15 | controller = ReactViewController(moduleName: "cidemon", bridge: reactNativeBridge.bridge)
16 |
17 | var screenHeight = CGFloat(800)
18 | if(NSScreen.main != nil) {
19 | screenHeight = NSScreen.main!.frame.height
20 | }
21 |
22 | let windowHeight = Int(min(screenHeight / 1.5, 700))
23 | let windowWidth = Int(min(screenHeight, 600))
24 |
25 | popover = NSWindow(
26 | contentRect: NSRect(x: 0, y: 0, width: 1, height: 1),
27 | styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
28 | backing: .buffered, defer: false)
29 |
30 | popover.titlebarAppearsTransparent = true
31 | popover.titleVisibility = .hidden
32 | popover.isMovableByWindowBackground = false
33 | popover.isReleasedWhenClosed = false
34 | popover.collectionBehavior = [.transient, .ignoresCycle]
35 | popover.standardWindowButton(.closeButton)?.isHidden = true
36 | popover.standardWindowButton(.zoomButton)?.isHidden = true
37 | popover.standardWindowButton(.miniaturizeButton)?.isHidden = true
38 | popover.contentViewController = controller
39 |
40 | self.myWindowDelegate = MyNSWindowDelegate(resignHandler: {
41 | self.hidePopover()
42 | })
43 | popover.delegate = myWindowDelegate
44 |
45 | statusBarItem = NSStatusBar.system.statusItem(withLength: CGFloat(NSStatusItem.variableLength))
46 |
47 | if let button = self.statusBarItem.button {
48 | button.imagePosition = NSControl.ImagePosition.imageLeft
49 | button.image = NSImage(named: "initial")
50 | button.action = #selector(togglePopover(_:))
51 | }
52 |
53 | NSUserNotificationCenter.default.delegate = self
54 |
55 | // #if DEBUG
56 | // window = NSWindow(
57 | // contentRect: NSRect(x: 0, y: 0, width: 1, height: 1),
58 | // styleMask: [.titled, .closable, .miniaturizable, .resizable],
59 | // backing: .buffered,
60 | // defer: false)
61 |
62 | // window.contentViewController = controller
63 | // window.center()
64 | // window.setFrameAutosaveName("CI Demon")
65 | // window.isReleasedWhenClosed = false
66 | // let screen: NSScreen = NSScreen.main!
67 | // let midScreenX = 600
68 | // let posScreenY = screen.frame.height
69 | // let origin = CGPoint(x: Int(midScreenX), y: Int(posScreenY))
70 | // let size = CGSize(width: 600, height: 700)
71 | // let frame = NSRect(origin: origin, size: size)
72 | // window.setFrame(frame, display: true)
73 | // window.makeKeyAndOrderFront(self)
74 | // #endif
75 |
76 | }
77 |
78 | func userNotificationCenter(_ center: NSUserNotificationCenter, didActivate notification: NSUserNotification) {
79 | let url = URL(string: notification.userInfo!["url"] as! String)!
80 | NSWorkspace.shared.open(url)
81 | }
82 |
83 | @objc func togglePopover(_ sender: AnyObject?) {
84 | if self.popover.isVisible && self.popover.isKeyWindow {
85 | self.popover.close()
86 |
87 | } else {
88 |
89 | self.popover.makeKeyAndOrderFront(self)
90 | self.popover.center()
91 | if !NSApp.isActive {
92 | NSApp.activate(ignoringOtherApps: true)
93 | }
94 | let screen: NSScreen = NSScreen.main!
95 | let midScreenX = self.statusBarItem.button!.window!.frame.origin.x - 300
96 | let posScreenY = screen.frame.height
97 | let origin = CGPoint(x: Int(midScreenX), y: Int(posScreenY))
98 | let size = CGSize(width: 600, height: 700)
99 | let frame = NSRect(origin: origin, size: size)
100 | self.popover.setFrame(frame, display: true)
101 | }
102 | }
103 |
104 | @objc func hidePopover() {
105 | if self.popover.isVisible {
106 | self.popover.close()
107 | }
108 | }
109 |
110 | // @objc func showPopover(_ sender: AnyObject?) {
111 | // if !(self.mainWindow.isVisible && self.mainWindow.isKeyWindow) {
112 | // self.hotKey.isPaused = false
113 | // self.mainWindow.makeKeyAndOrderFront(self)
114 | // self.mainWindow.center()
115 | // if !NSApp.isActive {
116 | // NSApp.activate(ignoringOtherApps: true)
117 | // }
118 | // }
119 | // }
120 |
121 | func setStatusText(failed: NSInteger, running: NSInteger, passed: NSInteger, useSimpleIcon: Bool) {
122 | if(useSimpleIcon) {
123 | if let button = self.statusBarItem.button {
124 | if(running > 0) {
125 | button.image = NSImage(named: "simple_pending")
126 | } else if(failed > 0) {
127 | button.image = NSImage(named: "simple_failed")
128 | } else if(running > 0) {
129 | button.image = NSImage(named: "simple_passed")
130 | } else {
131 | button.image = NSImage(named: "simple_initial")
132 | }
133 | }
134 | return
135 | }
136 |
137 | var statuses: [String] = []
138 | if(running > 0) {
139 | statuses.append("pending")
140 | }
141 | if(failed > 0) {
142 | statuses.append("failed")
143 | }
144 | if(passed > 0) {
145 | statuses.append("passed")
146 | }
147 |
148 | if let button = self.statusBarItem.button {
149 | if(statuses.count > 0) {
150 | button.image = NSImage(named: statuses.joined(separator: "_"))
151 | } else {
152 | button.image = NSImage(named: "initial")
153 | }
154 | }
155 | }
156 |
157 | func closeApp() {
158 | NSApp.terminate(nil)
159 | }
160 |
161 | func getWindowObject() -> NSView {
162 | return controller.view
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Icon-16.png",
5 | "idiom" : "mac",
6 | "scale" : "1x",
7 | "size" : "16x16"
8 | },
9 | {
10 | "filename" : "Icon-16@2x.png",
11 | "idiom" : "mac",
12 | "scale" : "2x",
13 | "size" : "16x16"
14 | },
15 | {
16 | "filename" : "Icon-32.png",
17 | "idiom" : "mac",
18 | "scale" : "1x",
19 | "size" : "32x32"
20 | },
21 | {
22 | "filename" : "Icon-32@2x.png",
23 | "idiom" : "mac",
24 | "scale" : "2x",
25 | "size" : "32x32"
26 | },
27 | {
28 | "filename" : "Icon-128.png",
29 | "idiom" : "mac",
30 | "scale" : "1x",
31 | "size" : "128x128"
32 | },
33 | {
34 | "filename" : "Icon-257.png",
35 | "idiom" : "mac",
36 | "scale" : "2x",
37 | "size" : "128x128"
38 | },
39 | {
40 | "filename" : "Icon-256.png",
41 | "idiom" : "mac",
42 | "scale" : "1x",
43 | "size" : "256x256"
44 | },
45 | {
46 | "filename" : "Icon-512.png",
47 | "idiom" : "mac",
48 | "scale" : "2x",
49 | "size" : "256x256"
50 | },
51 | {
52 | "filename" : "Icon-513.png",
53 | "idiom" : "mac",
54 | "scale" : "1x",
55 | "size" : "512x512"
56 | },
57 | {
58 | "filename" : "Icon-1024.png",
59 | "idiom" : "mac",
60 | "scale" : "2x",
61 | "size" : "512x512"
62 | }
63 | ],
64 | "info" : {
65 | "author" : "xcode",
66 | "version" : 1
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/AppIcon.appiconset/Icon-1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/AppIcon.appiconset/Icon-1024.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/AppIcon.appiconset/Icon-128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/AppIcon.appiconset/Icon-128.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/AppIcon.appiconset/Icon-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/AppIcon.appiconset/Icon-16.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/AppIcon.appiconset/Icon-16@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/AppIcon.appiconset/Icon-16@2x.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/AppIcon.appiconset/Icon-256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/AppIcon.appiconset/Icon-256.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/AppIcon.appiconset/Icon-257.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/AppIcon.appiconset/Icon-257.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/AppIcon.appiconset/Icon-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/AppIcon.appiconset/Icon-32.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/AppIcon.appiconset/Icon-32@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/AppIcon.appiconset/Icon-32@2x.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/AppIcon.appiconset/Icon-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/AppIcon.appiconset/Icon-512.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/AppIcon.appiconset/Icon-513.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/AppIcon.appiconset/Icon-513.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/copy.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "copy.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/copy.imageset/copy.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/copy.imageset/copy.pdf
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/failed.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Group 98.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "appearances" : [
10 | {
11 | "appearance" : "luminosity",
12 | "value" : "dark"
13 | }
14 | ],
15 | "filename" : "Group 106.png",
16 | "idiom" : "universal",
17 | "scale" : "1x"
18 | },
19 | {
20 | "filename" : "Group 98@2x.png",
21 | "idiom" : "universal",
22 | "scale" : "2x"
23 | },
24 | {
25 | "appearances" : [
26 | {
27 | "appearance" : "luminosity",
28 | "value" : "dark"
29 | }
30 | ],
31 | "filename" : "Group 106@2x.png",
32 | "idiom" : "universal",
33 | "scale" : "2x"
34 | },
35 | {
36 | "filename" : "Group 98@3x.png",
37 | "idiom" : "universal",
38 | "scale" : "3x"
39 | },
40 | {
41 | "appearances" : [
42 | {
43 | "appearance" : "luminosity",
44 | "value" : "dark"
45 | }
46 | ],
47 | "filename" : "Group 106@3x.png",
48 | "idiom" : "universal",
49 | "scale" : "3x"
50 | }
51 | ],
52 | "info" : {
53 | "author" : "xcode",
54 | "version" : 1
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/failed.imageset/Group 106.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/failed.imageset/Group 106.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/failed.imageset/Group 106@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/failed.imageset/Group 106@2x.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/failed.imageset/Group 106@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/failed.imageset/Group 106@3x.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/failed.imageset/Group 98.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/failed.imageset/Group 98.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/failed.imageset/Group 98@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/failed.imageset/Group 98@2x.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/failed.imageset/Group 98@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/failed.imageset/Group 98@3x.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/failed_passed.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Group 101.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "appearances" : [
10 | {
11 | "appearance" : "luminosity",
12 | "value" : "dark"
13 | }
14 | ],
15 | "filename" : "Group 109.png",
16 | "idiom" : "universal",
17 | "scale" : "1x"
18 | },
19 | {
20 | "filename" : "Group 101@2x.png",
21 | "idiom" : "universal",
22 | "scale" : "2x"
23 | },
24 | {
25 | "appearances" : [
26 | {
27 | "appearance" : "luminosity",
28 | "value" : "dark"
29 | }
30 | ],
31 | "filename" : "Group 109@2x.png",
32 | "idiom" : "universal",
33 | "scale" : "2x"
34 | },
35 | {
36 | "filename" : "Group 101@3x.png",
37 | "idiom" : "universal",
38 | "scale" : "3x"
39 | },
40 | {
41 | "appearances" : [
42 | {
43 | "appearance" : "luminosity",
44 | "value" : "dark"
45 | }
46 | ],
47 | "filename" : "Group 109@3x.png",
48 | "idiom" : "universal",
49 | "scale" : "3x"
50 | }
51 | ],
52 | "info" : {
53 | "author" : "xcode",
54 | "version" : 1
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/failed_passed.imageset/Group 101.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/failed_passed.imageset/Group 101.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/failed_passed.imageset/Group 101@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/failed_passed.imageset/Group 101@2x.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/failed_passed.imageset/Group 101@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/failed_passed.imageset/Group 101@3x.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/failed_passed.imageset/Group 109.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/failed_passed.imageset/Group 109.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/failed_passed.imageset/Group 109@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/failed_passed.imageset/Group 109@2x.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/failed_passed.imageset/Group 109@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/failed_passed.imageset/Group 109@3x.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/initial.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Group 99.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "appearances" : [
10 | {
11 | "appearance" : "luminosity",
12 | "value" : "dark"
13 | }
14 | ],
15 | "filename" : "Group 107.png",
16 | "idiom" : "universal",
17 | "scale" : "1x"
18 | },
19 | {
20 | "filename" : "Group 99@2x.png",
21 | "idiom" : "universal",
22 | "scale" : "2x"
23 | },
24 | {
25 | "appearances" : [
26 | {
27 | "appearance" : "luminosity",
28 | "value" : "dark"
29 | }
30 | ],
31 | "filename" : "Group 107@2x.png",
32 | "idiom" : "universal",
33 | "scale" : "2x"
34 | },
35 | {
36 | "filename" : "Group 99@3x.png",
37 | "idiom" : "universal",
38 | "scale" : "3x"
39 | },
40 | {
41 | "appearances" : [
42 | {
43 | "appearance" : "luminosity",
44 | "value" : "dark"
45 | }
46 | ],
47 | "filename" : "Group 107@3x.png",
48 | "idiom" : "universal",
49 | "scale" : "3x"
50 | }
51 | ],
52 | "info" : {
53 | "author" : "xcode",
54 | "version" : 1
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/initial.imageset/Group 107.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/initial.imageset/Group 107.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/initial.imageset/Group 107@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/initial.imageset/Group 107@2x.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/initial.imageset/Group 107@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/initial.imageset/Group 107@3x.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/initial.imageset/Group 99.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/initial.imageset/Group 99.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/initial.imageset/Group 99@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/initial.imageset/Group 99@2x.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/initial.imageset/Group 99@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/initial.imageset/Group 99@3x.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/passed.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Group 96.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "appearances" : [
10 | {
11 | "appearance" : "luminosity",
12 | "value" : "dark"
13 | }
14 | ],
15 | "filename" : "Group 104.png",
16 | "idiom" : "universal",
17 | "scale" : "1x"
18 | },
19 | {
20 | "filename" : "Group 96@2x.png",
21 | "idiom" : "universal",
22 | "scale" : "2x"
23 | },
24 | {
25 | "appearances" : [
26 | {
27 | "appearance" : "luminosity",
28 | "value" : "dark"
29 | }
30 | ],
31 | "filename" : "Group 104@2x.png",
32 | "idiom" : "universal",
33 | "scale" : "2x"
34 | },
35 | {
36 | "filename" : "Group 96@3x.png",
37 | "idiom" : "universal",
38 | "scale" : "3x"
39 | },
40 | {
41 | "appearances" : [
42 | {
43 | "appearance" : "luminosity",
44 | "value" : "dark"
45 | }
46 | ],
47 | "filename" : "Group 104@3x.png",
48 | "idiom" : "universal",
49 | "scale" : "3x"
50 | }
51 | ],
52 | "info" : {
53 | "author" : "xcode",
54 | "version" : 1
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/passed.imageset/Group 104.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/passed.imageset/Group 104.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/passed.imageset/Group 104@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/passed.imageset/Group 104@2x.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/passed.imageset/Group 104@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/passed.imageset/Group 104@3x.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/passed.imageset/Group 96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/passed.imageset/Group 96.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/passed.imageset/Group 96@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/passed.imageset/Group 96@2x.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/passed.imageset/Group 96@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/passed.imageset/Group 96@3x.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/pending.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Group 97.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "appearances" : [
10 | {
11 | "appearance" : "luminosity",
12 | "value" : "dark"
13 | }
14 | ],
15 | "filename" : "Group 105.png",
16 | "idiom" : "universal",
17 | "scale" : "1x"
18 | },
19 | {
20 | "filename" : "Group 97@2x.png",
21 | "idiom" : "universal",
22 | "scale" : "2x"
23 | },
24 | {
25 | "appearances" : [
26 | {
27 | "appearance" : "luminosity",
28 | "value" : "dark"
29 | }
30 | ],
31 | "filename" : "Group 105@2x.png",
32 | "idiom" : "universal",
33 | "scale" : "2x"
34 | },
35 | {
36 | "filename" : "Group 97@3x.png",
37 | "idiom" : "universal",
38 | "scale" : "3x"
39 | },
40 | {
41 | "appearances" : [
42 | {
43 | "appearance" : "luminosity",
44 | "value" : "dark"
45 | }
46 | ],
47 | "filename" : "Group 105@3x.png",
48 | "idiom" : "universal",
49 | "scale" : "3x"
50 | }
51 | ],
52 | "info" : {
53 | "author" : "xcode",
54 | "version" : 1
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/pending.imageset/Group 105.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/pending.imageset/Group 105.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/pending.imageset/Group 105@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/pending.imageset/Group 105@2x.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/pending.imageset/Group 105@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/pending.imageset/Group 105@3x.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/pending.imageset/Group 97.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/pending.imageset/Group 97.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/pending.imageset/Group 97@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/pending.imageset/Group 97@2x.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/pending.imageset/Group 97@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/pending.imageset/Group 97@3x.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/pending_failed.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Group 95.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "appearances" : [
10 | {
11 | "appearance" : "luminosity",
12 | "value" : "dark"
13 | }
14 | ],
15 | "filename" : "Group 103.png",
16 | "idiom" : "universal",
17 | "scale" : "1x"
18 | },
19 | {
20 | "filename" : "Group 95@2x.png",
21 | "idiom" : "universal",
22 | "scale" : "2x"
23 | },
24 | {
25 | "appearances" : [
26 | {
27 | "appearance" : "luminosity",
28 | "value" : "dark"
29 | }
30 | ],
31 | "filename" : "Group 103@2x.png",
32 | "idiom" : "universal",
33 | "scale" : "2x"
34 | },
35 | {
36 | "filename" : "Group 95@3x.png",
37 | "idiom" : "universal",
38 | "scale" : "3x"
39 | },
40 | {
41 | "appearances" : [
42 | {
43 | "appearance" : "luminosity",
44 | "value" : "dark"
45 | }
46 | ],
47 | "filename" : "Group 103@3x.png",
48 | "idiom" : "universal",
49 | "scale" : "3x"
50 | }
51 | ],
52 | "info" : {
53 | "author" : "xcode",
54 | "version" : 1
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/pending_failed.imageset/Group 103.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/pending_failed.imageset/Group 103.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/pending_failed.imageset/Group 103@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/pending_failed.imageset/Group 103@2x.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/pending_failed.imageset/Group 103@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/pending_failed.imageset/Group 103@3x.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/pending_failed.imageset/Group 95.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/pending_failed.imageset/Group 95.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/pending_failed.imageset/Group 95@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/pending_failed.imageset/Group 95@2x.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/pending_failed.imageset/Group 95@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/pending_failed.imageset/Group 95@3x.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/pending_failed_passed.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Group 110.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "appearances" : [
10 | {
11 | "appearance" : "luminosity",
12 | "value" : "dark"
13 | }
14 | ],
15 | "filename" : "Group 102.png",
16 | "idiom" : "universal",
17 | "scale" : "1x"
18 | },
19 | {
20 | "filename" : "Group 102@2x-1.png",
21 | "idiom" : "universal",
22 | "scale" : "2x"
23 | },
24 | {
25 | "appearances" : [
26 | {
27 | "appearance" : "luminosity",
28 | "value" : "dark"
29 | }
30 | ],
31 | "filename" : "Group 102@2x.png",
32 | "idiom" : "universal",
33 | "scale" : "2x"
34 | },
35 | {
36 | "filename" : "Group 110@3x.png",
37 | "idiom" : "universal",
38 | "scale" : "3x"
39 | },
40 | {
41 | "appearances" : [
42 | {
43 | "appearance" : "luminosity",
44 | "value" : "dark"
45 | }
46 | ],
47 | "filename" : "Group 110@3x-1.png",
48 | "idiom" : "universal",
49 | "scale" : "3x"
50 | }
51 | ],
52 | "info" : {
53 | "author" : "xcode",
54 | "version" : 1
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/pending_failed_passed.imageset/Group 102.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/pending_failed_passed.imageset/Group 102.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/pending_failed_passed.imageset/Group 102@2x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/pending_failed_passed.imageset/Group 102@2x-1.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/pending_failed_passed.imageset/Group 102@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/pending_failed_passed.imageset/Group 102@2x.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/pending_failed_passed.imageset/Group 110.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/pending_failed_passed.imageset/Group 110.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/pending_failed_passed.imageset/Group 110@3x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/pending_failed_passed.imageset/Group 110@3x-1.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/pending_failed_passed.imageset/Group 110@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/pending_failed_passed.imageset/Group 110@3x.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/pending_passed.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Group 100.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "appearances" : [
10 | {
11 | "appearance" : "luminosity",
12 | "value" : "dark"
13 | }
14 | ],
15 | "filename" : "Group 108.png",
16 | "idiom" : "universal",
17 | "scale" : "1x"
18 | },
19 | {
20 | "filename" : "Group 100@2x.png",
21 | "idiom" : "universal",
22 | "scale" : "2x"
23 | },
24 | {
25 | "appearances" : [
26 | {
27 | "appearance" : "luminosity",
28 | "value" : "dark"
29 | }
30 | ],
31 | "filename" : "Group 108@2x.png",
32 | "idiom" : "universal",
33 | "scale" : "2x"
34 | },
35 | {
36 | "filename" : "Group 100@3x.png",
37 | "idiom" : "universal",
38 | "scale" : "3x"
39 | },
40 | {
41 | "appearances" : [
42 | {
43 | "appearance" : "luminosity",
44 | "value" : "dark"
45 | }
46 | ],
47 | "filename" : "Group 108@3x.png",
48 | "idiom" : "universal",
49 | "scale" : "3x"
50 | }
51 | ],
52 | "info" : {
53 | "author" : "xcode",
54 | "version" : 1
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/pending_passed.imageset/Group 100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/pending_passed.imageset/Group 100.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/pending_passed.imageset/Group 100@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/pending_passed.imageset/Group 100@2x.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/pending_passed.imageset/Group 100@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/pending_passed.imageset/Group 100@3x.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/pending_passed.imageset/Group 108.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/pending_passed.imageset/Group 108.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/pending_passed.imageset/Group 108@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/pending_passed.imageset/Group 108@2x.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/pending_passed.imageset/Group 108@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/pending_passed.imageset/Group 108@3x.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/simple_failed.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Union.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "Union@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "Union@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/simple_failed.imageset/Union.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/simple_failed.imageset/Union.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/simple_failed.imageset/Union@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/simple_failed.imageset/Union@2x.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/simple_failed.imageset/Union@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/simple_failed.imageset/Union@3x.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/simple_initial.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Union-4.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "appearances" : [
10 | {
11 | "appearance" : "luminosity",
12 | "value" : "dark"
13 | }
14 | ],
15 | "filename" : "Union-3.png",
16 | "idiom" : "universal",
17 | "scale" : "1x"
18 | },
19 | {
20 | "filename" : "Union@2x-4.png",
21 | "idiom" : "universal",
22 | "scale" : "2x"
23 | },
24 | {
25 | "appearances" : [
26 | {
27 | "appearance" : "luminosity",
28 | "value" : "dark"
29 | }
30 | ],
31 | "filename" : "Union@2x-3.png",
32 | "idiom" : "universal",
33 | "scale" : "2x"
34 | },
35 | {
36 | "filename" : "Union@3x-4.png",
37 | "idiom" : "universal",
38 | "scale" : "3x"
39 | },
40 | {
41 | "appearances" : [
42 | {
43 | "appearance" : "luminosity",
44 | "value" : "dark"
45 | }
46 | ],
47 | "filename" : "Union@3x-3.png",
48 | "idiom" : "universal",
49 | "scale" : "3x"
50 | }
51 | ],
52 | "info" : {
53 | "author" : "xcode",
54 | "version" : 1
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/simple_initial.imageset/Union-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/simple_initial.imageset/Union-3.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/simple_initial.imageset/Union-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/simple_initial.imageset/Union-4.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/simple_initial.imageset/Union@2x-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/simple_initial.imageset/Union@2x-3.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/simple_initial.imageset/Union@2x-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/simple_initial.imageset/Union@2x-4.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/simple_initial.imageset/Union@3x-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/simple_initial.imageset/Union@3x-3.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/simple_initial.imageset/Union@3x-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/simple_initial.imageset/Union@3x-4.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/simple_passed.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Union-2.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "Union@2x-2.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "Union@3x-2.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/simple_passed.imageset/Union-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/simple_passed.imageset/Union-2.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/simple_passed.imageset/Union@2x-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/simple_passed.imageset/Union@2x-2.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/simple_passed.imageset/Union@3x-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/simple_passed.imageset/Union@3x-2.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/simple_pending.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Union-1.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "Union@2x-1.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "Union@3x-1.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/simple_pending.imageset/Union-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/simple_pending.imageset/Union-1.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/simple_pending.imageset/Union@2x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/simple_pending.imageset/Union@2x-1.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Assets.xcassets/simple_pending.imageset/Union@3x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/macos/cidemon-macOS/Assets.xcassets/simple_pending.imageset/Union@3x-1.png
--------------------------------------------------------------------------------
/macos/cidemon-macOS/CIDemonNative.m:
--------------------------------------------------------------------------------
1 | //Author: Oscar Franco, created on: 25.05.20
2 |
3 | #import "React/RCTBridgeModule.h"
4 |
5 | @interface RCT_EXTERN_MODULE(CIDemonNative, NSObject)
6 |
7 | RCT_EXTERN_METHOD(setStatusButtonText: (NSInteger)failed running:(NSInteger)running passed:(NSInteger)passed useSimpleIcon:(BOOL)useSimpleIcon)
8 |
9 | RCT_EXTERN_METHOD(securelyStore: (NSString)key payload:(NSString)payload resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
10 |
11 | RCT_EXTERN_METHOD(securelyRetrieve: (NSString)key resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
12 |
13 | RCT_EXTERN_METHOD(sendNotification: (NSString)title payload:(NSString)payload url:(NSString)url)
14 |
15 | RCT_EXTERN_METHOD(applyAutoLauncher: (BOOL)startAtLogin)
16 |
17 | RCT_EXTERN_METHOD(closeApp)
18 |
19 | RCT_EXTERN_METHOD(requestReview)
20 |
21 | RCT_EXTERN_METHOD(showShareMenu: (NSInteger)x y:(NSInteger)y text:(NSString) text)
22 |
23 | @end
24 |
--------------------------------------------------------------------------------
/macos/cidemon-macOS/CIDemonNative.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import KeychainAccess
3 | import ServiceManagement
4 | import StoreKit
5 | import LaunchAtLogin
6 |
7 | extension Notification.Name {
8 | static let killLauncher = Notification.Name("killLauncher")
9 | }
10 |
11 | private let keychain = Keychain(service: "Tempomat Keychain")
12 |
13 | @objc(CIDemonNative)
14 | class CIDemonNative: NSObject, NSSharingServicePickerDelegate {
15 |
16 | @objc static func requiresMainQueueSetup() -> Bool {
17 | return true
18 | }
19 |
20 |
21 | @objc func setStatusButtonText(_ failed: NSInteger, running: NSInteger, passed: NSInteger, useSimpleIcon: Bool) {
22 | DispatchQueue.main.async {
23 | let appDelegate = NSApp.delegate as! AppDelegate
24 | appDelegate.setStatusText(failed: failed, running: running, passed: passed, useSimpleIcon: useSimpleIcon)
25 | }
26 | }
27 |
28 |
29 | @objc func securelyStore(_ key: NSString, payload: NSString, resolver: RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock) {
30 | keychain[key as String] = payload as String
31 | resolver(true)
32 | }
33 |
34 |
35 | @objc func securelyRetrieve(_ key: NSString, resolver resolve: RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock) {
36 | let value = keychain[key as String]
37 | return resolve(value)
38 | }
39 |
40 |
41 | @objc func sendNotification(_ title: NSString, payload: NSString, url: NSString) {
42 | let notification = NSUserNotification()
43 | notification.identifier = UUID().uuidString
44 | notification.subtitle = payload as String
45 | notification.title = title as String
46 | notification.userInfo = [
47 | "url": url
48 | ]
49 | notification.soundName = "default"
50 |
51 | NSUserNotificationCenter.default.deliver(notification)
52 | }
53 |
54 | @objc func applyAutoLauncher(_ startAtLogin: Bool) {
55 | LaunchAtLogin.isEnabled = startAtLogin
56 | }
57 |
58 | @objc
59 | func closeApp() {
60 | DispatchQueue.main.async {
61 | let appDelegate = NSApp.delegate as? AppDelegate
62 | appDelegate?.closeApp()
63 | }
64 | }
65 |
66 | @objc
67 | func requestReview() {
68 | SKStoreReviewController.requestReview()
69 | }
70 |
71 | @objc
72 | func showShareMenu(_ x: NSInteger, y: NSInteger, text: NSString) {
73 | let sharingPicker = NSSharingServicePicker(items: [text])
74 | sharingPicker.delegate = self
75 |
76 | DispatchQueue.main.async {
77 | let appDelegate = NSApp.delegate as? AppDelegate
78 | let window = appDelegate?.getWindowObject()
79 | if window != nil {
80 | sharingPicker.show(relativeTo: NSRect(x: x, y: y, width: 1, height: 1), of: window!, preferredEdge: .minY)
81 | }
82 | }
83 | }
84 |
85 | func sharingServicePicker(_ sharingServicePicker: NSSharingServicePicker, sharingServicesForItems items: [Any], proposedSharingServices proposedServices: [NSSharingService]) -> [NSSharingService] {
86 | guard let image = NSImage(named: NSImage.Name("copy")) else {
87 | return proposedServices
88 | }
89 |
90 | var share = proposedServices
91 | let customService = NSSharingService(title: "Copy Text", image: image, alternateImage: image, handler: {
92 | if let text = items.first as? String {
93 | NSPasteboard.general.clearContents()
94 | NSPasteboard.general.setString(text, forType: .string)
95 | }
96 | })
97 |
98 | share.insert(customService, at: 0)
99 |
100 | return share
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/macos/cidemon-macOS/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ATSApplicationFontsPath
6 | Fonts
7 | CFBundleDevelopmentRegion
8 | $(DEVELOPMENT_LANGUAGE)
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIconFile
12 |
13 | CFBundleIdentifier
14 | $(PRODUCT_BUNDLE_IDENTIFIER)
15 | CFBundleInfoDictionaryVersion
16 | 6.0
17 | CFBundleName
18 | $(PRODUCT_NAME)
19 | CFBundlePackageType
20 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
21 | CFBundleShortVersionString
22 | $(MARKETING_VERSION)
23 | CFBundleVersion
24 | $(CURRENT_PROJECT_VERSION)
25 | ITSAppUsesNonExemptEncryption
26 |
27 | LSApplicationCategoryType
28 | public.app-category.developer-tools
29 | LSMinimumSystemVersion
30 | $(MACOSX_DEPLOYMENT_TARGET)
31 | LSUIElement
32 |
33 | NSAppTransportSecurity
34 |
35 | NSAllowsArbitraryLoads
36 |
37 | NSExceptionDomains
38 |
39 | localhost
40 |
41 | NSExceptionAllowsInsecureHTTPLoads
42 |
43 |
44 |
45 |
46 | NSMainStoryboardFile
47 | Main
48 | NSPrincipalClass
49 | NSApplication
50 | NSSupportsAutomaticTermination
51 |
52 | NSSupportsSuddenTermination
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/macos/cidemon-macOS/cidemon-macOS-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | //
2 | // Use this file to import your target's public headers that you would like to expose to Swift.
3 | //
4 |
5 | #import
6 | #import
7 | #import
8 | #import
9 | #import
10 | #import
11 | #import
12 |
--------------------------------------------------------------------------------
/macos/cidemon-macOS/cidemon.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.cs.allow-jit
8 |
9 | com.apple.security.network.client
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/macos/cidemon-macOS/lib/MyNSWindowDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MyNSWindowDelegate.swift
3 | // Messer
4 | //
5 | // Created by Oscar on 07.12.21.
6 | //
7 |
8 | import Foundation
9 | import AppKit
10 |
11 | class MyNSWindowDelegate: NSObject, NSWindowDelegate {
12 | var resignHandler: () -> Void
13 |
14 | init(resignHandler: @escaping () -> Void) {
15 | self.resignHandler = resignHandler
16 | }
17 |
18 | func windowDidResignKey(_ notification: Notification) {
19 | resignHandler()
20 | }
21 |
22 | func windowDidResignMain(_ notification: Notification) {
23 | resignHandler()
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/macos/cidemon-macOS/lib/ReactNativeBridge.swift:
--------------------------------------------------------------------------------
1 | //Author: Oscar Franco, created on: 04.07.20
2 |
3 | import Foundation
4 |
5 | class ReactNativeBridge {
6 | let bridge: RCTBridge
7 |
8 | init() {
9 | bridge = RCTBridge(delegate: ReactNativeBridgeDelegate(), launchOptions: nil)
10 | }
11 | }
12 |
13 | class ReactNativeBridgeDelegate: NSObject, RCTBridgeDelegate {
14 |
15 | func sourceURL(for bridge: RCTBridge!) -> URL! {
16 | let jsCodeLocation: URL
17 |
18 | #if DEBUG
19 | jsCodeLocation = RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index", fallbackResource:nil)
20 | #else
21 | jsCodeLocation = Bundle.main.url(forResource: "main", withExtension: "jsbundle")!
22 | #endif
23 | return jsCodeLocation
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/macos/cidemon-macOS/lib/ReactViewController.swift:
--------------------------------------------------------------------------------
1 | //Author: Oscar Franco, created on: 04.07.20
2 |
3 | import Foundation
4 |
5 | class ReactViewController: NSViewController {
6 | init(moduleName: String, bridge: RCTBridge) {
7 | super.init(nibName: nil, bundle: nil)
8 |
9 | view = RCTRootView(bridge: bridge, moduleName: moduleName, initialProperties: nil)
10 | }
11 |
12 | required init?(coder: NSCoder) {
13 | fatalError("init(coder:) has not been implemented")
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/macos/cidemon.xcodeproj/xcshareddata/xcschemes/cidemon-iOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
45 |
51 |
52 |
53 |
54 |
60 |
62 |
68 |
69 |
70 |
71 |
73 |
74 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/macos/cidemon.xcodeproj/xcshareddata/xcschemes/cidemon-macOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
45 |
51 |
52 |
53 |
54 |
60 |
62 |
68 |
69 |
70 |
71 |
73 |
74 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/macos/cidemon.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/macos/cidemon.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/macos/cidemon.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "LaunchAtLogin",
6 | "repositoryURL": "git@github.com:sindresorhus/LaunchAtLogin.git",
7 | "state": {
8 | "branch": null,
9 | "revision": "6b16bcdf7d45a9d76a768a5c4912dde925cf0e95",
10 | "version": "4.1.0"
11 | }
12 | }
13 | ]
14 | },
15 | "version": 1
16 | }
17 |
--------------------------------------------------------------------------------
/metro.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Metro configuration for React Native
3 | * https://github.com/facebook/react-native
4 | *
5 | * @format
6 | */
7 |
8 | module.exports = {
9 | transformer: {
10 | getTransformOptions: async () => ({
11 | transform: {
12 | experimentalImportSupport: false,
13 | inlineRequires: false,
14 | },
15 | }),
16 | },
17 | };
18 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cidemon",
3 | "version": "3.0.26",
4 | "private": true,
5 | "scripts": {
6 | "android": "react-native run-android",
7 | "ios": "react-native run-ios",
8 | "ios:install": "cd ios && pod install",
9 | "postinstall": "patch-package",
10 | "start": "react-native start",
11 | "test": "jest --forceExit",
12 | "test:ci": "jest --testLocationInResults --ci --outputFile=test_results.json --json",
13 | "macos": "node node_modules/react-native-macos/local-cli/cli.js run-macos",
14 | "macos:start": "node node_modules/react-native-macos/local-cli/cli.js start --use-react-native-macos",
15 | "macos:install": "cd macos && pod install",
16 | "bump": "./scripts/bumpVersion.sh"
17 | },
18 | "dependencies": {
19 | "@react-native-picker/picker": "^1.9.8",
20 | "@react-navigation/bottom-tabs": "^6.0.7",
21 | "@react-navigation/native": "^6.0.4",
22 | "@react-navigation/stack": "^6.0.9",
23 | "autoprefixer": "^10.3.4",
24 | "axios": "^0.21.1",
25 | "base-64": "^1.0.0",
26 | "classnames": "^2.3.1",
27 | "github-api": "3.4.0",
28 | "luxon": "^1.24.1",
29 | "mobx": "6.0.4",
30 | "mobx-react-lite": "3.1.6",
31 | "postcss": "^8.3.6",
32 | "promise.allsettled": "^1.0.2",
33 | "react": "16.13.1",
34 | "react-native": "0.63.4",
35 | "react-native-get-random-values": "^1.6.0",
36 | "react-native-macos": "0.63.38",
37 | "react-native-safe-area-context": "^3.3.2",
38 | "react-native-screens": "^3.8.0",
39 | "react-native-vector-icons": "6.6.0",
40 | "tailwind-rn": "^3.0.1",
41 | "tailwindcss": "^2.2.15",
42 | "uuid": "^8.3.2"
43 | },
44 | "devDependencies": {
45 | "@babel/core": "^7.8.4",
46 | "@babel/runtime": "^7.8.4",
47 | "@types/jest": "^26.0.22",
48 | "@types/luxon": "^1.24.1",
49 | "@types/react-native": "^0.63.42",
50 | "@types/react-native-vector-icons": "^6.4.6",
51 | "@types/uuid": "^8.3.0",
52 | "babel-jest": "^25.1.0",
53 | "babel-plugin-module-resolver": "^4.1.0",
54 | "jest": "^25.1.0",
55 | "metro-react-native-babel-preset": "^0.59.0",
56 | "patch-package": "^6.4.7",
57 | "prettier": "2.2.1",
58 | "react-test-renderer": "16.13.1",
59 | "typescript": "4.1.3"
60 | },
61 | "jest": {
62 | "preset": "react-native"
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/scripts/bumpVersion.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -ex
4 |
5 | npm --no-git-tag-version version patch
6 |
7 | PROJECT_DIR="macos/cidemon-macOS"
8 | INFOPLIST_FILE="Info.plist"
9 | INFOPLIST_DIR="${PROJECT_DIR}/${INFOPLIST_FILE}"
10 |
11 | PACKAGE_VERSION=$(cat package.json | grep version | head -1 | awk -F: '{ print $2 }' | sed 's/[\",]//g' | tr -d '[[:space:]]')
12 |
13 | BUILD_NUMBER=$(/usr/libexec/PlistBuddy -c "Print CFBundleVersion" "${INFOPLIST_DIR}")
14 | BUILD_NUMBER=$(($BUILD_NUMBER + 1))
15 |
16 | # Update plist with new values
17 | /usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString ${PACKAGE_VERSION}" "${INFOPLIST_DIR}"
18 | /usr/libexec/PlistBuddy -c "Set :CFBundleVersion $BUILD_NUMBER" "${INFOPLIST_DIR}"
19 |
20 | git add .
21 |
22 | git commit -m "Bump version"
23 |
24 | git tag $PACKAGE_VERSION
25 |
26 | git push
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import 'react-native-get-random-values';
2 | import {decode, encode} from 'base-64';
3 | //@ts-ignore
4 | if (!global.btoa) {
5 | //@ts-ignore
6 | global.btoa = encode;
7 | }
8 |
9 | //@ts-ignore
10 | if (!global.atob) {
11 | //@ts-ignore
12 | global.atob = decode;
13 | }
14 |
15 | import 'Theme';
16 | import {NavigationContainer} from '@react-navigation/native';
17 | import {ToastContainer} from 'container';
18 | import React, {useEffect, useState} from 'react';
19 | import {LogBox, Platform} from 'react-native';
20 | import {createRootStore, IRootStore, StoreProvider} from 'Root.store';
21 | import {Routes} from 'Route';
22 |
23 | LogBox.ignoreLogs([
24 | `Warning: componentWillReceiveProps`,
25 | `Calling \`getNode`,
26 | `Module RNBackgroundFetch`,
27 | `RCTBridge required dispatch_sync`,
28 | `currentlyFocusedField is deprecated`,
29 | `focusTextInput must be called with a host component`,
30 | ]);
31 |
32 | const theme = {
33 | colors: {
34 | background: 'transparent',
35 | },
36 | dark: false,
37 | };
38 |
39 | export function App() {
40 | let [rootStore, setRootStore] = useState(null);
41 | useEffect(() => {
42 | createRootStore().then(setRootStore);
43 | }, []);
44 |
45 | if (!rootStore) {
46 | return null;
47 | }
48 |
49 | return (
50 |
51 | {/* @ts-expect-error */}
52 |
53 |
54 |
55 |
56 |
57 | );
58 | }
59 |
--------------------------------------------------------------------------------
/src/Assets.ts:
--------------------------------------------------------------------------------
1 | import circleci from './assets/icons/circleci.png';
2 | import appcenter from './assets/icons/appcenter.png';
3 | import bitrise from './assets/icons/bitrise.png';
4 | import github from './assets/icons/github.png';
5 | import travisci from './assets/icons/travisci.png';
6 | import gitlab from './assets/icons/gitlab.png';
7 | import github_bar from './assets/icons/github_bar.png';
8 | import logo from './assets/icons/logo.png';
9 | import profile from './assets/image/profile.jpeg';
10 |
11 | export const Images: Record = {
12 | circleci,
13 | appcenter,
14 | bitrise,
15 | github,
16 | travisci,
17 | gitlab,
18 | logo,
19 | github_bar,
20 | profile,
21 | };
22 |
--------------------------------------------------------------------------------
/src/Root.store.ts:
--------------------------------------------------------------------------------
1 | import {createContext, useContext} from 'react';
2 | import {createApiStore, createNodeStore, createUIStore} from 'store';
3 |
4 | export interface IRootStore {
5 | api: ReturnType;
6 | ui: ReturnType;
7 | node: Awaited>;
8 | }
9 |
10 | export let createRootStore = async (): Promise => {
11 | let store: any = {};
12 | store.ui = createUIStore(store);
13 | store.api = createApiStore(store);
14 | store.node = await createNodeStore(store);
15 |
16 | return store;
17 | };
18 |
19 | // Placeholder value for the context, is replaced as soon as store is re-hydrated on renderer.tsx
20 | export let StoreContext = createContext(null as any);
21 | export let StoreProvider = StoreContext.Provider;
22 | export let useStore = () => useContext(StoreContext);
23 |
--------------------------------------------------------------------------------
/src/Route.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {createStackNavigator} from '@react-navigation/stack';
3 | import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';
4 | import {
5 | NodeListContainer,
6 | AddTokenContainer,
7 | IgnoreConfigContainer,
8 | GithubActionsConfigContainer,
9 | GeneralConfigContainer,
10 | } from 'container';
11 | import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
12 | import FontAwesomeIcon from 'react-native-vector-icons/FontAwesome5';
13 | import {useDarkTheme} from 'lib';
14 | import {tw, cw} from 'tailwind';
15 |
16 | export type IRootStackParams = {
17 | Home: undefined;
18 | Configuration: undefined;
19 | AddToken: undefined;
20 | PollingIntervalConfig: undefined;
21 | };
22 |
23 | let RootStack = createStackNavigator();
24 | let ConfigurationTabStack = createBottomTabNavigator();
25 |
26 | const ConfigurationRoutes = () => {
27 | let isDarkMode = useDarkTheme();
28 |
29 | return (
30 |
42 | (
47 |
52 | ),
53 | title: ``,
54 | }}
55 | />
56 | (
61 |
66 | ),
67 | title: ``,
68 | }}
69 | />
70 | {/* (
75 |
80 | ),
81 | title: ``,
82 | }}
83 | /> */}
84 | (
89 |
94 | ),
95 | title: ``,
96 | }}
97 | />
98 |
99 | );
100 | };
101 |
102 | export const Routes = () => {
103 | let isDarkMode = useDarkTheme();
104 | return (
105 |
114 |
115 |
116 |
117 |
118 | );
119 | };
120 |
--------------------------------------------------------------------------------
/src/Theme.ts:
--------------------------------------------------------------------------------
1 | global.colors = {
2 | blue800: `#37474F`,
3 | gray1000: `#282929`,
4 | gray900: `#313232`,
5 | gray800: `#444`,
6 | gray500: `#555`,
7 | gray400: `#777`,
8 | gray300: `#AAA`,
9 | gray200: `#CCC`,
10 | gray100: `#DDD`,
11 | gray050: `#EEE`,
12 | gray010: `#F3F3F3`,
13 | blue600: `#008dd9`,
14 | blue500: `#00A4FF`,
15 | blue400: `#00A5F8`,
16 | blue300: `#00cbf8`,
17 | green500: `#27CA42`,
18 | red500: `#FC4943`,
19 | orange500: `#FFBE30`,
20 | }
21 |
22 | global.metrics = {
23 | imgSmall: 18,
24 | imgMedium: 24,
25 | imgLarge: 32,
26 | pxs: 3,
27 | ps: 6,
28 | pm: 8,
29 | pxm: 12,
30 | pl: 16,
31 | ts: 14,
32 | tm: 16,
33 | tl: 18,
34 | }
35 |
--------------------------------------------------------------------------------
/src/assets/icons/appcenter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/src/assets/icons/appcenter.png
--------------------------------------------------------------------------------
/src/assets/icons/bitrise.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/src/assets/icons/bitrise.png
--------------------------------------------------------------------------------
/src/assets/icons/circleci.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/src/assets/icons/circleci.png
--------------------------------------------------------------------------------
/src/assets/icons/github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/src/assets/icons/github.png
--------------------------------------------------------------------------------
/src/assets/icons/github_bar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/src/assets/icons/github_bar.png
--------------------------------------------------------------------------------
/src/assets/icons/github_bar@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/src/assets/icons/github_bar@2x.png
--------------------------------------------------------------------------------
/src/assets/icons/github_bar@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/src/assets/icons/github_bar@3x.png
--------------------------------------------------------------------------------
/src/assets/icons/gitlab.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/src/assets/icons/gitlab.png
--------------------------------------------------------------------------------
/src/assets/icons/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/src/assets/icons/logo.png
--------------------------------------------------------------------------------
/src/assets/icons/travisci.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/src/assets/icons/travisci.png
--------------------------------------------------------------------------------
/src/assets/image/profile.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/src/assets/image/profile.jpeg
--------------------------------------------------------------------------------
/src/component/Divider.component.tsx:
--------------------------------------------------------------------------------
1 | import {useDynamic} from 'lib';
2 | import React from 'react';
3 | import {View} from 'react-native';
4 | import tw from 'tailwind-rn';
5 |
6 | interface IProps {
7 | width?: number | string;
8 | alignSelf?:
9 | | `auto`
10 | | `flex-start`
11 | | `flex-end`
12 | | `center`
13 | | `stretch`
14 | | `baseline`
15 | | undefined;
16 | style?: any;
17 | }
18 |
19 | export const Divider = ({width = `100%`, alignSelf, style}: IProps) => {
20 | const dynamic = useDynamic();
21 | return (
22 |
29 | );
30 | };
31 |
--------------------------------------------------------------------------------
/src/component/EmptyNodes.component.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {View, Text, StyleSheet, Image, ActivityIndicator} from 'react-native';
3 | import {TempoButton} from './TempoButton.component';
4 | import {Images} from 'Assets';
5 | import {observer} from 'mobx-react-lite';
6 | import {useStore} from 'Root.store';
7 | import {tw} from 'tailwind';
8 |
9 | interface IProps {
10 | onAddToken: () => void;
11 | }
12 |
13 | export const EmptyNodesComponent = observer(({onAddToken}: IProps) => {
14 | const root = useStore();
15 | const {fetching} = root.node;
16 |
17 | return (
18 |
19 | {!fetching && !!root.node.tokens.length && (
20 | Something might have gone wrong :(
21 | )}
22 |
23 | {!root.node.tokens.length && (
24 |
29 | )}
30 |
31 | {fetching && Fetching}
32 |
33 | {!root.node.tokens.length && (
34 |
40 | )}
41 |
42 | );
43 | });
44 |
45 | const styles = StyleSheet.create({
46 | container: {
47 | height: `100%`,
48 | alignItems: `center`,
49 | justifyContent: `center`,
50 | },
51 | title: {fontWeight: `bold`, paddingVertical: 10},
52 | });
53 |
--------------------------------------------------------------------------------
/src/component/NodeRow.component.tsx:
--------------------------------------------------------------------------------
1 | import {Images} from 'Assets';
2 | import {TINT_MAPPING, useBoolean, useDarkTheme, useDynamic} from 'lib';
3 | import {observer} from 'mobx-react-lite';
4 | import React, {useEffect, useState} from 'react';
5 | import {Image, StyleSheet, Text, TouchableOpacity, View} from 'react-native';
6 | import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
7 | import {tw} from 'tailwind';
8 | import {Row} from './Row.component';
9 |
10 | let captureBranchRegex = /(.*)(\[.*\])(.*)/;
11 |
12 | interface IProps {
13 | node: INode;
14 | onPress: () => void;
15 | style?: any;
16 | selected?: boolean;
17 | }
18 |
19 | export const NodeRow = observer(({node, onPress, style, selected}: IProps) => {
20 | const [hovered, onHover, offHover] = useBoolean();
21 | const [tokens, setTokens] = useState(null);
22 | const dynamic = useDynamic();
23 | const isDark = useDarkTheme();
24 |
25 | useEffect(() => {
26 | const newTokens = captureBranchRegex.exec(node.label);
27 | if (newTokens && newTokens.length > 1) {
28 | setTokens(newTokens);
29 | }
30 | }, [node.label]);
31 |
32 | let icon = Images[`${node.source.toLowerCase()}`];
33 |
34 | let tintColor = TINT_MAPPING[node.status];
35 |
36 | let textColor = dynamic(`text-gray-100`, `text-gray-800`);
37 | let hoverColor = dynamic(`bg-gray-700`, `bg-blue-100`);
38 |
39 | let text;
40 |
41 | if (tokens) {
42 | text = (
43 |
44 | {tokens[2].substring(1, tokens[2].length - 1)}
45 |
46 | );
47 | } else {
48 | text = {node.label};
49 | }
50 |
51 | return (
52 |
58 |
59 |
68 |
69 | {!!icon && (
70 |
71 |
72 |
73 | )}
74 |
75 | {text}
76 |
77 | {node.isAction && (
78 |
79 | ACTION
80 |
81 | )}
82 |
83 | {node.isPr && (
84 | <>
85 | PR
86 |
87 | >
88 | )}
89 |
90 | {node.isBranch && (
91 |
92 | )}
93 |
94 | {!!node.userAvatarUrl && (
95 |
99 | )}
100 |
101 |
102 | {/* Sub nodes */}
103 | {node.status === `failed` && node.subItems && (
104 |
105 | {node.subItems.map((subItem: ISubNode, index: number, items) => (
106 |
107 |
108 |
117 |
123 |
124 |
135 |
136 |
137 |
143 | {subItem.label}
144 | {!!subItem.extraLabel && (
145 | · {subItem.extraLabel}
146 | )}
147 |
148 |
149 | ))}
150 |
151 | )}
152 |
153 |
154 |
155 | );
156 | });
157 |
158 | const styles = StyleSheet.create({
159 | imageIcon: {
160 | height: 24,
161 | width: 24,
162 | resizeMode: `contain`,
163 | },
164 | icon: {
165 | marginLeft: global.metrics.ps,
166 | marginRight: global.metrics.pm,
167 | fontSize: global.metrics.tl,
168 | },
169 | statusIndicator: {
170 | width: 8,
171 | height: 8,
172 | borderRadius: 4,
173 | },
174 | });
175 |
--------------------------------------------------------------------------------
/src/component/Row.component.tsx:
--------------------------------------------------------------------------------
1 | import React, {memo} from 'react';
2 | import {View} from 'react-native';
3 |
4 | interface IProps {
5 | style?: any; // StyleProp is breaking here with paddingHorizontal... for some reason
6 | horizontal?:
7 | | `flex-end`
8 | | `flex-start`
9 | | `center`
10 | | `space-around`
11 | | `space-between`;
12 | wrap?: boolean;
13 | vertical?: `center` | `flex-start` | `flex-end` | `space-between`;
14 | children?: any;
15 | }
16 |
17 | export const Row = memo(
18 | ({
19 | style = {},
20 | horizontal = `flex-start`,
21 | vertical = `flex-start`,
22 | wrap,
23 | children,
24 | }: IProps) => {
25 | let innerStyle = {
26 | display: `flex`,
27 | flexDirection: `row`,
28 | justifyContent: horizontal,
29 | alignItems: vertical,
30 | flexWrap: wrap ? `wrap` : `nowrap`,
31 | };
32 |
33 | return {children};
34 | },
35 | );
36 |
--------------------------------------------------------------------------------
/src/component/Spacer.component.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import {View} from "react-native"
3 |
4 | export const Spacer = () =>
5 |
--------------------------------------------------------------------------------
/src/component/TempoButton.component.tsx:
--------------------------------------------------------------------------------
1 | import {useBoolean, useDarkTheme} from 'lib';
2 | import React, {useState} from 'react';
3 | import {
4 | TouchableOpacity,
5 | Text,
6 | StyleSheet,
7 | GestureResponderEvent,
8 | } from 'react-native';
9 | import {tw} from 'tailwind';
10 |
11 | interface IProps {
12 | onPress?: () => void;
13 | title?: string;
14 | primary?: boolean;
15 | secondary?: boolean;
16 | style?: any;
17 | children?: any;
18 | applyMargin?: boolean;
19 | onPressWithPosition?: (x: number, y: number) => void;
20 | }
21 |
22 | export const TempoButton = ({
23 | onPress,
24 | title,
25 | primary,
26 | secondary,
27 | style,
28 | children,
29 | applyMargin,
30 | onPressWithPosition,
31 | }: IProps) => {
32 | const [hovered, onHover, offHover] = useBoolean();
33 | const [coordinate, setCoordinate] = useState();
34 | const isDark = useDarkTheme();
35 | const _onPress = (e: GestureResponderEvent) => {
36 | e.preventDefault();
37 | e.stopPropagation();
38 | onPress?.();
39 | onPressWithPosition?.(
40 | coordinate.x + coordinate.width,
41 | coordinate.y + coordinate.height,
42 | );
43 | };
44 |
45 | if (secondary) {
46 | return (
47 | setCoordinate(e.nativeEvent.layout)}
50 | // @ts-ignore
51 | enableFocusRing={false}
52 | style={[styles.secondaryButton, applyMargin && styles.margin, style]}>
53 | {!!title && {title}}
54 | {children}
55 |
56 | );
57 | }
58 |
59 | if (primary) {
60 | return (
61 | {
63 | e.preventDefault();
64 | e.stopPropagation();
65 | }}
66 | onPress={_onPress}
67 | onPressOut={(e) => {
68 | e.preventDefault();
69 | e.stopPropagation();
70 | }}
71 | onLayout={(e) => setCoordinate(e.nativeEvent.layout)}
72 | // @ts-ignore
73 | enableFocusRing={false}
74 | onMouseEnter={onHover}
75 | onMouseLeave={offHover}
76 | style={[
77 | tw(
78 | {
79 | 'bg-sky-500': hovered,
80 | 'bg-blue-500': !hovered,
81 | },
82 | `px-3 py-1 items-center rounded`,
83 | ),
84 | style,
85 | ]}>
86 | {!!title && {title}}
87 | {children}
88 |
89 | );
90 | }
91 |
92 | return (
93 | setCoordinate(e.nativeEvent.layout)}
96 | // @ts-ignore
97 | enableFocusRing={false}
98 | onMouseEnter={onHover}
99 | onMouseLeave={offHover}
100 | style={[
101 | tw(
102 | {
103 | 'bg-gray-100': !isDark && hovered,
104 | 'bg-gray-700': isDark && hovered,
105 | },
106 | 'px-2 py-1 items-center bg-opacity-30 rounded',
107 | ),
108 | style,
109 | ]}>
110 | {!!title && {title}}
111 | {children}
112 |
113 | );
114 | };
115 |
116 | const styles = StyleSheet.create({
117 | flatButton: {
118 | color: global.colors.blue500,
119 | fontSize: 14,
120 | },
121 | secondaryButton: {
122 | paddingHorizontal: global.metrics.pl,
123 | paddingVertical: global.metrics.ps,
124 | //@ts-ignore
125 | backgroundColor: {
126 | dynamic: {
127 | dark: `#6E6F6F`,
128 | light: `#DDD`,
129 | },
130 | },
131 | alignItems: `center`,
132 | borderRadius: 7,
133 | },
134 | margin: {
135 | margin: global.metrics.pl,
136 | },
137 | });
138 |
--------------------------------------------------------------------------------
/src/component/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Row.component';
2 | export * from './Spacer.component';
3 | export * from './Divider.component';
4 | export * from './TempoButton.component';
5 | export * from './NodeRow.component';
6 | export * from './EmptyNodes.component';
7 |
--------------------------------------------------------------------------------
/src/container/AddToken.container.tsx:
--------------------------------------------------------------------------------
1 | import React, {useState, useRef, useMemo} from 'react';
2 | import {
3 | View,
4 | Text,
5 | TextInput,
6 | ScrollView,
7 | StyleSheet,
8 | TouchableOpacity,
9 | } from 'react-native';
10 | import {Picker} from '@react-native-picker/picker';
11 | import {StackNavigationProp} from '@react-navigation/stack';
12 | import {Row, Spacer, Divider, TempoButton} from 'component';
13 | import {observer} from 'mobx-react-lite';
14 | import {useStore} from 'Root.store';
15 | import {IRootStackParams} from 'Route';
16 | import {tw} from 'tailwind';
17 | import {useDarkTheme, useDynamic, REGEX_VALID_URL} from 'lib';
18 | import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
19 |
20 | interface IProps {
21 | navigation: StackNavigationProp;
22 | }
23 |
24 | const sources: Source[] = [
25 | `AppCenter`,
26 | `Bitrise`,
27 | `CircleCI`,
28 | `Gitlab`,
29 | `TravisCI`,
30 | ];
31 |
32 | export const AddTokenContainer = observer(({navigation}: IProps) => {
33 | let root = useStore();
34 | let [source, setSource] = useState(`CircleCI`);
35 | let [name, setName] = useState(``);
36 | let [key, setKey] = useState(``);
37 | let [baseURL, setBaseURL] = useState(`https://gitlab.com`);
38 | let [visibility, setVisibility] = useState(`private`);
39 | let secondField = useRef();
40 | let thirdField = useRef();
41 | let dynamic = useDynamic();
42 | let isDark = useDarkTheme();
43 |
44 | let isGitlab = useMemo(() => {
45 | if(source === 'Gitlab'){
46 | return true;
47 | }
48 | return false;
49 | }, [source]);
50 |
51 | let inputFieldStyle = tw(`p-3 ${dynamic(`bg-gray-900`, `bg-gray-100`)}`);
52 |
53 | function addToken() {
54 | if (!name) {
55 | root.ui.addToast({
56 | text: 'Please add a name',
57 | type: 'error',
58 | position: 'top',
59 | });
60 | return;
61 | }
62 |
63 | if (!key) {
64 | root.ui.addToast({
65 | text: 'Please add a key',
66 | type: 'error',
67 | position: 'top',
68 | });
69 | return;
70 | }
71 |
72 | if (isGitlab) {
73 | if(!(baseURL && REGEX_VALID_URL.test(baseURL))){
74 | root.ui.addToast({
75 | text: 'Please add a valid base URL',
76 | type: 'error',
77 | position: 'top',
78 | });
79 | return;
80 | }
81 |
82 | root.node.addToken(source, name, key, baseURL, visibility);
83 | } else {
84 | root.node.addToken(source, name, key);
85 | }
86 |
87 | navigation.goBack();
88 | }
89 |
90 | return (
91 |
92 |
93 | navigation.goBack()}>
94 | ← Add Token
95 |
96 | TOKEN NAME
97 | isGitlab ? secondField.current?.focus() : thirdField.current?.focus()}
117 | blurOnSubmit={false}
118 | />
119 |
120 | CI PROVIDER
121 |
122 |
130 | {sources.map((item, ii) => {
131 | return (
132 |
133 | setSource(item)}>
134 |
136 |
137 |
139 | {item}
140 |
141 |
142 | {item === source && (
143 |
144 | )}
145 |
146 |
147 |
148 | {ii !== sources.length - 1 && }
149 |
150 | );
151 | })}
152 |
153 | {isGitlab ? (
154 | <>
155 | HOST DOMAIN
156 |
157 |
158 | {`You can replace https://gitlab.com below with your own instance domain if you are using a self-managed Gitlab instance.`}
159 |
160 |
161 | thirdField.current?.focus()}
176 | />
177 |
178 | VISIBILITY
179 |
187 |
188 |
189 | Project visibility of repositories to observe
190 |
191 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 | >
204 | ) : null}
205 | KEY
206 | {/* @ts-ignore */}
207 |
229 |
230 |
236 |
237 |
238 | );
239 | });
240 |
241 | const styles = StyleSheet.create({
242 | macOSSwitch: {
243 | height: 15,
244 | width: 15,
245 | },
246 | });
247 |
--------------------------------------------------------------------------------
/src/container/GeneralConfig.container.tsx:
--------------------------------------------------------------------------------
1 | import {Picker} from '@react-native-picker/picker';
2 | import {StackNavigationProp} from '@react-navigation/stack';
3 | import {Images} from 'Assets';
4 | import {Divider, Row, Spacer} from 'component';
5 | import {useDarkTheme, useDynamic} from 'lib';
6 | import {observer} from 'mobx-react-lite';
7 | import React from 'react';
8 | import {
9 | Image,
10 | StyleSheet,
11 | Switch,
12 | Text,
13 | TouchableOpacity,
14 | View,
15 | ScrollView,
16 | Linking,
17 | } from 'react-native';
18 | import FoIcon from 'react-native-vector-icons/FontAwesome';
19 | import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
20 | import {useStore} from 'Root.store';
21 | import {IRootStackParams} from 'Route';
22 | import {tw} from 'tailwind';
23 | // @ts-ignore
24 | import {version} from '../../package.json';
25 |
26 | interface IProps {
27 | navigation: StackNavigationProp;
28 | }
29 |
30 | export let GeneralConfigContainer = observer(({navigation}: IProps) => {
31 | let root = useStore();
32 | let {tokens} = root.node;
33 | let dynamic = useDynamic();
34 | const isDark = useDarkTheme();
35 |
36 | let settingsRowStyle = tw(``);
37 |
38 | return (
39 |
40 |
41 | navigation.popToTop()}>
42 | ← Settings
43 |
44 |
45 | TOKENS
46 |
47 |
55 | {!tokens.length && (
56 |
57 | No saved tokens
58 |
59 | )}
60 |
61 |
62 |
63 | {tokens.map((t, idx) => {
64 | return (
65 |
69 |
85 | {t.name}
86 |
87 |
91 | root.node.removeTokenByName(t.name)}
95 | />
96 |
97 |
98 | );
99 | })}
100 |
101 | navigation.navigate(`AddToken`)}>
102 |
103 |
104 | Add Token
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 | GENERAL
113 |
114 |
122 |
123 |
124 | Polling Interval
125 | (minutes)
126 |
127 | root.node.setFetchInterval(v as any)}>
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 | Failing builds notifications
144 |
145 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 | Passing builds notifications
158 |
159 |
164 |
165 |
166 |
167 |
168 |
169 | Show build number
170 |
171 |
176 |
177 |
178 |
179 |
180 |
181 |
182 | Simple status bar icon
183 |
184 |
189 |
190 |
191 |
192 |
193 |
194 | Launch on login
195 |
196 |
201 |
202 |
203 |
204 |
205 |
206 |
207 | Report an issue
208 |
209 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 | Quit
222 |
223 |
228 |
229 |
230 |
231 |
232 |
233 | {/*
234 |
235 | Two row item names
236 |
237 |
242 |
243 | {global.isMacOS && }
244 | */}
245 |
246 | {/* {__DEV__ && (
247 |
248 | DEBUG BUILD COMMANDS
249 | {
252 | cidemonNative.sendNotification(`Test`, `Payload`, `URL`);
253 | }}
254 | />
255 | {
258 | root.ui.addToast({
259 | text: `test notification`,
260 | type: 'success',
261 | });
262 | }}
263 | />
264 | {
267 | root.ui.addToast({
268 | text: `test fail`,
269 | type: 'error',
270 | });
271 | }}
272 | />
273 |
274 | )} */}
275 |
276 |
277 |
278 | CI Demon
279 | v{version}
280 |
281 | {
284 | Linking.openURL(`https://ospfranco.com/`);
285 | }}>
286 | Oscar Franco
287 |
288 | ·
289 | {
292 | Linking.openURL(
293 | `https://apps.apple.com/de/app/ci-demon/id1560355863?l=en&mt=12`,
294 | );
295 | }}>
296 | Share
297 |
298 |
299 |
300 |
301 |
302 |
303 | );
304 | });
305 |
306 | const styles = StyleSheet.create({
307 | buttonContainer: {
308 | justifyContent: `space-around`,
309 | paddingVertical: 40,
310 | },
311 | switch: {
312 | height: 15,
313 | width: 15,
314 | },
315 | settingsRowInternal: {
316 | paddingVertical: global.metrics.pxm,
317 | paddingHorizontal: global.metrics.pl,
318 | },
319 | });
320 |
--------------------------------------------------------------------------------
/src/container/GithubActionsConfig.container.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | Text,
4 | TextInput,
5 | ScrollView,
6 | TouchableOpacity,
7 | StyleSheet,
8 | View,
9 | Linking,
10 | Switch,
11 | } from 'react-native';
12 | import {observer} from 'mobx-react-lite';
13 | import {useStore} from 'Root.store';
14 | import {Divider, Row, Spacer, TempoButton} from 'component';
15 | import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
16 | import {useDarkTheme, useDynamic} from 'lib';
17 | import {tw} from 'tailwind';
18 |
19 | let placeHolderStyle: any = {
20 | dynamic: {
21 | dark: global.colors.gray200,
22 | light: global.colors.gray500,
23 | },
24 | };
25 |
26 | function openGithubGuide() {
27 | Linking.openURL(
28 | `https://ospfranco.github.io/post/2021/05/08/how-to-add-a-github-token-to-ci-demon/`,
29 | );
30 | }
31 |
32 | export const GithubActionsConfigContainer = observer(({navigation}: any) => {
33 | let root = useStore();
34 | let dynamic = useDynamic();
35 | const isDark = useDarkTheme();
36 |
37 | let inputFieldStyle = tw(`p-3 ${dynamic(`bg-gray-900`, `bg-gray-100`)}`);
38 | let settingsRowStyle = tw('');
39 |
40 | return (
41 |
42 |
43 | navigation.popToTop()}>
44 | ← Github
45 |
46 |
47 |
48 | {`In order to connect with github, first you need to generate an access token and then subscribe to each repository individually (sorry! API limitations).`}
49 |
50 |
51 |
52 | How do I create an Access Token?
53 |
54 |
55 | {/* */}
59 |
60 | ACCESS TOKEN
61 |
75 |
76 | ITEMS TO FETCH
77 |
85 |
86 |
87 |
88 | Fetch pull requests
89 |
90 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 | Fetch branches
102 |
103 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 | Fetch workflows
115 |
116 |
120 |
121 |
122 |
123 |
124 |
125 | OBSERVED REPOSITORIES
126 |
127 | {root.node.githubRepos.map((r, ii) => {
128 | return (
129 |
130 | root.node.setGithubRepoAtIndex(t, ii)}
142 | />
143 |
144 | root.node.deleteGithubRepo(ii)}>
145 |
149 |
150 |
151 |
152 |
153 | );
154 | })}
155 |
156 |
157 |
163 |
164 |
165 |
166 |
167 | );
168 | });
169 |
170 | // @ts-ignore
171 | GithubActionsConfigContainer.navigationOptions = () => ({
172 | title: `Github Actions`,
173 | });
174 |
175 | const styles = StyleSheet.create({
176 | container: {
177 | flex: 1,
178 | // @ts-ignore
179 | backgroundColor: {
180 | dynamic: {
181 | light: global.colors.gray010,
182 | dark: global.colors.gray800,
183 | },
184 | },
185 | },
186 | contentContainer: {},
187 | listContainer: {
188 | flex: 1,
189 | // @ts-ignore
190 | backgroundColor: {
191 | dynamic: {
192 | light: global.colors.gray010,
193 | dark: global.colors.gray900,
194 | },
195 | },
196 | },
197 | row: {
198 | padding: global.metrics.pl,
199 | //@ts-ignore
200 | backgroundColor: {
201 | dynamic: {
202 | light: `white`,
203 | dark: `#1E1E1E`,
204 | },
205 | },
206 | },
207 | repositoryField: {
208 | width: `90%`,
209 | height: 20,
210 | borderWidth: 0,
211 | padding: 0,
212 | // @ts-ignore
213 | color: {
214 | dynamic: {
215 | light: `black`,
216 | dark: `white`,
217 | },
218 | },
219 | },
220 | });
221 |
--------------------------------------------------------------------------------
/src/container/IgnoreConfig.container.tsx:
--------------------------------------------------------------------------------
1 | import React, {useState} from 'react';
2 | import {
3 | View,
4 | Text,
5 | FlatList,
6 | TextInput,
7 | StyleSheet,
8 | Switch,
9 | TouchableOpacity,
10 | } from 'react-native';
11 | import {Divider, Row, Spacer, TempoButton} from 'component';
12 | import {useStore} from 'Root.store';
13 | import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
14 | import {observer} from 'mobx-react-lite';
15 | import {tw} from 'tailwind';
16 | import {useDarkTheme, useDynamic} from 'lib';
17 | import {StackNavigationProp} from '@react-navigation/stack';
18 | import {IRootStackParams} from 'Route';
19 |
20 | let placeHolderStyle: any = {
21 | dynamic: {
22 | dark: global.colors.gray400,
23 | light: global.colors.gray500,
24 | },
25 | };
26 |
27 | interface IProps {
28 | navigation: StackNavigationProp;
29 | }
30 |
31 | export const IgnoreConfigContainer = observer(({navigation}: IProps) => {
32 | let root = useStore();
33 | let [regex, setRegex] = useState(``);
34 | let [inverted, setInverted] = useState(false);
35 | let [id, setId] = useState(null);
36 | let [error, setError] = useState(false);
37 | const isDark = useDarkTheme();
38 | const dynamic = useDynamic();
39 |
40 | function commit() {
41 | if (id) {
42 | setId(null);
43 | setRegex(``);
44 | setInverted(false);
45 |
46 | let tempRegex = regex.trim();
47 |
48 | root.node.updateIgnoredRegex(id, tempRegex, inverted);
49 | } else {
50 | let tempRegex = regex.trim();
51 | if (tempRegex !== ``) {
52 | let inserted = root.node.addIgnoredRegex(tempRegex, inverted);
53 | if (!inserted) {
54 | setError(true);
55 | } else {
56 | setRegex(``);
57 | }
58 | }
59 | }
60 | }
61 |
62 | return (
63 |
64 |
65 | navigation.popToTop()}>
66 | ← Filters
67 |
68 |
69 | IGNORED REGEXES
70 | (
73 |
74 |
79 | {info.item.regex}
80 |
81 | {info.item.inverted && (
82 | Inverted
83 | )}
84 |
85 | {
90 | setId(info.item.id);
91 | setRegex(info.item.regex);
92 | setInverted(info.item.inverted);
93 | }}
94 | />
95 |
96 | root.node.removeIgnoredRegex(info.item.regex)}
101 | />
102 |
103 |
104 |
105 | )}
106 | keyExtractor={(t) => t.regex}
107 | contentContainerStyle={tw(`flex-grow`)}
108 | ListEmptyComponent={
109 |
110 |
111 | No ignored patterns
112 |
113 |
114 | }
115 | style={tw(
116 | {
117 | 'bg-white': !isDark,
118 | 'bg-gray-900': isDark,
119 | },
120 | 'rounded-lg bg-opacity-70 mb-3',
121 | )}
122 | />
123 |
124 | NEW REGEX
125 |
126 |
127 |
128 |
141 | {error && Not a valid regex}
142 |
143 | Inverted
144 | (Show matching branches)
145 | setInverted(!inverted)}
148 | style={tw(`w-6`)}
149 | />
150 |
151 |
152 |
158 | {!!id && (
159 | {
162 | setId(null);
163 | setRegex(``);
164 | setInverted(false);
165 | }}
166 | />
167 | )}
168 |
169 |
170 |
171 | );
172 | });
173 |
174 | // @ts-ignore
175 | IgnoreConfigContainer.navigationOptions = () => ({
176 | title: `Ignore`,
177 | });
178 |
179 | let styles = StyleSheet.create({
180 | container: {
181 | flex: 1,
182 | // @ts-ignore
183 | backgroundColor: {
184 | dynamic: {
185 | light: global.colors.gray010,
186 | dark: global.colors.gray800,
187 | },
188 | },
189 | },
190 | row: {
191 | flexDirection: `row`,
192 | alignItems: `center`,
193 | paddingHorizontal: global.metrics.pl,
194 | //@ts-ignore
195 | backgroundColor: {
196 | dynamic: {
197 | light: `white`,
198 | dark: global.colors.gray900,
199 | },
200 | },
201 | },
202 | list: {
203 | flex: 1,
204 | //@ts-ignore
205 | backgroundColor: {
206 | dynamic: {
207 | light: global.colors.gray010,
208 | dark: global.colors.gray900,
209 | },
210 | },
211 | },
212 | input: {
213 | paddingVertical: global.metrics.pm,
214 | // @ts-ignore
215 | color: {
216 | dynamic: {
217 | light: `black`,
218 | dark: `white`,
219 | },
220 | },
221 | },
222 | editIcon: {
223 | // @ts-ignore
224 | color: {
225 | dynamic: {
226 | light: global.colors.blue500,
227 | dark: `white`,
228 | },
229 | },
230 | paddingRight: 10,
231 | },
232 | emptyContainer: {justifyContent: `center`, height: 200},
233 | flex1: {
234 | flex: 1,
235 | },
236 | header: {
237 | paddingTop: global.metrics.pl * 2,
238 | padding: global.metrics.pl,
239 | fontWeight: `500`,
240 | },
241 | });
242 |
--------------------------------------------------------------------------------
/src/container/NodeList.container.tsx:
--------------------------------------------------------------------------------
1 | import {StackNavigationProp} from '@react-navigation/stack';
2 | import {Images} from 'Assets';
3 | import {
4 | EmptyNodesComponent,
5 | NodeRow,
6 | Row,
7 | Spacer,
8 | TempoButton,
9 | } from 'component';
10 | import {idExtractor, useDarkTheme} from 'lib';
11 | import {observer} from 'mobx-react-lite';
12 | import React from 'react';
13 | import {
14 | Image,
15 | Linking,
16 | ListRenderItemInfo,
17 | SectionList,
18 | Text,
19 | TouchableOpacity,
20 | View,
21 | } from 'react-native';
22 | import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
23 | import {useStore} from 'Root.store';
24 | import {IRootStackParams} from 'Route';
25 | import {tw} from 'tailwind';
26 |
27 | interface IProps {
28 | navigation: StackNavigationProp;
29 | }
30 |
31 | export let NodeListContainer = observer(({navigation}: IProps) => {
32 | let root = useStore();
33 | let isDark = useDarkTheme();
34 |
35 | let iconStyle = [
36 | tw(`${isDark ? `text-white` : ``} text-base`),
37 | {lineHeight: 16},
38 | ];
39 |
40 | let goToAddTokenScreen = () => {
41 | navigation.navigate(`AddToken`);
42 | };
43 |
44 | const renderNodeItem = ({item}: ListRenderItemInfo) => {
45 | return (
46 | {
49 | root.node.openNode(item.url);
50 | }}
51 | />
52 | );
53 | };
54 |
55 | const sectionsObj = root.node.sortedFilteredNodes.reduce(
56 | (acc: any, node: INode) => {
57 | if (node.slug) {
58 | if (acc[node.slug]) {
59 | acc[node.slug].push(node);
60 | } else {
61 | acc[node.slug] = [node];
62 | }
63 | } else {
64 | if (acc['No repository']) {
65 | acc['No repository'].push(node);
66 | } else {
67 | acc['No repository'] = [node];
68 | }
69 | }
70 |
71 | return acc;
72 | },
73 | {},
74 | );
75 |
76 | const sections: Array<{title: string; data: INode[]}> = Object.entries(
77 | sectionsObj,
78 | ).map(([slug, data]) => ({
79 | title: slug,
80 | data,
81 | })) as any;
82 |
83 | return (
84 |
89 |
90 |
96 | navigation.navigate(`Configuration`)}
98 | style={tw('py-1 px-3 rounded', {
99 | 'bg-gray-100': !isDark,
100 | 'bg-gray-700': isDark,
101 | })}>
102 |
103 |
104 |
105 |
106 |
112 |
117 | Filters:{' '}
118 |
119 | {root.node.filterHardOffSwitch ||
120 | !root.node.complexRegexes.length
121 | ? `Off`
122 | : `On`}
123 |
124 |
125 |
126 |
127 |
128 |
129 |
135 |
136 | Sort:{' '}
137 | {root.node.sortingKey}
138 |
139 |
140 |
141 |
142 |
143 |
147 |
153 |
154 |
155 | (
162 |
170 | {title}
171 |
172 | )}
173 | ListEmptyComponent={
174 |
175 | }
176 | />
177 |
178 |
179 | {/* Welcome */}
180 | {!root.node.welcomeShown && (
181 |
189 |
193 |
194 |
195 |
196 | Welcome to CI Demon.
197 |
198 |
199 |
200 | Start by generating an access token on your CI, then save it the
201 | settings. Afterwards, your repositories and CI Jobs will be
202 | automatically polled.
203 |
204 |
205 |
206 | Reviewing the app helps
207 | me create more tools, same for following me on Twitter.
208 |
209 | Thanks a lot!
210 |
211 |
212 |
213 | {
217 | Linking.openURL('https://twitter.com/ospfranco');
218 | root.node.dismissWelcome();
219 | }}>
220 |
221 | )}
222 |
223 | );
224 | });
225 |
--------------------------------------------------------------------------------
/src/container/Toast.container.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {observer} from 'mobx-react-lite';
3 | import {StyleSheet, Text, View} from 'react-native';
4 | import {useStore} from 'Root.store';
5 | import tw from 'tailwind-rn';
6 |
7 | export const ToastContainer = observer(() => {
8 | const root = useStore();
9 | const {toasts} = root.ui;
10 |
11 | if (!toasts.length) {
12 | return null;
13 | }
14 |
15 | const latestToast = toasts[0];
16 |
17 | const conditionalStyle = styles[latestToast.type];
18 |
19 | return (
20 |
26 | {latestToast.text}
27 |
28 | );
29 | });
30 |
31 | const styles = StyleSheet.create({
32 | success: tw(`bg-green-500`),
33 | error: tw(`bg-red-500`),
34 | neutral: {
35 | // @ts-ignore
36 | backgroundColor: {
37 | dynamic: {
38 | dark: global.colors.gray500,
39 | light: `white`,
40 | },
41 | },
42 | },
43 | });
44 |
--------------------------------------------------------------------------------
/src/container/index.ts:
--------------------------------------------------------------------------------
1 | export * from './NodeList.container';
2 | export * from './GeneralConfig.container';
3 | export * from './AddToken.container';
4 | export * from './IgnoreConfig.container';
5 | export * from './GithubActionsConfig.container';
6 | export * from './Toast.container';
7 |
--------------------------------------------------------------------------------
/src/lib/CIDemonNative.ts:
--------------------------------------------------------------------------------
1 | import { NativeModules } from 'react-native';
2 |
3 | interface CIDemonNative {
4 | setStatusButtonText: (passed: number, running: number, failed: number, simpleIcon: boolean) => void;
5 | requestReview: () => void;
6 | sendNotification: (title: string, payload: string, url: string) => void;
7 | securelyStore: (key: string, value: string) => void;
8 | securelyRetrieve: (key: string) => string | null;
9 | applyAutoLauncher: (autoLauncherValue: boolean) => void;
10 | closeApp: () => void;
11 | showShareMenu: (x: number, y: number, text: string) => void;
12 | }
13 |
14 | function createCIDemonNative(nativeModule: any): CIDemonNative {
15 | return {
16 | setStatusButtonText: nativeModule.setStatusButtonText,
17 | requestReview: nativeModule.requestReview,
18 | sendNotification: __DEV__ ? () => null : nativeModule.sendNotification,
19 | securelyStore: nativeModule.securelyStore,
20 | securelyRetrieve: nativeModule.securelyRetrieve,
21 | applyAutoLauncher: nativeModule.applyAutoLauncher,
22 | closeApp: nativeModule.closeApp,
23 | showShareMenu: nativeModule.showShareMenu
24 | };
25 | }
26 |
27 | export const cidemonNative = createCIDemonNative(NativeModules.CIDemonNative);
--------------------------------------------------------------------------------
/src/lib/DtoMapper.test.ts:
--------------------------------------------------------------------------------
1 | import {mapCircleCIProjects} from "./DtoMappings"
2 |
3 | describe(`DTO Mapper behaves correctly`, () => {
4 | it(`Handles CircleCI workflows correctly`, () => {
5 | const circleCIResWithFailedJob = [
6 | {
7 | branches: {
8 | "bet-fix-blob-error-state": {
9 | latest_workflows: {
10 | commit_validation: {
11 | status: `failed`,
12 | created_at: `2020-10-14T15:36:34.176Z`,
13 | id: `113c951a-f60c-4579-8dd8-d3ee988e4ecc`,
14 | },
15 | },
16 | pusher_logins: [`bhuang14`],
17 | running_builds: [],
18 | recent_builds: [
19 | {
20 | outcome: `success`,
21 | status: `success`,
22 | build_num: 460348,
23 | vcs_revision: `d7081bf668886ec52f2c5de70dcfa329e7fefa73`,
24 | pushed_at: `2020-10-14T15:36:33.000Z`,
25 | is_workflow_job: true,
26 | is_2_0_job: true,
27 | added_at: `2020-10-14T15:41:32.083Z`,
28 | },
29 | {
30 | outcome: `failed`,
31 | status: `failed`,
32 | build_num: 460347,
33 | vcs_revision: `d7081bf668886ec52f2c5de70dcfa329e7fefa73`,
34 | pushed_at: `2020-10-14T15:36:33.000Z`,
35 | is_workflow_job: true,
36 | is_2_0_job: true,
37 | added_at: `2020-10-14T15:47:20.003Z`,
38 | },
39 | {
40 | outcome: `success`,
41 | status: `success`,
42 | build_num: 460346,
43 | vcs_revision: `d7081bf668886ec52f2c5de70dcfa329e7fefa73`,
44 | pushed_at: `2020-10-14T15:36:33.000Z`,
45 | is_workflow_job: true,
46 | is_2_0_job: true,
47 | added_at: `2020-10-14T15:52:43.128Z`,
48 | },
49 | {
50 | outcome: `success`,
51 | status: `success`,
52 | build_num: 460345,
53 | vcs_revision: `d7081bf668886ec52f2c5de70dcfa329e7fefa73`,
54 | pushed_at: `2020-10-14T15:36:33.000Z`,
55 | is_workflow_job: true,
56 | is_2_0_job: true,
57 | added_at: `2020-10-14T15:41:40.057Z`,
58 | },
59 | {
60 | outcome: `success`,
61 | status: `success`,
62 | build_num: 460344,
63 | vcs_revision: `d7081bf668886ec52f2c5de70dcfa329e7fefa73`,
64 | pushed_at: `2020-10-14T15:36:33.000Z`,
65 | is_workflow_job: true,
66 | is_2_0_job: true,
67 | added_at: `2020-10-14T15:38:18.937Z`,
68 | },
69 | ],
70 | last_success: {
71 | outcome: `success`,
72 | status: `success`,
73 | build_num: 460348,
74 | vcs_revision: `d7081bf668886ec52f2c5de70dcfa329e7fefa73`,
75 | pushed_at: `2020-10-14T15:36:33.000Z`,
76 | is_workflow_job: true,
77 | is_2_0_job: true,
78 | added_at: `2020-10-14T15:41:32.083Z`,
79 | },
80 | last_non_success: {
81 | outcome: `failed`,
82 | status: `failed`,
83 | build_num: 460347,
84 | vcs_revision: `d7081bf668886ec52f2c5de70dcfa329e7fefa73`,
85 | pushed_at: `2020-10-14T15:36:33.000Z`,
86 | is_workflow_job: true,
87 | is_2_0_job: true,
88 | added_at: `2020-10-14T15:47:20.003Z`,
89 | },
90 | latest_completed_workflows: {
91 | commit_validation: {
92 | status: `failed`,
93 | created_at: `2020-10-14T15:36:34.176Z`,
94 | id: `113c951a-f60c-4579-8dd8-d3ee988e4ecc`,
95 | },
96 | },
97 | is_using_workflows: true,
98 | },
99 | },
100 | oss: false,
101 | reponame: `experimental`,
102 | parallel: 11,
103 | username: `kr-project`,
104 | has_usable_key: false,
105 | vcs_type: `github`,
106 | language: null,
107 | vcs_url: `https://github.com/kr-project/experimental`,
108 | following: true,
109 | default_branch: `master`,
110 | },
111 | ]
112 |
113 | const mappedNodes = mapCircleCIProjects(circleCIResWithFailedJob, `blah`, {
114 | showBuildNumber: true,
115 | })
116 |
117 | expect(mappedNodes[0].status).toBe(`failed`)
118 | })
119 | })
120 |
--------------------------------------------------------------------------------
/src/lib/DtoMappings.ts:
--------------------------------------------------------------------------------
1 | import {DateTime} from 'luxon';
2 | import {
3 | CircleciRepoDto,
4 | AppcenterRepoDto,
5 | AppcenterBranchDto,
6 | TravisRepoDto,
7 | TravisBranchesDto,
8 | BitriseBranchDto,
9 | BitriseRepoDto,
10 | BitriseStatus,
11 | GithubWorkflowDto,
12 | GithubWorkflowRunInfo,
13 | GitlabProjectDto,
14 | GitlabPipelineDto,
15 | IGithubCheck,
16 | } from 'model';
17 |
18 | interface IParsingOptions {
19 | showBuildNumber: boolean;
20 | }
21 |
22 | export function mapCircleCIProjects(
23 | repos: CircleciRepoDto[],
24 | key: string,
25 | options: IParsingOptions,
26 | ): INode[] {
27 | let nodes: INode[] = [];
28 |
29 | for (let i = 0; i < repos.length; i++) {
30 | let repo = repos[i];
31 | let branches = Object.entries(repo.branches);
32 | for (let j = 0; j < branches.length; j++) {
33 | let [name, info] = branches[j];
34 |
35 | let status: Status = `running`;
36 | let isRunning = !!info.running_builds?.length;
37 | let latestDate;
38 |
39 | let jobId: string = `-`;
40 |
41 | if (info.last_success && info.last_non_success) {
42 | if (info.last_success.pushed_at > info.last_non_success.pushed_at) {
43 | status = `passed`;
44 | latestDate = info.last_success.pushed_at;
45 | jobId = info.last_success.build_num.toString();
46 | } else {
47 | status = `failed`;
48 | latestDate = info.last_non_success.pushed_at;
49 | jobId = info.last_non_success.build_num.toString();
50 | }
51 | } else if (info.last_success) {
52 | status = `passed`;
53 | latestDate = info.last_success.pushed_at;
54 | jobId = info.last_success.build_num.toString();
55 | } else if (info.last_non_success) {
56 | status = `failed`;
57 | latestDate = info.last_non_success.pushed_at;
58 | jobId = info.last_non_success.build_num.toString();
59 | }
60 |
61 | if (isRunning) {
62 | status = `running`;
63 | latestDate = info.running_builds![0].pushed_at;
64 | }
65 |
66 | let vcsLong = repo.vcs_url.includes(`github`) ? `github` : `bitbucket`;
67 | let vcsShort = repo.vcs_url.includes(`github`) ? `gh` : `bb`;
68 | let unscapedName = unescape(name);
69 |
70 | if (!options.showBuildNumber) {
71 | jobId = ``;
72 | }
73 |
74 | let node: INode = {
75 | id: `${`CircleCI`}-${repo.username}-${repo.reponame}-${name}`,
76 | url: `https://circleci.com/${vcsShort}/${repo.username}/${repo.reponame}/tree/${name}`,
77 | date: latestDate,
78 | source: `CircleCI`,
79 | label: `${repo.username}/${repo.reponame} [${unscapedName}] #${jobId}`,
80 | status: status,
81 | key: key,
82 | buildUrl: `https://circleci.com/api/v1.1/project/${vcsLong}/${repo.username}/${repo.reponame}/tree/${name}?circle-token=${key}`,
83 | slug: `${repo.username}/${repo.reponame}`,
84 | };
85 |
86 | nodes.push(node);
87 | }
88 | }
89 |
90 | return nodes;
91 | }
92 |
93 | export function mapAppcenterTuplesToNodes(
94 | repo: AppcenterRepoDto,
95 | branches: AppcenterBranchDto[],
96 | key: string,
97 | options: IParsingOptions,
98 | ): INode[] {
99 | return branches.map((branch) => {
100 | let status: Status = `pending`;
101 |
102 | if (branch.lastBuild?.status === `inProgress`) {
103 | status = `running`;
104 | }
105 |
106 | if (branch.lastBuild?.result === `succeeded`) {
107 | status = `passed`;
108 | }
109 |
110 | if (branch.lastBuild?.result === `failed`) {
111 | status = `failed`;
112 | }
113 |
114 | let jobId = ``;
115 | if (options.showBuildNumber && branch.lastBuild) {
116 | jobId = ` #${branch.lastBuild.buildNumber}`;
117 | }
118 |
119 | let urlFriendlyBranchName = branch.branch.name.replace(`/`, `%2F`);
120 |
121 | let node: INode = {
122 | id: `${`AppCenter`}-${repo.owner.name}-${repo.name}-${
123 | branch.branch.name
124 | }`,
125 | url: `https://appcenter.ms/users/${repo.owner.name}/apps/${repo.name}/build/branches/${urlFriendlyBranchName}`,
126 | label: `${repo.owner.name}/${repo.name} [${branch.branch.name}]${jobId}`,
127 | source: `AppCenter`,
128 | status: status,
129 | key: key,
130 | buildUrl: `https://api.appcenter.ms/v0.1/apps/${repo.owner.name}/${repo.name}/branches/${urlFriendlyBranchName}/builds`,
131 | slug: `${repo.owner.name}/${repo.name}`,
132 | };
133 | return node;
134 | });
135 | }
136 |
137 | export function mapTravisTuplesToNodes(
138 | repo: TravisRepoDto,
139 | branches: TravisBranchesDto,
140 | key: string,
141 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
142 | options: IParsingOptions,
143 | ): INode[] {
144 | return branches.branches.map((branch) => {
145 | let commit = branches.commits.find((c) => c.id === branch.commit_id)!;
146 |
147 | let status: Status = `pending`;
148 | if (branch.state === `errored`) {
149 | status = `failed`;
150 | }
151 |
152 | if (branch.state === `started`) {
153 | status = `running`;
154 | }
155 |
156 | if (branch.state === `passed`) {
157 | status = `passed`;
158 | }
159 |
160 | let node: INode = {
161 | id: `${`TravisCI`}-${repo.slug}-${commit.branch}`,
162 | url: `https://travis-ci.com/github/${repo.slug}`,
163 | label: `${repo.slug} [${commit.branch}]`,
164 | source: `TravisCI`,
165 | status: status,
166 | key: key,
167 | date: branch.started_at,
168 | slug: repo.slug,
169 | };
170 |
171 | return node;
172 | });
173 | }
174 |
175 | export function mapBitriseTuplesToNode(
176 | repo: BitriseRepoDto,
177 | branches: BitriseBranchDto[],
178 | key: string,
179 | options: IParsingOptions,
180 | ): INode[] {
181 | return branches.map((branch) => {
182 | let status: Status = `pending`;
183 |
184 | switch (branch.status) {
185 | case BitriseStatus.successful:
186 | status = `passed`;
187 | break;
188 | case BitriseStatus.abortedWithFailure:
189 | status = `failed`;
190 | break;
191 | case BitriseStatus.abortedWithSuccess:
192 | status = `passed`;
193 | break;
194 | case BitriseStatus.notFinished:
195 | status = `running`;
196 | break;
197 | case BitriseStatus.failed:
198 | status = `failed`;
199 | break;
200 | default:
201 | status = `pending`;
202 | break;
203 | }
204 |
205 | let jobId = ``;
206 | if (options.showBuildNumber) {
207 | jobId = ` #${branch.build_number}`;
208 | }
209 |
210 | let mainName = branch.branch;
211 |
212 | if (!mainName || mainName.length === 0) {
213 | mainName = branch.commit_message ?? 'Unknown Branch';
214 | }
215 |
216 | if (branch.triggered_workflow) {
217 | mainName += `- ${branch.triggered_workflow}`;
218 | }
219 |
220 | let node: INode = {
221 | id: `${`Bitrise`}-${repo.title}-${branch.branch}-${branch.build_number}`,
222 | url: `https://app.bitrise.io/build/${branch.slug}`,
223 | label: `${repo.repo_owner}/${repo.title} [${mainName}]${jobId}`,
224 | source: `Bitrise`,
225 | status,
226 | key,
227 | // @TODO do not only use finished_at, for running builds use created_at
228 | date: branch.finished_at,
229 | buildUrl: `https://api.bitrise.io/v0.1/apps/${repo.slug}/builds`,
230 | // Storing the API provided repository "title" to be used in further calls to the api
231 | extra: repo.title,
232 | vcs: 'unknown',
233 | jobId: branch.build_number?.toString(),
234 | slug: `${repo.repo_owner}/${repo.title}`,
235 | };
236 |
237 | return node;
238 | });
239 | }
240 |
241 | export function mapGithubActionTupleToNode(
242 | slug: string,
243 | workflowDto: GithubWorkflowDto,
244 | latestRun: GithubWorkflowRunInfo,
245 | key: string,
246 | options: IParsingOptions,
247 | ): INode {
248 | let status: Status = `pending`;
249 |
250 | switch (latestRun.conclusion) {
251 | case `success`:
252 | status = `passed`;
253 | break;
254 |
255 | case `failure`:
256 | status = `failed`;
257 | break;
258 |
259 | case `in_progress`:
260 | status = `running`;
261 | break;
262 |
263 | default:
264 | break;
265 | }
266 |
267 | let pathComponent = `workflow%3A${workflowDto.name}`;
268 |
269 | let runNumber = ``;
270 | if (options.showBuildNumber) {
271 | runNumber = ` #${latestRun.run_number}`;
272 | }
273 |
274 | let node: INode = {
275 | id: `${`Github`}-${slug}-${workflowDto.name}`,
276 | url: `https://github.com/${slug}/actions?query=${pathComponent}`,
277 | label: `${slug} [${workflowDto.name}]${runNumber}`,
278 | date: latestRun.created_at,
279 | status: status,
280 | jobId: `${latestRun.run_number}`,
281 | source: `Github`,
282 | vcs: `github`,
283 | key: key,
284 | slug,
285 | };
286 |
287 | return node;
288 | }
289 |
290 | export function mapGitlabTupleToNodes(
291 | project: GitlabProjectDto,
292 | pipelines: GitlabPipelineDto[],
293 | key: string,
294 | options: IParsingOptions,
295 | ): INode[] {
296 | // A single ref can have multiple entries on the pipeline, which makes the output confusing
297 | // filter the repeated entries, just take the first one that appears
298 | return pipelines
299 | .reduce(
300 | (acc: GitlabPipelineDto[], a: GitlabPipelineDto): GitlabPipelineDto[] => {
301 | if (acc.findIndex((b) => b.ref === a.ref) === -1) {
302 | acc.push(a);
303 | }
304 |
305 | return acc;
306 | },
307 | [] as any,
308 | )
309 | .map((pipeline) => {
310 | let status: Status = `pending`;
311 |
312 | switch (pipeline.status) {
313 | case `failed`:
314 | status = `failed`;
315 | break;
316 | case `running`:
317 | status = `running`;
318 | break;
319 | case `success`:
320 | status = `passed`;
321 | break;
322 | case `canceled`: // not a typo, gitlab is wrong
323 | status = `pending`;
324 | break;
325 | default:
326 | status = `pending`;
327 | break;
328 | }
329 |
330 | let pipelineId = ``;
331 | if (options.showBuildNumber) {
332 | pipelineId = ` #${pipeline.id}`;
333 | }
334 |
335 | let node: INode = {
336 | id: `${`Gitlab`}-${project.name}-${pipeline.id}`,
337 | url: pipeline.web_url,
338 | label: `${project.name} [${pipeline.ref}] ${pipelineId}`,
339 | date: pipeline.created_at,
340 | status: status,
341 | jobId: pipeline.id,
342 | source: `Gitlab`,
343 | vcs: `gitlab`,
344 | key: key,
345 | slug: project.name,
346 | };
347 |
348 | return node;
349 | });
350 | }
351 |
352 | export function mapGithubPrToNode(
353 | slug: string,
354 | pr: any,
355 | statuses: IGithubCheck[],
356 | key: string,
357 | ): INode {
358 | let status: Status = `pending`;
359 | let subItems: ISubNode[] = [];
360 |
361 | if (statuses.length) {
362 | status = `passed`;
363 |
364 | statuses.forEach(
365 | ({name, conclusion, started_at, completed_at, details_url}) => {
366 | let lStartedAt = DateTime.fromISO(started_at);
367 |
368 | let subItem: ISubNode = {
369 | label: name,
370 | status: `pending`,
371 | url: details_url,
372 | };
373 |
374 | if (!conclusion) {
375 | if (status !== `failed`) {
376 | status = `running`;
377 | }
378 |
379 | let extraLabel = lStartedAt.toRelative({unit: 'minutes'});
380 | subItem.extraLabel = extraLabel;
381 | } else {
382 | let lCompletedAt = DateTime.fromISO(completed_at!);
383 | let extraLabel = `${Math.round(
384 | lCompletedAt.diff(lStartedAt, 'minutes').minutes,
385 | )}m`;
386 | subItem.extraLabel = extraLabel;
387 |
388 | if (conclusion === `failure` || conclusion === `timed_out`) {
389 | status = `failed`;
390 | subItem.status = `failed`;
391 | } else if (conclusion === `success`) {
392 | subItem.status = `passed`;
393 | } else if (conclusion === `neutral`) {
394 | subItem.status = `pending`;
395 | }
396 | }
397 |
398 | subItems.push(subItem);
399 | },
400 | );
401 | }
402 |
403 | let node: INode = {
404 | id: pr.id,
405 | url: pr.html_url,
406 | label: `${slug} [${pr.title}]`,
407 | date: pr.updated_at,
408 | status: status,
409 | jobId: pr.id,
410 | source: `Github`,
411 | vcs: `github`,
412 | key: key,
413 | subItems: subItems,
414 | sha: pr.head.sha,
415 | isPr: true,
416 | username: pr.user.login,
417 | userAvatarUrl: pr.user.avatar_url,
418 | slug,
419 | };
420 |
421 | return node;
422 | }
423 |
424 | export function mapGithubBranchToNode(
425 | slug: string,
426 | branch: any,
427 | statuses: IGithubCheck[],
428 | key: string,
429 | ): INode {
430 | let status: Status = `pending`;
431 | let subItems: ISubNode[] = [];
432 |
433 | if (statuses.length) {
434 | status = `passed`;
435 |
436 | statuses.forEach(
437 | ({name, conclusion, started_at, completed_at, details_url}) => {
438 | let lStartedAt = DateTime.fromISO(started_at);
439 |
440 | let subItem: ISubNode = {
441 | label: name,
442 | status: `pending`,
443 | url: details_url,
444 | };
445 |
446 | if (!conclusion) {
447 | if (status !== `failed`) {
448 | status = `running`;
449 | }
450 |
451 | let extraLabel = lStartedAt.toRelative({unit: 'minutes'});
452 | subItem.extraLabel = extraLabel;
453 | } else {
454 | let lCompletedAt = DateTime.fromISO(completed_at!);
455 | let extraLabel = `${Math.round(
456 | lCompletedAt.diff(lStartedAt, 'minutes').minutes,
457 | )}m`;
458 | subItem.extraLabel = extraLabel;
459 |
460 | if (conclusion === `failure` || conclusion === `timed_out`) {
461 | status = `failed`;
462 | subItem.status = `failed`;
463 | } else if (conclusion === `success`) {
464 | subItem.status = `passed`;
465 | } else if (conclusion === `neutral`) {
466 | subItem.status = `pending`;
467 | }
468 | }
469 |
470 | subItems.push(subItem);
471 | },
472 | );
473 | }
474 |
475 | let node: INode = {
476 | id: branch.commit.sha,
477 | url: `https://github.com/${slug}/commit/${branch.commit.sha}`,
478 | label: `${slug} [${branch.name}]`,
479 | // date: pr.updated_at,
480 | status: status,
481 | // jobId: pr.id,
482 | source: `Github`,
483 | vcs: `github`,
484 | key: key,
485 | subItems: subItems,
486 | sha: branch.commit.sha,
487 | slug,
488 | isBranch: true,
489 | };
490 |
491 | return node;
492 | }
493 |
494 | export function mapGithubActionRunToNode(
495 | slug: string,
496 | run: any,
497 | key: string,
498 | ): INode {
499 | let status: Status = `pending`;
500 |
501 | if (run.conclusion === `failure` || run.conclusion === `timed_out`) {
502 | status = `failed`;
503 | } else if (run.conclusion === `success`) {
504 | status = `passed`;
505 | } else if (run.conclusion === `neutral`) {
506 | status = `pending`;
507 | }
508 |
509 | let node: INode = {
510 | id: run.id,
511 | url: `https://github.com/${slug}/actions/runs/${run.id}`,
512 | label: `${slug} [${run.name}]`,
513 | date: run.updated_at,
514 | status: status,
515 | source: `Github`,
516 | vcs: `github`,
517 | key: key,
518 | sha: run.head_sha,
519 | isAction: true,
520 | slug,
521 | };
522 |
523 | return node;
524 | }
525 |
--------------------------------------------------------------------------------
/src/lib/__mocks__/CIDemonNative.ts:
--------------------------------------------------------------------------------
1 | interface CIDemonNative {
2 | requestReview: () => void;
3 | sendNotification: (title: string, payload: string, url: string) => void;
4 | securelyStore: (key: string, value: string) => void;
5 | securelyRetrieve: (key: string) => string | null;
6 | applyAutoLauncher: (autoLauncherValue: boolean) => void;
7 | closeApp: () => void;
8 | }
9 |
10 | function createCIDemonNative(): CIDemonNative {
11 | return {
12 | requestReview: jest.fn(),
13 | sendNotification: jest.fn(),
14 | securelyStore: jest.fn(),
15 | securelyRetrieve: jest.fn(),
16 | applyAutoLauncher: jest.fn(),
17 | closeApp: jest.fn(),
18 | };
19 | }
20 |
21 | export const cidemonNative = createCIDemonNative();
22 |
--------------------------------------------------------------------------------
/src/lib/constants.ts:
--------------------------------------------------------------------------------
1 | export let TINT_MAPPING: Record = {
2 | passed: `#65ea92`,
3 | failed: `#eb4e49`,
4 | running: `#FFBE30`,
5 | pending: {dynamic: {light: `#37474F`, dark: `#DDD`}},
6 | };
7 | export let REGEX_VALID_URL = new RegExp(
8 | '^' +
9 | // protocol identifier
10 | '(?:(?:https?|ftp)://)' +
11 | // user:pass authentication
12 | '(?:\\S+(?::\\S*)?@)?' +
13 | '(?:' +
14 | // IP address exclusion
15 | // private & local networks
16 | '(?!(?:10|127)(?:\\.\\d{1,3}){3})' +
17 | '(?!(?:169\\.254|192\\.168)(?:\\.\\d{1,3}){2})' +
18 | '(?!172\\.(?:1[6-9]|2\\d|3[0-1])(?:\\.\\d{1,3}){2})' +
19 | // IP address dotted notation octets
20 | // excludes loopback network 0.0.0.0
21 | // excludes reserved space >= 224.0.0.0
22 | // excludes network & broacast addresses
23 | // (first & last IP address of each class)
24 | '(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])' +
25 | '(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}' +
26 | '(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))' +
27 | '|' +
28 | // host name
29 | '(?:(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)' +
30 | // domain name
31 | '(?:\\.(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)*' +
32 | // TLD identifier
33 | '(?:\\.(?:[a-z\\u00a1-\\uffff]{2,}))' +
34 | // TLD may end with dot
35 | '\\.?' +
36 | ')' +
37 | // port number
38 | '(?::\\d{2,5})?' +
39 | // resource path
40 | '(?:[/?#]\\S*)?' +
41 | '$',
42 | 'i',
43 | );
44 |
--------------------------------------------------------------------------------
/src/lib/generalUtils.ts:
--------------------------------------------------------------------------------
1 | export function idExtractor(i: T) {
2 | return i.id.toString();
3 | }
4 |
--------------------------------------------------------------------------------
/src/lib/hooks.ts:
--------------------------------------------------------------------------------
1 | import {useState} from 'react';
2 | import {useColorScheme} from 'react-native';
3 |
4 | export function useDarkTheme() {
5 | let theme = useColorScheme();
6 |
7 | return theme === `dark`;
8 | }
9 |
10 | export function useDynamic() {
11 | let theme = useColorScheme();
12 |
13 | return (darkThemeValue: string, lightThemeValue: string) =>
14 | theme === 'dark' ? darkThemeValue : lightThemeValue;
15 | }
16 |
17 | export function useBoolean(
18 | initialState: boolean = false,
19 | ): [boolean, () => void, () => void] {
20 | let [v, setV] = useState(initialState);
21 | let setTrue = () => {
22 | setV(true);
23 | };
24 | let setFalse = () => {
25 | setV(false);
26 | };
27 | return [v, setTrue, setFalse];
28 | }
29 |
--------------------------------------------------------------------------------
/src/lib/index.ts:
--------------------------------------------------------------------------------
1 | export * from './CIDemonNative';
2 | export * from './DtoMappings';
3 | export * from './hooks';
4 | export * from './constants';
5 | export * from './generalUtils';
6 |
--------------------------------------------------------------------------------
/src/model/dto/AppcenterBranch.dto.ts:
--------------------------------------------------------------------------------
1 | export interface AppcenterBranchDto {
2 | branch: BranchInfoDto
3 | lastBuild: BuildInfoDto
4 | configured: boolean
5 | }
6 |
7 | interface BranchInfoDto {
8 | name: string
9 | }
10 |
11 | interface BuildInfoDto {
12 | id: number
13 | buildNumber: string
14 | sourceVersion: string
15 | status: string // "inProgress"
16 | result: string // "succeeded" / "failed" / among many others
17 | }
18 |
--------------------------------------------------------------------------------
/src/model/dto/AppcenterRepo.dto.ts:
--------------------------------------------------------------------------------
1 | export interface AppcenterRepoDto {
2 | id: string;
3 | name: string;
4 | owner: OwnerDto;
5 | }
6 |
7 | interface OwnerDto {
8 | id: string;
9 | name: string;
10 | }
11 |
--------------------------------------------------------------------------------
/src/model/dto/Bitrise.dto.ts:
--------------------------------------------------------------------------------
1 | export interface BitrisePaginated {
2 | data: T[]
3 | }
4 |
5 | export interface BitriseRepoDto {
6 | slug: string
7 | title: string
8 | repo_owner: string
9 | provider: string
10 | branch: string
11 | }
12 |
13 | export interface BitriseBranchDto {
14 | status: BitriseStatus
15 | build_number: number
16 | commit_message?: string
17 | slug: string
18 | branch: string
19 | finished_at?: string
20 | triggered_workflow?: string
21 | }
22 |
23 | export enum BitriseStatus {
24 | notFinished = 0,
25 | successful = 1,
26 | failed = 2,
27 | abortedWithFailure = 3,
28 | abortedWithSuccess = 4,
29 | }
30 |
--------------------------------------------------------------------------------
/src/model/dto/CircleciRepo.dto.ts:
--------------------------------------------------------------------------------
1 | export interface CircleciRepoDto {
2 | vcs_url: string
3 | username: string
4 | reponame: string
5 | branches: Record
6 | }
7 |
8 | interface BranchDto {
9 | recent_builds?: BuildInfoDto[]
10 | running_builds?: BuildInfoDto[]
11 | last_non_success?: BuildInfoDto
12 | last_success: BuildInfoDto
13 | latest_completed_workflows: Record
14 | is_using_workflows: boolean
15 | }
16 |
17 | interface BuildInfoDto {
18 | pushed_at: string
19 | vcs_revision: string
20 | build_num: number
21 | outcome: string
22 | }
23 |
24 | interface WorkflowDto {
25 | status: string
26 | created_at: string
27 | id: string
28 | }
29 |
--------------------------------------------------------------------------------
/src/model/dto/Github.dto.ts:
--------------------------------------------------------------------------------
1 | export interface GithubWorkflowsDto {
2 | total_count: number;
3 | workflows: GithubWorkflowDto[];
4 | }
5 |
6 | export interface GithubWorkflowDto {
7 | id: number;
8 | name: string;
9 | url: string;
10 | }
11 |
12 | export interface GithubWorkflowInfoDto {
13 | total_count: number;
14 | workflow_runs: GithubWorkflowRunInfo[];
15 | }
16 |
17 | export interface GithubWorkflowRunInfo {
18 | id: number;
19 | run_number: number;
20 | status: string;
21 | conclusion: string;
22 | created_at: string;
23 | }
24 |
25 | export interface IGithubCheck {
26 | id: number;
27 | name: string;
28 | node_id: string;
29 | head_sha: string;
30 | external_id: string;
31 | url: string;
32 | html_url: string;
33 | details_url: string;
34 | status: string;
35 | conclusion?: string;
36 | started_at: string;
37 | completed_at?: string;
38 | }
39 |
--------------------------------------------------------------------------------
/src/model/dto/Gitlab.dto.ts:
--------------------------------------------------------------------------------
1 | export interface GitlabProjectDto {
2 | id: string
3 | name: string
4 | web_url: string
5 | }
6 |
7 | export interface GitlabPipelineDto {
8 | id: string
9 | ref: string
10 | status: string
11 | created_at: string
12 | web_url: string
13 | }
14 |
--------------------------------------------------------------------------------
/src/model/dto/TravisBranches.dto.ts:
--------------------------------------------------------------------------------
1 | export interface TravisBranchesDto {
2 | branches: BranchDto[];
3 | commits: CommitDto[];
4 | }
5 |
6 | interface BranchDto {
7 | id: number;
8 | commit_id: number;
9 | state: string;
10 | started_at: string;
11 | }
12 |
13 | interface CommitDto {
14 | id: number;
15 | branch: string;
16 | message: string;
17 | author_name: string;
18 | author_email: string;
19 | }
20 |
--------------------------------------------------------------------------------
/src/model/dto/TravisRepos.dto.ts:
--------------------------------------------------------------------------------
1 | export interface TravisReposDto {
2 | repos: TravisRepoDto[];
3 | }
4 |
5 | export interface TravisRepoDto {
6 | id: number;
7 | slug: string;
8 | }
9 |
--------------------------------------------------------------------------------
/src/model/index.ts:
--------------------------------------------------------------------------------
1 | export * from './dto/AppcenterBranch.dto';
2 | export * from './dto/AppcenterRepo.dto';
3 | export * from './dto/Bitrise.dto';
4 | export * from './dto/CircleciRepo.dto';
5 | export * from './dto/Github.dto';
6 | export * from './dto/Gitlab.dto';
7 | export * from './dto/TravisBranches.dto';
8 | export * from './dto/TravisRepos.dto';
9 |
--------------------------------------------------------------------------------
/src/store/Api.store.ts:
--------------------------------------------------------------------------------
1 | import {IRootStore} from 'Root.store';
2 | import axios from 'axios';
3 | import {
4 | mapAppcenterTuplesToNodes,
5 | mapBitriseTuplesToNode,
6 | mapCircleCIProjects,
7 | mapGithubPrToNode,
8 | mapGithubBranchToNode,
9 | mapGitlabTupleToNodes,
10 | mapTravisTuplesToNodes,
11 | mapGithubActionRunToNode,
12 | } from 'lib';
13 | import {
14 | AppcenterRepoDto,
15 | TravisReposDto,
16 | BitrisePaginated,
17 | BitriseRepoDto,
18 | BitriseBranchDto,
19 | GitlabProjectDto,
20 | GitlabPipelineDto,
21 | CircleciRepoDto,
22 | } from 'model';
23 | import Github from 'github-api';
24 | import allSettled from 'promise.allsettled';
25 |
26 | interface AjaxCallParams {
27 | auth?: string;
28 | method: `GET` | `POST` | `DELETE` | `PUT`;
29 | url: string;
30 | body?: any;
31 | headers?: any;
32 | }
33 |
34 | interface IFetchOptions {
35 | showBuildNumber: boolean;
36 | }
37 |
38 | export let createApiStore = (root: IRootStore) => {
39 | let ajaxCall = async ({
40 | url,
41 | auth,
42 | method,
43 | body,
44 | headers = {},
45 | }: AjaxCallParams) => {
46 | let res = await axios({
47 | url,
48 | method,
49 | headers: {
50 | Authorization: auth,
51 | 'Content-Type': `application/json`,
52 | ...headers,
53 | },
54 | data: body ? JSON.stringify(body) : undefined,
55 | });
56 |
57 | return res.data;
58 | };
59 |
60 | let get = (props: {
61 | url: string;
62 | auth?: string;
63 | headers?: Record;
64 | }) => ajaxCall({method: `GET`, ...props});
65 |
66 | let post = (props: {
67 | url: string;
68 | auth?: string;
69 | body?: any;
70 | headers?: Record;
71 | }) => ajaxCall({method: `POST`, ...props});
72 |
73 | // let put = (props: {
74 | // url: string
75 | // auth?: string
76 | // headers?: Record
77 | // }) => ajaxCall({method: `PUT`, ...props})
78 |
79 | let store = {
80 | // _ _
81 | // /\ | | (_)
82 | // / \ ___| |_ _ ___ _ __ ___
83 | // / /\ \ / __| __| |/ _ \| '_ \/ __|
84 | // / ____ \ (__| |_| | (_) | | | \__ \
85 | // /_/ \_\___|\__|_|\___/|_| |_|___/
86 | fetchCircleciNodes: async (
87 | token: string,
88 | options: IFetchOptions,
89 | ): Promise => {
90 | try {
91 | let repos: CircleciRepoDto[] = await get({
92 | url: `https://circleci.com/api/v1.1/projects?circle-token=${token}`,
93 | });
94 | return mapCircleCIProjects(repos, token, options);
95 | } catch (e) {
96 | root.ui.addToast({
97 | text: 'Could not fetch CircleCI builds',
98 | type: 'error',
99 | });
100 | return [];
101 | }
102 | },
103 |
104 | fetchAppcenterNodes: async (
105 | key: string,
106 | options: IFetchOptions,
107 | ): Promise => {
108 | try {
109 | let repos: AppcenterRepoDto[] = await get({
110 | url: `https://api.appcenter.ms/v0.1/apps`,
111 | headers: {
112 | 'X-API-Token': key,
113 | },
114 | });
115 |
116 | let branchPromises = repos.map((r) =>
117 | get({
118 | url: `https://api.appcenter.ms/v0.1/apps/${r.owner.name}/${r.name}/branches`,
119 | headers: {
120 | 'X-API-Token': key,
121 | },
122 | }),
123 | );
124 |
125 | let resolvedBranches = await allSettled(branchPromises);
126 |
127 | let nodes = resolvedBranches
128 | .map((branchesResult, ii) => {
129 | if (branchesResult.status === 'fulfilled') {
130 | return mapAppcenterTuplesToNodes(
131 | repos[ii],
132 | branchesResult.value,
133 | key,
134 | options,
135 | );
136 | } else {
137 | return [];
138 | }
139 | })
140 | .flat();
141 |
142 | return nodes;
143 | } catch (e) {
144 | root.ui.addToast({
145 | text: 'Could not fetch App Center builds',
146 | type: 'error',
147 | });
148 | return [];
149 | }
150 | },
151 |
152 | fetchTravisciNodes: async (
153 | key: string,
154 | options: IFetchOptions,
155 | ): Promise => {
156 | try {
157 | let reposDto: TravisReposDto = await get({
158 | url: `https://api.travis-ci.com/repos`,
159 | headers: {
160 | Accept: `application/vnd.travis-ci.2.1+json`,
161 | Authorization: `token ${key}`,
162 | 'User-Agent': `Tempomat/2.0.0`,
163 | },
164 | });
165 |
166 | let branchPromises = reposDto.repos.map((r) =>
167 | get({
168 | url: `https://api.travis-ci.com/repos/${r.slug}/branches`,
169 | headers: {
170 | Accept: `application/vnd.travis-ci.2.1+json`,
171 | Authorization: `token ${key}`,
172 | 'User-Agent': `Tempomat/2.0.0`,
173 | },
174 | }),
175 | );
176 |
177 | let resolvedBranches = await allSettled(branchPromises);
178 |
179 | return resolvedBranches
180 | .map((branchesRes, ii) => {
181 | if (branchesRes.status === 'fulfilled') {
182 | return mapTravisTuplesToNodes(
183 | reposDto.repos[ii],
184 | branchesRes.value,
185 | key,
186 | options,
187 | );
188 | } else {
189 | return [];
190 | }
191 | })
192 | .flat();
193 | } catch (e) {
194 | root.ui.addToast({
195 | text: 'Could not fetch CircleCI Nodes',
196 | type: 'error',
197 | });
198 | return [];
199 | }
200 | },
201 |
202 | fetchBitriseNodes: async (
203 | key: string,
204 | options: IFetchOptions,
205 | ): Promise => {
206 | try {
207 | let apps: BitrisePaginated = await get({
208 | url: `https://api.bitrise.io/v0.1/apps`,
209 | headers: {
210 | Authorization: key,
211 | },
212 | });
213 |
214 | let aWeekAgo = new Date();
215 | aWeekAgo.setDate(aWeekAgo.getDate() - 7);
216 |
217 | let buildsPromises = apps.data.map((app) =>
218 | get({
219 | url: `https://api.bitrise.io/v0.1/apps/${app.slug}/builds`,
220 | headers: {
221 | Authorization: key,
222 | },
223 | }),
224 | );
225 |
226 | let builds = await Promise.all(buildsPromises);
227 |
228 | let nodes = builds
229 | .map((bSet, idx) => {
230 | let visitedBranches = new Set();
231 |
232 | let finalBuilds = bSet.data.filter((b: any) => {
233 | if (visitedBranches.has(b.triggered_workflow)) {
234 | return false;
235 | } else {
236 | visitedBranches.add(b.triggered_workflow);
237 | return true;
238 | }
239 | });
240 |
241 | return mapBitriseTuplesToNode(
242 | apps.data[idx],
243 | finalBuilds,
244 | key,
245 | options,
246 | );
247 | })
248 | .flat();
249 |
250 | return nodes;
251 | } catch (e) {
252 | root.ui.addToast({
253 | text: 'Could not fetch Bitrise Nodes',
254 | type: 'error',
255 | });
256 | return [];
257 | }
258 | },
259 |
260 | fetchBitriseRepoBranches: async (
261 | repo: BitriseRepoDto,
262 | branchNames: string[],
263 | key: string,
264 | options: IFetchOptions,
265 | ): Promise => {
266 | try {
267 | let branchesPromises = branchNames.map((name) => {
268 | let encodedName = name.replace(`#`, `%23`).replace(`&`, `%26`);
269 |
270 | return get({
271 | url: `https://api.bitrise.io/v0.1/apps/${repo.slug}/builds?branch=${encodedName}&limit=1`,
272 | headers: {
273 | Authorization: key,
274 | },
275 | });
276 | });
277 |
278 | let resolvedBranches: Array<
279 | BitrisePaginated
280 | > = await Promise.all(branchesPromises);
281 |
282 | return resolvedBranches
283 | .map((b) => mapBitriseTuplesToNode(repo, b.data, key, options))
284 | .flat();
285 | } catch (e) {
286 | root.ui.addToast({
287 | text: `Error fetching builds for Bitrise branches: ${e}`,
288 | type: 'error',
289 | });
290 |
291 | return [];
292 | }
293 | },
294 |
295 | fetchGithubNodes: async ({
296 | key,
297 | slug,
298 | fetchPrs,
299 | fetchBranches,
300 | fetchWorkflows,
301 | }: {
302 | key: string;
303 | slug: string;
304 | fetchPrs: boolean;
305 | fetchBranches: boolean;
306 | fetchWorkflows: boolean;
307 | }): Promise => {
308 | try {
309 | const githubApi = new Github({
310 | token: key,
311 | });
312 |
313 | const [username, reponame] = slug.split(`/`);
314 |
315 | const repo = githubApi.getRepo(username, reponame);
316 |
317 | let nodes: INode[] = [];
318 |
319 | if (fetchPrs) {
320 | const pullRequestsRes = await repo.listPullRequests();
321 |
322 | const pullRequests: any[] = pullRequestsRes.data;
323 |
324 | const checkPromises = pullRequests.map((pr: any) =>
325 | get({
326 | url: `https://api.github.com/repos/${slug}/commits/${pr.head.sha}/check-runs`,
327 | headers: {
328 | Accept: `application/vnd.github.v3+json`,
329 | Authorization: `token ${key}`,
330 | },
331 | }),
332 | );
333 |
334 | const statusesMatrix = await allSettled(checkPromises);
335 |
336 | const prNodes = pullRequests.map((pr: any, index: number) => {
337 | let statusRes = statusesMatrix[index];
338 |
339 | let checks =
340 | statusRes.status === 'fulfilled'
341 | ? statusRes.value.check_runs ?? []
342 | : [];
343 |
344 | return mapGithubPrToNode(slug, pr, checks, key);
345 | });
346 |
347 | nodes.push(...prNodes);
348 | }
349 |
350 | if (fetchBranches) {
351 | const branches = await get({
352 | url: `https://api.github.com/repos/${slug}/branches?per_page=100`,
353 | headers: {
354 | Accept: `application/vnd.github.v3+json`,
355 | Authorization: `token ${key}`,
356 | },
357 | });
358 |
359 | const checkPromises = branches.map((branch: any) =>
360 | get({
361 | url: `https://api.github.com/repos/${slug}/commits/${branch.commit.sha}/check-runs`,
362 | headers: {
363 | Accept: `application/vnd.github.v3+json`,
364 | Authorization: `token ${key}`,
365 | },
366 | }),
367 | );
368 |
369 | const statusesMatrix = await allSettled(checkPromises);
370 |
371 | const branchNodes = branches.map((branch: any, index: number) => {
372 | let statusRes = statusesMatrix[index];
373 |
374 | let checks =
375 | statusRes.status === 'fulfilled'
376 | ? statusRes.value.check_runs ?? []
377 | : [];
378 |
379 | return mapGithubBranchToNode(slug, branch, checks, key);
380 | });
381 |
382 | nodes.push(...branchNodes);
383 | }
384 |
385 | if (fetchWorkflows) {
386 | const runsRes = await get({
387 | url: `https://api.github.com/repos/${slug}/actions/runs?per_page=100`,
388 | headers: {
389 | Accept: `application/vnd.github.v3+json`,
390 | Authorization: `token ${key}`,
391 | },
392 | });
393 |
394 | const visitedRuns: Record = {};
395 | const runs = runsRes.workflow_runs.reduce((acc: any[], run: any) => {
396 | if (!visitedRuns[run.workflow_id]) {
397 | visitedRuns[run.workflow_id] = true;
398 | acc.push(run);
399 | }
400 |
401 | return acc;
402 | }, []);
403 |
404 | const runsNodes: any[] = runs.map((run: any) =>
405 | mapGithubActionRunToNode(slug, run, key),
406 | );
407 |
408 | nodes.push(...runsNodes.flat());
409 | }
410 |
411 | return nodes;
412 | } catch (e) {
413 | console.warn(`Github error`, e);
414 | root.ui.addToast({
415 | text: `Could not fetch info for Github repo: ${slug}`,
416 | type: 'error',
417 | });
418 | return [];
419 | }
420 | },
421 |
422 | triggerCircleciRebuild: (node: INode) => {
423 | if (!node.buildUrl) {
424 | throw new Error(`Could not rebuild node without a known build url`);
425 | }
426 |
427 | return post({
428 | url: node.buildUrl,
429 | headers: {
430 | 'Content-Type': `application/json`,
431 | },
432 | });
433 | },
434 |
435 | triggerAppcenterRebuild: async (node: INode) => {
436 | if (!node.buildUrl) {
437 | throw new Error(`Cannot retrigger build without build url`);
438 | }
439 |
440 | return post({
441 | url: node.buildUrl,
442 | headers: {
443 | 'Content-Type': `application/json`,
444 | 'X-API-Token': node.key!,
445 | },
446 | });
447 | },
448 |
449 | triggerTravisciRebuild: async (_: INode) => {
450 | return Promise.reject(
451 | new Error(
452 | `There is currently no way to manually trigger a job on travisCI`,
453 | ),
454 | );
455 | },
456 |
457 | triggerBitriseRebuild: async (node: INode) => {
458 | if (!node.buildUrl) {
459 | throw new Error(`Cannot retrigger build without build url`);
460 | }
461 |
462 | return post({
463 | url: node.buildUrl,
464 | headers: {
465 | 'Content-Type': `application/json`,
466 | Authorization: node.key!,
467 | },
468 | body: {
469 | hook_info: {
470 | type: `bitrise`,
471 | },
472 | build_params: {
473 | branch: node.extra,
474 | },
475 | },
476 | });
477 | },
478 |
479 | fetchGitlabNodes: async (
480 | baseURL: string,
481 | visibility: GitlabVisibility,
482 | key: string,
483 | options: IFetchOptions,
484 | ): Promise => {
485 | let nodes: INode[] = [];
486 |
487 | try {
488 | let shouldFetchProjects = true;
489 | let projects: GitlabProjectDto[] = [];
490 | let idAfter: string | null = null;
491 |
492 | while (shouldFetchProjects) {
493 | let url = `${baseURL}/api/v4/projects?${visibility ? `visibility=${visibility}&` : ``}pagination=keyset&simple=true&per_page=100&order_by=id&sort=asc`;
494 | if (!!idAfter) {
495 | url += `&id_after=${idAfter}`;
496 | }
497 |
498 | let projectBatch = await get({
499 | url,
500 | headers: {
501 | Authorization: `Bearer ${key}`,
502 | },
503 | });
504 | if (projectBatch.length === 0) {
505 | shouldFetchProjects = false;
506 | } else {
507 | projects.push(...projectBatch);
508 | idAfter = projectBatch[projectBatch.length - 1].id;
509 | }
510 | }
511 |
512 | let pipelines: Promise[] = projects.map((p) =>
513 | get({
514 | url: `${baseURL}/api/v4/projects/${p.id}/pipelines?per_page=100`,
515 | headers: {
516 | Authorization: `Bearer ${key}`,
517 | },
518 | }),
519 | );
520 |
521 | let resolvedPipelines: PromiseSettledResult<
522 | GitlabPipelineDto[]
523 | >[] = await allSettled(pipelines);
524 |
525 | nodes = resolvedPipelines
526 | .map((projectPipelines, ii) => {
527 | if (projectPipelines.status === 'fulfilled') {
528 | return mapGitlabTupleToNodes(
529 | projects[ii],
530 | projectPipelines.value,
531 | key,
532 | options,
533 | );
534 | } else {
535 | return [];
536 | }
537 | })
538 | .flat();
539 | } catch (e) {
540 | root.ui.addToast({
541 | text: `Could not fetch GitLab repos:\n${e}`,
542 | type: `error`,
543 | });
544 | }
545 |
546 | return nodes;
547 | },
548 | };
549 |
550 | return store;
551 | };
552 |
553 | export type IApiStore = ReturnType;
554 |
--------------------------------------------------------------------------------
/src/store/Node.store.test.ts:
--------------------------------------------------------------------------------
1 | import {createNodeStore, SortingKey} from './Node.store';
2 | import {cidemonNative} from 'lib';
3 | import {createApiStore} from './Api.store';
4 | import {IRootStore} from 'Root.store';
5 | import {when} from 'mobx';
6 |
7 | jest.mock('../lib/CIDemonNative');
8 | jest.mock('./Api.store');
9 |
10 | let ROOT_MOCK: IRootStore = {} as any;
11 |
12 | ROOT_MOCK.api = createApiStore(ROOT_MOCK);
13 |
14 | describe(`NodeStore`, () => {
15 | let nodeStore: Awaited>;
16 |
17 | beforeEach(async () => {
18 | nodeStore = await createNodeStore(ROOT_MOCK as any);
19 | // jest.useFakeTimers();
20 | });
21 |
22 | // afterEach(async () => {
23 | // jest.clearAllTimers();
24 | // });
25 |
26 | it(`Correctly instantiated and hydrated`, () => {
27 | expect(nodeStore).not.toBeNull();
28 | // Store hydrates and persists its state
29 | expect(cidemonNative.securelyRetrieve).toBeCalled();
30 | expect(cidemonNative.securelyStore).toBeCalled();
31 | // It applies its startAtLogin value
32 | expect(cidemonNative.applyAutoLauncher).toBeCalled();
33 | });
34 |
35 | it(`can add token (and trigger fetch)`, () => {
36 | nodeStore.fetchNodes = jest.fn();
37 |
38 | let name = `CircleCISampleKey`;
39 | let token = `123456789`;
40 | nodeStore.addToken(`CircleCI`, name, token);
41 |
42 | expect(nodeStore.tokens.length).toBe(1);
43 | expect(nodeStore.tokens[0].source).toEqual(`CircleCI`);
44 | expect(nodeStore.tokens[0].name).toEqual(name);
45 | expect(nodeStore.tokens[0].key).toEqual(token);
46 |
47 | // After token has been added fetch nodes is called
48 | expect(nodeStore.fetchNodes).toHaveBeenCalled();
49 | });
50 |
51 | it(`toggles sorting in the correct order`, () => {
52 | expect(nodeStore.sortingKey).toEqual(SortingKey.status);
53 |
54 | nodeStore.toggleSorting();
55 |
56 | expect(nodeStore.sortingKey).toEqual(SortingKey.name);
57 |
58 | nodeStore.toggleSorting();
59 |
60 | expect(nodeStore.sortingKey).toEqual(SortingKey.date);
61 |
62 | nodeStore.toggleSorting();
63 |
64 | expect(nodeStore.sortingKey).toEqual(SortingKey.status);
65 | });
66 |
67 | it(`can remove token by name`, () => {
68 | let name = `CircleCISampleKey`;
69 | let token = `123456789`;
70 | nodeStore.addToken(`CircleCI`, name, token);
71 |
72 | expect(nodeStore.tokens.length).toBe(1);
73 | expect(nodeStore.tokens[0].source).toEqual(`CircleCI`);
74 | expect(nodeStore.tokens[0].name).toEqual(name);
75 | expect(nodeStore.tokens[0].key).toEqual(token);
76 |
77 | nodeStore.removeTokenByName(name);
78 |
79 | expect(nodeStore.tokens.length).toBe(0);
80 | });
81 |
82 | it(`can add a ignore regex`, () => {
83 | // should pretty much ignore everything under the sun
84 | let testRegex = `.*[A-Za-z]`;
85 | let regexAdded = nodeStore.addIgnoredRegex(testRegex, false);
86 | expect(regexAdded).toBeTruthy();
87 | expect(nodeStore.complexRegexes.length).toEqual(1);
88 | expect(nodeStore.complexRegexes[0]).toEqual({
89 | id: nodeStore.complexRegexes[0].id,
90 | regex: testRegex,
91 | inverted: false,
92 | });
93 | });
94 |
95 | // TODO: add a test to actually see if nodes are being ignored
96 | // based on the added regex
97 |
98 | it(`can remove regex pattern`, () => {
99 | let testRegex = `.*[A-Za-z]`;
100 | let regexAdded = nodeStore.addIgnoredRegex(testRegex, false);
101 |
102 | expect(regexAdded).toBeTruthy();
103 | expect(nodeStore.complexRegexes.length).toEqual(1);
104 | expect(nodeStore.complexRegexes[0]).toEqual({
105 | id: nodeStore.complexRegexes[0].id,
106 | regex: testRegex,
107 | inverted: false,
108 | });
109 |
110 | nodeStore.removeIgnoredRegex(testRegex);
111 |
112 | expect(nodeStore.complexRegexes.length).toEqual(0);
113 | });
114 |
115 | it(`does not add a INVALID regex`, () => {
116 | let invalidRegex = `*`;
117 | let regexAdded = nodeStore.addIgnoredRegex(invalidRegex, false);
118 | expect(regexAdded).toBeFalsy();
119 | });
120 |
121 | it(`fetch CircleCI nodes`, async (done) => {
122 | let name = `CircleCISampleKey`;
123 | let token = `123456789`;
124 |
125 | let fakeNode: INode = {
126 | id: 'some random node id',
127 | url: 'random url',
128 | date: 'latest date',
129 | source: `CircleCI`,
130 | label: `fake label`,
131 | status: `passed`,
132 | };
133 |
134 | (ROOT_MOCK.api.fetchCircleciNodes as jest.Mock).mockImplementationOnce(() =>
135 | Promise.resolve([fakeNode]),
136 | );
137 |
138 | nodeStore.addToken(`CircleCI`, name, token);
139 |
140 | expect(ROOT_MOCK.api.fetchCircleciNodes).toHaveBeenCalledWith(token, {
141 | showBuildNumber: false,
142 | });
143 |
144 | when(
145 | () => nodeStore.fetching === false,
146 | () => {
147 | expect(nodeStore.nodes.length).toBe(1);
148 | expect(nodeStore.nodes[0]).toEqual(fakeNode);
149 | done();
150 | },
151 | );
152 | });
153 |
154 | it(`fetch AppCenter nodes`, async (done) => {
155 | let name = `AppStore Token`;
156 | let token = `123456789`;
157 |
158 | let fakeNode: INode = {
159 | id: 'some random node id',
160 | url: 'random url',
161 | date: 'latest date',
162 | source: `AppCenter`,
163 | label: `fake label`,
164 | status: `passed`,
165 | };
166 |
167 | (ROOT_MOCK.api
168 | .fetchAppcenterNodes as jest.Mock).mockImplementationOnce(() =>
169 | Promise.resolve([fakeNode]),
170 | );
171 |
172 | nodeStore.addToken(`AppCenter`, name, token);
173 |
174 | expect(ROOT_MOCK.api.fetchAppcenterNodes).toHaveBeenCalledWith(token, {
175 | showBuildNumber: false,
176 | });
177 |
178 | when(
179 | () => nodeStore.fetching === false,
180 | () => {
181 | expect(nodeStore.nodes.length).toBe(1);
182 | expect(nodeStore.nodes[0]).toEqual(fakeNode);
183 | done();
184 | },
185 | );
186 | });
187 |
188 | it(`fetch TravisCI nodes`, async (done) => {
189 | let name = `Travis key`;
190 | let token = `123456789`;
191 |
192 | let fakeNode: INode = {
193 | id: 'some random node id',
194 | url: 'random url',
195 | date: 'latest date',
196 | source: `CircleCI`,
197 | label: `fake label`,
198 | status: `passed`,
199 | };
200 |
201 | (ROOT_MOCK.api.fetchTravisciNodes as jest.Mock).mockImplementationOnce(
202 | () => {
203 | return Promise.resolve([fakeNode]);
204 | },
205 | );
206 |
207 | nodeStore.addToken(`TravisCI`, name, token);
208 |
209 | expect(ROOT_MOCK.api.fetchTravisciNodes).toHaveBeenCalledWith(token, {
210 | showBuildNumber: false,
211 | });
212 |
213 | when(
214 | () => nodeStore.fetching === false,
215 | () => {
216 | expect(nodeStore.nodes.length).toBe(1);
217 | expect(nodeStore.nodes[0]).toEqual(fakeNode);
218 | done();
219 | },
220 | );
221 | });
222 |
223 | it(`fetch Bitrise nodes`, async (done) => {
224 | let name = `bitrise key`;
225 | let token = `123456789`;
226 |
227 | let fakeNode: INode = {
228 | id: 'some random node id',
229 | url: 'random url',
230 | date: 'latest date',
231 | source: `CircleCI`,
232 | label: `fake label`,
233 | status: `passed`,
234 | };
235 |
236 | (ROOT_MOCK.api.fetchBitriseNodes as jest.Mock).mockImplementationOnce(
237 | () => {
238 | return Promise.resolve([fakeNode]);
239 | },
240 | );
241 |
242 | nodeStore.addToken(`Bitrise`, name, token);
243 |
244 | expect(ROOT_MOCK.api.fetchBitriseNodes).toHaveBeenCalledWith(token, {
245 | showBuildNumber: false,
246 | });
247 |
248 | when(
249 | () => nodeStore.fetching === false,
250 | () => {
251 | expect(nodeStore.nodes.length).toBe(1);
252 | expect(nodeStore.nodes[0]).toEqual(fakeNode);
253 | done();
254 | },
255 | );
256 | });
257 |
258 | it(`fetch Gitlab nodes`, async (done) => {
259 | let name = `Gitlab key`;
260 | let token = `123456789`;
261 | let baseURL = `https://gitlab.com`;
262 | let visibility: GitlabVisibility = `private`;
263 |
264 | let fakeNode: INode = {
265 | id: 'some random node id',
266 | url: 'random url',
267 | date: 'latest date',
268 | source: `Gitlab`,
269 | label: `fake label`,
270 | status: `passed`,
271 | };
272 |
273 | (ROOT_MOCK.api.fetchGitlabNodes as jest.Mock).mockImplementationOnce(() => {
274 | return Promise.resolve([fakeNode]);
275 | });
276 |
277 | nodeStore.addToken(`Gitlab`, name, token, baseURL, visibility);
278 |
279 | expect(ROOT_MOCK.api.fetchGitlabNodes).toHaveBeenCalledWith(baseURL, visibility, token, {
280 | showBuildNumber: false,
281 | });
282 |
283 | when(
284 | () => nodeStore.fetching === false,
285 | () => {
286 | expect(nodeStore.nodes.length).toBe(1);
287 | expect(nodeStore.nodes[0]).toEqual(fakeNode);
288 | done();
289 | },
290 | );
291 | });
292 | });
293 |
--------------------------------------------------------------------------------
/src/store/Ping.store.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ospfranco/cidemon/8bdd6f9449f98c9e045796c1cb7d195e8c882f78/src/store/Ping.store.ts
--------------------------------------------------------------------------------
/src/store/UI.store.ts:
--------------------------------------------------------------------------------
1 | import { makeAutoObservable, runInAction } from 'mobx';
2 | import { IRootStore } from 'Root.store';
3 |
4 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
5 | export let createUIStore = (root: IRootStore) => {
6 | let store = makeAutoObservable({
7 | toasts: [] as IToast[],
8 | addToast: (toast: IToast) => {
9 | store.toasts.push(toast);
10 |
11 | setTimeout(() => {
12 | runInAction(() => {
13 | store.toasts.pop();
14 | });
15 | }, 6000);
16 | },
17 | clearToasts: () => {
18 | store.toasts = []
19 | }
20 | // checkForUpdates: () => {
21 | // cidemonNative.checkForUpdates();
22 | // },
23 | });
24 |
25 | return store;
26 | };
27 |
--------------------------------------------------------------------------------
/src/store/__mocks__/Api.store.ts:
--------------------------------------------------------------------------------
1 | import { IRootStore } from "Root.store";
2 |
3 | export let createApiStore = (_: IRootStore) => {
4 | let store = {
5 | fetchCircleciNodes: jest.fn(),
6 |
7 | fetchAppcenterNodes: jest.fn(),
8 |
9 | fetchTravisciNodes: jest.fn(),
10 |
11 | fetchBitriseNodes: jest.fn(),
12 |
13 | fetchBitriseRepoBranches: jest.fn(),
14 |
15 | fetchGithubNodes: jest.fn(),
16 |
17 | triggerCircleciRebuild: jest.fn(),
18 |
19 | triggerAppcenterRebuild: jest.fn(),
20 |
21 | triggerTravisciRebuild: jest.fn(),
22 |
23 | triggerBitriseRebuild: jest.fn(),
24 |
25 | fetchGitlabNodes: jest.fn(),
26 | };
27 |
28 | return store;
29 | };
30 |
--------------------------------------------------------------------------------
/src/store/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./Api.store"
2 | export * from "./Node.store"
3 | export * from "./UI.store"
4 |
--------------------------------------------------------------------------------
/src/tailwind.ts:
--------------------------------------------------------------------------------
1 | import {create} from 'tailwind-rn';
2 | import styles from 'styles.json';
3 | import classNames from 'classnames';
4 |
5 | const {tailwind, getColor} = create(styles);
6 |
7 | export const cw = getColor;
8 | export const tw = (...styles: any[]) => tailwind(classNames(...styles));
9 |
--------------------------------------------------------------------------------
/src/typings.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable eslint-comments/no-unlimited-disable */
2 | /* eslint-disable */
3 |
4 | declare module 'github-api';
5 | declare module 'base-64';
6 | declare module 'promise.allsettled' {
7 | function allSettled(
8 | promises: Promise[],
9 | ): Array<{status: 'fulfilled'; value: T} | {status: 'rejected'; reason: any}>;
10 | export = allSettled;
11 | }
12 |
13 | type Awaited> = T extends Promise ? U : never;
14 |
15 | interface IMetrics {
16 | pxs: number;
17 | ps: number;
18 | pm: number;
19 | pxm: number;
20 | pl: number;
21 | ts: number;
22 | tm: number;
23 | tl: number;
24 | imgSmall: number;
25 | imgMedium: number;
26 | imgLarge: number;
27 | }
28 |
29 | declare var global: {
30 | metrics: IMetrics;
31 | colors: any;
32 | os: 'ios' | 'macos' | 'android' | 'web' | 'windows';
33 | };
34 |
35 | declare module '*.png';
36 | declare module '*.jpg';
37 | declare module '*.jpeg';
38 | declare interface IIgnoreRegex {
39 | id: string;
40 | regex: string;
41 | inverted: boolean;
42 | }
43 |
44 | declare type Source =
45 | | `CircleCI`
46 | | `AppCenter`
47 | | `TravisCI`
48 | | `Bitrise`
49 | | `Github`
50 | | `Gitlab`;
51 |
52 | declare type Status = `pending` | `passed` | `running` | `failed`;
53 |
54 | declare type VCS = `github` | `bitbucket` | `gitlab` | `unknown`;
55 |
56 | declare type GitlabVisibility = `public` | `internal` | `private` | null;
57 |
58 | declare interface IToken {
59 | source: Source;
60 | name: string;
61 | baseURL?: string | null;
62 | visibility?: GitlabVisibility;
63 | key: string;
64 | }
65 |
66 | declare interface ISubNode {
67 | label: string;
68 | extraLabel?: string | null;
69 | status: Status;
70 | url?: string;
71 | }
72 |
73 | declare interface INode {
74 | id: string;
75 | source: Source;
76 | url: string;
77 | label: string;
78 | status: Status;
79 | vcs?: VCS;
80 | key?: string;
81 | buildUrl?: string;
82 | date?: string;
83 | jobId?: string;
84 | // used for some extra field needed to make requests to the api
85 | extra?: string;
86 | subItems?: ISubNode[];
87 | // added fields for small nice things
88 | sha?: string;
89 | isPr?: boolean;
90 | isAction?: boolean;
91 | isBranch?: boolean;
92 | username?: string;
93 | userAvatarUrl?: string;
94 | slug?: string;
95 | }
96 |
97 | declare interface IToast {
98 | text: string;
99 | type: `success` | `error` | `neutral`;
100 | position?: `top` | `bottom`;
101 | }
102 |
103 | declare interface IPingTestDto {
104 | id?: string;
105 | name?: string;
106 | url?: string;
107 | method?: `GET` | `POST` | `PUT`;
108 | expectedStatus?: number | null;
109 | expectedResponse?: string | null;
110 | }
111 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | const colors = require('tailwindcss/colors');
2 |
3 | module.exports = {
4 | purge: [],
5 | darkMode: false, // or 'media' or 'class'
6 | theme: {
7 | extend: {
8 | colors: {...colors},
9 | pass: {
10 | 50: '#F0F9F8',
11 | 100: '#E8F9FA',
12 | 200: '#99F6E4',
13 | 300: '#5EEAD4',
14 | 400: '#2DD4BF',
15 | 500: '#16BDCA',
16 | 600: '#0891B2',
17 | 700: '#0F766E',
18 | 800: '#1E383C',
19 | 900: '#1E3034',
20 | },
21 | },
22 | },
23 | variants: {
24 | extend: {},
25 | },
26 | plugins: [],
27 | };
28 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "compilerOptions": {
4 | /* Basic Options */
5 | "target": "ESNEXT", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
6 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
7 | "lib": [
8 | "ES2020"
9 | ], /* Specify library files to be included in the compilation. */
10 | // "allowJs": false, /* Allow javascript files to be compiled. */
11 | // "checkJs": true, /* Report errors in .js files. */
12 | "jsx": "react-native", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
13 | // "declaration": true, /* Generates corresponding '.d.ts' file. */
14 | // "sourceMap": true, /* Generates corresponding '.map' file. */
15 | // "outFile": "./", /* Concatenate and emit output to single file. */
16 | // "outDir": "./", /* Redirect output structure to the directory. */
17 | "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
18 | "removeComments": true, /* Do not emit comments to output. */
19 | "noEmit": true, /* Do not emit outputs. */
20 | // "incremental": true, /* Enable incremental compilation */
21 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */
22 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
23 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
24 |
25 | /* Strict Type-Checking Options */
26 | "strict": true, /* Enable all strict type-checking options. */
27 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
28 | // "strictNullChecks": true, /* Enable strict null checks. */
29 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */
30 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
31 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
32 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
33 |
34 | /* Additional Checks */
35 | // "noUnusedLocals": true, /* Report errors on unused locals. */
36 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
37 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
38 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
39 |
40 | /* Module Resolution Options */
41 | "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
42 | "baseUrl": "./src", /* Base directory to resolve non-absolute module names. */
43 | // "paths": { }, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
44 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
45 | // "typeRoots": ["./src/typings", "node_modules/@types"], /* List of folders to include type definitions from. */
46 | // "types": ["jest"], /* Type declaration files to be included in compilation. */
47 | "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
48 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
49 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
50 |
51 | /* Source Map Options */
52 | // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
53 | // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */
54 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
55 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
56 |
57 | /* Experimental Options */
58 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
59 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
60 | "useDefineForClassFields": true, /* required for mobx 6*/
61 | "resolveJsonModule": true,
62 | "skipLibCheck": true
63 | },
64 | "exclude": [
65 | "node_modules", "babel.config.js", "metro.config.js", "jest.config.js"
66 | ]
67 | }
68 |
--------------------------------------------------------------------------------