@absolutejs/absolute 0.20.0-beta.38 → 0.20.0-beta.39

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/cli/index.js CHANGED
@@ -2589,7 +2589,14 @@ import {
2589
2589
  writeFile as writeFile3
2590
2590
  } from "fs/promises";
2591
2591
  import { dirname as dirname4, isAbsolute as isAbsolute2, join as join9, relative as relative3, resolve as resolve6, sep as sep2 } from "path";
2592
- var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), requireManifest = (value) => {
2592
+ var developmentTeamArgument = (value) => {
2593
+ if (value === undefined)
2594
+ return;
2595
+ const team = value.trim().toUpperCase();
2596
+ if (!/^[A-Z0-9]{10}$/u.test(team))
2597
+ throw new TypeError("iOS development team must contain ten letters or digits.");
2598
+ return `DEVELOPMENT_TEAM=${team}`;
2599
+ }, isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), requireManifest = (value) => {
2593
2600
  if (!isRecord2(value) || typeof value.appBuild !== "string" || typeof value.appId !== "string" || typeof value.runtime !== "string") {
2594
2601
  throw new TypeError("Invalid embedded AbsoluteJS mobile manifest.");
2595
2602
  }
@@ -2762,8 +2769,10 @@ var isRecord2 = (value) => typeof value === "object" && value !== null && !Array
2762
2769
  await writeFile3(exportPlist, exportOptions());
2763
2770
  const run = options.run ?? defaultRun2;
2764
2771
  try {
2772
+ const developmentTeam = developmentTeamArgument(options.developmentTeam);
2765
2773
  const versionArguments = [
2766
2774
  `MARKETING_VERSION=${marketingVersion}`,
2775
+ ...developmentTeam ? [developmentTeam] : [],
2767
2776
  ...buildNumber === undefined ? [] : [`CURRENT_PROJECT_VERSION=${buildNumber}`]
2768
2777
  ];
2769
2778
  const archiveExit = await run([
@@ -18740,6 +18749,20 @@ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord13 = (value) => typeof value ==
18740
18749
  artifactPath
18741
18750
  ]);
18742
18751
  return result.exitCode === 0 && /jar verified/iu.test(result.stdout);
18752
+ }, signAab = (artifactPath, capture, jarsigner, signing) => {
18753
+ const result = capture([
18754
+ jarsigner,
18755
+ "-keystore",
18756
+ signing.keystorePath,
18757
+ "-storepass:env",
18758
+ signing.storePasswordEnvironment,
18759
+ "-keypass:env",
18760
+ signing.keyPasswordEnvironment,
18761
+ artifactPath,
18762
+ signing.keyAlias
18763
+ ]);
18764
+ if (result.exitCode !== 0)
18765
+ throw new TypeError("jarsigner could not sign the Android App Bundle with the configured CI identity.");
18743
18766
  }, sha256File2 = async (path) => createHash13("sha256").update(await readFile18(path)).digest("hex"), safeOutputDirectory2 = (projectRoot, requested) => {
18744
18767
  const root = resolve40(projectRoot);
18745
18768
  const output = resolve40(root, requested ?? ".absolutejs/mobile/releases/android");
@@ -18830,7 +18853,16 @@ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord13 = (value) => typeof value ==
18830
18853
  throw new TypeError(`Android Gradle did not produce the expected App Bundle: ${artifactPath}`);
18831
18854
  }
18832
18855
  const capture = options.capture ?? defaultCapture4;
18833
- const signed2 = verifyAabSignature(artifactPath, capture, options.jarsigner);
18856
+ const jarsigner = options.jarsigner === undefined ? Bun.which("jarsigner") : options.jarsigner;
18857
+ let signed2 = verifyAabSignature(artifactPath, capture, jarsigner);
18858
+ if (signed2 === false && options.signing) {
18859
+ if (!jarsigner)
18860
+ throw new TypeError("Could not sign the Android App Bundle because jarsigner is unavailable.");
18861
+ signAab(artifactPath, capture, jarsigner, options.signing);
18862
+ signed2 = verifyAabSignature(artifactPath, capture, jarsigner);
18863
+ if (!signed2)
18864
+ throw new TypeError("Android App Bundle signature verification failed after CI signing.");
18865
+ }
18834
18866
  if (signed2 === null && !options.allowUnsigned) {
18835
18867
  throw new TypeError("Could not verify the Android App Bundle signature because jarsigner is unavailable. Install a JDK, or use --unsigned only for a non-publishable build.");
18836
18868
  }
@@ -19413,9 +19445,9 @@ var prepareAbsoluteIosRelease = async (publisher, options) => {
19413
19445
  var init_releasePublisher = () => {};
19414
19446
 
19415
19447
  // src/mobile/mobileInspect.ts
19416
- import { access as access12, readFile as readFile21, stat as stat5 } from "fs/promises";
19448
+ import { access as access12, readFile as readFile21 } from "fs/promises";
19417
19449
  import { join as join53, relative as relative28, resolve as resolve42 } from "path";
19418
- var ABSOLUTE_MOBILE_INSPECTION_FORMAT = 1, MOBILE_PACKAGE_NAMES, MOBILE_FRAMEWORKS2, isObject3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), portablePath2 = (projectRoot, path) => {
19450
+ var ABSOLUTE_MOBILE_INSPECTION_FORMAT = 1, MOBILE_PACKAGE_NAMES, isObject3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), portablePath2 = (projectRoot, path) => {
19419
19451
  const value = relative28(resolve42(projectRoot), resolve42(path)).replaceAll("\\", "/");
19420
19452
  return value || ".";
19421
19453
  }, pathExists8 = async (path) => {
@@ -19430,103 +19462,6 @@ var ABSOLUTE_MOBILE_INSPECTION_FORMAT = 1, MOBILE_PACKAGE_NAMES, MOBILE_FRAMEWOR
19430
19462
  if (!isObject3(value))
19431
19463
  throw new TypeError("JSON root must be an object.");
19432
19464
  return value;
19433
- }, requireString2 = (value, field) => {
19434
- if (typeof value !== "string" || value.length === 0)
19435
- throw new TypeError(`${field} must be a non-empty string.`);
19436
- return value;
19437
- }, requireStringArray2 = (value, field) => {
19438
- if (!Array.isArray(value) || !value.every((item) => typeof item === "string"))
19439
- throw new TypeError(`${field} must be a string array.`);
19440
- return value;
19441
- }, requireBundleFile2 = async (root, value, field) => {
19442
- const portable = requireString2(value, field);
19443
- const path = resolve42(root, portable);
19444
- const normalizedRoot = resolve42(root);
19445
- if (path === normalizedRoot || !path.startsWith(`${normalizedRoot}/`))
19446
- throw new TypeError(`${field} must remain inside the mobile bundle.`);
19447
- if (!(await stat5(path).catch(() => {
19448
- return;
19449
- }))?.isFile())
19450
- throw new TypeError(`${field} does not exist in the mobile bundle.`);
19451
- return portable;
19452
- }, inspectBundle = async (config, projectRoot) => {
19453
- const manifestPath = join53(config.bundleDirectory, "absolute-mobile-manifest.json");
19454
- const manifest = portablePath2(projectRoot, manifestPath);
19455
- if (!await pathExists8(manifestPath))
19456
- return { manifest, status: "missing" };
19457
- try {
19458
- const value = await readObject2(manifestPath);
19459
- if (value.format !== 1)
19460
- throw new TypeError("format is not supported by this runtime.");
19461
- if (requireString2(value.appId, "appId") !== config.appId)
19462
- throw new TypeError("appId does not match the effective mobile config.");
19463
- if (requireString2(value.productionOrigin, "productionOrigin") !== config.productionOrigin)
19464
- throw new TypeError("productionOrigin does not match the effective mobile config.");
19465
- const appBuild = requireString2(value.appBuild, "appBuild");
19466
- const runtime = requireString2(value.runtime, "runtime");
19467
- const capabilities = requireStringArray2(value.deviceCapabilities, "deviceCapabilities").sort();
19468
- if (!Array.isArray(value.pages) || !Array.isArray(value.routes))
19469
- throw new TypeError("pages and routes must be arrays.");
19470
- const pageIds = new Set;
19471
- const frameworks7 = new Set;
19472
- await Promise.all(value.pages.map(async (candidate) => {
19473
- if (!isObject3(candidate))
19474
- throw new TypeError("pages contains an invalid entry.");
19475
- const pageId = requireString2(candidate.pageId, "page.pageId");
19476
- if (pageIds.has(pageId))
19477
- throw new TypeError("page.pageId values must be unique.");
19478
- pageIds.add(pageId);
19479
- const framework = requireString2(candidate.framework, "page.framework");
19480
- if (!MOBILE_FRAMEWORKS2.has(framework))
19481
- throw new TypeError("page.framework is unsupported.");
19482
- frameworks7.add(framework);
19483
- requireString2(candidate.bundleHash, "page.bundleHash");
19484
- requireString2(candidate.contract, "page.contract");
19485
- requireString2(candidate.propsSchemaHash, "page.propsSchemaHash");
19486
- await requireBundleFile2(config.bundleDirectory, candidate.localBundlePath, "page.localBundlePath");
19487
- if (candidate.localStylePath !== undefined)
19488
- await requireBundleFile2(config.bundleDirectory, candidate.localStylePath, "page.localStylePath");
19489
- }));
19490
- const routes = value.routes.map((candidate) => {
19491
- if (!isObject3(candidate))
19492
- throw new TypeError("routes contains an invalid entry.");
19493
- const { method } = candidate;
19494
- if (method !== "GET" && method !== "HEAD")
19495
- throw new TypeError("route.method must be GET or HEAD.");
19496
- const pageId = requireString2(candidate.pageId, "route.pageId");
19497
- if (!pageIds.has(pageId))
19498
- throw new TypeError("route.pageId references a missing page.");
19499
- return {
19500
- method,
19501
- pageId,
19502
- pattern: requireString2(candidate.pattern, "route.pattern")
19503
- };
19504
- });
19505
- await Promise.all(["index.html", "absolute-mobile-bootstrap.js"].map((file) => requireBundleFile2(config.bundleDirectory, file, file)));
19506
- const entryPath = new URL(config.entry, "https://absolute.invalid").pathname;
19507
- const entryResolved = resolveAbsoluteMobileRoute(routes, entryPath) !== undefined;
19508
- if (!entryResolved)
19509
- throw new TypeError("entry is not owned by an embedded route.");
19510
- return {
19511
- appBuild,
19512
- auth: isObject3(value.auth),
19513
- capabilities,
19514
- entryResolved,
19515
- frameworks: [...frameworks7].sort(),
19516
- manifest,
19517
- pageCount: value.pages.length,
19518
- routeCount: value.routes.length,
19519
- runtime,
19520
- status: "valid",
19521
- sync: isObject3(value.sync)
19522
- };
19523
- } catch (error) {
19524
- return {
19525
- issue: error instanceof Error ? error.message : "The embedded mobile manifest is invalid.",
19526
- manifest,
19527
- status: "invalid"
19528
- };
19529
- }
19530
19465
  }, addPackageDeclarations = (declarations, value) => {
19531
19466
  if (!isObject3(value))
19532
19467
  return;
@@ -19550,7 +19485,7 @@ var ABSOLUTE_MOBILE_INSPECTION_FORMAT = 1, MOBILE_PACKAGE_NAMES, MOBILE_FRAMEWOR
19550
19485
  };
19551
19486
  }));
