├── .circleci
└── config.yml
├── .github
├── FUNDING.yml
└── workflows
│ ├── macOS.yml
│ └── ubuntu.yml
├── .gitignore
├── .gitmodules
├── .hound.yml
├── .swift-version
├── .swiftformat
├── .swiftlint.yml
├── .travis.yml
├── Brewfile
├── Documentation
└── Reference
│ ├── README.md
│ └── structs
│ └── ArchiveItem.md
├── LICENSE
├── Package.swift
├── README.md
├── Scripts
├── before_install.sh
└── script.sh
├── Sources
├── EggSeedKit
│ ├── Controllers
│ │ ├── ArchiveExpander.swift
│ │ ├── EggSeedRunner.swift
│ │ ├── LicenseDownloadIssuer.swift
│ │ ├── ProcessGitterface.swift
│ │ ├── ProcessLauncher.swift
│ │ ├── ProcessPackageFactory.swift
│ │ └── URLDownloader.swift
│ ├── EggSeed.swift
│ ├── Extensions
│ │ ├── Process.swift
│ │ └── Result.swift
│ ├── Models
│ │ ├── ArchiveEntry.swift
│ │ ├── CISystem.swift
│ │ ├── CPU.swift
│ │ ├── EggSeedError.swift
│ │ ├── LauncherError.swift
│ │ ├── License.swift
│ │ ├── Platform.swift
│ │ ├── SupportedPlatforms.swift
│ │ └── SwiftPackageType.swift
│ └── Protocols
│ │ ├── Cancellable.swift
│ │ ├── Downloader.swift
│ │ ├── EggSeedConfiguration.swift
│ │ ├── Expander.swift
│ │ ├── ExpansionEntry.swift
│ │ ├── Gitterface.swift
│ │ ├── Launcher.swift
│ │ ├── LicenseIssuer.swift
│ │ ├── PackageFactory.swift
│ │ ├── Runner.swift
│ │ └── Session.swift
└── eggseed
│ └── main.swift
├── Tests
├── EggSeedTests
│ ├── EggSeedTests.swift
│ └── XCTestManifests.swift
└── LinuxMain.swift
├── bitrise.yml
├── codecov.yml
├── eggseed.png
├── eggseed.svg
├── setup.sh.reference
└── word.svg
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | # For a detailed guide to building and testing on iOS, read the docs:
2 | # https://circleci.com/docs/2.0/testing-ios/
3 |
4 | version: 2.1
5 | parameters:
6 | package-name:
7 | type: string
8 | default: "EggSeed"
9 | swift-ver:
10 | type: string
11 | default: "5.2.4"
12 | codecov-upload-file:
13 | type: string
14 | default: "info.lcov"
15 | orbs:
16 | codecov: codecov/codecov@1.0.5
17 | jobs:
18 | build-xenial:
19 | machine:
20 | image: ubuntu-1604:201903-01
21 | environment:
22 | PACKAGE_NAME: << pipeline.parameters.package-name >>
23 | SWIFT_VER: << pipeline.parameters.swift-ver >>
24 | steps:
25 | - checkout
26 | - run:
27 | name: Update PATH and Define Environment Variable at Runtime
28 | command: |
29 | echo 'export RELEASE_DOT=$(lsb_release -sr)' >> $BASH_ENV
30 | echo 'export RELEASE_NAME=$(lsb_release -sc)' >> $BASH_ENV
31 | echo 'export RELEASE_NUM=${RELEASE_DOT//[-._]/}' >> $BASH_ENV
32 | source $BASH_ENV
33 | - run:
34 | name: Download Swift 5.2
35 | command: wget -q https://swift.org/builds/swift-${SWIFT_VER}-release/ubuntu${RELEASE_NUM}/swift-${SWIFT_VER}-RELEASE/swift-${SWIFT_VER}-RELEASE-ubuntu${RELEASE_DOT}.tar.gz
36 | - run:
37 | name: Extract Swift 5.2
38 | command: tar xzf swift-${SWIFT_VER}-RELEASE-ubuntu${RELEASE_DOT}.tar.gz
39 | - run:
40 | name: Add Path
41 | command: echo 'export PATH=${PWD}/swift-${SWIFT_VER}-RELEASE-ubuntu${RELEASE_DOT}/usr/bin:$PATH' >> $BASH_ENV
42 | - run:
43 | name: Resolve
44 | command: swift package resolve
45 | - run:
46 | name: Build
47 | command: swift build -v
48 | - run:
49 | name: Run tests
50 | command: swift test --enable-test-discovery --enable-code-coverage
51 | - run:
52 | name: Prepare Code Coverage
53 | command: llvm-cov export -format="lcov" .build/x86_64-unknown-linux-gnu/debug/${PACKAGE_NAME}PackageTests.xctest -instr-profile .build/debug/codecov/default.profdata > info.lcov
54 | - codecov/upload:
55 | file: << pipeline.parameters.codecov-upload-file >>
56 | flags: circleci,${RELEASE_NAME}
57 | workflows:
58 | version: 2
59 | build:
60 | jobs:
61 | - build-xenial
62 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: brightdigit
4 |
--------------------------------------------------------------------------------
/.github/workflows/macOS.yml:
--------------------------------------------------------------------------------
1 | name: macOS
2 |
3 | on:
4 | push:
5 | branches:
6 | - '*'
7 | - 'feature/*'
8 | - 'release/*'
9 | tags: '*'
10 |
11 | jobs:
12 | build:
13 | env:
14 | PACKAGE_NAME: EggSeed
15 |
16 | runs-on: macos-latest
17 | if: "!contains(github.event.head_commit.message, 'ci skip')"
18 |
19 | strategy:
20 | matrix:
21 | runs-on: [macos-10.15,macos-11.0]
22 | xcode: ["/Applications/Xcode_11.7.app","/Applications/Xcode_12.app","/Applications/Xcode_12.1.app","/Applications/Xcode_12.2.app","/Applications/Xcode_12.3.app"]
23 | include:
24 | - os: macos-10.15
25 | xcode: "/Applications/Xcode_11.5.app"
26 |
27 | steps:
28 | - uses: actions/checkout@v2
29 | - name: Set Xcode Name
30 | run: echo "XCODE_NAME=$(basename -- ${{ matrix.xcode }} | sed 's/\.[^.]*$//' | cut -d'_' -f2)" >> $GITHUB_ENV
31 | - name: Setup Xcode
32 | run: sudo xcode-select -s ${{ matrix.xcode }}/Contents/Developer
33 | - name: Build
34 | run: swift build
35 | - name: Lint
36 | if: startsWith(github.ref, 'refs/tags/') != true
37 | run: swift run swiftformat --lint . && swift run swiftlint
38 | - name: Run tests
39 | run: swift test -v --enable-code-coverage
40 | - name: Prepare Code Coverage
41 | run: xcrun llvm-cov export -format="lcov" .build/debug/${{ env.PACKAGE_NAME }}PackageTests.xctest/Contents/MacOS/${{ env.PACKAGE_NAME }}PackageTests -instr-profile .build/debug/codecov/default.profdata > info.lcov
42 | - name: Upload to CodeCov.io
43 | run: bash <(curl https://codecov.io/bash) -F github -F macOS -F ${{ matrix.runs-on }} -n ${{ github.sha }}
44 | env:
45 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
46 | - name: Build Documentation
47 | if: ${{ matrix.os == 'macos-11.0' && matrix.xcode == '/Applications/Xcode_12.3.app' && !startsWith(github.ref, 'refs/tags/') }}
48 | run: |
49 | swift run sourcedocs generate build -cra
50 | git config --local user.email "action@github.com"
51 | git config --local user.name "GitHub Action"
52 | git status
53 | git add Documentation
54 | git diff-index --quiet HEAD || git commit -m "[github action] Update Docs"
55 | git push
56 |
--------------------------------------------------------------------------------
/.github/workflows/ubuntu.yml:
--------------------------------------------------------------------------------
1 | name: ubuntu
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 | env:
8 | PACKAGE_NAME: EggSeed
9 | SWIFT_VER: ${{ matrix.swift-version }}
10 |
11 | runs-on: ${{ matrix.runs-on }}
12 | if: "!contains(github.event.head_commit.message, 'ci skip')"
13 |
14 | strategy:
15 | matrix:
16 | runs-on: [ubuntu-18.04,ubuntu-20.04]
17 | swift-version: [5.2.4, 5.3.1]
18 | steps:
19 | - uses: actions/checkout@v2
20 | - name: Set Ubuntu Release DOT
21 | run: echo "RELEASE_DOT=$(lsb_release -sr)" >> $GITHUB_ENV
22 | - name: Set Ubuntu Release NUM
23 | run: echo "RELEASE_NUM=${RELEASE_DOT//[-._]/}" >> $GITHUB_ENV
24 | - name: Set Ubuntu Codename
25 | run: echo "RELEASE_NAME=$(lsb_release -sc)" >> $GITHUB_ENV
26 | - name: Download Swift
27 | run: curl -O https://swift.org/builds/swift-${SWIFT_VER}-release/ubuntu${RELEASE_NUM}/swift-${SWIFT_VER}-RELEASE/swift-${SWIFT_VER}-RELEASE-ubuntu${RELEASE_DOT}.tar.gz
28 | - name: Extract Swift
29 | run: tar xzf swift-${SWIFT_VER}-RELEASE-ubuntu${RELEASE_DOT}.tar.gz
30 | - name: Add Path
31 | run: echo "$GITHUB_WORKSPACE/swift-${SWIFT_VER}-RELEASE-ubuntu${RELEASE_DOT}/usr/bin" >> $GITHUB_PATH
32 | - name: Build
33 | run: swift build
34 | - name: Run tests
35 | run: swift test --enable-test-discovery --enable-code-coverage
36 | - name: Prepare Code Coverage
37 | run: llvm-cov export -format="lcov" .build/x86_64-unknown-linux-gnu/debug/${{ env.PACKAGE_NAME }}PackageTests.xctest -instr-profile .build/debug/codecov/default.profdata > info.lcov
38 | - name: Upload to CodeCov.io
39 | run: bash <(curl https://codecov.io/bash) -F github -F ${RELEASE_NAME} -F ${SWIFT_VER} -n ${{ github.sha }}
40 | env:
41 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
42 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.gitignore.io/api/swiftpm,swiftpackagemanager,xcode,swift,macos
2 | # Edit at https://www.gitignore.io/?templates=swiftpm,swiftpackagemanager,xcode,swift,macos
3 |
4 |
5 | ### macOS ###
6 | # General
7 | .DS_Store
8 | *.DS_Store
9 |
10 | .AppleDouble
11 | .LSOverride
12 |
13 | # Icon must end with two \r
14 | Icon
15 |
16 | # Thumbnails
17 | ._*
18 |
19 | # Files that might appear in the root of a volume
20 | .DocumentRevisions-V100
21 | .fseventsd
22 | .Spotlight-V100
23 | .TemporaryItems
24 | .Trashes
25 | .VolumeIcon.icns
26 | .com.apple.timemachine.donotpresent
27 |
28 | # Directories potentially created on remote AFP share
29 | .AppleDB
30 | .AppleDesktop
31 | Network Trash Folder
32 | Temporary Items
33 | .apdisk
34 |
35 | ### Swift ###
36 | # Xcode
37 | #
38 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
39 |
40 | ## Build generated
41 | build/
42 | DerivedData/
43 |
44 | ## Various settings
45 | # Xcode
46 | #
47 | build/
48 |
49 | *.pbxuser
50 | !default.pbxuser
51 | *.mode1v3
52 | !default.mode1v3
53 | *.mode2v3
54 | !default.mode2v3
55 | *.perspectivev3
56 | !default.perspectivev3
57 | xcuserdata/
58 |
59 | ## Other
60 | *.moved-aside
61 | *.xccheckout
62 | *.xcuserstate
63 |
64 | *.xcscmblueprint
65 |
66 | ## Obj-C/Swift specific
67 | *.hmap
68 | *.ipa
69 | *.dSYM.zip
70 | *.dSYM
71 |
72 | ## Playgrounds
73 | timeline.xctimeline
74 | playground.xcworkspace
75 |
76 | # Swift Package Manager
77 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
78 | # Packages/
79 | # Package.pins
80 | # Package.resolved
81 | .build/
82 | xcuserdata
83 | *.xccheckout
84 | *.moved-aside
85 | DerivedData
86 | *.hmap
87 | *.ipa
88 | *.xcuserstate
89 |
90 | # CocoaPods
91 |
92 | # We recommend against adding the Pods directory to your .gitignore. However
93 | # you should judge for yourself, the pros and cons are mentioned at:
94 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
95 |
96 | Example/Pods/
97 | Example/*.xcworkspace
98 |
99 | # Carthage
100 |
101 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
102 | # Carthage/Checkouts
103 |
104 | Carthage/Build
105 |
106 | # Accio dependency management
107 | Dependencies/
108 | .accio/
109 |
110 | # fastlane
111 |
112 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
113 | # screenshots whenever they are needed.
114 | # For more information about the recommended setup visit:
115 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
116 |
117 | fastlane/report.xml
118 | fastlane/Preview.html
119 | fastlane/screenshots/**/*.png
120 | fastlane/test_output
121 |
122 | # Code Injection
123 | # After new code Injection tools there's a generated folder /iOSInjectionProject
124 | # https://github.com/johnno1962/injectionforxcode
125 | VCS.swift
126 |
127 | iOSInjectionProject/
128 | Products
129 |
130 |
131 | ### SwiftPackageManager ###
132 | Packages
133 | xcuserdata
134 | /*.xcodeproj
135 | .tmp
136 |
137 |
138 | ### SwiftPM ###
139 |
140 |
141 | ### Xcode ###
142 | # Xcode
143 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
144 |
145 | ## User settings
146 |
147 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
148 |
149 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
150 |
151 | ## Xcode Patch
152 | /*.xcodeproj/*
153 | .swiftpm
154 |
155 | !*.xcodeproj/project.pbxproj
156 | !*.xcodeproj/xcshareddata/
157 | !*.xcworkspace/contents.xcworkspacedata
158 | /*.gcno
159 |
160 | ### Xcode Patch ###
161 | **/xcshareddata/WorkspaceSettings.xcsettings
162 |
163 | # End of https://www.gitignore.io/api/swiftpm,swiftpackagemanager,xcode,swift,macos
164 | *.xcworkspace
165 | Pods
166 | *.lcov
167 | Brewfile.lock.json
168 | Example
169 | Package.resolved
170 | Sample
171 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "Template"]
2 | path = Template
3 | url = https://github.com/brightdigit/eggseed-template
4 | branch = feature/templating
5 |
--------------------------------------------------------------------------------
/.hound.yml:
--------------------------------------------------------------------------------
1 | swiftlint:
2 | config_file: .swiftlint.yml
3 |
--------------------------------------------------------------------------------
/.swift-version:
--------------------------------------------------------------------------------
1 | 5
2 |
--------------------------------------------------------------------------------
/.swiftformat:
--------------------------------------------------------------------------------
1 | --indent 2
2 | --header strip
3 | --commas inline
4 | --exclude .build, DerivedData
5 |
--------------------------------------------------------------------------------
/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | cyclomatic_complexity: 12
2 | file_length: 550
3 | function_body_length: 80
4 | function_parameter_count: 8
5 | line_length: 150
6 | type_body_length: 300
7 | identifier_name:
8 | excluded: # excluded via string array
9 | - id
10 | excluded:
11 | - Tests/*/XCTestManifests.swift
12 | - DerivedData
13 | - .build
14 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | jobs:
2 | include:
3 | - os: linux
4 | dist: bionic
5 | arch: amd64
6 | - os: linux
7 | dist: focal
8 | arch: amd64
9 | - os: linux
10 | dist: bionic
11 | arch: arm64
12 | - os: linux
13 | dist: focal
14 | arch: arm64
15 | - os: osx
16 | osx_image: xcode11.4
17 | - os: osx
18 | osx_image: xcode11.5
19 | - os: osx
20 | osx_image: xcode11.6
21 | - os: osx
22 | osx_image: xcode12
23 | env:
24 | global:
25 | - FRAMEWORK_NAME=EggSeed
26 | - SWIFT_VER=5.2.4
27 | before_install:
28 | - bash -e ./Scripts/before_install.sh
29 | script:
30 | - bash -e ./Scripts/script.sh
31 |
--------------------------------------------------------------------------------
/Brewfile:
--------------------------------------------------------------------------------
1 | brew "swiftformat"
2 | brew "swiftlint"
3 | brew "sourcedocs"
4 |
--------------------------------------------------------------------------------
/Documentation/Reference/README.md:
--------------------------------------------------------------------------------
1 | # Reference Documentation
2 |
3 | This file was generated by [SourceDocs](https://github.com/eneko/SourceDocs)
--------------------------------------------------------------------------------
/Documentation/Reference/structs/ArchiveItem.md:
--------------------------------------------------------------------------------
1 | **STRUCT**
2 |
3 | # `ArchiveItem`
4 |
5 | ```swift
6 | public struct ArchiveItem: TemplateExtractorItem
7 | ```
8 |
9 | ## Properties
10 | ### `data`
11 |
12 | ```swift
13 | public let data: Data
14 | ```
15 |
16 | ### `relativePath`
17 |
18 | ```swift
19 | public let relativePath: String
20 | ```
21 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 brightdigit
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 |
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.2
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "EggSeed",
8 | platforms: [
9 | .macOS(.v10_13)
10 | ],
11 | products: [
12 | // Products define the executables and libraries produced by a package, and make them visible to other packages.
13 | .executable(
14 | name: "eggseed",
15 | targets: ["eggseed"]
16 | ),
17 | .library(name: "EggSeedKit", targets: ["EggSeedKit"])
18 | ],
19 | dependencies: [
20 | .package(url: "https://github.com/apple/swift-argument-parser", from: "0.0.1"),
21 | .package(url: "https://github.com/weichsel/ZIPFoundation", .upToNextMajor(from: "0.9.0")),
22 | .package(url: "https://github.com/mxcl/PromiseKit.git", from: "7.0.0-alpha.3"),
23 | .package(name: "SwiftSyntax", url: "https://github.com/apple/swift-syntax.git", .exact("0.50200.0")),
24 | .package(name: "mustache", url: "https://github.com/AlwaysRightInstitute/mustache", from: "0.5.9"),
25 | .package(url: "https://github.com/shibapm/Komondor", from: "1.0.6"), // dev
26 | .package(url: "https://github.com/eneko/SourceDocs", from: "1.2.1"), // dev
27 | .package(url: "https://github.com/nicklockwood/SwiftFormat", from: "0.47.0"), // dev
28 | .package(url: "https://github.com/realm/SwiftLint", from: "0.41.0"), // dev
29 | .package(url: "https://github.com/brightdigit/Rocket", .branch("feature/yams-4.0.0")), // dev
30 | .package(url: "https://github.com/mattpolzin/swift-test-codecov", .branch("master")) // dev
31 | ],
32 | targets: [
33 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
34 | // Targets can depend on other targets in this package, and on products in packages which this package depends on.
35 | .target(
36 | name: "eggseed",
37 | dependencies: [
38 | "EggSeedKit"
39 | ]
40 | ),
41 | .target(
42 | name: "EggSeedKit",
43 | dependencies: [
44 | .product(name: "ArgumentParser", package: "swift-argument-parser"),
45 | "ZIPFoundation", "PromiseKit", "SwiftSyntax", "mustache"
46 | ]
47 | ),
48 | .testTarget(
49 | name: "EggSeedTests",
50 | dependencies: ["eggseed"]
51 | )
52 | ]
53 | )
54 |
55 | #if canImport(PackageConfig)
56 | import PackageConfig
57 |
58 | let config = PackageConfiguration([
59 | "komondor": [
60 | "pre-push": "swift test --enable-code-coverage --enable-test-discovery",
61 | "pre-commit": [
62 | "swift test --enable-code-coverage --enable-test-discovery --generate-linuxmain",
63 | "swift run swiftformat .",
64 | "swift run swiftlint autocorrect",
65 | "swift run sourcedocs generate build -cra",
66 | "git add .",
67 | "swift run swiftformat --lint .",
68 | "swift run swiftlint"
69 | ]
70 | ]
71 | ]).write()
72 | #endif
73 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | [](https://swift.org)
4 | [](http://twitter.com/leogdion)
5 | 
6 | 
7 |
8 | [](https://github.com/brightdigit/EggSeed/actions?query=workflow%3AmacOS)
9 | [](https://github.com/brightdigit/EggSeed/actions?query=workflow%3Aubuntu)
10 | [](https://github.com/brightdigit/EggSeed/actions?query=workflow%3Aarm)
11 | [](https://travis-ci.com/brightdigit/EggSeed)
12 | [](https://circleci.com/gh/brightdigit/EggSeed)
13 | [](https://app.bitrise.io/app/238176596b2afbd3)
14 |
15 | [](https://codecov.io/gh/brightdigit/EggSeed)
16 | [](https://www.codefactor.io/repository/github/brightdigit/EggSeed)
17 | [](https://codebeat.co/projects/github-com-brightdigit-EggSeed-master)
18 | [](https://codeclimate.com/github/brightdigit/EggSeed)
19 | [](https://codeclimate.com/github/brightdigit/EggSeed)
20 | [](https://codeclimate.com/github/brightdigit/EggSeed)
21 | [](https://houndci.com)
22 |
23 | ---
24 |
25 | **EggSeed** is a command-line tool for creating swift pacakges with continous integration support. While `swift package init`, creates simple packages, there is no guarantee that your package will work on everyone else's device. That's where _continuous integration_ goes in.
26 |
27 | By using `eggseed`, you can create a package with full integration into CI services such as: _[GitHub Actions](https://github.com/actions), [Travis-CI](https://travis-ci.com/), [BitRise](https://www.bitrise.io), [CircleCI](https://circleci.com)_ and more. Not only that but **EggSeed** also sets up code documentation, linting, and more...
28 |
29 | Check out the [roadmap below](#roadmap) for more details on future integrations.
30 |
31 | # Installation
32 |
33 | ### [Mint](https://github.com/yonaskolb/mint)
34 | ```sh
35 | mint install brightdigit/EggSeed
36 | ```
37 |
38 | ### Swift Package Manager
39 |
40 | **Use as CLI**
41 |
42 | ```shell
43 | git clone https://github.com/brightdigit/EggSeed.git
44 | cd EggSeed
45 | swift run eggseed
46 | ```
47 |
48 | **Use as dependency**
49 |
50 | Add the following to your Package.swift file's dependencies:
51 |
52 | ```swift
53 | .package(url: "https://github.com/brightdigit/EggSeed.git", from: "0.2.0"),
54 | ```
55 |
56 | And then import wherever needed: `import EggSeed`
57 |
58 | # Usage
59 |
60 | ```
61 | USAGE: eggseed [--package-type ] [--user-name ] [--path ]
62 |
63 | OPTIONS:
64 | --package-type
65 | Swift Package Type (default: library)
66 | --user-name User name or Owner of Repostory.
67 | --path Root path of the Swift Package.
68 | -h, --help Show help information.
69 | ```
70 |
71 | **Eggseed** can be run without any options. However there are a few options which can help customize your package:
72 |
73 | ## Package Type `--package-type` (library or executable)
74 |
75 | Desginates what type of package you are creating.
76 |
77 | ## User Name `--user-name`
78 |
79 | The owner to user name of the repository. If not specified, EggSeed will attempt to parse the URL for that information.
80 |
81 | ## Path `--path`
82 |
83 | Directory to create the Swift Package in, otherwise use the current directory.
84 |
85 | # Documentation
86 |
87 | All code [documentation is here.](/Documentation/Reference/README.md)
88 |
89 | # Roadmap
90 |
91 | Future Released Will Include:
92 |
93 | * Choosing a License ([MIT](https://choosealicense.com/licenses/mit/), [GNU GPLv3](https://choosealicense.com/licenses/gpl-3.0/), [Apache License 2.0](https://choosealicense.com/licenses/apache-2.0/), etc...)
94 | * Choosing Target OS and Version for CI (macOS v10_12, watchOS v6_2, Ubuntu Focal, iOS 12, etc...)
95 | * Choosing CI Services ([GitHub Actions](https://github.com/actions), [Travis-CI](https://travis-ci.com/), [BitRise](https://www.bitrise.io), [CircleCI](https://circleci.com), etc...)
96 | * Custom Template URLs
97 | * Adding [Cocoapod](https://cocoapods.org) Support
98 | * Adding [Carthage](https://github.com/Carthage/Carthage) Support
99 | * Adding [Homebrew](https://brew.sh) Support
100 | * Choosing Code Documentation Tool ([SourceDocs](https://github.com/eneko/SourceDocs), [Jazzy](https://github.com/realm/jazzy), etc...)
101 | * Choosing Linting Support ([SwiftFormat](https://github.com/nicklockwood/SwiftFormat), [SwiftLint](https://github.com/realm/SwiftLint), etc...)
102 | * Allow For Multiple Products On Setup
103 | * Choosing Architecture Support (amd64, aarch64, etc...)
104 | * Support for [Komondor](https://github.com/shibapm/Komondor)
105 | * Support for [Rocket](https://github.com/shibapm/Rocket)
106 | * Support for [Swift Package Index](https://swiftpackageindex.com)
107 | * Automated Code Quality Integrations ([codebeat](https://codebeat.co), [Code Climate](https://codeclimate.com), [Code Factory](https://www.codefactor.io), etc...)
108 | * README template and badges
109 |
110 | Feel free to [add an issue for any suggestions](https://github.com/brightdigit/EggSeed/issues).
111 |
112 | ## Contact
113 |
114 | Follow and contact me on [Twitter](http://twitter.com/leogdion). If you find an issue,
115 | just [open a ticket](https://github.com/brightdigit/EggSeed/issues/new) on it. Pull
116 | requests are warmly welcome as well.
117 |
118 | # License
119 |
120 | EggSeed is licensed under the MIT license. See [LICENSE](LICENSE) for more info.
121 |
--------------------------------------------------------------------------------
/Scripts/before_install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | if [[ $TRAVIS_OS_NAME = 'osx' ]]; then
4 | :
5 | elif [[ $TRAVIS_OS_NAME = 'linux' ]]; then
6 | RELEASE_DOT=$(lsb_release -sr)
7 | RELEASE_NUM=${RELEASE_DOT//[-._]/}
8 |
9 | if [[ $RELEASE_DOT == "20.04" ]]; then
10 | sudo apt-get update
11 | sudo apt-get -y install \
12 | binutils \
13 | git \
14 | gnupg2 \
15 | libc6-dev \
16 | libcurl4 \
17 | libedit2 \
18 | libgcc-9-dev \
19 | libpython2.7 \
20 | libsqlite3-0 \
21 | libstdc++-9-dev \
22 | libxml2 \
23 | libz3-dev \
24 | pkg-config \
25 | tzdata \
26 | zlib1g-dev
27 | fi
28 |
29 | if [[ $TRAVIS_CPU_ARCH == "arm64" ]]; then
30 | curl -s https://packagecloud.io/install/repositories/swift-arm/release/script.deb.sh | sudo bash
31 | sudo apt-get install swift-lang
32 | else
33 | wget https://swift.org/builds/swift-${SWIFT_VER}-release/ubuntu${RELEASE_NUM}/swift-${SWIFT_VER}-RELEASE/swift-${SWIFT_VER}-RELEASE-ubuntu${RELEASE_DOT}.tar.gz
34 | tar xzf swift-${SWIFT_VER}-RELEASE-ubuntu${RELEASE_DOT}.tar.gz
35 | fi
36 | fi
37 |
--------------------------------------------------------------------------------
/Scripts/script.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | if [[ $TRAVIS_OS_NAME = 'osx' ]]; then
4 | swift run swiftformat --lint . && swift run swiftlint
5 | elif [[ $TRAVIS_OS_NAME = 'linux' ]]; then
6 | # What to do in Ubunutu
7 | RELEASE_DOT=$(lsb_release -sr)
8 | RELEASE_NUM=${RELEASE_DOT//[-._]/}
9 | RELEASE_NAME=$(lsb_release -sc)
10 | [[ $TRAVIS_CPU_ARCH = "arm64" ]] && ARCH_PREFIX="aarch64" || ARCH_PREFIX="x86_64"
11 | export PATH="${PWD}/swift-${SWIFT_VER}-RELEASE-ubuntu${RELEASE_DOT}/usr/bin:$PATH"
12 | fi
13 |
14 | ARCH=${TRAVIS_CPU_ARCH:-amd64}
15 | [[ $TRAVIS_CPU_ARCH = "arm64" ]] && ARCH_PREFIX="aarch64" || ARCH_PREFIX="x86_64"
16 |
17 | swift build
18 | swift test --enable-code-coverage --enable-test-discovery
19 |
20 | if [[ $TRAVIS_OS_NAME = 'osx' ]]; then
21 | xcrun llvm-cov export -format="lcov" .build/debug/${FRAMEWORK_NAME}PackageTests.xctest/Contents/MacOS/${FRAMEWORK_NAME}PackageTests -instr-profile .build/debug/codecov/default.profdata > info.lcov
22 | bash <(curl https://codecov.io/bash) -F travis -F macOS -n $TRAVIS_JOB_NUMBER-$TRAVIS_OS_NAME
23 | else
24 | llvm-cov export -format="lcov" .build/${ARCH_PREFIX}-unknown-linux-gnu/debug/${FRAMEWORK_NAME}PackageTests.xctest -instr-profile .build/debug/codecov/default.profdata > info.lcov
25 | bash <(curl https://codecov.io/bash) -F travis -F $RELEASE_NAME -F $ARCH -n $TRAVIS_JOB_NUMBER-$TRAVIS_OS_NAME
26 | fi
--------------------------------------------------------------------------------
/Sources/EggSeedKit/Controllers/ArchiveExpander.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import ZIPFoundation
3 |
4 | #if canImport(FoundationNetworking)
5 | import FoundationNetworking
6 | #endif
7 |
8 | struct ArchiveExpander: Expander {
9 | func extract(
10 | fromURL sourceURL: URL,
11 | toURL destinationURL: URL,
12 | forEach: (ExpansionEntry, (Result) -> Void) -> Void,
13 | completion: @escaping (Error?) -> Void
14 | ) {
15 | guard let archive = Archive(url: sourceURL, accessMode: .read) else {
16 | completion(EggSeedError.invalidData(sourceURL))
17 | return
18 | }
19 | let items = archive.enumerated()
20 | for entryItem in items {
21 | do {
22 | _ = try archive.extract(entryItem.element) { data in
23 | let path = entryItem.element.path.components(separatedBy: "/").dropFirst().joined(separator: "/")
24 | let item = ArchiveEntry(data: data, relativePath: path)
25 | let destinationFileURL = destinationURL.appendingPathComponent(path)
26 | if entryItem.element.type == Entry.EntryType.file {
27 | try? FileManager.default.createDirectory(
28 | at: destinationFileURL.deletingLastPathComponent(),
29 | withIntermediateDirectories: true,
30 | attributes: nil
31 | )
32 | forEach(item) { result in
33 | if (try? result.get()) != true {
34 | do {
35 | try data.write(to: destinationFileURL)
36 | } catch {
37 | completion(error)
38 | return
39 | }
40 | }
41 | }
42 | }
43 | }
44 | } catch {
45 | completion(error)
46 | return
47 | }
48 | }
49 | completion(nil)
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Sources/EggSeedKit/Controllers/EggSeedRunner.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import mustache
3 | import PromiseKit
4 |
5 | #if canImport(FoundationNetworking)
6 | import FoundationNetworking
7 | #endif
8 |
9 | public class EggSeedRunner: Runner {
10 | let session: Session = URLSession.shared
11 | let downloader: Downloader = URLDownloader()
12 | let gitterface: Gitterface = ProcessGitterface()
13 | let expander: Expander = ArchiveExpander()
14 | let packageFactory: PackageFactory = ProcessPackageFactory()
15 | let licenseIssuer: LicenseIssuer = LicenseDownloadIssuer()
16 |
17 | public init() {}
18 |
19 | public func run(
20 | withConfiguration configuration: EggSeedConfiguration,
21 | _ completion: @escaping (EggSeedError?) -> Void
22 | ) -> Cancellable {
23 | let url = configuration.templateURL
24 | let filesFilter = [".eggseed/.github/workflows/macOS.yml",
25 | ".eggseed/.github/workflows/ubuntu.yml",
26 | ".eggseed/.travis.yml",
27 | ".eggseed/bitrise.yml",
28 | ".eggseed/.circleci/config.yml",
29 | "README.md",
30 | "Example/project.yml"]
31 | let destinationFolderURL = URL(fileURLWithPath: configuration.path ?? FileManager.default.currentDirectoryPath)
32 |
33 | let packageName = destinationFolderURL.lastPathComponent
34 |
35 | let userNamePromise = Promise { resolver in
36 | if let readUserName = configuration.userName {
37 | resolver.fulfill(readUserName)
38 | } else {
39 | gitterface.getSpecs(for: "origin", at: destinationFolderURL) { result in
40 | resolver.resolve(result.map { $0.owner }.mapError { _ in EggSeedError.missingValue("userName")
41 | })
42 | }
43 | }
44 | }
45 |
46 | let downloadPromise = Promise { resolver in
47 | self.downloader.downloadURL(url, with: self.session) { result in
48 | resolver.resolve(result.mapError { error in
49 | EggSeedError.downloadFailure(error)
50 | })
51 | }
52 | }
53 |
54 | let parser = MustacheParser()
55 | parser.openCharacter = "["
56 | parser.closeCharacter = "]"
57 |
58 | var savedUserName: String!
59 |
60 | let queue = DispatchQueue.global(qos: .userInitiated)
61 | let cancellable = when(fulfilled: userNamePromise, downloadPromise).then(on: queue) { (args) -> Promise in
62 | let (userName, url) = args
63 | savedUserName = userName
64 |
65 | return Promise { resolver in
66 | self.expander.extract(fromURL: url, toURL: destinationFolderURL, forEach: { item, completed in
67 | if filesFilter.contains(item.relativePath) || item.relativePath.hasPrefix(".eggseed/"), let templateText = String(data: item.data, encoding: .utf8) {
68 | let relativePath: String
69 | if item.relativePath.hasPrefix(".eggseed/") {
70 | relativePath = item.relativePath.components(separatedBy: "/").dropFirst().joined(separator: "/")
71 | } else {
72 | relativePath = item.relativePath
73 | }
74 | let url = destinationFolderURL.appendingPathComponent(relativePath)
75 | let result = Result {
76 | if !FileManager.default.fileExists(atPath: url.deletingLastPathComponent().path) {
77 | try FileManager.default.createDirectory(at: url.deletingLastPathComponent(), withIntermediateDirectories: true, attributes: nil)
78 | }
79 | let template = parser.parse(string: templateText)
80 | let text = template.render(object: ["packageName": packageName, "userName": userName])
81 | try text.write(to: url, atomically: true, encoding: .utf8)
82 | return true
83 | }
84 | completed(result)
85 | } else {
86 | completed(.success(false))
87 | }
88 | }, completion: resolver.resolve)
89 | }
90 | }.then(on: queue) { _ in
91 | Promise { resolver in
92 | self.packageFactory.create(atURL: destinationFolderURL, withType: configuration.packageType, resolver.resolve)
93 | }
94 | }.then(on: queue) { _ in
95 | Promise { resolver in
96 | self.licenseIssuer.issue(configuration.license, to: destinationFolderURL, withSession: self.session, usingFullName: savedUserName, resolver.resolve)
97 | }
98 | }.map(on: queue) { (_) -> EggSeedError? in
99 | nil
100 | }
101 | .recover(only: EggSeedError.self, on: queue) { error -> Promise in
102 | Promise { resolver in
103 | resolver.fulfill(error)
104 | }
105 | }.done(on: queue, completion)
106 |
107 | return cancellable
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/Sources/EggSeedKit/Controllers/LicenseDownloadIssuer.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import mustache
3 |
4 | public struct LicenseData: Codable {
5 | let body: String
6 | }
7 |
8 | public struct LicenseDownloadIssuer: LicenseIssuer {
9 | public let fileManager = FileManager.default
10 | public let baseURL = URL(string: "https://api.github.com/licenses")!
11 | public func issue(_ license: License, to destinationURL: URL, withSession session: Session, usingFullName fullName: String, _ completion: @escaping (Error?) -> Void) {
12 | let year = Calendar.current.component(.year, from: Date())
13 | let data = ["year": year, "fullname": fullName] as [String: Any]
14 | let licenseURL = destinationURL.appendingPathComponent("LICENSE")
15 | let sourceURL = baseURL.appendingPathComponent(license.rawValue)
16 | let decoder = JSONDecoder()
17 |
18 | let parser = MustacheParser()
19 | parser.openCharacter = "["
20 | parser.closeCharacter = "]"
21 | session.decode(LicenseData.self, fromURL: sourceURL, withDecoder: decoder) { result in
22 | let completed = result.map { $0.body.replacingOccurrences(of: "[", with: "[[").replacingOccurrences(of: "]", with: "]]") }.map(parser.parse(string:)).map {
23 | $0.render(object: data)
24 | }.flatMap { text in
25 | Result { try text.write(to: licenseURL, atomically: true, encoding: .utf8) }
26 | }.mapError(EggSeedError.decodeFailure)
27 | completion(completed.error)
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Sources/EggSeedKit/Controllers/ProcessGitterface.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | #if canImport(FoundationNetworking)
4 | import FoundationNetworking
5 | #endif
6 |
7 | struct ProcessGitterface: Gitterface {
8 | let launcher: Launcher = ProcessLauncher()
9 | func getRemote(for _: String, at url: URL, _ completion: @escaping (Result) -> Void) {
10 | launcher.bash("git remote get-url origin", at: url) { remoteURLString in
11 | let result: Result = remoteURLString.map { (data) -> String? in
12 | String(bytes: data, encoding: .utf8)
13 | }.map { string in
14 | string.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
15 | }.mapError { (esError) -> Error in
16 | esError as Error
17 | }.flatMap { (string) -> Result in
18 | guard let string = string else {
19 | return .failure(EggSeedError.empty)
20 | }
21 | return .success(string)
22 | }
23 | completion(result)
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Sources/EggSeedKit/Controllers/ProcessLauncher.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | #if canImport(FoundationNetworking)
4 | import FoundationNetworking
5 | #endif
6 |
7 | struct ProcessLauncher: Launcher {
8 | let bashExecutableURL = URL(fileURLWithPath: "/bin/bash")
9 | func bash(
10 | _ command: String,
11 | at url: URL?,
12 | _ completion: @escaping ((Result) -> Void)
13 | ) -> Cancellable {
14 | let process = Process()
15 | process.executableURL = bashExecutableURL
16 | process.arguments = ["-c", command]
17 | process.currentDirectoryURL = url
18 | let outputPipe = Pipe()
19 | process.standardOutput = outputPipe
20 |
21 | let errorPipe = Pipe()
22 | process.standardError = errorPipe
23 | process.terminationHandler = { _ in
24 | completion(
25 | process.getResult(
26 | fromOutput: outputPipe.fileHandleForReading,
27 | andError: errorPipe.fileHandleForReading
28 | ))
29 | }
30 | process.launch()
31 | return process
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Sources/EggSeedKit/Controllers/ProcessPackageFactory.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | #if canImport(FoundationNetworking)
4 | import FoundationNetworking
5 | #endif
6 |
7 | struct ProcessPackageFactory: PackageFactory {
8 | let launcher: Launcher = ProcessLauncher()
9 |
10 | func create(atURL _: URL, withType type: SwiftPackageType, _ completion: @escaping (Error?) -> Void) {
11 | launcher.bash("swift package init --type \(type.rawValue)", at: nil) {
12 | completion($0.mapError(EggSeedError.packageFailure).error)
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Sources/EggSeedKit/Controllers/URLDownloader.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | #if canImport(FoundationNetworking)
4 | import FoundationNetworking
5 | #endif
6 |
7 | struct URLDownloader: Downloader {
8 | func downloadURL(
9 | _ url: URL,
10 | with session: Session,
11 | _ completion: @escaping ((Result) -> Void)
12 | ) -> Cancellable {
13 | return session.downloadURL(url, completion)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Sources/EggSeedKit/EggSeed.swift:
--------------------------------------------------------------------------------
1 | import ArgumentParser
2 | import Foundation
3 |
4 | public enum StylingTool: Int, CaseIterable {
5 | case swiftformat = 1
6 | case swiftlint = 2
7 | }
8 |
9 | public struct StylingToolOption: OptionSet, ExpressibleByArgument {
10 | public let rawValue: StylingTool.RawValue
11 | public init(rawValue: StylingTool.RawValue) {
12 | self.rawValue = rawValue
13 | }
14 |
15 | public static let swiftformat = Self(rawValue: StylingTool.swiftformat.rawValue)
16 | public static let swiftlint = Self(rawValue: StylingTool.swiftlint.rawValue)
17 | public static let none = Self()
18 | public static let all = Self(StylingTool.allCases.map { Self(rawValue: $0.rawValue) })
19 | }
20 |
21 | public enum DocumentationTooling: Int, ExpressibleByArgument {
22 | case sourcedocs = 1
23 | }
24 |
25 | public struct EggSeed: ParsableCommand, EggSeedConfiguration {
26 | public static var configuration = CommandConfiguration(
27 | commandName: "eggseed")
28 | static var runner: Runner!
29 |
30 | static func setupRunner(_ runner: Runner) {
31 | Self.runner = runner
32 | }
33 |
34 | public init() {}
35 |
36 | // Licence
37 | @Option(help: "Software License.") public var license: License = .mit
38 |
39 | // OS
40 | @Option(help: "Platforms and OSes") public var platforms: [SupportedPlatform] = []
41 |
42 | // CI
43 | #warning("Add CI targets")
44 | @Option(default: ContinuousIntegration.all, help: "Continuous Integration Services")
45 | public var ci: ContinuousIntegration
46 | // Template
47 |
48 | @Option()
49 | public var template: String = "https://github.com/brightdigit/eggseed-template/archive/master.zip"
50 |
51 | public var templateURL: URL {
52 | return URL(string: template) ?? URL(string: "https://github.com/brightdigit/eggseed-template/archive/master.zip")!
53 | }
54 |
55 | // cocoapods support
56 | #warning("Add Cocoapods Support Flag")
57 | @Flag(help: "Supports Cocoapods")
58 | public var cocoapods: Bool = false
59 |
60 | @Option(help: "Use Komondor")
61 | public var komondor: Bool = true
62 |
63 | // sourcedocs or jazzy
64 | #warning("Add Documentation Tool Option")
65 | @Option(help: "Documentation Tool")
66 | public var documentation: DocumentationTooling = .sourcedocs
67 |
68 | // swiftformat or/and swiftlint danger etc...
69 | #warning("Add Linting Options")
70 | @Option(help: "Formatting, Linters, Styling Tools")
71 | public var linters: StylingToolOption = .all
72 |
73 | #warning("Allow Multiple Products")
74 | @Option(help: "Swift Package Type") public var packageType: SwiftPackageType = .library
75 | @Option(help: "User name or Owner of Repostory.") public var userName: String?
76 | @Option(help: "Root path of the Swift Package.") public var path: String?
77 |
78 | public static func main(by runner: Runner, _ arguments: [String]? = nil) {
79 | Self.setupRunner(runner)
80 | return Self.main(arguments)
81 | }
82 |
83 | public func run() throws {
84 | let semaphore = DispatchSemaphore(value: 0)
85 | var error: Error?
86 | Self.runner.run(withConfiguration: self) {
87 | error = $0
88 | semaphore.signal()
89 | }
90 | semaphore.wait()
91 | Self.exit(withError: error)
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/Sources/EggSeedKit/Extensions/Process.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | extension Process: Cancellable {
4 | func getResult(
5 | fromOutput output: FileHandle,
6 | andError error: FileHandle
7 | ) -> Result {
8 | let outputData = output.readDataToEndOfFile()
9 |
10 | if terminationStatus == 0 {
11 | return .success(outputData)
12 | }
13 |
14 | return .failure(LauncherError(
15 | terminationStatus: terminationStatus,
16 | outputData: outputData,
17 | errorData: error.readDataToEndOfFile()
18 | ))
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Sources/EggSeedKit/Extensions/Result.swift:
--------------------------------------------------------------------------------
1 | extension Result {
2 | init(success: Success?, failure: Failure?, fallback: () -> Failure) {
3 | if let success = success {
4 | self = .success(success)
5 | } else {
6 | self = .failure(failure ?? fallback())
7 | }
8 | }
9 |
10 | var error: Failure? {
11 | guard case let .failure(error) = self else {
12 | return nil
13 | }
14 | return error
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Sources/EggSeedKit/Models/ArchiveEntry.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public struct ArchiveEntry: ExpansionEntry {
4 | public let data: Data
5 | public let relativePath: String
6 | }
7 |
--------------------------------------------------------------------------------
/Sources/EggSeedKit/Models/CISystem.swift:
--------------------------------------------------------------------------------
1 | import ArgumentParser
2 | import Foundation
3 |
4 | #if canImport(FoundationNetworking)
5 | import FoundationNetworking
6 | #endif
7 |
8 | public enum ContinuousIntegrationSystem: Int, CaseIterable {
9 | case bitrise = 1
10 | case travisci = 2
11 | case github = 4
12 | case circleci = 8
13 | }
14 |
15 | public struct ContinuousIntegration: OptionSet, ExpressibleByArgument {
16 | public let rawValue: ContinuousIntegrationSystem.RawValue
17 |
18 | public typealias RawValue = ContinuousIntegrationSystem.RawValue
19 |
20 | public init(rawValue: RawValue) {
21 | self.rawValue = rawValue
22 | }
23 |
24 | public static let bitrise = Self(rawValue: ContinuousIntegrationSystem.bitrise.rawValue)
25 | public static let travisci = Self(rawValue: ContinuousIntegrationSystem.travisci.rawValue)
26 | public static let github = Self(rawValue: ContinuousIntegrationSystem.github.rawValue)
27 | public static let circleci = Self(rawValue: ContinuousIntegrationSystem.circleci.rawValue)
28 | public static let none = Self()
29 | public static let all = Self(ContinuousIntegrationSystem.allCases.map { Self(rawValue: $0.rawValue) })
30 | }
31 |
32 | protocol CISystem {
33 | func remove(fromURL url: URL, _ completion: @escaping (Error?) -> Void)
34 | func verify(atURL url: URL, for platforms: SupportedPlatform, _ completion: @escaping (Error?) -> Void)
35 | }
36 |
--------------------------------------------------------------------------------
/Sources/EggSeedKit/Models/CPU.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | enum CPU {
4 | case amd64
5 | case aarch64
6 | }
7 |
--------------------------------------------------------------------------------
/Sources/EggSeedKit/Models/EggSeedError.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | #if canImport(FoundationNetworking)
4 | import FoundationNetworking
5 | #endif
6 |
7 | public enum EggSeedError: Error {
8 | case missingValue(String)
9 | case downloadFailure(Error?)
10 | case invalidData(URL)
11 | case decodeFailure(Error)
12 | case extractionFailure(Error)
13 | case packageFailure(Error)
14 | case destinationFailure(Error)
15 | case empty
16 |
17 | static func fallback() -> Error {
18 | return EggSeedError.empty
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Sources/EggSeedKit/Models/LauncherError.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | struct LauncherError: Error, CustomStringConvertible {
4 | let terminationStatus: Int32
5 | let outputData: Data
6 | let errorData: Data
7 |
8 | var errorString: String? {
9 | String(bytes: errorData, encoding: .utf8)
10 | }
11 |
12 | var outputString: String? {
13 | String(bytes: outputData, encoding: .utf8)
14 | }
15 |
16 | var description: String {
17 | let errorString = self.errorString ?? errorData.description
18 | let outputString = self.outputString ?? outputData.description
19 | return "\terminated with \(terminationStatus): (error: \"\(errorString)\", output: \"\(outputString)\""
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Sources/EggSeedKit/Models/License.swift:
--------------------------------------------------------------------------------
1 | import ArgumentParser
2 |
3 | // swiftlint:disable identifier_name
4 | public enum License: String, ExpressibleByArgument {
5 | case agpl3_0 = "agpl-3.0"
6 | case apache2_0 = "apache-2.0"
7 | case bsd2clause = "bsd-2-clause"
8 | case bsd3clause = "bsd-3-clause"
9 | case cc01_0 = "cc0-1.0"
10 | case epl2_0 = "epl-2.0"
11 | case gpl2_0 = "gpl-2.0"
12 | case gpl3_0 = "gpl-3.0"
13 | case lgpl2_1 = "lgpl-2.1"
14 | case lgpl3_0 = "lgpl-3.0"
15 | case mit
16 | case mpl2_0 = "mpl-2.0"
17 | case unlicense
18 | }
19 |
--------------------------------------------------------------------------------
/Sources/EggSeedKit/Models/Platform.swift:
--------------------------------------------------------------------------------
1 | enum Platform: String {
2 | case macOS
3 | case iOS
4 | case watchOS
5 | case tvOS
6 | case ubuntu
7 |
8 | var minimumVersion: Version? {
9 | guard self != .ubuntu else {
10 | return nil
11 | }
12 | let version: Version?
13 | switch self {
14 | case .iOS:
15 | version = Version(argument: "8.0")
16 | case .macOS:
17 | version = Version(argument: "10.10")
18 | case .tvOS:
19 | version = Version(argument: "9.0")
20 | case .watchOS:
21 | version = Version(argument: "2.0")
22 | default:
23 | version = nil
24 | }
25 | precondition(version != nil, "Invalid Platform or Version String.")
26 | return version
27 | }
28 |
29 | func spmPlatform(fromVersion version: Version) -> String? {
30 | guard let minimumVersion = self.minimumVersion else {
31 | return nil
32 | }
33 |
34 | let actualVersion = max(version, minimumVersion)
35 | return ".\(rawValue)(\"\(actualVersion)\")"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Sources/EggSeedKit/Models/SupportedPlatforms.swift:
--------------------------------------------------------------------------------
1 | import ArgumentParser
2 |
3 | struct Version: Comparable, CustomStringConvertible {
4 | var description: String {
5 | [majorVersion, minorVersion].compactMap { $0 }.map(String.init).joined(separator: ".")
6 | }
7 |
8 | static func < (lhs: Version, rhs: Version) -> Bool {
9 | guard lhs.majorVersion == rhs.majorVersion else {
10 | return lhs.majorVersion < rhs.majorVersion
11 | }
12 | guard let lmv = lhs.minorVersion, let rmv = rhs.minorVersion else {
13 | return lhs.minorVersion == nil || rhs.minorVersion == nil
14 | }
15 | return lmv < rmv
16 | }
17 |
18 | let majorVersion: Int
19 | let minorVersion: Int?
20 | init?(argument: String) {
21 | let components = argument.components(separatedBy: ".").prefix(2)
22 | let numbers = components.compactMap(Int.init)
23 | guard let majorVersion = numbers.first, components.count == numbers.count else {
24 | return nil
25 | }
26 | self.majorVersion = majorVersion
27 | minorVersion = numbers.count > 1 ? numbers.last : nil
28 | }
29 | }
30 |
31 | public struct SupportedPlatform: ExpressibleByArgument {
32 | public init?(argument: String) {
33 | let components = argument.components(separatedBy: "v")
34 | guard components.count < 3 else {
35 | return nil
36 | }
37 | guard let platform = components.first.flatMap(Platform.init) else {
38 | return nil
39 | }
40 | let version: Version?
41 | if let last = components.last {
42 | guard let lastVersion = Version(argument: last) else {
43 | return nil
44 | }
45 | version = lastVersion
46 | } else {
47 | version = nil
48 | }
49 | self.platform = platform
50 | self.version = version
51 | }
52 |
53 | let platform: Platform
54 | let version: Version?
55 |
56 | init(platform: Platform, version: Version?) {
57 | self.platform = platform
58 | self.version = version
59 | }
60 |
61 | // static let macOS = SupportedPlatforms(platform: .macOS)
62 | // static let iOS = SupportedPlatforms(platform: .iOS)
63 | // static let watchOS = SupportedPlatforms(platform: .watchOS)
64 | // static let tvOS = SupportedPlatforms(platform: .tvOS)
65 | // #warning("allow for multiple versions (ubuntus, amazon, centos)")
66 | // static let ubuntu = SupportedPlatforms(platform: .ubuntu)
67 | }
68 |
69 | // let platforms = Set(arrayLiteral: SupportedPlatform(platform: .iOS, version: "13"))
70 |
--------------------------------------------------------------------------------
/Sources/EggSeedKit/Models/SwiftPackageType.swift:
--------------------------------------------------------------------------------
1 | import ArgumentParser
2 |
3 | public enum SwiftPackageType: String, ExpressibleByArgument {
4 | case library
5 | case executable
6 | }
7 |
--------------------------------------------------------------------------------
/Sources/EggSeedKit/Protocols/Cancellable.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import PromiseKit
3 |
4 | #if canImport(FoundationNetworking)
5 | import FoundationNetworking
6 | #endif
7 |
8 | public protocol Cancellable {}
9 |
10 | extension URLSessionTask: Cancellable {}
11 |
12 | extension Promise: Cancellable {}
13 |
--------------------------------------------------------------------------------
/Sources/EggSeedKit/Protocols/Downloader.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | #if canImport(FoundationNetworking)
4 | import FoundationNetworking
5 | #endif
6 |
7 | public protocol Downloader {
8 | @discardableResult
9 | func downloadURL(
10 | _ url: URL,
11 | with session: Session,
12 | _ completion: @escaping ((Result) -> Void)
13 | ) -> Cancellable
14 | }
15 |
--------------------------------------------------------------------------------
/Sources/EggSeedKit/Protocols/EggSeedConfiguration.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | #if canImport(FoundationNetworking)
4 | import FoundationNetworking
5 | #endif
6 |
7 | public protocol EggSeedConfiguration {
8 | var userName: String? { get }
9 | var path: String? { get }
10 | var packageType: SwiftPackageType { get }
11 | var license: License { get }
12 | var templateURL: URL { get }
13 | }
14 |
--------------------------------------------------------------------------------
/Sources/EggSeedKit/Protocols/Expander.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | #if canImport(FoundationNetworking)
4 | import FoundationNetworking
5 | #endif
6 |
7 | protocol Expander {
8 | func extract(
9 | fromURL url: URL,
10 | toURL url: URL,
11 | forEach: (ExpansionEntry, (Result) -> Void) -> Void,
12 | completion: @escaping (Error?) -> Void
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/Sources/EggSeedKit/Protocols/ExpansionEntry.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | protocol ExpansionEntry {
4 | var data: Data { get }
5 | var relativePath: String { get }
6 | }
7 |
--------------------------------------------------------------------------------
/Sources/EggSeedKit/Protocols/Gitterface.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | #if canImport(FoundationNetworking)
4 | import FoundationNetworking
5 | #endif
6 |
7 | struct InvalidFormatError: Error {}
8 |
9 | public struct GitHubSpec {
10 | let owner: String
11 | let repo: String
12 |
13 | public init(string: String) throws {
14 | if let url = URL(string: string), url.scheme != nil {
15 | repo = url.deletingPathExtension().lastPathComponent
16 | owner = url.deletingLastPathComponent().lastPathComponent
17 | } else {
18 | guard let components = string.components(separatedBy: ":").last?.components(separatedBy: "/") else {
19 | throw InvalidFormatError()
20 | }
21 | guard components.count == 2 else {
22 | throw InvalidFormatError()
23 | }
24 | guard let owner = components.first else {
25 | throw InvalidFormatError()
26 | }
27 | guard let repo = components.last?.components(separatedBy: ".").first else {
28 | throw InvalidFormatError()
29 | }
30 | self.repo = repo
31 | self.owner = owner
32 | }
33 | }
34 | }
35 |
36 | public protocol Gitterface {
37 | func getRemote(for name: String, at url: URL, _ completion: @escaping (Result) -> Void)
38 | }
39 |
40 | public extension Gitterface {
41 | func getSpecs(for name: String, at url: URL, _ completion: @escaping (Result) -> Void) {
42 | getRemote(for: name, at: url) { result in
43 | let newResult = result.flatMap { string in
44 | Result {
45 | try GitHubSpec(string: string)
46 | }
47 | }
48 | completion(newResult)
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Sources/EggSeedKit/Protocols/Launcher.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | #if canImport(FoundationNetworking)
4 | import FoundationNetworking
5 | #endif
6 |
7 | protocol Launcher {
8 | @discardableResult
9 | func bash(
10 | _ string: String,
11 | at url: URL?,
12 | _ completion: @escaping ((Result) -> Void)
13 | ) -> Cancellable
14 | }
15 |
--------------------------------------------------------------------------------
/Sources/EggSeedKit/Protocols/LicenseIssuer.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public protocol LicenseIssuer {
4 | func issue(_ license: License, to destinationURL: URL, withSession session: Session, usingFullName fullName: String, _ completion: @escaping (Error?) -> Void)
5 | }
6 |
--------------------------------------------------------------------------------
/Sources/EggSeedKit/Protocols/PackageFactory.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | #if canImport(FoundationNetworking)
4 | import FoundationNetworking
5 | #endif
6 |
7 | protocol PackageFactory {
8 | func create(atURL url: URL, withType type: SwiftPackageType, _ completion: @escaping (Error?) -> Void)
9 | }
10 |
--------------------------------------------------------------------------------
/Sources/EggSeedKit/Protocols/Runner.swift:
--------------------------------------------------------------------------------
1 | public protocol Runner {
2 | @discardableResult
3 | func run(withConfiguration configuration: EggSeedConfiguration, _ completion: @escaping (EggSeedError?) -> Void) -> Cancellable
4 | }
5 |
--------------------------------------------------------------------------------
/Sources/EggSeedKit/Protocols/Session.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | #if canImport(FoundationNetworking)
4 | import FoundationNetworking
5 | #endif
6 |
7 | public protocol Session {
8 | @discardableResult
9 | func downloadURL(_ url: URL, _ completion: @escaping ((Result) -> Void)) -> Cancellable
10 |
11 | @discardableResult
12 | func decode(_ type: Success.Type, fromURL url: URL, withDecoder decoder: JSONDecoder, _ completion: @escaping ((Result) -> Void)) -> Cancellable
13 | }
14 |
15 | extension URLSession: Session {
16 | public func decode(_ type: Success.Type, fromURL url: URL, withDecoder decoder: JSONDecoder, _ completion: @escaping ((Result) -> Void)) -> Cancellable where Success: Decodable {
17 | let task = dataTask(with: url) { data, _, error in
18 | let result = Result(success: data, failure: error, fallback: EggSeedError.fallback).flatMap { data in
19 | Result {
20 | try decoder.decode(type, from: data)
21 | }
22 | }
23 | completion(result)
24 | }
25 | task.resume()
26 | return task
27 | }
28 |
29 | @discardableResult
30 | public func downloadURL(_ url: URL, _ completion: @escaping ((Result) -> Void)) -> Cancellable {
31 | let task = downloadTask(with: url) { url, _, error in
32 | let result: Result
33 | if let url = url {
34 | result = .success(url)
35 | } else {
36 | result = .failure(error ?? EggSeedError.empty)
37 | }
38 | completion(result)
39 | }
40 | task.resume()
41 | return task
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Sources/eggseed/main.swift:
--------------------------------------------------------------------------------
1 | import EggSeedKit
2 |
3 | EggSeed.main(by: EggSeedRunner())
4 |
--------------------------------------------------------------------------------
/Tests/EggSeedTests/EggSeedTests.swift:
--------------------------------------------------------------------------------
1 | // @testable import eggseed
2 | import XCTest
3 |
4 | final class EggSeedTests: XCTestCase {
5 | func testExample() {
6 | // This is an example of a functional test case.
7 | // Use XCTAssert and related functions to verify your tests produce the correct
8 | // results.
9 | // XCTAssertEqual(EggSeed().text, "Hello, World!")
10 | }
11 |
12 | // static var allTests = [
13 | // //("testExample", testExample),
14 | // ]
15 | }
16 |
--------------------------------------------------------------------------------
/Tests/EggSeedTests/XCTestManifests.swift:
--------------------------------------------------------------------------------
1 | #if !canImport(ObjectiveC)
2 | import XCTest
3 |
4 | extension EggSeedTests {
5 | // DO NOT MODIFY: This is autogenerated, use:
6 | // `swift test --generate-linuxmain`
7 | // to regenerate.
8 | static let __allTests__EggSeedTests = [
9 | ("testExample", testExample)
10 | ]
11 | }
12 |
13 | public func __allTests() -> [XCTestCaseEntry] {
14 | return [
15 | testCase(EggSeedTests.__allTests__EggSeedTests)
16 | ]
17 | }
18 | #endif
19 |
--------------------------------------------------------------------------------
/Tests/LinuxMain.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | import EggSeedTests
4 |
5 | var tests = [XCTestCaseEntry]()
6 | tests += EggSeedTests.__allTests()
7 |
8 | XCTMain(tests)
9 |
--------------------------------------------------------------------------------
/bitrise.yml:
--------------------------------------------------------------------------------
1 | ---
2 | format_version: '8'
3 | default_step_lib_source: 'https://github.com/bitrise-io/bitrise-steplib.git'
4 | project_type: other
5 | app:
6 | envs:
7 | - PACKAGE_NAME: EggSeed
8 | workflows:
9 | ci:
10 | steps:
11 | - git-clone@4: {}
12 | - script@1:
13 | inputs:
14 | - content: >-
15 | #!/usr/bin/env bash # fail if any commands fails
16 |
17 | set -e
18 |
19 | # debug log
20 |
21 | set -x
22 |
23 | pwd
24 |
25 | ls
26 |
27 | swift run swiftformat --lint . && swift run swiftlint
28 |
29 | swift build
30 |
31 | swift test --enable-code-coverage
32 |
33 |
34 | xcrun llvm-cov export -format="lcov" .build/debug/${PACKAGE_NAME}PackageTests.xctest/Contents/MacOS/${PACKAGE_NAME}PackageTests -instr-profile .build/debug/codecov/default.profdata > info.lcov
35 |
36 | bash <(curl https://codecov.io/bash) -F bitrise -F macOS -n
37 | $BITRISE_BUILD_NUMBER
38 | trigger_map:
39 | - push_branch: '*'
40 | workflow: ci
41 |
--------------------------------------------------------------------------------
/codecov.yml:
--------------------------------------------------------------------------------
1 | ignore:
2 | - "Tests"
3 |
--------------------------------------------------------------------------------
/eggseed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brightdigit/EggSeed/1fdf171b2011a9a85cc6e19c5cebb0c93f108d3d/eggseed.png
--------------------------------------------------------------------------------
/eggseed.svg:
--------------------------------------------------------------------------------
1 |
2 |
110 |
--------------------------------------------------------------------------------
/setup.sh.reference:
--------------------------------------------------------------------------------
1 | # Get the package name based on the directory name
2 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
3 | PACKAGE_NAME=`basename $DIR`
4 |
5 | # Get the user name based on the git repo url
6 | USER_NAME=`basename $(dirname $(git remote get-url origin))`
7 |
8 | # Create the Swift Package Library
9 | swift package init --type library
10 |
11 | # Use `sed` to replace the package name
12 | # in the Github Action CI Workflows
13 | sed -i '' "s/_PACKAGE_NAME/$PACKAGE_NAME/g" .github/workflows/macOS.yml
14 | sed -i '' "s/_PACKAGE_NAME/$PACKAGE_NAME/g" .github/workflows/ubuntu.yml
15 | # in the Travis CI Setup
16 | sed -i '' "s/_PACKAGE_NAME/$PACKAGE_NAME/g" .travis.yml
17 | # in the README.md
18 | sed -i '' "s/_PACKAGE_NAME/$PACKAGE_NAME/g" README.md
19 | # in the License
20 | sed -i '' "s/_PACKAGE_NAME/$PACKAGE_NAME/g" LICENSE
21 |
22 | # Use `sed` to replace the package name
23 | # in the Github Action CI Workflows
24 | sed -i '' "s/_USER_NAME/$USER_NAME/g" .github/workflows/macOS.yml
25 | sed -i '' "s/_USER_NAME/$USER_NAME/g" .github/workflows/ubuntu.yml
26 | # in the Travis CI Setup
27 | sed -i '' "s/_USER_NAME/$USER_NAME/g" .travis.yml
28 | # in the README.md
29 | sed -i '' "s/_USER_NAME/$USER_NAME/g" README.md
30 | # in the License
31 | sed -i '' "s/_USER_NAME/$USER_NAME/g" LICENSE
32 | # in the xcodegen Example project for Cocoapods
33 | sed -i '' "s/_USER_NAME/$USER_NAME/g" Example/project.yml
34 |
35 | # Create a CocoaPod spec
36 | pod spec create $(git remote get-url origin) --silent
37 |
38 | # Update the CocoaPod Spec with valid settings for all operating systems
39 | perl -pi -e '$_ .= qq(Lorem Description\n) if /spec.description = <<-DESC/' $PACKAGE_NAME.podspec
40 | sed -i '' 's|spec.summary.*|spec.summary = "Lorem Ipsum"|g' $PACKAGE_NAME.podspec
41 | sed -i '' 's|"MIT (example)"|{ :type => "MIT", :file => "LICENSE" }|g' $PACKAGE_NAME.podspec
42 | sed -i '' 's|spec.source_files =.*|spec.source_files = "Sources/**/*.swift"|g' $PACKAGE_NAME.podspec
43 | sed -i '' 's|spec.exclude_files.*|spec.swift_versions = "5"|g' $PACKAGE_NAME.podspec
44 |
45 | sed -i '' 's|spec.ios.deployment_target.*|spec.ios.deployment_target = "8.0"|g' $PACKAGE_NAME.podspec
46 | sed -i '' 's|spec.osx.deployment_target.*|spec.osx.deployment_target = "10.9"|g' $PACKAGE_NAME.podspec
47 | sed -i '' 's|spec.watchos.deployment_target.*|spec.watchos.deployment_target = "2.0"|g' $PACKAGE_NAME.podspec
48 | sed -i '' 's|spec.tvos.deployment_target.*|spec.tvos.deployment_target = "9.0"|g' $PACKAGE_NAME.podspec
49 |
50 | # Build the Swift Package
51 | swift build
52 | # Test the Swift Package
53 | swift test
54 | # Generate the first set of docs
55 | sourcedocs generate --spm-module $PACKAGE_NAME --output-folder docs
56 | # Generate the Example project using xcodegen
57 | pushd Example
58 | xcodegen generate
59 | # Generate Podfile
60 | pod init
61 | # Setup the Podfile for using the new Pod
62 | sed -i '' "s|# Pods for.*|pod '$PACKAGE_NAME', :path => '../'|g" Podfile
63 | # Setup the Example workspace
64 | pod install
65 | popd
66 |
67 | # Build all the schemes on the example workspace
68 | xcodebuild -workspace Example/Example.xcworkspace -scheme "iOS_Example" ONLY_ACTIVE_ARCH=NO CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO &
69 | xcodebuild -workspace Example/Example.xcworkspace -scheme "tvOS_Example" ONLY_ACTIVE_ARCH=NO CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO &
70 | xcodebuild -workspace Example/Example.xcworkspace -scheme "macOS_Example" ONLY_ACTIVE_ARCH=NO CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO &
71 | wait
72 |
--------------------------------------------------------------------------------
/word.svg:
--------------------------------------------------------------------------------
1 |
2 |
90 |
--------------------------------------------------------------------------------