@capgo/capacitor-video-player 8.2.5 → 8.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/src/main/java/com/capgo/videoplayer/VideoPlayerPlugin.java +1 -1
- package/ios/Sources/VideoPlayerPlugin/AVPlayerControlsVisibilityObserver.swift +227 -0
- package/ios/Sources/VideoPlayerPlugin/DismissObservingPlayerViewController.swift +43 -0
- package/ios/Sources/VideoPlayerPlugin/ExitEmissionGuard.swift +11 -0
- package/ios/Sources/VideoPlayerPlugin/FullscreenVideoPlayer.swift +40 -15
- package/ios/Sources/VideoPlayerPlugin/PlayerDismissExitDetection.swift +16 -0
- package/ios/Sources/VideoPlayerPlugin/VideoPlayerCastController.swift +200 -63
- package/ios/Sources/VideoPlayerPlugin/VideoPlayerPlugin.swift +1 -1
- package/ios/Tests/VideoPlayerPluginTests/PlayerDismissExitDetectionTests.swift +49 -0
- package/ios/Tests/VideoPlayerPluginTests/VideoPlayerCastControllerTests.swift +103 -0
- package/ios/Tests/VideoPlayerPluginTests/VideoPlayerPluginTests.swift +9 -11
- package/package.json +4 -2
- package/scripts/check-capacitor-plugin-wiring.mjs +254 -0
- package/scripts/generate-ai-changelog.mjs +135 -0
- package/scripts/test-ios.sh +109 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Capacitor plugin wiring/name checker.
|
|
4
|
+
*
|
|
5
|
+
* Enforces that runtime plugin name matches across:
|
|
6
|
+
* - JS: registerPlugin('Name')
|
|
7
|
+
* - Android: @CapacitorPlugin(name = "Name")
|
|
8
|
+
* - iOS: CAPBridgedPlugin jsName = "Name"
|
|
9
|
+
*
|
|
10
|
+
* And (when iOS is declared in package.json capacitor config):
|
|
11
|
+
* - CocoaPods podspec s.name matches SwiftPM Package(name: ...) and .library(name: ...)
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* node scripts/check-capacitor-plugin-wiring.mjs # checks current working dir
|
|
15
|
+
* node scripts/check-capacitor-plugin-wiring.mjs --dir path # checks given plugin dir
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import fs from "node:fs";
|
|
19
|
+
import path from "node:path";
|
|
20
|
+
|
|
21
|
+
const SKIP_DIRS = new Set([
|
|
22
|
+
"node_modules",
|
|
23
|
+
"dist",
|
|
24
|
+
"build",
|
|
25
|
+
".build",
|
|
26
|
+
".gradle",
|
|
27
|
+
"Pods",
|
|
28
|
+
"DerivedData",
|
|
29
|
+
".swiftpm",
|
|
30
|
+
".git",
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
function readText(p) {
|
|
34
|
+
try {
|
|
35
|
+
return fs.readFileSync(p, "utf8");
|
|
36
|
+
} catch {
|
|
37
|
+
return "";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function exists(p) {
|
|
42
|
+
try {
|
|
43
|
+
fs.accessSync(p);
|
|
44
|
+
return true;
|
|
45
|
+
} catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function walkFiles(rootDir, exts) {
|
|
51
|
+
const out = [];
|
|
52
|
+
const stack = [rootDir];
|
|
53
|
+
while (stack.length) {
|
|
54
|
+
const dir = stack.pop();
|
|
55
|
+
let entries;
|
|
56
|
+
try {
|
|
57
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
58
|
+
} catch {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
for (const e of entries) {
|
|
62
|
+
if (e.isDirectory()) {
|
|
63
|
+
if (SKIP_DIRS.has(e.name)) continue;
|
|
64
|
+
stack.push(path.join(dir, e.name));
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (!e.isFile()) continue;
|
|
68
|
+
for (const ext of exts) {
|
|
69
|
+
if (e.name.endsWith(ext)) {
|
|
70
|
+
out.push(path.join(dir, e.name));
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
out.sort();
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function uniq(arr) {
|
|
81
|
+
const out = [];
|
|
82
|
+
for (const x of arr) {
|
|
83
|
+
if (!x) continue;
|
|
84
|
+
if (!out.includes(x)) out.push(x);
|
|
85
|
+
}
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function parseArgs(argv) {
|
|
90
|
+
const out = { dir: process.cwd() };
|
|
91
|
+
for (let i = 2; i < argv.length; i++) {
|
|
92
|
+
const a = argv[i];
|
|
93
|
+
if (a === "--dir" || a === "--pluginDir") {
|
|
94
|
+
out.dir = path.resolve(argv[++i] || ".");
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const args = parseArgs(process.argv);
|
|
102
|
+
const pluginDir = args.dir;
|
|
103
|
+
const pkgPath = path.join(pluginDir, "package.json");
|
|
104
|
+
|
|
105
|
+
if (!exists(pkgPath)) {
|
|
106
|
+
console.error(`[wiring] ERROR: missing package.json in ${pluginDir}`);
|
|
107
|
+
process.exit(2);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
let pkg;
|
|
111
|
+
try {
|
|
112
|
+
pkg = JSON.parse(readText(pkgPath));
|
|
113
|
+
} catch (e) {
|
|
114
|
+
console.error(`[wiring] ERROR: invalid package.json (${pkgPath}): ${e?.message || e}`);
|
|
115
|
+
process.exit(2);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const cap = typeof pkg.capacitor === "object" && pkg.capacitor ? pkg.capacitor : {};
|
|
119
|
+
const supportsAndroid = typeof cap.android === "object" && cap.android;
|
|
120
|
+
const supportsIos = typeof cap.ios === "object" && cap.ios;
|
|
121
|
+
|
|
122
|
+
// Not a Capacitor plugin package (e.g. meta/workspace package).
|
|
123
|
+
// We only enforce wiring rules for actual plugin packages declaring a `capacitor` config.
|
|
124
|
+
if (!supportsAndroid && !supportsIos) {
|
|
125
|
+
process.exit(0);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ---------------- JS (registerPlugin) ----------------
|
|
129
|
+
const jsSrcDir = path.join(pluginDir, "src");
|
|
130
|
+
let jsName = "";
|
|
131
|
+
if (exists(jsSrcDir)) {
|
|
132
|
+
const jsFiles = walkFiles(jsSrcDir, [".ts", ".js"]);
|
|
133
|
+
const reRegister = /registerPlugin(?:<[^>]*>)?\(\s*['"]([^'"]+)['"]/;
|
|
134
|
+
for (const f of jsFiles) {
|
|
135
|
+
const m = reRegister.exec(readText(f));
|
|
136
|
+
if (m) {
|
|
137
|
+
jsName = m[1];
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ---------------- Android (@CapacitorPlugin) ----------------
|
|
144
|
+
let androidNames = [];
|
|
145
|
+
if (supportsAndroid) {
|
|
146
|
+
const androidMain = path.join(pluginDir, "android", "src", "main");
|
|
147
|
+
const files = walkFiles(androidMain, [".java", ".kt"]);
|
|
148
|
+
const foundAnnotations = [];
|
|
149
|
+
for (const f of files) {
|
|
150
|
+
const txt = readText(f);
|
|
151
|
+
if (!txt.includes("@CapacitorPlugin")) continue;
|
|
152
|
+
foundAnnotations.push(f);
|
|
153
|
+
const m =
|
|
154
|
+
/@CapacitorPlugin\(\s*name\s*=\s*"([^"]+)"/.exec(txt) ||
|
|
155
|
+
/@CapacitorPlugin\(\s*name\s*=\s*([A-Za-z0-9_]+)\b/.exec(txt);
|
|
156
|
+
if (m) androidNames.push(m[1]);
|
|
157
|
+
}
|
|
158
|
+
androidNames = uniq(androidNames);
|
|
159
|
+
|
|
160
|
+
// Enforce explicit name attribute to prevent silent class-name drift.
|
|
161
|
+
if (foundAnnotations.length && !androidNames.length) {
|
|
162
|
+
console.error(
|
|
163
|
+
`[wiring] ERROR: Android has @CapacitorPlugin but none specify name = \"...\". Add an explicit name to the plugin class.`
|
|
164
|
+
);
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ---------------- iOS (jsName) ----------------
|
|
170
|
+
let iosJsNames = [];
|
|
171
|
+
if (supportsIos) {
|
|
172
|
+
const iosDir = path.join(pluginDir, "ios");
|
|
173
|
+
const scanRoot = exists(path.join(iosDir, "Sources")) ? path.join(iosDir, "Sources") : iosDir;
|
|
174
|
+
const swiftFiles = walkFiles(scanRoot, [".swift"]);
|
|
175
|
+
const reJsName = /\bjsName\s*=\s*"([^"]+)"/g;
|
|
176
|
+
for (const f of swiftFiles) {
|
|
177
|
+
const txt = readText(f);
|
|
178
|
+
if (!txt.includes("jsName")) continue;
|
|
179
|
+
let m;
|
|
180
|
+
while ((m = reJsName.exec(txt))) iosJsNames.push(m[1]);
|
|
181
|
+
}
|
|
182
|
+
iosJsNames = uniq(iosJsNames);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ---------------- Podspec/SPM ----------------
|
|
186
|
+
function parsePodspecName(podspecPath) {
|
|
187
|
+
const txt = readText(podspecPath);
|
|
188
|
+
const m = /\bs\.name\s*=\s*'([^']+)'/.exec(txt);
|
|
189
|
+
return m ? m[1] : "";
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function parseSpmNames(packageSwiftPath) {
|
|
193
|
+
const txt = readText(packageSwiftPath);
|
|
194
|
+
const pkg = /Package\(\s*name\s*:\s*"([^"]+)"/.exec(txt)?.[1] || "";
|
|
195
|
+
const libs = [];
|
|
196
|
+
const reLib = /\.library\(\s*name\s*:\s*"([^"]+)"/g;
|
|
197
|
+
let m;
|
|
198
|
+
while ((m = reLib.exec(txt))) libs.push(m[1]);
|
|
199
|
+
return { pkgName: pkg, libNames: uniq(libs) };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ---------------- Validate ----------------
|
|
203
|
+
const errors = [];
|
|
204
|
+
|
|
205
|
+
if (!jsName) {
|
|
206
|
+
errors.push("JS: no registerPlugin('...') found under src/");
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (supportsAndroid) {
|
|
210
|
+
if (!androidNames.length) errors.push("Android: missing @CapacitorPlugin(name = \"...\")");
|
|
211
|
+
if (jsName && androidNames.length && androidNames.some((n) => n !== jsName)) {
|
|
212
|
+
errors.push(`Android: @CapacitorPlugin(name)=${JSON.stringify(androidNames)} != JS registerPlugin=${jsName}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (supportsIos) {
|
|
217
|
+
if (!iosJsNames.length) errors.push('iOS: missing jsName = "..." in Swift sources');
|
|
218
|
+
if (jsName && iosJsNames.length && iosJsNames.some((n) => n !== jsName)) {
|
|
219
|
+
errors.push(`iOS: jsName=${JSON.stringify(iosJsNames)} != JS registerPlugin=${jsName}`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const podspecs = fs
|
|
223
|
+
.readdirSync(pluginDir, { withFileTypes: true })
|
|
224
|
+
.filter((e) => e.isFile() && e.name.endsWith(".podspec"))
|
|
225
|
+
.map((e) => path.join(pluginDir, e.name))
|
|
226
|
+
.sort();
|
|
227
|
+
if (!podspecs.length) errors.push("iOS: missing *.podspec at plugin root");
|
|
228
|
+
if (podspecs.length > 1) errors.push(`iOS: multiple podspecs at plugin root: ${podspecs.map((p) => path.basename(p))}`);
|
|
229
|
+
|
|
230
|
+
const pkgSwift = path.join(pluginDir, "Package.swift");
|
|
231
|
+
if (!exists(pkgSwift)) {
|
|
232
|
+
errors.push("iOS: missing Package.swift at plugin root");
|
|
233
|
+
} else if (podspecs.length) {
|
|
234
|
+
const podName = parsePodspecName(podspecs[0]);
|
|
235
|
+
const { pkgName, libNames } = parseSpmNames(pkgSwift);
|
|
236
|
+
if (!podName) errors.push("Podspec: missing s.name = '...'");
|
|
237
|
+
if (!pkgName) errors.push('SPM: missing Package(name: "...")');
|
|
238
|
+
if (podName && pkgName && podName !== pkgName) {
|
|
239
|
+
errors.push(`Podspec: s.name=${podName} != Package(name)=${pkgName}`);
|
|
240
|
+
}
|
|
241
|
+
if (pkgName && libNames.length && !libNames.includes(pkgName)) {
|
|
242
|
+
errors.push(`SPM: Package(name)=${pkgName} not present in .library(name) list ${JSON.stringify(libNames)}`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (errors.length) {
|
|
248
|
+
const relDir = path.relative(process.cwd(), pluginDir) || ".";
|
|
249
|
+
console.error(`[wiring] FAIL in ${relDir}`);
|
|
250
|
+
for (const e of errors) console.error(`- ${e}`);
|
|
251
|
+
process.exit(1);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
process.exit(0);
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Generate a release changelog with Cloudflare Workers AI in CI.
|
|
4
|
+
* Invoked from build.yml on tag releases via `bun run changelog:ai`.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { execFileSync } from 'node:child_process';
|
|
8
|
+
import { appendFileSync } from 'node:fs';
|
|
9
|
+
|
|
10
|
+
const DEFAULT_MODEL = '@cf/moonshotai/kimi-k2.7-code';
|
|
11
|
+
|
|
12
|
+
function parseArgs(argv) {
|
|
13
|
+
const args = { fromTag: process.env.FROM_TAG, toTag: process.env.TO_TAG };
|
|
14
|
+
for (let i = 0; i < argv.length; i++) {
|
|
15
|
+
if (argv[i] === '--from-tag') args.fromTag = argv[++i];
|
|
16
|
+
if (argv[i] === '--to-tag') args.toTag = argv[++i];
|
|
17
|
+
if (argv[i] === '--model') args.model = argv[++i];
|
|
18
|
+
}
|
|
19
|
+
return args;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function git(args) {
|
|
23
|
+
return execFileSync('/usr/bin/git', args, { encoding: 'utf8' }).trim();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function resolveTags({ fromTag, toTag }) {
|
|
27
|
+
const currentTag =
|
|
28
|
+
toTag ??
|
|
29
|
+
(process.env.GITHUB_REF?.startsWith('refs/tags/') ? process.env.GITHUB_REF.replace('refs/tags/', '') : null);
|
|
30
|
+
|
|
31
|
+
if (!currentTag) {
|
|
32
|
+
throw new Error('Missing target tag. Set GITHUB_REF to refs/tags/<tag> or pass --to-tag.');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const previousTag = fromTag ?? git(['describe', '--tags', '--abbrev=0', `${currentTag}^`]);
|
|
36
|
+
return { previousTag, currentTag };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function buildPrompt(previousTag, currentTag) {
|
|
40
|
+
const commits = git(['log', `${previousTag}..${currentTag}`, '--pretty=format:%s']);
|
|
41
|
+
const diffLines = git(['diff', '--stat', `${previousTag}..${currentTag}`]).split('\n');
|
|
42
|
+
const diffStat = diffLines.at(-1)?.trim() ?? '';
|
|
43
|
+
|
|
44
|
+
return `You are a technical writer creating a changelog for a software project.
|
|
45
|
+
|
|
46
|
+
There are changes in the git repository between two points:
|
|
47
|
+
- From tag: ${previousTag}
|
|
48
|
+
- To tag: ${currentTag}
|
|
49
|
+
- Commits:
|
|
50
|
+
${commits || '(none)'}
|
|
51
|
+
- Diff summary: ${diffStat || '(no file changes)'}
|
|
52
|
+
|
|
53
|
+
## Instructions:
|
|
54
|
+
1. **Categorize changes** using standard changelog categories:
|
|
55
|
+
- **Added**: New features
|
|
56
|
+
- **Changed**: Changes in existing functionality
|
|
57
|
+
- **Deprecated**: Soon-to-be removed features
|
|
58
|
+
- **Removed**: Removed features
|
|
59
|
+
- **Fixed**: Bug fixes
|
|
60
|
+
- **Security**: Security improvements
|
|
61
|
+
|
|
62
|
+
2. **Write clear, user-focused descriptions** that explain:
|
|
63
|
+
- What changed from a user's perspective
|
|
64
|
+
- Why it matters
|
|
65
|
+
- Any breaking changes or migration notes
|
|
66
|
+
|
|
67
|
+
3. **Use consistent formatting**:
|
|
68
|
+
- Each item should be a concise bullet point
|
|
69
|
+
- Start with an action verb when possible
|
|
70
|
+
- Group related changes together
|
|
71
|
+
|
|
72
|
+
4. **Focus on semantic meaning** rather than technical implementation details
|
|
73
|
+
|
|
74
|
+
Output ONLY the changelog content in markdown format. Do NOT include any explanatory text, metadata, introductory phrases like "Based on the git analysis" or "Here's the changelog", or concluding remarks. Start IMMEDIATELY with the changelog categories and items (e.g., "## Added"). Do not include version numbers or dates. Do not preface your response with any text whatsoever.`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function generateChangelog(prompt, model) {
|
|
78
|
+
const accountId = process.env.CLOUDFLARE_ACCOUNT_ID;
|
|
79
|
+
const apiToken = process.env.CLOUDFLARE_API_TOKEN;
|
|
80
|
+
|
|
81
|
+
if (!accountId || !apiToken) {
|
|
82
|
+
throw new Error('CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_API_TOKEN are required.');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const url = `https://api.cloudflare.com/client/v4/accounts/${accountId}/ai/run/${model}`;
|
|
86
|
+
const response = await fetch(url, {
|
|
87
|
+
method: 'POST',
|
|
88
|
+
headers: {
|
|
89
|
+
Authorization: `Bearer ${apiToken}`,
|
|
90
|
+
'Content-Type': 'application/json',
|
|
91
|
+
},
|
|
92
|
+
body: JSON.stringify({
|
|
93
|
+
messages: [{ role: 'user', content: prompt }],
|
|
94
|
+
}),
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const payload = await response.json();
|
|
98
|
+
if (!response.ok || !payload.success) {
|
|
99
|
+
throw new Error(`Cloudflare AI request failed with status ${response.status}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const content = payload.result?.choices?.[0]?.message?.content ?? payload.result?.response;
|
|
103
|
+
if (!content || typeof content !== 'string') {
|
|
104
|
+
throw new Error('Cloudflare AI returned an empty changelog response');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return content.trim();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function writeGithubOutput({ result, fromTag, toTag }) {
|
|
111
|
+
const outputFile = process.env.GITHUB_OUTPUT;
|
|
112
|
+
if (!outputFile) return;
|
|
113
|
+
|
|
114
|
+
const delimiter = `changelog_${Date.now()}`;
|
|
115
|
+
appendFileSync(outputFile, `result<<${delimiter}\n${result}\n${delimiter}\n`);
|
|
116
|
+
appendFileSync(outputFile, `from_tag=${fromTag}\n`);
|
|
117
|
+
appendFileSync(outputFile, `to_tag=${toTag}\n`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const args = parseArgs(process.argv.slice(2));
|
|
121
|
+
const model = args.model ?? process.env.CLOUDFLARE_AI_MODEL ?? DEFAULT_MODEL;
|
|
122
|
+
const { previousTag, currentTag } = resolveTags(args);
|
|
123
|
+
const prompt = buildPrompt(previousTag, currentTag);
|
|
124
|
+
|
|
125
|
+
console.error(`Generating changelog with ${model}`);
|
|
126
|
+
console.error(`Range: ${previousTag}..${currentTag}`);
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
const result = await generateChangelog(prompt, model);
|
|
130
|
+
writeGithubOutput({ result, fromTag: previousTag, toTag: currentTag });
|
|
131
|
+
} catch (error) {
|
|
132
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
133
|
+
console.error(`Changelog generation failed: ${message}`);
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
5
|
+
cd "$ROOT_DIR"
|
|
6
|
+
|
|
7
|
+
SIMULATOR_ID=$(xcrun simctl list devices available | awk -F '[()]' '/iPhone/{print $2; exit}')
|
|
8
|
+
|
|
9
|
+
if [[ -z "${SIMULATOR_ID:-}" ]]; then
|
|
10
|
+
echo "No available iPhone simulator found. Please install one via Xcode." >&2
|
|
11
|
+
exit 1
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
DESTINATION="id=${SIMULATOR_ID}"
|
|
15
|
+
SCHEME="CapgoCapacitorVideoPlayer"
|
|
16
|
+
SCRIPT_TIMEOUT_SECONDS=600
|
|
17
|
+
DEADLINE=$((SECONDS + SCRIPT_TIMEOUT_SECONDS))
|
|
18
|
+
|
|
19
|
+
BUILD_ARGS=()
|
|
20
|
+
TEST_ARGS=()
|
|
21
|
+
while [[ $# -gt 0 ]]; do
|
|
22
|
+
case "$1" in
|
|
23
|
+
-only-testing|-skip-testing|-resultBundlePath)
|
|
24
|
+
TEST_ARGS+=("$1")
|
|
25
|
+
shift
|
|
26
|
+
if [[ $# -gt 0 ]]; then
|
|
27
|
+
TEST_ARGS+=("$1")
|
|
28
|
+
shift
|
|
29
|
+
fi
|
|
30
|
+
;;
|
|
31
|
+
-testPlan)
|
|
32
|
+
BUILD_ARGS+=("$1")
|
|
33
|
+
TEST_ARGS+=("$1")
|
|
34
|
+
shift
|
|
35
|
+
if [[ $# -gt 0 ]]; then
|
|
36
|
+
BUILD_ARGS+=("$1")
|
|
37
|
+
TEST_ARGS+=("$1")
|
|
38
|
+
shift
|
|
39
|
+
fi
|
|
40
|
+
;;
|
|
41
|
+
-only-testing:*|-skip-testing:*|-resultBundlePath=*)
|
|
42
|
+
TEST_ARGS+=("$1")
|
|
43
|
+
shift
|
|
44
|
+
;;
|
|
45
|
+
-testPlan=*)
|
|
46
|
+
BUILD_ARGS+=("$1")
|
|
47
|
+
TEST_ARGS+=("$1")
|
|
48
|
+
shift
|
|
49
|
+
;;
|
|
50
|
+
*)
|
|
51
|
+
BUILD_ARGS+=("$1")
|
|
52
|
+
TEST_ARGS+=("$1")
|
|
53
|
+
shift
|
|
54
|
+
;;
|
|
55
|
+
esac
|
|
56
|
+
done
|
|
57
|
+
|
|
58
|
+
timeout_dir=$(mktemp -d "${TMPDIR:-/tmp}/test-ios-timeout.XXXXXX")
|
|
59
|
+
timeout_marker="${timeout_dir}/timed-out"
|
|
60
|
+
trap 'rm -rf "$timeout_dir"' EXIT
|
|
61
|
+
|
|
62
|
+
run_with_timeout() {
|
|
63
|
+
local remaining=$((DEADLINE - SECONDS))
|
|
64
|
+
if [[ "$remaining" -le 0 ]]; then
|
|
65
|
+
echo "error: xcodebuild timed out after ${SCRIPT_TIMEOUT_SECONDS}s" >&2
|
|
66
|
+
: >"$timeout_marker"
|
|
67
|
+
return 124
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
set +e
|
|
71
|
+
(
|
|
72
|
+
set -m
|
|
73
|
+
"$@" &
|
|
74
|
+
local pid=$!
|
|
75
|
+
(
|
|
76
|
+
sleep "$remaining"
|
|
77
|
+
if kill -0 "$pid" 2>/dev/null; then
|
|
78
|
+
echo "error: xcodebuild timed out after ${SCRIPT_TIMEOUT_SECONDS}s" >&2
|
|
79
|
+
: >"$timeout_marker"
|
|
80
|
+
kill -TERM -"$pid" 2>/dev/null || kill -TERM "$pid" 2>/dev/null
|
|
81
|
+
sleep 2
|
|
82
|
+
kill -KILL -"$pid" 2>/dev/null || kill -KILL "$pid" 2>/dev/null
|
|
83
|
+
fi
|
|
84
|
+
) &
|
|
85
|
+
local watchdog=$!
|
|
86
|
+
wait "$pid"
|
|
87
|
+
local status=$?
|
|
88
|
+
kill "$watchdog" 2>/dev/null || true
|
|
89
|
+
wait "$watchdog" 2>/dev/null || true
|
|
90
|
+
exit "$status"
|
|
91
|
+
)
|
|
92
|
+
local status=$?
|
|
93
|
+
set -e
|
|
94
|
+
|
|
95
|
+
if [[ -f "$timeout_marker" ]]; then
|
|
96
|
+
return 124
|
|
97
|
+
fi
|
|
98
|
+
return "$status"
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
run_with_timeout xcodebuild build-for-testing \
|
|
102
|
+
-scheme "$SCHEME" \
|
|
103
|
+
-destination "$DESTINATION" \
|
|
104
|
+
"${BUILD_ARGS[@]}"
|
|
105
|
+
|
|
106
|
+
run_with_timeout xcodebuild test-without-building \
|
|
107
|
+
-scheme "$SCHEME" \
|
|
108
|
+
-destination "$DESTINATION" \
|
|
109
|
+
"${TEST_ARGS[@]}"
|