19552
19487
  }, inspectAbsoluteMobileProject = async (config, projectRoot, options = {}) => {
19553
- const bundle = await inspectBundle(config, projectRoot);
19488
+ const bundle = await inspectAbsoluteMobileBundle(config, projectRoot);
19554
19489
  let currentCapabilities = [];
19555
19490
  let capabilityIssue;
19556
19491
  let plugins = [];
@@ -19643,7 +19578,7 @@ var ABSOLUTE_MOBILE_INSPECTION_FORMAT = 1, MOBILE_PACKAGE_NAMES, MOBILE_FRAMEWOR
19643
19578
  var init_mobileInspect = __esm(() => {
19644
19579
  init_deviceCapabilities();
19645
19580
  init_releaseDoctor();
19646
- init_routeMatcher();
19581
+ init_mobileBundleInspection();
19647
19582
  MOBILE_PACKAGE_NAMES = new Set([
19648
19583
  "@absolutejs/absolute",
19649
19584
  "@absolutejs/auth",
@@ -19655,15 +19590,484 @@ var init_mobileInspect = __esm(() => {
19655
19590
  "@absolutejs/sync-capacitor",
19656
19591
  "@capacitor-community/sqlite"
19657
19592
  ]);
19658
- MOBILE_FRAMEWORKS2 = new Set([
19659
- "angular",
19660
- "ember",
19661
- "html",
19662
- "htmx",
19663
- "react",
19664
- "svelte",
19665
- "vue"
19593
+ });
19594
+
19595
+ // src/mobile/ciWorkflow.ts
19596
+ import { existsSync as existsSync42 } from "fs";
19597
+ import { access as access13, mkdir as mkdir14, readFile as readFile22, writeFile as writeFile16 } from "fs/promises";
19598
+ import { dirname as dirname31, extname as extname9, relative as relative29, resolve as resolve43, sep as sep8 } from "path";
19599
+ var ABSOLUTE_MOBILE_CI_WORKFLOW_FORMAT = 1, SECRET_NAME_PATTERN, CI_ENV_INDENTATION = 6, RESERVED_SECRET_NAMES, exists3 = async (path) => {
19600
+ try {
19601
+ await access13(path);
19602
+ return true;
19603
+ } catch {
19604
+ return false;
19605
+ }
19606
+ }, yamlString = (value) => `'${value.replaceAll("'", "''")}'`, projectPath = (projectRoot, value, field, options = {}) => {
19607
+ const root = resolve43(projectRoot);
19608
+ const path = resolve43(root, value);
19609
+ const portable = relative29(root, path).replaceAll("\\", "/");
19610
+ if (portable === ".." || portable.startsWith(`..${sep8}`) || portable.startsWith("../") || portable === "") {
19611
+ throw new TypeError(`${field} must remain inside the project root.`);
19612
+ }
19613
+ if (/\r|\n/u.test(portable) || portable.startsWith("-"))
19614
+ throw new TypeError(`${field} contains an unsafe path.`);
19615
+ if (!options.allowMissing && !existsSync42(path))
19616
+ throw new TypeError(`${field} does not exist inside the project.`);
19617
+ return portable;
19618
+ }, workflowOutputPath = (projectRoot, value) => {
19619
+ const root = resolve43(projectRoot);
19620
+ const workflows = resolve43(root, ".github/workflows");
19621
+ const path = resolve43(root, value ?? ".github/workflows/absolute-mobile.yml");
19622
+ const portable = relative29(workflows, path);
19623
+ if (portable === ".." || portable.startsWith(`..${sep8}`) || extname9(path) !== ".yml" && extname9(path) !== ".yaml") {
19624
+ throw new TypeError("mobile ci github --output must be a .yml or .yaml file inside .github/workflows.");
19625
+ }
19626
+ return path;
19627
+ }, normalizeSecretEnvironment = (values = []) => {
19628
+ const names = [...new Set(values)].sort();
19629
+ for (const name of names) {
19630
+ if (!SECRET_NAME_PATTERN.test(name))
19631
+ throw new TypeError("mobile ci github --secret-env values must be uppercase environment variable names.");
19632
+ if (name.startsWith("GITHUB_") || name.startsWith("RUNNER_") || name.startsWith("ACTIONS_") || RESERVED_SECRET_NAMES.has(name)) {
19633
+ throw new TypeError(`mobile ci github --secret-env cannot replace reserved variable ${name}.`);
19634
+ }
19635
+ }
19636
+ return names;
19637
+ }, customSecretEnvironment = (names, indentation = CI_ENV_INDENTATION) => names.map((name) => `${" ".repeat(indentation)}${name}: \${{ secrets.${name} }}`).join(`
19638
+ `), commandEnvironment = (options) => ` ABSOLUTE_CONFIG_PATH: ${yamlString(options.configPath ?? "")}
19639
+ ABSOLUTE_REGISTRY_MODULE: ${yamlString(options.registryModule)}
19640
+ ABSOLUTE_SERVER_ENTRY: ${yamlString(options.serverEntry)}`, appendConfigArgument = `if [[ -n "$ABSOLUTE_CONFIG_PATH" ]]; then
19641
+ args+=(--config "$ABSOLUTE_CONFIG_PATH")
19642
+ fi`, installSteps = ` - name: Check out source
19643
+ uses: actions/checkout@v6
19644
+ - name: Install Bun
19645
+ uses: oven-sh/setup-bun@v2
19646
+ - name: Install exact dependencies
19647
+ run: bun ci`, bundleAuditSteps, releaseAuditSteps = (platform6) => ` - name: Run redacted mobile release audit
19648
+ id: mobile-release-audit
19649
+ continue-on-error: true
19650
+ shell: bash
19651
+ run: |
19652
+ mkdir -p .absolutejs/mobile-ci
19653
+ args=(bunx absolute mobile doctor release ${platform6} --json)
19654
+ ${appendConfigArgument}
19655
+ "\${args[@]}" > .absolutejs/mobile-ci/compliance.json
19656
+ - name: Upload mobile compliance report
19657
+ if: always()
19658
+ uses: actions/upload-artifact@v7
19659
+ with:
19660
+ name: absolute-mobile-compliance-\${{ github.job }}
19661
+ path: .absolutejs/mobile-ci/compliance.json
19662
+ if-no-files-found: error
19663
+ retention-days: 30
19664
+ include-hidden-files: true
19665
+ - name: Enforce mobile release audit
19666
+ if: steps.mobile-release-audit.outcome != 'success'
19667
+ run: exit 1`, platformInput = (platforms) => {
19668
+ const choices = platforms.length === 2 ? ["all", ...platforms] : platforms;
19669
+ return ` platform:
19670
+ description: Native platform to build
19671
+ required: true
19672
+ type: choice
19673
+ default: ${choices[0]}
19674
+ options:
19675
+ ${choices.map((value) => ` - ${value}`).join(`
19676
+ `)}`;
19677
+ }, publishingInputs = (platforms, includePublishing) => {
19678
+ if (!includePublishing)
19679
+ return "";
19680
+ const fields = [
19681
+ ` publish:
19682
+ description: Publish through mobile.release.ts after the signed build
19683
+ required: true
19684
+ type: boolean
19685
+ default: false`,
19686
+ ` channel:
19687
+ description: Optional AbsoluteJS immutable release channel
19688
+ required: false
19689
+ type: string`
19690
+ ];
19691
+ if (platforms.includes("android"))
19692
+ fields.push(` play_track:
19693
+ description: Optional Google Play track
19694
+ required: true
19695
+ type: choice
19696
+ default: registry-only
19697
+ options:
19698
+ - registry-only
19699
+ - internal
19700
+ - alpha
19701
+ - beta
19702
+ - production`);
19703
+ if (platforms.includes("ios")) {
19704
+ fields.push(` testflight_group:
19705
+ description: Optional internal or external TestFlight group
19706
+ required: false
19707
+ type: string`);
19708
+ fields.push(` submit_testflight_review:
19709
+ description: Explicitly submit an external TestFlight build for review
19710
+ required: true
19711
+ type: boolean
19712
+ default: false`);
19713
+ }
19714
+ return `
19715
+ ${fields.join(`
19716
+ `)}`;
19717
+ }, jobCondition = (platform6) => `github.event_name == 'workflow_dispatch' && (inputs.platform == 'all' || inputs.platform == '${platform6}')`, androidJob = (options) => {
19718
+ const custom = customSecretEnvironment(options.customSecrets);
19719
+ const publishEnvironment = options.includePublishing ? `
19720
+ ABSOLUTE_PUBLISH: \${{ inputs.publish }}
19721
+ ABSOLUTE_RELEASE_CHANNEL: \${{ inputs.channel }}
19722
+ ABSOLUTE_PLAY_TRACK: \${{ inputs.play_track }}
19723
+ ABSOLUTE_GOOGLE_CREDENTIALS_BASE64: \${{ secrets.ABSOLUTE_GOOGLE_CREDENTIALS_BASE64 }}
19724
+ GOOGLE_APPLICATION_CREDENTIALS: \${{ runner.temp }}/absolute-google-credentials.json` : "";
19725
+ const publishCommand = options.includePublishing ? `if [[ "$ABSOLUTE_PUBLISH" == "true" ]]; then
19726
+ args=(bunx absolute mobile publish android "$ABSOLUTE_SERVER_ENTRY" --registry "$ABSOLUTE_REGISTRY_MODULE")
19727
+ if [[ -n "$ABSOLUTE_RELEASE_CHANNEL" ]]; then
19728
+ args+=(--channel "$ABSOLUTE_RELEASE_CHANNEL")
19729
+ fi
19730
+ if [[ "$ABSOLUTE_PLAY_TRACK" != "registry-only" ]]; then
19731
+ args+=(--play-track "$ABSOLUTE_PLAY_TRACK")
19732
+ fi
19733
+ else
19734
+ args=(bunx absolute mobile build android "$ABSOLUTE_SERVER_ENTRY")
19735
+ fi` : `args=(bunx absolute mobile build android "$ABSOLUTE_SERVER_ENTRY")`;
19736
+ const googleSetup = options.includePublishing ? `
19737
+ if [[ "$ABSOLUTE_PUBLISH" == "true" && "$ABSOLUTE_PLAY_TRACK" != "registry-only" ]]; then
19738
+ if [[ -z "$ABSOLUTE_GOOGLE_CREDENTIALS_BASE64" ]]; then
19739
+ echo "ABSOLUTE_GOOGLE_CREDENTIALS_BASE64 is required for Google Play publication." >&2
19740
+ exit 1
19741
+ fi
19742
+ printf '%s' "$ABSOLUTE_GOOGLE_CREDENTIALS_BASE64" | base64 --decode > "$GOOGLE_APPLICATION_CREDENTIALS"
19743
+ chmod 600 "$GOOGLE_APPLICATION_CREDENTIALS"
19744
+ fi` : "";
19745
+ return `
19746
+ android:
19747
+ name: Signed Android release
19748
+ needs: validate
19749
+ if: \${{ ${jobCondition("android")} }}
19750
+ runs-on: ubuntu-latest
19751
+ environment: absolute-mobile-release
19752
+ permissions:
19753
+ contents: read
19754
+ id-token: write
19755
+ attestations: write
19756
+ env:
19757
+ ABSOLUTE_ANDROID_KEYSTORE_BASE64: \${{ secrets.ABSOLUTE_ANDROID_KEYSTORE_BASE64 }}
19758
+ ABSOLUTE_ANDROID_KEYSTORE_PASSWORD: \${{ secrets.ABSOLUTE_ANDROID_KEYSTORE_PASSWORD }}
19759
+ ABSOLUTE_ANDROID_KEY_ALIAS: \${{ secrets.ABSOLUTE_ANDROID_KEY_ALIAS }}
19760
+ ABSOLUTE_ANDROID_KEY_PASSWORD: \${{ secrets.ABSOLUTE_ANDROID_KEY_PASSWORD }}
19761
+ ABSOLUTE_ANDROID_KEYSTORE_PATH: \${{ runner.temp }}/absolute-release.jks${publishEnvironment}${custom ? `
19762
+ ${custom}` : ""}
19763
+ ${commandEnvironment({ configPath: undefined, registryModule: "", serverEntry: "" })}
19764
+ steps:
19765
+ ${installSteps}
19766
+ - name: Provision Android signing
19767
+ shell: bash
19768
+ run: |
19769
+ required=(
19770
+ ABSOLUTE_ANDROID_KEYSTORE_BASE64
19771
+ ABSOLUTE_ANDROID_KEYSTORE_PASSWORD
19772
+ ABSOLUTE_ANDROID_KEY_ALIAS
19773
+ ABSOLUTE_ANDROID_KEY_PASSWORD
19774
+ )
19775
+ for name in "\${required[@]}"; do
19776
+ if [[ -z "\${!name}" ]]; then
19777
+ echo "$name is required in the absolute-mobile-release environment." >&2
19778
+ exit 1
19779
+ fi
19780
+ done
19781
+ printf '%s' "$ABSOLUTE_ANDROID_KEYSTORE_BASE64" | base64 --decode > "\${{ runner.temp }}/absolute-release.jks"
19782
+ chmod 600 "\${{ runner.temp }}/absolute-release.jks"${googleSetup}
19783
+ - name: Build or publish Android
19784
+ shell: bash
19785
+ run: |
19786
+ ${publishCommand}
19787
+ ${appendConfigArgument}
19788
+ "\${args[@]}"
19789
+ ${releaseAuditSteps("android")}
19790
+ - name: Attest Android App Bundle
19791
+ if: inputs.attest
19792
+ uses: actions/attest@v4
19793
+ with:
19794
+ subject-path: .absolutejs/mobile/releases/android/**/app-release.aab
19795
+ - name: Upload Android release
19796
+ uses: actions/upload-artifact@v7
19797
+ with:
19798
+ name: absolute-mobile-android
19799
+ path: .absolutejs/mobile/releases/android/
19800
+ if-no-files-found: error
19801
+ retention-days: 14
19802
+ include-hidden-files: true
19803
+ - name: Remove Android credentials
19804
+ if: always()
19805
+ shell: bash
19806
+ run: |
19807
+ rm -f "\${{ runner.temp }}/absolute-release.jks"
19808
+ rm -f "\${{ runner.temp }}/absolute-google-credentials.json"`;
19809
+ }, iosJob = (options) => {
19810
+ const custom = customSecretEnvironment(options.customSecrets);
19811
+ const publishEnvironment = options.includePublishing ? `
19812
+ ABSOLUTE_PUBLISH: \${{ inputs.publish }}
19813
+ ABSOLUTE_RELEASE_CHANNEL: \${{ inputs.channel }}
19814
+ ABSOLUTE_TESTFLIGHT_GROUP: \${{ inputs.testflight_group }}
19815
+ ABSOLUTE_TESTFLIGHT_SUBMIT_REVIEW: \${{ inputs.submit_testflight_review }}
19816
+ APP_STORE_CONNECT_ISSUER_ID: \${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
19817
+ APP_STORE_CONNECT_KEY_ID: \${{ secrets.APP_STORE_CONNECT_KEY_ID }}
19818
+ APP_STORE_CONNECT_PRIVATE_KEY_BASE64: \${{ secrets.APP_STORE_CONNECT_PRIVATE_KEY_BASE64 }}
19819
+ APP_STORE_CONNECT_PRIVATE_KEY_PATH: \${{ runner.temp }}/AuthKey_AbsoluteJS.p8` : "";
19820
+ const publishCommand = options.includePublishing ? `if [[ "$ABSOLUTE_PUBLISH" == "true" ]]; then
19821
+ args=(bunx absolute mobile publish ios "$ABSOLUTE_SERVER_ENTRY" --registry "$ABSOLUTE_REGISTRY_MODULE")
19822
+ if [[ -n "$ABSOLUTE_RELEASE_CHANNEL" ]]; then
19823
+ args+=(--channel "$ABSOLUTE_RELEASE_CHANNEL")
19824
+ fi
19825
+ if [[ -n "$ABSOLUTE_TESTFLIGHT_GROUP" ]]; then
19826
+ args+=(--testflight-group "$ABSOLUTE_TESTFLIGHT_GROUP")
19827
+ fi
19828
+ if [[ "$ABSOLUTE_TESTFLIGHT_SUBMIT_REVIEW" == "true" ]]; then
19829
+ args+=(--testflight-submit-review)
19830
+ fi
19831
+ else
19832
+ args=(bunx absolute mobile build ios "$ABSOLUTE_SERVER_ENTRY")
19833
+ fi` : `args=(bunx absolute mobile build ios "$ABSOLUTE_SERVER_ENTRY")`;
19834
+ const appStoreSetup = options.includePublishing ? `
19835
+ if [[ "$ABSOLUTE_PUBLISH" == "true" && -n "$ABSOLUTE_TESTFLIGHT_GROUP" ]]; then
19836
+ required+=(APP_STORE_CONNECT_ISSUER_ID APP_STORE_CONNECT_KEY_ID APP_STORE_CONNECT_PRIVATE_KEY_BASE64)
19837
+ fi` : "";
19838
+ const appStoreDecode = options.includePublishing ? `
19839
+ if [[ "$ABSOLUTE_PUBLISH" == "true" && -n "$ABSOLUTE_TESTFLIGHT_GROUP" ]]; then
19840
+ printf '%s' "$APP_STORE_CONNECT_PRIVATE_KEY_BASE64" | base64 --decode > "$APP_STORE_CONNECT_PRIVATE_KEY_PATH"
19841
+ chmod 600 "$APP_STORE_CONNECT_PRIVATE_KEY_PATH"
19842
+ fi` : "";
19843
+ return `
19844
+ ios:
19845
+ name: Signed iOS release
19846
+ needs: validate
19847
+ if: \${{ ${jobCondition("ios")} }}
19848
+ runs-on: macos-latest
19849
+ environment: absolute-mobile-release
19850
+ permissions:
19851
+ contents: read
19852
+ id-token: write
19853
+ attestations: write
19854
+ env:
19855
+ ABSOLUTE_IOS_CERTIFICATE_BASE64: \${{ secrets.ABSOLUTE_IOS_CERTIFICATE_BASE64 }}
19856
+ ABSOLUTE_IOS_CERTIFICATE_PASSWORD: \${{ secrets.ABSOLUTE_IOS_CERTIFICATE_PASSWORD }}
19857
+ ABSOLUTE_IOS_PROVISIONING_PROFILE_BASE64: \${{ secrets.ABSOLUTE_IOS_PROVISIONING_PROFILE_BASE64 }}
19858
+ ABSOLUTE_IOS_KEYCHAIN_PASSWORD: \${{ secrets.ABSOLUTE_IOS_KEYCHAIN_PASSWORD }}${publishEnvironment}${custom ? `
19859
+ ${custom}` : ""}
19860
+ ABSOLUTE_IOS_DEVELOPMENT_TEAM: \${{ secrets.ABSOLUTE_IOS_DEVELOPMENT_TEAM }}
19861
+ ${commandEnvironment({ configPath: undefined, registryModule: "", serverEntry: "" })}
19862
+ steps:
19863
+ ${installSteps}
19864
+ - name: Provision iOS signing
19865
+ shell: bash
19866
+ run: |
19867
+ required=(
19868
+ ABSOLUTE_IOS_CERTIFICATE_BASE64
19869
+ ABSOLUTE_IOS_CERTIFICATE_PASSWORD
19870
+ ABSOLUTE_IOS_PROVISIONING_PROFILE_BASE64
19871
+ ABSOLUTE_IOS_KEYCHAIN_PASSWORD
19872
+ ABSOLUTE_IOS_DEVELOPMENT_TEAM
19873
+ )${appStoreSetup}
19874
+ for name in "\${required[@]}"; do
19875
+ if [[ -z "\${!name}" ]]; then
19876
+ echo "$name is required in the absolute-mobile-release environment." >&2
19877
+ exit 1
19878
+ fi
19879
+ done
19880
+ CERTIFICATE_PATH="\${{ runner.temp }}/absolute-signing.p12"
19881
+ PROFILE_PATH="\${{ runner.temp }}/absolute.mobileprovision"
19882
+ KEYCHAIN_PATH="\${{ runner.temp }}/absolute-signing.keychain-db"
19883
+ PROFILE_DESTINATION="$HOME/Library/MobileDevice/Provisioning Profiles/absolute.mobileprovision"
19884
+ printf '%s' "$ABSOLUTE_IOS_CERTIFICATE_BASE64" | base64 --decode > "$CERTIFICATE_PATH"
19885
+ printf '%s' "$ABSOLUTE_IOS_PROVISIONING_PROFILE_BASE64" | base64 --decode > "$PROFILE_PATH"
19886
+ security create-keychain -p "$ABSOLUTE_IOS_KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
19887
+ security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
19888
+ security unlock-keychain -p "$ABSOLUTE_IOS_KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
19889
+ security import "$CERTIFICATE_PATH" -P "$ABSOLUTE_IOS_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"
19890
+ security set-key-partition-list -S apple-tool:,apple: -k "$ABSOLUTE_IOS_KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
19891
+ security list-keychain -d user -s "$KEYCHAIN_PATH"
19892
+ mkdir -p "$(dirname "$PROFILE_DESTINATION")"
19893
+ cp "$PROFILE_PATH" "$PROFILE_DESTINATION"${appStoreDecode}
19894
+ - name: Build or publish iOS
19895
+ shell: bash
19896
+ run: |
19897
+ ${publishCommand}
19898
+ ${appendConfigArgument}
19899
+ "\${args[@]}"
19900
+ ${releaseAuditSteps("ios")}
19901
+ - name: Attest iOS IPA
19902
+ if: inputs.attest
19903
+ uses: actions/attest@v4
19904
+ with:
19905
+ subject-path: .absolutejs/mobile/releases/ios/**/App.ipa
19906
+ - name: Upload iOS release
19907
+ uses: actions/upload-artifact@v7
19908
+ with:
19909
+ name: absolute-mobile-ios
19910
+ path: .absolutejs/mobile/releases/ios/
19911
+ if-no-files-found: error
19912
+ retention-days: 14
19913
+ include-hidden-files: true
19914
+ - name: Remove iOS credentials
19915
+ if: always()
19916
+ shell: bash
19917
+ run: |
19918
+ security delete-keychain "\${{ runner.temp }}/absolute-signing.keychain-db" 2>/dev/null || true
19919
+ rm -f "$HOME/Library/MobileDevice/Provisioning Profiles/absolute.mobileprovision"
19920
+ rm -f "\${{ runner.temp }}/absolute-signing.p12"
19921
+ rm -f "\${{ runner.temp }}/absolute.mobileprovision"
19922
+ rm -f "\${{ runner.temp }}/AuthKey_AbsoluteJS.p8"`;
19923
+ }, requiredSecrets = (platforms, includePublishing, custom) => [
19924
+ ...platforms.includes("android") ? [
19925
+ "ABSOLUTE_ANDROID_KEYSTORE_BASE64",
19926
+ "ABSOLUTE_ANDROID_KEYSTORE_PASSWORD",
19927
+ "ABSOLUTE_ANDROID_KEY_ALIAS",
19928
+ "ABSOLUTE_ANDROID_KEY_PASSWORD",
19929
+ ...includePublishing ? ["ABSOLUTE_GOOGLE_CREDENTIALS_BASE64"] : []
19930
+ ] : [],
19931
+ ...platforms.includes("ios") ? [
19932
+ "ABSOLUTE_IOS_CERTIFICATE_BASE64",
19933
+ "ABSOLUTE_IOS_CERTIFICATE_PASSWORD",
19934
+ "ABSOLUTE_IOS_PROVISIONING_PROFILE_BASE64",
19935
+ "ABSOLUTE_IOS_KEYCHAIN_PASSWORD",
19936
+ "ABSOLUTE_IOS_DEVELOPMENT_TEAM",
19937
+ ...includePublishing ? [
19938
+ "APP_STORE_CONNECT_ISSUER_ID",
19939
+ "APP_STORE_CONNECT_KEY_ID",
19940
+ "APP_STORE_CONNECT_PRIVATE_KEY_BASE64"
19941
+ ] : []
19942
+ ] : [],
19943
+ ...custom
19944
+ ], createAbsoluteMobileGithubWorkflow = (options) => {
19945
+ const platforms = [
19946
+ ...options.config.platforms
19947
+ ].sort();
19948
+ const includePublishing = options.includePublishing === true;
19949
+ const customSecrets = normalizeSecretEnvironment(options.secretEnvironment);
19950
+ const serverEntry = projectPath(options.projectRoot, options.serverEntry ?? "server.ts", "mobile ci github server entry");
19951
+ const configPath2 = options.configPath ? projectPath(options.projectRoot, options.configPath, "mobile ci github --config") : undefined;
19952
+ const registryModule = projectPath(options.projectRoot, options.registryModule ?? "mobile.release.ts", "mobile ci github --registry", { allowMissing: !includePublishing });
19953
+ const environment = commandEnvironment({
19954
+ configPath: configPath2,
19955
+ registryModule,
19956
+ serverEntry
19957
+ });
19958
+ let workflow = `# Generated by AbsoluteJS. Regenerate with: absolute mobile ci github${includePublishing ? " --publish" : ""}
19959
+ name: AbsoluteJS Mobile
19960
+
19961
+ on:
19962
+ pull_request:
19963
+ workflow_dispatch:
19964
+ inputs:
19965
+ ${platformInput(platforms)}
19966
+ attest:
19967
+ description: Generate GitHub artifact provenance attestations
19968
+ required: true
19969
+ type: boolean
19970
+ default: false${publishingInputs(platforms, includePublishing)}
19971
+
19972
+ concurrency:
19973
+ group: absolute-mobile-\${{ github.repository }}
19974
+ cancel-in-progress: false
19975
+
19976
+ jobs:
19977
+ validate:
19978
+ name: Validate mobile release inputs
19979
+ runs-on: ubuntu-latest
19980
+ permissions:
19981
+ contents: read
19982
+ env:
19983
+ ${environment}
19984
+ steps:
19985
+ ${installSteps}
19986
+ ${bundleAuditSteps}${platforms.includes("android") ? androidJob({ customSecrets, includePublishing }) : ""}${platforms.includes("ios") ? iosJob({ customSecrets, includePublishing }) : ""}
19987
+ `;
19988
+ const replacements = new Map([
19989
+ [
19990
+ "ABSOLUTE_CONFIG_PATH: ''",
19991
+ `ABSOLUTE_CONFIG_PATH: ${yamlString(configPath2 ?? "")}`
19992
+ ],
19993
+ [
19994
+ "ABSOLUTE_REGISTRY_MODULE: ''",
19995
+ `ABSOLUTE_REGISTRY_MODULE: ${yamlString(registryModule)}`
19996
+ ],
19997
+ [
19998
+ "ABSOLUTE_SERVER_ENTRY: ''",
19999
+ `ABSOLUTE_SERVER_ENTRY: ${yamlString(serverEntry)}`
20000
+ ]
20001
+ ]);
20002
+ for (const [placeholder, replacement] of replacements)
20003
+ workflow = workflow.replaceAll(placeholder, replacement);
20004
+ return {
20005
+ requiredSecrets: requiredSecrets(platforms, includePublishing, customSecrets),
20006
+ workflow
20007
+ };
20008
+ }, writeAbsoluteMobileGithubWorkflow = async (options) => {
20009
+ const path = workflowOutputPath(options.projectRoot, options.outputPath);
20010
+ const generated = createAbsoluteMobileGithubWorkflow(options);
20011
+ const previous = await exists3(path) ? await readFile22(path, "utf8") : undefined;
20012
+ if (previous !== undefined && previous !== generated.workflow && !options.force)
20013
+ throw new TypeError(`${relative29(options.projectRoot, path)} already exists and differs. Rerun with --force to replace the generated workflow.`);
20014
+ if (previous !== generated.workflow) {
20015
+ await mkdir14(dirname31(path), { recursive: true });
20016
+ await writeFile16(path, generated.workflow);
20017
+ }
20018
+ return {
20019
+ changed: previous !== generated.workflow,
20020
+ format: ABSOLUTE_MOBILE_CI_WORKFLOW_FORMAT,
20021
+ path,
20022
+ platforms: [...options.config.platforms].sort(),
20023
+ publishing: options.includePublishing === true,
20024
+ requiredSecrets: generated.requiredSecrets
20025
+ };
20026
+ };
20027
+ var init_ciWorkflow = __esm(() => {
20028
+ SECRET_NAME_PATTERN = /^[A-Z][A-Z0-9_]*$/u;
20029
+ RESERVED_SECRET_NAMES = new Set([
20030
+ "ABSOLUTE_ANDROID_KEYSTORE_BASE64",
20031
+ "ABSOLUTE_ANDROID_KEYSTORE_PASSWORD",
20032
+ "ABSOLUTE_ANDROID_KEY_ALIAS",
20033
+ "ABSOLUTE_ANDROID_KEY_PASSWORD",
20034
+ "ABSOLUTE_GOOGLE_CREDENTIALS_BASE64",
20035
+ "ABSOLUTE_IOS_CERTIFICATE_BASE64",
20036
+ "ABSOLUTE_IOS_CERTIFICATE_PASSWORD",
20037
+ "ABSOLUTE_IOS_PROVISIONING_PROFILE_BASE64",
20038
+ "ABSOLUTE_IOS_KEYCHAIN_PASSWORD",
20039
+ "ABSOLUTE_IOS_DEVELOPMENT_TEAM",
20040
+ "APP_STORE_CONNECT_ISSUER_ID",
20041
+ "APP_STORE_CONNECT_KEY_ID",
20042
+ "APP_STORE_CONNECT_PRIVATE_KEY_BASE64"
19666
20043
  ]);
20044
+ bundleAuditSteps = ` - name: Prepare production mobile bundle
20045
+ shell: bash
20046
+ run: |
20047
+ args=(bunx absolute prepare "$ABSOLUTE_SERVER_ENTRY" --outdir .absolutejs/mobile-ci/server)
20048
+ ${appendConfigArgument}
20049
+ "\${args[@]}"
20050
+ - name: Run cryptographic mobile bundle audit
20051
+ id: mobile-bundle-audit
20052
+ continue-on-error: true
20053
+ shell: bash
20054
+ run: |
20055
+ mkdir -p .absolutejs/mobile-ci
20056
+ args=(bunx absolute mobile inspect --json --require-bundle)
20057
+ ${appendConfigArgument}
20058
+ "\${args[@]}" > .absolutejs/mobile-ci/inspection.json
20059
+ - name: Upload mobile inspection report
20060
+ if: always()
20061
+ uses: actions/upload-artifact@v7
20062
+ with:
20063
+ name: absolute-mobile-inspection
20064
+ path: .absolutejs/mobile-ci/inspection.json
20065
+ if-no-files-found: error
20066
+ retention-days: 30
20067
+ include-hidden-files: true
20068
+ - name: Enforce mobile bundle audit
20069
+ if: steps.mobile-bundle-audit.outcome != 'success'
20070
+ run: exit 1`;
19667
20071
  });
19668
20072
 
19669
20073
  // src/cli/scripts/mobile.ts
@@ -19671,11 +20075,11 @@ var exports_mobile = {};
19671
20075
  __export(exports_mobile, {
19672
20076
  runMobile: () => runMobile
19673
20077
  });
19674
- import { access as access13, mkdir as mkdir14, readFile as readFile22, writeFile as writeFile16 } from "fs/promises";
19675
- import { join as join54, resolve as resolve43 } from "path";
20078
+ import { access as access14, mkdir as mkdir15, readFile as readFile23, writeFile as writeFile17 } from "fs/promises";
20079
+ import { join as join54, relative as relative30, resolve as resolve44 } from "path";
19676
20080
  import { createInterface } from "readline/promises";
19677
20081
  var NOT_FOUND4 = -1, isRecord15 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), CAPACITOR_PACKAGES, CAPACITOR_PACKAGE_SPECS, CAPACITOR_SYNC_PACKAGE_SPECS, packageNameFromSpec = (spec) => spec.slice(0, spec.lastIndexOf("@")), directProjectPackages = async (projectRoot) => {
19678
- const manifest = JSON.parse(await readFile22(join54(projectRoot, "package.json"), "utf8"));
20082
+ const manifest = JSON.parse(await readFile23(join54(projectRoot, "package.json"), "utf8"));
19679
20083
  if (!isRecord15(manifest))
19680
20084
  throw new TypeError("Application package.json must contain an object.");
19681
20085
  const names = new Set;
@@ -19688,7 +20092,7 @@ var NOT_FOUND4 = -1, isRecord15 = (value) => typeof value === "object" && value
19688
20092
  return names;
19689
20093
  }, resolvedPackageVersion = async (projectRoot, packageName) => {
19690
20094
  try {
19691
- const manifest = JSON.parse(await readFile22(join54(projectRoot, "node_modules", packageName, "package.json"), "utf8"));
20095
+ const manifest = JSON.parse(await readFile23(join54(projectRoot, "node_modules", packageName, "package.json"), "utf8"));
19692
20096
  return isRecord15(manifest) && typeof manifest.version === "string" ? manifest.version : undefined;
19693
20097
  } catch {
19694
20098
  return;
@@ -19731,7 +20135,7 @@ var NOT_FOUND4 = -1, isRecord15 = (value) => typeof value === "object" && value
19731
20135
  }, capacitorExecutable = async (projectRoot) => {
19732
20136
  const executable = join54(projectRoot, "node_modules", ".bin", "cap");
19733
20137
  try {
19734
- await access13(executable);
20138
+ await access14(executable);
19735
20139
  return executable;
19736
20140
  } catch {
19737
20141
  throw new TypeError(`Capacitor is not installed in this app. Run: bun add ${CAPACITOR_PACKAGES.join(" ")}`);
@@ -19758,11 +20162,19 @@ var NOT_FOUND4 = -1, isRecord15 = (value) => typeof value === "object" && value
19758
20162
  const report = await inspectAbsoluteMobileProject(mobile, projectRoot, {
19759
20163
  absolutejsVersion: await absolutejsVersionForReport()
19760
20164
  });
20165
+ const requireBundle = () => {
20166
+ if (!args.includes("--require-bundle"))
20167
+ return;
20168
+ if (report.bundle.status !== "valid" || report.capabilities.issue || report.capabilities.embeddedMatchesCurrent !== true)
20169
+ throw new TypeError("Mobile bundle validation failed. Regenerate the production bundle and resolve capability drift.");
20170
+ };
19761
20171
  if (args.includes("--json")) {
19762
20172
  console.log(JSON.stringify(report, null, 2));
20173
+ requireBundle();
19763
20174
  return report;
19764
20175
  }
19765
20176
  console.log(renderAbsoluteMobileProjectInspection(report).trimEnd());
20177
+ requireBundle();
19766
20178
  return report;
19767
20179
  }, remoteProfilePath = () => process.env.ABSOLUTE_REMOTE_MAC_PROFILE_PATH || undefined, pairRemoteMac = async (args) => {
19768
20180
  if (args[0] !== "mac" || !args[1] || !args[2])
@@ -19828,7 +20240,7 @@ var NOT_FOUND4 = -1, isRecord15 = (value) => typeof value === "object" && value
19828
20240
  await applyAbsoluteNativeBackgroundSync(projectRoot, mobile, platforms);
19829
20241
  }, associations = async (args) => {
19830
20242
  const { mobile, projectRoot } = await loadMobile(valueAfter(args, "--config"));
19831
- const outputDirectory = resolve43(projectRoot, valueAfter(args, "--outdir") ?? ".absolutejs/mobile/associations");
20243
+ const outputDirectory = resolve44(projectRoot, valueAfter(args, "--outdir") ?? ".absolutejs/mobile/associations");
19832
20244
  if (args.includes("--verify")) {
19833
20245
  const result2 = await verifyAbsoluteMobileAssociationFiles(mobile);
19834
20246
  console.log(`Verified ${result2.results.length} hosted association files`);
@@ -19839,6 +20251,59 @@ var NOT_FOUND4 = -1, isRecord15 = (value) => typeof value === "object" && value
19839
20251
  }
19840
20252
  const result = await materializeAbsoluteMobileAssociationFiles(mobile, outputDirectory);
19841
20253
  console.log(`Generated ${result.written.length} association files in ${result.root}`);
20254
+ }, mobileCiServerEntry = (args) => {
20255
+ const valueFlags = new Set([
20256
+ "--config",
20257
+ "--output",
20258
+ "--registry",
20259
+ "--secret-env"
20260
+ ]);
20261
+ const skipped = new Set;
20262
+ args.forEach((value, index) => {
20263
+ if (!valueFlags.has(value))
20264
+ return;
20265
+ skipped.add(index);
20266
+ skipped.add(index + 1);
20267
+ });
20268
+ return args.find((value, index) => !skipped.has(index) && value !== "github" && !value.startsWith("-")) ?? DEFAULT_SERVER_ENTRY;
20269
+ }, generateGithubCi = async (args) => {
20270
+ if (args[0] !== "github")
20271
+ throw new TypeError("Usage: absolute mobile ci github [server-entry] [--publish] [--registry module] [--secret-env NAME] [--output path] [--force] [--json] [--config path]");
20272
+ const configPath2 = valueAfter(args, "--config");
20273
+ const { mobile, projectRoot } = await loadMobile(configPath2);
20274
+ const result = await writeAbsoluteMobileGithubWorkflow({
20275
+ config: mobile,
20276
+ configPath: configPath2,
20277
+ force: args.includes("--force"),
20278
+ includePublishing: args.includes("--publish"),
20279
+ outputPath: valueAfter(args, "--output"),
20280
+ projectRoot,
20281
+ registryModule: valueAfter(args, "--registry"),
20282
+ secretEnvironment: valuesAfter(args, "--secret-env"),
20283
+ serverEntry: mobileCiServerEntry(args)
20284
+ });
20285
+ sendTelemetryEvent("mobile:ci-generated", {
20286
+ platformCount: result.platforms.length,
20287
+ provider: "github-actions",
20288
+ publishing: result.publishing
20289
+ });
20290
+ const publicResult = {
20291
+ changed: result.changed,
20292
+ format: result.format,
20293
+ path: relative30(projectRoot, result.path).replaceAll("\\", "/"),
20294
+ platforms: result.platforms,
20295
+ publishing: result.publishing,
20296
+ requiredSecrets: result.requiredSecrets
20297
+ };
20298
+ if (args.includes("--json")) {
20299
+ console.log(JSON.stringify(publicResult, null, 2));
20300
+ return publicResult;
20301
+ }
20302
+ console.log(`${result.changed ? "Generated" : "Verified"} ${publicResult.path} for ${result.platforms.join(" and ")}.`);
20303
+ console.log("Create a protected GitHub environment named absolute-mobile-release, then add these secrets:");
20304
+ result.requiredSecrets.forEach((name) => console.log(` ${name}`));
20305
+ console.log("Pull requests run the secret-free release audit. Signed builds and optional publishing run only through manual workflow dispatch.");
20306
+ return publicResult;
19842
20307
  }, doctorMark = (status2) => {
19843
20308
  if (status2 === "pass")
19844
20309
  return "\x1B[32m\u2713\x1B[0m";
@@ -19871,9 +20336,11 @@ var NOT_FOUND4 = -1, isRecord15 = (value) => typeof value === "object" && value
19871
20336
  }
19872
20337
  }, runReleaseDoctor = async (args) => {
19873
20338
  const { mobile, projectRoot } = await loadMobile(valueAfter(args, "--config"));
19874
- const result = await inspectAbsoluteMobileRelease(mobile, projectRoot);
20339
+ const platform6 = args.find((value) => value === "android" || value === "ios");
20340
+ const effectiveMobile = platform6 ? { ...mobile, platforms: [platform6] } : mobile;
20341
+ const result = await inspectAbsoluteMobileRelease(effectiveMobile, projectRoot);
19875
20342
  if (args.includes("--json")) {
19876
- console.log(JSON.stringify(createAbsoluteMobileComplianceReport(mobile, result), null, 2));
20343
+ console.log(JSON.stringify(createAbsoluteMobileComplianceReport(effectiveMobile, result), null, 2));
19877
20344
  } else {
19878
20345
  result.checks.forEach((check2) => {
19879
20346
  console.log(`${doctorMark(check2.status)} ${check2.detail}${check2.path ? ` (${check2.path})` : ""}`);
@@ -20029,6 +20496,27 @@ Mobile release security and compliance checks failed.`);
20029
20496
  status: check2.status
20030
20497
  })));
20031
20498
  throw new TypeError("Android release validation failed before Gradle signing.");
20499
+ }, androidCiSigning = () => {
20500
+ const keyAlias = process.env.ABSOLUTE_ANDROID_KEY_ALIAS;
20501
+ const keyPassword = process.env.ABSOLUTE_ANDROID_KEY_PASSWORD;
20502
+ const keystorePath = process.env.ABSOLUTE_ANDROID_KEYSTORE_PATH;
20503
+ const storePassword = process.env.ABSOLUTE_ANDROID_KEYSTORE_PASSWORD;
20504
+ const supplied = [
20505
+ keyAlias,
20506
+ keyPassword,
20507
+ keystorePath,
20508
+ storePassword
20509
+ ].filter((value) => value !== undefined && value !== "").length;
20510
+ if (supplied === 0)
20511
+ return;
20512
+ if (!keyAlias || !keyPassword || !keystorePath || !storePassword)
20513
+ throw new TypeError("AbsoluteJS CI signing requires ABSOLUTE_ANDROID_KEYSTORE_PATH, ABSOLUTE_ANDROID_KEYSTORE_PASSWORD, ABSOLUTE_ANDROID_KEY_ALIAS, and ABSOLUTE_ANDROID_KEY_PASSWORD together.");
20514
+ return {
20515
+ keyAlias,
20516
+ keyPasswordEnvironment: "ABSOLUTE_ANDROID_KEY_PASSWORD",
20517
+ keystorePath,
20518
+ storePasswordEnvironment: "ABSOLUTE_ANDROID_KEYSTORE_PASSWORD"
20519
+ };
20032
20520
  }, buildAndroid = async (args, prepareVersionCode) => {
20033
20521
  const configPath2 = valueAfter(args, "--config");
20034
20522
  const { mobile, projectRoot } = await loadMobile(configPath2);
@@ -20055,6 +20543,7 @@ Mobile release security and compliance checks failed.`);
20055
20543
  config: mobile,
20056
20544
  outputDirectory: valueAfter(args, "--outdir"),
20057
20545
  projectRoot,
20546
+ signing: androidCiSigning(),
20058
20547
  ...prepareVersionCode === undefined ? {} : { prepareVersionCode }
20059
20548
  });
20060
20549
  success = true;
@@ -20156,6 +20645,7 @@ Mobile release security and compliance checks failed.`);
20156
20645
  const release = await buildAbsoluteIosRelease({
20157
20646
  allowUnsigned: args.includes("--unsigned"),
20158
20647
  config: mobile,
20648
+ developmentTeam: process.env.ABSOLUTE_IOS_DEVELOPMENT_TEAM,
20159
20649
  outputDirectory: valueAfter(args, "--outdir"),
20160
20650
  ...prepareBuildNumber === undefined ? {} : { prepareBuildNumber },
20161
20651
  projectRoot
@@ -20364,7 +20854,7 @@ Emulator setup verification:`);
20364
20854
  }
20365
20855
  return { https: args.includes("--https"), port };
20366
20856
  }
20367
- const instances = listLiveInstances().filter((instance2) => resolve43(instance2.cwd) === resolve43(projectRoot) && instance2.source === "dev" && instance2.port !== null);
20857
+ const instances = listLiveInstances().filter((instance2) => resolve44(instance2.cwd) === resolve44(projectRoot) && instance2.source === "dev" && instance2.port !== null);
20368
20858
  if (instances.length !== 1) {
20369
20859
  throw new TypeError(instances.length === 0 ? "No running AbsoluteJS dev server was found for this project. Start `bun dev`, wait for Android to report ready, then run `absolute mobile test android`." : "Multiple dev servers are running for this project. Select one with mobile test android --port <port>.");
20370
20860
  }
@@ -20407,8 +20897,8 @@ Emulator setup verification:`);
20407
20897
  }
20408
20898
  return selected;
20409
20899
  }, safeArtifactRoot = (projectRoot, value) => {
20410
- const root = resolve43(projectRoot, value ?? ".absolutejs/mobile/test-artifacts");
20411
- if (root !== projectRoot && !root.startsWith(`${resolve43(projectRoot)}/`)) {
20900
+ const root = resolve44(projectRoot, value ?? ".absolutejs/mobile/test-artifacts");
20901
+ if (root !== projectRoot && !root.startsWith(`${resolve44(projectRoot)}/`)) {
20412
20902
  throw new TypeError("mobile test --artifacts must remain inside the project.");
20413
20903
  }
20414
20904
  return root;
@@ -20432,12 +20922,12 @@ Emulator setup verification:`);
20432
20922
  timeoutMs
20433
20923
  });
