├── .action ├── artifact │ ├── README.md │ ├── lib │ │ ├── __mocks__ │ │ │ └── internal-config-variables.js │ │ ├── artifact-client.js │ │ ├── internal-artifact-client.js │ │ ├── internal-config-variables.js │ │ ├── internal-contracts.js │ │ ├── internal-download-http-client.js │ │ ├── internal-download-options.js │ │ ├── internal-download-response.js │ │ ├── internal-download-specification.js │ │ ├── internal-upload-http-client.js │ │ ├── internal-upload-options.js │ │ ├── internal-upload-response.js │ │ ├── internal-upload-specification.js │ │ ├── internal-utils.js │ │ └── internal │ │ │ ├── __mocks__ │ │ │ └── config-variables.js │ │ │ ├── artifact-client.js │ │ │ ├── config-variables.js │ │ │ ├── contracts.js │ │ │ ├── download-http-client.js │ │ │ ├── download-options.js │ │ │ ├── download-response.js │ │ │ ├── download-specification.js │ │ │ ├── http-manager.js │ │ │ ├── status-reporter.js │ │ │ ├── upload-gzip.js │ │ │ ├── upload-http-client.js │ │ │ ├── upload-options.js │ │ │ ├── upload-response.js │ │ │ ├── upload-specification.js │ │ │ └── utils.js │ └── package.json ├── core │ ├── README.md │ ├── lib │ │ ├── command.js │ │ └── core.js │ └── package.json ├── exec │ ├── README.md │ ├── lib │ │ ├── exec.js │ │ ├── interfaces.js │ │ └── toolrunner.js │ └── package.json ├── http-client │ ├── LICENSE │ ├── README.md │ ├── RELEASES.md │ ├── actions.png │ ├── auth.js │ ├── index.js │ ├── interfaces.js │ ├── package.json │ └── proxy.js ├── io │ ├── README.md │ ├── lib │ │ ├── io-util.js │ │ └── io.js │ └── package.json ├── rimraf │ ├── LICENSE │ ├── README.md │ ├── bin.js │ ├── package.json │ └── rimraf.js ├── tmp-promise │ ├── .circleci │ │ └── config.yml │ ├── README.md │ ├── example-usage.js │ ├── index.js │ ├── package.json │ └── test.js └── tmp │ ├── LICENSE │ ├── README.md │ ├── lib │ └── tmp.js │ └── package.json ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── BUG_REPORT.md │ └── FEATURE_REQUEST.md ├── PULL_REQUEST_TEMPLATE │ └── NEW.md ├── release-drafter.yml └── workflows │ ├── draft-release.yml │ ├── stale-issues.yml │ ├── swiftlint.yml │ └── tests.yml ├── .gitignore ├── .swiftlint.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── Package.resolved ├── Package.swift ├── README.md ├── Sources └── CreateXCFramework │ ├── BuildSetting.swift │ ├── Command+Options.swift │ ├── Command.swift │ ├── Extensions │ ├── Collection+Extensions.swift │ ├── PackageModel+Extensions.swift │ └── Xcodeproj+ProductValidation.swift │ ├── PackageInfo.swift │ ├── Platforms.swift │ ├── ProjectGenerator.swift │ ├── XcodeBuilder.swift │ ├── Zipper.swift │ └── main.swift ├── Tests ├── CreateXCFrameworkTests │ ├── XCTestManifests.swift │ └── swift_create_frameworkTests.swift └── LinuxMain.swift ├── action.js └── action.yml /.action/artifact/README.md: -------------------------------------------------------------------------------- 1 | # `@actions/artifact` 2 | 3 | ## Usage 4 | 5 | You can use this package to interact with the actions artifacts. 6 | - [Upload an Artifact](#Upload-an-Artifact) 7 | - [Download a Single Artifact](#Download-a-Single-Artifact) 8 | - [Download All Artifacts](#Download-all-Artifacts) 9 | - [Additional Documentation](#Additional-Documentation) 10 | - [Contributions](#Contributions) 11 | 12 | Relative paths and absolute paths are both allowed. Relative paths are rooted against the current working directory. 13 | 14 | ## Upload an Artifact 15 | 16 | Method Name: `uploadArtifact` 17 | 18 | #### Inputs 19 | - `name` 20 | - The name of the artifact that is being uploaded 21 | - Required 22 | - `files` 23 | - A list of file paths that describe what should be uploaded as part of the artifact 24 | - If a path is provided that does not exist, an error will be thrown 25 | - Can be absolute or relative. Internally everything is normalized and resolved 26 | - Required 27 | - `rootDirectory` 28 | - A file path that denotes the root directory of the files being uploaded. This path is used to strip the paths provided in `files` to control how they are uploaded and structured 29 | - If a file specified in `files` is not in the `rootDirectory`, an error will be thrown 30 | - Required 31 | - `options` 32 | - Extra options that allow for the customization of the upload behavior 33 | - Optional 34 | 35 | #### Available Options 36 | 37 | - `continueOnError` 38 | - Indicates if the artifact upload should continue in the event a file fails to upload. If there is a error during upload, a partial artifact will always be created and available for download at the end. The `size` reported will be the amount of storage that the user or org will be charged for the partial artifact. 39 | - If set to `false`, and an error is encountered, all other uploads will stop and any files that were queued will not be attempted to be uploaded. The partial artifact available will only include files up until the failure. 40 | - If set to `true` and an error is encountered, the failed file will be skipped and ignored and all other queued files will be attempted to be uploaded. There will be an artifact available for download at the end with everything excluding the file that failed to upload 41 | - Optional, defaults to `true` if not specified 42 | 43 | #### Example using Absolute File Paths 44 | 45 | ```js 46 | const artifact = require('@actions/artifact'); 47 | const artifactClient = artifact.create() 48 | const artifactName = 'my-artifact'; 49 | const files = [ 50 | '/home/user/files/plz-upload/file1.txt', 51 | '/home/user/files/plz-upload/file2.txt', 52 | '/home/user/files/plz-upload/dir/file3.txt' 53 | ] 54 | const rootDirectory = '/home/user/files/plz-upload' 55 | const options = { 56 | continueOnError: true 57 | } 58 | 59 | const uploadResult = await artifactClient.uploadArtifact(artifactName, files, rootDirectory, options) 60 | ``` 61 | 62 | #### Example using Relative File Paths 63 | ```js 64 | // Assuming the current working directory is /home/user/files/plz-upload 65 | const artifact = require('@actions/artifact'); 66 | const artifactClient = artifact.create() 67 | const artifactName = 'my-artifact'; 68 | const files = [ 69 | 'file1.txt', 70 | 'file2.txt', 71 | 'dir/file3.txt' 72 | ] 73 | 74 | const rootDirectory = '.' // Also possible to use __dirname 75 | const options = { 76 | continueOnError: false 77 | } 78 | 79 | const uploadResponse = await artifactClient.uploadArtifact(artifactName, files, rootDirectory, options) 80 | ``` 81 | 82 | #### Upload Result 83 | 84 | The returned `UploadResponse` will contain the following information 85 | 86 | - `artifactName` 87 | - The name of the artifact that was uploaded 88 | - `artifactItems` 89 | - A list of all files that describe what is uploaded if there are no errors encountered. Usually this will be equal to the inputted `files` with the exception of empty directories (will not be uploaded) 90 | - `size` 91 | - Total size of the artifact that was uploaded in bytes 92 | - `failedItems` 93 | - A list of items that were not uploaded successfully (this will include queued items that were not uploaded if `continueOnError` is set to false). This is a subset of `artifactItems` 94 | 95 | ## Download a Single Artifact 96 | 97 | Method Name: `downloadArtifact` 98 | 99 | #### Inputs 100 | - `name` 101 | - The name of the artifact to download 102 | - Required 103 | - `path` 104 | - Path that denotes where the artifact will be downloaded to 105 | - Optional. Defaults to the GitHub workspace directory(`$GITHUB_WORKSPACE`) if not specified 106 | - `options` 107 | - Extra options that allow for the customization of the download behavior 108 | - Optional 109 | 110 | 111 | #### Available Options 112 | 113 | - `createArtifactFolder` 114 | - Specifies if a folder (the artifact name) is created for the artifact that is downloaded (contents downloaded into this folder), 115 | - Optional. Defaults to false if not specified 116 | 117 | #### Example 118 | 119 | ```js 120 | const artifact = require('@actions/artifact'); 121 | const artifactClient = artifact.create() 122 | const artifactName = 'my-artifact'; 123 | const path = 'some/directory' 124 | const options = { 125 | createArtifactFolder: false 126 | } 127 | 128 | const downloadResponse = await artifactClient.downloadArtifact(artifactName, path, options) 129 | 130 | // Post download, the directory structure will look like this 131 | /some 132 | /directory 133 | /file1.txt 134 | /file2.txt 135 | /dir 136 | /file3.txt 137 | 138 | // If createArtifactFolder is set to true, the directory structure will look like this 139 | /some 140 | /directory 141 | /my-artifact 142 | /file1.txt 143 | /file2.txt 144 | /dir 145 | /file3.txt 146 | ``` 147 | 148 | #### Download Response 149 | 150 | The returned `DownloadResponse` will contain the following information 151 | 152 | - `artifactName` 153 | - The name of the artifact that was downloaded 154 | - `downloadPath` 155 | - The full Path to where the artifact was downloaded 156 | 157 | 158 | ## Download All Artifacts 159 | 160 | Method Name: `downloadAllArtifacts` 161 | 162 | #### Inputs 163 | - `path` 164 | - Path that denotes where the artifact will be downloaded to 165 | - Optional. Defaults to the GitHub workspace directory(`$GITHUB_WORKSPACE`) if not specified 166 | 167 | ```js 168 | const artifact = require('@actions/artifact'); 169 | const artifactClient = artifact.create(); 170 | const downloadResponse = await artifactClient.downloadAllArtifacts(); 171 | 172 | // output result 173 | for (response in downloadResponse) { 174 | console.log(response.artifactName); 175 | console.log(response.downloadPath); 176 | } 177 | ``` 178 | 179 | Because there are multiple artifacts, an extra directory (denoted by the name of the artifact) will be created for each artifact in the path. With 2 artifacts(`my-artifact-1` and `my-artifact-2` for example) and the default path, the directory structure will be as follows: 180 | ```js 181 | /GITHUB_WORKSPACE 182 | /my-artifact-1 183 | / .. contents of `my-artifact-1` 184 | /my-artifact-2 185 | / .. contents of `my-artifact-2` 186 | ``` 187 | 188 | #### Download Result 189 | 190 | An array will be returned that describes the results for downloading all artifacts. The number of items in the array indicates the number of artifacts that were downloaded. 191 | 192 | Each artifact will have the same `DownloadResponse` as if it was individually downloaded 193 | - `artifactName` 194 | - The name of the artifact that was downloaded 195 | - `downloadPath` 196 | - The full Path to where the artifact was downloaded 197 | 198 | ## Additional Documentation 199 | 200 | Check out [additional-information](docs/additional-information.md) for extra documentation around usage, restrictions and behavior. 201 | 202 | Check out [implementation-details](docs/implementation-details.md) for extra information about the implementation of this package. 203 | 204 | ## Contributions 205 | 206 | See [contributor guidelines](https://github.com/actions/toolkit/blob/master/.github/CONTRIBUTING.md) for general guidelines and information about toolkit contributions. 207 | 208 | For contributions related to this package, see [artifact contributions](CONTRIBUTIONS.md) for more information. 209 | -------------------------------------------------------------------------------- /.action/artifact/lib/__mocks__/internal-config-variables.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | /** 4 | * Mocks default limits for easier testing 5 | */ 6 | function getUploadFileConcurrency() { 7 | return 1; 8 | } 9 | exports.getUploadFileConcurrency = getUploadFileConcurrency; 10 | function getUploadChunkConcurrency() { 11 | return 1; 12 | } 13 | exports.getUploadChunkConcurrency = getUploadChunkConcurrency; 14 | function getUploadChunkSize() { 15 | return 4 * 1024 * 1024; // 4 MB Chunks 16 | } 17 | exports.getUploadChunkSize = getUploadChunkSize; 18 | /** 19 | * Mocks the 'ACTIONS_RUNTIME_TOKEN', 'ACTIONS_RUNTIME_URL' and 'GITHUB_RUN_ID' env variables 20 | * that are only available from a node context on the runner. This allows for tests to run 21 | * locally without the env variables actually being set 22 | */ 23 | function getRuntimeToken() { 24 | return 'totally-valid-token'; 25 | } 26 | exports.getRuntimeToken = getRuntimeToken; 27 | function getRuntimeUrl() { 28 | return 'https://www.example.com/'; 29 | } 30 | exports.getRuntimeUrl = getRuntimeUrl; 31 | function getWorkFlowRunId() { 32 | return '15'; 33 | } 34 | exports.getWorkFlowRunId = getWorkFlowRunId; 35 | //# sourceMappingURL=internal-config-variables.js.map -------------------------------------------------------------------------------- /.action/artifact/lib/artifact-client.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const artifact_client_1 = require("./internal/artifact-client"); 4 | /** 5 | * Constructs an ArtifactClient 6 | */ 7 | function create() { 8 | return artifact_client_1.DefaultArtifactClient.create(); 9 | } 10 | exports.create = create; 11 | //# sourceMappingURL=artifact-client.js.map -------------------------------------------------------------------------------- /.action/artifact/lib/internal-artifact-client.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __importStar = (this && this.__importStar) || function (mod) { 12 | if (mod && mod.__esModule) return mod; 13 | var result = {}; 14 | if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; 15 | result["default"] = mod; 16 | return result; 17 | }; 18 | Object.defineProperty(exports, "__esModule", { value: true }); 19 | const core = __importStar(require("../../core")); 20 | const internal_upload_specification_1 = require("./internal-upload-specification"); 21 | const internal_upload_http_client_1 = require("./internal-upload-http-client"); 22 | const internal_utils_1 = require("./internal-utils"); 23 | const internal_download_http_client_1 = require("./internal-download-http-client"); 24 | const internal_download_specification_1 = require("./internal-download-specification"); 25 | const internal_config_variables_1 = require("./internal-config-variables"); 26 | const path_1 = require("path"); 27 | class DefaultArtifactClient { 28 | /** 29 | * Constructs a DefaultArtifactClient 30 | */ 31 | static create() { 32 | return new DefaultArtifactClient(); 33 | } 34 | /** 35 | * Uploads an artifact 36 | */ 37 | uploadArtifact(name, files, rootDirectory, options) { 38 | return __awaiter(this, void 0, void 0, function* () { 39 | internal_utils_1.checkArtifactName(name); 40 | // Get specification for the files being uploaded 41 | const uploadSpecification = internal_upload_specification_1.getUploadSpecification(name, rootDirectory, files); 42 | const uploadResponse = { 43 | artifactName: name, 44 | artifactItems: [], 45 | size: 0, 46 | failedItems: [] 47 | }; 48 | if (uploadSpecification.length === 0) { 49 | core.warning(`No files found that can be uploaded`); 50 | } 51 | else { 52 | // Create an entry for the artifact in the file container 53 | const response = yield internal_upload_http_client_1.createArtifactInFileContainer(name); 54 | if (!response.fileContainerResourceUrl) { 55 | core.debug(response.toString()); 56 | throw new Error('No URL provided by the Artifact Service to upload an artifact to'); 57 | } 58 | core.debug(`Upload Resource URL: ${response.fileContainerResourceUrl}`); 59 | // Upload each of the files that were found concurrently 60 | const uploadResult = yield internal_upload_http_client_1.uploadArtifactToFileContainer(response.fileContainerResourceUrl, uploadSpecification, options); 61 | //Update the size of the artifact to indicate we are done uploading 62 | yield internal_upload_http_client_1.patchArtifactSize(uploadResult.size, name); 63 | core.info(`Finished uploading artifact ${name}. Reported size is ${uploadResult.size} bytes. There were ${uploadResult.failedItems.length} items that failed to upload`); 64 | uploadResponse.artifactItems = uploadSpecification.map(item => item.absoluteFilePath); 65 | uploadResponse.size = uploadResult.size; 66 | uploadResponse.failedItems = uploadResult.failedItems; 67 | } 68 | return uploadResponse; 69 | }); 70 | } 71 | downloadArtifact(name, path, options) { 72 | var _a; 73 | return __awaiter(this, void 0, void 0, function* () { 74 | const artifacts = yield internal_download_http_client_1.listArtifacts(); 75 | if (artifacts.count === 0) { 76 | throw new Error(`Unable to find any artifacts for the associated workflow`); 77 | } 78 | const artifactToDownload = artifacts.value.find(artifact => { 79 | return artifact.name === name; 80 | }); 81 | if (!artifactToDownload) { 82 | throw new Error(`Unable to find an artifact with the name: ${name}`); 83 | } 84 | const items = yield internal_download_http_client_1.getContainerItems(artifactToDownload.name, artifactToDownload.fileContainerResourceUrl); 85 | if (!path) { 86 | path = internal_config_variables_1.getWorkSpaceDirectory(); 87 | } 88 | path = path_1.normalize(path); 89 | path = path_1.resolve(path); 90 | // During upload, empty directories are rejected by the remote server so there should be no artifacts that consist of only empty directories 91 | const downloadSpecification = internal_download_specification_1.getDownloadSpecification(name, items.value, path, ((_a = options) === null || _a === void 0 ? void 0 : _a.createArtifactFolder) || false); 92 | if (downloadSpecification.filesToDownload.length === 0) { 93 | core.info(`No downloadable files were found for the artifact: ${artifactToDownload.name}`); 94 | } 95 | else { 96 | // Create all necessary directories recursively before starting any download 97 | yield internal_utils_1.createDirectoriesForArtifact(downloadSpecification.directoryStructure); 98 | yield internal_download_http_client_1.downloadSingleArtifact(downloadSpecification.filesToDownload); 99 | } 100 | return { 101 | artifactName: name, 102 | downloadPath: downloadSpecification.rootDownloadLocation 103 | }; 104 | }); 105 | } 106 | downloadAllArtifacts(path) { 107 | return __awaiter(this, void 0, void 0, function* () { 108 | const response = []; 109 | const artifacts = yield internal_download_http_client_1.listArtifacts(); 110 | if (artifacts.count === 0) { 111 | core.info('Unable to find any artifacts for the associated workflow'); 112 | return response; 113 | } 114 | if (!path) { 115 | path = internal_config_variables_1.getWorkSpaceDirectory(); 116 | } 117 | path = path_1.normalize(path); 118 | path = path_1.resolve(path); 119 | const ARTIFACT_CONCURRENCY = internal_config_variables_1.getDownloadArtifactConcurrency(); 120 | const parallelDownloads = [...new Array(ARTIFACT_CONCURRENCY).keys()]; 121 | let downloadedArtifacts = 0; 122 | yield Promise.all(parallelDownloads.map(() => __awaiter(this, void 0, void 0, function* () { 123 | while (downloadedArtifacts < artifacts.count) { 124 | const currentArtifactToDownload = artifacts.value[downloadedArtifacts]; 125 | downloadedArtifacts += 1; 126 | // Get container entries for the specific artifact 127 | const items = yield internal_download_http_client_1.getContainerItems(currentArtifactToDownload.name, currentArtifactToDownload.fileContainerResourceUrl); 128 | // Promise.All is not correctly inferring that 'path' is no longer possibly undefined: https://github.com/microsoft/TypeScript/issues/34925 129 | const downloadSpecification = internal_download_specification_1.getDownloadSpecification(currentArtifactToDownload.name, items.value, path, // eslint-disable-line @typescript-eslint/no-non-null-assertion 130 | true); 131 | if (downloadSpecification.filesToDownload.length === 0) { 132 | core.info(`No downloadable files were found for any artifact ${currentArtifactToDownload.name}`); 133 | } 134 | else { 135 | yield internal_utils_1.createDirectoriesForArtifact(downloadSpecification.directoryStructure); 136 | yield internal_download_http_client_1.downloadSingleArtifact(downloadSpecification.filesToDownload); 137 | } 138 | response.push({ 139 | artifactName: currentArtifactToDownload.name, 140 | downloadPath: downloadSpecification.rootDownloadLocation 141 | }); 142 | } 143 | }))); 144 | return response; 145 | }); 146 | } 147 | } 148 | exports.DefaultArtifactClient = DefaultArtifactClient; 149 | //# sourceMappingURL=internal-artifact-client.js.map -------------------------------------------------------------------------------- /.action/artifact/lib/internal-config-variables.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | function getUploadFileConcurrency() { 4 | return 2; 5 | } 6 | exports.getUploadFileConcurrency = getUploadFileConcurrency; 7 | function getUploadChunkConcurrency() { 8 | return 1; 9 | } 10 | exports.getUploadChunkConcurrency = getUploadChunkConcurrency; 11 | function getUploadChunkSize() { 12 | return 4 * 1024 * 1024; // 4 MB Chunks 13 | } 14 | exports.getUploadChunkSize = getUploadChunkSize; 15 | function getDownloadFileConcurrency() { 16 | return 2; 17 | } 18 | exports.getDownloadFileConcurrency = getDownloadFileConcurrency; 19 | function getDownloadArtifactConcurrency() { 20 | // when downloading all artifact at once, this is number of concurrent artifacts being downloaded 21 | return 1; 22 | } 23 | exports.getDownloadArtifactConcurrency = getDownloadArtifactConcurrency; 24 | function getRuntimeToken() { 25 | const token = process.env['ACTIONS_RUNTIME_TOKEN']; 26 | if (!token) { 27 | throw new Error('Unable to get ACTIONS_RUNTIME_TOKEN env variable'); 28 | } 29 | return token; 30 | } 31 | exports.getRuntimeToken = getRuntimeToken; 32 | function getRuntimeUrl() { 33 | const runtimeUrl = process.env['ACTIONS_RUNTIME_URL']; 34 | if (!runtimeUrl) { 35 | throw new Error('Unable to get ACTIONS_RUNTIME_URL env variable'); 36 | } 37 | return runtimeUrl; 38 | } 39 | exports.getRuntimeUrl = getRuntimeUrl; 40 | function getWorkFlowRunId() { 41 | const workFlowRunId = process.env['GITHUB_RUN_ID']; 42 | if (!workFlowRunId) { 43 | throw new Error('Unable to get GITHUB_RUN_ID env variable'); 44 | } 45 | return workFlowRunId; 46 | } 47 | exports.getWorkFlowRunId = getWorkFlowRunId; 48 | function getWorkSpaceDirectory() { 49 | const workspaceDirectory = process.env['GITHUB_WORKSPACE']; 50 | if (!workspaceDirectory) { 51 | throw new Error('Unable to get GITHUB_WORKSPACE env variable'); 52 | } 53 | return workspaceDirectory; 54 | } 55 | exports.getWorkSpaceDirectory = getWorkSpaceDirectory; 56 | //# sourceMappingURL=internal-config-variables.js.map -------------------------------------------------------------------------------- /.action/artifact/lib/internal-contracts.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | //# sourceMappingURL=internal-contracts.js.map -------------------------------------------------------------------------------- /.action/artifact/lib/internal-download-http-client.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __importStar = (this && this.__importStar) || function (mod) { 12 | if (mod && mod.__esModule) return mod; 13 | var result = {}; 14 | if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; 15 | result["default"] = mod; 16 | return result; 17 | }; 18 | Object.defineProperty(exports, "__esModule", { value: true }); 19 | const fs = __importStar(require("fs")); 20 | const internal_utils_1 = require("./internal-utils"); 21 | const url_1 = require("url"); 22 | const internal_config_variables_1 = require("./internal-config-variables"); 23 | const core_1 = require("../../core"); 24 | /** 25 | * Gets a list of all artifacts that are in a specific container 26 | */ 27 | function listArtifacts() { 28 | return __awaiter(this, void 0, void 0, function* () { 29 | const artifactUrl = internal_utils_1.getArtifactUrl(); 30 | const client = internal_utils_1.createHttpClient(); 31 | const requestOptions = internal_utils_1.getRequestOptions('application/json'); 32 | const rawResponse = yield client.get(artifactUrl, requestOptions); 33 | const body = yield rawResponse.readBody(); 34 | if (internal_utils_1.isSuccessStatusCode(rawResponse.message.statusCode) && body) { 35 | return JSON.parse(body); 36 | } 37 | // eslint-disable-next-line no-console 38 | console.log(rawResponse); 39 | throw new Error(`Unable to list artifacts for the run`); 40 | }); 41 | } 42 | exports.listArtifacts = listArtifacts; 43 | /** 44 | * Fetches a set of container items that describe the contents of an artifact 45 | * @param artifactName the name of the artifact 46 | * @param containerUrl the artifact container URL for the run 47 | */ 48 | function getContainerItems(artifactName, containerUrl) { 49 | return __awaiter(this, void 0, void 0, function* () { 50 | // The itemPath search parameter controls which containers will be returned 51 | const resourceUrl = new url_1.URL(containerUrl); 52 | resourceUrl.searchParams.append('itemPath', artifactName); 53 | const client = internal_utils_1.createHttpClient(); 54 | const rawResponse = yield client.get(resourceUrl.toString()); 55 | const body = yield rawResponse.readBody(); 56 | if (internal_utils_1.isSuccessStatusCode(rawResponse.message.statusCode) && body) { 57 | return JSON.parse(body); 58 | } 59 | // eslint-disable-next-line no-console 60 | console.log(rawResponse); 61 | throw new Error(`Unable to get ContainersItems from ${resourceUrl}`); 62 | }); 63 | } 64 | exports.getContainerItems = getContainerItems; 65 | /** 66 | * Concurrently downloads all the files that are part of an artifact 67 | * @param downloadItems information about what items to download and where to save them 68 | */ 69 | function downloadSingleArtifact(downloadItems) { 70 | return __awaiter(this, void 0, void 0, function* () { 71 | const DOWNLOAD_CONCURRENCY = internal_config_variables_1.getDownloadFileConcurrency(); 72 | // Limit the number of files downloaded at a single time 73 | const parallelDownloads = [...new Array(DOWNLOAD_CONCURRENCY).keys()]; 74 | const client = internal_utils_1.createHttpClient(); 75 | let downloadedFiles = 0; 76 | yield Promise.all(parallelDownloads.map(() => __awaiter(this, void 0, void 0, function* () { 77 | while (downloadedFiles < downloadItems.length) { 78 | const currentFileToDownload = downloadItems[downloadedFiles]; 79 | downloadedFiles += 1; 80 | yield downloadIndividualFile(client, currentFileToDownload.sourceLocation, currentFileToDownload.targetPath); 81 | } 82 | }))); 83 | }); 84 | } 85 | exports.downloadSingleArtifact = downloadSingleArtifact; 86 | /** 87 | * Downloads an individual file 88 | * @param client http client that will be used to make the necessary calls 89 | * @param artifactLocation origin location where a file will be downloaded from 90 | * @param downloadPath destination location for the file being downloaded 91 | */ 92 | function downloadIndividualFile(client, artifactLocation, downloadPath) { 93 | return __awaiter(this, void 0, void 0, function* () { 94 | const stream = fs.createWriteStream(downloadPath); 95 | const response = yield client.get(artifactLocation); 96 | if (internal_utils_1.isSuccessStatusCode(response.message.statusCode)) { 97 | yield pipeResponseToStream(response, stream); 98 | } 99 | else if (internal_utils_1.isRetryableStatusCode(response.message.statusCode)) { 100 | core_1.warning(`Received http ${response.message.statusCode} during file download, will retry ${artifactLocation} after 10 seconds`); 101 | yield new Promise(resolve => setTimeout(resolve, 10000)); 102 | const retryResponse = yield client.get(artifactLocation); 103 | if (internal_utils_1.isSuccessStatusCode(retryResponse.message.statusCode)) { 104 | yield pipeResponseToStream(response, stream); 105 | } 106 | else { 107 | // eslint-disable-next-line no-console 108 | console.log(retryResponse); 109 | throw new Error(`Unable to download ${artifactLocation}`); 110 | } 111 | } 112 | else { 113 | // eslint-disable-next-line no-console 114 | console.log(response); 115 | throw new Error(`Unable to download ${artifactLocation}`); 116 | } 117 | }); 118 | } 119 | exports.downloadIndividualFile = downloadIndividualFile; 120 | function pipeResponseToStream(response, stream) { 121 | return __awaiter(this, void 0, void 0, function* () { 122 | return new Promise(resolve => { 123 | response.message.pipe(stream).on('close', () => { 124 | resolve(); 125 | }); 126 | }); 127 | }); 128 | } 129 | exports.pipeResponseToStream = pipeResponseToStream; 130 | //# sourceMappingURL=internal-download-http-client.js.map -------------------------------------------------------------------------------- /.action/artifact/lib/internal-download-options.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | //# sourceMappingURL=internal-download-options.js.map -------------------------------------------------------------------------------- /.action/artifact/lib/internal-download-response.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | //# sourceMappingURL=internal-download-response.js.map -------------------------------------------------------------------------------- /.action/artifact/lib/internal-download-specification.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importStar = (this && this.__importStar) || function (mod) { 3 | if (mod && mod.__esModule) return mod; 4 | var result = {}; 5 | if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; 6 | result["default"] = mod; 7 | return result; 8 | }; 9 | Object.defineProperty(exports, "__esModule", { value: true }); 10 | const path = __importStar(require("path")); 11 | /** 12 | * Creates a specification for a set of files that will be downloaded 13 | * @param artifactName the name of the artifact 14 | * @param artifactEntries a set of container entries that describe that files that make up an artifact 15 | * @param downloadPath the path where the artifact will be downloaded to 16 | * @param includeRootDirectory specifies if there should be an extra directory (denoted by the artifact name) where the artifact files should be downloaded to 17 | */ 18 | function getDownloadSpecification(artifactName, artifactEntries, downloadPath, includeRootDirectory) { 19 | const directories = new Set(); 20 | const specifications = { 21 | rootDownloadLocation: includeRootDirectory 22 | ? path.join(downloadPath, artifactName) 23 | : downloadPath, 24 | directoryStructure: [], 25 | filesToDownload: [] 26 | }; 27 | for (const entry of artifactEntries) { 28 | // Ignore artifacts in the container that don't begin with the same name 29 | if (entry.path.startsWith(`${artifactName}/`) || 30 | entry.path.startsWith(`${artifactName}\\`)) { 31 | // normalize all separators to the local OS 32 | const normalizedPathEntry = path.normalize(entry.path); 33 | // entry.path always starts with the artifact name, if includeRootDirectory is false, remove the name from the beginning of the path 34 | const filePath = path.join(downloadPath, includeRootDirectory 35 | ? normalizedPathEntry 36 | : normalizedPathEntry.replace(artifactName, '')); 37 | // Case insensitive folder structure maintained in the backend, not every folder is created so the 'folder' 38 | // itemType cannot be relied upon. The file must be used to determine the directory structure 39 | if (entry.itemType === 'file') { 40 | // Get the directories that we need to create from the filePath for each individual file 41 | directories.add(path.dirname(filePath)); 42 | specifications.filesToDownload.push({ 43 | sourceLocation: entry.contentLocation, 44 | targetPath: filePath 45 | }); 46 | } 47 | } 48 | } 49 | specifications.directoryStructure = Array.from(directories); 50 | return specifications; 51 | } 52 | exports.getDownloadSpecification = getDownloadSpecification; 53 | //# sourceMappingURL=internal-download-specification.js.map -------------------------------------------------------------------------------- /.action/artifact/lib/internal-upload-options.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | //# sourceMappingURL=internal-upload-options.js.map -------------------------------------------------------------------------------- /.action/artifact/lib/internal-upload-response.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | //# sourceMappingURL=internal-upload-response.js.map -------------------------------------------------------------------------------- /.action/artifact/lib/internal-upload-specification.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importStar = (this && this.__importStar) || function (mod) { 3 | if (mod && mod.__esModule) return mod; 4 | var result = {}; 5 | if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; 6 | result["default"] = mod; 7 | return result; 8 | }; 9 | Object.defineProperty(exports, "__esModule", { value: true }); 10 | const fs = __importStar(require("fs")); 11 | const core_1 = require("../../core"); 12 | const path_1 = require("path"); 13 | const internal_utils_1 = require("./internal-utils"); 14 | /** 15 | * Creates a specification that describes how each file that is part of the artifact will be uploaded 16 | * @param artifactName the name of the artifact being uploaded. Used during upload to denote where the artifact is stored on the server 17 | * @param rootDirectory an absolute file path that denotes the path that should be removed from the beginning of each artifact file 18 | * @param artifactFiles a list of absolute file paths that denote what should be uploaded as part of the artifact 19 | */ 20 | function getUploadSpecification(artifactName, rootDirectory, artifactFiles) { 21 | internal_utils_1.checkArtifactName(artifactName); 22 | const specifications = []; 23 | if (!fs.existsSync(rootDirectory)) { 24 | throw new Error(`Provided rootDirectory ${rootDirectory} does not exist`); 25 | } 26 | if (!fs.lstatSync(rootDirectory).isDirectory()) { 27 | throw new Error(`Provided rootDirectory ${rootDirectory} is not a valid directory`); 28 | } 29 | // Normalize and resolve, this allows for either absolute or relative paths to be used 30 | rootDirectory = path_1.normalize(rootDirectory); 31 | rootDirectory = path_1.resolve(rootDirectory); 32 | /* 33 | Example to demonstrate behavior 34 | 35 | Input: 36 | artifactName: my-artifact 37 | rootDirectory: '/home/user/files/plz-upload' 38 | artifactFiles: [ 39 | '/home/user/files/plz-upload/file1.txt', 40 | '/home/user/files/plz-upload/file2.txt', 41 | '/home/user/files/plz-upload/dir/file3.txt' 42 | ] 43 | 44 | Output: 45 | specifications: [ 46 | ['/home/user/files/plz-upload/file1.txt', 'my-artifact/file1.txt'], 47 | ['/home/user/files/plz-upload/file1.txt', 'my-artifact/file2.txt'], 48 | ['/home/user/files/plz-upload/file1.txt', 'my-artifact/dir/file3.txt'] 49 | ] 50 | */ 51 | for (let file of artifactFiles) { 52 | if (!fs.existsSync(file)) { 53 | throw new Error(`File ${file} does not exist`); 54 | } 55 | if (!fs.lstatSync(file).isDirectory()) { 56 | // Normalize and resolve, this allows for either absolute or relative paths to be used 57 | file = path_1.normalize(file); 58 | file = path_1.resolve(file); 59 | if (!file.startsWith(rootDirectory)) { 60 | throw new Error(`The rootDirectory: ${rootDirectory} is not a parent directory of the file: ${file}`); 61 | } 62 | /* 63 | uploadFilePath denotes where the file will be uploaded in the file container on the server. During a run, if multiple artifacts are uploaded, they will all 64 | be saved in the same container. The artifact name is used as the root directory in the container to separate and distinguish uploaded artifacts 65 | 66 | path.join handles all the following cases and would return 'artifact-name/file-to-upload.txt 67 | join('artifact-name/', 'file-to-upload.txt') 68 | join('artifact-name/', '/file-to-upload.txt') 69 | join('artifact-name', 'file-to-upload.txt') 70 | join('artifact-name', '/file-to-upload.txt') 71 | */ 72 | specifications.push({ 73 | absoluteFilePath: file, 74 | uploadFilePath: path_1.join(artifactName, file.replace(rootDirectory, '')) 75 | }); 76 | } 77 | else { 78 | // Directories are rejected by the server during upload 79 | core_1.debug(`Removing ${file} from rawSearchResults because it is a directory`); 80 | } 81 | } 82 | return specifications; 83 | } 84 | exports.getUploadSpecification = getUploadSpecification; 85 | //# sourceMappingURL=internal-upload-specification.js.map -------------------------------------------------------------------------------- /.action/artifact/lib/internal-utils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | const core_1 = require("../../core"); 13 | const fs_1 = require("fs"); 14 | const http_client_1 = require("../../http-client"); 15 | const auth_1 = require("../../http-client/auth"); 16 | const internal_config_variables_1 = require("./internal-config-variables"); 17 | /** 18 | * Parses a env variable that is a number 19 | */ 20 | function parseEnvNumber(key) { 21 | const value = Number(process.env[key]); 22 | if (Number.isNaN(value) || value < 0) { 23 | return undefined; 24 | } 25 | return value; 26 | } 27 | exports.parseEnvNumber = parseEnvNumber; 28 | /** 29 | * Various utility functions to help with the necessary API calls 30 | */ 31 | function getApiVersion() { 32 | return '6.0-preview'; 33 | } 34 | exports.getApiVersion = getApiVersion; 35 | function isSuccessStatusCode(statusCode) { 36 | if (!statusCode) { 37 | return false; 38 | } 39 | return statusCode >= 200 && statusCode < 300; 40 | } 41 | exports.isSuccessStatusCode = isSuccessStatusCode; 42 | function isRetryableStatusCode(statusCode) { 43 | if (!statusCode) { 44 | return false; 45 | } 46 | const retryableStatusCodes = [ 47 | http_client_1.HttpCodes.BadGateway, 48 | http_client_1.HttpCodes.ServiceUnavailable, 49 | http_client_1.HttpCodes.GatewayTimeout 50 | ]; 51 | return retryableStatusCodes.includes(statusCode); 52 | } 53 | exports.isRetryableStatusCode = isRetryableStatusCode; 54 | function getContentRange(start, end, total) { 55 | // Format: `bytes start-end/fileSize 56 | // start and end are inclusive 57 | // For a 200 byte chunk starting at byte 0: 58 | // Content-Range: bytes 0-199/200 59 | return `bytes ${start}-${end}/${total}`; 60 | } 61 | exports.getContentRange = getContentRange; 62 | function getRequestOptions(contentType, contentLength, contentRange) { 63 | const requestOptions = { 64 | Accept: `application/json;api-version=${getApiVersion()}` 65 | }; 66 | if (contentType) { 67 | requestOptions['Content-Type'] = contentType; 68 | } 69 | if (contentLength) { 70 | requestOptions['Content-Length'] = contentLength; 71 | } 72 | if (contentRange) { 73 | requestOptions['Content-Range'] = contentRange; 74 | } 75 | return requestOptions; 76 | } 77 | exports.getRequestOptions = getRequestOptions; 78 | function createHttpClient() { 79 | return new http_client_1.HttpClient('action/artifact', [ 80 | new auth_1.BearerCredentialHandler(internal_config_variables_1.getRuntimeToken()) 81 | ]); 82 | } 83 | exports.createHttpClient = createHttpClient; 84 | function getArtifactUrl() { 85 | const artifactUrl = `${internal_config_variables_1.getRuntimeUrl()}_apis/pipelines/workflows/${internal_config_variables_1.getWorkFlowRunId()}/artifacts?api-version=${getApiVersion()}`; 86 | core_1.debug(`Artifact Url: ${artifactUrl}`); 87 | return artifactUrl; 88 | } 89 | exports.getArtifactUrl = getArtifactUrl; 90 | /** 91 | * Invalid characters that cannot be in the artifact name or an uploaded file. Will be rejected 92 | * from the server if attempted to be sent over. These characters are not allowed due to limitations with certain 93 | * file systems such as NTFS. To maintain platform-agnostic behavior, all characters that are not supported by an 94 | * individual filesystem/platform will not be supported on all fileSystems/platforms 95 | */ 96 | const invalidCharacters = ['\\', '/', '"', ':', '<', '>', '|', '*', '?', ' ']; 97 | /** 98 | * Scans the name of the item being uploaded to make sure there are no illegal characters 99 | */ 100 | function checkArtifactName(name) { 101 | if (!name) { 102 | throw new Error(`Artifact name: ${name}, is incorrectly provided`); 103 | } 104 | for (const invalidChar of invalidCharacters) { 105 | if (name.includes(invalidChar)) { 106 | throw new Error(`Artifact name is not valid: ${name}. Contains character: "${invalidChar}". Invalid characters include: ${invalidCharacters.toString()}.`); 107 | } 108 | } 109 | } 110 | exports.checkArtifactName = checkArtifactName; 111 | function createDirectoriesForArtifact(directories) { 112 | return __awaiter(this, void 0, void 0, function* () { 113 | for (const directory of directories) { 114 | yield fs_1.promises.mkdir(directory, { 115 | recursive: true 116 | }); 117 | } 118 | }); 119 | } 120 | exports.createDirectoriesForArtifact = createDirectoriesForArtifact; 121 | //# sourceMappingURL=internal-utils.js.map -------------------------------------------------------------------------------- /.action/artifact/lib/internal/__mocks__/config-variables.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | /** 4 | * Mocks default limits for easier testing 5 | */ 6 | function getUploadFileConcurrency() { 7 | return 1; 8 | } 9 | exports.getUploadFileConcurrency = getUploadFileConcurrency; 10 | function getUploadChunkConcurrency() { 11 | return 1; 12 | } 13 | exports.getUploadChunkConcurrency = getUploadChunkConcurrency; 14 | function getUploadChunkSize() { 15 | return 4 * 1024 * 1024; // 4 MB Chunks 16 | } 17 | exports.getUploadChunkSize = getUploadChunkSize; 18 | function getRetryLimit() { 19 | return 2; 20 | } 21 | exports.getRetryLimit = getRetryLimit; 22 | function getRetryMultiplier() { 23 | return 1.5; 24 | } 25 | exports.getRetryMultiplier = getRetryMultiplier; 26 | function getInitialRetryIntervalInMilliseconds() { 27 | return 10; 28 | } 29 | exports.getInitialRetryIntervalInMilliseconds = getInitialRetryIntervalInMilliseconds; 30 | function getDownloadFileConcurrency() { 31 | return 1; 32 | } 33 | exports.getDownloadFileConcurrency = getDownloadFileConcurrency; 34 | /** 35 | * Mocks the 'ACTIONS_RUNTIME_TOKEN', 'ACTIONS_RUNTIME_URL' and 'GITHUB_RUN_ID' env variables 36 | * that are only available from a node context on the runner. This allows for tests to run 37 | * locally without the env variables actually being set 38 | */ 39 | function getRuntimeToken() { 40 | return 'totally-valid-token'; 41 | } 42 | exports.getRuntimeToken = getRuntimeToken; 43 | function getRuntimeUrl() { 44 | return 'https://www.example.com/'; 45 | } 46 | exports.getRuntimeUrl = getRuntimeUrl; 47 | function getWorkFlowRunId() { 48 | return '15'; 49 | } 50 | exports.getWorkFlowRunId = getWorkFlowRunId; 51 | //# sourceMappingURL=config-variables.js.map -------------------------------------------------------------------------------- /.action/artifact/lib/internal/artifact-client.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __importStar = (this && this.__importStar) || function (mod) { 12 | if (mod && mod.__esModule) return mod; 13 | var result = {}; 14 | if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; 15 | result["default"] = mod; 16 | return result; 17 | }; 18 | Object.defineProperty(exports, "__esModule", { value: true }); 19 | const core = __importStar(require("../../../core")); 20 | const upload_specification_1 = require("./upload-specification"); 21 | const upload_http_client_1 = require("./upload-http-client"); 22 | const utils_1 = require("./utils"); 23 | const download_http_client_1 = require("./download-http-client"); 24 | const download_specification_1 = require("./download-specification"); 25 | const config_variables_1 = require("./config-variables"); 26 | const path_1 = require("path"); 27 | class DefaultArtifactClient { 28 | /** 29 | * Constructs a DefaultArtifactClient 30 | */ 31 | static create() { 32 | return new DefaultArtifactClient(); 33 | } 34 | /** 35 | * Uploads an artifact 36 | */ 37 | uploadArtifact(name, files, rootDirectory, options) { 38 | return __awaiter(this, void 0, void 0, function* () { 39 | utils_1.checkArtifactName(name); 40 | // Get specification for the files being uploaded 41 | const uploadSpecification = upload_specification_1.getUploadSpecification(name, rootDirectory, files); 42 | const uploadResponse = { 43 | artifactName: name, 44 | artifactItems: [], 45 | size: 0, 46 | failedItems: [] 47 | }; 48 | const uploadHttpClient = new upload_http_client_1.UploadHttpClient(); 49 | if (uploadSpecification.length === 0) { 50 | core.warning(`No files found that can be uploaded`); 51 | } 52 | else { 53 | // Create an entry for the artifact in the file container 54 | const response = yield uploadHttpClient.createArtifactInFileContainer(name); 55 | if (!response.fileContainerResourceUrl) { 56 | core.debug(response.toString()); 57 | throw new Error('No URL provided by the Artifact Service to upload an artifact to'); 58 | } 59 | core.debug(`Upload Resource URL: ${response.fileContainerResourceUrl}`); 60 | // Upload each of the files that were found concurrently 61 | const uploadResult = yield uploadHttpClient.uploadArtifactToFileContainer(response.fileContainerResourceUrl, uploadSpecification, options); 62 | // Update the size of the artifact to indicate we are done uploading 63 | // The uncompressed size is used for display when downloading a zip of the artifact from the UI 64 | yield uploadHttpClient.patchArtifactSize(uploadResult.totalSize, name); 65 | core.info(`Finished uploading artifact ${name}. Reported size is ${uploadResult.uploadSize} bytes. There were ${uploadResult.failedItems.length} items that failed to upload`); 66 | uploadResponse.artifactItems = uploadSpecification.map(item => item.absoluteFilePath); 67 | uploadResponse.size = uploadResult.uploadSize; 68 | uploadResponse.failedItems = uploadResult.failedItems; 69 | } 70 | return uploadResponse; 71 | }); 72 | } 73 | downloadArtifact(name, path, options) { 74 | return __awaiter(this, void 0, void 0, function* () { 75 | const downloadHttpClient = new download_http_client_1.DownloadHttpClient(); 76 | const artifacts = yield downloadHttpClient.listArtifacts(); 77 | if (artifacts.count === 0) { 78 | throw new Error(`Unable to find any artifacts for the associated workflow`); 79 | } 80 | const artifactToDownload = artifacts.value.find(artifact => { 81 | return artifact.name === name; 82 | }); 83 | if (!artifactToDownload) { 84 | throw new Error(`Unable to find an artifact with the name: ${name}`); 85 | } 86 | const items = yield downloadHttpClient.getContainerItems(artifactToDownload.name, artifactToDownload.fileContainerResourceUrl); 87 | if (!path) { 88 | path = config_variables_1.getWorkSpaceDirectory(); 89 | } 90 | path = path_1.normalize(path); 91 | path = path_1.resolve(path); 92 | // During upload, empty directories are rejected by the remote server so there should be no artifacts that consist of only empty directories 93 | const downloadSpecification = download_specification_1.getDownloadSpecification(name, items.value, path, (options === null || options === void 0 ? void 0 : options.createArtifactFolder) || false); 94 | if (downloadSpecification.filesToDownload.length === 0) { 95 | core.info(`No downloadable files were found for the artifact: ${artifactToDownload.name}`); 96 | } 97 | else { 98 | // Create all necessary directories recursively before starting any download 99 | yield utils_1.createDirectoriesForArtifact(downloadSpecification.directoryStructure); 100 | core.info('Directory structure has been setup for the artifact'); 101 | yield utils_1.createEmptyFilesForArtifact(downloadSpecification.emptyFilesToCreate); 102 | yield downloadHttpClient.downloadSingleArtifact(downloadSpecification.filesToDownload); 103 | } 104 | return { 105 | artifactName: name, 106 | downloadPath: downloadSpecification.rootDownloadLocation 107 | }; 108 | }); 109 | } 110 | downloadAllArtifacts(path) { 111 | return __awaiter(this, void 0, void 0, function* () { 112 | const downloadHttpClient = new download_http_client_1.DownloadHttpClient(); 113 | const response = []; 114 | const artifacts = yield downloadHttpClient.listArtifacts(); 115 | if (artifacts.count === 0) { 116 | core.info('Unable to find any artifacts for the associated workflow'); 117 | return response; 118 | } 119 | if (!path) { 120 | path = config_variables_1.getWorkSpaceDirectory(); 121 | } 122 | path = path_1.normalize(path); 123 | path = path_1.resolve(path); 124 | let downloadedArtifacts = 0; 125 | while (downloadedArtifacts < artifacts.count) { 126 | const currentArtifactToDownload = artifacts.value[downloadedArtifacts]; 127 | downloadedArtifacts += 1; 128 | // Get container entries for the specific artifact 129 | const items = yield downloadHttpClient.getContainerItems(currentArtifactToDownload.name, currentArtifactToDownload.fileContainerResourceUrl); 130 | const downloadSpecification = download_specification_1.getDownloadSpecification(currentArtifactToDownload.name, items.value, path, true); 131 | if (downloadSpecification.filesToDownload.length === 0) { 132 | core.info(`No downloadable files were found for any artifact ${currentArtifactToDownload.name}`); 133 | } 134 | else { 135 | yield utils_1.createDirectoriesForArtifact(downloadSpecification.directoryStructure); 136 | yield utils_1.createEmptyFilesForArtifact(downloadSpecification.emptyFilesToCreate); 137 | yield downloadHttpClient.downloadSingleArtifact(downloadSpecification.filesToDownload); 138 | } 139 | response.push({ 140 | artifactName: currentArtifactToDownload.name, 141 | downloadPath: downloadSpecification.rootDownloadLocation 142 | }); 143 | } 144 | return response; 145 | }); 146 | } 147 | } 148 | exports.DefaultArtifactClient = DefaultArtifactClient; 149 | //# sourceMappingURL=artifact-client.js.map -------------------------------------------------------------------------------- /.action/artifact/lib/internal/config-variables.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | // The number of concurrent uploads that happens at the same time 4 | function getUploadFileConcurrency() { 5 | return 2; 6 | } 7 | exports.getUploadFileConcurrency = getUploadFileConcurrency; 8 | // When uploading large files that can't be uploaded with a single http call, this controls 9 | // the chunk size that is used during upload 10 | function getUploadChunkSize() { 11 | return 4 * 1024 * 1024; // 4 MB Chunks 12 | } 13 | exports.getUploadChunkSize = getUploadChunkSize; 14 | // The maximum number of retries that can be attempted before an upload or download fails 15 | function getRetryLimit() { 16 | return 5; 17 | } 18 | exports.getRetryLimit = getRetryLimit; 19 | // With exponential backoff, the larger the retry count, the larger the wait time before another attempt 20 | // The retry multiplier controls by how much the backOff time increases depending on the number of retries 21 | function getRetryMultiplier() { 22 | return 1.5; 23 | } 24 | exports.getRetryMultiplier = getRetryMultiplier; 25 | // The initial wait time if an upload or download fails and a retry is being attempted for the first time 26 | function getInitialRetryIntervalInMilliseconds() { 27 | return 3000; 28 | } 29 | exports.getInitialRetryIntervalInMilliseconds = getInitialRetryIntervalInMilliseconds; 30 | // The number of concurrent downloads that happens at the same time 31 | function getDownloadFileConcurrency() { 32 | return 2; 33 | } 34 | exports.getDownloadFileConcurrency = getDownloadFileConcurrency; 35 | function getRuntimeToken() { 36 | const token = process.env['ACTIONS_RUNTIME_TOKEN']; 37 | if (!token) { 38 | throw new Error('Unable to get ACTIONS_RUNTIME_TOKEN env variable'); 39 | } 40 | return token; 41 | } 42 | exports.getRuntimeToken = getRuntimeToken; 43 | function getRuntimeUrl() { 44 | const runtimeUrl = process.env['ACTIONS_RUNTIME_URL']; 45 | if (!runtimeUrl) { 46 | throw new Error('Unable to get ACTIONS_RUNTIME_URL env variable'); 47 | } 48 | return runtimeUrl; 49 | } 50 | exports.getRuntimeUrl = getRuntimeUrl; 51 | function getWorkFlowRunId() { 52 | const workFlowRunId = process.env['GITHUB_RUN_ID']; 53 | if (!workFlowRunId) { 54 | throw new Error('Unable to get GITHUB_RUN_ID env variable'); 55 | } 56 | return workFlowRunId; 57 | } 58 | exports.getWorkFlowRunId = getWorkFlowRunId; 59 | function getWorkSpaceDirectory() { 60 | const workspaceDirectory = process.env['GITHUB_WORKSPACE']; 61 | if (!workspaceDirectory) { 62 | throw new Error('Unable to get GITHUB_WORKSPACE env variable'); 63 | } 64 | return workspaceDirectory; 65 | } 66 | exports.getWorkSpaceDirectory = getWorkSpaceDirectory; 67 | //# sourceMappingURL=config-variables.js.map -------------------------------------------------------------------------------- /.action/artifact/lib/internal/contracts.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | //# sourceMappingURL=contracts.js.map -------------------------------------------------------------------------------- /.action/artifact/lib/internal/download-options.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | //# sourceMappingURL=download-options.js.map -------------------------------------------------------------------------------- /.action/artifact/lib/internal/download-response.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | //# sourceMappingURL=download-response.js.map -------------------------------------------------------------------------------- /.action/artifact/lib/internal/download-specification.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importStar = (this && this.__importStar) || function (mod) { 3 | if (mod && mod.__esModule) return mod; 4 | var result = {}; 5 | if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; 6 | result["default"] = mod; 7 | return result; 8 | }; 9 | Object.defineProperty(exports, "__esModule", { value: true }); 10 | const path = __importStar(require("path")); 11 | /** 12 | * Creates a specification for a set of files that will be downloaded 13 | * @param artifactName the name of the artifact 14 | * @param artifactEntries a set of container entries that describe that files that make up an artifact 15 | * @param downloadPath the path where the artifact will be downloaded to 16 | * @param includeRootDirectory specifies if there should be an extra directory (denoted by the artifact name) where the artifact files should be downloaded to 17 | */ 18 | function getDownloadSpecification(artifactName, artifactEntries, downloadPath, includeRootDirectory) { 19 | // use a set for the directory paths so that there are no duplicates 20 | const directories = new Set(); 21 | const specifications = { 22 | rootDownloadLocation: includeRootDirectory 23 | ? path.join(downloadPath, artifactName) 24 | : downloadPath, 25 | directoryStructure: [], 26 | emptyFilesToCreate: [], 27 | filesToDownload: [] 28 | }; 29 | for (const entry of artifactEntries) { 30 | // Ignore artifacts in the container that don't begin with the same name 31 | if (entry.path.startsWith(`${artifactName}/`) || 32 | entry.path.startsWith(`${artifactName}\\`)) { 33 | // normalize all separators to the local OS 34 | const normalizedPathEntry = path.normalize(entry.path); 35 | // entry.path always starts with the artifact name, if includeRootDirectory is false, remove the name from the beginning of the path 36 | const filePath = path.join(downloadPath, includeRootDirectory 37 | ? normalizedPathEntry 38 | : normalizedPathEntry.replace(artifactName, '')); 39 | // Case insensitive folder structure maintained in the backend, not every folder is created so the 'folder' 40 | // itemType cannot be relied upon. The file must be used to determine the directory structure 41 | if (entry.itemType === 'file') { 42 | // Get the directories that we need to create from the filePath for each individual file 43 | directories.add(path.dirname(filePath)); 44 | if (entry.fileLength === 0) { 45 | // An empty file was uploaded, create the empty files locally so that no extra http calls are made 46 | specifications.emptyFilesToCreate.push(filePath); 47 | } 48 | else { 49 | specifications.filesToDownload.push({ 50 | sourceLocation: entry.contentLocation, 51 | targetPath: filePath 52 | }); 53 | } 54 | } 55 | } 56 | } 57 | specifications.directoryStructure = Array.from(directories); 58 | return specifications; 59 | } 60 | exports.getDownloadSpecification = getDownloadSpecification; 61 | //# sourceMappingURL=download-specification.js.map -------------------------------------------------------------------------------- /.action/artifact/lib/internal/http-manager.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const utils_1 = require("./utils"); 4 | /** 5 | * Used for managing http clients during either upload or download 6 | */ 7 | class HttpManager { 8 | constructor(clientCount) { 9 | if (clientCount < 1) { 10 | throw new Error('There must be at least one client'); 11 | } 12 | this.clients = new Array(clientCount).fill(utils_1.createHttpClient()); 13 | } 14 | getClient(index) { 15 | return this.clients[index]; 16 | } 17 | // client disposal is necessary if a keep-alive connection is used to properly close the connection 18 | // for more information see: https://github.com/actions/http-client/blob/04e5ad73cd3fd1f5610a32116b0759eddf6570d2/index.ts#L292 19 | disposeAndReplaceClient(index) { 20 | this.clients[index].dispose(); 21 | this.clients[index] = utils_1.createHttpClient(); 22 | } 23 | disposeAndReplaceAllClients() { 24 | for (const [index] of this.clients.entries()) { 25 | this.disposeAndReplaceClient(index); 26 | } 27 | } 28 | } 29 | exports.HttpManager = HttpManager; 30 | //# sourceMappingURL=http-manager.js.map -------------------------------------------------------------------------------- /.action/artifact/lib/internal/status-reporter.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const core_1 = require("../../../core"); 4 | /** 5 | * Status Reporter that displays information about the progress/status of an artifact that is being uploaded or downloaded 6 | * 7 | * Variable display time that can be adjusted using the displayFrequencyInMilliseconds variable 8 | * The total status of the upload/download gets displayed according to this value 9 | * If there is a large file that is being uploaded, extra information about the individual status can also be displayed using the updateLargeFileStatus function 10 | */ 11 | class StatusReporter { 12 | constructor(displayFrequencyInMilliseconds) { 13 | this.totalNumberOfFilesToProcess = 0; 14 | this.processedCount = 0; 15 | this.largeFiles = new Map(); 16 | this.totalFileStatus = undefined; 17 | this.largeFileStatus = undefined; 18 | this.displayFrequencyInMilliseconds = displayFrequencyInMilliseconds; 19 | } 20 | setTotalNumberOfFilesToProcess(fileTotal) { 21 | this.totalNumberOfFilesToProcess = fileTotal; 22 | } 23 | start() { 24 | // displays information about the total upload/download status 25 | this.totalFileStatus = setInterval(() => { 26 | // display 1 decimal place without any rounding 27 | const percentage = this.formatPercentage(this.processedCount, this.totalNumberOfFilesToProcess); 28 | core_1.info(`Total file count: ${this.totalNumberOfFilesToProcess} ---- Processed file #${this.processedCount} (${percentage.slice(0, percentage.indexOf('.') + 2)}%)`); 29 | }, this.displayFrequencyInMilliseconds); 30 | // displays extra information about any large files that take a significant amount of time to upload or download every 1 second 31 | this.largeFileStatus = setInterval(() => { 32 | for (const value of Array.from(this.largeFiles.values())) { 33 | core_1.info(value); 34 | } 35 | // delete all entries in the map after displaying the information so it will not be displayed again unless explicitly added 36 | this.largeFiles.clear(); 37 | }, 1000); 38 | } 39 | // if there is a large file that is being uploaded in chunks, this is used to display extra information about the status of the upload 40 | updateLargeFileStatus(fileName, numerator, denominator) { 41 | // display 1 decimal place without any rounding 42 | const percentage = this.formatPercentage(numerator, denominator); 43 | const displayInformation = `Uploading ${fileName} (${percentage.slice(0, percentage.indexOf('.') + 2)}%)`; 44 | // any previously added display information should be overwritten for the specific large file because a map is being used 45 | this.largeFiles.set(fileName, displayInformation); 46 | } 47 | stop() { 48 | if (this.totalFileStatus) { 49 | clearInterval(this.totalFileStatus); 50 | } 51 | if (this.largeFileStatus) { 52 | clearInterval(this.largeFileStatus); 53 | } 54 | } 55 | incrementProcessedCount() { 56 | this.processedCount++; 57 | } 58 | formatPercentage(numerator, denominator) { 59 | // toFixed() rounds, so use extra precision to display accurate information even though 4 decimal places are not displayed 60 | return ((numerator / denominator) * 100).toFixed(4).toString(); 61 | } 62 | } 63 | exports.StatusReporter = StatusReporter; 64 | //# sourceMappingURL=status-reporter.js.map -------------------------------------------------------------------------------- /.action/artifact/lib/internal/upload-gzip.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __asyncValues = (this && this.__asyncValues) || function (o) { 12 | if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); 13 | var m = o[Symbol.asyncIterator], i; 14 | return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i); 15 | function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; } 16 | function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); } 17 | }; 18 | var __importStar = (this && this.__importStar) || function (mod) { 19 | if (mod && mod.__esModule) return mod; 20 | var result = {}; 21 | if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; 22 | result["default"] = mod; 23 | return result; 24 | }; 25 | Object.defineProperty(exports, "__esModule", { value: true }); 26 | const fs = __importStar(require("fs")); 27 | const zlib = __importStar(require("zlib")); 28 | const util_1 = require("util"); 29 | const stat = util_1.promisify(fs.stat); 30 | /** 31 | * Creates a Gzip compressed file of an original file at the provided temporary filepath location 32 | * @param {string} originalFilePath filepath of whatever will be compressed. The original file will be unmodified 33 | * @param {string} tempFilePath the location of where the Gzip file will be created 34 | * @returns the size of gzip file that gets created 35 | */ 36 | function createGZipFileOnDisk(originalFilePath, tempFilePath) { 37 | return __awaiter(this, void 0, void 0, function* () { 38 | return new Promise((resolve, reject) => { 39 | const inputStream = fs.createReadStream(originalFilePath); 40 | const gzip = zlib.createGzip(); 41 | const outputStream = fs.createWriteStream(tempFilePath); 42 | inputStream.pipe(gzip).pipe(outputStream); 43 | outputStream.on('finish', () => __awaiter(this, void 0, void 0, function* () { 44 | // wait for stream to finish before calculating the size which is needed as part of the Content-Length header when starting an upload 45 | const size = (yield stat(tempFilePath)).size; 46 | resolve(size); 47 | })); 48 | outputStream.on('error', error => { 49 | // eslint-disable-next-line no-console 50 | console.log(error); 51 | reject; 52 | }); 53 | }); 54 | }); 55 | } 56 | exports.createGZipFileOnDisk = createGZipFileOnDisk; 57 | /** 58 | * Creates a GZip file in memory using a buffer. Should be used for smaller files to reduce disk I/O 59 | * @param originalFilePath the path to the original file that is being GZipped 60 | * @returns a buffer with the GZip file 61 | */ 62 | function createGZipFileInBuffer(originalFilePath) { 63 | return __awaiter(this, void 0, void 0, function* () { 64 | return new Promise((resolve) => __awaiter(this, void 0, void 0, function* () { 65 | var e_1, _a; 66 | const inputStream = fs.createReadStream(originalFilePath); 67 | const gzip = zlib.createGzip(); 68 | inputStream.pipe(gzip); 69 | // read stream into buffer, using experimental async iterators see https://github.com/nodejs/readable-stream/issues/403#issuecomment-479069043 70 | const chunks = []; 71 | try { 72 | for (var gzip_1 = __asyncValues(gzip), gzip_1_1; gzip_1_1 = yield gzip_1.next(), !gzip_1_1.done;) { 73 | const chunk = gzip_1_1.value; 74 | chunks.push(chunk); 75 | } 76 | } 77 | catch (e_1_1) { e_1 = { error: e_1_1 }; } 78 | finally { 79 | try { 80 | if (gzip_1_1 && !gzip_1_1.done && (_a = gzip_1.return)) yield _a.call(gzip_1); 81 | } 82 | finally { if (e_1) throw e_1.error; } 83 | } 84 | resolve(Buffer.concat(chunks)); 85 | })); 86 | }); 87 | } 88 | exports.createGZipFileInBuffer = createGZipFileInBuffer; 89 | //# sourceMappingURL=upload-gzip.js.map -------------------------------------------------------------------------------- /.action/artifact/lib/internal/upload-options.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | //# sourceMappingURL=upload-options.js.map -------------------------------------------------------------------------------- /.action/artifact/lib/internal/upload-response.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | //# sourceMappingURL=upload-response.js.map -------------------------------------------------------------------------------- /.action/artifact/lib/internal/upload-specification.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importStar = (this && this.__importStar) || function (mod) { 3 | if (mod && mod.__esModule) return mod; 4 | var result = {}; 5 | if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; 6 | result["default"] = mod; 7 | return result; 8 | }; 9 | Object.defineProperty(exports, "__esModule", { value: true }); 10 | const fs = __importStar(require("fs")); 11 | const core_1 = require("../../../core"); 12 | const path_1 = require("path"); 13 | const utils_1 = require("./utils"); 14 | /** 15 | * Creates a specification that describes how each file that is part of the artifact will be uploaded 16 | * @param artifactName the name of the artifact being uploaded. Used during upload to denote where the artifact is stored on the server 17 | * @param rootDirectory an absolute file path that denotes the path that should be removed from the beginning of each artifact file 18 | * @param artifactFiles a list of absolute file paths that denote what should be uploaded as part of the artifact 19 | */ 20 | function getUploadSpecification(artifactName, rootDirectory, artifactFiles) { 21 | utils_1.checkArtifactName(artifactName); 22 | const specifications = []; 23 | if (!fs.existsSync(rootDirectory)) { 24 | throw new Error(`Provided rootDirectory ${rootDirectory} does not exist`); 25 | } 26 | if (!fs.lstatSync(rootDirectory).isDirectory()) { 27 | throw new Error(`Provided rootDirectory ${rootDirectory} is not a valid directory`); 28 | } 29 | // Normalize and resolve, this allows for either absolute or relative paths to be used 30 | rootDirectory = path_1.normalize(rootDirectory); 31 | rootDirectory = path_1.resolve(rootDirectory); 32 | /* 33 | Example to demonstrate behavior 34 | 35 | Input: 36 | artifactName: my-artifact 37 | rootDirectory: '/home/user/files/plz-upload' 38 | artifactFiles: [ 39 | '/home/user/files/plz-upload/file1.txt', 40 | '/home/user/files/plz-upload/file2.txt', 41 | '/home/user/files/plz-upload/dir/file3.txt' 42 | ] 43 | 44 | Output: 45 | specifications: [ 46 | ['/home/user/files/plz-upload/file1.txt', 'my-artifact/file1.txt'], 47 | ['/home/user/files/plz-upload/file1.txt', 'my-artifact/file2.txt'], 48 | ['/home/user/files/plz-upload/file1.txt', 'my-artifact/dir/file3.txt'] 49 | ] 50 | */ 51 | for (let file of artifactFiles) { 52 | if (!fs.existsSync(file)) { 53 | throw new Error(`File ${file} does not exist`); 54 | } 55 | if (!fs.lstatSync(file).isDirectory()) { 56 | // Normalize and resolve, this allows for either absolute or relative paths to be used 57 | file = path_1.normalize(file); 58 | file = path_1.resolve(file); 59 | if (!file.startsWith(rootDirectory)) { 60 | throw new Error(`The rootDirectory: ${rootDirectory} is not a parent directory of the file: ${file}`); 61 | } 62 | // Check for forbidden characters in file paths that will be rejected during upload 63 | const uploadPath = file.replace(rootDirectory, ''); 64 | utils_1.checkArtifactFilePath(uploadPath); 65 | /* 66 | uploadFilePath denotes where the file will be uploaded in the file container on the server. During a run, if multiple artifacts are uploaded, they will all 67 | be saved in the same container. The artifact name is used as the root directory in the container to separate and distinguish uploaded artifacts 68 | 69 | path.join handles all the following cases and would return 'artifact-name/file-to-upload.txt 70 | join('artifact-name/', 'file-to-upload.txt') 71 | join('artifact-name/', '/file-to-upload.txt') 72 | join('artifact-name', 'file-to-upload.txt') 73 | join('artifact-name', '/file-to-upload.txt') 74 | */ 75 | specifications.push({ 76 | absoluteFilePath: file, 77 | uploadFilePath: path_1.join(artifactName, uploadPath) 78 | }); 79 | } 80 | else { 81 | // Directories are rejected by the server during upload 82 | core_1.debug(`Removing ${file} from rawSearchResults because it is a directory`); 83 | } 84 | } 85 | return specifications; 86 | } 87 | exports.getUploadSpecification = getUploadSpecification; 88 | //# sourceMappingURL=upload-specification.js.map -------------------------------------------------------------------------------- /.action/artifact/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "_from": "@actions/artifact", 3 | "_id": "@actions/artifact@0.3.2", 4 | "_inBundle": false, 5 | "_integrity": "sha512-KzUe5DEeVXprAodxfGKtx9f7ukuVKE6V6pge6t5GDGk0cdkfiMEfahoq7HfBsOsmVy4J7rr1YZQPUTvXveYinw==", 6 | "_location": "/@actions/artifact", 7 | "_phantomChildren": {}, 8 | "_requested": { 9 | "type": "tag", 10 | "registry": true, 11 | "raw": "@actions/artifact", 12 | "name": "@actions/artifact", 13 | "escapedName": "@actions%2fartifact", 14 | "scope": "@actions", 15 | "rawSpec": "", 16 | "saveSpec": null, 17 | "fetchSpec": "latest" 18 | }, 19 | "_requiredBy": [ 20 | "#USER", 21 | "/" 22 | ], 23 | "_resolved": "https://registry.npmjs.org/@actions/artifact/-/artifact-0.3.2.tgz", 24 | "_shasum": "46f6a14d8daac4503448a115ab21a9bb4efcdbe2", 25 | "_spec": "@actions/artifact", 26 | "_where": "/Users/bok/Projects/Open Source/swift-create-xcframework", 27 | "bugs": { 28 | "url": "https://github.com/actions/toolkit/issues" 29 | }, 30 | "bundleDependencies": false, 31 | "dependencies": { 32 | "@actions/core": "^1.2.1", 33 | "@actions/http-client": "^1.0.7", 34 | "@types/tmp": "^0.1.0", 35 | "tmp": "^0.1.0", 36 | "tmp-promise": "^2.0.2" 37 | }, 38 | "deprecated": false, 39 | "description": "Actions artifact lib", 40 | "devDependencies": { 41 | "typescript": "^3.8.3" 42 | }, 43 | "directories": { 44 | "lib": "lib", 45 | "test": "__tests__" 46 | }, 47 | "files": [ 48 | "lib" 49 | ], 50 | "homepage": "https://github.com/actions/toolkit/tree/master/packages/artifact", 51 | "keywords": [ 52 | "github", 53 | "actions", 54 | "artifact" 55 | ], 56 | "license": "MIT", 57 | "main": "lib/artifact-client.js", 58 | "name": "@actions/artifact", 59 | "preview": true, 60 | "publishConfig": { 61 | "access": "public" 62 | }, 63 | "repository": { 64 | "type": "git", 65 | "url": "git+https://github.com/actions/toolkit.git", 66 | "directory": "packages/artifact" 67 | }, 68 | "scripts": { 69 | "audit-moderate": "npm install && npm audit --audit-level=moderate", 70 | "test": "echo \"Error: run tests from root\" && exit 1", 71 | "tsc": "tsc" 72 | }, 73 | "types": "lib/artifact-client.d.ts", 74 | "version": "0.3.2" 75 | } 76 | -------------------------------------------------------------------------------- /.action/core/README.md: -------------------------------------------------------------------------------- 1 | # `@actions/core` 2 | 3 | > Core functions for setting results, logging, registering secrets and exporting variables across actions 4 | 5 | ## Usage 6 | 7 | ### Import the package 8 | 9 | ```js 10 | // javascript 11 | const core = require('@actions/core'); 12 | 13 | // typescript 14 | import * as core from '@actions/core'; 15 | ``` 16 | 17 | #### Inputs/Outputs 18 | 19 | Action inputs can be read with `getInput`. Outputs can be set with `setOutput` which makes them available to be mapped into inputs of other actions to ensure they are decoupled. 20 | 21 | ```js 22 | const myInput = core.getInput('inputName', { required: true }); 23 | 24 | core.setOutput('outputKey', 'outputVal'); 25 | ``` 26 | 27 | #### Exporting variables 28 | 29 | Since each step runs in a separate process, you can use `exportVariable` to add it to this step and future steps environment blocks. 30 | 31 | ```js 32 | core.exportVariable('envVar', 'Val'); 33 | ``` 34 | 35 | #### Setting a secret 36 | 37 | Setting a secret registers the secret with the runner to ensure it is masked in logs. 38 | 39 | ```js 40 | core.setSecret('myPassword'); 41 | ``` 42 | 43 | #### PATH Manipulation 44 | 45 | To make a tool's path available in the path for the remainder of the job (without altering the machine or containers state), use `addPath`. The runner will prepend the path given to the jobs PATH. 46 | 47 | ```js 48 | core.addPath('/path/to/mytool'); 49 | ``` 50 | 51 | #### Exit codes 52 | 53 | You should use this library to set the failing exit code for your action. If status is not set and the script runs to completion, that will lead to a success. 54 | 55 | ```js 56 | const core = require('@actions/core'); 57 | 58 | try { 59 | // Do stuff 60 | } 61 | catch (err) { 62 | // setFailed logs the message and sets a failing exit code 63 | core.setFailed(`Action failed with error ${err}`); 64 | } 65 | 66 | Note that `setNeutral` is not yet implemented in actions V2 but equivalent functionality is being planned. 67 | 68 | ``` 69 | 70 | #### Logging 71 | 72 | Finally, this library provides some utilities for logging. Note that debug logging is hidden from the logs by default. This behavior can be toggled by enabling the [Step Debug Logs](../../docs/action-debugging.md#step-debug-logs). 73 | 74 | ```js 75 | const core = require('@actions/core'); 76 | 77 | const myInput = core.getInput('input'); 78 | try { 79 | core.debug('Inside try block'); 80 | 81 | if (!myInput) { 82 | core.warning('myInput was not set'); 83 | } 84 | 85 | if (core.isDebug()) { 86 | // curl -v https://github.com 87 | } else { 88 | // curl https://github.com 89 | } 90 | 91 | // Do stuff 92 | } 93 | catch (err) { 94 | core.error(`Error ${err}, action may still succeed though`); 95 | } 96 | ``` 97 | 98 | This library can also wrap chunks of output in foldable groups. 99 | 100 | ```js 101 | const core = require('@actions/core') 102 | 103 | // Manually wrap output 104 | core.startGroup('Do some function') 105 | doSomeFunction() 106 | core.endGroup() 107 | 108 | // Wrap an asynchronous function call 109 | const result = await core.group('Do something async', async () => { 110 | const response = await doSomeHTTPRequest() 111 | return response 112 | }) 113 | ``` 114 | 115 | #### Action state 116 | 117 | You can use this library to save state and get state for sharing information between a given wrapper action: 118 | 119 | **action.yml** 120 | ```yaml 121 | name: 'Wrapper action sample' 122 | inputs: 123 | name: 124 | default: 'GitHub' 125 | runs: 126 | using: 'node12' 127 | main: 'main.js' 128 | post: 'cleanup.js' 129 | ``` 130 | 131 | In action's `main.js`: 132 | 133 | ```js 134 | const core = require('@actions/core'); 135 | 136 | core.saveState("pidToKill", 12345); 137 | ``` 138 | 139 | In action's `cleanup.js`: 140 | ```js 141 | const core = require('@actions/core'); 142 | 143 | var pid = core.getState("pidToKill"); 144 | 145 | process.kill(pid); 146 | ``` -------------------------------------------------------------------------------- /.action/core/lib/command.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importStar = (this && this.__importStar) || function (mod) { 3 | if (mod && mod.__esModule) return mod; 4 | var result = {}; 5 | if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; 6 | result["default"] = mod; 7 | return result; 8 | }; 9 | Object.defineProperty(exports, "__esModule", { value: true }); 10 | const os = __importStar(require("os")); 11 | /** 12 | * Commands 13 | * 14 | * Command Format: 15 | * ::name key=value,key=value::message 16 | * 17 | * Examples: 18 | * ::warning::This is the message 19 | * ::set-env name=MY_VAR::some value 20 | */ 21 | function issueCommand(command, properties, message) { 22 | const cmd = new Command(command, properties, message); 23 | process.stdout.write(cmd.toString() + os.EOL); 24 | } 25 | exports.issueCommand = issueCommand; 26 | function issue(name, message = '') { 27 | issueCommand(name, {}, message); 28 | } 29 | exports.issue = issue; 30 | const CMD_STRING = '::'; 31 | class Command { 32 | constructor(command, properties, message) { 33 | if (!command) { 34 | command = 'missing.command'; 35 | } 36 | this.command = command; 37 | this.properties = properties; 38 | this.message = message; 39 | } 40 | toString() { 41 | let cmdStr = CMD_STRING + this.command; 42 | if (this.properties && Object.keys(this.properties).length > 0) { 43 | cmdStr += ' '; 44 | let first = true; 45 | for (const key in this.properties) { 46 | if (this.properties.hasOwnProperty(key)) { 47 | const val = this.properties[key]; 48 | if (val) { 49 | if (first) { 50 | first = false; 51 | } 52 | else { 53 | cmdStr += ','; 54 | } 55 | cmdStr += `${key}=${escapeProperty(val)}`; 56 | } 57 | } 58 | } 59 | } 60 | cmdStr += `${CMD_STRING}${escapeData(this.message)}`; 61 | return cmdStr; 62 | } 63 | } 64 | /** 65 | * Sanitizes an input into a string so it can be passed into issueCommand safely 66 | * @param input input to sanitize into a string 67 | */ 68 | function toCommandValue(input) { 69 | if (input === null || input === undefined) { 70 | return ''; 71 | } 72 | else if (typeof input === 'string' || input instanceof String) { 73 | return input; 74 | } 75 | return JSON.stringify(input); 76 | } 77 | exports.toCommandValue = toCommandValue; 78 | function escapeData(s) { 79 | return toCommandValue(s) 80 | .replace(/%/g, '%25') 81 | .replace(/\r/g, '%0D') 82 | .replace(/\n/g, '%0A'); 83 | } 84 | function escapeProperty(s) { 85 | return toCommandValue(s) 86 | .replace(/%/g, '%25') 87 | .replace(/\r/g, '%0D') 88 | .replace(/\n/g, '%0A') 89 | .replace(/:/g, '%3A') 90 | .replace(/,/g, '%2C'); 91 | } 92 | //# sourceMappingURL=command.js.map -------------------------------------------------------------------------------- /.action/core/lib/core.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __importStar = (this && this.__importStar) || function (mod) { 12 | if (mod && mod.__esModule) return mod; 13 | var result = {}; 14 | if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; 15 | result["default"] = mod; 16 | return result; 17 | }; 18 | Object.defineProperty(exports, "__esModule", { value: true }); 19 | const command_1 = require("./command"); 20 | const os = __importStar(require("os")); 21 | const path = __importStar(require("path")); 22 | /** 23 | * The code to exit an action 24 | */ 25 | var ExitCode; 26 | (function (ExitCode) { 27 | /** 28 | * A code indicating that the action was successful 29 | */ 30 | ExitCode[ExitCode["Success"] = 0] = "Success"; 31 | /** 32 | * A code indicating that the action was a failure 33 | */ 34 | ExitCode[ExitCode["Failure"] = 1] = "Failure"; 35 | })(ExitCode = exports.ExitCode || (exports.ExitCode = {})); 36 | //----------------------------------------------------------------------- 37 | // Variables 38 | //----------------------------------------------------------------------- 39 | /** 40 | * Sets env variable for this action and future actions in the job 41 | * @param name the name of the variable to set 42 | * @param val the value of the variable. Non-string values will be converted to a string via JSON.stringify 43 | */ 44 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 45 | function exportVariable(name, val) { 46 | const convertedVal = command_1.toCommandValue(val); 47 | process.env[name] = convertedVal; 48 | command_1.issueCommand('set-env', { name }, convertedVal); 49 | } 50 | exports.exportVariable = exportVariable; 51 | /** 52 | * Registers a secret which will get masked from logs 53 | * @param secret value of the secret 54 | */ 55 | function setSecret(secret) { 56 | command_1.issueCommand('add-mask', {}, secret); 57 | } 58 | exports.setSecret = setSecret; 59 | /** 60 | * Prepends inputPath to the PATH (for this action and future actions) 61 | * @param inputPath 62 | */ 63 | function addPath(inputPath) { 64 | command_1.issueCommand('add-path', {}, inputPath); 65 | process.env['PATH'] = `${inputPath}${path.delimiter}${process.env['PATH']}`; 66 | } 67 | exports.addPath = addPath; 68 | /** 69 | * Gets the value of an input. The value is also trimmed. 70 | * 71 | * @param name name of the input to get 72 | * @param options optional. See InputOptions. 73 | * @returns string 74 | */ 75 | function getInput(name, options) { 76 | const val = process.env[`INPUT_${name.replace(/ /g, '_').toUpperCase()}`] || ''; 77 | if (options && options.required && !val) { 78 | throw new Error(`Input required and not supplied: ${name}`); 79 | } 80 | return val.trim(); 81 | } 82 | exports.getInput = getInput; 83 | /** 84 | * Sets the value of an output. 85 | * 86 | * @param name name of the output to set 87 | * @param value value to store. Non-string values will be converted to a string via JSON.stringify 88 | */ 89 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 90 | function setOutput(name, value) { 91 | command_1.issueCommand('set-output', { name }, value); 92 | } 93 | exports.setOutput = setOutput; 94 | /** 95 | * Enables or disables the echoing of commands into stdout for the rest of the step. 96 | * Echoing is disabled by default if ACTIONS_STEP_DEBUG is not set. 97 | * 98 | */ 99 | function setCommandEcho(enabled) { 100 | command_1.issue('echo', enabled ? 'on' : 'off'); 101 | } 102 | exports.setCommandEcho = setCommandEcho; 103 | //----------------------------------------------------------------------- 104 | // Results 105 | //----------------------------------------------------------------------- 106 | /** 107 | * Sets the action status to failed. 108 | * When the action exits it will be with an exit code of 1 109 | * @param message add error issue message 110 | */ 111 | function setFailed(message) { 112 | process.exitCode = ExitCode.Failure; 113 | error(message); 114 | } 115 | exports.setFailed = setFailed; 116 | //----------------------------------------------------------------------- 117 | // Logging Commands 118 | //----------------------------------------------------------------------- 119 | /** 120 | * Gets whether Actions Step Debug is on or not 121 | */ 122 | function isDebug() { 123 | return process.env['RUNNER_DEBUG'] === '1'; 124 | } 125 | exports.isDebug = isDebug; 126 | /** 127 | * Writes debug message to user log 128 | * @param message debug message 129 | */ 130 | function debug(message) { 131 | command_1.issueCommand('debug', {}, message); 132 | } 133 | exports.debug = debug; 134 | /** 135 | * Adds an error issue 136 | * @param message error issue message. Errors will be converted to string via toString() 137 | */ 138 | function error(message) { 139 | command_1.issue('error', message instanceof Error ? message.toString() : message); 140 | } 141 | exports.error = error; 142 | /** 143 | * Adds an warning issue 144 | * @param message warning issue message. Errors will be converted to string via toString() 145 | */ 146 | function warning(message) { 147 | command_1.issue('warning', message instanceof Error ? message.toString() : message); 148 | } 149 | exports.warning = warning; 150 | /** 151 | * Writes info to log with console.log. 152 | * @param message info message 153 | */ 154 | function info(message) { 155 | process.stdout.write(message + os.EOL); 156 | } 157 | exports.info = info; 158 | /** 159 | * Begin an output group. 160 | * 161 | * Output until the next `groupEnd` will be foldable in this group 162 | * 163 | * @param name The name of the output group 164 | */ 165 | function startGroup(name) { 166 | command_1.issue('group', name); 167 | } 168 | exports.startGroup = startGroup; 169 | /** 170 | * End an output group. 171 | */ 172 | function endGroup() { 173 | command_1.issue('endgroup'); 174 | } 175 | exports.endGroup = endGroup; 176 | /** 177 | * Wrap an asynchronous function call in a group. 178 | * 179 | * Returns the same type as the function itself. 180 | * 181 | * @param name The name of the group 182 | * @param fn The function to wrap in the group 183 | */ 184 | function group(name, fn) { 185 | return __awaiter(this, void 0, void 0, function* () { 186 | startGroup(name); 187 | let result; 188 | try { 189 | result = yield fn(); 190 | } 191 | finally { 192 | endGroup(); 193 | } 194 | return result; 195 | }); 196 | } 197 | exports.group = group; 198 | //----------------------------------------------------------------------- 199 | // Wrapper action state 200 | //----------------------------------------------------------------------- 201 | /** 202 | * Saves state for current action, the state can only be retrieved by this action's post job execution. 203 | * 204 | * @param name name of the state to store 205 | * @param value value to store. Non-string values will be converted to a string via JSON.stringify 206 | */ 207 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 208 | function saveState(name, value) { 209 | command_1.issueCommand('save-state', { name }, value); 210 | } 211 | exports.saveState = saveState; 212 | /** 213 | * Gets the value of an state set by this action's main execution. 214 | * 215 | * @param name name of the state to get 216 | * @returns string 217 | */ 218 | function getState(name) { 219 | return process.env[`STATE_${name}`] || ''; 220 | } 221 | exports.getState = getState; 222 | //# sourceMappingURL=core.js.map -------------------------------------------------------------------------------- /.action/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "_from": "@actions/core@^1.2.4", 3 | "_id": "@actions/core@1.2.4", 4 | "_inBundle": false, 5 | "_integrity": "sha512-YJCEq8BE3CdN8+7HPZ/4DxJjk/OkZV2FFIf+DlZTC/4iBlzYCD5yjRR6eiOS5llO11zbRltIRuKAjMKaWTE6cg==", 6 | "_location": "/@actions/core", 7 | "_phantomChildren": {}, 8 | "_requested": { 9 | "type": "range", 10 | "registry": true, 11 | "raw": "@actions/core@^1.2.4", 12 | "name": "@actions/core", 13 | "escapedName": "@actions%2fcore", 14 | "scope": "@actions", 15 | "rawSpec": "^1.2.4", 16 | "saveSpec": null, 17 | "fetchSpec": "^1.2.4" 18 | }, 19 | "_requiredBy": [ 20 | "#USER", 21 | "/" 22 | ], 23 | "_resolved": "https://registry.npmjs.org/@actions/core/-/core-1.2.4.tgz", 24 | "_shasum": "96179dbf9f8d951dd74b40a0dbd5c22555d186ab", 25 | "_spec": "@actions/core@^1.2.4", 26 | "_where": "/Users/bok/Projects/Open Source/swift-create-xcframework", 27 | "bugs": { 28 | "url": "https://github.com/actions/toolkit/issues" 29 | }, 30 | "bundleDependencies": false, 31 | "deprecated": false, 32 | "description": "Actions core lib", 33 | "devDependencies": { 34 | "@types/node": "^12.0.2" 35 | }, 36 | "directories": { 37 | "lib": "lib", 38 | "test": "__tests__" 39 | }, 40 | "files": [ 41 | "lib" 42 | ], 43 | "homepage": "https://github.com/actions/toolkit/tree/master/packages/core", 44 | "keywords": [ 45 | "github", 46 | "actions", 47 | "core" 48 | ], 49 | "license": "MIT", 50 | "main": "lib/core.js", 51 | "name": "@actions/core", 52 | "publishConfig": { 53 | "access": "public" 54 | }, 55 | "repository": { 56 | "type": "git", 57 | "url": "git+https://github.com/actions/toolkit.git", 58 | "directory": "packages/core" 59 | }, 60 | "scripts": { 61 | "audit-moderate": "npm install && npm audit --audit-level=moderate", 62 | "test": "echo \"Error: run tests from root\" && exit 1", 63 | "tsc": "tsc" 64 | }, 65 | "types": "lib/core.d.ts", 66 | "version": "1.2.4" 67 | } 68 | -------------------------------------------------------------------------------- /.action/exec/README.md: -------------------------------------------------------------------------------- 1 | # `@actions/exec` 2 | 3 | ## Usage 4 | 5 | #### Basic 6 | 7 | You can use this package to execute tools in a cross platform way: 8 | 9 | ```js 10 | const exec = require('@actions/exec'); 11 | 12 | await exec.exec('node index.js'); 13 | ``` 14 | 15 | #### Args 16 | 17 | You can also pass in arg arrays: 18 | 19 | ```js 20 | const exec = require('@actions/exec'); 21 | 22 | await exec.exec('node', ['index.js', 'foo=bar']); 23 | ``` 24 | 25 | #### Output/options 26 | 27 | Capture output or specify [other options](https://github.com/actions/toolkit/blob/d9347d4ab99fd507c0b9104b2cf79fb44fcc827d/packages/exec/src/interfaces.ts#L5): 28 | 29 | ```js 30 | const exec = require('@actions/exec'); 31 | 32 | let myOutput = ''; 33 | let myError = ''; 34 | 35 | const options = {}; 36 | options.listeners = { 37 | stdout: (data: Buffer) => { 38 | myOutput += data.toString(); 39 | }, 40 | stderr: (data: Buffer) => { 41 | myError += data.toString(); 42 | } 43 | }; 44 | options.cwd = './lib'; 45 | 46 | await exec.exec('node', ['index.js', 'foo=bar'], options); 47 | ``` 48 | 49 | #### Exec tools not in the PATH 50 | 51 | You can specify the full path for tools not in the PATH: 52 | 53 | ```js 54 | const exec = require('@actions/exec'); 55 | 56 | await exec.exec('"/path/to/my-tool"', ['arg1']); 57 | ``` 58 | -------------------------------------------------------------------------------- /.action/exec/lib/exec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __importStar = (this && this.__importStar) || function (mod) { 12 | if (mod && mod.__esModule) return mod; 13 | var result = {}; 14 | if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; 15 | result["default"] = mod; 16 | return result; 17 | }; 18 | Object.defineProperty(exports, "__esModule", { value: true }); 19 | const tr = __importStar(require("./toolrunner")); 20 | /** 21 | * Exec a command. 22 | * Output will be streamed to the live console. 23 | * Returns promise with return code 24 | * 25 | * @param commandLine command to execute (can include additional args). Must be correctly escaped. 26 | * @param args optional arguments for tool. Escaping is handled by the lib. 27 | * @param options optional exec options. See ExecOptions 28 | * @returns Promise exit code 29 | */ 30 | function exec(commandLine, args, options) { 31 | return __awaiter(this, void 0, void 0, function* () { 32 | const commandArgs = tr.argStringToArray(commandLine); 33 | if (commandArgs.length === 0) { 34 | throw new Error(`Parameter 'commandLine' cannot be null or empty.`); 35 | } 36 | // Path to tool to execute should be first arg 37 | const toolPath = commandArgs[0]; 38 | args = commandArgs.slice(1).concat(args || []); 39 | const runner = new tr.ToolRunner(toolPath, args, options); 40 | return runner.exec(); 41 | }); 42 | } 43 | exports.exec = exec; 44 | //# sourceMappingURL=exec.js.map -------------------------------------------------------------------------------- /.action/exec/lib/interfaces.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | //# sourceMappingURL=interfaces.js.map -------------------------------------------------------------------------------- /.action/exec/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "_from": "@actions/exec", 3 | "_id": "@actions/exec@1.0.4", 4 | "_inBundle": false, 5 | "_integrity": "sha512-4DPChWow9yc9W3WqEbUj8Nr86xkpyE29ZzWjXucHItclLbEW6jr80Zx4nqv18QL6KK65+cifiQZXvnqgTV6oHw==", 6 | "_location": "/@actions/exec", 7 | "_phantomChildren": {}, 8 | "_requested": { 9 | "type": "tag", 10 | "registry": true, 11 | "raw": "@actions/exec", 12 | "name": "@actions/exec", 13 | "escapedName": "@actions%2fexec", 14 | "scope": "@actions", 15 | "rawSpec": "", 16 | "saveSpec": null, 17 | "fetchSpec": "latest" 18 | }, 19 | "_requiredBy": [ 20 | "#USER", 21 | "/" 22 | ], 23 | "_resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.0.4.tgz", 24 | "_shasum": "99d75310e62e59fc37d2ee6dcff6d4bffadd3a5d", 25 | "_spec": "@actions/exec", 26 | "_where": "/Users/bok/Projects/Open Source/swift-create-xcframework", 27 | "bugs": { 28 | "url": "https://github.com/actions/toolkit/issues" 29 | }, 30 | "bundleDependencies": false, 31 | "dependencies": { 32 | "@actions/io": "^1.0.1" 33 | }, 34 | "deprecated": false, 35 | "description": "Actions exec lib", 36 | "directories": { 37 | "lib": "lib", 38 | "test": "__tests__" 39 | }, 40 | "files": [ 41 | "lib" 42 | ], 43 | "homepage": "https://github.com/actions/toolkit/tree/master/packages/exec", 44 | "keywords": [ 45 | "github", 46 | "actions", 47 | "exec" 48 | ], 49 | "license": "MIT", 50 | "main": "lib/exec.js", 51 | "name": "@actions/exec", 52 | "publishConfig": { 53 | "access": "public" 54 | }, 55 | "repository": { 56 | "type": "git", 57 | "url": "git+https://github.com/actions/toolkit.git", 58 | "directory": "packages/exec" 59 | }, 60 | "scripts": { 61 | "audit-moderate": "npm install && npm audit --audit-level=moderate", 62 | "test": "echo \"Error: run tests from root\" && exit 1", 63 | "tsc": "tsc" 64 | }, 65 | "types": "lib/exec.d.ts", 66 | "version": "1.0.4" 67 | } 68 | -------------------------------------------------------------------------------- /.action/http-client/LICENSE: -------------------------------------------------------------------------------- 1 | Actions Http Client for Node.js 2 | 3 | Copyright (c) GitHub, Inc. 4 | 5 | All rights reserved. 6 | 7 | MIT License 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 10 | associated documentation files (the "Software"), to deal in the Software without restriction, 11 | including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 13 | subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 18 | LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 19 | NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 21 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /.action/http-client/README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | 4 |

