@akanjs/devkit 3.2.0-rc.3 → 3.2.0-rc.5
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/CHANGELOG.md +62 -4
- package/akanApp/akanApp.host.test.ts +51 -0
- package/akanApp/akanApp.host.ts +3 -3
- package/akanContext.ts +2 -3
- package/applicationBuildRunner.test.ts +67 -0
- package/applicationBuildRunner.ts +1 -0
- package/codeAgent/skills/akan-debug/SKILL.md +6 -2
- package/codeAgent/skills/akan-transport/SKILL.md +2 -0
- package/commandDecorators/argMeta.ts +4 -0
- package/commandDecorators/command.ts +51 -33
- package/commandDecorators/commandDecorators.test.ts +74 -9
- package/frontendBuild/bundleDefine.ts +2 -0
- package/frontendBuild/clientBuildTypes.ts +0 -1
- package/frontendBuild/nativeCore.test.ts +148 -0
- package/frontendBuild/ssrBaseArtifactBuilder.test.ts +26 -0
- package/frontendBuild/ssrBaseArtifactBuilder.ts +23 -19
- package/frontendBuild/vendorImportScanner.test.ts +24 -3
- package/frontendBuild/vendorImportScanner.ts +25 -10
- package/incrementalBuilder/incrementalBuilder.host.test.ts +5 -1
- package/incrementalBuilder/incrementalBuilder.host.ts +2 -1
- package/lint/no-web-only-api-outside-webkit.grit +3 -3
- package/{constantFieldNames.test.ts → moduleFieldNames.test.ts} +24 -2
- package/moduleFieldNames.ts +25 -0
- package/package.json +3 -3
- package/preview/PreviewView.ts +32 -13
- package/preview/previewView.test.ts +33 -3
- package/workflow/plainName.ts +16 -3
- package/constantFieldNames.ts +0 -18
package/preview/PreviewView.ts
CHANGED
|
@@ -77,20 +77,12 @@ export class PreviewView {
|
|
|
77
77
|
}: PreviewProbeOptions = {},
|
|
78
78
|
): Promise<PreviewProbeResult> {
|
|
79
79
|
const logs: string[] = [];
|
|
80
|
-
const view = PreviewView.#
|
|
80
|
+
const view = await PreviewView.#start(
|
|
81
|
+
() => PreviewView.#open({ width, height, engine, onConsole: (line) => logs.push(line) }),
|
|
82
|
+
engine,
|
|
83
|
+
{ startTimeoutMs, timeoutMs },
|
|
84
|
+
);
|
|
81
85
|
try {
|
|
82
|
-
//? A browser that missed its start bound may still come up, so a later page retries it within only its own bound.
|
|
83
|
-
//? One that died or was closed instead is spawned afresh by the next view, which gets the full bound.
|
|
84
|
-
const startMs = PreviewView.#stalled.has(engine) ? Math.min(startTimeoutMs, timeoutMs) : startTimeoutMs;
|
|
85
|
-
const stalled = new Error(`Timed out starting the ${engine} browser after ${startMs / 1_000} s.`);
|
|
86
|
-
await PreviewView.#within(view.navigate("about:blank"), Date.now() + startMs, stalled).then(
|
|
87
|
-
() => PreviewView.#stalled.delete(engine),
|
|
88
|
-
(error: unknown) => {
|
|
89
|
-
if (error === stalled) PreviewView.#stalled.add(engine);
|
|
90
|
-
else PreviewView.#stalled.delete(engine);
|
|
91
|
-
throw error;
|
|
92
|
-
},
|
|
93
|
-
);
|
|
94
86
|
const deadline = Date.now() + timeoutMs;
|
|
95
87
|
const within = <T>(work: Promise<T>, what: string) =>
|
|
96
88
|
PreviewView.#within(
|
|
@@ -122,6 +114,33 @@ export class PreviewView {
|
|
|
122
114
|
}
|
|
123
115
|
}
|
|
124
116
|
|
|
117
|
+
static async #start(
|
|
118
|
+
open: () => WebViewLike,
|
|
119
|
+
engine: PreviewEngine,
|
|
120
|
+
{ startTimeoutMs, timeoutMs }: { startTimeoutMs: number; timeoutMs: number },
|
|
121
|
+
) {
|
|
122
|
+
for (let attempt = 1; ; attempt++) {
|
|
123
|
+
//? A browser that missed its start bound may still come up, so a later page retries it within only its own bound.
|
|
124
|
+
//? One that died or was closed instead is spawned afresh by the next view, which gets the full bound.
|
|
125
|
+
const startMs = PreviewView.#stalled.has(engine) ? Math.min(startTimeoutMs, timeoutMs) : startTimeoutMs;
|
|
126
|
+
const stalled = new Error(`Timed out starting the ${engine} browser after ${startMs / 1_000} s.`);
|
|
127
|
+
let view: WebViewLike | undefined;
|
|
128
|
+
try {
|
|
129
|
+
view = open();
|
|
130
|
+
await PreviewView.#within(view.navigate("about:blank"), Date.now() + startMs, stalled);
|
|
131
|
+
PreviewView.#stalled.delete(engine);
|
|
132
|
+
return view;
|
|
133
|
+
} catch (error) {
|
|
134
|
+
await view?.close();
|
|
135
|
+
if (error === stalled) PreviewView.#stalled.add(engine);
|
|
136
|
+
else PreviewView.#stalled.delete(engine);
|
|
137
|
+
if (error === stalled || attempt > 1) throw error;
|
|
138
|
+
//? Bun reaps a browser that died only on a later timer turn; until then `new Bun.WebView()` throws "Failed to spawn Chrome".
|
|
139
|
+
await Bun.sleep(100);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
125
144
|
static async #load(
|
|
126
145
|
view: WebViewLike,
|
|
127
146
|
url: string,
|
|
@@ -178,9 +178,7 @@ Bun.WebView.closeAll();`,
|
|
|
178
178
|
console.log(await open());
|
|
179
179
|
const killed = open();
|
|
180
180
|
Bun.WebView.closeAll();
|
|
181
|
-
console.
|
|
182
|
-
await Bun.sleep(500);
|
|
183
|
-
console.log(await open());
|
|
181
|
+
console.log(await killed);
|
|
184
182
|
Bun.WebView.closeAll();`,
|
|
185
183
|
6,
|
|
186
184
|
);
|
|
@@ -194,4 +192,36 @@ Bun.WebView.closeAll();`,
|
|
|
194
192
|
},
|
|
195
193
|
browserTestMs,
|
|
196
194
|
);
|
|
195
|
+
|
|
196
|
+
test.skipIf(!slowChromeRuns)(
|
|
197
|
+
"starts a new browser for the page right after one that the browser died on",
|
|
198
|
+
async () => {
|
|
199
|
+
const { code, out, log } = await withSlowChrome(
|
|
200
|
+
`const { promise: loading, resolve } = Promise.withResolvers();
|
|
201
|
+
const server = Bun.serve({
|
|
202
|
+
port: 0,
|
|
203
|
+
fetch: (req) =>
|
|
204
|
+
new URL(req.url).pathname === "/hang"
|
|
205
|
+
? (resolve(), new Promise(() => undefined))
|
|
206
|
+
: new Response("<!doctype html><body>up</body>", { headers: { "content-type": "text/html" } }),
|
|
207
|
+
});
|
|
208
|
+
const open = (path) =>
|
|
209
|
+
PreviewView.probe(\`http://localhost:\${server.port}\${path}\`, { engine: "chrome" }).then(
|
|
210
|
+
(probe) => probe.text,
|
|
211
|
+
(error) => error.message,
|
|
212
|
+
);
|
|
213
|
+
const killed = open("/hang");
|
|
214
|
+
await loading;
|
|
215
|
+
Bun.WebView.closeAll();
|
|
216
|
+
console.error(await killed);
|
|
217
|
+
console.log(await open("/"));
|
|
218
|
+
server.stop(true);
|
|
219
|
+
Bun.WebView.closeAll();`,
|
|
220
|
+
0,
|
|
221
|
+
);
|
|
222
|
+
expect(out, log).toBe("up");
|
|
223
|
+
expect(code, log).toBe(0);
|
|
224
|
+
},
|
|
225
|
+
browserTestMs,
|
|
226
|
+
);
|
|
197
227
|
});
|
package/workflow/plainName.ts
CHANGED
|
@@ -1,15 +1,28 @@
|
|
|
1
1
|
export const sysNamePattern = /^[a-z0-9][a-zA-Z0-9_-]*$/;
|
|
2
2
|
export const identifierPattern = /^_{0,2}[a-zA-Z][a-zA-Z0-9_-]*$/;
|
|
3
|
+
const packageNamePattern = /^(@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/;
|
|
4
|
+
|
|
5
|
+
export type PlainNameKind = "app" | "lib" | "module" | "service" | "scalar";
|
|
3
6
|
|
|
4
7
|
export const isWorkflowSysName = (value: unknown): value is string =>
|
|
5
8
|
typeof value === "string" && sysNamePattern.test(value);
|
|
6
9
|
export const isWorkflowModuleName = (value: unknown): value is string =>
|
|
7
10
|
typeof value === "string" && identifierPattern.test(value);
|
|
8
11
|
|
|
9
|
-
export const
|
|
12
|
+
export const plainNameProblem = (kind: PlainNameKind, name: string) => {
|
|
10
13
|
const isSys = kind === "app" || kind === "lib";
|
|
11
|
-
if (isSys ? isWorkflowSysName(name) : isWorkflowModuleName(name)) return;
|
|
14
|
+
if (isSys ? isWorkflowSysName(name) : isWorkflowModuleName(name)) return null;
|
|
15
|
+
return `${JSON.stringify(name)} is no ${kind} name; use letters, digits, - and _, starting with ${isSys ? "a lowercase letter or digit" : "a letter"}`;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const assertPlainName = (kind: PlainNameKind, name: string) => {
|
|
19
|
+
const problem = plainNameProblem(kind, name);
|
|
20
|
+
if (problem) throw new Error(problem);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const assertPackageName = (name: string) => {
|
|
24
|
+
if (packageNamePattern.test(name)) return;
|
|
12
25
|
throw new Error(
|
|
13
|
-
`${JSON.stringify(name)} is no
|
|
26
|
+
`${JSON.stringify(name)} is no package name; use name or @scope/name, each of lowercase letters, digits, -, . and _, starting with a letter or digit`,
|
|
14
27
|
);
|
|
15
28
|
};
|
package/constantFieldNames.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import ts from "typescript";
|
|
2
|
-
|
|
3
|
-
const isFieldCall = (node: ts.Expression): boolean =>
|
|
4
|
-
ts.isCallExpression(node) &&
|
|
5
|
-
(ts.isIdentifier(node.expression)
|
|
6
|
-
? node.expression.text === "field"
|
|
7
|
-
: ts.isPropertyAccessExpression(node.expression) && isFieldCall(node.expression.expression));
|
|
8
|
-
|
|
9
|
-
export const constantFieldNames = (content: string) => {
|
|
10
|
-
const names: string[] = [];
|
|
11
|
-
const visit = (node: ts.Node) => {
|
|
12
|
-
if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && isFieldCall(node.initializer))
|
|
13
|
-
names.push(node.name.text);
|
|
14
|
-
ts.forEachChild(node, visit);
|
|
15
|
-
};
|
|
16
|
-
visit(ts.createSourceFile("constant.ts", content, ts.ScriptTarget.Latest));
|
|
17
|
-
return names;
|
|
18
|
-
};
|