@akanjs/cli 3.0.0-beta.3 → 3.0.0-beta.30
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/.build-stamp +1 -1
- package/CodeAgent-xnfyyz8h.js +9 -0
- package/CodeAgentRpcHost-p7h6n2hp.js +7 -0
- package/CodeAgentRpcListener-7p7z5dd7.js +97 -0
- package/CodeTui-h66xb1fh.js +1457 -0
- package/README.md +3 -0
- package/TunnelShare-cwh9ezsm.js +9 -0
- package/{agent.command-d2gfxfck.js → agent.command-c4v7kjff.js} +8 -9
- package/akanCode.js +102 -0
- package/application.command-yzsswrz6.js +241 -0
- package/{applicationBuildRunner-kxwbssgn.js → applicationBuildRunner-qkt6spd5.js} +121 -77
- package/buildBatch.proc.js +120 -32
- package/cloud.command-v8h688jc.js +95 -0
- package/cloud.script-yct1sk4k.js +20 -0
- package/code.command-jff3tw7y.js +154 -0
- package/commandManifest.json +1 -1
- package/consoleToStderr-h5bryj94.js +7 -0
- package/context.command-y5sevk3c.js +48 -0
- package/context.runner-90hsmacr.js +21 -0
- package/{dependencyScanner-shs82ynp.js → dependencyScanner-06z2dmrr.js} +2 -2
- package/{devTui-40ggmd99.js → devTui-q7k7bakd.js} +122 -40
- package/fileSys-wzzrntkw.js +9 -0
- package/guideline.command-11cndccb.js +42 -0
- package/guidelines/agentRule/agentRule.instruction.md +209 -33
- package/guidelines/conventions/conventions.instruction.md +143 -41
- package/guidelines/fieldRule/fieldRule.instruction.md +1 -0
- package/guidelines/mcpRule/mcpRule.instruction.md +7 -5
- package/guidelines/queryRule/queryRule.instruction.md +21 -7
- package/guidelines/runtimeRule/runtimeRule.instruction.md +268 -6
- package/guidelines/ssrRule/ssrRule.instruction.md +4 -2
- package/guidelines/transportRule/transportRule.instruction.md +51 -0
- package/guidelines/workspaceOnboarding/workspaceOnboarding.instruction.md +20 -0
- package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +66 -3
- package/incrementalBuilder.proc.js +564 -109
- package/{index-fz5q3gd7.js → index-0ncc0mbk.js} +10 -17
- package/index-167c77aq.js +69 -0
- package/{index-kcxmv48c.js → index-2ahj896a.js} +45 -79
- package/index-2wc41k5w.js +49 -0
- package/{index-j9yhg404.js → index-35mmm9ca.js} +4 -7
- package/{index-a64esmtb.js → index-376gpdds.js} +523 -1022
- package/index-3hx1z96c.js +87 -0
- package/{index-v0qh106m.js → index-3rh7thcx.js} +961 -892
- package/{index-hwkygn9s.js → index-48k96p55.js} +325 -36
- package/{index-q0y2jq9j.js → index-52w0fvh8.js} +21 -4
- package/index-5rp0h6na.js +1119 -0
- package/index-6x9p8dpb.js +4300 -0
- package/index-7md2g9vw.js +6 -0
- package/index-8tv7356t.js +700 -0
- package/index-94s080qm.js +266 -0
- package/index-9dp031by.js +12 -0
- package/{index-ws5e0j51.js → index-a4s954kq.js} +43 -26
- package/{index-3d9y066c.js → index-bjwxs904.js} +9 -8
- package/{index-ne2qy0nk.js → index-bxmejydw.js} +12 -12
- package/index-c6eggbee.js +123 -0
- package/index-d2242ksy.js +99 -0
- package/{index-92zztqy1.js → index-dy0jx9ay.js} +2 -17
- package/{index-259dsnz3.js → index-gxamdf18.js} +5 -8
- package/{index-4edzf1gr.js → index-hf6f4xnb.js} +77 -150
- package/{index-by5gpp8x.js → index-hj6w4ka1.js} +2 -1
- package/{index-gyd220p2.js → index-hmdq5pqm.js} +554 -577
- package/{index-600zhnve.js → index-hqd0h1pb.js} +75 -203
- package/index-kb16nzyt.js +997 -0
- package/index-mk3b9rk1.js +2729 -0
- package/{index-2tv3fdx4.js → index-n3jk2501.js} +1205 -1458
- package/index-p6pbz9n5.js +25 -0
- package/{index-zry5nz81.js → index-qcejqgts.js} +51 -30
- package/{index-7737gxcp.js → index-rtwhw4aq.js} +107 -241
- package/{index-pnqbkf5p.js → index-se643zvp.js} +6 -2
- package/index-tsb0f8ya.js +53 -0
- package/{index-ktb5njgy.js → index-tzc1gtrg.js} +49 -99
- package/index-wa5jbxj1.js +760 -0
- package/index-wfwgcnyc.js +187 -0
- package/{index-mak3cd28.js → index-xb7qhxa8.js} +137 -255
- package/{index-h7yw2z9n.js → index-zzvvc845.js} +8 -14
- package/index.js +23 -22
- package/{library.command-nbsky672.js → library.command-n9kp7yms.js} +4 -5
- package/{localRegistry.command-yeq57wjr.js → localRegistry.command-jx6kxt2x.js} +23 -42
- package/{module.command-29vqzrh8.js → module.command-q2xjf6cx.js} +8 -11
- package/{package.command-axv0abmn.js → package.command-q5a3nsw5.js} +6 -7
- package/package.json +15 -7
- package/{page.command-vs9s3zrh.js → page.command-ppenrxjn.js} +4 -5
- package/{primitive.command-cbxmqbtc.js → primitive.command-dvgg8nem.js} +9 -22
- package/prompter-76k56b37.js +8 -0
- package/{quality.command-gmbs7nws.js → quality.command-pz7kg3pc.js} +205 -377
- package/{recipeScanner-fpagtgc1.js → recipeScanner-4b69ybe0.js} +1 -1
- package/{repair.command-bqsjptsj.js → repair.command-a6py9xvz.js} +11 -35
- package/{routeSourceValidator-apyqw7wk.js → routeSourceValidator-7ycnfz0e.js} +0 -6
- package/{scalar.command-6k1vhe9s.js → scalar.command-7f3xfpen.js} +7 -10
- package/skills/akan-code/SKILL.md +130 -0
- package/skills/akan-debug/SKILL.md +42 -0
- package/skills/akan-deps/SKILL.md +50 -0
- package/skills/akan-err/SKILL.md +39 -0
- package/skills/akan-module/SKILL.md +45 -0
- package/skills/akan-scaffold/SKILL.md +76 -0
- package/skills/akan-scalar/SKILL.md +52 -0
- package/skills/akan-service/SKILL.md +34 -0
- package/skills/akan-store/SKILL.md +67 -0
- package/skills/akan-ui/SKILL.md +63 -0
- package/skills/akan-validate/SKILL.md +44 -0
- package/{subspace.command-fwjfcts7.js → subspace.command-z7nqs508.js} +138 -67
- package/templates/__scalar/__model__/__model__.abstract.md.template +8 -0
- package/templates/appSample/lib/__scalar/workHistory/workHistory.abstract.ts +8 -39
- package/templates/appSample/lib/_noti/noti.abstract.ts +6 -29
- package/templates/appSample/lib/task/task.abstract.ts +12 -32
- package/templates/appSample/lib/task/task.signal.spec.ts +15 -18
- package/templates/appSample/lib/task/task.signal.test.ts +15 -1
- package/templates/appSample/lib/task/task.signal.ts +1 -1
- package/templates/appSample/srvkit/{AuthGuard.ts → guards.ts} +7 -4
- package/templates/appSample/srvkit/{SessionInternalArg.ts → internalArgs.ts} +6 -4
- package/templates/client.ts +7 -30
- package/templates/crudPages/[__model__Id]/edit/page.tsx +2 -1
- package/templates/crudPages/[__model__Id]/page.tsx +2 -1
- package/templates/crudPages/new/page.tsx +2 -1
- package/templates/crudPages/page.tsx +2 -1
- package/templates/crudSinglePage/page.tsx +2 -1
- package/templates/lib/__lib/lib.dictionary.ts +0 -2
- package/templates/lib/__lib/lib.document.ts +0 -2
- package/templates/lib/sig.ts +8 -30
- package/templates/localDev/docker-compose.yaml.template +3 -14
- package/templates/module/__model__.signal.ts +12 -4
- package/templates/pkgRoot/tsconfig.json.template +2 -7
- package/templates/server.ts +1 -3
- package/templates/service/__model__.abstract.md.template +8 -0
- package/templates/workspaceRoot/.gitignore.template +0 -1
- package/templates/workspaceRoot/package.json.template +1 -3
- package/tunnel.command-zy4wbcgt.js +129 -0
- package/{typeChecker-3xgrzc2v.js → typeChecker-px49mezc.js} +1 -1
- package/typecheck.proc.js +1 -1
- package/workflow.command-f142t17s.js +79 -0
- package/{workspace.command-8s050m0h.js → workspace.command-gpk1149x.js} +77 -109
- package/abstractCompactor-mjh5ppsq.js +0 -101
- package/application.command-pj2m7584.js +0 -204
- package/applicationReleasePackager-4yasxwv9.js +0 -244
- package/capacitorApp-36rbr858.js +0 -12
- package/cloud.command-jshwdq22.js +0 -93
- package/context.command-ez8a0zyz.js +0 -80
- package/devTuiView-c08ccbat.js +0 -9
- package/getDirname-dr3jz1xg.js +0 -7
- package/guideline.command-xmqwx8zm.js +0 -359
- package/guidelines/agentRule/agentRule.generate.json +0 -31
- package/guidelines/componentRule/componentRule.generate.json +0 -31
- package/guidelines/conventions/conventions.generate.json +0 -25
- package/guidelines/cssRule/cssRule.generate.json +0 -31
- package/guidelines/docPageRule/docPageRule.generate.json +0 -31
- package/guidelines/docPageRule/docPageRule.instruction.md +0 -25
- package/guidelines/docSyncRule/docSyncRule.generate.json +0 -31
- package/guidelines/docSyncRule/docSyncRule.instruction.md +0 -25
- package/guidelines/enumConstant/enumConstant.generate.json +0 -31
- package/guidelines/enumConstant/enumConstant.instruction.md +0 -28
- package/guidelines/fieldRule/fieldRule.generate.json +0 -31
- package/guidelines/framework/framework.generate.json +0 -32
- package/guidelines/mcpRule/mcpRule.generate.json +0 -35
- package/guidelines/modelConstant/modelConstant.generate.json +0 -32
- package/guidelines/modelDictionary/modelDictionary.generate.json +0 -32
- package/guidelines/modelDocument/modelDocument.generate.json +0 -31
- package/guidelines/modelService/modelService.generate.json +0 -31
- package/guidelines/modelSignal/modelSignal.generate.json +0 -31
- package/guidelines/modelStore/modelStore.generate.json +0 -31
- package/guidelines/modelTemplate/modelTemplate.generate.json +0 -31
- package/guidelines/modelUnit/modelUnit.generate.json +0 -31
- package/guidelines/modelUtil/modelUtil.generate.json +0 -31
- package/guidelines/modelView/modelView.generate.json +0 -31
- package/guidelines/modelZone/modelZone.generate.json +0 -31
- package/guidelines/moduleCodegen/moduleCodegen.generate.json +0 -31
- package/guidelines/moduleCodegen/moduleCodegen.instruction.md +0 -28
- package/guidelines/moduleOverview/moduleOverview.generate.json +0 -31
- package/guidelines/queryRule/queryRule.generate.json +0 -31
- package/guidelines/recipeRule/recipeRule.generate.json +0 -31
- package/guidelines/runtimeRule/runtimeRule.generate.json +0 -25
- package/guidelines/scalarConstant/scalarConstant.generate.json +0 -32
- package/guidelines/scalarDictionary/scalarDictionary.generate.json +0 -32
- package/guidelines/scalarModule/scalarModule.generate.json +0 -31
- package/guidelines/sharedUiUsage/sharedUiUsage.generate.json +0 -31
- package/guidelines/sharedUiUsage/sharedUiUsage.instruction.md +0 -25
- package/guidelines/ssrRule/ssrRule.generate.json +0 -31
- package/guidelines/transportRule/transportRule.generate.json +0 -31
- package/guidelines/utilUiUsage/utilUiUsage.generate.json +0 -31
- package/guidelines/utilUiUsage/utilUiUsage.instruction.md +0 -25
- package/guidelines/workspaceOnboarding/workspaceOnboarding.generate.json +0 -24
- package/guidelines/workspaceRecipes/workspaceRecipes.generate.json +0 -33
- package/index-2hq52jq3.js +0 -465
- package/index-47pj3aee.js +0 -76
- package/index-6wwzs1bc.js +0 -7
- package/index-8pm30m73.js +0 -841
- package/index-kn1h7p1x.js +0 -560
- package/index-meh558jy.js +0 -65
- package/index-rwg2aa4f.js +0 -229
- package/index-w8kmx1b6.js +0 -2176
- package/index-wt7qrtd4.js +0 -1516
- package/templates/__scalar/__model__/__model__.abstract.md +0 -28
- package/templates/module/__model__.abstract.md +0 -28
- package/templates/service/__model__.abstract.md +0 -28
- package/templates/workspaceRoot/patches/react-dom@19.2.7.patch.template +0 -62
- package/workflow.command-nwpcdc8p.js +0 -107
|
@@ -1,11 +1,294 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
// pkgs/@akanjs/devkit/applicationBuildReporter.ts
|
|
3
|
+
import { realpathSync } from "fs";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { Logger } from "akanjs/common";
|
|
6
|
+
|
|
7
|
+
class ApplicationBuildReporter {
|
|
8
|
+
static create() {
|
|
9
|
+
return {
|
|
10
|
+
phaseDone: (phase) => Logger.rawLog(ApplicationBuildReporter.formatPhaseLine(phase))
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
static printSummary(result) {
|
|
14
|
+
Logger.rawLog("");
|
|
15
|
+
Logger.rawLog(`Route artifacts: ${result.artifactDir}`);
|
|
16
|
+
Logger.rawLog(`Server output: ${result.outputDir}`);
|
|
17
|
+
Logger.rawLog(`Done in ${ApplicationBuildReporter.formatDuration(result.durationMs)}`);
|
|
18
|
+
}
|
|
19
|
+
static formatError(error, workspaceRoot = process.cwd()) {
|
|
20
|
+
if (error instanceof AggregateError) {
|
|
21
|
+
const nestedMessages = error.errors.map((nestedError, index) => ApplicationBuildReporter.formatError(nestedError, workspaceRoot).trim() || `Unknown error ${index + 1}`).map((message) => message.replace(/^/gm, " ")).join(`
|
|
22
|
+
`);
|
|
23
|
+
return nestedMessages ? `${error.message}
|
|
24
|
+
${nestedMessages}` : error.message;
|
|
25
|
+
}
|
|
26
|
+
if (error instanceof Error) {
|
|
27
|
+
const causeMessage = error.cause ? `
|
|
28
|
+
Caused by: ${ApplicationBuildReporter.formatError(error.cause, workspaceRoot)}` : "";
|
|
29
|
+
return `${ApplicationBuildReporter.#withLocation(error.message, error, workspaceRoot)}${causeMessage}`;
|
|
30
|
+
}
|
|
31
|
+
if (typeof error === "object" && error !== null && "message" in error)
|
|
32
|
+
return ApplicationBuildReporter.#withLocation(String(error.message), error, workspaceRoot);
|
|
33
|
+
return String(error);
|
|
34
|
+
}
|
|
35
|
+
static #withLocation(message, error, workspaceRoot) {
|
|
36
|
+
const { position } = error;
|
|
37
|
+
if (!position?.file)
|
|
38
|
+
return message;
|
|
39
|
+
const file = ApplicationBuildReporter.#relativeToWorkspace(position.file, workspaceRoot);
|
|
40
|
+
const location = position.line > 0 ? `${file}:${position.line}:${position.column}` : file;
|
|
41
|
+
return message.includes(location) ? message : message.replace(/^.*/, (headline) => `${headline} (${location})`);
|
|
42
|
+
}
|
|
43
|
+
static #relativeToWorkspace(file, workspaceRoot) {
|
|
44
|
+
if (!path.isAbsolute(file))
|
|
45
|
+
return file;
|
|
46
|
+
for (const root of [workspaceRoot, ApplicationBuildReporter.#realpathOf(workspaceRoot)]) {
|
|
47
|
+
const relative = path.relative(root, file);
|
|
48
|
+
if (!relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative))
|
|
49
|
+
return relative.replaceAll(path.sep, "/");
|
|
50
|
+
}
|
|
51
|
+
return file;
|
|
52
|
+
}
|
|
53
|
+
static #realpathOf(dir) {
|
|
54
|
+
try {
|
|
55
|
+
return realpathSync(dir);
|
|
56
|
+
} catch {
|
|
57
|
+
return dir;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
static formatDuration(ms) {
|
|
61
|
+
if (ms < 1000)
|
|
62
|
+
return `${ms}ms`;
|
|
63
|
+
if (ms < 60000)
|
|
64
|
+
return `${Math.round(ms / 100) / 10}s`;
|
|
65
|
+
const seconds = Math.floor(ms / 1000);
|
|
66
|
+
return `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
|
|
67
|
+
}
|
|
68
|
+
static formatPhaseLine(phase) {
|
|
69
|
+
const summary = phase.summary ? `: ${phase.summary}` : "";
|
|
70
|
+
return `\u2713 ${phase.label}${summary} (${ApplicationBuildReporter.formatDuration(phase.durationMs)})`;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
2
74
|
// pkgs/@akanjs/devkit/frontendBuild/hmrWatcher.ts
|
|
3
75
|
import fs from "fs";
|
|
4
|
-
import
|
|
76
|
+
import path5 from "path";
|
|
5
77
|
|
|
6
78
|
// pkgs/@akanjs/devkit/frontendBuild/hmrChangeClassifier.ts
|
|
7
|
-
import
|
|
79
|
+
import path3 from "path";
|
|
80
|
+
|
|
81
|
+
// pkgs/@akanjs/devkit/akanApp/devHostPolicy.ts
|
|
82
|
+
import path2 from "path";
|
|
83
|
+
var BACKEND_RECOVERY_MAX_ATTEMPTS = 5;
|
|
84
|
+
var HELD_BUILDER_REQUEST_LIMIT = 64;
|
|
85
|
+
var BUILDER_MIN_RSS_RECYCLE_INTERVAL_MS = 30000;
|
|
86
|
+
var BUILDER_TIGHT_RSS_REPORT_LIMIT = 2;
|
|
87
|
+
var BUILDER_RSS_HARD_MULTIPLE = 1.5;
|
|
88
|
+
var DEV_IDLE_SUSPEND_MS = 300000;
|
|
89
|
+
var DEV_IDLE_MIN_UPTIME_MS = 30000;
|
|
8
90
|
var SOURCE_EXTS = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
91
|
+
var SERVER_SUFFIXES = [".service.ts", ".document.ts"];
|
|
92
|
+
var SHARED_SUFFIXES = [".constant.ts", ".dictionary.ts", ".signal.ts"];
|
|
93
|
+
var RUNTIME_METADATA_BASENAMES = new Set(["dict.ts", "sig.ts", "useClient.ts", "useServer.ts"]);
|
|
94
|
+
var shouldRestartBackendByDevPlan = (message) => {
|
|
95
|
+
if (!message.devPlan)
|
|
96
|
+
return null;
|
|
97
|
+
if (message.devPlan.actions.includes("report-error"))
|
|
98
|
+
return false;
|
|
99
|
+
if (message.devPlan.actions.includes("restart-builder"))
|
|
100
|
+
return false;
|
|
101
|
+
return message.devPlan.actions.includes("restart-backend");
|
|
102
|
+
};
|
|
103
|
+
var shouldRestartBuilderByDevPlan = (message) => message.devPlan?.actions.includes("restart-builder") ?? false;
|
|
104
|
+
var shouldAbandonBackendRecovery = (attempts, maxAttempts = BACKEND_RECOVERY_MAX_ATTEMPTS) => attempts >= maxAttempts;
|
|
105
|
+
var normalizeBackendReportedGeneration = (generation) => generation >= 0 ? generation : undefined;
|
|
106
|
+
var shouldRestartDevHostByDevPlan = (message) => message.devPlan?.actions.includes("restart-dev-host") ?? message.kinds.includes("config");
|
|
107
|
+
var devHostStateByBackendState = {
|
|
108
|
+
starting: "starting",
|
|
109
|
+
ready: "ready",
|
|
110
|
+
"restart-pending": "restarting",
|
|
111
|
+
recovering: "recovering",
|
|
112
|
+
stopping: "stopped",
|
|
113
|
+
stopped: "stopped"
|
|
114
|
+
};
|
|
115
|
+
var devHostStateOf = (state, gaveUp) => state === "stopped" && gaveUp ? "failed" : devHostStateByBackendState[state];
|
|
116
|
+
var RESTART_ROLE_ORDER = ["server", "shared", "barrel", "config"];
|
|
117
|
+
var generationValue = (generation) => generation ?? -1;
|
|
118
|
+
var isLegacyBackendFallbackFile = (file, workspaceRoot) => {
|
|
119
|
+
const abs = path2.resolve(file);
|
|
120
|
+
const ext = path2.extname(abs).toLowerCase();
|
|
121
|
+
if (!SOURCE_EXTS.has(ext))
|
|
122
|
+
return false;
|
|
123
|
+
const rel = path2.relative(path2.resolve(workspaceRoot), abs);
|
|
124
|
+
if (!rel || rel.startsWith("..") || path2.isAbsolute(rel))
|
|
125
|
+
return false;
|
|
126
|
+
const parts = rel.split(path2.sep).filter(Boolean);
|
|
127
|
+
const [scope] = parts;
|
|
128
|
+
if (scope !== "apps" && scope !== "libs" && scope !== "pkgs")
|
|
129
|
+
return false;
|
|
130
|
+
const base = path2.basename(abs);
|
|
131
|
+
return parts.includes("srvkit") || parts.includes("common") || SERVER_SUFFIXES.some((suffix) => base.endsWith(suffix)) || SHARED_SUFFIXES.some((suffix) => base.endsWith(suffix)) || RUNTIME_METADATA_BASENAMES.has(base) || base === "main.ts" || base === "server.ts";
|
|
132
|
+
};
|
|
133
|
+
var shouldMarkBuildPhaseRecovered = (previousByPhase, status) => {
|
|
134
|
+
const previous = previousByPhase.get(status.phase);
|
|
135
|
+
if (!previous || previous.ok || !status.ok)
|
|
136
|
+
return false;
|
|
137
|
+
const generation = generationValue(status.generation);
|
|
138
|
+
if (status.phase === "route" && status.scope !== undefined && previous.scope !== undefined)
|
|
139
|
+
return status.scope === previous.scope && generation >= previous.generation;
|
|
140
|
+
if (generation !== previous.generation)
|
|
141
|
+
return generation > previous.generation;
|
|
142
|
+
return status.phase === "backend";
|
|
143
|
+
};
|
|
144
|
+
var shouldKeepBuildFailure = (previousByPhase, status) => status.ok && previousByPhase.get(status.phase)?.ok === false && !shouldMarkBuildPhaseRecovered(previousByPhase, status);
|
|
145
|
+
var createBackendBuildStatus = ({
|
|
146
|
+
generation,
|
|
147
|
+
ok,
|
|
148
|
+
files = [],
|
|
149
|
+
message
|
|
150
|
+
}) => ({
|
|
151
|
+
generation,
|
|
152
|
+
phase: "backend",
|
|
153
|
+
ok,
|
|
154
|
+
files,
|
|
155
|
+
message
|
|
156
|
+
});
|
|
157
|
+
var backendRestartReasonFromMessage = (message) => {
|
|
158
|
+
const roleSet = new Set;
|
|
159
|
+
for (const role of message.devPlan?.roles ?? []) {
|
|
160
|
+
if (role === "server" || role === "shared" || role === "barrel" || role === "config")
|
|
161
|
+
roleSet.add(role);
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
generation: message.devPlan?.generation ?? message.generation,
|
|
165
|
+
files: [...new Set(message.files)].sort(),
|
|
166
|
+
roles: RESTART_ROLE_ORDER.filter((role) => roleSet.has(role))
|
|
167
|
+
};
|
|
168
|
+
};
|
|
169
|
+
var mergeBackendRestartReasons = (current, next) => ({
|
|
170
|
+
generation: generationValue(next.generation) >= generationValue(current?.generation) ? next.generation : current?.generation,
|
|
171
|
+
files: [...new Set([...current?.files ?? [], ...next.files])].sort(),
|
|
172
|
+
roles: RESTART_ROLE_ORDER.filter((role) => current?.roles.includes(role) || next.roles.includes(role))
|
|
173
|
+
});
|
|
174
|
+
var shouldReplaceLastGoodMessage = (current, next) => !current || generationValue(next.data.generation) >= generationValue(current.data.generation);
|
|
175
|
+
var shouldQueueBuildStatusReplay = (backendReady, pendingReplayCount) => !backendReady || pendingReplayCount > 0;
|
|
176
|
+
var hasBuildFailureForGeneration = (statusByPhase, generation) => {
|
|
177
|
+
if (typeof generation !== "number")
|
|
178
|
+
return false;
|
|
179
|
+
for (const status of statusByPhase.values()) {
|
|
180
|
+
if (!status.ok && status.phase !== "route" && status.generation === generation)
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
return false;
|
|
184
|
+
};
|
|
185
|
+
var decideBuilderRssRecycle = ({
|
|
186
|
+
rssBytes,
|
|
187
|
+
ceilingBytes,
|
|
188
|
+
buildFailed,
|
|
189
|
+
msSinceLastRecycle,
|
|
190
|
+
minIntervalMs = BUILDER_MIN_RSS_RECYCLE_INTERVAL_MS
|
|
191
|
+
}) => {
|
|
192
|
+
if (!ceilingBytes)
|
|
193
|
+
return "unbounded";
|
|
194
|
+
if (rssBytes < ceilingBytes)
|
|
195
|
+
return "below-ceiling";
|
|
196
|
+
if (buildFailed)
|
|
197
|
+
return "build-failed";
|
|
198
|
+
if (msSinceLastRecycle !== null && msSinceLastRecycle < minIntervalMs)
|
|
199
|
+
return "too-soon";
|
|
200
|
+
return "recycle";
|
|
201
|
+
};
|
|
202
|
+
var decideBuilderRssSettle = ({
|
|
203
|
+
rssBytes,
|
|
204
|
+
ceilingBytes,
|
|
205
|
+
hardMultiple = BUILDER_RSS_HARD_MULTIPLE
|
|
206
|
+
}) => rssBytes >= ceilingBytes * hardMultiple ? "recycle-now" : "wait-and-recheck";
|
|
207
|
+
var decideIdleSuspend = ({
|
|
208
|
+
enabled,
|
|
209
|
+
suspended,
|
|
210
|
+
builderReady,
|
|
211
|
+
backendReady,
|
|
212
|
+
buildFailed,
|
|
213
|
+
restartPending,
|
|
214
|
+
msSinceWake,
|
|
215
|
+
minUptimeMs = DEV_IDLE_MIN_UPTIME_MS
|
|
216
|
+
}) => {
|
|
217
|
+
if (!enabled)
|
|
218
|
+
return "disabled";
|
|
219
|
+
if (suspended)
|
|
220
|
+
return "already-suspended";
|
|
221
|
+
if (!builderReady)
|
|
222
|
+
return "builder-not-ready";
|
|
223
|
+
if (!backendReady)
|
|
224
|
+
return "backend-not-ready";
|
|
225
|
+
if (buildFailed)
|
|
226
|
+
return "build-failed";
|
|
227
|
+
if (restartPending)
|
|
228
|
+
return "restart-pending";
|
|
229
|
+
if (msSinceWake !== null && msSinceWake < minUptimeMs)
|
|
230
|
+
return "too-soon";
|
|
231
|
+
return "suspend";
|
|
232
|
+
};
|
|
233
|
+
var resolveIdleSuspendMs = (raw) => {
|
|
234
|
+
if (raw === undefined || raw === "")
|
|
235
|
+
return DEV_IDLE_SUSPEND_MS;
|
|
236
|
+
const parsed = Number(raw);
|
|
237
|
+
if (!Number.isFinite(parsed) || parsed <= 0)
|
|
238
|
+
return null;
|
|
239
|
+
return Math.round(parsed);
|
|
240
|
+
};
|
|
241
|
+
var hasAnyBuildFailure = (statusByPhase) => [...statusByPhase.values()].some((status) => !status.ok && status.phase !== "route");
|
|
242
|
+
var shouldRefreshConfigOnIdleWake = (batch) => !!batch && batch.kinds.has("config");
|
|
243
|
+
var RETURNING_BUILDER_STATUSES = new Set(["starting", "recycling", "restarting"]);
|
|
244
|
+
var shouldHoldForReturningBuilder = ({
|
|
245
|
+
status,
|
|
246
|
+
heldCount,
|
|
247
|
+
limit = HELD_BUILDER_REQUEST_LIMIT
|
|
248
|
+
}) => RETURNING_BUILDER_STATUSES.has(status) && heldCount < limit;
|
|
249
|
+
var shouldWarnBuilderRssCeilingTight = (reportsSinceRecycle, limit = BUILDER_TIGHT_RSS_REPORT_LIMIT) => reportsSinceRecycle >= limit;
|
|
250
|
+
var isRssCeilingUnreachable = (freshRssBytes, ceilingBytes) => freshRssBytes !== null && ceilingBytes !== null && freshRssBytes >= ceilingBytes;
|
|
251
|
+
var shouldRelayRecycledFrontendState = (current, next) => {
|
|
252
|
+
if (!current || current.type !== next.type)
|
|
253
|
+
return true;
|
|
254
|
+
if (current.type === "pages-updated" && next.type === "pages-updated")
|
|
255
|
+
return current.data.bundlePath !== next.data.bundlePath;
|
|
256
|
+
if (current.type === "css-updated" && next.type === "css-updated")
|
|
257
|
+
return JSON.stringify(current.data.cssAssets) !== JSON.stringify(next.data.cssAssets);
|
|
258
|
+
return true;
|
|
259
|
+
};
|
|
260
|
+
var mergeDevPlans = (current, next) => {
|
|
261
|
+
if (!current)
|
|
262
|
+
return next;
|
|
263
|
+
if (!next)
|
|
264
|
+
return current;
|
|
265
|
+
const reasonByFile = { ...current.reasonByFile };
|
|
266
|
+
for (const [file, reasons] of Object.entries(next.reasonByFile)) {
|
|
267
|
+
reasonByFile[file] = [...new Set([...reasonByFile[file] ?? [], ...reasons])].sort();
|
|
268
|
+
}
|
|
269
|
+
return {
|
|
270
|
+
generation: Math.max(current.generation, next.generation),
|
|
271
|
+
files: [...new Set([...current.files, ...next.files])].sort(),
|
|
272
|
+
generatedFiles: [...new Set([...current.generatedFiles, ...next.generatedFiles])].sort(),
|
|
273
|
+
roles: [...new Set([...current.roles, ...next.roles])].sort(),
|
|
274
|
+
actions: [...new Set([...current.actions, ...next.actions])].sort(),
|
|
275
|
+
reasonByFile
|
|
276
|
+
};
|
|
277
|
+
};
|
|
278
|
+
var mergeInvalidateMessages = (current, next) => {
|
|
279
|
+
const generation = Math.max(generationValue(current.generation), generationValue(next.generation));
|
|
280
|
+
return {
|
|
281
|
+
type: "invalidate",
|
|
282
|
+
kinds: [...new Set([...current.kinds, ...next.kinds])].sort(),
|
|
283
|
+
files: [...new Set([...current.files, ...next.files])].sort(),
|
|
284
|
+
generation: generation >= 0 ? generation : undefined,
|
|
285
|
+
devPlan: mergeDevPlans(current.devPlan, next.devPlan)
|
|
286
|
+
};
|
|
287
|
+
};
|
|
288
|
+
var buildStatusReplaySequence = (pendingReplay, latestByPhase) => [...pendingReplay, ...latestByPhase.values()];
|
|
289
|
+
var filesChangedSince = (before, after) => [...before].filter(([file, stamp]) => after.get(file) !== stamp).map(([file]) => file);
|
|
290
|
+
|
|
291
|
+
// pkgs/@akanjs/devkit/frontendBuild/hmrChangeClassifier.ts
|
|
9
292
|
var CSS_EXTS = new Set([".css"]);
|
|
10
293
|
var CONFIG_BASENAMES = new Set(["akan.config.ts", "bunfig.toml", "tsconfig.json", "package.json"]);
|
|
11
294
|
|
|
@@ -13,10 +296,10 @@ class HmrChangeClassifier {
|
|
|
13
296
|
classify(abs) {
|
|
14
297
|
if (this.#isUninteresting(abs))
|
|
15
298
|
return "ignore";
|
|
16
|
-
const base =
|
|
299
|
+
const base = path3.basename(abs);
|
|
17
300
|
if (CONFIG_BASENAMES.has(base))
|
|
18
301
|
return "config";
|
|
19
|
-
const ext =
|
|
302
|
+
const ext = path3.extname(abs).toLowerCase();
|
|
20
303
|
if (CSS_EXTS.has(ext))
|
|
21
304
|
return "css";
|
|
22
305
|
if (SOURCE_EXTS.has(ext))
|
|
@@ -24,16 +307,16 @@ class HmrChangeClassifier {
|
|
|
24
307
|
return "ignore";
|
|
25
308
|
}
|
|
26
309
|
#isUninteresting(abs) {
|
|
27
|
-
const base =
|
|
310
|
+
const base = path3.basename(abs);
|
|
28
311
|
if (!base)
|
|
29
312
|
return true;
|
|
30
313
|
if (base.startsWith("."))
|
|
31
314
|
return true;
|
|
32
315
|
if (base.endsWith("~") || base.endsWith(".swp") || base.endsWith(".swx") || base.endsWith(".tmp"))
|
|
33
316
|
return true;
|
|
34
|
-
if (abs.includes(`${
|
|
317
|
+
if (abs.includes(`${path3.sep}node_modules${path3.sep}`))
|
|
35
318
|
return true;
|
|
36
|
-
if (abs.includes(`${
|
|
319
|
+
if (abs.includes(`${path3.sep}.akan${path3.sep}`))
|
|
37
320
|
return true;
|
|
38
321
|
return false;
|
|
39
322
|
}
|
|
@@ -41,7 +324,7 @@ class HmrChangeClassifier {
|
|
|
41
324
|
|
|
42
325
|
// pkgs/@akanjs/devkit/frontendBuild/sourceMtimeIndex.ts
|
|
43
326
|
import { readdir, stat } from "fs/promises";
|
|
44
|
-
import
|
|
327
|
+
import path4 from "path";
|
|
45
328
|
var UNREADABLE = Number.NaN;
|
|
46
329
|
|
|
47
330
|
class SourceMtimeIndex {
|
|
@@ -104,7 +387,7 @@ class SourceMtimeIndex {
|
|
|
104
387
|
if (!this.#primed)
|
|
105
388
|
return;
|
|
106
389
|
await Promise.all(paths.map(async (file) => {
|
|
107
|
-
const abs =
|
|
390
|
+
const abs = path4.resolve(file);
|
|
108
391
|
const stats = await stat(abs).catch(() => null);
|
|
109
392
|
if (!stats?.isFile()) {
|
|
110
393
|
this.#files.delete(abs);
|
|
@@ -190,7 +473,7 @@ class SourceMtimeIndex {
|
|
|
190
473
|
for (const entry of entries) {
|
|
191
474
|
if (SourceMtimeIndex.#skipDirent(entry.name))
|
|
192
475
|
continue;
|
|
193
|
-
const abs =
|
|
476
|
+
const abs = path4.join(dir, entry.name);
|
|
194
477
|
if (entry.isDirectory()) {
|
|
195
478
|
descend.push(abs);
|
|
196
479
|
continue;
|
|
@@ -217,7 +500,7 @@ class SourceMtimeIndex {
|
|
|
217
500
|
}
|
|
218
501
|
if (known) {
|
|
219
502
|
for (const abs of [...this.#files.keys()]) {
|
|
220
|
-
if (present.has(abs) ||
|
|
503
|
+
if (present.has(abs) || path4.dirname(abs) !== dir)
|
|
221
504
|
continue;
|
|
222
505
|
this.#files.delete(abs);
|
|
223
506
|
changed?.add(abs);
|
|
@@ -235,7 +518,7 @@ class SourceMtimeIndex {
|
|
|
235
518
|
this.#unsettled.delete(dir);
|
|
236
519
|
}
|
|
237
520
|
#forget(dir) {
|
|
238
|
-
const prefix = `${dir}${
|
|
521
|
+
const prefix = `${dir}${path4.sep}`;
|
|
239
522
|
this.#dirs.delete(dir);
|
|
240
523
|
this.#unreadable.delete(dir);
|
|
241
524
|
this.#unsettled.delete(dir);
|
|
@@ -273,8 +556,8 @@ class SourceMtimeIndex {
|
|
|
273
556
|
return !name || name.startsWith(".") || name === "node_modules";
|
|
274
557
|
}
|
|
275
558
|
static #pruneNestedRoots(roots) {
|
|
276
|
-
const resolved = [...new Set(roots.map((root) =>
|
|
277
|
-
return resolved.filter((root) => !resolved.some((other) => other !== root && root.startsWith(`${other}${
|
|
559
|
+
const resolved = [...new Set(roots.map((root) => path4.resolve(root)))].sort();
|
|
560
|
+
return resolved.filter((root) => !resolved.some((other) => other !== root && root.startsWith(`${other}${path4.sep}`)));
|
|
278
561
|
}
|
|
279
562
|
}
|
|
280
563
|
|
|
@@ -292,14 +575,15 @@ class HmrWatcher {
|
|
|
292
575
|
#index;
|
|
293
576
|
#timer = null;
|
|
294
577
|
#verifyTimer = null;
|
|
578
|
+
#windowStartedAt = null;
|
|
295
579
|
#stopped = false;
|
|
296
580
|
#flushing = false;
|
|
297
581
|
#unreportedChanges = 0;
|
|
298
582
|
#reportedCompensating = false;
|
|
299
583
|
#reportedGaps = "";
|
|
300
584
|
constructor(opts) {
|
|
301
|
-
this.#roots = [...new Set(opts.roots.map((r) =>
|
|
302
|
-
this.#debounceMs = opts.debounceMs ??
|
|
585
|
+
this.#roots = [...new Set(opts.roots.map((r) => path5.resolve(r)))];
|
|
586
|
+
this.#debounceMs = opts.debounceMs ?? HmrWatcher.#envDebounceMs() ?? 30;
|
|
303
587
|
this.#verifyDelayMs = opts.verifyDelayMs ?? 250;
|
|
304
588
|
this.#onBatch = opts.onBatch;
|
|
305
589
|
this.#logger = opts.logger;
|
|
@@ -314,7 +598,7 @@ class HmrWatcher {
|
|
|
314
598
|
const w = fs.watch(root, { recursive: true, persistent: false }, (_event, filename) => {
|
|
315
599
|
if (!filename)
|
|
316
600
|
return;
|
|
317
|
-
const abs =
|
|
601
|
+
const abs = path5.resolve(root, filename.toString());
|
|
318
602
|
this.#queue(abs);
|
|
319
603
|
});
|
|
320
604
|
this.#watchers.push(w);
|
|
@@ -365,6 +649,7 @@ class HmrWatcher {
|
|
|
365
649
|
this.#scheduleFlush();
|
|
366
650
|
}
|
|
367
651
|
#scheduleFlush() {
|
|
652
|
+
this.#windowStartedAt ??= Date.now();
|
|
368
653
|
if (this.#flushing)
|
|
369
654
|
return;
|
|
370
655
|
if (this.#timer)
|
|
@@ -382,14 +667,19 @@ class HmrWatcher {
|
|
|
382
667
|
try {
|
|
383
668
|
while (!this.#stopped) {
|
|
384
669
|
await this.#mergeDetectedChanges();
|
|
385
|
-
if (this.#pending.size === 0)
|
|
670
|
+
if (this.#pending.size === 0) {
|
|
671
|
+
this.#windowStartedAt = null;
|
|
386
672
|
break;
|
|
673
|
+
}
|
|
387
674
|
const files = Array.from(this.#pending.keys());
|
|
388
675
|
const kinds = new Set(this.#pending.values());
|
|
676
|
+
const flushAt = Date.now();
|
|
677
|
+
const trace = { eventAt: this.#windowStartedAt ?? flushAt, flushAt };
|
|
678
|
+
this.#windowStartedAt = null;
|
|
389
679
|
this.#pending.clear();
|
|
390
680
|
this.#hinted.clear();
|
|
391
681
|
try {
|
|
392
|
-
await this.#onBatch({ files, kinds });
|
|
682
|
+
await this.#onBatch({ files, kinds, trace });
|
|
393
683
|
} catch (e) {
|
|
394
684
|
this.#logger.error(`[hmr] onBatch error: ${e.message}`);
|
|
395
685
|
}
|
|
@@ -440,6 +730,11 @@ class HmrWatcher {
|
|
|
440
730
|
const rest = gaps.length > 3 ? ` and ${gaps.length - 3} more` : "";
|
|
441
731
|
this.#logger.warn(`[hmr] cannot read ${gaps.length} path(s), so edits underneath them will not rebuild: ${shown}${rest}`);
|
|
442
732
|
}
|
|
733
|
+
static #envDebounceMs() {
|
|
734
|
+
const raw = process.env.AKAN_DEV_WATCH_DEBOUNCE_MS;
|
|
735
|
+
const ms = raw ? Number(raw) : Number.NaN;
|
|
736
|
+
return Number.isInteger(ms) && ms >= 0 ? ms : null;
|
|
737
|
+
}
|
|
443
738
|
#scheduleVerify() {
|
|
444
739
|
if (this.#stopped || this.#verifyDelayMs <= 0)
|
|
445
740
|
return;
|
|
@@ -464,7 +759,7 @@ class HmrWatcher {
|
|
|
464
759
|
// pkgs/@akanjs/devkit/frontendBuild/watchRootResolver.ts
|
|
465
760
|
import fs2 from "fs";
|
|
466
761
|
import { readFile } from "fs/promises";
|
|
467
|
-
import
|
|
762
|
+
import path6 from "path";
|
|
468
763
|
|
|
469
764
|
class WatchRootResolver {
|
|
470
765
|
#app;
|
|
@@ -473,20 +768,20 @@ class WatchRootResolver {
|
|
|
473
768
|
}
|
|
474
769
|
async resolve() {
|
|
475
770
|
const tsconfig = await this.#app.getTsConfig();
|
|
476
|
-
const appDir =
|
|
477
|
-
const appsContainer =
|
|
478
|
-
const libsContainer =
|
|
479
|
-
const libRoots = await this.#
|
|
771
|
+
const appDir = path6.resolve(this.#app.cwdPath);
|
|
772
|
+
const appsContainer = path6.dirname(appDir);
|
|
773
|
+
const libsContainer = path6.resolve(this.#app.workspace.workspaceRoot, "libs");
|
|
774
|
+
const libRoots = (await this.#resolveLibDeps(libsContainer))?.map((name) => path6.join(libsContainer, name)).filter((dir) => fs2.existsSync(dir));
|
|
480
775
|
const set = new Set;
|
|
481
|
-
set.add(
|
|
776
|
+
set.add(path6.resolve(`${this.#app.cwdPath}/page`));
|
|
482
777
|
for (const targets of Object.values(tsconfig.compilerOptions.paths ?? {})) {
|
|
483
778
|
for (const target of targets) {
|
|
484
779
|
if (!target)
|
|
485
780
|
continue;
|
|
486
|
-
if (
|
|
781
|
+
if (path6.isAbsolute(target))
|
|
487
782
|
continue;
|
|
488
783
|
const cleaned = target.replace(/\/?\*+.*$/, "").replace(/\/[^/]+\.[^/]+$/, "");
|
|
489
|
-
const resolved =
|
|
784
|
+
const resolved = path6.resolve(this.#app.workspace.workspaceRoot, cleaned);
|
|
490
785
|
if (resolved === libsContainer && libRoots) {
|
|
491
786
|
for (const root of libRoots)
|
|
492
787
|
set.add(root);
|
|
@@ -499,17 +794,11 @@ class WatchRootResolver {
|
|
|
499
794
|
}
|
|
500
795
|
return [...set];
|
|
501
796
|
}
|
|
502
|
-
async#resolveLibRoots(libsContainer) {
|
|
503
|
-
const libDeps = await this.#resolveLibDeps(libsContainer);
|
|
504
|
-
if (!libDeps)
|
|
505
|
-
return null;
|
|
506
|
-
return libDeps.map((name) => path4.join(libsContainer, name)).filter((dir) => fs2.existsSync(dir));
|
|
507
|
-
}
|
|
508
797
|
async#resolveLibDeps(libsContainer) {
|
|
509
798
|
const scanInfo = this.#app.getScanInfo({ allowEmpty: true });
|
|
510
799
|
if (scanInfo?.type === "app")
|
|
511
800
|
return scanInfo.libDeps;
|
|
512
|
-
const direct = await WatchRootResolver.#readManifestLibDeps(
|
|
801
|
+
const direct = await WatchRootResolver.#readManifestLibDeps(path6.join(this.#app.cwdPath, "akan.app.json"));
|
|
513
802
|
if (!direct)
|
|
514
803
|
return null;
|
|
515
804
|
const closure = new Set;
|
|
@@ -519,7 +808,7 @@ class WatchRootResolver {
|
|
|
519
808
|
if (!name || closure.has(name))
|
|
520
809
|
continue;
|
|
521
810
|
closure.add(name);
|
|
522
|
-
const nested = await WatchRootResolver.#readManifestLibDeps(
|
|
811
|
+
const nested = await WatchRootResolver.#readManifestLibDeps(path6.join(libsContainer, name, "akan.lib.json"));
|
|
523
812
|
if (!nested)
|
|
524
813
|
return null;
|
|
525
814
|
queue.push(...nested);
|
|
@@ -538,4 +827,4 @@ class WatchRootResolver {
|
|
|
538
827
|
}
|
|
539
828
|
}
|
|
540
829
|
|
|
541
|
-
export { HmrWatcher, WatchRootResolver };
|
|
830
|
+
export { ApplicationBuildReporter, BUILDER_MIN_RSS_RECYCLE_INTERVAL_MS, SOURCE_EXTS, shouldRestartBackendByDevPlan, shouldRestartBuilderByDevPlan, shouldAbandonBackendRecovery, normalizeBackendReportedGeneration, shouldRestartDevHostByDevPlan, devHostStateOf, isLegacyBackendFallbackFile, shouldMarkBuildPhaseRecovered, shouldKeepBuildFailure, createBackendBuildStatus, backendRestartReasonFromMessage, mergeBackendRestartReasons, shouldReplaceLastGoodMessage, shouldQueueBuildStatusReplay, hasBuildFailureForGeneration, decideBuilderRssRecycle, decideBuilderRssSettle, decideIdleSuspend, resolveIdleSuspendMs, hasAnyBuildFailure, shouldRefreshConfigOnIdleWake, shouldHoldForReturningBuilder, shouldWarnBuilderRssCeilingTight, isRssCeilingUnreachable, shouldRelayRecycledFrontendState, mergeInvalidateMessages, buildStatusReplaySequence, filesChangedSince, HmrWatcher, WatchRootResolver };
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// pkgs/@akanjs/devkit/fileSys.ts
|
|
3
3
|
import { lstat, rename, rm, stat } from "fs/promises";
|
|
4
|
+
import nodePath from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
4
6
|
import { Logger } from "akanjs/common";
|
|
7
|
+
var getDirname2 = (url) => nodePath.dirname(fileURLToPath(url));
|
|
5
8
|
|
|
6
|
-
class
|
|
9
|
+
class FileSys2 {
|
|
7
10
|
static logger = new Logger("FileSys");
|
|
8
11
|
static async fileExists(path) {
|
|
9
12
|
return await Bun.file(path).exists();
|
|
@@ -24,7 +27,7 @@ class FileSys {
|
|
|
24
27
|
try {
|
|
25
28
|
return await Bun.file(path).json();
|
|
26
29
|
} catch (error) {
|
|
27
|
-
|
|
30
|
+
FileSys2.logger.error(`Failed to read JSON file: ${path}`);
|
|
28
31
|
throw error;
|
|
29
32
|
}
|
|
30
33
|
}
|
|
@@ -38,7 +41,7 @@ class FileSys {
|
|
|
38
41
|
const temp = `${filePath}.${process.pid}.${Date.now().toString(36)}.tmp`;
|
|
39
42
|
try {
|
|
40
43
|
await Bun.write(temp, content);
|
|
41
|
-
await
|
|
44
|
+
await FileSys2.replace(temp, filePath);
|
|
42
45
|
} catch (error) {
|
|
43
46
|
await rm(temp, { force: true }).catch(() => {
|
|
44
47
|
return;
|
|
@@ -46,10 +49,24 @@ class FileSys {
|
|
|
46
49
|
throw error;
|
|
47
50
|
}
|
|
48
51
|
}
|
|
52
|
+
static #replaceAttempts = 100;
|
|
53
|
+
static async replace(temp, target) {
|
|
54
|
+
for (let attempt = 1;; attempt++) {
|
|
55
|
+
try {
|
|
56
|
+
return await rename(temp, target);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
const code = error.code;
|
|
59
|
+
const busy = process.platform === "win32" && (code === "EPERM" || code === "EACCES" || code === "EBUSY");
|
|
60
|
+
if (!busy || attempt >= FileSys2.#replaceAttempts)
|
|
61
|
+
throw error;
|
|
62
|
+
await Bun.sleep(Math.min(10 * attempt, 50));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
49
66
|
static async writeJson(path, content) {
|
|
50
67
|
return await Bun.file(path).write(`${JSON.stringify(content, null, 2)}
|
|
51
68
|
`);
|
|
52
69
|
}
|
|
53
70
|
}
|
|
54
71
|
|
|
55
|
-
export {
|
|
72
|
+
export { getDirname2, FileSys2 };
|