5 | 6 | # Actions Http-Client 7 | 8 | [![Http Status](https://github.com/actions/http-client/workflows/http-tests/badge.svg)](https://github.com/actions/http-client/actions) 9 | 10 | A lightweight HTTP client optimized for use with actions, TypeScript with generics and async await. 11 | 12 | ## Features 13 | 14 | - HTTP client with TypeScript generics and async/await/Promises 15 | - Typings included so no need to acquire separately (great for intellisense and no versioning drift) 16 | - [Proxy support](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/about-self-hosted-runners#using-a-proxy-server-with-self-hosted-runners) just works with actions and the runner 17 | - Targets ES2019 (runner runs actions with node 12+). Only supported on node 12+. 18 | - Basic, Bearer and PAT Support out of the box. Extensible handlers for others. 19 | - Redirects supported 20 | 21 | Features and releases [here](./RELEASES.md) 22 | 23 | ## Install 24 | 25 | ``` 26 | npm install @actions/http-client --save 27 | ``` 28 | 29 | ## Samples 30 | 31 | See the [HTTP](./__tests__) tests for detailed examples. 32 | 33 | ## Errors 34 | 35 | ### HTTP 36 | 37 | The HTTP client does not throw unless truly exceptional. 38 | 39 | * A request that successfully executes resulting in a 404, 500 etc... will return a response object with a status code and a body. 40 | * Redirects (3xx) will be followed by default. 41 | 42 | See [HTTP tests](./__tests__) for detailed examples. 43 | 44 | ## Debugging 45 | 46 | To enable detailed console logging of all HTTP requests and responses, set the NODE_DEBUG environment varible: 47 | 48 | ``` 49 | export NODE_DEBUG=http 50 | ``` 51 | 52 | ## Node support 53 | 54 | The http-client is built using the latest LTS version of Node 12. It may work on previous node LTS versions but it's tested and officially supported on Node12+. 55 | 56 | ## Support and Versioning 57 | 58 | We follow semver and will hold compatibility between major versions and increment the minor version with new features and capabilities (while holding compat). 59 | 60 | ## Contributing 61 | 62 | We welcome PRs. Please create an issue and if applicable, a design before proceeding with code. 63 | 64 | once: 65 | 66 | ```bash 67 | $ npm install 68 | ``` 69 | 70 | To build: 71 | 72 | ```bash 73 | $ npm run build 74 | ``` 75 | 76 | To run all tests: 77 | ```bash 78 | $ npm test 79 | ``` 80 | -------------------------------------------------------------------------------- /.action/http-client/RELEASES.md: -------------------------------------------------------------------------------- 1 | ## Releases 2 | 3 | ## 1.0.7 4 | Update NPM dependencies and add 429 to the list of HttpCodes 5 | 6 | ## 1.0.6 7 | Automatically sends Content-Type and Accept application/json headers for \Json() helper methods if not set in the client or parameters. 8 | 9 | ## 1.0.5 10 | Adds \Json() helper methods for json over http scenarios. 11 | 12 | ## 1.0.4 13 | Started to add \Json() helper methods. Do not use this release for that. Use >= 1.0.5 since there was an issue with types. 14 | 15 | ## 1.0.1 to 1.0.3 16 | Adds proxy support. -------------------------------------------------------------------------------- /.action/http-client/actions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unsignedapps/swift-create-xcframework/88472e8e4f0227a47ca461fccd38c6d080ad92f2/.action/http-client/actions.png -------------------------------------------------------------------------------- /.action/http-client/auth.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | class BasicCredentialHandler { 4 | constructor(username, password) { 5 | this.username = username; 6 | this.password = password; 7 | } 8 | prepareRequest(options) { 9 | options.headers['Authorization'] = 10 | 'Basic ' + 11 | Buffer.from(this.username + ':' + this.password).toString('base64'); 12 | } 13 | // This handler cannot handle 401 14 | canHandleAuthentication(response) { 15 | return false; 16 | } 17 | handleAuthentication(httpClient, requestInfo, objs) { 18 | return null; 19 | } 20 | } 21 | exports.BasicCredentialHandler = BasicCredentialHandler; 22 | class BearerCredentialHandler { 23 | constructor(token) { 24 | this.token = token; 25 | } 26 | // currently implements pre-authorization 27 | // TODO: support preAuth = false where it hooks on 401 28 | prepareRequest(options) { 29 | options.headers['Authorization'] = 'Bearer ' + this.token; 30 | } 31 | // This handler cannot handle 401 32 | canHandleAuthentication(response) { 33 | return false; 34 | } 35 | handleAuthentication(httpClient, requestInfo, objs) { 36 | return null; 37 | } 38 | } 39 | exports.BearerCredentialHandler = BearerCredentialHandler; 40 | class PersonalAccessTokenCredentialHandler { 41 | constructor(token) { 42 | this.token = token; 43 | } 44 | // currently implements pre-authorization 45 | // TODO: support preAuth = false where it hooks on 401 46 | prepareRequest(options) { 47 | options.headers['Authorization'] = 48 | 'Basic ' + Buffer.from('PAT:' + this.token).toString('base64'); 49 | } 50 | // This handler cannot handle 401 51 | canHandleAuthentication(response) { 52 | return false; 53 | } 54 | handleAuthentication(httpClient, requestInfo, objs) { 55 | return null; 56 | } 57 | } 58 | exports.PersonalAccessTokenCredentialHandler = PersonalAccessTokenCredentialHandler; 59 | -------------------------------------------------------------------------------- /.action/http-client/interfaces.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | -------------------------------------------------------------------------------- /.action/http-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "_from": "@actions/http-client@^1.0.7", 3 | "_id": "@actions/http-client@1.0.8", 4 | "_inBundle": false, 5 | "_integrity": "sha512-G4JjJ6f9Hb3Zvejj+ewLLKLf99ZC+9v+yCxoYf9vSyH+WkzPLB2LuUtRMGNkooMqdugGBFStIKXOuvH1W+EctA==", 6 | "_location": "/@actions/http-client", 7 | "_phantomChildren": {}, 8 | "_requested": { 9 | "type": "range", 10 | "registry": true, 11 | "raw": "@actions/http-client@^1.0.7", 12 | "name": "@actions/http-client", 13 | "escapedName": "@actions%2fhttp-client", 14 | "scope": "@actions", 15 | "rawSpec": "^1.0.7", 16 | "saveSpec": null, 17 | "fetchSpec": "^1.0.7" 18 | }, 19 | "_requiredBy": [ 20 | "/@actions/artifact" 21 | ], 22 | "_resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.8.tgz", 23 | "_shasum": "8bd76e8eca89dc8bcf619aa128eba85f7a39af45", 24 | "_spec": "@actions/http-client@^1.0.7", 25 | "_where": "/Users/bok/Projects/Open Source/swift-create-xcframework/node_modules/@actions/artifact", 26 | "author": { 27 | "name": "GitHub, Inc." 28 | }, 29 | "bugs": { 30 | "url": "https://github.com/actions/http-client/issues" 31 | }, 32 | "bundleDependencies": false, 33 | "dependencies": { 34 | "tunnel": "0.0.6" 35 | }, 36 | "deprecated": false, 37 | "description": "Actions Http Client", 38 | "devDependencies": { 39 | "@types/jest": "^25.1.4", 40 | "@types/node": "^12.12.31", 41 | "jest": "^25.1.0", 42 | "prettier": "^2.0.4", 43 | "proxy": "^1.0.1", 44 | "ts-jest": "^25.2.1", 45 | "typescript": "^3.8.3" 46 | }, 47 | "homepage": "https://github.com/actions/http-client#readme", 48 | "keywords": [ 49 | "Actions", 50 | "Http" 51 | ], 52 | "license": "MIT", 53 | "main": "index.js", 54 | "name": "@actions/http-client", 55 | "repository": { 56 | "type": "git", 57 | "url": "git+https://github.com/actions/http-client.git" 58 | }, 59 | "scripts": { 60 | "audit-check": "npm audit --audit-level=moderate", 61 | "build": "rm -Rf ./_out && tsc && cp package*.json ./_out && cp *.md ./_out && cp LICENSE ./_out && cp actions.png ./_out", 62 | "format": "prettier --write *.ts && prettier --write **/*.ts", 63 | "format-check": "prettier --check *.ts && prettier --check **/*.ts", 64 | "test": "jest" 65 | }, 66 | "version": "1.0.8" 67 | } 68 | -------------------------------------------------------------------------------- /.action/http-client/proxy.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const url = require("url"); 4 | function getProxyUrl(reqUrl) { 5 | let usingSsl = reqUrl.protocol === 'https:'; 6 | let proxyUrl; 7 | if (checkBypass(reqUrl)) { 8 | return proxyUrl; 9 | } 10 | let proxyVar; 11 | if (usingSsl) { 12 | proxyVar = process.env['https_proxy'] || process.env['HTTPS_PROXY']; 13 | } 14 | else { 15 | proxyVar = process.env['http_proxy'] || process.env['HTTP_PROXY']; 16 | } 17 | if (proxyVar) { 18 | proxyUrl = url.parse(proxyVar); 19 | } 20 | return proxyUrl; 21 | } 22 | exports.getProxyUrl = getProxyUrl; 23 | function checkBypass(reqUrl) { 24 | if (!reqUrl.hostname) { 25 | return false; 26 | } 27 | let noProxy = process.env['no_proxy'] || process.env['NO_PROXY'] || ''; 28 | if (!noProxy) { 29 | return false; 30 | } 31 | // Determine the request port 32 | let reqPort; 33 | if (reqUrl.port) { 34 | reqPort = Number(reqUrl.port); 35 | } 36 | else if (reqUrl.protocol === 'http:') { 37 | reqPort = 80; 38 | } 39 | else if (reqUrl.protocol === 'https:') { 40 | reqPort = 443; 41 | } 42 | // Format the request hostname and hostname with port 43 | let upperReqHosts = [reqUrl.hostname.toUpperCase()]; 44 | if (typeof reqPort === 'number') { 45 | upperReqHosts.push(`${upperReqHosts[0]}:${reqPort}`); 46 | } 47 | // Compare request host against noproxy 48 | for (let upperNoProxyItem of noProxy 49 | .split(',') 50 | .map(x => x.trim().toUpperCase()) 51 | .filter(x => x)) { 52 | if (upperReqHosts.some(x => x === upperNoProxyItem)) { 53 | return true; 54 | } 55 | } 56 | return false; 57 | } 58 | exports.checkBypass = checkBypass; 59 | -------------------------------------------------------------------------------- /.action/io/README.md: -------------------------------------------------------------------------------- 1 | # `@actions/io` 2 | 3 | > Core functions for cli filesystem scenarios 4 | 5 | ## Usage 6 | 7 | #### mkdir -p 8 | 9 | Recursively make a directory. Follows rules specified in [man mkdir](https://linux.die.net/man/1/mkdir) with the `-p` option specified: 10 | 11 | ```js 12 | const io = require('@actions/io'); 13 | 14 | await io.mkdirP('path/to/make'); 15 | ``` 16 | 17 | #### cp/mv 18 | 19 | Copy or move files or folders. Follows rules specified in [man cp](https://linux.die.net/man/1/cp) and [man mv](https://linux.die.net/man/1/mv): 20 | 21 | ```js 22 | const io = require('@actions/io'); 23 | 24 | // Recursive must be true for directories 25 | const options = { recursive: true, force: false } 26 | 27 | await io.cp('path/to/directory', 'path/to/dest', options); 28 | await io.mv('path/to/file', 'path/to/dest'); 29 | ``` 30 | 31 | #### rm -rf 32 | 33 | Remove a file or folder recursively. Follows rules specified in [man rm](https://linux.die.net/man/1/rm) with the `-r` and `-f` rules specified. 34 | 35 | ```js 36 | const io = require('@actions/io'); 37 | 38 | await io.rmRF('path/to/directory'); 39 | await io.rmRF('path/to/file'); 40 | ``` 41 | 42 | #### which 43 | 44 | Get the path to a tool and resolves via paths. Follows the rules specified in [man which](https://linux.die.net/man/1/which). 45 | 46 | ```js 47 | const exec = require('@actions/exec'); 48 | const io = require('@actions/io'); 49 | 50 | const pythonPath: string = await io.which('python', true) 51 | 52 | await exec.exec(`"${pythonPath}"`, ['main.py']); 53 | ``` 54 | -------------------------------------------------------------------------------- /.action/io/lib/io-util.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var _a; 12 | Object.defineProperty(exports, "__esModule", { value: true }); 13 | const assert_1 = require("assert"); 14 | const fs = require("fs"); 15 | const path = require("path"); 16 | _a = fs.promises, exports.chmod = _a.chmod, exports.copyFile = _a.copyFile, exports.lstat = _a.lstat, exports.mkdir = _a.mkdir, exports.readdir = _a.readdir, exports.readlink = _a.readlink, exports.rename = _a.rename, exports.rmdir = _a.rmdir, exports.stat = _a.stat, exports.symlink = _a.symlink, exports.unlink = _a.unlink; 17 | exports.IS_WINDOWS = process.platform === 'win32'; 18 | function exists(fsPath) { 19 | return __awaiter(this, void 0, void 0, function* () { 20 | try { 21 | yield exports.stat(fsPath); 22 | } 23 | catch (err) { 24 | if (err.code === 'ENOENT') { 25 | return false; 26 | } 27 | throw err; 28 | } 29 | return true; 30 | }); 31 | } 32 | exports.exists = exists; 33 | function isDirectory(fsPath, useStat = false) { 34 | return __awaiter(this, void 0, void 0, function* () { 35 | const stats = useStat ? yield exports.stat(fsPath) : yield exports.lstat(fsPath); 36 | return stats.isDirectory(); 37 | }); 38 | } 39 | exports.isDirectory = isDirectory; 40 | /** 41 | * On OSX/Linux, true if path starts with '/'. On Windows, true for paths like: 42 | * \, \hello, \\hello\share, C:, and C:\hello (and corresponding alternate separator cases). 43 | */ 44 | function isRooted(p) { 45 | p = normalizeSeparators(p); 46 | if (!p) { 47 | throw new Error('isRooted() parameter "p" cannot be empty'); 48 | } 49 | if (exports.IS_WINDOWS) { 50 | return (p.startsWith('\\') || /^[A-Z]:/i.test(p) // e.g. \ or \hello or \\hello 51 | ); // e.g. C: or C:\hello 52 | } 53 | return p.startsWith('/'); 54 | } 55 | exports.isRooted = isRooted; 56 | /** 57 | * Recursively create a directory at `fsPath`. 58 | * 59 | * This implementation is optimistic, meaning it attempts to create the full 60 | * path first, and backs up the path stack from there. 61 | * 62 | * @param fsPath The path to create 63 | * @param maxDepth The maximum recursion depth 64 | * @param depth The current recursion depth 65 | */ 66 | function mkdirP(fsPath, maxDepth = 1000, depth = 1) { 67 | return __awaiter(this, void 0, void 0, function* () { 68 | assert_1.ok(fsPath, 'a path argument must be provided'); 69 | fsPath = path.resolve(fsPath); 70 | if (depth >= maxDepth) 71 | return exports.mkdir(fsPath); 72 | try { 73 | yield exports.mkdir(fsPath); 74 | return; 75 | } 76 | catch (err) { 77 | switch (err.code) { 78 | case 'ENOENT': { 79 | yield mkdirP(path.dirname(fsPath), maxDepth, depth + 1); 80 | yield exports.mkdir(fsPath); 81 | return; 82 | } 83 | default: { 84 | let stats; 85 | try { 86 | stats = yield exports.stat(fsPath); 87 | } 88 | catch (err2) { 89 | throw err; 90 | } 91 | if (!stats.isDirectory()) 92 | throw err; 93 | } 94 | } 95 | } 96 | }); 97 | } 98 | exports.mkdirP = mkdirP; 99 | /** 100 | * Best effort attempt to determine whether a file exists and is executable. 101 | * @param filePath file path to check 102 | * @param extensions additional file extensions to try 103 | * @return if file exists and is executable, returns the file path. otherwise empty string. 104 | */ 105 | function tryGetExecutablePath(filePath, extensions) { 106 | return __awaiter(this, void 0, void 0, function* () { 107 | let stats = undefined; 108 | try { 109 | // test file exists 110 | stats = yield exports.stat(filePath); 111 | } 112 | catch (err) { 113 | if (err.code !== 'ENOENT') { 114 | // eslint-disable-next-line no-console 115 | console.log(`Unexpected error attempting to determine if executable file exists '${filePath}': ${err}`); 116 | } 117 | } 118 | if (stats && stats.isFile()) { 119 | if (exports.IS_WINDOWS) { 120 | // on Windows, test for valid extension 121 | const upperExt = path.extname(filePath).toUpperCase(); 122 | if (extensions.some(validExt => validExt.toUpperCase() === upperExt)) { 123 | return filePath; 124 | } 125 | } 126 | else { 127 | if (isUnixExecutable(stats)) { 128 | return filePath; 129 | } 130 | } 131 | } 132 | // try each extension 133 | const originalFilePath = filePath; 134 | for (const extension of extensions) { 135 | filePath = originalFilePath + extension; 136 | stats = undefined; 137 | try { 138 | stats = yield exports.stat(filePath); 139 | } 140 | catch (err) { 141 | if (err.code !== 'ENOENT') { 142 | // eslint-disable-next-line no-console 143 | console.log(`Unexpected error attempting to determine if executable file exists '${filePath}': ${err}`); 144 | } 145 | } 146 | if (stats && stats.isFile()) { 147 | if (exports.IS_WINDOWS) { 148 | // preserve the case of the actual file (since an extension was appended) 149 | try { 150 | const directory = path.dirname(filePath); 151 | const upperName = path.basename(filePath).toUpperCase(); 152 | for (const actualName of yield exports.readdir(directory)) { 153 | if (upperName === actualName.toUpperCase()) { 154 | filePath = path.join(directory, actualName); 155 | break; 156 | } 157 | } 158 | } 159 | catch (err) { 160 | // eslint-disable-next-line no-console 161 | console.log(`Unexpected error attempting to determine the actual case of the file '${filePath}': ${err}`); 162 | } 163 | return filePath; 164 | } 165 | else { 166 | if (isUnixExecutable(stats)) { 167 | return filePath; 168 | } 169 | } 170 | } 171 | } 172 | return ''; 173 | }); 174 | } 175 | exports.tryGetExecutablePath = tryGetExecutablePath; 176 | function normalizeSeparators(p) { 177 | p = p || ''; 178 | if (exports.IS_WINDOWS) { 179 | // convert slashes on Windows 180 | p = p.replace(/\//g, '\\'); 181 | // remove redundant slashes 182 | return p.replace(/\\\\+/g, '\\'); 183 | } 184 | // remove redundant slashes 185 | return p.replace(/\/\/+/g, '/'); 186 | } 187 | // on Mac/Linux, test the execute bit 188 | // R W X R W X R W X 189 | // 256 128 64 32 16 8 4 2 1 190 | function isUnixExecutable(stats) { 191 | return ((stats.mode & 1) > 0 || 192 | ((stats.mode & 8) > 0 && stats.gid === process.getgid()) || 193 | ((stats.mode & 64) > 0 && stats.uid === process.getuid())); 194 | } 195 | //# sourceMappingURL=io-util.js.map -------------------------------------------------------------------------------- /.action/io/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "_from": "@actions/io@^1.0.1", 3 | "_id": "@actions/io@1.0.2", 4 | "_inBundle": false, 5 | "_integrity": "sha512-J8KuFqVPr3p6U8W93DOXlXW6zFvrQAJANdS+vw0YhusLIq+bszW8zmK2Fh1C2kDPX8FMvwIl1OUcFgvJoXLbAg==", 6 | "_location": "/@actions/io", 7 | "_phantomChildren": {}, 8 | "_requested": { 9 | "type": "range", 10 | "registry": true, 11 | "raw": "@actions/io@^1.0.1", 12 | "name": "@actions/io", 13 | "escapedName": "@actions%2fio", 14 | "scope": "@actions", 15 | "rawSpec": "^1.0.1", 16 | "saveSpec": null, 17 | "fetchSpec": "^1.0.1" 18 | }, 19 | "_requiredBy": [ 20 | "/@actions/exec" 21 | ], 22 | "_resolved": "https://registry.npmjs.org/@actions/io/-/io-1.0.2.tgz", 23 | "_shasum": "2f614b6e69ce14d191180451eb38e6576a6e6b27", 24 | "_spec": "@actions/io@^1.0.1", 25 | "_where": "/Users/bok/Projects/Open Source/swift-create-xcframework/node_modules/@actions/exec", 26 | "bugs": { 27 | "url": "https://github.com/actions/toolkit/issues" 28 | }, 29 | "bundleDependencies": false, 30 | "deprecated": false, 31 | "description": "Actions io lib", 32 | "directories": { 33 | "lib": "lib", 34 | "test": "__tests__" 35 | }, 36 | "files": [ 37 | "lib" 38 | ], 39 | "homepage": "https://github.com/actions/toolkit/tree/master/packages/io", 40 | "keywords": [ 41 | "github", 42 | "actions", 43 | "io" 44 | ], 45 | "license": "MIT", 46 | "main": "lib/io.js", 47 | "name": "@actions/io", 48 | "publishConfig": { 49 | "access": "public" 50 | }, 51 | "repository": { 52 | "type": "git", 53 | "url": "git+https://github.com/actions/toolkit.git", 54 | "directory": "packages/io" 55 | }, 56 | "scripts": { 57 | "audit-moderate": "npm install && npm audit --audit-level=moderate", 58 | "test": "echo \"Error: run tests from root\" && exit 1", 59 | "tsc": "tsc" 60 | }, 61 | "types": "lib/io.d.ts", 62 | "version": "1.0.2" 63 | } 64 | -------------------------------------------------------------------------------- /.action/rimraf/LICENSE: -------------------------------------------------------------------------------- 1 | The ISC License 2 | 3 | Copyright (c) Isaac Z. Schlueter and Contributors 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 15 | IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /.action/rimraf/README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/isaacs/rimraf.svg?branch=master)](https://travis-ci.org/isaacs/rimraf) [![Dependency Status](https://david-dm.org/isaacs/rimraf.svg)](https://david-dm.org/isaacs/rimraf) [![devDependency Status](https://david-dm.org/isaacs/rimraf/dev-status.svg)](https://david-dm.org/isaacs/rimraf#info=devDependencies) 2 | 3 | The [UNIX command](http://en.wikipedia.org/wiki/Rm_(Unix)) `rm -rf` for node. 4 | 5 | Install with `npm install rimraf`, or just drop rimraf.js somewhere. 6 | 7 | ## API 8 | 9 | `rimraf(f, [opts], callback)` 10 | 11 | The first parameter will be interpreted as a globbing pattern for files. If you 12 | want to disable globbing you can do so with `opts.disableGlob` (defaults to 13 | `false`). This might be handy, for instance, if you have filenames that contain 14 | globbing wildcard characters. 15 | 16 | The callback will be called with an error if there is one. Certain 17 | errors are handled for you: 18 | 19 | * Windows: `EBUSY` and `ENOTEMPTY` - rimraf will back off a maximum of 20 | `opts.maxBusyTries` times before giving up, adding 100ms of wait 21 | between each attempt. The default `maxBusyTries` is 3. 22 | * `ENOENT` - If the file doesn't exist, rimraf will return 23 | successfully, since your desired outcome is already the case. 24 | * `EMFILE` - Since `readdir` requires opening a file descriptor, it's 25 | possible to hit `EMFILE` if too many file descriptors are in use. 26 | In the sync case, there's nothing to be done for this. But in the 27 | async case, rimraf will gradually back off with timeouts up to 28 | `opts.emfileWait` ms, which defaults to 1000. 29 | 30 | ## options 31 | 32 | * unlink, chmod, stat, lstat, rmdir, readdir, 33 | unlinkSync, chmodSync, statSync, lstatSync, rmdirSync, readdirSync 34 | 35 | In order to use a custom file system library, you can override 36 | specific fs functions on the options object. 37 | 38 | If any of these functions are present on the options object, then 39 | the supplied function will be used instead of the default fs 40 | method. 41 | 42 | Sync methods are only relevant for `rimraf.sync()`, of course. 43 | 44 | For example: 45 | 46 | ```javascript 47 | var myCustomFS = require('some-custom-fs') 48 | 49 | rimraf('some-thing', myCustomFS, callback) 50 | ``` 51 | 52 | * maxBusyTries 53 | 54 | If an `EBUSY`, `ENOTEMPTY`, or `EPERM` error code is encountered 55 | on Windows systems, then rimraf will retry with a linear backoff 56 | wait of 100ms longer on each try. The default maxBusyTries is 3. 57 | 58 | Only relevant for async usage. 59 | 60 | * emfileWait 61 | 62 | If an `EMFILE` error is encountered, then rimraf will retry 63 | repeatedly with a linear backoff of 1ms longer on each try, until 64 | the timeout counter hits this max. The default limit is 1000. 65 | 66 | If you repeatedly encounter `EMFILE` errors, then consider using 67 | [graceful-fs](http://npm.im/graceful-fs) in your program. 68 | 69 | Only relevant for async usage. 70 | 71 | * glob 72 | 73 | Set to `false` to disable [glob](http://npm.im/glob) pattern 74 | matching. 75 | 76 | Set to an object to pass options to the glob module. The default 77 | glob options are `{ nosort: true, silent: true }`. 78 | 79 | Glob version 6 is used in this module. 80 | 81 | Relevant for both sync and async usage. 82 | 83 | * disableGlob 84 | 85 | Set to any non-falsey value to disable globbing entirely. 86 | (Equivalent to setting `glob: false`.) 87 | 88 | ## rimraf.sync 89 | 90 | It can remove stuff synchronously, too. But that's not so good. Use 91 | the async API. It's better. 92 | 93 | ## CLI 94 | 95 | If installed with `npm install rimraf -g` it can be used as a global 96 | command `rimraf [ ...]` which is useful for cross platform support. 97 | 98 | ## mkdirp 99 | 100 | If you need to create a directory recursively, check out 101 | [mkdirp](https://github.com/substack/node-mkdirp). 102 | -------------------------------------------------------------------------------- /.action/rimraf/bin.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var rimraf = require('./') 4 | 5 | var help = false 6 | var dashdash = false 7 | var noglob = false 8 | var args = process.argv.slice(2).filter(function(arg) { 9 | if (dashdash) 10 | return !!arg 11 | else if (arg === '--') 12 | dashdash = true 13 | else if (arg === '--no-glob' || arg === '-G') 14 | noglob = true 15 | else if (arg === '--glob' || arg === '-g') 16 | noglob = false 17 | else if (arg.match(/^(-+|\/)(h(elp)?|\?)$/)) 18 | help = true 19 | else 20 | return !!arg 21 | }) 22 | 23 | if (help || args.length === 0) { 24 | // If they didn't ask for help, then this is not a "success" 25 | var log = help ? console.log : console.error 26 | log('Usage: rimraf [ ...]') 27 | log('') 28 | log(' Deletes all files and folders at "path" recursively.') 29 | log('') 30 | log('Options:') 31 | log('') 32 | log(' -h, --help Display this usage info') 33 | log(' -G, --no-glob Do not expand glob patterns in arguments') 34 | log(' -g, --glob Expand glob patterns in arguments (default)') 35 | process.exit(help ? 0 : 1) 36 | } else 37 | go(0) 38 | 39 | function go (n) { 40 | if (n >= args.length) 41 | return 42 | var options = {} 43 | if (noglob) 44 | options = { glob: false } 45 | rimraf(args[n], options, function (er) { 46 | if (er) 47 | throw er 48 | go(n+1) 49 | }) 50 | } 51 | -------------------------------------------------------------------------------- /.action/rimraf/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "_from": "rimraf@^2.6.3", 3 | "_id": "rimraf@2.7.1", 4 | "_inBundle": false, 5 | "_integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", 6 | "_location": "/rimraf", 7 | "_phantomChildren": {}, 8 | "_requested": { 9 | "type": "range", 10 | "registry": true, 11 | "raw": "rimraf@^2.6.3", 12 | "name": "rimraf", 13 | "escapedName": "rimraf", 14 | "rawSpec": "^2.6.3", 15 | "saveSpec": null, 16 | "fetchSpec": "^2.6.3" 17 | }, 18 | "_requiredBy": [ 19 | "/tmp" 20 | ], 21 | "_resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", 22 | "_shasum": "35797f13a7fdadc566142c29d4f07ccad483e3ec", 23 | "_spec": "rimraf@^2.6.3", 24 | "_where": "/Users/bok/Projects/Open Source/swift-create-xcframework/node_modules/tmp", 25 | "author": { 26 | "name": "Isaac Z. Schlueter", 27 | "email": "i@izs.me", 28 | "url": "http://blog.izs.me/" 29 | }, 30 | "bin": { 31 | "rimraf": "bin.js" 32 | }, 33 | "bugs": { 34 | "url": "https://github.com/isaacs/rimraf/issues" 35 | }, 36 | "bundleDependencies": false, 37 | "dependencies": { 38 | "glob": "^7.1.3" 39 | }, 40 | "deprecated": false, 41 | "description": "A deep deletion module for node (like `rm -rf`)", 42 | "devDependencies": { 43 | "mkdirp": "^0.5.1", 44 | "tap": "^12.1.1" 45 | }, 46 | "files": [ 47 | "LICENSE", 48 | "README.md", 49 | "bin.js", 50 | "rimraf.js" 51 | ], 52 | "homepage": "https://github.com/isaacs/rimraf#readme", 53 | "license": "ISC", 54 | "main": "rimraf.js", 55 | "name": "rimraf", 56 | "repository": { 57 | "type": "git", 58 | "url": "git://github.com/isaacs/rimraf.git" 59 | }, 60 | "scripts": { 61 | "postpublish": "git push origin --all; git push origin --tags", 62 | "postversion": "npm publish", 63 | "preversion": "npm test", 64 | "test": "tap test/*.js" 65 | }, 66 | "version": "2.7.1" 67 | } 68 | -------------------------------------------------------------------------------- /.action/tmp-promise/.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | common_steps: &common_steps 4 | steps: 5 | - checkout 6 | 7 | - run: 8 | name: Install dependencies 9 | command: npm install 10 | 11 | - run: 12 | name: Run tests 13 | command: npm run mocha 14 | 15 | - run: 16 | name: Check Typescript types 17 | command: npm run check-types 18 | when: always 19 | 20 | jobs: 21 | node-8: 22 | docker: 23 | - image: circleci/node:8 24 | 25 | <<: *common_steps 26 | 27 | node-10: 28 | docker: 29 | - image: circleci/node:10 30 | 31 | <<: *common_steps 32 | 33 | node-11: 34 | docker: 35 | - image: circleci/node:11 36 | 37 | <<: *common_steps 38 | 39 | node-12: 40 | docker: 41 | - image: circleci/node:12 42 | 43 | <<: *common_steps 44 | 45 | workflows: 46 | version: 2 47 | 48 | on-commit: 49 | jobs: 50 | - node-8 51 | - node-10 52 | - node-11 53 | - node-12 54 | -------------------------------------------------------------------------------- /.action/tmp-promise/README.md: -------------------------------------------------------------------------------- 1 | # tmp-promise 2 | 3 | [![CircleCI](https://circleci.com/gh/benjamingr/tmp-promise.svg?style=svg)](https://circleci.com/gh/benjamingr/tmp-promise) 4 | [![npm version](https://badge.fury.io/js/tmp-promise.svg)](https://badge.fury.io/js/tmp-promise) 5 | 6 | A simple utility for creating temporary files or directories. 7 | 8 | The [tmp](https://github.com/raszi/node-tmp) package with promises support. If you want to use `tmp` with `async/await` then this helper might be for you. 9 | 10 | This documentation is mostly copied from that package's - but with promise usage instead of callback usage adapted. 11 | 12 | ## Installation 13 | 14 | npm i tmp-promise 15 | 16 | **Note:** Node.js 8+ is supported - older versions of Node.js are not supported by the Node.js foundation. If you need to use an older version of Node.js install tmp-promise@1.10 17 | 18 | npm i tmp-promise@1.1.0 19 | 20 | ## About 21 | 22 | This adds promises support to a [widely used library][2]. This package is used to create temporary files and directories in a [Node.js][1] environment. 23 | 24 | 25 | tmp-promise offers both an asynchronous and a synchronous API. For all API calls, all 26 | the parameters are optional. 27 | 28 | Internally, tmp uses crypto for determining random file names, or, when using templates, a six letter random identifier. And just in case that you do not have that much entropy left on your system, tmp will fall back to pseudo random numbers. 29 | 30 | You can set whether you want to remove the temporary file on process exit or not, and the destination directory can also be set. 31 | 32 | tmp-promise also uses promise [disposers](http://stackoverflow.com/questions/28915677/what-is-the-promise-disposer-pattern) to provide a nice way to perform cleanup when you're done working with the files. 33 | 34 | ## Usage (API Reference) 35 | 36 | ### Asynchronous file creation 37 | 38 | Simple temporary file creation, the file will be closed and unlinked on process exit. 39 | 40 | With Node.js 10 and es - modules: 41 | 42 | ```js 43 | import { file } from 'tmp-promise' 44 | 45 | (async () => { 46 | const {fd, path, cleanup} = await file(); 47 | // work with file here in fd 48 | cleanup(); 49 | })(); 50 | ``` 51 | 52 | Or the older way: 53 | 54 | ```javascript 55 | var tmp = require('tmp-promise'); 56 | 57 | tmp.file().then(o => { 58 | console.log("File: ", o.path); 59 | console.log("Filedescriptor: ", o.fd); 60 | 61 | // If we don't need the file anymore we could manually call cleanup 62 | // But that is not necessary if we didn't pass the keep option because the library 63 | // will clean after itself. 64 | o.cleanup(); 65 | }); 66 | ``` 67 | 68 | Simple temporary file creation with a [disposer](http://stackoverflow.com/questions/28915677/what-is-the-promise-disposer-pattern): 69 | 70 | With Node.js 10 and es - modules: 71 | 72 | ```js 73 | import { withFile } from 'tmp-promise' 74 | 75 | withFile(async ({path, fd}) => { 76 | // when this function returns or throws - release the file 77 | await doSomethingWithFile(db); 78 | }); 79 | ``` 80 | 81 | Or the older way: 82 | 83 | ```js 84 | tmp.withFile(o => { 85 | console.log("File: ", o.path); 86 | console.log("Filedescriptor: ", o.fd); 87 | // the file remains opens until the below promise resolves 88 | return somePromiseReturningFn(); 89 | }).then(v => { 90 | // file is closed here automatically, v is the value of somePromiseReturningFn 91 | }); 92 | ``` 93 | 94 | 95 | ### Synchronous file creation 96 | 97 | A synchronous version of the above. 98 | 99 | ```javascript 100 | var tmp = require('tmp-promise'); 101 | 102 | var tmpobj = tmp.fileSync(); 103 | console.log("File: ", tmpobj.name); 104 | console.log("Filedescriptor: ", tmpobj.fd); 105 | 106 | // If we don't need the file anymore we could manually call the removeCallback 107 | // But that is not necessary if we didn't pass the keep option because the library 108 | // will clean after itself. 109 | tmpobj.removeCallback(); 110 | ``` 111 | 112 | Note that this might throw an exception if either the maximum limit of retries 113 | for creating a temporary name fails, or, in case that you do not have the permission 114 | to write to the directory where the temporary file should be created in. 115 | 116 | ### Asynchronous directory creation 117 | 118 | Simple temporary directory creation, it will be removed on process exit. 119 | 120 | If the directory still contains items on process exit, then it won't be removed. 121 | 122 | ```javascript 123 | var tmp = require('tmp-promise'); 124 | 125 | tmp.dir().then(o => { 126 | console.log("Dir: ", o.path); 127 | 128 | // Manual cleanup 129 | o.cleanup(); 130 | }); 131 | ``` 132 | 133 | If you want to cleanup the directory even when there are entries in it, then 134 | you can pass the `unsafeCleanup` option when creating it. 135 | 136 | You can also use a [disposer](http://stackoverflow.com/questions/28915677/what-is-the-promise-disposer-pattern) here which takes care of cleanup automatically: 137 | 138 | ```javascript 139 | var tmp = require('tmp-promise'); 140 | 141 | tmp.withDir(o => { 142 | console.log("Dir: ", o.path); 143 | 144 | // automatic cleanup when the below promise resolves 145 | return somePromiseReturningFn(); 146 | }).then(v => { 147 | // the directory has been cleaned here 148 | }); 149 | ``` 150 | 151 | ### Synchronous directory creation 152 | 153 | A synchronous version of the above. 154 | 155 | ```javascript 156 | var tmp = require('tmp-promise'); 157 | 158 | var tmpobj = tmp.dirSync(); 159 | console.log("Dir: ", tmpobj.name); 160 | // Manual cleanup 161 | tmpobj.removeCallback(); 162 | ``` 163 | 164 | Note that this might throw an exception if either the maximum limit of retries 165 | for creating a temporary name fails, or, in case that you do not have the permission 166 | to write to the directory where the temporary directory should be created in. 167 | 168 | ### Asynchronous filename generation 169 | 170 | It is possible with this library to generate a unique filename in the specified 171 | directory. 172 | 173 | ```javascript 174 | var tmp = require('tmp-promise'); 175 | 176 | tmp.tmpName().then(path => { 177 | console.log("Created temporary filename: ", path); 178 | }); 179 | ``` 180 | 181 | ### Synchronous filename generation 182 | 183 | A synchronous version of the above. 184 | 185 | ```javascript 186 | var tmp = require('tmp-promise'); 187 | 188 | var name = tmp.tmpNameSync(); 189 | console.log("Created temporary filename: ", name); 190 | ``` 191 | 192 | ## Advanced usage 193 | 194 | ### Asynchronous file creation 195 | 196 | Creates a file with mode `0644`, prefix will be `prefix-` and postfix will be `.txt`. 197 | 198 | ```javascript 199 | var tmp = require('tmp-promise'); 200 | 201 | tmp.file({ mode: 0644, prefix: 'prefix-', postfix: '.txt' }).then(o => { 202 | console.log("File: ", o.path); 203 | console.log("Filedescriptor: ", o.fd); 204 | }); 205 | ``` 206 | 207 | ### Synchronous file creation 208 | 209 | A synchronous version of the above. 210 | 211 | ```javascript 212 | var tmp = require('tmp-promise'); 213 | 214 | var tmpobj = tmp.fileSync({ mode: 0644, prefix: 'prefix-', postfix: '.txt' }); 215 | console.log("File: ", tmpobj.name); 216 | console.log("Filedescriptor: ", tmpobj.fd); 217 | ``` 218 | 219 | ### Asynchronous directory creation 220 | 221 | Creates a directory with mode `0755`, prefix will be `myTmpDir_`. 222 | 223 | ```javascript 224 | var tmp = require('tmp-promise'); 225 | 226 | tmp.dir({ mode: 0750, prefix: 'myTmpDir_' }).then(o => { 227 | console.log("Dir: ", o.path); 228 | }); 229 | ``` 230 | 231 | ### Synchronous directory creation 232 | 233 | Again, a synchronous version of the above. 234 | 235 | ```javascript 236 | var tmp = require('tmp-promise'); 237 | 238 | var tmpobj = tmp.dirSync({ mode: 0750, prefix: 'myTmpDir_' }); 239 | console.log("Dir: ", tmpobj.name); 240 | ``` 241 | 242 | 243 | ### mkstemp like, asynchronously 244 | 245 | Creates a new temporary directory with mode `0700` and filename like `/tmp/tmp-nk2J1u`. 246 | 247 | ```javascript 248 | var tmp = require('tmp-promise'); 249 | tmp.dir({ template: '/tmp/tmp-XXXXXX' }).then(console.log); 250 | ``` 251 | 252 | 253 | ### mkstemp like, synchronously 254 | 255 | This will behave similarly to the asynchronous version. 256 | 257 | ```javascript 258 | var tmp = require('tmp-promise'); 259 | 260 | var tmpobj = tmp.dirSync({ template: '/tmp/tmp-XXXXXX' }); 261 | console.log("Dir: ", tmpobj.name); 262 | ``` 263 | 264 | ### Asynchronous filename generation 265 | 266 | The `tmpName()` function accepts the `prefix`, `postfix`, `dir`, etc. parameters also: 267 | 268 | ```javascript 269 | var tmp = require('tmp-promise'); 270 | 271 | tmp.tmpName({ template: '/tmp/tmp-XXXXXX' }).then(path => 272 | console.log("Created temporary filename: ", path); 273 | ); 274 | ``` 275 | 276 | ### Synchronous filename generation 277 | 278 | The `tmpNameSync()` function works similarly to `tmpName()`. 279 | 280 | ```javascript 281 | var tmp = require('tmp-promise'); 282 | var tmpname = tmp.tmpNameSync({ template: '/tmp/tmp-XXXXXX' }); 283 | console.log("Created temporary filename: ", tmpname); 284 | ``` 285 | 286 | 287 | ## Graceful cleanup 288 | 289 | One may want to cleanup the temporary files even when an uncaught exception 290 | occurs. To enforce this, you can call the `setGracefulCleanup()` method: 291 | 292 | ```javascript 293 | var tmp = require('tmp'); 294 | 295 | tmp.setGracefulCleanup(); 296 | ``` 297 | 298 | ## Options 299 | 300 | All options are optional :) 301 | 302 | * `mode`: the file mode to create with, it fallbacks to `0600` on file creation and `0700` on directory creation 303 | * `prefix`: the optional prefix, fallbacks to `tmp-` if not provided 304 | * `postfix`: the optional postfix, fallbacks to `.tmp` on file creation 305 | * `template`: [`mkstemp`][3] like filename template, no default 306 | * `dir`: the optional temporary directory, fallbacks to system default (guesses from environment) 307 | * `tries`: how many times should the function try to get a unique filename before giving up, default `3` 308 | * `keep`: signals that the temporary file or directory should not be deleted on exit, default is `false`, means delete 309 | * Please keep in mind that it is recommended in this case to call the provided `cleanupCallback` function manually. 310 | * `unsafeCleanup`: recursively removes the created temporary directory, even when it's not empty. default is `false` 311 | 312 | 313 | 314 | [1]: http://nodejs.org/ 315 | [2]: https://www.npmjs.com/browse/depended/tmp 316 | [3]: http://www.kernel.org/doc/man-pages/online/pages/man3/mkstemp.3.html 317 | -------------------------------------------------------------------------------- /.action/tmp-promise/example-usage.js: -------------------------------------------------------------------------------- 1 | var tmp = require("./index.js"); 2 | var Promise = require("bluebird"); // just for delay, this works with native promises 3 | // disposer 4 | tmp.withFile((path) => { 5 | console.log("Created at path", path); 6 | return Promise.delay(1000); 7 | }).then(() => { 8 | console.log("File automatically disposed"); 9 | }); -------------------------------------------------------------------------------- /.action/tmp-promise/index.js: -------------------------------------------------------------------------------- 1 | const {promisify} = require("util"); 2 | const tmp = require("../tmp"); 3 | 4 | // file 5 | module.exports.fileSync = tmp.fileSync; 6 | const fileWithOptions = promisify((options, cb) => 7 | tmp.file(options, (err, path, fd, cleanup) => 8 | err ? cb(err) : cb(undefined, { path, fd, cleanup: promisify(cleanup) }) 9 | ) 10 | ); 11 | module.exports.file = async (options) => fileWithOptions(options); 12 | 13 | module.exports.withFile = async function withFile(fn, options) { 14 | const { path, fd, cleanup } = await module.exports.file(options); 15 | try { 16 | return await fn({ path, fd }); 17 | } finally { 18 | await cleanup(); 19 | } 20 | }; 21 | 22 | 23 | // directory 24 | module.exports.dirSync = tmp.dirSync; 25 | const dirWithOptions = promisify((options, cb) => 26 | tmp.dir(options, (err, path, cleanup) => 27 | err ? cb(err) : cb(undefined, { path, cleanup: promisify(cleanup) }) 28 | ) 29 | ); 30 | module.exports.dir = async (options) => dirWithOptions(options); 31 | 32 | module.exports.withDir = async function withDir(fn, options) { 33 | const { path, cleanup } = await module.exports.dir(options); 34 | try { 35 | return await fn({ path }); 36 | } finally { 37 | await cleanup(); 38 | } 39 | }; 40 | 41 | 42 | // name generation 43 | module.exports.tmpNameSync = tmp.tmpNameSync; 44 | module.exports.tmpName = promisify(tmp.tmpName); 45 | 46 | module.exports.tmpdir = tmp.tmpdir; 47 | 48 | module.exports.setGracefulCleanup = tmp.setGracefulCleanup; 49 | -------------------------------------------------------------------------------- /.action/tmp-promise/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "_from": "tmp-promise@^2.0.2", 3 | "_id": "tmp-promise@2.1.1", 4 | "_inBundle": false, 5 | "_integrity": "sha512-Z048AOz/w9b6lCbJUpevIJpRpUztENl8zdv1bmAKVHimfqRFl92ROkmT9rp7TVBnrEw2gtMTol/2Cp2S2kJa4Q==", 6 | "_location": "/tmp-promise", 7 | "_phantomChildren": {}, 8 | "_requested": { 9 | "type": "range", 10 | "registry": true, 11 | "raw": "tmp-promise@^2.0.2", 12 | "name": "tmp-promise", 13 | "escapedName": "tmp-promise", 14 | "rawSpec": "^2.0.2", 15 | "saveSpec": null, 16 | "fetchSpec": "^2.0.2" 17 | }, 18 | "_requiredBy": [ 19 | "/@actions/artifact" 20 | ], 21 | "_resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-2.1.1.tgz", 22 | "_shasum": "eb97c038995af74efbfe8156f5e07fdd0c935539", 23 | "_spec": "tmp-promise@^2.0.2", 24 | "_where": "/Users/bok/Projects/Open Source/swift-create-xcframework/node_modules/@actions/artifact", 25 | "author": { 26 | "name": "Benjamin Gruenbaum and collaborators." 27 | }, 28 | "bugs": { 29 | "url": "https://github.com/benjamingr/tmp-promise/issues" 30 | }, 31 | "bundleDependencies": false, 32 | "dependencies": { 33 | "tmp": "0.1.0" 34 | }, 35 | "deprecated": false, 36 | "description": "The tmp package with promises support and disposers.", 37 | "devDependencies": { 38 | "@types/tmp": "^0.1.0", 39 | "mocha": "^6.1.4", 40 | "tsd": "^0.7.2" 41 | }, 42 | "homepage": "https://github.com/benjamingr/tmp-promise#readme", 43 | "keywords": [ 44 | "tmp", 45 | "promise", 46 | "tempfile", 47 | "mkdtemp", 48 | "mktemp" 49 | ], 50 | "license": "MIT", 51 | "main": "index.js", 52 | "name": "tmp-promise", 53 | "repository": { 54 | "type": "git", 55 | "url": "git://github.com/benjamingr/tmp-promise.git" 56 | }, 57 | "scripts": { 58 | "check-types": "tsd", 59 | "mocha": "mocha", 60 | "test": "npm run mocha && npm run check-types" 61 | }, 62 | "types": "index.d.ts", 63 | "version": "2.1.1" 64 | } 65 | -------------------------------------------------------------------------------- /.action/tmp-promise/test.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const {promisify} = require('util') 3 | const assert = require('assert') 4 | const {extname} = require('path') 5 | 6 | const tmp = require('.') 7 | 8 | async function checkFileResult(result) { 9 | assert.deepEqual(Object.keys(result).sort(), ['cleanup', 'fd', 'path']) 10 | 11 | const { path, fd, cleanup } = result 12 | assert.ok(typeof path === 'string') 13 | assert.ok(typeof fd === 'number') 14 | assert.ok(typeof cleanup === 'function') 15 | 16 | // Check that the path is a fille. 17 | assert.ok(fs.statSync(path).isFile()) 18 | 19 | // Check that the fd is correct and points to the file. 20 | const message = 'hello there!' 21 | fs.writeSync(fd, message) 22 | fs.fdatasyncSync(fd) 23 | assert.equal(fs.readFileSync(path), message) 24 | 25 | // Check that the cleanup works. 26 | await cleanup() 27 | assert.throws(() => fs.statSync(path)) 28 | } 29 | 30 | describe('file()', function() 31 | { 32 | context('when called without options', function() 33 | { 34 | it('creates the file, returns the expected result, and the cleanup function works', async function() 35 | { 36 | const result = await tmp.file() 37 | await checkFileResult(result) 38 | }) 39 | }) 40 | 41 | context('when called with options', function() 42 | { 43 | it('creates the file, returns the expected result, and the cleanup function works', async function() 44 | { 45 | const prefix = 'myTmpDir_' 46 | const result = await tmp.file({ prefix }) 47 | await checkFileResult(result) 48 | assert.ok(result.path.includes(prefix)) 49 | }) 50 | }) 51 | 52 | it('propagates errors', async function() { 53 | try { 54 | await tmp.file({ dir: 'nonexistent-path' }); 55 | throw Error('Expected to throw'); 56 | } catch (e) { 57 | assert.ok(e.message.startsWith('ENOENT: no such file or directory')); 58 | } 59 | }); 60 | }) 61 | 62 | async function checkDirResult(result) { 63 | assert.deepEqual(Object.keys(result).sort(), ['cleanup', 'path']) 64 | 65 | const { path, cleanup } = result 66 | assert.ok(typeof path === 'string') 67 | assert.ok(typeof cleanup === 'function') 68 | 69 | assert.ok(fs.statSync(path).isDirectory()) 70 | 71 | await cleanup() 72 | assert.throws(() => fs.statSync(path)) 73 | } 74 | 75 | describe('dir()', function() 76 | { 77 | context('when called without options', function() 78 | { 79 | it('creates the directory, returns the expected result, and the cleanup function works', async function() 80 | { 81 | const result = await tmp.dir() 82 | await checkDirResult(result) 83 | }) 84 | }) 85 | 86 | context('when called with options', function() 87 | { 88 | it('creates the directory, returns the expected result, and the cleanup function works', async function() 89 | { 90 | const prefix = 'myTmpDir_' 91 | const result = await tmp.dir({ prefix }) 92 | await checkDirResult(result) 93 | assert.ok(result.path.includes(prefix)) 94 | }) 95 | }) 96 | 97 | it('propagates errors', async function() { 98 | try { 99 | await tmp.dir({ dir: 'nonexistent-path' }); 100 | throw Error('Expected to throw'); 101 | } catch (e) { 102 | assert.ok(e.message.startsWith('ENOENT: no such file or directory')); 103 | } 104 | }); 105 | }) 106 | 107 | describe('withFile()', function() 108 | { 109 | it("file doesn't exist after going out of scope", function() 110 | { 111 | var filepath 112 | 113 | return tmp.withFile(function(o) 114 | { 115 | filepath = o.path 116 | 117 | fs.accessSync(filepath) 118 | assert.strictEqual(extname(filepath), '.txt') 119 | }, {discardDescriptor: true, postfix: '.txt'}) 120 | .then(function() 121 | { 122 | assert.throws(function() 123 | { 124 | fs.accessSync(filepath) 125 | }, filepath + ' still exists') 126 | }) 127 | }) 128 | }) 129 | 130 | 131 | describe('withDir()', function() 132 | { 133 | it("dir doesn't exist after going out of scope", function() 134 | { 135 | var filepath 136 | 137 | return tmp.withDir(function(o) 138 | { 139 | filepath = o.path 140 | 141 | fs.accessSync(filepath) 142 | assert.strictEqual(extname(filepath), '.dir') 143 | }, {postfix: '.dir'}) 144 | .then(function() 145 | { 146 | assert.throws(function() 147 | { 148 | fs.accessSync(filepath) 149 | }, filepath + ' still exists') 150 | }) 151 | }) 152 | }) 153 | -------------------------------------------------------------------------------- /.action/tmp/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 KARASZI István 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 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.action/tmp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "_from": "tmp@^0.1.0", 3 | "_id": "tmp@0.1.0", 4 | "_inBundle": false, 5 | "_integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==", 6 | "_location": "/tmp", 7 | "_phantomChildren": {}, 8 | "_requested": { 9 | "type": "range", 10 | "registry": true, 11 | "raw": "tmp@^0.1.0", 12 | "name": "tmp", 13 | "escapedName": "tmp", 14 | "rawSpec": "^0.1.0", 15 | "saveSpec": null, 16 | "fetchSpec": "^0.1.0" 17 | }, 18 | "_requiredBy": [ 19 | "/@actions/artifact", 20 | "/tmp-promise" 21 | ], 22 | "_resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz", 23 | "_shasum": "ee434a4e22543082e294ba6201dcc6eafefa2877", 24 | "_spec": "tmp@^0.1.0", 25 | "_where": "/Users/bok/Projects/Open Source/swift-create-xcframework/node_modules/@actions/artifact", 26 | "author": { 27 | "name": "KARASZI István", 28 | "email": "github@spam.raszi.hu", 29 | "url": "http://raszi.hu/" 30 | }, 31 | "bugs": { 32 | "url": "http://github.com/raszi/node-tmp/issues" 33 | }, 34 | "bundleDependencies": false, 35 | "dependencies": { 36 | "rimraf": "^2.6.3" 37 | }, 38 | "deprecated": false, 39 | "description": "Temporary file and directory creator", 40 | "devDependencies": { 41 | "eslint": "^4.19.1", 42 | "eslint-plugin-mocha": "^5.0.0", 43 | "istanbul": "^0.4.5", 44 | "mocha": "^5.1.1" 45 | }, 46 | "engines": { 47 | "node": ">=6" 48 | }, 49 | "files": [ 50 | "lib/" 51 | ], 52 | "homepage": "http://github.com/raszi/node-tmp", 53 | "keywords": [ 54 | "temporary", 55 | "tmp", 56 | "temp", 57 | "tempdir", 58 | "tempfile", 59 | "tmpdir", 60 | "tmpfile" 61 | ], 62 | "license": "MIT", 63 | "main": "lib/tmp.js", 64 | "name": "tmp", 65 | "repository": { 66 | "type": "git", 67 | "url": "git+https://github.com/raszi/node-tmp.git" 68 | }, 69 | "scripts": { 70 | "clean": "rm -Rf ./coverage", 71 | "doc": "jsdoc -c .jsdoc.json", 72 | "lint": "eslint lib --env mocha test", 73 | "test": "npm run clean && istanbul cover ./node_modules/mocha/bin/_mocha --report none --print none --dir ./coverage/json -u exports -R test/*-test.js && istanbul report --root ./coverage/json html && istanbul report text-summary" 74 | }, 75 | "version": "0.1.0" 76 | } 77 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | .action/* linguist-vendored 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/BUG_REPORT.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🐛 Bug Report 3 | about: Something isn't working as expected 4 | --- 5 | 6 | 16 | 17 | Replace this paragraph with a short description of the incorrect incorrect behavior. If this is a regression, please note the last version that the behavior was correct in addition to your current version. Please also include any information you have about the environment as this tool is very sensitive to Xcode and SDK versions. 18 | 19 | **swift-create-xcframework version:** `0.1.0` or the `master` branch, for example. 20 | **Swift version:** Paste the output of `swift --version` here. 21 | **Environment:** Xcode and macOS versions as appropriate 22 | 23 | ### Checklist 24 | - [ ] If possible, I've reproduced the issue using the `master` branch of this package 25 | - [ ] I've searched for [existing GitHub issues](https://github.com/unsignedapps/swift-create-xcframework/issues) 26 | 27 | ### Steps to Reproduce 28 | 29 | Replace this paragraph with an explanation of how to reproduce the incorrect behavior. This could include a code listing for a reduced version of your command, or a link to the code that is exhibiting the issue. 30 | 31 | ### Expected behavior 32 | 33 | Describe what you expect to happen. 34 | 35 | ### Actual behavior 36 | 37 | Describe or copy/paste the behavior you observe. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 💡 Feature Request 3 | about: A suggestion for a new feature 4 | --- 5 | 6 | 12 | 13 | Replace this paragraph with a description of your proposed feature. Code samples that show what's missing, or what new capabilities will be possible, are very helpful! Provide links to existing issues or external references/discussions, if appropriate. -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/NEW.md: -------------------------------------------------------------------------------- 1 | 11 | 12 | ### Description 13 | 14 | Replace this paragraph with a description of your changes and rationale. Provide links to an existing issue or external references/discussions, if appropriate. 15 | 16 | ### Detailed Design 17 | 18 | Include any additional information about the design here. At minimum, show any new API: 19 | 20 | ```swift 21 | /// The new feature implemented by this pull request. 22 | struct NewFeature {} 23 | ``` 24 | 25 | ### Documentation Plan 26 | 27 | How has the new feature been documented? Have the relevant portions of the guide been updated in addition to symbol-level documentation? 28 | 29 | ### Test Plan 30 | 31 | How is the new feature tested? 32 | 33 | ### Source Impact 34 | 35 | What is the impact of this change on existing users? Does it deprecate or remove any existing API? 36 | 37 | ### Checklist 38 | 39 | - [ ] I've added at least one test that validates that my change is working, if appropriate 40 | - [ ] I've followed the code style of the rest of the project 41 | - [ ] I've read the [Contribution Guidelines](CONTRIBUTING.md) 42 | - [ ] I've updated the documentation if necessary -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name-template: 'v$RESOLVED_VERSION 🌈' 2 | tag-template: 'v$RESOLVED_VERSION' 3 | 4 | categories: 5 | - title: '🚀 Features' 6 | labels: 7 | - 'enhancement' 8 | - 'story' 9 | - 'story:task' 10 | - title: '🐛 Bug Fixes' 11 | labels: 12 | - 'bug' 13 | - title: '🔒 Security Changes' 14 | labels: 15 | - 'security' 16 | - title: '📚 Documentation Changes' 17 | labels: 18 | - 'documentation' 19 | - title: '🧰 Maintenance' 20 | labels: 21 | - 'tech' 22 | - 'ci' 23 | 24 | exclude-labels: 25 | - 'skip:changelog' 26 | - 'trivial' 27 | 28 | change-template: '- $TITLE @$AUTHOR (#$NUMBER)' 29 | 30 | template: | 31 | ## What’s Changed 32 | 33 | $CHANGES 34 | 35 | version-resolver: 36 | major: 37 | labels: 38 | - 'major' 39 | minor: 40 | labels: 41 | - 'minor' 42 | patch: 43 | labels: 44 | - 'patch' 45 | default: patch -------------------------------------------------------------------------------- /.github/workflows/draft-release.yml: -------------------------------------------------------------------------------- 1 | name: Draft Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | update_release_draft: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: release-drafter/release-drafter@v5 13 | env: 14 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/stale-issues.yml: -------------------------------------------------------------------------------- 1 | name: Close stale issues and PRs 2 | on: 3 | schedule: 4 | - cron: '30 1 * * *' 5 | 6 | jobs: 7 | stale: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/stale@v4 11 | with: 12 | stale-issue-message: This issue has been marked stale as it has not been updated in 60 days. It will be closed in one week. It can be re-opened at any time. 13 | stale-pr-message: This PR has been marked stale as it has not been updated in 60 days. It will be closed in one week. It can be re-opened at any time. -------------------------------------------------------------------------------- /.github/workflows/swiftlint.yml: -------------------------------------------------------------------------------- 1 | name: SwiftLint 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - '.github/workflows/swiftlint.yml' 7 | - '.swiftlint.yml' 8 | - '**/*.swift' 9 | 10 | jobs: 11 | SwiftLint: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v1 15 | - name: "🧹 SwiftLint changed files" 16 | uses: norio-nomura/action-swiftlint@3.1.0 17 | env: 18 | DIFF_BASE: ${{ github.base_ref }} -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | paths: 7 | - '.github/workflows/tests.yml' 8 | - '**/*.swift' 9 | pull_request: 10 | branches: [ main ] 11 | paths: 12 | - '.github/workflows/tests.yml' 13 | - '**/*.swift' 14 | 15 | jobs: 16 | tests-macos12: 17 | name: Test Builds - macOS 12 18 | runs-on: macos-12 19 | strategy: 20 | matrix: 21 | xcode: [ "13.1", "13.2.1", "13.3.1", "13.4.1" ] 22 | 23 | env: 24 | DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer 25 | 26 | steps: 27 | - name: 🛒 Checkout 28 | uses: actions/checkout@v2 29 | 30 | - name: 🧱 Build 31 | run: make build-release 32 | 33 | - name: 🚩 Checkout Vexil 34 | uses: actions/checkout@v2 35 | with: 36 | repository: unsignedapps/Vexil 37 | ref: v1.2.2 38 | path: Vexil 39 | 40 | - name: 📦 Package Vexil 41 | run: cd Vexil && ../.build/release/swift-create-xcframework --zip --zip-version 1.2.2 --platform ios --platform macos --platform tvos --platform watchos 42 | 43 | tests-macos11: 44 | name: Test Builds - macOS 11 45 | runs-on: macos-11.0 46 | strategy: 47 | matrix: 48 | xcode: [ "12.4", "12.5.1", "13.0", "13.1", "13.2" ] 49 | 50 | env: 51 | DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer 52 | 53 | steps: 54 | - name: 🛒 Checkout 55 | uses: actions/checkout@v2 56 | 57 | - name: 🧱 Build 58 | run: make build-release 59 | 60 | - name: 🚩 Checkout Vexil 61 | uses: actions/checkout@v2 62 | with: 63 | repository: unsignedapps/Vexil 64 | ref: v1.2.2 65 | path: Vexil 66 | 67 | - name: 📦 Package Vexil 68 | run: cd Vexil && ../.build/release/swift-create-xcframework --zip --zip-version 1.2.2 --platform ios --platform macos --platform tvos --platform watchos 69 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /.swiftpm 4 | /Packages 5 | /*.xcodeproj 6 | xcuserdata/ 7 | DerivedData/ 8 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - no_space_in_method_call 3 | - object_literal 4 | - prefixed_toplevel_constant 5 | - todo 6 | - let_var_whitespace 7 | - type_name 8 | - vertical_whitespace 9 | 10 | opt_in_rules: 11 | - anyobject_protocol 12 | - array_init 13 | - attributes 14 | - collection_alignment 15 | - closure_end_indentation 16 | - closure_spacing 17 | - contains_over_first_not_nil 18 | - convenience_type 19 | - discouraged_object_literal 20 | - empty_count 21 | - empty_string 22 | - empty_xctest_method 23 | - explicit_init 24 | - extension_access_modifier 25 | - fatal_error_message 26 | - first_where 27 | - identical_operands 28 | - joined_default_parameter 29 | - last_where 30 | - legacy_multiple 31 | - legacy_random 32 | - literal_expression_end_indentation 33 | - lower_acl_than_parent 34 | - modifier_order 35 | - multiline_arguments 36 | - multiline_arguments_brackets 37 | - multiline_function_chains 38 | - multiline_literal_brackets 39 | - multiline_parameters 40 | - multiline_parameters_brackets 41 | - multiple_closures_with_trailing_closure 42 | - operator_usage_whitespace 43 | - overridden_super_call 44 | - override_in_extension 45 | - pattern_matching_keywords 46 | - prohibited_super_call 47 | - reduce_into 48 | - redundant_nil_coalescing 49 | - redundant_type_annotation 50 | - single_test_class 51 | - sorted_first_last 52 | - sorted_imports 53 | - static_operator 54 | - strong_iboutlet 55 | - toggle_bool 56 | - unavailable_function 57 | - unneeded_parentheses_in_closure_argument 58 | - vertical_parameter_alignment_on_call 59 | - xct_specific_matcher 60 | - yoda_condition 61 | 62 | cyclomatic_complexity: 63 | ignores_case_statements: true 64 | 65 | line_length: 66 | ignores_function_declarations: true 67 | warning: 180 68 | 69 | identifier_name: 70 | excluded: [id, x, y, z, r, g, b, a, i, j, k, cx, cy, dx, dy, _rootGroup] 71 | 72 | modifier_order: 73 | preferred_modifier_order: 74 | - acl 75 | - override 76 | 77 | nesting: 78 | type_level: 2 79 | 80 | included: 81 | - "Sources" 82 | 83 | excluded: 84 | - "Package.swift" 85 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, and in the interest of 4 | fostering an open and welcoming community, we pledge to respect all people who 5 | contribute through reporting issues, posting feature requests, updating 6 | documentation, submitting pull requests or patches, and other activities. 7 | 8 | We are committed to making participation in this project a harassment-free 9 | experience for everyone, regardless of level of experience, gender, gender 10 | identity and expression, sexual orientation, disability, personal appearance, 11 | body size, race, ethnicity, age, religion, or nationality. 12 | 13 | Examples of unacceptable behaviour by participants include: 14 | 15 | - The use of sexualised language or imagery 16 | - Personal attacks 17 | - Trolling or insulting/derogatory comments 18 | - Public or private harassment 19 | - Publishing other's private information, such as physical or electronic 20 | addresses, without explicit permission 21 | - Other unethical or unprofessional conduct 22 | 23 | Project maintainers have the right and responsibility to remove, edit, or 24 | reject comments, commits, code, wiki edits, issues, and other contributions 25 | that are not aligned to this Code of Conduct, or to ban temporarily or 26 | permanently any contributor for other behaviours that they deem inappropriate, 27 | threatening, offensive, or harmful. 28 | 29 | By adopting this Code of Conduct, project maintainers commit themselves to 30 | fairly and consistently applying these principles to every aspect of managing 31 | this project. Project maintainers who do not follow or enforce the Code of 32 | Conduct may be permanently removed from the project team. 33 | 34 | This code of conduct applies both within project spaces and in public spaces 35 | when an individual is representing the project or its community. 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behaviour may be 38 | reported by contacting a project maintainer at [info@unsignedapps.com][email]. 39 | 40 | All complaints will be reviewed and investigated and will result in a response that 41 | is deemed necessary and appropriate to the circumstances. Maintainers are 42 | obligated to maintain confidentiality with regard to the reporter of an 43 | incident. 44 | 45 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 46 | version 1.3.0, available at 47 | [http://contributor-covenant.org/version/1/3/0/][version] 48 | 49 | [email]: mailto:info@unsignedapps.com 50 | [homepage]: http://contributor-covenant.org 51 | [version]: http://contributor-covenant.org/version/1/3/0/ 52 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | 4 | **Thank you for coming!** 5 | 6 | We welcome everyone to swift-create-xcframework and provide this guide for anyone interested in contributing. 7 | 8 | 9 | ### Report Bugs 10 | 11 | Please make sure the bug is not already reported by searching existing [issues]. 12 | 13 | If you're unable to find an existing issue addressing the problem, [open a new one][new-issue]. Be sure to include a title and clear description, as much relevant information as possible, and a code sample or an executable test case demonstrating the expected behaviour that is not occurring. 14 | 15 | 16 | ### Suggest an Enhancement 17 | 18 | Feel free to contact [@bok_][twitter] on Twitter, or [open a new issue][new-issue]. 19 | 20 | 21 | ### Submit a Pull Request 22 | 23 | Discuss your idea first, so that your changes have a good chance of being merged in. 24 | 25 | Submit your pull request against the `master` branch. 26 | 27 | Pull requests that include tests for modified and new functionalities, inline documentation, and relevant updates to the main README.md are merged faster, because you won't have to wait for somebody else to complete your contribution. 28 | 29 | [issues]: https://github.com/unsignedapps/swift-create-xcframework/issues 30 | [new-issue]: https://github.com/unsignedapps/swift-create-xcframework/issues/new 31 | [twitter]: http://twitter.com/bok -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Unsigned Apps 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Makefile 3 | # swift-create-xcframework 4 | # 5 | # Created by Rob Amos on 7/5/20. 6 | # 7 | 8 | PRODUCT := swift-create-xcframework 9 | INSTALL_DIR := /usr/local/bin 10 | 11 | # Override this on the command line if you need to 12 | BUILD_FLAGS := 13 | 14 | .PHONY: build build-release install install-debug 15 | 16 | default: build 17 | build: build-debug 18 | 19 | # Release Builds 20 | 21 | build-release: $(wildcard Sources/*/*.swift) 22 | swift build $(BUILD_FLAGS) --configuration release 23 | 24 | install: build-release 25 | cp .build/release/swift-create-xcframework $(INSTALL_DIR)/$(PRODUCT) 26 | touch -c $(INSTALL_DIR)/$(PRODUCT) 27 | 28 | # Debug builds 29 | 30 | build-debug: $(wildcard Sources/*/*.swift) 31 | swift build $(BUILD_FLAGS) --configuration debug 32 | 33 | install-debug: build-debug 34 | cp .build/debug/swift-create-xcframework $(INSTALL_DIR)/$(PRODUCT) 35 | touch -c $(INSTALL_DIR)/$(PRODUCT) 36 | 37 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "swift-argument-parser", 6 | "repositoryURL": "https://github.com/apple/swift-argument-parser.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "e394bf350e38cb100b6bc4172834770ede1b7232", 10 | "version": "1.0.3" 11 | } 12 | }, 13 | { 14 | "package": "swift-collections", 15 | "repositoryURL": "https://github.com/apple/swift-collections.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "48254824bb4248676bf7ce56014ff57b142b77eb", 19 | "version": "1.0.2" 20 | } 21 | }, 22 | { 23 | "package": "swift-crypto", 24 | "repositoryURL": "https://github.com/apple/swift-crypto.git", 25 | "state": { 26 | "branch": null, 27 | "revision": "ddb07e896a2a8af79512543b1c7eb9797f8898a5", 28 | "version": "1.1.7" 29 | } 30 | }, 31 | { 32 | "package": "swift-driver", 33 | "repositoryURL": "https://github.com/apple/swift-driver.git", 34 | "state": { 35 | "branch": "release/5.7", 36 | "revision": "3d1af45a920fb1a4c3a1a3ca416fdd49dc8d48b3", 37 | "version": null 38 | } 39 | }, 40 | { 41 | "package": "llbuild", 42 | "repositoryURL": "https://github.com/apple/swift-llbuild.git", 43 | "state": { 44 | "branch": "release/5.7", 45 | "revision": "e2c27ee7ae7bd82ba35e97bac3c453faa582afd9", 46 | "version": null 47 | } 48 | }, 49 | { 50 | "package": "SwiftPM", 51 | "repositoryURL": "https://github.com/apple/swift-package-manager.git", 52 | "state": { 53 | "branch": "release/5.7", 54 | "revision": "9f3157bfb7d5dc06cac36b4c86e441136067b7ce", 55 | "version": null 56 | } 57 | }, 58 | { 59 | "package": "swift-system", 60 | "repositoryURL": "https://github.com/apple/swift-system.git", 61 | "state": { 62 | "branch": null, 63 | "revision": "836bc4557b74fe6d2660218d56e3ce96aff76574", 64 | "version": "1.1.1" 65 | } 66 | }, 67 | { 68 | "package": "swift-tools-support-core", 69 | "repositoryURL": "https://github.com/apple/swift-tools-support-core.git", 70 | "state": { 71 | "branch": "release/5.7", 72 | "revision": "184eba382f6abbb362ffc02942d790ff35019ad4", 73 | "version": null 74 | } 75 | }, 76 | { 77 | "package": "Yams", 78 | "repositoryURL": "https://github.com/jpsim/Yams.git", 79 | "state": { 80 | "branch": null, 81 | "revision": "9ff1cc9327586db4e0c8f46f064b6a82ec1566fa", 82 | "version": "4.0.6" 83 | } 84 | } 85 | ] 86 | }, 87 | "version": 1 88 | } 89 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let dependencies: [Package.Dependency] 7 | #if swift(>=5.7) 8 | dependencies = [ 9 | .package(url: "https://github.com/apple/swift-argument-parser.git", .exact("1.0.3")), 10 | .package(name: "SwiftPM", url: "https://github.com/apple/swift-package-manager.git", .branch("release/5.7")), 11 | .package(url: "https://github.com/apple/swift-tools-support-core.git", .branch("release/5.7")), 12 | ] 13 | #elseif swift(>=5.6) 14 | dependencies = [ 15 | .package(url: "https://github.com/apple/swift-argument-parser.git", .exact("1.0.3")), 16 | .package(name: "SwiftPM", url: "https://github.com/apple/swift-package-manager.git", .branch("release/5.6")), 17 | .package(url: "https://github.com/apple/swift-tools-support-core.git", .exact("0.2.5")) 18 | ] 19 | #elseif swift(>=5.5) 20 | dependencies = [ 21 | .package(url: "https://github.com/apple/swift-argument-parser.git", .exact("0.4.4")), 22 | .package(name: "SwiftPM", url: "https://github.com/apple/swift-package-manager.git", .branch("release/5.5")), 23 | .package(url: "https://github.com/apple/swift-tools-support-core.git", .exact("0.2.3")) 24 | ] 25 | #else 26 | dependencies = [ 27 | .package(url: "https://github.com/apple/swift-argument-parser.git", .exact("0.3.2")), 28 | .package(name: "SwiftPM", url: "https://github.com/apple/swift-package-manager.git", .revision("swift-5.3.3-RELEASE")), 29 | .package(url: "https://github.com/apple/swift-tools-support-core.git", .exact("0.1.12")) 30 | ] 31 | #endif 32 | 33 | let platforms: [SupportedPlatform] 34 | #if swift(>=5.6) 35 | platforms = [ 36 | .macOS(.v11), 37 | ] 38 | #else 39 | platforms = [ 40 | .macOS(.v10_15), 41 | ] 42 | #endif 43 | 44 | let package = Package( 45 | name: "swift-create-xcframework", 46 | 47 | // TODO: Add Linux / Windows support 48 | platforms: platforms, 49 | 50 | products: [ 51 | .executable(name: "swift-create-xcframework", targets: [ "CreateXCFramework" ]), 52 | ], 53 | 54 | dependencies: dependencies, 55 | 56 | targets: [ 57 | .target(name: "CreateXCFramework", dependencies: [ 58 | .product(name: "ArgumentParser", package: "swift-argument-parser"), 59 | .product(name: "SwiftPM-auto", package: "SwiftPM"), 60 | .product(name: "SwiftToolsSupport-auto", package: "swift-tools-support-core"), 61 | ]), 62 | .testTarget(name: "CreateXCFrameworkTests", dependencies: [ "CreateXCFramework" ]), 63 | ], 64 | 65 | swiftLanguageVersions: [ 66 | .v5 67 | ] 68 | ) 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # swift-create-xcframework 2 | 3 | >[!IMPORTANT] 4 | >This project is **unmaintained**. It is recommended that you use a fork like [segment-integrations/swift-create-xcframework](https://github.com/segment-integrations/swift-create-xcframework) that includes support for Xcode 15. 5 | 6 | swift-create-xcframework is a very simple tool designed to wrap `xcodebuild` and the process of creating multiple frameworks for a Swift Package and merging them into a single XCFramework. 7 | 8 | On the 23rd of June 2020, Apple announced Xcode 12 and Swift 5.3 with support for Binary Targets. Though they provide a simplified way to [include Binary Frameworks in your packages][apple-docs], they did not provide a simple way to create your XCFrameworks, with only some [documentation for the long manual process][manual-docs]. swift-create-xcframework bridges that gap. 9 | 10 | **Note:** swift-create-xcframework pre-dates the WWDC20 announcement and is tested with Xcode 11.4 or later, but should work with Xcode 11.2 or later. You can include the generated XCFrameworks in your app manually even without Xcode 12. 11 | 12 | ## Usage 13 | 14 | Inside your Swift Package folder you can just run: 15 | 16 | ```shell 17 | swift create-xcframework 18 | ``` 19 | 20 | By default swift-create-xcframework will build XCFrameworks for all library products in your Package.swift, or any targets you specify on the command line (this can be for any dependencies you include as well). 21 | 22 | Then for every target or product specified, swift-create-xcframework will: 23 | 24 | 1. Generate an Xcode Project for your package (in `.build/swift-create-xcframework`). 25 | 2. Build a `.framework` for each supported platform/SDK. 26 | 3. Merge the SDK-specific framework into an XCFramework using `xcodebuild -create-xcframework`. 27 | 4. Optionally package it up into a zip file ready for a GitHub release. 28 | 29 | This process mirrors the [official documentation][manual-docs]. 30 | 31 | ## Choosing what to build 32 | 33 | Let's use an example `Package.swift`: 34 | 35 | ```swift 36 | var package = Package( 37 | name: "example-generator", 38 | platforms: [ 39 | .ios(.v12), 40 | .macos(.v10_12) 41 | ], 42 | products: [ 43 | .library( 44 | name: "ExampleGenerator", 45 | targets: [ "ExampleGenerator" ]), 46 | ], 47 | dependencies: [], 48 | targets: [ 49 | ... 50 | ] 51 | ) 52 | ``` 53 | 54 | By default swift-create-xcframework will build `ExampleGenerator.xcframework` that supports: macosx, iphoneos, iphonesimulator. Additional `.library` products would be built automatically as well. 55 | 56 | ### Choosing Platforms 57 | 58 | You can narrow down what gets built 59 | If you omit the platforms specification, we'll build for all platforms that support Swift Binary Frameworks, which at the time of writing is just the Apple SDKs: macosx, iphoneos, iphonesimulator, watchos, watchsimulator, appletvos, appletvsimulator. 60 | 61 | **Note:** Because only Apple's platforms are supported at this time, swift-create-xcframework will ignore Linux and other platforms in the Package.swift. 62 | 63 | You can specify a subset of the platforms to build using the `--platform` option, for example: 64 | 65 | ```shell 66 | swift create-xcframework --platform ios --platform macos ... 67 | ``` 68 | 69 | #### Catalyst 70 | 71 | You can build your XCFrameworks with support for Mac Catalyst by specifying `--platform maccatalyst` on the command line. As you can't include or exclude Catalyst support in your `Package.swift` we don't try to build it automatically. 72 | 73 | ### Choosing Products 74 | 75 | Because we wrap `xcodebuild`, you can actually build XCFrameworks from anything that will be mapped to an Xcode project as a Framework target. This includes all of the dependencies your Package has. 76 | 77 | To see whats available: 78 | 79 | ```shell 80 | swift create-xcframework --list-products 81 | ``` 82 | 83 | And then to choose what to build: 84 | 85 | ```shell 86 | swift create-xcframework Target1 Target2 Target3... 87 | ``` 88 | 89 | By default it builds all top-level library products in your Package.swift. 90 | 91 | ## Command Line Options 92 | 93 | Because of the low-friction to adding command line options with [swift-argument-parser](https://github.com/apple/swift-argument-parser), there are a number of useful command line options available, so `--help` should be your first port of call. 94 | 95 | ## Packaging for distribution 96 | 97 | swift-create-xcframework provides a `--zip` option to automatically zip up your newly created XCFrameworks ready for upload to GitHub as a release artefact, or anywhere you choose. 98 | 99 | If the target you are creating an XCFramework happens to be a dependency, swift-create-xcframework will look back into the package graph, locate the version that dependency resolved to, and append the version number to your zip file name. eg: `ArgumentParser-0.0.6.zip` 100 | 101 | If the target you are creating is a product from the root package, unfortunately there is no standard way to identify the version number. For those cases you can specify one with `--zip-version`. 102 | 103 | Because you're probably wanting to [distribute your binary frameworks as Swift Packages][apple-docs] `swift create-xcframework --zip` will also calculate the necessary SHA256 checksum and place it alongside the zip. eg: `ArgumentParser-0.0.6.sha256`. 104 | 105 | ## GitHub Action 106 | 107 | swift-create-xcframework includes a GitHub Action that can kick off and automatically create an XCFramework when you tag a release in your project. 108 | 109 | The action produces one zipped XCFramework and checksum artifact for every target specified. 110 | 111 | **Note:** You MUST use a macOS-based runner (such as `macos-latest`) as xcodebuild doesn't run on Linux. 112 | 113 | You can then take those artifacts and add them to your release. 114 | 115 | An incomplete example: 116 | 117 | ### .github/workflows/create-release.yml 118 | 119 | ```yaml 120 | name: Create Release 121 | 122 | # Create XCFramework when a version is tagged 123 | on: 124 | push: 125 | tags: 126 | 127 | jobs: 128 | create_release: 129 | name: Create Release 130 | runs-on: macos-latest 131 | steps: 132 | 133 | - uses: actions/checkout@v2 134 | 135 | - name: Create XCFramework 136 | uses: unsignedapps/swift-create-xcframework@v2 137 | 138 | # Create a release 139 | # Upload those artifacts to the release 140 | ``` 141 | 142 | ## Installation 143 | 144 | You can install using mint: 145 | 146 | ```shell 147 | mint install unsignedapps/swift-create-xcframework 148 | ``` 149 | 150 | Or manually: 151 | 152 | ```shell 153 | git clone https://github.com/unsignedapps/swift-create-xcframework.git 154 | cd swift-create-xcframework 155 | make install 156 | ``` 157 | 158 | Either should pop the swift-create-xcframework binary into `/usr/local/bin`. And because the `swift` binary is extensible, you can then call it as a subcommand of `swift` itself: 159 | 160 | ```shell 161 | swift create-xcframework --help 162 | ``` 163 | 164 | ## Contributing 165 | 166 | Please read the [Contribution Guide](CONTRIBUTING.md) for details on how to contribute to this project. 167 | 168 | ## License 169 | 170 | swift-create-xcframework is available under the MIT license. See the [LICENSE](LICENSE) file for more info. 171 | 172 | [apple-docs]: https://developer.apple.com/documentation/swift_packages/distributing_binary_frameworks_as_swift_packages 173 | [manual-docs]: https://help.apple.com/xcode/mac/11.4/#/dev544efab96 174 | -------------------------------------------------------------------------------- /Sources/CreateXCFramework/BuildSetting.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BuildSetting.swift 3 | // swift-create-xcframework 4 | // 5 | // Created by Dalton Claybrook on 4/17/21. 6 | // 7 | 8 | import ArgumentParser 9 | 10 | /// A representation of a build setting in an Xcode project, e.g. 11 | /// `IPHONEOS_DEPLOYMENT_TARGET=13.0` 12 | struct BuildSetting: ExpressibleByArgument { 13 | /// The name of the build setting, e.g. `IPHONEOS_DEPLOYMENT_TARGET` 14 | let name: String 15 | /// The value of the build setting 16 | let value: String 17 | 18 | init?(argument: String) { 19 | let components = argument.components(separatedBy: "=") 20 | guard components.count == 2 else { return nil } 21 | self.name = components[0].trimmingCharacters(in: .whitespacesAndNewlines) 22 | self.value = components[1].trimmingCharacters(in: .whitespacesAndNewlines) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/CreateXCFramework/Command+Options.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Command+Options.swift 3 | // swift-create-xcframework 4 | // 5 | // Created by Rob Amos on 7/5/20. 6 | // 7 | 8 | import ArgumentParser 9 | import PackageModel 10 | 11 | extension Command { 12 | struct Options: ParsableArguments { 13 | 14 | // MARK: - Package Loading 15 | 16 | @Option(help: ArgumentHelp("The location of the Package", valueName: "directory")) 17 | var packagePath = "." 18 | 19 | 20 | // MARK: - Building 21 | 22 | @Option(help: ArgumentHelp("The location of the build/cache directory to use", valueName: "directory")) 23 | var buildPath = ".build" 24 | 25 | @Option(help: ArgumentHelp("Build with a specific configuration", valueName: "debug|release")) 26 | var configuration = PackageModel.BuildConfiguration.release 27 | 28 | @Flag(inversion: .prefixedNo, help: "Whether to clean before we build") 29 | var clean = true 30 | 31 | @Flag(inversion: .prefixedNo, help: "Whether to include debug symbols in the built XCFramework") 32 | var debugSymbols = true 33 | 34 | @Flag(help: "Prints the available products and targets") 35 | var listProducts = false 36 | 37 | @Option(help: "The path to a .xcconfig file that can be used to override Xcode build settings. Relative to the package path.") 38 | var xcconfig: String? 39 | 40 | @Flag(help: "Enables Library Evolution for the whole build stack. Normally we apply it only to the targets listed to be built to work around issues with projects that don't support it.") 41 | var stackEvolution: Bool = false 42 | 43 | @Option(help: ArgumentHelp("Arbitrary Xcode build settings that are passed directly to the `xcodebuild` invocation. Can be specified multiple times.", valueName: "NAME=VALUE")) 44 | var xcSetting: [BuildSetting] = [] 45 | 46 | 47 | // MARK: - Output Options 48 | 49 | @Option ( 50 | help: ArgumentHelp ( 51 | "A list of platforms you want to build for. Can be specified multiple times." 52 | + " Default is to build for all platforms supported in your Package.swift, or all Apple platforms (except for maccatalyst platform) if omitted", 53 | valueName: TargetPlatform.allCases.map({ $0.rawValue }).joined(separator: "|") 54 | ) 55 | ) 56 | var platform: [TargetPlatform] = [] 57 | 58 | @Option(help: ArgumentHelp("Where to place the compiled .xcframework(s)", valueName: "directory")) 59 | var output = "." 60 | 61 | @Flag(help: "Whether to wrap the .xcframework(s) up in a versioned zip file ready for deployment") 62 | var zip = false 63 | 64 | @Option ( 65 | help: ArgumentHelp ( 66 | "The version number to append to the name of the zip file\n\nIf the target you are packaging is a dependency," 67 | + " swift-create-xcframework will look into the package graph and locate the version number the dependency resolved to." 68 | + " As there is no standard way to specify the version inside your Swift Package, --zip-version lets you specify it manually.", 69 | valueName: "version" 70 | ) 71 | ) 72 | var zipVersion: String? 73 | 74 | @Flag(help: .hidden) 75 | var githubAction: Bool = false 76 | 77 | 78 | // MARK: - Targets 79 | 80 | @Argument(help: "An optional list of products (or targets) to build. Defaults to building all `.library` products") 81 | var products: [String] = [] 82 | } 83 | } 84 | 85 | 86 | // MARK: - ParsableArguments Extensions 87 | 88 | extension PackageModel.BuildConfiguration: ExpressibleByArgument {} 89 | -------------------------------------------------------------------------------- /Sources/CreateXCFramework/Command.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Command.swift 3 | // swift-create-xcframework 4 | // 5 | // Created by Rob Amos on 7/5/20. 6 | // 7 | 8 | import ArgumentParser 9 | import Foundation 10 | import PackageLoading 11 | import PackageModel 12 | import TSCBasic 13 | import Workspace 14 | import Xcodeproj 15 | 16 | struct Command: ParsableCommand { 17 | 18 | // MARK: - Configuration 19 | 20 | static var configuration = CommandConfiguration ( 21 | abstract: "Creates an XCFramework out of a Swift Package using xcodebuild", 22 | discussion: 23 | """ 24 | Note that Swift Binary Frameworks (XCFramework) support is only available in Swift 5.1 25 | or newer, and so it is only supported by recent versions of Xcode and the *OS SDKs. Likewise, 26 | only Apple platforms are supported. 27 | 28 | Supported platforms: \(TargetPlatform.allCases.map({ $0.rawValue }).joined(separator: ", ")) 29 | """, 30 | version: "2.3.0" 31 | ) 32 | 33 | 34 | // MARK: - Arguments 35 | 36 | @OptionGroup() 37 | var options: Options 38 | 39 | 40 | // MARK: - Execution 41 | 42 | // swiftlint:disable:next function_body_length 43 | func run() throws { 44 | 45 | // load all/validate of the package info 46 | let package = try PackageInfo(options: self.options) 47 | 48 | // validate that package to make sure we can generate it 49 | let validation = package.validationErrors() 50 | if validation.isEmpty == false { 51 | for error in validation { 52 | print((error.isFatal ? "Error:" : "Warning:"), error.errorDescription!) 53 | } 54 | if validation.contains(where: { $0.isFatal }) { 55 | Darwin.exit(1) 56 | } 57 | } 58 | 59 | // generate the Xcode project file 60 | let generator = ProjectGenerator(package: package) 61 | 62 | let platforms = try package.supportedPlatforms() 63 | 64 | // get what we're building 65 | try generator.writeDistributionXcconfig() 66 | let project = try generator.generate() 67 | 68 | // printing packages? 69 | if self.options.listProducts { 70 | package.printAllProducts(project: project) 71 | Darwin.exit(0) 72 | } 73 | 74 | // get valid packages and their SDKs 75 | let productNames = try package.validProductNames(project: project) 76 | let sdks = platforms.flatMap { $0.sdks } 77 | 78 | // we've applied the xcconfig to everything, but some dependencies (*cough* swift-nio) 79 | // have build errors, so we remove it from targets we're not building 80 | if self.options.stackEvolution == false { 81 | try project.enableDistribution(targets: productNames, xcconfig: AbsolutePath(package.distributionBuildXcconfig.path).relative(to: AbsolutePath(package.rootDirectory.path))) 82 | } 83 | 84 | // save the project 85 | try project.save(to: generator.projectPath) 86 | 87 | // start building 88 | let builder = XcodeBuilder(project: project, projectPath: generator.projectPath, package: package, options: self.options) 89 | 90 | // clean first 91 | if self.options.clean { 92 | try builder.clean() 93 | } 94 | 95 | // all of our targets for each platform, then group the resulting .frameworks by target 96 | var frameworkFiles: [String: [XcodeBuilder.BuildResult]] = [:] 97 | 98 | for sdk in sdks { 99 | try builder.build(targets: productNames, sdk: sdk) 100 | .forEach { pair in 101 | if frameworkFiles[pair.key] == nil { 102 | frameworkFiles[pair.key] = [] 103 | } 104 | frameworkFiles[pair.key]?.append(pair.value) 105 | } 106 | } 107 | 108 | var xcframeworkFiles: [(String, Foundation.URL)] = [] 109 | 110 | // then we merge the resulting frameworks 111 | try frameworkFiles 112 | .forEach { pair in 113 | xcframeworkFiles.append((pair.key, try builder.merge(target: pair.key, buildResults: pair.value))) 114 | } 115 | 116 | // zip it up if thats what they want 117 | if self.options.zip { 118 | let zipper = Zipper(package: package) 119 | let zipped = try xcframeworkFiles 120 | .flatMap { pair -> [Foundation.URL] in 121 | let zip = try zipper.zip(target: pair.0, version: self.options.zipVersion, file: pair.1) 122 | let checksum = try zipper.checksum(file: zip) 123 | try zipper.clean(file: pair.1) 124 | 125 | return [ zip, checksum ] 126 | } 127 | 128 | // notify the action if we have one 129 | if self.options.githubAction { 130 | let zips = zipped.map({ $0.path }).joined(separator: "\n") 131 | let data = Data(zips.utf8) 132 | let url = Foundation.URL(fileURLWithPath: self.options.buildPath).appendingPathComponent("xcframework-zipfile.url") 133 | try data.write(to: url) 134 | } 135 | 136 | } 137 | } 138 | } 139 | 140 | 141 | // MARK: - Errors 142 | 143 | private enum Error: Swift.Error, LocalizedError { 144 | case noProducts 145 | 146 | var errorDescription: String? { 147 | switch self { 148 | case .noProducts: return "" 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /Sources/CreateXCFramework/Extensions/Collection+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Collection-Extensions.swift 3 | // swift-create-xcframework 4 | // 5 | // Created by Rob Amos on 9/5/20. 6 | // 7 | 8 | extension Collection { 9 | var nonEmpty: Self? { 10 | return self.isEmpty ? nil : self 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/CreateXCFramework/Extensions/PackageModel+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PackageDescription+Extensions.swift 3 | // swift-create-xcframework 4 | // 5 | // Created by Rob Amos on 7/5/20. 6 | // 7 | 8 | import PackageModel 9 | 10 | extension ProductType { 11 | var isLibrary: Bool { 12 | if case .library = self { 13 | return true 14 | } 15 | return false 16 | } 17 | } 18 | 19 | extension Manifest { 20 | var libraryProductNames: [String] { 21 | return self.products 22 | .compactMap { product in 23 | guard product.type.isLibrary else { return nil } 24 | return product.name 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/CreateXCFramework/Extensions/Xcodeproj+ProductValidation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Xcodeproj+ProductValidation.swift 3 | // swift-create-xcframeworks 4 | // 5 | // Created by Rob Amos on 8/5/20. 6 | // 7 | 8 | import Foundation 9 | import Xcodeproj 10 | 11 | extension Xcode.Project { 12 | var frameworkTargets: [Xcode.Target] { 13 | targets.filter { $0.productType == .framework } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/CreateXCFramework/Platforms.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Constants.swift 3 | // swift-create-xcframework 4 | // 5 | // Created by Rob Amos on 9/5/20. 6 | // 7 | 8 | import ArgumentParser 9 | import PackageModel 10 | 11 | enum TargetPlatform: String, ExpressibleByArgument, CaseIterable { 12 | case ios 13 | case macos 14 | case maccatalyst 15 | case tvos 16 | case watchos 17 | 18 | init?(argument: String) { 19 | self.init(rawValue: argument.lowercased()) 20 | } 21 | 22 | 23 | var platformName: String { 24 | switch self { 25 | case .ios: return "ios" 26 | case .macos: return "macos" 27 | case .maccatalyst: return "macos" 28 | case .tvos: return "tvos" 29 | case .watchos: return "watchos" 30 | } 31 | } 32 | 33 | // MARK: - Target SDKs 34 | 35 | struct SDK { 36 | let destination: String 37 | let archiveName: String 38 | let releaseFolder: String 39 | let buildSettings: [String: String]? 40 | } 41 | 42 | var sdks: [SDK] { 43 | switch self { 44 | case .ios: 45 | return [ 46 | SDK ( 47 | destination: "generic/platform=iOS", 48 | archiveName: "iphoneos.xcarchive", 49 | releaseFolder: "Release-iphoneos", 50 | buildSettings: nil 51 | ), 52 | SDK ( 53 | destination: "generic/platform=iOS Simulator", 54 | archiveName: "iphonesimulator.xcarchive", 55 | releaseFolder: "Release-iphonesimulator", 56 | buildSettings: nil 57 | ) 58 | ] 59 | 60 | case .macos: 61 | return [ 62 | SDK ( 63 | destination: "generic/platform=macOS,name=Any Mac", 64 | archiveName: "macos.xcarchive", 65 | releaseFolder: "Release", 66 | buildSettings: nil 67 | ) 68 | ] 69 | 70 | case .maccatalyst: 71 | return [ 72 | SDK ( 73 | destination: "generic/platform=macOS,variant=Mac Catalyst", 74 | archiveName: "maccatalyst.xcarchive", 75 | releaseFolder: "Release-maccatalyst", 76 | buildSettings: [ "SUPPORTS_MACCATALYST": "YES" ] 77 | ) 78 | ] 79 | 80 | case .tvos: 81 | return [ 82 | SDK ( 83 | destination: "generic/platform=tvOS", 84 | archiveName: "appletvos.xcarchive", 85 | releaseFolder: "Release-appletvos", 86 | buildSettings: nil 87 | ), 88 | SDK ( 89 | destination: "generic/platform=tvOS Simulator", 90 | archiveName: "appletvsimulator.xcarchive", 91 | releaseFolder: "Release-appletvsimulator", 92 | buildSettings: nil 93 | ) 94 | ] 95 | 96 | case .watchos: 97 | return [ 98 | SDK ( 99 | destination: "generic/platform=watchOS", 100 | archiveName: "watchos.xcarchive", 101 | releaseFolder: "Release-watchos", 102 | buildSettings: nil 103 | ), 104 | SDK ( 105 | destination: "generic/platform=watchOS Simulator", 106 | archiveName: "watchsimulator.xcarchive", 107 | releaseFolder: "Release-watchsimulator", 108 | buildSettings: nil 109 | ) 110 | ] 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /Sources/CreateXCFramework/ProjectGenerator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProjectGenerator.swift 3 | // swift-create-xcframework 4 | // 5 | // Created by Rob Amos on 7/5/20. 6 | // 7 | 8 | import Foundation 9 | import TSCBasic 10 | import TSCUtility 11 | import Xcodeproj 12 | 13 | struct ProjectGenerator { 14 | 15 | private enum Constants { 16 | static let `extension` = "xcodeproj" 17 | } 18 | 19 | 20 | // MARK: - Properties 21 | 22 | let package: PackageInfo 23 | 24 | var projectPath: AbsolutePath { 25 | let dir = AbsolutePath(self.package.projectBuildDirectory.path) 26 | #if swift(>=5.7) 27 | return XcodeProject.makePath(outputDir: dir, projectName: self.package.manifest.displayName) 28 | #else 29 | return buildXcodeprojPath(outputDir: dir, projectName: self.package.manifest.displayName) 30 | #endif 31 | } 32 | 33 | 34 | // MARK: - Initialisation 35 | 36 | init (package: PackageInfo) { 37 | self.package = package 38 | } 39 | 40 | 41 | // MARK: - Generation 42 | 43 | /// Writes out the Xcconfig file 44 | func writeDistributionXcconfig () throws { 45 | guard self.package.hasDistributionBuildXcconfig else { 46 | return 47 | } 48 | 49 | try makeDirectories(self.projectPath) 50 | 51 | let path = AbsolutePath(self.package.distributionBuildXcconfig.path) 52 | try open(path) { stream in 53 | if let absolutePath = self.package.overridesXcconfig?.path { 54 | stream ( 55 | """ 56 | #include "\(AbsolutePath(absolutePath).relative(to: AbsolutePath(path.dirname)).pathString)" 57 | 58 | """ 59 | ) 60 | } 61 | 62 | stream ( 63 | """ 64 | BUILD_LIBRARY_FOR_DISTRIBUTION=YES 65 | """ 66 | ) 67 | } 68 | } 69 | 70 | /// Generate an Xcode project. 71 | /// 72 | /// This is basically a copy of Xcodeproj.generate() 73 | /// 74 | func generate () throws -> Xcode.Project { 75 | let path = self.projectPath 76 | try makeDirectories(path) 77 | 78 | // Generate the contents of project.xcodeproj (inside the .xcodeproj). 79 | #if swift(>=5.6) 80 | let project = try pbxproj ( 81 | xcodeprojPath: path, 82 | graph: self.package.graph, 83 | extraDirs: [], 84 | extraFiles: [], 85 | options: XcodeprojOptions ( 86 | xcconfigOverrides: (self.package.overridesXcconfig?.path).flatMap { AbsolutePath($0) }, 87 | useLegacySchemeGenerator: true 88 | ), 89 | fileSystem: localFileSystem, 90 | observabilityScope: self.package.observabilitySystem.topScope 91 | ) 92 | #else 93 | let project = try pbxproj ( 94 | xcodeprojPath: path, 95 | graph: self.package.graph, 96 | extraDirs: [], 97 | extraFiles: [], 98 | options: XcodeprojOptions ( 99 | xcconfigOverrides: (self.package.overridesXcconfig?.path).flatMap { AbsolutePath($0) }, 100 | useLegacySchemeGenerator: true 101 | ), 102 | diagnostics: self.package.diagnostics 103 | ) 104 | #endif 105 | 106 | return project 107 | } 108 | 109 | } 110 | 111 | 112 | // MARK: - Saving Xcode Projects 113 | 114 | extension Xcode.Project { 115 | 116 | /// This is the group that is normally created in Xcodeproj.xcodeProject() when you specify an xcconfigOverride 117 | var configGroup: Xcode.Group { 118 | let name = "Configs" 119 | 120 | if let group = self.mainGroup.subitems.lazy.compactMap({ $0 as? Xcode.Group }).first(where: { $0.name == name }) { 121 | return group 122 | } 123 | 124 | // doesn't exist - lets creat it 125 | return self.mainGroup.addGroup(path: "", name: name) 126 | } 127 | 128 | func enableDistribution (targets: [String], xcconfig: RelativePath) throws { 129 | let group = self.configGroup 130 | let ref = group.addFileReference ( 131 | path: xcconfig.pathString, 132 | name: xcconfig.basename 133 | ) 134 | 135 | for target in self.targets where targets.contains(target.name) { 136 | target.buildSettings.xcconfigFileRef = ref 137 | } 138 | } 139 | 140 | func save (to path: AbsolutePath) throws { 141 | try open(path.appending(component: "project.pbxproj")) { stream in 142 | // Serialize the project model we created to a plist, and return 143 | // its string description. 144 | #if swift(>=5.6) 145 | let str = try "// !$*UTF8*$!\n" + self.generatePlist().description 146 | #else 147 | let str = "// !$*UTF8*$!\n" + self.generatePlist().description 148 | #endif 149 | stream(str) 150 | } 151 | 152 | for target in self.frameworkTargets { 153 | ///// For framework targets, generate target.c99Name_Info.plist files in the 154 | ///// directory that Xcode project is generated 155 | let name = "\(target.name.spm_mangledToC99ExtendedIdentifier())_Info.plist" 156 | try open(path.appending(RelativePath(name))) { print in 157 | print( 158 | """ 159 | 160 | 161 | 162 | CFBundleDevelopmentRegion 163 | en 164 | CFBundleExecutable 165 | $(EXECUTABLE_NAME) 166 | CFBundleIdentifier 167 | $(PRODUCT_BUNDLE_IDENTIFIER) 168 | CFBundleInfoDictionaryVersion 169 | 6.0 170 | CFBundleName 171 | $(PRODUCT_NAME) 172 | CFBundlePackageType 173 | FMWK 174 | CFBundleShortVersionString 175 | 1.0 176 | CFBundleSignature 177 | ???? 178 | CFBundleVersion 179 | $(CURRENT_PROJECT_VERSION) 180 | NSPrincipalClass 181 | 182 | 183 | 184 | """ 185 | ) 186 | } 187 | } 188 | } 189 | } 190 | 191 | /// Writes the contents to the file specified. 192 | /// 193 | /// This method doesn't rewrite the file in case the new and old contents of 194 | /// file are same. 195 | private func open(_ path: AbsolutePath, body: ((String) -> Void) throws -> Void) throws { 196 | let stream = BufferedOutputByteStream() 197 | try body { line in 198 | stream <<< line 199 | stream <<< "\n" 200 | } 201 | // If the file exists with the identical contents, we don't need to rewrite it. 202 | // 203 | // This avoids unnecessarily triggering Xcode reloads of the project file. 204 | if let contents = try? localFileSystem.readFileContents(path), contents == stream.bytes { 205 | return 206 | } 207 | 208 | // Write the real file. 209 | try localFileSystem.writeFileContents(path, bytes: stream.bytes) 210 | } 211 | -------------------------------------------------------------------------------- /Sources/CreateXCFramework/Zipper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Zipper.swift 3 | // swift-create-xcframework 4 | // 5 | // Created by Rob Amos on 13/5/20. 6 | // 7 | 8 | #if canImport(Basics) 9 | import Basics 10 | #endif 11 | import Foundation 12 | #if swift(>=5.6) 13 | import PackageGraph 14 | #endif 15 | import PackageModel 16 | import TSCBasic 17 | import Workspace 18 | 19 | struct Zipper { 20 | 21 | // MARK: - Properties 22 | 23 | let package: PackageInfo 24 | 25 | init (package: PackageInfo) { 26 | self.package = package 27 | } 28 | 29 | 30 | // MARK: - Zippering 31 | 32 | func zip (target: String, version: String?, file: Foundation.URL) throws -> Foundation.URL { 33 | 34 | let suffix = self.versionSuffix(target: target, default: version) ?? "" 35 | let zipPath = file.path.replacingOccurrences(of: "\\.xcframework$", with: "\(suffix).zip", options: .regularExpression) 36 | let zipURL = URL(fileURLWithPath: zipPath) 37 | 38 | let process = TSCBasic.Process ( 39 | arguments: self.zipCommand(source: file, target: zipURL), 40 | outputRedirection: .none 41 | ) 42 | 43 | print("\nPackaging \(file.path) into \(zipURL.path)\n\n") 44 | try process.launch() 45 | let result = try process.waitUntilExit() 46 | 47 | switch result.exitStatus { 48 | case let .terminated(code: code): 49 | if code != 0 { 50 | throw XcodeBuilder.Error.nonZeroExit("ditto", code) 51 | } 52 | case let .signalled(signal: signal): 53 | throw XcodeBuilder.Error.signalExit("ditto", signal) 54 | } 55 | 56 | return zipURL 57 | } 58 | 59 | func checksum (file: Foundation.URL) throws -> Foundation.URL { 60 | #if swift(>=5.7) 61 | let sum = try checksum(forBinaryArtifactAt: AbsolutePath(file.path)) 62 | #elseif swift(>=5.6) 63 | let sum = try self.package.workspace.checksum(forBinaryArtifactAt: AbsolutePath(file.path)) 64 | #else 65 | let sum = self.package.workspace.checksum(forBinaryArtifactAt: AbsolutePath(file.path), diagnostics: self.package.diagnostics) 66 | #endif 67 | let checksumFile = file.deletingPathExtension().appendingPathExtension("sha256") 68 | try Data(sum.utf8).write(to: checksumFile) 69 | return checksumFile 70 | } 71 | 72 | private func zipCommand (source: Foundation.URL, target: Foundation.URL) -> [String] { 73 | return [ 74 | "ditto", 75 | "-c", 76 | "-k", 77 | "--keepParent", 78 | source.path, 79 | target.path 80 | ] 81 | } 82 | 83 | private func versionSuffix (target: String, default fallback: String?) -> String? { 84 | 85 | // find the package that contains our target 86 | guard let packageRef = self.package.graph.packages.first(where: { $0.targets.contains(where: { $0.name == target }) }) else { return nil } 87 | 88 | #if swift(>=5.6) 89 | guard 90 | let dependency = self.package.workspace.state.dependencies[packageRef.identity], 91 | case let .custom(version, _) = dependency.state 92 | else { 93 | return fallback.flatMap { "-" + $0 } 94 | } 95 | #else 96 | guard 97 | let dependency = self.package.workspace.state.dependencies[forNameOrIdentity: packageRef.packageName], 98 | case let .checkout(checkout) = dependency.state, 99 | let version = checkout.version 100 | else { 101 | return fallback.flatMap { "-" + $0 } 102 | } 103 | #endif 104 | 105 | return "-" + version.description 106 | } 107 | 108 | 109 | // MARK: - Cleaning 110 | 111 | func clean (file: Foundation.URL) throws { 112 | try FileManager.default.removeItem(at: file) 113 | } 114 | 115 | #if swift(>=5.7) 116 | private func checksum(forBinaryArtifactAt path: AbsolutePath) throws -> String { 117 | let fileSystem = localFileSystem 118 | let checksumAlgorithm = SHA256() 119 | let archiver = ZipArchiver(fileSystem: fileSystem) 120 | 121 | // Validate the path has a supported extension. 122 | guard let pathExtension = path.extension, archiver.supportedExtensions.contains(pathExtension) else { 123 | let supportedExtensionList = archiver.supportedExtensions.joined(separator: ", ") 124 | throw StringError("unexpected file type; supported extensions are: \(supportedExtensionList)") 125 | } 126 | 127 | // Ensure that the path with the accepted extension is a file. 128 | guard fileSystem.isFile(path) else { 129 | throw StringError("file not found at path: \(path.pathString)") 130 | } 131 | 132 | let contents = try fileSystem.readFileContents(path) 133 | return checksumAlgorithm.hash(contents).hexadecimalRepresentation 134 | } 135 | #endif 136 | } 137 | 138 | #if swift(>=5.6) 139 | // Intentionally left blank 140 | #elseif swift(>=5.5) 141 | private extension ResolvedPackage { 142 | var packageName: String { 143 | self.manifestName 144 | } 145 | } 146 | #else 147 | private extension ResolvedPackage { 148 | var packageName: String { 149 | self.name 150 | } 151 | } 152 | #endif 153 | -------------------------------------------------------------------------------- /Sources/CreateXCFramework/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // swift-create-xcframework 4 | // 5 | // Created by Rob Amos on 7/5/20. 6 | // 7 | 8 | Command.main() 9 | -------------------------------------------------------------------------------- /Tests/CreateXCFrameworkTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(swift_create_frameworkTests.allTests), 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /Tests/CreateXCFrameworkTests/swift_create_frameworkTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import class Foundation.Bundle 3 | 4 | final class swift_create_frameworkTests: XCTestCase { 5 | func testExample() throws { 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 | 10 | // Some of the APIs that we use below are available in macOS 10.13 and above. 11 | guard #available(macOS 10.13, *) else { 12 | return 13 | } 14 | 15 | let fooBinary = productsDirectory.appendingPathComponent("swift-create-xcframework") 16 | 17 | let process = Process() 18 | process.executableURL = fooBinary 19 | 20 | let pipe = Pipe() 21 | process.standardOutput = pipe 22 | 23 | try process.run() 24 | process.waitUntilExit() 25 | 26 | let data = pipe.fileHandleForReading.readDataToEndOfFile() 27 | let output = String(data: data, encoding: .utf8) 28 | 29 | XCTAssertEqual(output, "Hello, world!\n") 30 | } 31 | 32 | /// Returns path to the built products directory. 33 | var productsDirectory: URL { 34 | #if os(macOS) 35 | for bundle in Bundle.allBundles where bundle.bundlePath.hasSuffix(".xctest") { 36 | return bundle.bundleURL.deletingLastPathComponent() 37 | } 38 | fatalError("couldn't find the products directory") 39 | #else 40 | return Bundle.main.bundleURL 41 | #endif 42 | } 43 | 44 | static var allTests = [ 45 | ("testExample", testExample), 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import swift_create_frameworkTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += swift_create_frameworkTests.allTests() 7 | XCTMain(tests) 8 | -------------------------------------------------------------------------------- /action.js: -------------------------------------------------------------------------------- 1 | const core = require('./.action/core') 2 | const exec = require('./.action/exec') 3 | const path = require('path') 4 | const artifact = require('././.action/artifact') 5 | const fs = require('fs') 6 | 7 | const scxVersion = 'v2.3.0' 8 | const outputPath = '.build/xcframework-zipfile.url' 9 | 10 | core.setCommandEcho(true) 11 | 12 | async function run() { 13 | try { 14 | let packagePath = core.getInput('path', { required: false }) 15 | let targets = core.getInput('target', { required: false }) 16 | let configuration = core.getInput('configuration', { required: false }) 17 | let platforms = core.getInput('platforms', { required: false }) 18 | let xcconfig = core.getInput('xcconfig', { required: false }) 19 | 20 | // install mint if its not installed 21 | await installUsingBrewIfRequired("mint") 22 | 23 | // install ourselves if not installed 24 | await installUsingMintIfRequired('swift-create-xcframework', 'unsignedapps/swift-create-xcframework') 25 | 26 | // put together our options 27 | var options = ['--zip', '--github-action'] 28 | if (!!packagePath) { 29 | options.push('--package-path') 30 | options.push(packagePath) 31 | } 32 | 33 | if (!!configuration) { 34 | options.push('--configuration') 35 | options.push(configuration) 36 | } 37 | 38 | if (!!xcconfig) { 39 | options.push('--xcconfig') 40 | options.push(xcconfig) 41 | } 42 | 43 | if (!!platforms) { 44 | platforms 45 | .split(',') 46 | .map((p) => p.trim()) 47 | .forEach((platform) => { 48 | options.push('--platform') 49 | options.push(platform) 50 | }) 51 | } 52 | 53 | if (!targets) { 54 | targets 55 | .split(',') 56 | .map((t) => t.trim()) 57 | .filter((t) => t.length > 0) 58 | .forEach((target) => { 59 | options.push(target) 60 | }) 61 | } 62 | 63 | await runUsingMint('swift-create-xcframework', options) 64 | 65 | let client = artifact.create() 66 | let files = fs.readFileSync(outputPath, { encoding: 'utf8' }) 67 | .split('\n') 68 | .map((file) => file.trim()) 69 | 70 | for (var i = 0, c = files.length; i < c; i++) { 71 | let file = files[i] 72 | let name = path.basename(file) 73 | await client.uploadArtifact(name, [file], path.dirname(file)) 74 | } 75 | 76 | } catch (error) { 77 | core.setFailed(error) 78 | } 79 | } 80 | 81 | async function installUsingBrewIfRequired(package) { 82 | if (await isInstalled(package)) { 83 | core.info(package + " is already installed.") 84 | 85 | } else { 86 | core.info("Installing " + package) 87 | await exec.exec('brew', ['install', package]) 88 | } 89 | } 90 | 91 | async function installUsingMintIfRequired(command, package) { 92 | if (await isInstalled(command)) { 93 | core.info(command + " is already installed") 94 | 95 | } else { 96 | core.info("Installing " + package) 97 | await exec.exec('mint', ['install', 'unsignedapps/swift-create-xcframework@' + scxVersion]) 98 | } 99 | } 100 | 101 | async function isInstalled(command) { 102 | return await exec.exec('which', [command], { silent: true, failOnStdErr: false, ignoreReturnCode: true }) == 0 103 | } 104 | 105 | async function runUsingMint(command, options) { 106 | await exec.exec('mint', ['run', command, ...options]) 107 | } 108 | 109 | run() 110 | 111 | 112 | // Kuroneko:swift-create-xcframework bok$ swift create-xcframework --help 113 | // OVERVIEW: Creates an XCFramework out of a Swift Package using xcodebuild 114 | 115 | // Note that Swift Binary Frameworks (XCFramework) support is only available in Swift 5.1 116 | // or newer, and so it is only supported by recent versions of Xcode and the *OS SDKs. Likewise, 117 | // only Apple pplatforms are supported. 118 | 119 | // Supported platforms: ios, macos, tvos, watchos 120 | 121 | // USAGE: command [--package-path ] [--build-path ] [--configuration ] [--clean] [--no-clean] [--list-products] [--platform ...] [--output ] [--zip] [--zip-version ] [ ...] 122 | 123 | // ARGUMENTS: 124 | // An optional list of products (or targets) to build. Defaults to building all `.library` products 125 | 126 | // OPTIONS: 127 | // --package-path 128 | // The location of the Package (default: .) 129 | // --build-path 130 | // The location of the build/cache directory to use (default: .build) 131 | // --configuration 132 | // Build with a specific configuration (default: release) 133 | // --clean/--no-clean Whether to clean before we build (default: true) 134 | // --list-products Prints the available products and targets 135 | // --platform 136 | // A list of platforms you want to build for. Can be specified multiple times. Default is to build for all platforms supported in your 137 | // Package.swift, or all Apple platforms if omitted 138 | // --output Where to place the compiled .xcframework(s) (default: .) 139 | // --zip Whether to wrap the .xcframework(s) up in a versioned zip file ready for deployment 140 | // --zip-version The version number to append to the name of the zip file 141 | 142 | // If the target you are packaging is a dependency, swift-create-xcframework will look into the package graph and locate the version 143 | // number the dependency resolved to. As there is no standard way to specify the version inside your Swift Package, --zip-version lets 144 | // you specify it manually. 145 | // --version Show the version. 146 | // -h, --help Show help information. 147 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: "swift-create-xcframework" 2 | description: "Creates XCFramework bundles for the products in your Swift Package and uploads them as Artifacts you can attach to a release" 3 | 4 | inputs: 5 | target: 6 | description: "The name of the target(s) you want to create an XCFramework bundle for. One artifact will be uploaded for each target. Separate target names with commas." 7 | required: false 8 | platforms: 9 | description: "The platform(s) that you want to build for. Default is to build for all platforms supported in your Package.swift, or all Apple platforms if omited. Comma-delimited string supported." 10 | required: false 11 | configuration: 12 | description: "Build with a specific configuration ('debug' or 'release')" 13 | required: false 14 | default: release 15 | xcconfig: 16 | description: "The path to a .xcconfig file that can be used to override Xcode build settings. Relative to the package path." 17 | required: false 18 | 19 | runs: 20 | using: node12 21 | main: action.js 22 | 23 | branding: 24 | icon: "package" 25 | color: "green" --------------------------------------------------------------------------------