20434
20924
  }, writeAndroidFailureArtifacts = async (options) => {
20435
- await mkdir14(options.artifactRoot, { recursive: true });
20925
+ await mkdir15(options.artifactRoot, { recursive: true });
20436
20926
  const screenshot = options.session ? await options.session.screenshot(join54(options.artifactRoot, "android-failure.png")).catch(() => {
20437
20927
  return;
20438
20928
  }) : undefined;
20439
20929
  const diagnosticPath = join54(options.artifactRoot, "android-failure.json");
20440
- await writeFile16(diagnosticPath, `${JSON.stringify({
20930
+ await writeFile17(diagnosticPath, `${JSON.stringify({
20441
20931
  diagnostics: options.session?.diagnostics ?? [],
20442
20932
  error: options.error instanceof Error ? options.error.message : String(options.error),
20443
20933
  platform: "android",
@@ -20568,14 +21058,14 @@ Emulator setup verification:`);
20568
21058
  const port = Number(explicit);
20569
21059
  if (!Number.isInteger(port) || port < 1 || port > 65535)
20570
21060
  throw new TypeError("mobile test --port must be a valid TCP port.");
20571
- const instance2 = listLiveInstances().find((candidate) => resolve43(candidate.cwd) === resolve43(projectRoot) && candidate.source === "dev" && candidate.port === port);
21061
+ const instance2 = listLiveInstances().find((candidate) => resolve44(candidate.cwd) === resolve44(projectRoot) && candidate.source === "dev" && candidate.port === port);
20572
21062
  return {
20573
21063
  https: instance2?.https ?? args.includes("--https"),
20574
21064
  instance: instance2,
20575
21065
  port
20576
21066
  };
20577
21067
  }
20578
- const instances = listLiveInstances().filter((instance2) => resolve43(instance2.cwd) === resolve43(projectRoot) && instance2.source === "dev" && instance2.port !== null);
21068
+ const instances = listLiveInstances().filter((instance2) => resolve44(instance2.cwd) === resolve44(projectRoot) && instance2.source === "dev" && instance2.port !== null);
20579
21069
  if (instances.length !== 1)
20580
21070
  throw new TypeError(instances.length === 0 ? "No running AbsoluteJS dev server was found for this project. Start `bun dev`, wait for iOS to report ready, then run `absolute mobile test ios`." : "Multiple dev servers are running for this project. Select one with mobile test ios --port <port>.");
20581
21071
  const [instance] = instances;
@@ -20689,7 +21179,7 @@ Emulator setup verification:`);
20689
21179
  throw new Error(`${label} failed: ${result.stderr.trim() || result.stdout.trim() || `status ${result.exitCode}`}`);
20690
21180
  return result;
20691
21181
  }, writeIosFailureArtifacts = async (options) => {
20692
- await mkdir14(options.artifactRoot, { recursive: true });
21182
+ await mkdir15(options.artifactRoot, { recursive: true });
20693
21183
  const screenshot = join54(options.artifactRoot, "ios-failure.png");
20694
21184
  const screenshotResult = captureCommand4([
20695
21185
  options.xcrun,
@@ -20700,7 +21190,7 @@ Emulator setup verification:`);
20700
21190
  screenshot
20701
21191
  ]);
20702
21192
  const diagnosticPath = join54(options.artifactRoot, "ios-failure.json");
20703
- await writeFile16(diagnosticPath, `${JSON.stringify({
21193
+ await writeFile17(diagnosticPath, `${JSON.stringify({
20704
21194
  appId: options.appId,
20705
21195
  error: options.error instanceof Error ? options.error.message : String(options.error),
20706
21196
  platform: "ios",
@@ -20725,8 +21215,8 @@ Emulator setup verification:`);
20725
21215
  }, absolutejsVersionForReport = async () => {
20726
21216
  let absolutejsVersion = process.env.ABSOLUTE_VERSION ?? "unknown";
20727
21217
  const versions = await Promise.all([
20728
- resolve43(import.meta.dir, "..", "..", "package.json"),
20729
- resolve43(import.meta.dir, "..", "..", "..", "package.json")
21218
+ resolve44(import.meta.dir, "..", "..", "package.json"),
21219
+ resolve44(import.meta.dir, "..", "..", "..", "package.json")
20730
21220
  ].map((candidate) => readPackageVersionForIosReport(candidate).catch(() => "unknown")));
20731
21221
  for (const version2 of versions) {
20732
21222
  if (version2 === "unknown")
@@ -20944,7 +21434,7 @@ Emulator setup verification:`);
20944
21434
  mobile.appId
20945
21435
  ], "iOS app launch");
20946
21436
  await waitForIosHmrClient({ https, port, timeoutMs });
20947
- await mkdir14(artifactRoot, { recursive: true });
21437
+ await mkdir15(artifactRoot, { recursive: true });
20948
21438
  const screenshot = join54(artifactRoot, "ios-simulator.png");
20949
21439
  requireCapturedCommand([xcrun, "simctl", "io", simulator.udid, "screenshot", screenshot], "iOS simulator screenshot");
20950
21440
  const hmrApply = await waitForRequestedIosHmr(args, instance, timeoutMs);
@@ -21054,6 +21544,10 @@ Emulator setup verification:`);
21054
21544
  await associations(args.slice(1));
21055
21545
  return;
21056
21546
  }
21547
+ if (command === "ci") {
21548
+ await generateGithubCi(args.slice(1));
21549
+ return;
21550
+ }
21057
21551
  if (command === "doctor") {
21058
21552
  await doctor(args.slice(1));
21059
21553
  return;
@@ -21086,7 +21580,7 @@ Emulator setup verification:`);
21086
21580
  await publishIos(args.slice(2));
21087
21581
  return;
21088
21582
  }
21089
- throw new TypeError("Usage: absolute mobile <pair mac <name> <user@host> [--port n] [--workspace path] | remotes [--json] | unpair mac <name> | init [--no-native] [--force] | sync [ios|android] | inspect [--json] | associations [--outdir dir] [--verify] | doctor [ios|android|release] [--remote name] [--json|--fix [--yes]] | build <android|ios> [server-entry] [--outdir dir] [--web-outdir dir] [--unsigned] | publish android [server-entry] [--registry module] [--channel name] [--play-track track] [--play-status completed|draft|halted|in-progress] [--play-rollout fraction] [--play-name name] [--play-notes language=text] [--play-update-priority 0..5] [--play-hold-review] [--play-cancel-existing-review] [--outdir dir] [--web-outdir dir] [--unsigned] | publish ios [server-entry] [--registry module] [--channel name] [--testflight-group name-or-id] [--testflight-notes locale=text] [--testflight-submit-review] [--outdir dir] [--web-outdir dir] [--unsigned] | test android [--route path] [--wait-for-hmr] [--report [dir]] [--timeout ms] [--port n] [--serial id] [--artifacts dir] [--json] | test ios [--device identifier [--remote name] | --udid id] [--wait-for-hmr] [--report [dir]] [--timeout ms] [--port n] [--artifacts dir] [--json]> [--config path]");
21583
+ throw new TypeError("Usage: absolute mobile <pair mac <name> <user@host> [--port n] [--workspace path] | remotes [--json] | unpair mac <name> | init [--no-native] [--force] | sync [ios|android] | inspect [--json] [--require-bundle] | associations [--outdir dir] [--verify] | ci github [server-entry] [--publish] [--registry module] [--secret-env NAME] [--output path] [--force] [--json] | doctor [ios|android|release [ios|android]] [--remote name] [--json|--fix [--yes]] | build <android|ios> [server-entry] [--outdir dir] [--web-outdir dir] [--unsigned] | publish android [server-entry] [--registry module] [--channel name] [--play-track track] [--play-status completed|draft|halted|in-progress] [--play-rollout fraction] [--play-name name] [--play-notes language=text] [--play-update-priority 0..5] [--play-hold-review] [--play-cancel-existing-review] [--outdir dir] [--web-outdir dir] [--unsigned] | publish ios [server-entry] [--registry module] [--channel name] [--testflight-group name-or-id] [--testflight-notes locale=text] [--testflight-submit-review] [--outdir dir] [--web-outdir dir] [--unsigned] | test android [--route path] [--wait-for-hmr] [--report [dir]] [--timeout ms] [--port n] [--serial id] [--artifacts dir] [--json] | test ios [--device identifier [--remote name] | --udid id] [--wait-for-hmr] [--report [dir]] [--timeout ms] [--port n] [--artifacts dir] [--json]> [--config path]");
21090
21584
  };
21091
21585
  var init_mobile = __esm(() => {
21092
21586
  init_dependencies();
@@ -21121,6 +21615,7 @@ var init_mobile = __esm(() => {
21121
21615
  init_syncSchema();
21122
21616
  init_deviceCapabilities();
21123
21617
  init_mobileInspect();
21618
+ init_ciWorkflow();
21124
21619
  CAPACITOR_PACKAGES = [
21125
21620
  "@capacitor/core",
21126
21621
  "@capacitor/app",
@@ -21156,11 +21651,11 @@ var exports_typecheck = {};
21156
21651
  __export(exports_typecheck, {
21157
21652
  typecheck: () => typecheck
21158
21653
  });
21159
- import { resolve as resolve44, join as join55 } from "path";
21160
- import { existsSync as existsSync42, readFileSync as readFileSync40 } from "fs";
21161
- import { mkdir as mkdir15, writeFile as writeFile17 } from "fs/promises";
21162
- var isCommandService3 = (service) => service.kind === "command" || Array.isArray(service.command), resolveConfigPath = (configPath2) => resolve44(configPath2 ?? process.env.ABSOLUTE_CONFIG ?? "absolute.config.ts"), getTypecheckTargets = async (configPath2) => {
21163
- if (!existsSync42(resolveConfigPath(configPath2))) {
21654
+ import { resolve as resolve45, join as join55 } from "path";
21655
+ import { existsSync as existsSync43, readFileSync as readFileSync40 } from "fs";
21656
+ import { mkdir as mkdir16, writeFile as writeFile18 } from "fs/promises";
21657
+ var isCommandService3 = (service) => service.kind === "command" || Array.isArray(service.command), resolveConfigPath = (configPath2) => resolve45(configPath2 ?? process.env.ABSOLUTE_CONFIG ?? "absolute.config.ts"), getTypecheckTargets = async (configPath2) => {
21658
+ if (!existsSync43(resolveConfigPath(configPath2))) {
21164
21659
  const defaultService = {};
21165
21660
  return [defaultService];
21166
21661
  }
@@ -21181,8 +21676,8 @@ var isCommandService3 = (service) => service.kind === "command" || Array.isArray
21181
21676
  const exitCode = await proc.exited;
21182
21677
  return { exitCode, name, output: (stdout + stderr).trim() };
21183
21678
  }, shellEscape = (value) => `'${value.replaceAll("'", "'\\''")}'`, runShell = async (name, command) => run(name, ["/bin/bash", "-lc", command]), findBin = (name) => {
21184
- const local = resolve44("node_modules", ".bin", name);
21185
- return existsSync42(local) ? local : null;
21679
+ const local = resolve45("node_modules", ".bin", name);
21680
+ return existsSync43(local) ? local : null;
21186
21681
  }, ANSI_COLOR_REGEX, ANSI_PURPLE_REGEX, ANSI_CYAN_REGEX, ANSI_TOKEN_END_REGEX, stripAnsi4 = (str) => str.replace(ANSI_COLOR_REGEX, ""), formatSvelteOutput = (output) => {
21187
21682
  const cwd = `${process.cwd()}/`;
21188
21683
  const summaryMatch = stripAnsi4(output).match(/svelte-check found (\d+) error/);
@@ -21229,15 +21724,15 @@ Found ${errorCount} error${suffix}.`;
21229
21724
  return formatted;
21230
21725
  }, ABSOLUTE_INTERNAL_EXCLUDES, resolveAbsoluteTypeFile = (fileName) => {
21231
21726
  const candidates = [
21232
- resolve44("node_modules/@absolutejs/absolute/dist/types", fileName),
21233
- resolve44(import.meta.dir, "../types", fileName),
21234
- resolve44(import.meta.dir, "../../types", fileName),
21235
- resolve44(import.meta.dir, "../../../types", fileName)
21727
+ resolve45("node_modules/@absolutejs/absolute/dist/types", fileName),
21728
+ resolve45(import.meta.dir, "../types", fileName),
21729
+ resolve45(import.meta.dir, "../../types", fileName),
21730
+ resolve45(import.meta.dir, "../../../types", fileName)
21236
21731
  ];
21237
- return candidates.find((candidate) => existsSync42(candidate)) ?? candidates[0];
21732
+ return candidates.find((candidate) => existsSync43(candidate)) ?? candidates[0];
21238
21733
  }, ABSOLUTE_TYPECHECK_FILES, readProjectTsconfig = () => {
21239
21734
  try {
21240
- return JSON.parse(readFileSync40(resolve44("tsconfig.json"), "utf-8"));
21735
+ return JSON.parse(readFileSync40(resolve45("tsconfig.json"), "utf-8"));
21241
21736
  } catch {
21242
21737
  return {};
21243
21738
  }
@@ -21266,19 +21761,19 @@ Found ${errorCount} error${suffix}.`;
21266
21761
  process.exit(1);
21267
21762
  }
21268
21763
  const vueTsconfigPath = join55(cacheDir, "tsconfig.vue-check.json");
21269
- await writeFile17(vueTsconfigPath, JSON.stringify({
21764
+ await writeFile18(vueTsconfigPath, JSON.stringify({
21270
21765
  compilerOptions: {
21271
21766
  rootDir: ".."
21272
21767
  },
21273
21768
  exclude: getProjectTypecheckExcludes(),
21274
- extends: resolve44("tsconfig.json"),
21769
+ extends: resolve45("tsconfig.json"),
21275
21770
  include: getProjectTypecheckIncludes()
21276
21771
  }, null, "\t"));
21277
21772
  const base = [
21278
21773
  vueTscBin,
21279
21774
  "--noEmit",
21280
21775
  "--project",
21281
- resolve44(vueTsconfigPath),
21776
+ resolve45(vueTsconfigPath),
21282
21777
  "--pretty"
21283
21778
  ];
21284
21779
  const cached = await run("vue-tsc", [
@@ -21297,7 +21792,7 @@ Found ${errorCount} error${suffix}.`;
21297
21792
  process.exit(1);
21298
21793
  }
21299
21794
  const angularTsconfigPath = join55(cacheDir, "tsconfig.angular-check.json");
21300
- await writeFile17(angularTsconfigPath, JSON.stringify({
21795
+ await writeFile18(angularTsconfigPath, JSON.stringify({
21301
21796
  angularCompilerOptions: {
21302
21797
  strictTemplates: true
21303
21798
  },
@@ -21306,10 +21801,10 @@ Found ${errorCount} error${suffix}.`;
21306
21801
  rootDir: ".."
21307
21802
  },
21308
21803
  exclude: ABSOLUTE_INTERNAL_EXCLUDES.map(toGeneratedConfigPath),
21309
- extends: resolve44("tsconfig.json"),
21804
+ extends: resolve45("tsconfig.json"),
21310
21805
  include: [`../${angularDir}/**/*`]
21311
21806
  }, null, "\t"));
21312
- return runShell("ngc", `${shellEscape(ngcBin)} -p ${shellEscape(resolve44(angularTsconfigPath))}`);
21807
+ return runShell("ngc", `${shellEscape(ngcBin)} -p ${shellEscape(resolve45(angularTsconfigPath))}`);
21313
21808
  }, buildTscCheck = (cacheDir) => {
21314
21809
  const tscBin = findBin("tsc");
21315
21810
  if (!tscBin) {
@@ -21317,18 +21812,18 @@ Found ${errorCount} error${suffix}.`;
21317
21812
  process.exit(1);
21318
21813
  }
21319
21814
  const tscConfigPath = join55(cacheDir, "tsconfig.typecheck.json");
21320
- return writeFile17(tscConfigPath, JSON.stringify({
21815
+ return writeFile18(tscConfigPath, JSON.stringify({
21321
21816
  compilerOptions: {
21322
21817
  rootDir: ".."
21323
21818
  },
21324
21819
  exclude: getProjectTypecheckExcludes(),
21325
- extends: resolve44("tsconfig.json"),
21820
+ extends: resolve45("tsconfig.json"),
21326
21821
  include: getProjectTypecheckIncludes()
21327
21822
  }, null, "\t")).then(() => run("tsc", [
21328
21823
  tscBin,
21329
21824
  "--noEmit",
21330
21825
  "--project",
21331
- resolve44(tscConfigPath),
21826
+ resolve45(tscConfigPath),
21332
21827
  "--incremental",
21333
21828
  "--tsBuildInfoFile",
21334
21829
  join55(cacheDir, "tsc.tsbuildinfo"),
@@ -21341,15 +21836,15 @@ Found ${errorCount} error${suffix}.`;
21341
21836
  process.exit(1);
21342
21837
  }
21343
21838
  const svelteTsconfigPath = join55(cacheDir, "tsconfig.svelte-check.json");
21344
- await writeFile17(svelteTsconfigPath, JSON.stringify({
21345
- extends: resolve44("tsconfig.json"),
21839
+ await writeFile18(svelteTsconfigPath, JSON.stringify({
21840
+ extends: resolve45("tsconfig.json"),
21346
21841
  files: ABSOLUTE_TYPECHECK_FILES,
21347
21842
  include: [`../${svelteDir}/**/*`]
21348
21843
  }, null, "\t"));
21349
21844
  return run("svelte-check", [
21350
21845
  svelteBin,
21351
21846
  "--tsconfig",
21352
- resolve44(svelteTsconfigPath),
21847
+ resolve45(svelteTsconfigPath),
21353
21848
  "--threshold",
21354
21849
  "error",
21355
21850
  "--compiler-warnings",
@@ -21370,7 +21865,7 @@ Found ${errorCount} error${suffix}.`;
21370
21865
  ...new Set(targets.map((config) => config.angularDirectory).filter((dir) => typeof dir === "string" && dir.length > 0))
21371
21866
  ];
21372
21867
  const cacheDir = ".absolutejs";
21373
- await mkdir15(cacheDir, { recursive: true });
21868
+ await mkdir16(cacheDir, { recursive: true });
21374
21869
  const checks = [];
21375
21870
  checks.push(hasVue ? buildVueTscCheck(cacheDir) : buildTscCheck(cacheDir));
21376
21871
  for (const svelteDir of hasSvelte ? svelteDirs : []) {
@@ -21543,11 +22038,11 @@ var DEFAULT_RELAY_PORT = 8787, DEFAULT_REQUEST_TIMEOUT_MS = 30000, headersToObje
21543
22038
  url: url.pathname + url.search,
21544
22039
  ...bodyBytes && bodyBytes.length > 0 ? { bodyBase64: Buffer.from(bodyBytes).toString("base64") } : {}
21545
22040
  };
21546
- const responsePromise = new Promise((resolve45) => {
21547
- pending.set(id, resolve45);
22041
+ const responsePromise = new Promise((resolve46) => {
22042
+ pending.set(id, resolve46);
21548
22043
  });
21549
22044
  client.send(encodeTunnelMessage(message));
21550
- const timeout = new Promise((resolve45) => setTimeout(() => resolve45({ id, message: "timeout", type: "error" }), requestTimeoutMs));
22045
+ const timeout = new Promise((resolve46) => setTimeout(() => resolve46({ id, message: "timeout", type: "error" }), requestTimeoutMs));
21551
22046
  const result = await Promise.race([responsePromise, timeout]);
21552
22047
  pending.delete(id);
21553
22048
  if (result.type === "error") {
@@ -25460,7 +25955,7 @@ if (command === "dev") {
25460
25955
  console.error(" prepare [entry] [--outdir dir] Build production assets and server without launching");
25461
25956
  console.error(" start [entry] [--outdir dir] [--prebuilt] Start production server");
25462
25957
  console.error(" compile [entry] [--outdir dir] [--outfile path] Compile standalone executable");
25463
- console.error(" mobile <init|sync|inspect|pair|remotes|doctor|test> Manage Capacitor projects, simulators, physical devices, Remote Macs, guided setup, and deep links");
25958
+ console.error(" mobile <init|sync|inspect|ci|pair|remotes|doctor|test> Manage Capacitor projects, CI, simulators, physical devices, Remote Macs, guided setup, and deep links");
25464
25959
  console.error(" config [--port n] Open the unified config UI (ESLint, tsconfig, Prettier)");
25465
25960
  console.error(" db <backup|restore|seed> Backup/restore any Postgres DB (ORM-agnostic, upsert by PK) or run the seed script");
25466
25961
  console.error(" doctor [--fix] [--json] Diagnose the project (bun, type graph, config, framework dirs, env, port)");