@akanjs/devkit 1.0.20 → 2.1.0-rc.1
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/README.ko.md +65 -0
- package/README.md +62 -6
- package/aiEditor.ts +304 -0
- package/akanApp/akanApp.host.ts +393 -0
- package/akanApp/index.ts +1 -0
- package/akanConfig/akanConfig.test.ts +236 -0
- package/akanConfig/akanConfig.ts +384 -0
- package/akanConfig/index.ts +2 -0
- package/akanConfig/types.ts +23 -0
- package/applicationBuildReporter.ts +69 -0
- package/applicationBuildRunner.ts +302 -0
- package/applicationReleasePackager.ts +206 -0
- package/artifact/implicitRootLayout.ts +155 -0
- package/artifact/index.ts +1 -0
- package/artifact/routeSeedIndex.test.ts +98 -0
- package/artifact/routeSeedIndex.ts +130 -0
- package/auth.ts +41 -0
- package/builder.ts +164 -0
- package/capacitor.base.config.ts +88 -0
- package/capacitorApp.ts +440 -0
- package/commandDecorators/argMeta.ts +102 -0
- package/commandDecorators/command.ts +351 -0
- package/commandDecorators/commandBuilder.ts +224 -0
- package/commandDecorators/commandDecorators.test.ts +212 -0
- package/commandDecorators/commandMeta.ts +7 -0
- package/commandDecorators/dependencyBuilder.ts +100 -0
- package/{esm/src/commandDecorators/helpFormatter.js → commandDecorators/helpFormatter.ts} +100 -47
- package/{esm/src/commandDecorators/index.js → commandDecorators/index.ts} +4 -2
- package/commandDecorators/targetMeta.ts +31 -0
- package/commandDecorators/types.ts +10 -0
- package/constants.ts +25 -0
- package/createTunnel.ts +36 -0
- package/dependencyScanner.ts +357 -0
- package/devkitUtils.test.ts +259 -0
- package/executors.test.ts +315 -0
- package/executors.ts +1390 -0
- package/{esm/src/extractDeps.js → extractDeps.ts} +26 -20
- package/{esm/src/fileEditor.js → fileEditor.ts} +51 -32
- package/fileSys.ts +39 -0
- package/frontendBuild/allRoutesBuilder.ts +103 -0
- package/frontendBuild/buildRouteClient.test.ts +190 -0
- package/frontendBuild/clientBuildTypes.ts +114 -0
- package/frontendBuild/clientEntriesBundler.ts +303 -0
- package/frontendBuild/clientEntryDiscovery.ts +199 -0
- package/frontendBuild/csrArtifactBuilder.ts +237 -0
- package/frontendBuild/cssCompiler.ts +286 -0
- package/frontendBuild/cssImportResolver.ts +116 -0
- package/frontendBuild/fontOptimizer.ts +427 -0
- package/frontendBuild/frontendBuild.test.ts +204 -0
- package/frontendBuild/hmrChangeClassifier.ts +28 -0
- package/frontendBuild/hmrWatcher.ts +102 -0
- package/frontendBuild/index.ts +18 -0
- package/frontendBuild/pagesBundleBuilder.ts +137 -0
- package/frontendBuild/pagesEntrySourceGenerator.ts +37 -0
- package/frontendBuild/precompressArtifacts.ts +59 -0
- package/frontendBuild/routeClientBuilder.ts +290 -0
- package/frontendBuild/routesManifestArtifactSerializer.ts +62 -0
- package/frontendBuild/ssrBaseArtifactBuilder.ts +139 -0
- package/frontendBuild/vendorSpecifiers.ts +16 -0
- package/frontendBuild/watchRootResolver.ts +28 -0
- package/getCredentials.ts +19 -0
- package/getDirname.ts +3 -0
- package/getModelFileData.ts +59 -0
- package/getRelatedCnsts.ts +313 -0
- package/guideline.ts +19 -0
- package/incrementalBuilder/incrementalBuilder.host.test.ts +51 -0
- package/incrementalBuilder/incrementalBuilder.host.ts +152 -0
- package/incrementalBuilder/incrementalBuilder.proc.ts +331 -0
- package/incrementalBuilder/index.ts +1 -0
- package/{esm/src/index.js → index.ts} +28 -15
- package/lint/no-deep-internal-import.grit +25 -0
- package/lint/no-import-client-functions.grit +32 -0
- package/lint/no-import-external-library.grit +21 -0
- package/lint/no-js-private-class-method.grit +42 -0
- package/lint/no-use-client-in-server.grit +7 -0
- package/lint/non-scalar-props-restricted.grit +13 -0
- package/linter.ts +271 -0
- package/mobile/index.ts +1 -0
- package/mobile/mobileTarget.test.ts +53 -0
- package/mobile/mobileTarget.ts +88 -0
- package/package.json +48 -31
- package/prompter.ts +72 -0
- package/scanInfo.ts +606 -0
- package/selectModel.ts +11 -0
- package/{esm/src/spinner.js → spinner.ts} +22 -28
- package/{esm/src/capacitorApp.js → src/capacitorApp.ts} +82 -81
- package/sshTunnel.ts +152 -0
- package/{esm/src/streamAi.js → streamAi.ts} +18 -12
- package/transforms/barrelAnalyzer.ts +278 -0
- package/transforms/barrelImportsPlugin.ts +504 -0
- package/transforms/externalizeFrameworkPlugin.ts +185 -0
- package/transforms/index.ts +5 -0
- package/transforms/rscUseClientTransform.ts +59 -0
- package/transforms/transforms.test.ts +208 -0
- package/transforms/useClientBundlePlugin.ts +47 -0
- package/tsconfig.json +37 -0
- package/typeChecker.ts +264 -0
- package/types.ts +44 -0
- package/ui/MultiScrollList.tsx +242 -0
- package/ui/ScrollList.tsx +107 -0
- package/ui/index.ts +2 -0
- package/{esm/src/uploadRelease.js → uploadRelease.ts} +50 -34
- package/{esm/src/useStdoutDimensions.js → useStdoutDimensions.ts} +5 -5
- package/cjs/index.js +0 -21
- package/cjs/src/aiEditor.js +0 -311
- package/cjs/src/auth.js +0 -72
- package/cjs/src/builder.js +0 -114
- package/cjs/src/capacitorApp.js +0 -313
- package/cjs/src/commandDecorators/argMeta.js +0 -88
- package/cjs/src/commandDecorators/command.js +0 -324
- package/cjs/src/commandDecorators/commandMeta.js +0 -30
- package/cjs/src/commandDecorators/helpFormatter.js +0 -211
- package/cjs/src/commandDecorators/index.js +0 -31
- package/cjs/src/commandDecorators/targetMeta.js +0 -57
- package/cjs/src/commandDecorators/types.js +0 -15
- package/cjs/src/constants.js +0 -46
- package/cjs/src/createTunnel.js +0 -49
- package/cjs/src/dependencyScanner.js +0 -220
- package/cjs/src/executors.js +0 -964
- package/cjs/src/extractDeps.js +0 -103
- package/cjs/src/fileEditor.js +0 -120
- package/cjs/src/getCredentials.js +0 -44
- package/cjs/src/getDirname.js +0 -38
- package/cjs/src/getModelFileData.js +0 -66
- package/cjs/src/getRelatedCnsts.js +0 -260
- package/cjs/src/guideline.js +0 -15
- package/cjs/src/index.js +0 -65
- package/cjs/src/linter.js +0 -238
- package/cjs/src/prompter.js +0 -85
- package/cjs/src/scanInfo.js +0 -491
- package/cjs/src/selectModel.js +0 -46
- package/cjs/src/spinner.js +0 -93
- package/cjs/src/streamAi.js +0 -62
- package/cjs/src/typeChecker.js +0 -207
- package/cjs/src/types.js +0 -15
- package/cjs/src/uploadRelease.js +0 -112
- package/cjs/src/useStdoutDimensions.js +0 -43
- package/esm/index.js +0 -1
- package/esm/src/aiEditor.js +0 -282
- package/esm/src/auth.js +0 -42
- package/esm/src/builder.js +0 -81
- package/esm/src/commandDecorators/argMeta.js +0 -54
- package/esm/src/commandDecorators/command.js +0 -290
- package/esm/src/commandDecorators/commandMeta.js +0 -7
- package/esm/src/commandDecorators/targetMeta.js +0 -33
- package/esm/src/commandDecorators/types.js +0 -0
- package/esm/src/constants.js +0 -17
- package/esm/src/createTunnel.js +0 -26
- package/esm/src/dependencyScanner.js +0 -187
- package/esm/src/executors.js +0 -928
- package/esm/src/getCredentials.js +0 -11
- package/esm/src/getDirname.js +0 -5
- package/esm/src/getModelFileData.js +0 -33
- package/esm/src/getRelatedCnsts.js +0 -221
- package/esm/src/guideline.js +0 -0
- package/esm/src/linter.js +0 -205
- package/esm/src/prompter.js +0 -51
- package/esm/src/scanInfo.js +0 -455
- package/esm/src/selectModel.js +0 -13
- package/esm/src/typeChecker.js +0 -174
- package/esm/src/types.js +0 -0
- package/index.d.ts +0 -1
- package/src/aiEditor.d.ts +0 -50
- package/src/auth.d.ts +0 -9
- package/src/builder.d.ts +0 -18
- package/src/capacitorApp.d.ts +0 -39
- package/src/commandDecorators/argMeta.d.ts +0 -67
- package/src/commandDecorators/command.d.ts +0 -2
- package/src/commandDecorators/commandMeta.d.ts +0 -2
- package/src/commandDecorators/helpFormatter.d.ts +0 -3
- package/src/commandDecorators/index.d.ts +0 -6
- package/src/commandDecorators/targetMeta.d.ts +0 -19
- package/src/commandDecorators/types.d.ts +0 -1
- package/src/constants.d.ts +0 -26
- package/src/createTunnel.d.ts +0 -8
- package/src/dependencyScanner.d.ts +0 -23
- package/src/executors.d.ts +0 -296
- package/src/extractDeps.d.ts +0 -7
- package/src/fileEditor.d.ts +0 -16
- package/src/getCredentials.d.ts +0 -12
- package/src/getDirname.d.ts +0 -1
- package/src/getModelFileData.d.ts +0 -16
- package/src/getRelatedCnsts.d.ts +0 -53
- package/src/guideline.d.ts +0 -19
- package/src/index.d.ts +0 -23
- package/src/linter.d.ts +0 -109
- package/src/prompter.d.ts +0 -14
- package/src/scanInfo.d.ts +0 -82
- package/src/selectModel.d.ts +0 -1
- package/src/spinner.d.ts +0 -20
- package/src/streamAi.d.ts +0 -6
- package/src/typeChecker.d.ts +0 -52
- package/src/types.d.ts +0 -31
- package/src/uploadRelease.d.ts +0 -10
- package/src/useStdoutDimensions.d.ts +0 -1
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
// Pure "use client" transform: when `source` starts with the `"use client"`
|
|
4
|
+
// directive, replace its exports with `registerClientReference` stubs so the
|
|
5
|
+
// RSC renderer can serialize them as client component references instead of
|
|
6
|
+
// trying to run them on the server.
|
|
7
|
+
|
|
8
|
+
// Matches `"use client"` or `'use client'` at the start of a file,
|
|
9
|
+
// optionally after leading whitespace and JS comments.
|
|
10
|
+
const USE_CLIENT_RE = /^\s*(?:\/\*[\s\S]*?\*\/\s*|\/\/[^\n]*\n\s*)*["']use client["']/;
|
|
11
|
+
const IMPLICIT_ROOT_LAYOUT_RE =
|
|
12
|
+
/[/\\]\.akan[/\\]generated[/\\](?:implicit-root-layout|root-layouts[/\\].*__root_layout)\.(tsx|ts|jsx|js)$/;
|
|
13
|
+
|
|
14
|
+
export interface UseClientTransformArgs {
|
|
15
|
+
path: string;
|
|
16
|
+
workspaceRoot?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function toClientReferencePath(absPath: string, workspaceRoot: string): string {
|
|
20
|
+
return path.relative(path.resolve(workspaceRoot), path.resolve(absPath)).split(path.sep).join("/");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Returns the stubbed module source if `source` is a client module, else null.
|
|
25
|
+
* The returned source is TypeScript-compatible (loader "ts" is safe).
|
|
26
|
+
*/
|
|
27
|
+
export function transformUseClient(source: string, args: UseClientTransformArgs): string | null {
|
|
28
|
+
if (!USE_CLIENT_RE.test(source)) return null;
|
|
29
|
+
if (IMPLICIT_ROOT_LAYOUT_RE.test(args.path)) return null;
|
|
30
|
+
const transpiler = new Bun.Transpiler({ loader: loaderFor(args.path) });
|
|
31
|
+
const { exports } = transpiler.scan(source);
|
|
32
|
+
if (exports.length === 0) return null;
|
|
33
|
+
|
|
34
|
+
const referencePath = args.workspaceRoot ? toClientReferencePath(args.path, args.workspaceRoot) : args.path;
|
|
35
|
+
const filePathLit = JSON.stringify(referencePath);
|
|
36
|
+
const lines: string[] = [`import { registerClientReference } from "react-server-dom-webpack/server.node";`];
|
|
37
|
+
|
|
38
|
+
for (const name of exports) {
|
|
39
|
+
const nameLit = JSON.stringify(name);
|
|
40
|
+
const errMsg = JSON.stringify(
|
|
41
|
+
`Attempted to call '${name}' from '${referencePath}' on the server, but it is a client-only export.`,
|
|
42
|
+
);
|
|
43
|
+
const proxy = `() => { throw new Error(${errMsg}); }`;
|
|
44
|
+
const binding =
|
|
45
|
+
name === "default"
|
|
46
|
+
? `export default registerClientReference(${proxy}, ${filePathLit}, ${nameLit});`
|
|
47
|
+
: `export const ${name} = registerClientReference(${proxy}, ${filePathLit}, ${nameLit});`;
|
|
48
|
+
lines.push(binding);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return lines.join("\n");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function loaderFor(absPath: string): "ts" | "tsx" | "js" | "jsx" {
|
|
55
|
+
if (absPath.endsWith(".tsx")) return "tsx";
|
|
56
|
+
if (absPath.endsWith(".jsx")) return "jsx";
|
|
57
|
+
if (absPath.endsWith(".ts")) return "ts";
|
|
58
|
+
return "js";
|
|
59
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { BarrelAnalyzer } from "./barrelAnalyzer";
|
|
6
|
+
import { rewriteBarrelImports } from "./barrelImportsPlugin";
|
|
7
|
+
import { toClientReferencePath, transformUseClient } from "./rscUseClientTransform";
|
|
8
|
+
|
|
9
|
+
const tempRoots: string[] = [];
|
|
10
|
+
|
|
11
|
+
const makeTempRoot = async () => {
|
|
12
|
+
const root = await mkdtemp(path.join(os.tmpdir(), "akan-devkit-transform-"));
|
|
13
|
+
tempRoots.push(root);
|
|
14
|
+
return root;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const write = async (filePath: string, content: string) => {
|
|
18
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
19
|
+
await writeFile(filePath, content);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
afterEach(async () => {
|
|
23
|
+
await Promise.all(tempRoots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe("transformUseClient", () => {
|
|
27
|
+
test("returns null for non-client modules and generated root layouts", () => {
|
|
28
|
+
expect(transformUseClient("export const value = 1;", { path: "/repo/app/page.tsx" })).toBeNull();
|
|
29
|
+
expect(
|
|
30
|
+
transformUseClient('"use client"; export const value = 1;', {
|
|
31
|
+
path: "/repo/apps/demo/.akan/generated/implicit-root-layout.tsx",
|
|
32
|
+
}),
|
|
33
|
+
).toBeNull();
|
|
34
|
+
expect(
|
|
35
|
+
transformUseClient('"use client"; export const value = 1;', {
|
|
36
|
+
path: "/repo/apps/demo/.akan/generated/root-layouts/admin__root_layout.tsx",
|
|
37
|
+
}),
|
|
38
|
+
).toBeNull();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("stubs named and default exports as RSC client references", () => {
|
|
42
|
+
const source = [
|
|
43
|
+
"// comment before directive",
|
|
44
|
+
'"use client";',
|
|
45
|
+
"export const Button = () => null;",
|
|
46
|
+
"export function useThing() { return null; }",
|
|
47
|
+
"export default function DefaultButton() { return null; }",
|
|
48
|
+
"",
|
|
49
|
+
].join("\n");
|
|
50
|
+
|
|
51
|
+
const transformed = transformUseClient(source, {
|
|
52
|
+
path: "/repo/apps/demo/components/Button.tsx",
|
|
53
|
+
workspaceRoot: "/repo",
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
expect(transformed).toContain('import { registerClientReference } from "react-server-dom-webpack/server.node";');
|
|
57
|
+
expect(transformed).toContain('"apps/demo/components/Button.tsx"');
|
|
58
|
+
expect(transformed).toContain("export const Button = registerClientReference");
|
|
59
|
+
expect(transformed).toContain("export const useThing = registerClientReference");
|
|
60
|
+
expect(transformed).toContain("export default registerClientReference");
|
|
61
|
+
expect(toClientReferencePath("/repo/apps/demo/Button.tsx", "/repo")).toBe("apps/demo/Button.tsx");
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe("BarrelAnalyzer and rewriteBarrelImports", () => {
|
|
66
|
+
test("analyzes local, named, aliased, and star re-exports while skipping type and namespace exports", async () => {
|
|
67
|
+
const root = await makeTempRoot();
|
|
68
|
+
const pkgDir = path.join(root, "pkg");
|
|
69
|
+
await write(
|
|
70
|
+
path.join(pkgDir, "index.ts"),
|
|
71
|
+
[
|
|
72
|
+
'export { A, B as Bee, type TypeOnly } from "./leaf";',
|
|
73
|
+
'export * from "./star";',
|
|
74
|
+
'export * as ns from "./namespace";',
|
|
75
|
+
"export const Local = 1;",
|
|
76
|
+
"export type LocalType = string;",
|
|
77
|
+
"",
|
|
78
|
+
].join("\n"),
|
|
79
|
+
);
|
|
80
|
+
await write(
|
|
81
|
+
path.join(pkgDir, "leaf.ts"),
|
|
82
|
+
["export const A = 1;", "export const B = 2;", "export type TypeOnly = string;", ""].join("\n"),
|
|
83
|
+
);
|
|
84
|
+
await write(path.join(pkgDir, "star.ts"), "export const Star = 3;\n");
|
|
85
|
+
await write(path.join(pkgDir, "namespace.ts"), "export const Hidden = 4;\n");
|
|
86
|
+
|
|
87
|
+
const analyzer = new BarrelAnalyzer({
|
|
88
|
+
resolvePackage: async () => ({ pkgName: "@scope/pkg", entryFile: path.join(pkgDir, "index.ts"), pkgDir }),
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const map = await analyzer.analyze("@scope/pkg");
|
|
92
|
+
expect(map?.get("A")).toEqual({ subpath: "@scope/pkg/leaf", originalName: "A" });
|
|
93
|
+
expect(map?.get("Bee")).toEqual({ subpath: "@scope/pkg/leaf", originalName: "B" });
|
|
94
|
+
expect(map?.get("Star")).toEqual({ subpath: "@scope/pkg/star", originalName: "Star" });
|
|
95
|
+
expect(map?.get("Local")).toEqual({ subpath: "@scope/pkg", originalName: "Local" });
|
|
96
|
+
expect(map?.has("TypeOnly")).toBe(false);
|
|
97
|
+
expect(map?.has("ns")).toBe(false);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("rewrites flattenable named imports and preserves default, type, and unknown imports", async () => {
|
|
101
|
+
const analyzer = {
|
|
102
|
+
analyze: async () =>
|
|
103
|
+
new Map([
|
|
104
|
+
["A", { subpath: "@scope/pkg/leaf", originalName: "A" }],
|
|
105
|
+
["Bee", { subpath: "@scope/pkg/leaf", originalName: "B" }],
|
|
106
|
+
]),
|
|
107
|
+
} as BarrelAnalyzer;
|
|
108
|
+
|
|
109
|
+
const rewritten = await rewriteBarrelImports(
|
|
110
|
+
'import DefaultExport, { A, Bee as LocalBee, type Shape, Missing } from "@scope/pkg";\nconsole.log(A);',
|
|
111
|
+
["@scope/pkg"],
|
|
112
|
+
analyzer,
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
expect(rewritten).toBe(
|
|
116
|
+
[
|
|
117
|
+
'import DefaultExport, { type Shape, Missing } from "@scope/pkg";',
|
|
118
|
+
'import { A, B as LocalBee } from "@scope/pkg/leaf";',
|
|
119
|
+
"console.log(A);",
|
|
120
|
+
].join("\n"),
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
expect(await rewriteBarrelImports('import * as pkg from "@scope/pkg";', ["@scope/pkg"], analyzer)).toBeNull();
|
|
124
|
+
expect(await rewriteBarrelImports('import type { A } from "@scope/pkg";', ["@scope/pkg"], analyzer)).toBeNull();
|
|
125
|
+
expect(await rewriteBarrelImports('import { A } from "@other/pkg";', ["@scope/pkg"], analyzer)).toBeNull();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("preserves generated client barrel side effects when flattening app client imports", async () => {
|
|
129
|
+
const analyzer = {
|
|
130
|
+
analyze: async () => new Map([["st", { subpath: "@apps/demo/lib/st", originalName: "st" }]]),
|
|
131
|
+
} as BarrelAnalyzer;
|
|
132
|
+
|
|
133
|
+
const rewritten = await rewriteBarrelImports(
|
|
134
|
+
'import { st } from "@apps/demo/client";\nvoid st;\n',
|
|
135
|
+
["@apps/demo/client"],
|
|
136
|
+
analyzer,
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
expect(rewritten).toBe(
|
|
140
|
+
['import "@apps/demo/client";', 'import { st } from "@apps/demo/lib/st";', "void st;\n"].join("\n"),
|
|
141
|
+
);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("does not rewrite import-looking code inside template literals", async () => {
|
|
145
|
+
const analyzer = {
|
|
146
|
+
analyze: async () => new Map([["AkanApp", { subpath: "akanjs/server/akanApp", originalName: "AkanApp" }]]),
|
|
147
|
+
} as BarrelAnalyzer;
|
|
148
|
+
|
|
149
|
+
const source = [
|
|
150
|
+
'import { Code } from "@apps/docs/ui";',
|
|
151
|
+
"export const Example = () => (",
|
|
152
|
+
" <Code.Snippet",
|
|
153
|
+
" code={`",
|
|
154
|
+
'import { AkanApp } from "akanjs/server";',
|
|
155
|
+
"",
|
|
156
|
+
"void new AkanApp().start();",
|
|
157
|
+
"`}",
|
|
158
|
+
" />",
|
|
159
|
+
");",
|
|
160
|
+
"",
|
|
161
|
+
].join("\n");
|
|
162
|
+
|
|
163
|
+
expect(await rewriteBarrelImports(source, ["akanjs/server"], analyzer)).toBeNull();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("rewrites single-package Akan facet barrels to leaf subpaths", async () => {
|
|
167
|
+
const analyzer = {
|
|
168
|
+
analyze: async () =>
|
|
169
|
+
new Map([["BottomInset", { subpath: "akanjs/ui/Layout/BottomInset", originalName: "BottomInset" }]]),
|
|
170
|
+
} as BarrelAnalyzer;
|
|
171
|
+
|
|
172
|
+
const rewritten = await rewriteBarrelImports('import { BottomInset } from "akanjs/ui";\n', ["akanjs/ui"], analyzer);
|
|
173
|
+
|
|
174
|
+
expect(rewritten).toBe('import { BottomInset } from "akanjs/ui/Layout/BottomInset";\n');
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test("preserves concrete file paths for package-exported barrels", async () => {
|
|
178
|
+
const root = await makeTempRoot();
|
|
179
|
+
await write(
|
|
180
|
+
path.join(root, "node_modules/akanjs/ui/index.ts"),
|
|
181
|
+
'export { Link } from "./Link";\nexport { System } from "./System";\n',
|
|
182
|
+
);
|
|
183
|
+
await write(path.join(root, "node_modules/akanjs/ui/Link/index.tsx"), "export const Link = () => null;\n");
|
|
184
|
+
await write(path.join(root, "node_modules/akanjs/ui/System/index.tsx"), "export const System = () => null;\n");
|
|
185
|
+
|
|
186
|
+
const analyzer = new BarrelAnalyzer({
|
|
187
|
+
resolvePackage: async () => ({
|
|
188
|
+
pkgName: "akanjs/ui",
|
|
189
|
+
entryFile: path.join(root, "node_modules/akanjs/ui/index.ts"),
|
|
190
|
+
pkgDir: path.join(root, "node_modules/akanjs/ui"),
|
|
191
|
+
preserveFilePath: true,
|
|
192
|
+
}),
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
const rewritten = await rewriteBarrelImports(
|
|
196
|
+
'import { Link, System } from "akanjs/ui";\n',
|
|
197
|
+
["akanjs/ui"],
|
|
198
|
+
analyzer,
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
expect(rewritten).toBe(
|
|
202
|
+
`${[
|
|
203
|
+
'import { Link } from "akanjs/ui/Link/index.tsx";',
|
|
204
|
+
'import { System } from "akanjs/ui/System/index.tsx";',
|
|
205
|
+
].join("\n")}\n`,
|
|
206
|
+
);
|
|
207
|
+
});
|
|
208
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { BunPlugin } from "bun";
|
|
2
|
+
import { transformUseClient } from "./rscUseClientTransform";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* BunPlugin that stubs `"use client"` modules so the server bundle sees
|
|
6
|
+
* `registerClientReference(proxy, absPath, name)` stubs instead of the
|
|
7
|
+
* original client code.
|
|
8
|
+
*
|
|
9
|
+
* The bundle produced by `PagesBundleBuilder` is loaded in the RSC worker
|
|
10
|
+
* with `--conditions react-server`; client components are never supposed
|
|
11
|
+
* to execute there. The resulting stub references keep the client
|
|
12
|
+
* component manifest lookups working at render time while ensuring the
|
|
13
|
+
* server bundle never drags the real client source graph in.
|
|
14
|
+
*
|
|
15
|
+
* Important: the second argument to `registerClientReference` must match the
|
|
16
|
+
* key the client manifest uses. When `workspaceRoot` is provided, both sides
|
|
17
|
+
* use workspace-relative keys so production artifacts are portable.
|
|
18
|
+
*/
|
|
19
|
+
export function createUseClientBundlePlugin(options: { workspaceRoot?: string } = {}): BunPlugin {
|
|
20
|
+
return {
|
|
21
|
+
name: "akan-use-client-bundle",
|
|
22
|
+
setup(build) {
|
|
23
|
+
build.onLoad({ filter: /\.(tsx|ts|jsx|js)$/ }, async (args) => {
|
|
24
|
+
if (args.path.includes("/node_modules/")) return undefined;
|
|
25
|
+
let source: string;
|
|
26
|
+
try {
|
|
27
|
+
source = await Bun.file(args.path).text();
|
|
28
|
+
} catch {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
const stubbed = transformUseClient(source, {
|
|
32
|
+
path: args.path,
|
|
33
|
+
workspaceRoot: options.workspaceRoot,
|
|
34
|
+
});
|
|
35
|
+
if (stubbed === null) return undefined;
|
|
36
|
+
return { contents: stubbed, loader: loaderFor(args.path) };
|
|
37
|
+
});
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function loaderFor(absPath: string): "ts" | "tsx" | "js" | "jsx" {
|
|
43
|
+
if (absPath.endsWith(".tsx")) return "tsx";
|
|
44
|
+
if (absPath.endsWith(".jsx")) return "jsx";
|
|
45
|
+
if (absPath.endsWith(".ts")) return "ts";
|
|
46
|
+
return "js";
|
|
47
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"lib": [
|
|
4
|
+
"ESNext",
|
|
5
|
+
"DOM"
|
|
6
|
+
],
|
|
7
|
+
"target": "ESNext",
|
|
8
|
+
"module": "esnext",
|
|
9
|
+
"moduleDetection": "force",
|
|
10
|
+
"jsx": "react-jsx",
|
|
11
|
+
"allowJs": true,
|
|
12
|
+
"types": [
|
|
13
|
+
"bun"
|
|
14
|
+
],
|
|
15
|
+
"moduleResolution": "bundler",
|
|
16
|
+
"allowImportingTsExtensions": true,
|
|
17
|
+
"verbatimModuleSyntax": true,
|
|
18
|
+
"noEmit": true,
|
|
19
|
+
"strict": true,
|
|
20
|
+
"skipLibCheck": true,
|
|
21
|
+
"noFallthroughCasesInSwitch": true,
|
|
22
|
+
"noUncheckedSideEffectImports": false,
|
|
23
|
+
"noUncheckedIndexedAccess": false,
|
|
24
|
+
"noImplicitOverride": true,
|
|
25
|
+
"noUnusedLocals": false,
|
|
26
|
+
"noUnusedParameters": false,
|
|
27
|
+
"noPropertyAccessFromIndexSignature": false,
|
|
28
|
+
"experimentalDecorators": true,
|
|
29
|
+
"paths": {}
|
|
30
|
+
},
|
|
31
|
+
"extends": "../../../tsconfig.json",
|
|
32
|
+
"files": [],
|
|
33
|
+
"include": [
|
|
34
|
+
"**/*.ts",
|
|
35
|
+
"**/*.tsx"
|
|
36
|
+
]
|
|
37
|
+
}
|
package/typeChecker.ts
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import * as ts from "typescript";
|
|
5
|
+
|
|
6
|
+
import type { Executor } from "./executors";
|
|
7
|
+
|
|
8
|
+
export interface ProjectTypecheckResult {
|
|
9
|
+
configPath: string;
|
|
10
|
+
diagnostics: ts.Diagnostic[];
|
|
11
|
+
errors: ts.Diagnostic[];
|
|
12
|
+
warnings: ts.Diagnostic[];
|
|
13
|
+
message: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class TypeChecker {
|
|
17
|
+
readonly configPath: string;
|
|
18
|
+
readonly configFile: { config?: any; error?: ts.Diagnostic };
|
|
19
|
+
readonly config: ts.ParsedCommandLine;
|
|
20
|
+
constructor(executor: Executor) {
|
|
21
|
+
const configPath = this.#findConfigFile(executor.cwdPath);
|
|
22
|
+
if (!configPath) throw new Error("No tsconfig.json found in the project");
|
|
23
|
+
this.configPath = configPath;
|
|
24
|
+
this.configFile = ts.readConfigFile(this.configPath, (fileName) => ts.sys.readFile(fileName));
|
|
25
|
+
const parsedConfig = ts.parseJsonConfigFileContent(
|
|
26
|
+
this.configFile.config,
|
|
27
|
+
ts.sys,
|
|
28
|
+
path.dirname(this.configPath),
|
|
29
|
+
undefined,
|
|
30
|
+
this.configPath,
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
if (parsedConfig.errors.length > 0) {
|
|
34
|
+
const errorMessages = parsedConfig.errors
|
|
35
|
+
.map((error) => ts.flattenDiagnosticMessageText(error.messageText, "\n"))
|
|
36
|
+
.join("\n");
|
|
37
|
+
throw new Error(`Error parsing tsconfig.json:\n${errorMessages}`);
|
|
38
|
+
}
|
|
39
|
+
this.config = parsedConfig;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Find tsconfig.json by walking up the directory tree
|
|
43
|
+
*/
|
|
44
|
+
#findConfigFile(searchPath: string): string | undefined {
|
|
45
|
+
return ts.findConfigFile(searchPath, (fileName) => ts.sys.fileExists(fileName), "tsconfig.json");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Type-check a single TypeScript file
|
|
50
|
+
* @param filePath - Path to the TypeScript file to check
|
|
51
|
+
* @returns Array of diagnostic messages
|
|
52
|
+
*/
|
|
53
|
+
check(filePath: string): {
|
|
54
|
+
diagnostics: ts.Diagnostic[];
|
|
55
|
+
errors: ts.Diagnostic[];
|
|
56
|
+
warnings: ts.Diagnostic[];
|
|
57
|
+
fileDiagnostics: ts.Diagnostic[];
|
|
58
|
+
fileErrors: ts.Diagnostic[];
|
|
59
|
+
fileWarnings: ts.Diagnostic[];
|
|
60
|
+
} {
|
|
61
|
+
const program = ts.createProgram([filePath], this.config.options);
|
|
62
|
+
const diagnostics = [
|
|
63
|
+
...program.getSemanticDiagnostics(),
|
|
64
|
+
...program.getSyntacticDiagnostics(),
|
|
65
|
+
// Only check declaration diagnostics when declaration emit is enabled
|
|
66
|
+
...(this.config.options.declaration ? program.getDeclarationDiagnostics() : []),
|
|
67
|
+
];
|
|
68
|
+
const errors = diagnostics.filter((diagnostic) => diagnostic.category === ts.DiagnosticCategory.Error);
|
|
69
|
+
const warnings = diagnostics.filter((diagnostic) => diagnostic.category === ts.DiagnosticCategory.Warning);
|
|
70
|
+
const fileDiagnostics = diagnostics.filter((diagnostic) => diagnostic.file?.fileName === filePath);
|
|
71
|
+
const fileErrors = fileDiagnostics.filter((diagnostic) => diagnostic.category === ts.DiagnosticCategory.Error);
|
|
72
|
+
const fileWarnings = fileDiagnostics.filter((diagnostic) => diagnostic.category === ts.DiagnosticCategory.Warning);
|
|
73
|
+
return { diagnostics, errors, warnings, fileDiagnostics, fileErrors, fileWarnings };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Format diagnostics for console output
|
|
78
|
+
* @param diagnostics - Array of TypeScript diagnostics
|
|
79
|
+
* @returns Formatted string
|
|
80
|
+
*/
|
|
81
|
+
formatDiagnostics(diagnostics: ts.Diagnostic[]): string {
|
|
82
|
+
if (diagnostics.length === 0) return chalk.bold("✅ No type errors found");
|
|
83
|
+
|
|
84
|
+
const output: string[] = [];
|
|
85
|
+
let errorCount = 0;
|
|
86
|
+
let warningCount = 0;
|
|
87
|
+
let suggestionCount = 0;
|
|
88
|
+
|
|
89
|
+
// Group diagnostics by file
|
|
90
|
+
const diagnosticsByFile = new Map<string, ts.Diagnostic[]>();
|
|
91
|
+
diagnostics.forEach((diagnostic) => {
|
|
92
|
+
if (diagnostic.category === ts.DiagnosticCategory.Error) errorCount++;
|
|
93
|
+
else if (diagnostic.category === ts.DiagnosticCategory.Warning) warningCount++;
|
|
94
|
+
else if (diagnostic.category === ts.DiagnosticCategory.Suggestion) suggestionCount++;
|
|
95
|
+
|
|
96
|
+
if (diagnostic.file) {
|
|
97
|
+
const fileName = diagnostic.file.fileName;
|
|
98
|
+
if (!diagnosticsByFile.has(fileName)) diagnosticsByFile.set(fileName, []);
|
|
99
|
+
const fileDiagnostics = diagnosticsByFile.get(fileName);
|
|
100
|
+
if (fileDiagnostics) fileDiagnostics.push(diagnostic);
|
|
101
|
+
} else {
|
|
102
|
+
if (!diagnosticsByFile.has("")) diagnosticsByFile.set("", []);
|
|
103
|
+
const fileDiagnostics = diagnosticsByFile.get("");
|
|
104
|
+
if (fileDiagnostics) fileDiagnostics.push(diagnostic);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Format diagnostics by file
|
|
109
|
+
diagnosticsByFile.forEach((fileDiagnostics, fileName) => {
|
|
110
|
+
if (fileName) output.push(`\n${chalk.cyan(fileName)}`);
|
|
111
|
+
|
|
112
|
+
fileDiagnostics.forEach((diagnostic) => {
|
|
113
|
+
const categoryText =
|
|
114
|
+
diagnostic.category === ts.DiagnosticCategory.Error
|
|
115
|
+
? "error"
|
|
116
|
+
: diagnostic.category === ts.DiagnosticCategory.Warning
|
|
117
|
+
? "warning"
|
|
118
|
+
: "suggestion";
|
|
119
|
+
const categoryColor =
|
|
120
|
+
diagnostic.category === ts.DiagnosticCategory.Error
|
|
121
|
+
? chalk.red
|
|
122
|
+
: diagnostic.category === ts.DiagnosticCategory.Warning
|
|
123
|
+
? chalk.yellow
|
|
124
|
+
: chalk.blue;
|
|
125
|
+
const icon =
|
|
126
|
+
diagnostic.category === ts.DiagnosticCategory.Error
|
|
127
|
+
? "❌"
|
|
128
|
+
: diagnostic.category === ts.DiagnosticCategory.Warning
|
|
129
|
+
? "⚠️"
|
|
130
|
+
: "💡";
|
|
131
|
+
const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
|
|
132
|
+
const tsCode = chalk.dim(`(TS${diagnostic.code})`);
|
|
133
|
+
|
|
134
|
+
if (diagnostic.file && diagnostic.start !== undefined) {
|
|
135
|
+
const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
|
|
136
|
+
|
|
137
|
+
output.push(`\n ${icon} ${categoryColor(categoryText)}: ${message} ${tsCode}`);
|
|
138
|
+
output.push(` ${chalk.gray("at")} ${fileName}:${chalk.bold(`${line + 1}:${character + 1}`)}`);
|
|
139
|
+
|
|
140
|
+
// Show source line with underline
|
|
141
|
+
const sourceLines = diagnostic.file.text.split("\n");
|
|
142
|
+
if (line < sourceLines.length) {
|
|
143
|
+
const sourceLine = sourceLines[line];
|
|
144
|
+
const lineNumber = (line + 1).toString().padStart(5, " ");
|
|
145
|
+
|
|
146
|
+
output.push(`\n${chalk.dim(`${lineNumber} |`)} ${sourceLine}`);
|
|
147
|
+
|
|
148
|
+
// Create underline with squiggly for TypeScript
|
|
149
|
+
const underlinePrefix = " ".repeat(character);
|
|
150
|
+
const length = diagnostic.length ?? 1;
|
|
151
|
+
const underline = "~".repeat(Math.max(1, length));
|
|
152
|
+
|
|
153
|
+
output.push(
|
|
154
|
+
`${chalk.dim(`${" ".repeat(lineNumber.length)} |`)} ${underlinePrefix}${categoryColor(underline)}`,
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
} else output.push(`\n ${icon} ${categoryColor(categoryText)}: ${message} ${tsCode}`);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const summary = [] as string[];
|
|
162
|
+
if (errorCount > 0) summary.push(chalk.red(`${errorCount} error(s)`));
|
|
163
|
+
if (warningCount > 0) summary.push(chalk.yellow(`${warningCount} warning(s)`));
|
|
164
|
+
if (suggestionCount > 0) summary.push(chalk.blue(`${suggestionCount} suggestion(s)`));
|
|
165
|
+
|
|
166
|
+
return `\n${summary.join(", ")} found${output.join("\n")}`;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Get detailed diagnostic information with code snippet
|
|
171
|
+
* @param filePath - Path to the TypeScript file to check
|
|
172
|
+
* @returns Object containing diagnostics and detailed information
|
|
173
|
+
*/
|
|
174
|
+
getDetailedDiagnostics(filePath: string): {
|
|
175
|
+
diagnostics: ts.Diagnostic[];
|
|
176
|
+
details: { line: number; column: number; message: string; code: number; codeSnippet?: string }[];
|
|
177
|
+
} {
|
|
178
|
+
const { diagnostics } = this.check(filePath);
|
|
179
|
+
const sourceFile = ts.createSourceFile(filePath, readFileSync(filePath, "utf8"), ts.ScriptTarget.Latest, true);
|
|
180
|
+
|
|
181
|
+
const details = diagnostics.map((diagnostic) => {
|
|
182
|
+
if (diagnostic.file && diagnostic.start !== undefined) {
|
|
183
|
+
const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
|
|
184
|
+
const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
|
|
185
|
+
|
|
186
|
+
const lines = sourceFile.text.split("\n");
|
|
187
|
+
const codeSnippet = line < lines.length ? lines[line] : undefined;
|
|
188
|
+
return { line: line + 1, column: character + 1, message, code: diagnostic.code, codeSnippet };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
line: 0,
|
|
193
|
+
column: 0,
|
|
194
|
+
message: ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"),
|
|
195
|
+
code: diagnostic.code,
|
|
196
|
+
};
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
return { diagnostics, details };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Check if a file has type errors
|
|
204
|
+
* @param filePath - Path to the TypeScript file to check
|
|
205
|
+
* @returns true if there are no type errors, false otherwise
|
|
206
|
+
*/
|
|
207
|
+
hasNoTypeErrors(filePath: string): boolean {
|
|
208
|
+
try {
|
|
209
|
+
const { diagnostics } = this.check(filePath);
|
|
210
|
+
return diagnostics.length === 0;
|
|
211
|
+
} catch (error) {
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
static checkProject(configPath: string): ProjectTypecheckResult {
|
|
217
|
+
const parsedConfig = TypeChecker.parseConfig(configPath);
|
|
218
|
+
const host = ts.createIncrementalCompilerHost(parsedConfig.options);
|
|
219
|
+
const builderProgram = ts.createIncrementalProgram({
|
|
220
|
+
rootNames: parsedConfig.fileNames,
|
|
221
|
+
options: parsedConfig.options,
|
|
222
|
+
projectReferences: parsedConfig.projectReferences,
|
|
223
|
+
configFileParsingDiagnostics: parsedConfig.errors,
|
|
224
|
+
host,
|
|
225
|
+
});
|
|
226
|
+
const program = builderProgram.getProgram();
|
|
227
|
+
const diagnostics = [...ts.getPreEmitDiagnostics(program), ...builderProgram.emit().diagnostics];
|
|
228
|
+
const errors = diagnostics.filter((diagnostic) => diagnostic.category === ts.DiagnosticCategory.Error);
|
|
229
|
+
const warnings = diagnostics.filter((diagnostic) => diagnostic.category === ts.DiagnosticCategory.Warning);
|
|
230
|
+
return {
|
|
231
|
+
configPath,
|
|
232
|
+
diagnostics,
|
|
233
|
+
errors,
|
|
234
|
+
warnings,
|
|
235
|
+
message: TypeChecker.formatDiagnosticMessages(diagnostics),
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
static parseConfig(configPath: string): ts.ParsedCommandLine {
|
|
240
|
+
const configFile = ts.readConfigFile(configPath, (fileName) => ts.sys.readFile(fileName));
|
|
241
|
+
const configDiagnostics = configFile.error ? [configFile.error] : [];
|
|
242
|
+
if (!configFile.config) {
|
|
243
|
+
const message = TypeChecker.formatDiagnosticMessages(configDiagnostics);
|
|
244
|
+
throw new Error(message || `Error reading tsconfig.json: ${configPath}`);
|
|
245
|
+
}
|
|
246
|
+
const parsedConfig = ts.parseJsonConfigFileContent(
|
|
247
|
+
configFile.config,
|
|
248
|
+
ts.sys,
|
|
249
|
+
path.dirname(configPath),
|
|
250
|
+
undefined,
|
|
251
|
+
configPath,
|
|
252
|
+
);
|
|
253
|
+
if (parsedConfig.errors.length > 0) throw new Error(TypeChecker.formatDiagnosticMessages(parsedConfig.errors));
|
|
254
|
+
return parsedConfig;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
static formatDiagnosticMessages(diagnostics: ts.Diagnostic[]): string {
|
|
258
|
+
return ts.formatDiagnosticsWithColorAndContext(diagnostics, {
|
|
259
|
+
getCanonicalFileName: (fileName) => fileName,
|
|
260
|
+
getCurrentDirectory: () => process.cwd(),
|
|
261
|
+
getNewLine: () => ts.sys.newLine,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
}
|
package/types.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export interface PackageJson {
|
|
2
|
+
name: string;
|
|
3
|
+
type?: "module" | "commonjs";
|
|
4
|
+
version: string;
|
|
5
|
+
main?: string;
|
|
6
|
+
description: string;
|
|
7
|
+
scripts?: Record<string, string>;
|
|
8
|
+
dependencies?: Record<string, string>;
|
|
9
|
+
devDependencies?: Record<string, string>;
|
|
10
|
+
peerDependencies?: Record<string, string>;
|
|
11
|
+
peerDependenciesMeta?: Record<string, { optional?: boolean }>;
|
|
12
|
+
optionalDependencies?: Record<string, string>;
|
|
13
|
+
engines?: Record<string, string>;
|
|
14
|
+
exports?: Record<string, string | Record<string, string>>;
|
|
15
|
+
bun?: {
|
|
16
|
+
platform?: "node" | "browser" | "bun";
|
|
17
|
+
};
|
|
18
|
+
[key: string]: unknown;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface TsConfigJson {
|
|
22
|
+
extends?: string;
|
|
23
|
+
compilerOptions: {
|
|
24
|
+
target: string;
|
|
25
|
+
paths?: Record<string, string[]>;
|
|
26
|
+
};
|
|
27
|
+
references?: {
|
|
28
|
+
path: string;
|
|
29
|
+
}[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface FileContent {
|
|
33
|
+
filePath: string;
|
|
34
|
+
content: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface BaseDevEnv {
|
|
38
|
+
workspaceRoot: string | undefined;
|
|
39
|
+
repoName: string;
|
|
40
|
+
serveDomain: string;
|
|
41
|
+
env: "testing" | "debug" | "develop" | "main" | "local";
|
|
42
|
+
portOffset: number;
|
|
43
|
+
appName?: string | undefined;
|
|
44
|
+
}
|