@akanjs/cli 2.4.0-rc.7 → 2.4.0-rc.9
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/incrementalBuilder.proc.js +410 -63
- package/index.js +360 -45
- package/package.json +2 -2
|
@@ -722,7 +722,6 @@ var SSR_RUNTIME_PACKAGES = ["react", "react-dom", "react-server-dom-webpack"];
|
|
|
722
722
|
var NATIVE_RUNTIME_PACKAGES = ["sharp"];
|
|
723
723
|
var DEFAULT_BACKEND_RUNTIME_PACKAGES = ["croner"];
|
|
724
724
|
var MOBILE_RUNTIME_PACKAGES = [
|
|
725
|
-
"firebase",
|
|
726
725
|
"@capacitor/cli",
|
|
727
726
|
"@capacitor/core",
|
|
728
727
|
"@capacitor/ios",
|
|
@@ -730,7 +729,6 @@ var MOBILE_RUNTIME_PACKAGES = [
|
|
|
730
729
|
"@capacitor/assets"
|
|
731
730
|
];
|
|
732
731
|
var MOBILE_APP_CAPACITOR_PLUGINS = [
|
|
733
|
-
"@capacitor-community/fcm",
|
|
734
732
|
"@capacitor/app",
|
|
735
733
|
"@capacitor/browser",
|
|
736
734
|
"@capacitor/camera",
|
|
@@ -780,6 +778,13 @@ var normalizeStringList = (values) => {
|
|
|
780
778
|
const normalized = values?.map((value) => value.trim()).filter(Boolean) ?? [];
|
|
781
779
|
return normalized.length > 0 ? [...new Set(normalized)] : undefined;
|
|
782
780
|
};
|
|
781
|
+
var sanitizeAppIdSegment = (value) => {
|
|
782
|
+
const cleaned = value.toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
783
|
+
if (!cleaned)
|
|
784
|
+
return "app";
|
|
785
|
+
return /^[a-z]/.test(cleaned) ? cleaned : `app${cleaned}`;
|
|
786
|
+
};
|
|
787
|
+
var deriveDefaultAppId = (orgName, appName) => `com.${sanitizeAppIdSegment(orgName)}.${sanitizeAppIdSegment(appName)}`;
|
|
783
788
|
var normalizeDeepLinkDomain = (domain) => {
|
|
784
789
|
const normalized = domain.trim();
|
|
785
790
|
if (!normalized)
|
|
@@ -820,6 +825,7 @@ class AkanAppConfig {
|
|
|
820
825
|
i18n;
|
|
821
826
|
publicEnv;
|
|
822
827
|
mobile;
|
|
828
|
+
hasMobileConfig;
|
|
823
829
|
secrets;
|
|
824
830
|
baseDevEnv;
|
|
825
831
|
libs;
|
|
@@ -850,6 +856,7 @@ class AkanAppConfig {
|
|
|
850
856
|
process.env.AKAN_PUBLIC_LOCALES = this.i18n.locales.join(",");
|
|
851
857
|
this.publicEnv = config?.publicEnv ?? [];
|
|
852
858
|
this.secrets = config?.secrets ?? [];
|
|
859
|
+
this.hasMobileConfig = Boolean(config.mobile);
|
|
853
860
|
this.mobile = this.#resolveMobileConfig(config.mobile);
|
|
854
861
|
this.docker = this.#makeDockerContent(config?.docker ?? {});
|
|
855
862
|
}
|
|
@@ -860,7 +867,7 @@ class AkanAppConfig {
|
|
|
860
867
|
...rawMobile
|
|
861
868
|
} = mobile ?? {};
|
|
862
869
|
const appName = rawMobile.appName ?? this.app.name;
|
|
863
|
-
const appId = rawMobile.appId ??
|
|
870
|
+
const appId = rawMobile.appId ?? deriveDefaultAppId(this.baseDevEnv.repoName, this.app.name);
|
|
864
871
|
const version = rawMobile.version ?? "0.0.1";
|
|
865
872
|
const buildNum = rawMobile.buildNum ?? 1;
|
|
866
873
|
const defaultTargetName = this.#defaultMobileTargetName(rawTargets);
|
|
@@ -4326,6 +4333,8 @@ var BACKEND_RECOVERY_MAX_ATTEMPTS = 5;
|
|
|
4326
4333
|
var BACKEND_STDERR_TAIL_LIMIT = 40;
|
|
4327
4334
|
var BUILDER_READY_TIMEOUT_MS = 150000;
|
|
4328
4335
|
var BUILDER_START_MAX_ATTEMPTS = 3;
|
|
4336
|
+
var BUILDER_RECOVERY_BASE_DELAY_MS = 2000;
|
|
4337
|
+
var BUILDER_RECOVERY_MAX_DELAY_MS = 60000;
|
|
4329
4338
|
var SOURCE_EXTS = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
4330
4339
|
var NON_SOURCE_EXT_RE = /\.(css|scss|sass|less|json|svg|png|jpe?g|webp|gif|avif|ico|woff2?|ttf|otf|mp3|mp4|wav|html)$/i;
|
|
4331
4340
|
var SERVER_SUFFIXES = [".service.ts", ".document.ts"];
|
|
@@ -4402,6 +4411,43 @@ var mergeBackendRestartReasons = (current, next) => ({
|
|
|
4402
4411
|
});
|
|
4403
4412
|
var shouldReplaceLastGoodMessage = (current, next) => !current || generationValue(next.data.generation) >= generationValue(current.data.generation);
|
|
4404
4413
|
var shouldQueueBuildStatusReplay = (backendReady, pendingReplayCount) => !backendReady || pendingReplayCount > 0;
|
|
4414
|
+
var hasBuildFailureForGeneration = (statusByPhase, generation) => {
|
|
4415
|
+
if (typeof generation !== "number")
|
|
4416
|
+
return false;
|
|
4417
|
+
for (const status of statusByPhase.values()) {
|
|
4418
|
+
if (!status.ok && status.generation === generation)
|
|
4419
|
+
return true;
|
|
4420
|
+
}
|
|
4421
|
+
return false;
|
|
4422
|
+
};
|
|
4423
|
+
var mergeDevPlans = (current, next) => {
|
|
4424
|
+
if (!current)
|
|
4425
|
+
return next;
|
|
4426
|
+
if (!next)
|
|
4427
|
+
return current;
|
|
4428
|
+
const reasonByFile = { ...current.reasonByFile };
|
|
4429
|
+
for (const [file, reasons] of Object.entries(next.reasonByFile)) {
|
|
4430
|
+
reasonByFile[file] = [...new Set([...reasonByFile[file] ?? [], ...reasons])].sort();
|
|
4431
|
+
}
|
|
4432
|
+
return {
|
|
4433
|
+
generation: Math.max(current.generation, next.generation),
|
|
4434
|
+
files: [...new Set([...current.files, ...next.files])].sort(),
|
|
4435
|
+
generatedFiles: [...new Set([...current.generatedFiles, ...next.generatedFiles])].sort(),
|
|
4436
|
+
roles: [...new Set([...current.roles, ...next.roles])].sort(),
|
|
4437
|
+
actions: [...new Set([...current.actions, ...next.actions])].sort(),
|
|
4438
|
+
reasonByFile
|
|
4439
|
+
};
|
|
4440
|
+
};
|
|
4441
|
+
var mergeInvalidateMessages = (current, next) => {
|
|
4442
|
+
const generation = Math.max(generationValue(current.generation), generationValue(next.generation));
|
|
4443
|
+
return {
|
|
4444
|
+
type: "invalidate",
|
|
4445
|
+
kinds: [...new Set([...current.kinds, ...next.kinds])].sort(),
|
|
4446
|
+
files: [...new Set([...current.files, ...next.files])].sort(),
|
|
4447
|
+
generation: generation >= 0 ? generation : undefined,
|
|
4448
|
+
devPlan: mergeDevPlans(current.devPlan, next.devPlan)
|
|
4449
|
+
};
|
|
4450
|
+
};
|
|
4405
4451
|
var buildStatusReplaySequence = (pendingReplay, latestByPhase) => [...pendingReplay, ...latestByPhase.values()];
|
|
4406
4452
|
|
|
4407
4453
|
class BackendImportGraph {
|
|
@@ -4524,8 +4570,12 @@ class AkanAppHost {
|
|
|
4524
4570
|
#restartTimer = null;
|
|
4525
4571
|
#backendRecoveryTimer = null;
|
|
4526
4572
|
#backendRecoveryAttempts = 0;
|
|
4573
|
+
#backendGaveUp = false;
|
|
4527
4574
|
#backendLifecycleState = "stopped";
|
|
4528
4575
|
#pendingRestartReason = null;
|
|
4576
|
+
#pendingRecycle = null;
|
|
4577
|
+
#builderRecoveryTimer = null;
|
|
4578
|
+
#builderRecoveryAttempts = 0;
|
|
4529
4579
|
#backendStartStatus = null;
|
|
4530
4580
|
#backendBuildStatusGeneration = 0;
|
|
4531
4581
|
#backendStderrTail = [];
|
|
@@ -4563,6 +4613,10 @@ class AkanAppHost {
|
|
|
4563
4613
|
clearTimeout(this.#backendRecoveryTimer);
|
|
4564
4614
|
this.#backendRecoveryTimer = null;
|
|
4565
4615
|
}
|
|
4616
|
+
if (this.#builderRecoveryTimer) {
|
|
4617
|
+
clearTimeout(this.#builderRecoveryTimer);
|
|
4618
|
+
this.#builderRecoveryTimer = null;
|
|
4619
|
+
}
|
|
4566
4620
|
await this.#stopBackend();
|
|
4567
4621
|
this.#stopBuilder();
|
|
4568
4622
|
return this;
|
|
@@ -4578,6 +4632,7 @@ class AkanAppHost {
|
|
|
4578
4632
|
}
|
|
4579
4633
|
#startBackend(startStatus = null) {
|
|
4580
4634
|
this.#backendStartStatus = startStatus;
|
|
4635
|
+
this.#backendGaveUp = false;
|
|
4581
4636
|
this.#setBackendLifecycleState("starting");
|
|
4582
4637
|
this.#backendReady = false;
|
|
4583
4638
|
this.#backendStderrTail = [];
|
|
@@ -4769,7 +4824,8 @@ class AkanAppHost {
|
|
|
4769
4824
|
if (this.#backendRecoveryTimer || this.#backend)
|
|
4770
4825
|
return;
|
|
4771
4826
|
if (shouldAbandonBackendRecovery(this.#backendRecoveryAttempts)) {
|
|
4772
|
-
const message = `Backend exited ${this.#backendRecoveryAttempts} times in a row (${reason}); waiting for a
|
|
4827
|
+
const message = `Backend exited ${this.#backendRecoveryAttempts} times in a row (${reason}); waiting for an edit or a green build to retry.`;
|
|
4828
|
+
this.#backendGaveUp = true;
|
|
4773
4829
|
this.#setBackendLifecycleState("stopped", `gave up after ${this.#backendRecoveryAttempts} recovery attempts`);
|
|
4774
4830
|
this.logger.error(`[backend-recovery] ${message}`);
|
|
4775
4831
|
if (this.#backendStderrTail.length > 0) {
|
|
@@ -4816,6 +4872,7 @@ ${this.#backendStderrTail.join(`
|
|
|
4816
4872
|
if (message.type === "build-status") {
|
|
4817
4873
|
this.#recordBuildStatus(message.data);
|
|
4818
4874
|
this.#sendOrQueueBuildStatus(message.data);
|
|
4875
|
+
this.#reviveBackendAfterGreenBuild(message.data);
|
|
4819
4876
|
return;
|
|
4820
4877
|
}
|
|
4821
4878
|
if (message.type === "pages-updated")
|
|
@@ -4830,19 +4887,25 @@ ${this.#backendStderrTail.join(`
|
|
|
4830
4887
|
}
|
|
4831
4888
|
async#handleInvalidate(message) {
|
|
4832
4889
|
this.#logDevPlan(message);
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4890
|
+
const wantsDevHostRestart = shouldRestartDevHostByDevPlan(message);
|
|
4891
|
+
const pending = this.#pendingRecycle;
|
|
4892
|
+
if (wantsDevHostRestart || shouldRestartBuilderByDevPlan(message) || pending && message.kinds.includes("code")) {
|
|
4893
|
+
const refreshConfig = wantsDevHostRestart || (pending?.refreshConfig ?? false);
|
|
4894
|
+
const merged = pending ? mergeInvalidateMessages(pending.message, message) : message;
|
|
4895
|
+
const generation = message.devPlan?.generation ?? message.generation;
|
|
4896
|
+
if (hasBuildFailureForGeneration(this.#buildStatusByPhase, generation)) {
|
|
4897
|
+
this.#deferRecycle(merged, { refreshConfig, generation });
|
|
4898
|
+
return;
|
|
4838
4899
|
}
|
|
4839
|
-
|
|
4840
|
-
}
|
|
4841
|
-
if (shouldRestartBuilderByDevPlan(message)) {
|
|
4900
|
+
this.#pendingRecycle = null;
|
|
4842
4901
|
try {
|
|
4843
|
-
|
|
4902
|
+
if (refreshConfig)
|
|
4903
|
+
await this.#restartDevHost(merged);
|
|
4904
|
+
else
|
|
4905
|
+
await this.#restartDevChildren(merged);
|
|
4844
4906
|
} catch (err) {
|
|
4845
|
-
this.#recordDevHostRestartFailure(
|
|
4907
|
+
this.#recordDevHostRestartFailure(merged, err, refreshConfig ? "Config" : "Runtime metadata");
|
|
4908
|
+
this.#resurrectDevChildren(merged);
|
|
4846
4909
|
}
|
|
4847
4910
|
return;
|
|
4848
4911
|
}
|
|
@@ -4852,6 +4915,70 @@ ${this.#backendStderrTail.join(`
|
|
|
4852
4915
|
}
|
|
4853
4916
|
this.#sendToBackend(message);
|
|
4854
4917
|
}
|
|
4918
|
+
#deferRecycle(message, { refreshConfig, generation }) {
|
|
4919
|
+
this.#pendingRecycle = { message, refreshConfig };
|
|
4920
|
+
const kind = refreshConfig ? "Config" : "Runtime metadata";
|
|
4921
|
+
this.logger.warn(`[dev-host] ${kind.toLowerCase()} restart deferred generation=${generation ?? "(unknown)"}; keeping the running dev server until the build error is fixed`);
|
|
4922
|
+
const status = {
|
|
4923
|
+
generation: generation ?? this.#nextBackendBuildStatusGeneration(),
|
|
4924
|
+
phase: "scan",
|
|
4925
|
+
ok: false,
|
|
4926
|
+
files: message.files,
|
|
4927
|
+
message: `${kind} change is on hold while the build is failing; it will apply automatically once the error is fixed.`
|
|
4928
|
+
};
|
|
4929
|
+
this.#recordBuildStatus(status);
|
|
4930
|
+
this.#sendOrQueueBuildStatus(status);
|
|
4931
|
+
}
|
|
4932
|
+
#resurrectDevChildren(message) {
|
|
4933
|
+
const generation = message.devPlan?.generation ?? message.generation;
|
|
4934
|
+
if (!this.#backend)
|
|
4935
|
+
this.#startBackend({ generation, files: message.files });
|
|
4936
|
+
this.#scheduleBuilderRecovery({ generation, files: message.files });
|
|
4937
|
+
}
|
|
4938
|
+
#scheduleBuilderRecovery(reason) {
|
|
4939
|
+
if (this.#builderRecoveryTimer || this.#builder)
|
|
4940
|
+
return;
|
|
4941
|
+
const attempt = this.#builderRecoveryAttempts;
|
|
4942
|
+
const delay = Math.min(BUILDER_RECOVERY_BASE_DELAY_MS * 2 ** attempt, BUILDER_RECOVERY_MAX_DELAY_MS);
|
|
4943
|
+
this.#builderRecoveryAttempts = attempt + 1;
|
|
4944
|
+
this.logger.warn(`[builder-recovery] builder is down; retrying start in ${delay}ms (attempt ${this.#builderRecoveryAttempts})`);
|
|
4945
|
+
this.#builderRecoveryTimer = setTimeout(() => {
|
|
4946
|
+
this.#builderRecoveryTimer = null;
|
|
4947
|
+
if (this.#builder)
|
|
4948
|
+
return;
|
|
4949
|
+
this.#recoverBuilder(reason);
|
|
4950
|
+
}, delay);
|
|
4951
|
+
}
|
|
4952
|
+
async#recoverBuilder(reason) {
|
|
4953
|
+
try {
|
|
4954
|
+
await this.#startBuilder();
|
|
4955
|
+
} catch (err) {
|
|
4956
|
+
this.logger.warn(`[builder-recovery] builder start failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
4957
|
+
this.#scheduleBuilderRecovery(reason);
|
|
4958
|
+
return;
|
|
4959
|
+
}
|
|
4960
|
+
this.#builderRecoveryAttempts = 0;
|
|
4961
|
+
this.logger.info("[builder-recovery] builder recovered");
|
|
4962
|
+
const status = {
|
|
4963
|
+
generation: reason.generation ?? this.#nextBackendBuildStatusGeneration(),
|
|
4964
|
+
phase: "scan",
|
|
4965
|
+
ok: true,
|
|
4966
|
+
files: reason.files,
|
|
4967
|
+
message: "Builder recovered"
|
|
4968
|
+
};
|
|
4969
|
+
this.#recordBuildStatus(status);
|
|
4970
|
+
this.#sendOrQueueBuildStatus(status);
|
|
4971
|
+
if (!this.#backend && !this.#backendRecoveryTimer) {
|
|
4972
|
+
this.#startBackend({ generation: reason.generation, files: reason.files });
|
|
4973
|
+
}
|
|
4974
|
+
}
|
|
4975
|
+
#reviveBackendAfterGreenBuild(status) {
|
|
4976
|
+
if (!status.ok || !this.#backendGaveUp || this.#backend || this.#backendRecoveryTimer)
|
|
4977
|
+
return;
|
|
4978
|
+
this.logger.info(`[backend-recovery] build went green (generation=${status.generation}); retrying backend`);
|
|
4979
|
+
this.#backendRecoveryAttempts = 0;
|
|
4980
|
+
this.#startBackend({ generation: status.generation, files: status.files });
|
|
4981
|
+
}
|
|
4855
4982
|
async#restartDevChildren(message) {
|
|
4856
4983
|
const generation = message.devPlan?.generation ?? message.generation;
|
|
4857
4984
|
this.logger.warn(`[dev-host] recycling builder/backend for runtime metadata generation=${generation ?? "(unknown)"} files=${message.files.length}`);
|
|
@@ -4872,6 +4999,11 @@ ${this.#backendStderrTail.join(`
|
|
|
4872
4999
|
clearTimeout(this.#backendRecoveryTimer);
|
|
4873
5000
|
this.#backendRecoveryTimer = null;
|
|
4874
5001
|
}
|
|
5002
|
+
if (this.#builderRecoveryTimer) {
|
|
5003
|
+
clearTimeout(this.#builderRecoveryTimer);
|
|
5004
|
+
this.#builderRecoveryTimer = null;
|
|
5005
|
+
}
|
|
5006
|
+
this.#builderRecoveryAttempts = 0;
|
|
4875
5007
|
this.#pendingRestartReason = null;
|
|
4876
5008
|
this.#lastGoodFrontend = {};
|
|
4877
5009
|
this.#buildStatusByPhase.clear();
|
|
@@ -4909,7 +5041,7 @@ ${this.#backendStderrTail.join(`
|
|
|
4909
5041
|
phase: "scan",
|
|
4910
5042
|
ok: false,
|
|
4911
5043
|
files: message.files,
|
|
4912
|
-
message: `${kind} change
|
|
5044
|
+
message: `${kind} change failed to apply; recovering the dev server automatically: ${detail}`
|
|
4913
5045
|
};
|
|
4914
5046
|
this.#recordBuildStatus(status);
|
|
4915
5047
|
this.#sendOrQueueBuildStatus(status);
|
|
@@ -14079,6 +14211,7 @@ var resolveMobilePath = (target, pathname) => {
|
|
|
14079
14211
|
};
|
|
14080
14212
|
var targetHtmlFilename = (target) => target.basePath?.replace(/^\/+|\/+$/g, "") ? `${target.basePath.replace(/^\/+|\/+$/g, "")}.html` : "index.html";
|
|
14081
14213
|
// pkgs/@akanjs/devkit/capacitorApp.ts
|
|
14214
|
+
var SWIFTUICORE_MIN_IOS_MAJOR = 18;
|
|
14082
14215
|
var iosNativeBlockedEnvKeys = new Set([
|
|
14083
14216
|
"AR",
|
|
14084
14217
|
"AS",
|
|
@@ -14116,17 +14249,56 @@ async function writeRootCapacitorConfig(appRoot, content) {
|
|
|
14116
14249
|
await clearRootCapacitorConfigs(appRoot);
|
|
14117
14250
|
await Bun.write(path42.join(appRoot, "capacitor.config.json"), content);
|
|
14118
14251
|
}
|
|
14119
|
-
var
|
|
14120
|
-
|
|
14121
|
-
|
|
14122
|
-
|
|
14123
|
-
|
|
14124
|
-
|
|
14125
|
-
|
|
14126
|
-
|
|
14252
|
+
var virtualInterfacePrefixes = [
|
|
14253
|
+
"bridge",
|
|
14254
|
+
"utun",
|
|
14255
|
+
"llw",
|
|
14256
|
+
"awdl",
|
|
14257
|
+
"ap",
|
|
14258
|
+
"vmnet",
|
|
14259
|
+
"vnic",
|
|
14260
|
+
"tap",
|
|
14261
|
+
"tun",
|
|
14262
|
+
"docker",
|
|
14263
|
+
"veth",
|
|
14264
|
+
"vboxnet",
|
|
14265
|
+
"gif",
|
|
14266
|
+
"stf"
|
|
14267
|
+
];
|
|
14268
|
+
var physicalInterfacePrefixes = ["en", "eth", "wlan", "wlp", "enp", "eno", "wlo"];
|
|
14269
|
+
var isPrivateLanIpv4 = (address) => {
|
|
14270
|
+
if (address.startsWith("10.") || address.startsWith("192.168."))
|
|
14271
|
+
return true;
|
|
14272
|
+
const secondOctet = Number(address.match(/^172\.(\d+)\./)?.[1]);
|
|
14273
|
+
return Number.isFinite(secondOctet) && secondOctet >= 16 && secondOctet <= 31;
|
|
14274
|
+
};
|
|
14275
|
+
var scoreDevHostCandidate = (name, address) => {
|
|
14276
|
+
const lowerName = name.toLowerCase();
|
|
14277
|
+
let score = 0;
|
|
14278
|
+
if (address.startsWith("169.254."))
|
|
14279
|
+
score -= 1000;
|
|
14280
|
+
if (virtualInterfacePrefixes.some((prefix) => lowerName.startsWith(prefix)))
|
|
14281
|
+
score -= 100;
|
|
14282
|
+
else if (physicalInterfacePrefixes.some((prefix) => lowerName.startsWith(prefix)))
|
|
14283
|
+
score += 100;
|
|
14284
|
+
if (isPrivateLanIpv4(address))
|
|
14285
|
+
score += 10;
|
|
14286
|
+
return score;
|
|
14287
|
+
};
|
|
14288
|
+
var selectLocalDevHost = (interfaces, { override } = {}) => {
|
|
14289
|
+
const candidates = [];
|
|
14290
|
+
for (const [name, aliases] of Object.entries(interfaces)) {
|
|
14291
|
+
for (const alias of aliases ?? []) {
|
|
14292
|
+
if (alias.family !== "IPv4" || alias.internal)
|
|
14293
|
+
continue;
|
|
14294
|
+
candidates.push({ name, address: alias.address });
|
|
14127
14295
|
}
|
|
14128
14296
|
}
|
|
14129
|
-
|
|
14297
|
+
const trimmedOverride = override?.trim();
|
|
14298
|
+
if (trimmedOverride)
|
|
14299
|
+
return { host: trimmedOverride, source: "override", candidates };
|
|
14300
|
+
const [best] = [...candidates].sort((a, b) => scoreDevHostCandidate(b.name, b.address) - scoreDevHostCandidate(a.name, a.address) || a.name.localeCompare(b.name) || a.address.localeCompare(b.address));
|
|
14301
|
+
return best ? { host: best.address, source: "detected", candidates } : { host: "127.0.0.1", source: "loopback", candidates };
|
|
14130
14302
|
};
|
|
14131
14303
|
var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14132
14304
|
var asString = (value) => typeof value === "string" ? value : undefined;
|
|
@@ -14146,6 +14318,30 @@ var dedupeIosRunTargets = (targets) => {
|
|
|
14146
14318
|
}
|
|
14147
14319
|
return [...byKey.values()];
|
|
14148
14320
|
};
|
|
14321
|
+
var formatSimctlRuntime = (key) => {
|
|
14322
|
+
if (!key)
|
|
14323
|
+
return;
|
|
14324
|
+
if (/^(iOS|watchOS|tvOS|visionOS)\s+[\d.]+$/i.test(key))
|
|
14325
|
+
return key;
|
|
14326
|
+
const match = key.match(/(iOS|watchOS|tvOS|visionOS)-(\d+)(?:-(\d+))?/i);
|
|
14327
|
+
if (!match)
|
|
14328
|
+
return;
|
|
14329
|
+
return `${match[1]} ${match[2]}${match[3] ? `.${match[3]}` : ""}`;
|
|
14330
|
+
};
|
|
14331
|
+
var parseIosRuntimeMajor = (runtime) => {
|
|
14332
|
+
const major = runtime?.match(/(\d+)/)?.[1];
|
|
14333
|
+
if (major === undefined)
|
|
14334
|
+
return;
|
|
14335
|
+
const parsed = Number.parseInt(major, 10);
|
|
14336
|
+
return Number.isNaN(parsed) ? undefined : parsed;
|
|
14337
|
+
};
|
|
14338
|
+
var iosRunTargetRank = (target) => {
|
|
14339
|
+
const state = target.state?.toLowerCase() ?? "";
|
|
14340
|
+
const ready = state.includes("booted") || state.includes("connected") || state.includes("available") ? 1000 : 0;
|
|
14341
|
+
const device = target.kind === "device" ? 500 : 0;
|
|
14342
|
+
return ready + device + (parseIosRuntimeMajor(target.runtime) ?? 0);
|
|
14343
|
+
};
|
|
14344
|
+
var sortIosRunTargets = (targets) => [...targets].sort((a, b) => iosRunTargetRank(b) - iosRunTargetRank(a) || a.name.localeCompare(b.name));
|
|
14149
14345
|
function walkRecords(value, visit) {
|
|
14150
14346
|
if (Array.isArray(value)) {
|
|
14151
14347
|
for (const item of value)
|
|
@@ -14198,21 +14394,38 @@ function parseSimctlDevices(output) {
|
|
|
14198
14394
|
try {
|
|
14199
14395
|
const json = JSON.parse(output);
|
|
14200
14396
|
const devices = json.devices ?? {};
|
|
14201
|
-
return Object.
|
|
14202
|
-
const
|
|
14203
|
-
|
|
14204
|
-
|
|
14205
|
-
|
|
14206
|
-
|
|
14207
|
-
|
|
14397
|
+
return Object.entries(devices).flatMap(([runtimeKey, list]) => {
|
|
14398
|
+
const runtime = formatSimctlRuntime(runtimeKey);
|
|
14399
|
+
return (Array.isArray(list) ? list : []).filter(isRecord2).flatMap((device) => {
|
|
14400
|
+
const id = firstString(device.udid, device.UDID, device.identifier);
|
|
14401
|
+
const name = firstString(device.name, device.displayName);
|
|
14402
|
+
const isAvailable = device.isAvailable !== false && device.availabilityError === undefined;
|
|
14403
|
+
if (!id || !name || !isAvailable)
|
|
14404
|
+
return [];
|
|
14405
|
+
return [
|
|
14406
|
+
{
|
|
14407
|
+
id,
|
|
14408
|
+
name,
|
|
14409
|
+
kind: "simulator",
|
|
14410
|
+
state: asString(device.state),
|
|
14411
|
+
runtime: runtime ?? formatSimctlRuntime(asString(device.runtimeIdentifier))
|
|
14412
|
+
}
|
|
14413
|
+
];
|
|
14414
|
+
});
|
|
14208
14415
|
});
|
|
14209
14416
|
} catch {
|
|
14210
14417
|
const targets = [];
|
|
14418
|
+
let runtime;
|
|
14211
14419
|
for (const line of output.split(/\r?\n/)) {
|
|
14420
|
+
const header = line.match(/^--\s*(.+?)\s*--\s*$/);
|
|
14421
|
+
if (header) {
|
|
14422
|
+
runtime = formatSimctlRuntime(header[1]);
|
|
14423
|
+
continue;
|
|
14424
|
+
}
|
|
14212
14425
|
const match = line.match(/^\s*(.+?)\s+\(([0-9A-Fa-f-]{20,})\)\s+\(([^)]+)\)/);
|
|
14213
14426
|
if (!match)
|
|
14214
14427
|
continue;
|
|
14215
|
-
targets.push({ id: match[2], name: match[1].trim(), kind: "simulator", state: match[3] });
|
|
14428
|
+
targets.push({ id: match[2], name: match[1].trim(), kind: "simulator", state: match[3], runtime });
|
|
14216
14429
|
}
|
|
14217
14430
|
return targets;
|
|
14218
14431
|
}
|
|
@@ -14247,6 +14460,13 @@ function buildIosNativeRunCommand({
|
|
|
14247
14460
|
}
|
|
14248
14461
|
function classifyIosRunFailure(log) {
|
|
14249
14462
|
const lower = log.toLowerCase();
|
|
14463
|
+
if (lower.includes("swiftuicore") && (lower.includes("library not loaded") || lower.includes("library missing") || lower.includes("dyld"))) {
|
|
14464
|
+
return {
|
|
14465
|
+
kind: "simulator-runtime",
|
|
14466
|
+
title: "App crashed loading SwiftUICore \u2014 the iOS runtime is too old.",
|
|
14467
|
+
detail: `The build links SwiftUICore, which only exists on iOS ${SWIFTUICORE_MIN_IOS_MAJOR}+. Rerun on an iOS ${SWIFTUICORE_MIN_IOS_MAJOR} or newer simulator/device (pass --device to pick one non-interactively).`
|
|
14468
|
+
};
|
|
14469
|
+
}
|
|
14250
14470
|
if (lower.includes("unknown argument: '-index-store-path'") || lower.includes("compiler was not recognized")) {
|
|
14251
14471
|
return {
|
|
14252
14472
|
kind: "compiler-toolchain",
|
|
@@ -14344,6 +14564,21 @@ function formatIosRunFailureMessage(input2) {
|
|
|
14344
14564
|
function sanitizeIosNativeRunEnv(env) {
|
|
14345
14565
|
return Object.fromEntries(Object.entries(env).filter(([key]) => !iosNativeBlockedEnvKeys.has(key)));
|
|
14346
14566
|
}
|
|
14567
|
+
var PLACEHOLDER_APP_IDS = [
|
|
14568
|
+
"com.myapp.app",
|
|
14569
|
+
"com.myorg.myapp",
|
|
14570
|
+
"com.example.app",
|
|
14571
|
+
"com.example.myapp"
|
|
14572
|
+
];
|
|
14573
|
+
var placeholderAppIdSegment = /^(example|examples|myorg|myapp|mycompany|myorganization|changeme|todo|sample|test)$/;
|
|
14574
|
+
var isPlaceholderAppId = (appId) => {
|
|
14575
|
+
const normalized = appId?.trim().toLowerCase() ?? "";
|
|
14576
|
+
if (!normalized)
|
|
14577
|
+
return true;
|
|
14578
|
+
if (PLACEHOLDER_APP_IDS.includes(normalized))
|
|
14579
|
+
return true;
|
|
14580
|
+
return normalized.split(".").some((segment) => placeholderAppIdSegment.test(segment));
|
|
14581
|
+
};
|
|
14347
14582
|
var androidReleaseSigningKeys = [
|
|
14348
14583
|
"MYAPP_RELEASE_STORE_FILE",
|
|
14349
14584
|
"MYAPP_RELEASE_STORE_PASSWORD",
|
|
@@ -14592,23 +14827,37 @@ class CapacitorApp {
|
|
|
14592
14827
|
await this.#runIosPhysicalDevice({ operation, env, runTarget, noAllowProvisioningUpdates });
|
|
14593
14828
|
}
|
|
14594
14829
|
async#selectIosRunTarget(deviceId) {
|
|
14595
|
-
const targets = await this.#loadIosRunTargets();
|
|
14830
|
+
const targets = sortIosRunTargets(await this.#loadIosRunTargets());
|
|
14596
14831
|
if (deviceId) {
|
|
14597
|
-
const
|
|
14598
|
-
|
|
14599
|
-
|
|
14832
|
+
const needle = deviceId.toLowerCase();
|
|
14833
|
+
const found = targets.find((target) => target.id === deviceId) ?? targets.find((target) => target.name.toLowerCase() === needle) ?? targets.find((target) => target.name.toLowerCase().includes(needle) || target.id.toLowerCase().includes(needle) || (target.runtime?.toLowerCase().includes(needle) ?? false));
|
|
14834
|
+
if (!found) {
|
|
14835
|
+
const available = targets.map((t) => `${t.name}${t.runtime ? ` (${t.runtime})` : ""}`).join(", ") || "none";
|
|
14836
|
+
throw new Error(`iOS run target '${deviceId}' was not found. Available: ${available}`);
|
|
14837
|
+
}
|
|
14838
|
+
this.#warnIfLegacySimulatorRuntime(found);
|
|
14600
14839
|
return found;
|
|
14601
14840
|
}
|
|
14602
14841
|
if (targets.length === 0) {
|
|
14603
14842
|
throw new Error("No iOS run targets found. Open Simulator or connect an iPhone, then retry.");
|
|
14604
14843
|
}
|
|
14605
|
-
|
|
14844
|
+
const selected = await select2({
|
|
14606
14845
|
message: "Select iOS run target",
|
|
14607
14846
|
choices: targets.map((target) => ({
|
|
14608
|
-
name: `[${target.kind}] ${target.name}${target.state ? ` (${target.state})` : ""}`,
|
|
14847
|
+
name: `[${target.kind}] ${target.name}${target.runtime ? ` \u2014 ${target.runtime}` : ""}${target.state ? ` (${target.state})` : ""}`,
|
|
14609
14848
|
value: target
|
|
14610
14849
|
}))
|
|
14611
14850
|
});
|
|
14851
|
+
this.#warnIfLegacySimulatorRuntime(selected);
|
|
14852
|
+
return selected;
|
|
14853
|
+
}
|
|
14854
|
+
#warnIfLegacySimulatorRuntime(target) {
|
|
14855
|
+
if (target.kind !== "simulator")
|
|
14856
|
+
return;
|
|
14857
|
+
const major = parseIosRuntimeMajor(target.runtime);
|
|
14858
|
+
if (major === undefined || major >= SWIFTUICORE_MIN_IOS_MAJOR)
|
|
14859
|
+
return;
|
|
14860
|
+
this.app.logger.warn(`Selected simulator runs ${target.runtime ?? "an older iOS"}. Recent SDK builds link SwiftUICore and require iOS ${SWIFTUICORE_MIN_IOS_MAJOR}+; if the app crashes at launch with a "Library not loaded: SwiftUICore" dyld error, pick an iOS ${SWIFTUICORE_MIN_IOS_MAJOR}+ simulator instead.`);
|
|
14612
14861
|
}
|
|
14613
14862
|
async#loadIosRunTargets() {
|
|
14614
14863
|
const devices = await this.#loadPhysicalIosDevices();
|
|
@@ -14855,7 +15104,13 @@ ${error.message}`;
|
|
|
14855
15104
|
}
|
|
14856
15105
|
async#writeCapacitorConfig({ operation }, commandEnv) {
|
|
14857
15106
|
await mkdir11(this.targetRoot, { recursive: true });
|
|
14858
|
-
|
|
15107
|
+
let localIp;
|
|
15108
|
+
if (operation === "local") {
|
|
15109
|
+
const override = commandEnv.AKAN_PUBLIC_CLIENT_HOST ?? process.env.AKAN_PUBLIC_CLIENT_HOST;
|
|
15110
|
+
const resolution = selectLocalDevHost(os.networkInterfaces(), { override });
|
|
15111
|
+
localIp = resolution.host;
|
|
15112
|
+
this.#logDevHostResolution(resolution, commandEnv);
|
|
15113
|
+
}
|
|
14859
15114
|
const config = materializeCapacitorConfig(this.target, {
|
|
14860
15115
|
operation,
|
|
14861
15116
|
localIp,
|
|
@@ -14866,6 +15121,17 @@ ${error.message}`;
|
|
|
14866
15121
|
await Bun.write(path42.join(this.targetRoot, "capacitor.config.json"), content);
|
|
14867
15122
|
return content;
|
|
14868
15123
|
}
|
|
15124
|
+
#logDevHostResolution(resolution, commandEnv) {
|
|
15125
|
+
this.app.log(`Mobile live-reload server: ${this.#localCsrUrl(resolution.host, commandEnv)}`);
|
|
15126
|
+
if (resolution.source === "override")
|
|
15127
|
+
return;
|
|
15128
|
+
const suspicious = resolution.host === "127.0.0.1" || resolution.host.startsWith("169.254.");
|
|
15129
|
+
const alternatives = resolution.candidates.filter((candidate) => candidate.address !== resolution.host);
|
|
15130
|
+
if (!suspicious && alternatives.length === 0)
|
|
15131
|
+
return;
|
|
15132
|
+
const alternativeText = alternatives.length ? ` Other interfaces: ${alternatives.map((candidate) => `${candidate.address} (${candidate.name})`).join(", ")}.` : "";
|
|
15133
|
+
this.app.logger.warn(`A physical device must reach ${resolution.host} on your LAN.${suspicious ? " That address looks non-routable." : ""}${alternativeText} If the device shows a blank screen, pin the right one with AKAN_PUBLIC_CLIENT_HOST=<ip>.`);
|
|
15134
|
+
}
|
|
14869
15135
|
async#prepareTargetAssets() {
|
|
14870
15136
|
if (!this.target.assets)
|
|
14871
15137
|
return;
|
|
@@ -17114,10 +17380,14 @@ class IncrementalBuilder {
|
|
|
17114
17380
|
this.#cssCompiler = options.cssCompiler;
|
|
17115
17381
|
this.#optimizedFonts = options.optimizedFonts;
|
|
17116
17382
|
this.#discovery = options.discovery;
|
|
17383
|
+
this.#generation = options.initialGeneration ?? 0;
|
|
17117
17384
|
this.#changePlanner = new DevChangePlanner({ workspaceRoot: options.app.workspace.workspaceRoot });
|
|
17118
17385
|
this.#generatedIndexSync = new DevGeneratedIndexSync({ workspaceRoot: options.app.workspace.workspaceRoot });
|
|
17119
17386
|
this.#autoImportSync = new AutoImportSync({ workspaceRoot: options.app.workspace.workspaceRoot });
|
|
17120
17387
|
}
|
|
17388
|
+
get #artifactDir() {
|
|
17389
|
+
return `${this.#app.cwdPath}/.akan/artifact`;
|
|
17390
|
+
}
|
|
17121
17391
|
async handleBuildRoute(msg) {
|
|
17122
17392
|
return this.#enqueueWork(`build-route:${msg.routeId}`, async () => this.#handleBuildRoute(msg));
|
|
17123
17393
|
}
|
|
@@ -17306,7 +17576,7 @@ ${cssText}`).toString(36);
|
|
|
17306
17576
|
});
|
|
17307
17577
|
}
|
|
17308
17578
|
async installWatcher() {
|
|
17309
|
-
const [appDir, artifactDir] = [`${this.#app.cwdPath}/page`,
|
|
17579
|
+
const [appDir, artifactDir] = [`${this.#app.cwdPath}/page`, this.#artifactDir];
|
|
17310
17580
|
const roots = await new WatchRootResolver(this.#app).resolve();
|
|
17311
17581
|
const watcher = new HmrWatcher({
|
|
17312
17582
|
roots,
|
|
@@ -17403,45 +17673,122 @@ ${cssText}`).toString(36);
|
|
|
17403
17673
|
}
|
|
17404
17674
|
}
|
|
17405
17675
|
async boot() {
|
|
17406
|
-
process.on("message", async (msg) => {
|
|
17407
|
-
if (!msg || typeof msg !== "object")
|
|
17408
|
-
return;
|
|
17409
|
-
switch (msg.type) {
|
|
17410
|
-
case "build-route": {
|
|
17411
|
-
const res = await this.handleBuildRoute(msg);
|
|
17412
|
-
process.send?.(res);
|
|
17413
|
-
return;
|
|
17414
|
-
}
|
|
17415
|
-
default:
|
|
17416
|
-
return;
|
|
17417
|
-
}
|
|
17418
|
-
});
|
|
17419
|
-
process.on("disconnect", () => {
|
|
17420
|
-
this.#logger.warn("host IPC channel closed; exiting builder");
|
|
17421
|
-
process.exit(0);
|
|
17422
|
-
});
|
|
17423
17676
|
if (this.#watch)
|
|
17424
17677
|
await this.installWatcher();
|
|
17425
17678
|
process.send?.({ type: "builder-ready" });
|
|
17426
17679
|
this.#logger.verbose(`ready (watch=${this.#watch})`);
|
|
17427
17680
|
}
|
|
17681
|
+
async announceRecoveredState(changedFiles) {
|
|
17682
|
+
const generation = ++this.#generation;
|
|
17683
|
+
await this.#enqueueWork("boot-recovered", async () => {
|
|
17684
|
+
try {
|
|
17685
|
+
const next = await new PagesBundleBuilder(this.#app).build();
|
|
17686
|
+
process.send?.({
|
|
17687
|
+
type: "pages-updated",
|
|
17688
|
+
data: { bundlePath: next.bundlePath, buildId: next.buildId, generation, changedFiles }
|
|
17689
|
+
});
|
|
17690
|
+
this.#sendBuildStatus("pages", { generation, ok: true, files: changedFiles, message: "Boot build recovered" });
|
|
17691
|
+
} catch (err) {
|
|
17692
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
17693
|
+
this.#logger.error(`recovered pages rebundle failed: ${message}`);
|
|
17694
|
+
this.#sendBuildStatus("pages", { generation, ok: false, files: changedFiles, message });
|
|
17695
|
+
}
|
|
17696
|
+
this.scheduleCssRebuild(this.#artifactDir, { refresh: true, generation, changedFiles });
|
|
17697
|
+
});
|
|
17698
|
+
}
|
|
17428
17699
|
#shouldRebuildCsr() {
|
|
17429
17700
|
return true;
|
|
17430
17701
|
}
|
|
17431
|
-
static async
|
|
17702
|
+
static async#buildBootDeps(app) {
|
|
17703
|
+
const { artifact: artifact2, cssCompiler: cssCompiler2, optimizedFonts } = await new SsrBaseArtifactBuilder(app).build();
|
|
17704
|
+
await new CsrArtifactBuilder(app).build();
|
|
17705
|
+
const discovery = await GraphClientEntryDiscovery.create(app);
|
|
17706
|
+
return { artifact: artifact2, cssCompiler: cssCompiler2, optimizedFonts, discovery };
|
|
17707
|
+
}
|
|
17708
|
+
static #recoverBoot(app, bootError, logger) {
|
|
17709
|
+
const firstMessage = bootError instanceof Error ? bootError.message : String(bootError);
|
|
17710
|
+
logger.error(`boot build failed; entering degraded watch mode until the error is fixed: ${firstMessage}`);
|
|
17711
|
+
let generation = 0;
|
|
17712
|
+
const sendFailure = (files, message) => {
|
|
17713
|
+
process.send?.({
|
|
17714
|
+
type: "build-status",
|
|
17715
|
+
data: { generation, phase: "pages", ok: false, files, message: `Boot build failed: ${message}` }
|
|
17716
|
+
});
|
|
17717
|
+
};
|
|
17718
|
+
sendFailure([], firstMessage);
|
|
17719
|
+
process.send?.({ type: "builder-ready" });
|
|
17720
|
+
return new Promise((resolve, reject) => {
|
|
17721
|
+
(async () => {
|
|
17722
|
+
const roots = await new WatchRootResolver(app).resolve();
|
|
17723
|
+
const watcher = new HmrWatcher({
|
|
17724
|
+
roots,
|
|
17725
|
+
logger,
|
|
17726
|
+
onBatch: async (batch) => {
|
|
17727
|
+
generation += 1;
|
|
17728
|
+
const files = [...batch.files].sort();
|
|
17729
|
+
try {
|
|
17730
|
+
if (new Set(batch.kinds).has("config"))
|
|
17731
|
+
await app.getConfig({ refresh: true });
|
|
17732
|
+
const deps = await IncrementalBuilder.#buildBootDeps(app);
|
|
17733
|
+
const builder2 = new IncrementalBuilder({ app, watch: true, initialGeneration: generation, ...deps });
|
|
17734
|
+
watcher.stop();
|
|
17735
|
+
logger.info(`boot build recovered generation=${generation}`);
|
|
17736
|
+
resolve({ builder: builder2, changedFiles: files });
|
|
17737
|
+
} catch (err) {
|
|
17738
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
17739
|
+
logger.error(`boot build retry failed: ${message}`);
|
|
17740
|
+
sendFailure(files, message);
|
|
17741
|
+
}
|
|
17742
|
+
}
|
|
17743
|
+
});
|
|
17744
|
+
watcher.start();
|
|
17745
|
+
logger.warn(`[degraded] watching ${roots.length} roots for a fix`);
|
|
17746
|
+
})().catch(reject);
|
|
17747
|
+
});
|
|
17748
|
+
}
|
|
17749
|
+
static async main() {
|
|
17750
|
+
const logger = new Logger11("IncrementalBuilder");
|
|
17432
17751
|
const { appName, repoName, workspaceRoot } = WorkspaceExecutor.getBaseDevEnv();
|
|
17433
17752
|
if (!workspaceRoot || !appName)
|
|
17434
17753
|
throw new Error("AKAN_WORKSPACE_ROOT or AKAN_PUBLIC_APP_NAME is not set");
|
|
17435
17754
|
const workspace = WorkspaceExecutor.fromRoot({ workspaceRoot, repoName });
|
|
17436
17755
|
const app = AppExecutor.from(workspace, appName);
|
|
17437
17756
|
const watch = process.env.AKAN_WATCH !== "0";
|
|
17438
|
-
|
|
17439
|
-
|
|
17440
|
-
|
|
17441
|
-
|
|
17757
|
+
let builder2 = null;
|
|
17758
|
+
process.on("message", (msg) => {
|
|
17759
|
+
if (!msg || typeof msg !== "object" || msg.type !== "build-route")
|
|
17760
|
+
return;
|
|
17761
|
+
if (!builder2) {
|
|
17762
|
+
process.send?.({
|
|
17763
|
+
type: "build-route-res",
|
|
17764
|
+
id: msg.id,
|
|
17765
|
+
ok: false,
|
|
17766
|
+
error: "builder is recovering from a failed boot build; retry after the build error is fixed"
|
|
17767
|
+
});
|
|
17768
|
+
return;
|
|
17769
|
+
}
|
|
17770
|
+
builder2.handleBuildRoute(msg).then((res) => process.send?.(res));
|
|
17771
|
+
});
|
|
17772
|
+
process.on("disconnect", () => {
|
|
17773
|
+
logger.warn("host IPC channel closed; exiting builder");
|
|
17774
|
+
process.exit(0);
|
|
17775
|
+
});
|
|
17776
|
+
let recoveredFiles = null;
|
|
17777
|
+
try {
|
|
17778
|
+
builder2 = new IncrementalBuilder({ app, watch, ...await IncrementalBuilder.#buildBootDeps(app) });
|
|
17779
|
+
} catch (err) {
|
|
17780
|
+
if (!watch)
|
|
17781
|
+
throw err;
|
|
17782
|
+
const recovered = await IncrementalBuilder.#recoverBoot(app, err, logger);
|
|
17783
|
+
builder2 = recovered.builder;
|
|
17784
|
+
recoveredFiles = recovered.changedFiles;
|
|
17785
|
+
}
|
|
17786
|
+
await builder2.boot();
|
|
17787
|
+
if (recoveredFiles)
|
|
17788
|
+
await builder2.announceRecoveredState(recoveredFiles);
|
|
17442
17789
|
}
|
|
17443
17790
|
}
|
|
17444
|
-
|
|
17791
|
+
IncrementalBuilder.main().catch((err) => {
|
|
17445
17792
|
console.error(err);
|
|
17446
17793
|
process.exit(1);
|
|
17447
17794
|
});
|