runcloud 0.2.155 → 0.2.157
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/dist/commands/appStoreEvaluate.js +12 -26
- package/dist/commands/run-cloud.js +1 -1
- package/dist/commands/xcode.js +46 -3
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/runcloud-0.2.157.tgz +0 -0
- package/runcloud-0.2.155.tgz +0 -0
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { existsSync, readFileSync, readdirSync, statSync, } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
const PREVIEW_APP_ID = 'app.newly.preview';
|
|
4
|
+
const PLACEHOLDER_COPY = /lorem ipsum|example\.com|\bTBD\b|\bTODO\b|\bplaceholder\b/i;
|
|
5
|
+
const INPUT_PLACEHOLDER = /\bplaceholder\??\s*[=:]\s*(\{[^}]*\}|"[^"]*"|'[^']*'|`[^`]*`|[\w.$]+)/gi;
|
|
6
|
+
const PLACEHOLDER_NAME = /\.placeholder\b|\bplaceholder\b(?=\s*[,})\]])/gi;
|
|
7
|
+
function withoutInputPlaceholders(line) {
|
|
8
|
+
return line
|
|
9
|
+
.replace(INPUT_PLACEHOLDER, (_prop, value) => (/^["'`]/.test(value) ? value.replace(/example\.com/gi, '') : ''))
|
|
10
|
+
.replace(PLACEHOLDER_NAME, '');
|
|
11
|
+
}
|
|
4
12
|
const CONFIG_RULES = {
|
|
5
13
|
app_name: { path: ['expo', 'name'], absent: 'fail' },
|
|
6
14
|
slug: { path: ['expo', 'slug'], absent: 'fail' },
|
|
@@ -157,7 +165,9 @@ function evaluateLocalItem(root, appJson, item) {
|
|
|
157
165
|
case 'http_endpoints':
|
|
158
166
|
return patternItem(root, item, /http:\/\/(?!localhost|127\.0\.0\.1)/i, 'warning', 'Non-local HTTP endpoint found.', 'No non-local HTTP endpoints found.');
|
|
159
167
|
case 'placeholder_content':
|
|
160
|
-
return patternItem(root, item,
|
|
168
|
+
return patternItem(root, item, PLACEHOLDER_COPY, 'warning', 'Placeholder text found.', 'No placeholder text found.', {
|
|
169
|
+
filter: (match) => PLACEHOLDER_COPY.test(withoutInputPlaceholders(match.text)),
|
|
170
|
+
});
|
|
161
171
|
case 'competitor_reference':
|
|
162
172
|
return patternItem(root, item, /google play|\.apk\b|samsung|android app/i, 'warning', 'Possible platform/competitor reference found in user-facing copy.', 'No competitor platform references found.', { dirs: ['app', 'src', 'components'] });
|
|
163
173
|
case 'private_api_usage':
|
|
@@ -180,7 +190,7 @@ function evaluateLocalItem(root, appJson, item) {
|
|
|
180
190
|
case 'external_payment_digital':
|
|
181
191
|
return evaluateExternalPayment(root, item);
|
|
182
192
|
case 'expo_layout_missing':
|
|
183
|
-
return
|
|
193
|
+
return makeItem(item, 'pass', 'Expo Router adds a layout where a directory has none.', 'expo-router getRoutesCore.js');
|
|
184
194
|
case 'expo_duplicate_route':
|
|
185
195
|
return evaluateDuplicateRoutes(root, item);
|
|
186
196
|
case 'ios_encryption':
|
|
@@ -248,16 +258,6 @@ function evaluateExternalPayment(root, item) {
|
|
|
248
258
|
return makeItem(item, 'pass', 'No external payment SDK patterns found.', 'scan: no Stripe/PayPal/Braintree patterns found');
|
|
249
259
|
return makeItem(item, 'warning', 'External payment SDK found; verify it is not used for digital goods on iOS.', evidence(matches));
|
|
250
260
|
}
|
|
251
|
-
function evaluateExpoLayout(root, item) {
|
|
252
|
-
const appDir = path.join(root, 'app');
|
|
253
|
-
if (!existsSync(appDir))
|
|
254
|
-
return makeItem(item, 'pass', 'No Expo Router app directory found.', 'scan: app/ directory absent');
|
|
255
|
-
const dirs = walkDirs(appDir).map((dir) => path.relative(root, dir) || 'app');
|
|
256
|
-
const missing = dirs.filter((dir) => !hasLayoutFile(path.join(root, dir)));
|
|
257
|
-
if (!missing.length)
|
|
258
|
-
return makeItem(item, 'pass', 'Every app route directory has a layout file.', 'scan: app route directories');
|
|
259
|
-
return makeItem(item, 'fail', 'Expo Router directories without _layout files found.', missing.slice(0, 8).join(', '));
|
|
260
|
-
}
|
|
261
261
|
function evaluateDuplicateRoutes(root, item) {
|
|
262
262
|
const appDir = routerDir(root);
|
|
263
263
|
if (!existsSync(appDir))
|
|
@@ -516,17 +516,6 @@ function walkFiles(dir) {
|
|
|
516
516
|
}
|
|
517
517
|
return files;
|
|
518
518
|
}
|
|
519
|
-
function walkDirs(dir) {
|
|
520
|
-
const dirs = [dir];
|
|
521
|
-
for (const entry of safeReadDir(dir)) {
|
|
522
|
-
if (SKIP_DIRS.has(entry.name))
|
|
523
|
-
continue;
|
|
524
|
-
const full = path.join(dir, entry.name);
|
|
525
|
-
if (entry.isDirectory())
|
|
526
|
-
dirs.push(...walkDirs(full));
|
|
527
|
-
}
|
|
528
|
-
return dirs;
|
|
529
|
-
}
|
|
530
519
|
function shouldScanFile(file) {
|
|
531
520
|
return TEXT_EXTENSIONS.has(path.extname(file));
|
|
532
521
|
}
|
|
@@ -536,9 +525,6 @@ function findByName(root, name) {
|
|
|
536
525
|
function sourceText(root, dirs) {
|
|
537
526
|
return collectScanFiles(root, dirs).map((file) => readText(file) ?? '').join('\n');
|
|
538
527
|
}
|
|
539
|
-
function hasLayoutFile(dir) {
|
|
540
|
-
return ['_layout.tsx', '_layout.ts', '_layout.jsx', '_layout.js'].some((name) => existsSync(path.join(dir, name)));
|
|
541
|
-
}
|
|
542
528
|
function routerDir(root) {
|
|
543
529
|
const src = path.join(root, 'src', 'app');
|
|
544
530
|
return safeStat(src)?.isDirectory() ? src : path.join(root, 'app');
|
|
@@ -1666,7 +1666,7 @@ export function registerRunCloud(program) {
|
|
|
1666
1666
|
.command('launch')
|
|
1667
1667
|
.description('Create one session and install one reusable output')
|
|
1668
1668
|
.option('--asset <id>', 'Asset ID to install')
|
|
1669
|
-
.option('--build <id>', '
|
|
1669
|
+
.option('--build <id>', 'installable Xcode simulator build to install')
|
|
1670
1670
|
.requiredOption('--idempotency-key <key>', 'stable retry key owned by this launch intent')
|
|
1671
1671
|
.option('--platform <ios|android>', 'mobile platform', 'ios')
|
|
1672
1672
|
.option('--org-id <id>', 'organization ID')
|
package/dist/commands/xcode.js
CHANGED
|
@@ -252,12 +252,16 @@ export async function requestFor(flags) {
|
|
|
252
252
|
throw new Error('Cloud signing requires --sdk iphoneos');
|
|
253
253
|
if (flags.uploadToAppstore && sdk !== 'iphoneos')
|
|
254
254
|
throw new Error('--upload-to-appstore requires --sdk iphoneos');
|
|
255
|
+
if (flags.applicationId !== undefined && !/^[A-Za-z0-9_.:-]{1,200}$/.test(flags.applicationId)) {
|
|
256
|
+
throw new Error('--application-id must contain 1–200 letters, digits, dots, underscores, colons, or hyphens');
|
|
257
|
+
}
|
|
255
258
|
const ascPrivateKey = flags.ascKey ? await base64File(flags.ascKey, '--asc-key') : undefined;
|
|
256
259
|
const waitTimeoutSeconds = integerFlag(flags.ascWaitTimeout, '--asc-wait-timeout', 0, 1800, 0);
|
|
257
260
|
const timeoutSeconds = integerFlag(flags.timeoutSeconds, '--timeout-seconds', 1, 21600, 3600);
|
|
258
261
|
const parsedBuildSettings = buildSettings(flags.buildSetting);
|
|
259
262
|
return {
|
|
260
263
|
...(flags.orgId ? { orgId: flags.orgId } : {}),
|
|
264
|
+
...(flags.applicationId ? { applicationId: flags.applicationId } : {}),
|
|
261
265
|
...(flags.workspace ? { workspace: flags.workspace } : {}),
|
|
262
266
|
...(flags.project ? { project: flags.project } : {}),
|
|
263
267
|
...(flags.scheme ? { scheme: flags.scheme } : {}),
|
|
@@ -313,13 +317,18 @@ async function logChunk(client, id, offset, source) {
|
|
|
313
317
|
return await retryXcodeRequest(async () => await client.get(`/run-cloud/xcode/builds/${encodeURIComponent(id)}/logs?offset=${offset}${source ? `&source=${source}` : ''}`));
|
|
314
318
|
}
|
|
315
319
|
async function waitForBuild(client, id, flags) {
|
|
320
|
+
validateWaitFlags(flags);
|
|
316
321
|
const timeout = integerFlag(flags.waitTimeout, '--wait-timeout', 1, 23400, 22500);
|
|
317
322
|
const deadline = Date.now() + timeout * 1000;
|
|
318
323
|
let offset = 0;
|
|
319
324
|
let source;
|
|
320
325
|
for (;;) {
|
|
321
326
|
const build = await retryXcodeRequest(async () => await client.get(`/run-cloud/xcode/builds/${encodeURIComponent(id)}`));
|
|
322
|
-
if (
|
|
327
|
+
if (flags.until === 'installable' && build.artifact.kind === 'simulator-app'
|
|
328
|
+
&& build.artifact.assetId && (build.artifact.installable === true
|
|
329
|
+
|| (build.status === 'succeeded' && build.artifact.available)))
|
|
330
|
+
return build;
|
|
331
|
+
if (build.logs.available || build.status === 'queued' || build.status === 'running' || build.status === 'staged') {
|
|
323
332
|
const chunk = await logChunk(client, id, offset, source);
|
|
324
333
|
if (chunk.text)
|
|
325
334
|
(flags.json ? process.stderr : process.stdout).write(chunk.text);
|
|
@@ -337,6 +346,14 @@ async function waitForBuild(client, id, flags) {
|
|
|
337
346
|
await new Promise((resolvePromise) => setTimeout(resolvePromise, 1000));
|
|
338
347
|
}
|
|
339
348
|
}
|
|
349
|
+
function validateWaitFlags(flags) {
|
|
350
|
+
if (flags.until !== undefined && !['terminal', 'installable'].includes(flags.until)) {
|
|
351
|
+
throw new Error('--until must be terminal or installable');
|
|
352
|
+
}
|
|
353
|
+
if (flags.until === 'installable' && flags.output) {
|
|
354
|
+
throw new Error('--output requires durable completion. Use --until terminal to download, or launch the installable Asset with runcloud mobile launch --asset.');
|
|
355
|
+
}
|
|
356
|
+
}
|
|
340
357
|
async function downloadArtifact(client, build, output, force = false) {
|
|
341
358
|
if (!build.artifact.available)
|
|
342
359
|
throw new Error(`Artifact for Xcode build ${build.id} is not available`);
|
|
@@ -383,15 +400,18 @@ export function registerXcode(program) {
|
|
|
383
400
|
.option('--include <regex>', 'force-include matching source paths; repeatable', repeated, [])
|
|
384
401
|
.option('--additional-file <local=remote>', 'include an extra file; repeatable', repeated, [])
|
|
385
402
|
.option('--org-id <id>', 'organization to own the build')
|
|
403
|
+
.option('--application-id <id>', 'stable application identity for native artifact cache retirement')
|
|
386
404
|
.option('--idempotency-key <key>', 'stable key for retry-safe submission')
|
|
387
405
|
.option('--timeout-seconds <seconds>', 'remote xcodebuild timeout, max 21600', '3600')
|
|
388
406
|
.option('--wait-timeout <seconds>', 'client wait timeout, max 23400', '22500')
|
|
407
|
+
.option('--until <state>', 'wait for terminal completion or an installable simulator app', 'terminal')
|
|
389
408
|
.option('--no-wait', 'return after the control plane accepts the build')
|
|
390
409
|
.option('--detach', 'alias for --no-wait; terminal state remains queryable', false)
|
|
391
410
|
.option('-o, --output <path>', 'download the artifact here after a successful build')
|
|
392
411
|
.option('--force', 'overwrite --output if it exists', false)
|
|
393
412
|
.option('--json', 'print the build record as JSON; streamed logs go to stderr', false)
|
|
394
413
|
.action((sourcePath, flags) => action(async () => {
|
|
414
|
+
validateWaitFlags(flags);
|
|
395
415
|
const request = await requestFor(flags);
|
|
396
416
|
const source = await packSource(sourcePath, flags);
|
|
397
417
|
const archiveBuffer = new ArrayBuffer(source.body.byteLength);
|
|
@@ -415,13 +435,16 @@ export function registerXcode(program) {
|
|
|
415
435
|
if (flags.json) {
|
|
416
436
|
print({ ...build, ...(artifactPath ? { artifactPath } : {}) }, true);
|
|
417
437
|
}
|
|
438
|
+
else if (build.status === 'staged' && flags.until === 'installable') {
|
|
439
|
+
console.log(`Build ${build.id} is ready to install. Asset: ${build.artifact.assetId}`);
|
|
440
|
+
}
|
|
418
441
|
else if (build.status === 'succeeded') {
|
|
419
442
|
console.log(`Build ${build.id} succeeded (exit code ${build.result?.exitCode ?? 0}).`);
|
|
420
443
|
console.log(artifactPath
|
|
421
444
|
? `Artifact: ${artifactPath}`
|
|
422
445
|
: `Download: runcloud xcode artifact ${build.id}`);
|
|
423
446
|
}
|
|
424
|
-
if (build.status !== 'succeeded') {
|
|
447
|
+
if (build.status !== 'succeeded' && !(build.status === 'staged' && flags.until === 'installable')) {
|
|
425
448
|
const reason = build.failure?.reason ?? `Remote build ended with ${build.status}`;
|
|
426
449
|
const next = build.failure?.action ?? `Inspect logs with runcloud xcode logs ${build.id}`;
|
|
427
450
|
throw new Error(`${reason}\n${next}\nBuild ID: ${build.id}`);
|
|
@@ -437,15 +460,18 @@ export function registerXcode(program) {
|
|
|
437
460
|
.option('--include <regex>', 'force-include matching source paths; repeatable', repeated, [])
|
|
438
461
|
.option('--additional-file <local=remote>', 'include an extra file; repeatable', repeated, [])
|
|
439
462
|
.option('--org-id <id>', 'organization to own the build')
|
|
463
|
+
.option('--application-id <id>', 'stable application identity for native artifact cache retirement')
|
|
440
464
|
.option('--idempotency-key <key>', 'stable key for retry-safe submission')
|
|
441
465
|
.option('--timeout-seconds <seconds>', 'remote bundle timeout, max 21600', '3600')
|
|
442
466
|
.option('--wait-timeout <seconds>', 'client wait timeout, max 23400', '22500')
|
|
467
|
+
.option('--until <state>', 'wait for terminal completion or an installable simulator app', 'terminal')
|
|
443
468
|
.option('--no-wait', 'return after the control plane accepts the build')
|
|
444
469
|
.option('--detach', 'alias for --no-wait; terminal state remains queryable', false)
|
|
445
470
|
.option('-o, --output <path>', 'download the rebundled app here after success')
|
|
446
471
|
.option('--force', 'overwrite --output if it exists', false)
|
|
447
472
|
.option('--json', 'print the build record as JSON; streamed logs go to stderr', false)
|
|
448
473
|
.action((sourcePath, flags) => action(async () => {
|
|
474
|
+
validateWaitFlags(flags);
|
|
449
475
|
if (!/^xcb_[A-Za-z0-9]+$/.test(flags.basisBuild)) {
|
|
450
476
|
throw new Error('--basis-build must be a run.cloud Xcode build ID');
|
|
451
477
|
}
|
|
@@ -477,14 +503,31 @@ export function registerXcode(program) {
|
|
|
477
503
|
: undefined;
|
|
478
504
|
if (flags.json)
|
|
479
505
|
print({ ...build, ...(artifactPath ? { artifactPath } : {}) }, true);
|
|
506
|
+
else if (build.status === 'staged' && flags.until === 'installable') {
|
|
507
|
+
console.log(`Rebundle ${build.id} is ready to install. Asset: ${build.artifact.assetId}`);
|
|
508
|
+
}
|
|
480
509
|
else if (build.status === 'succeeded') {
|
|
481
510
|
console.log(`Rebundle ${build.id} succeeded.`);
|
|
482
511
|
console.log(artifactPath ? `Artifact: ${artifactPath}` : `Asset: ${build.artifact.assetId}`);
|
|
483
512
|
}
|
|
484
|
-
if (build.status !== 'succeeded') {
|
|
513
|
+
if (build.status !== 'succeeded' && !(build.status === 'staged' && flags.until === 'installable')) {
|
|
485
514
|
throw new Error(`${build.failure?.reason ?? `Rebundle ended with ${build.status}`}\n${build.failure?.action ?? `Inspect logs with runcloud xcode logs ${build.id}`}\nBuild ID: ${build.id}`);
|
|
486
515
|
}
|
|
487
516
|
}));
|
|
517
|
+
xcode
|
|
518
|
+
.command('wait')
|
|
519
|
+
.description('Wait for build completion or an installable simulator app')
|
|
520
|
+
.argument('<id>')
|
|
521
|
+
.option('--until <state>', 'terminal or installable', 'terminal')
|
|
522
|
+
.option('--wait-timeout <seconds>', 'client wait timeout, max 23400', '22500')
|
|
523
|
+
.option('--json', 'print the build record as JSON; logs go to stderr', false)
|
|
524
|
+
.action((id, flags) => action(async () => {
|
|
525
|
+
const build = await waitForBuild(api(), id, flags);
|
|
526
|
+
print(build, flags.json);
|
|
527
|
+
if (build.status === 'failed' || build.status === 'cancelled') {
|
|
528
|
+
throw new Error(build.failure?.reason ?? `Build ${id} ${build.status}`);
|
|
529
|
+
}
|
|
530
|
+
}));
|
|
488
531
|
xcode
|
|
489
532
|
.command('get')
|
|
490
533
|
.description('Show product-visible Xcode build state')
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const CLI_VERSION = '0.2.
|
|
1
|
+
export const CLI_VERSION = '0.2.157';
|
package/package.json
CHANGED
|
Binary file
|
package/runcloud-0.2.155.tgz
DELETED
|
Binary file
|