@akanjs/devkit 3.0.0-beta.19 → 3.0.0-beta.20
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/abstractDoc.test.ts +6 -15
- package/abstractDoc.ts +1 -4
- package/agentsIndex.test.ts +4 -7
- package/agentsIndex.ts +6 -22
- package/akanApp/BackendImportGraph.test.ts +8 -22
- package/akanApp/BackendImportGraph.ts +1 -20
- package/akanApp/akanApp.host.test.ts +1 -3
- package/akanApp/akanApp.host.ts +64 -250
- package/akanApp/devHostPolicy.test.ts +10 -34
- package/akanApp/devHostPolicy.ts +12 -98
- package/akanConfig/akanConfig.test.ts +30 -39
- package/akanConfig/akanConfig.ts +40 -136
- package/akanContext.ts +106 -159
- package/akanMcpContract.ts +28 -73
- package/appSelectionMemory.ts +1 -4
- package/applicationBuildReporter.ts +37 -5
- package/applicationBuildRunner.ts +25 -59
- package/applicationReleasePackager.ts +24 -44
- package/applicationTestPreload.ts +3 -12
- package/artifact/implicitRootLayout.test.ts +3 -19
- package/artifact/implicitRootLayout.ts +34 -91
- package/artifact/routeSeedIndex.ts +11 -44
- package/biomeBase.ts +2 -5
- package/capacitorApp.test.ts +4 -14
- package/capacitorApp.ts +86 -235
- package/cloud/cloudApi.test.ts +202 -0
- package/cloud/cloudApi.ts +33 -57
- package/cloud/configLock.test.ts +64 -0
- package/cloud/configLock.ts +96 -0
- package/cloud/constants.ts +3 -2
- package/cloud/globalConfig.ts +64 -31
- package/codeAgent/agent/AkanCodeServices.ts +15 -57
- package/codeAgent/agent/AkanContextFiles.ts +5 -28
- package/codeAgent/agent/CodeAgent.ts +33 -108
- package/codeAgent/agent/CodeAgentAsks.ts +7 -30
- package/codeAgent/agent/CodeAgentEventMapper.ts +9 -52
- package/codeAgent/agent/CodeAgentGate.ts +8 -34
- package/codeAgent/agent/CodeAgentProxy.ts +4 -13
- package/codeAgent/agent/CodeAgentRpcHost.ts +3 -16
- package/codeAgent/agent/CodeAgentRpcListener.ts +2 -10
- package/codeAgent/agent/CodeAgentStreamPrinter.ts +4 -15
- package/codeAgent/agent/CodeAgentSuspended.ts +1 -8
- package/codeAgent/agent/CodeAgentUi.ts +2 -7
- package/codeAgent/agent/CodeMailbox.ts +7 -34
- package/codeAgent/agent/CodeSessionFork.ts +4 -19
- package/codeAgent/agent/CodeSessionIndex.ts +5 -31
- package/codeAgent/agent/SubagentPool.ts +48 -58
- package/codeAgent/agent/akanCodeModel.ts +5 -39
- package/codeAgent/agent/akanCodePaths.ts +4 -24
- package/codeAgent/agent/akanContextFiles.test.ts +0 -2
- package/codeAgent/agent/akanSystemPrompt.ts +1 -9
- package/codeAgent/agent/codeAgent.test.ts +178 -0
- package/codeAgent/agent/codeAgentContract.test.ts +5 -28
- package/codeAgent/agent/codeMailbox.test.ts +0 -4
- package/codeAgent/agent/codeSessionFork.test.ts +0 -2
- package/codeAgent/agent/codeSessionIndex.test.ts +9 -4
- package/codeAgent/agent/consoleToStderr.ts +2 -12
- package/codeAgent/agent/jsonLines.ts +29 -0
- package/codeAgent/agent/stringArg.ts +5 -0
- package/codeAgent/feedback/DevLogFeedback.ts +3 -18
- package/codeAgent/feedback/PreviewChrome.ts +5 -29
- package/codeAgent/feedback/PreviewFeedback.ts +2 -9
- package/codeAgent/feedback/PreviewView.ts +3 -15
- package/codeAgent/feedback/TurnFeedback.ts +2 -17
- package/codeAgent/feedback/VerifyFeedback.ts +1 -7
- package/codeAgent/feedback/previewChrome.test.ts +1 -1
- package/codeAgent/index.ts +2 -10
- package/codeAgent/skills/akan-code/SKILL.md +12 -4
- package/codeAgent/tools/AkanCli.ts +4 -16
- package/codeAgent/tools/AkanCodePlugins.ts +21 -43
- package/codeAgent/tools/AkanEditScope.ts +2 -12
- package/codeAgent/tools/AkanEnvKeys.ts +3 -11
- package/codeAgent/tools/AkanToolPack.ts +6 -28
- package/codeAgent/tools/AkanVerifier.ts +1 -8
- package/codeAgent/tools/AskToolPack.ts +3 -15
- package/codeAgent/tools/MailToolPack.ts +0 -9
- package/codeAgent/tools/McpClient.ts +3 -24
- package/codeAgent/tools/McpOAuth.ts +6 -41
- package/codeAgent/tools/McpOAuthLoopback.ts +2 -15
- package/codeAgent/tools/McpServerConfig.ts +7 -26
- package/codeAgent/tools/McpSignIn.ts +46 -49
- package/codeAgent/tools/McpTokenStore.ts +6 -17
- package/codeAgent/tools/McpToolPack.ts +107 -35
- package/codeAgent/tools/SessionToolPack.ts +1 -11
- package/codeAgent/tools/WebToolPack.ts +1 -7
- package/codeAgent/tools/akanCodePlugins.test.ts +231 -63
- package/codeAgent/tools/askToolPack.test.ts +0 -3
- package/codeAgent/tools/mcpOAuth.test.ts +315 -27
- package/codeAgent/tools/mcpServerConfig.test.ts +21 -14
- package/codegenLock.test.ts +4 -13
- package/codegenLock.ts +3 -17
- package/commandDecorators/argMeta.ts +1 -5
- package/commandDecorators/camelToKebabCase.ts +1 -0
- package/commandDecorators/command.ts +44 -102
- package/commandDecorators/commandBuilder.ts +30 -61
- package/commandDecorators/commandDecorators.test.ts +1 -9
- package/commandDecorators/dependencyBuilder.ts +10 -1
- package/commandDecorators/helpFormatter.ts +66 -144
- package/commandDecorators/index.ts +0 -2
- package/commandDecorators/targetMeta.ts +9 -7
- package/createTunnel.ts +7 -14
- package/dependencyScanner.test.ts +4 -16
- package/dependencyScanner.ts +27 -78
- package/devkitUtils.test.ts +47 -19
- package/executors.test.ts +114 -60
- package/executors.ts +165 -484
- package/fileEditor.ts +9 -37
- package/{getDirname.test.ts → fileSys.test.ts} +1 -1
- package/fileSys.ts +6 -8
- package/formSetterScanner.ts +3 -16
- package/frontendBuild/allRoutesBuilder.ts +7 -21
- package/frontendBuild/autoImportSync.test.ts +14 -38
- package/frontendBuild/autoImportSync.ts +15 -67
- package/frontendBuild/buildRouteClient.test.ts +10 -36
- package/frontendBuild/bundleDefine.ts +12 -0
- package/frontendBuild/clientBuildTypes.ts +5 -36
- package/frontendBuild/clientEntriesBundler.ts +9 -40
- package/frontendBuild/clientEntryDiscovery.ts +6 -41
- package/frontendBuild/csrArtifactBuilder.ts +6 -25
- package/frontendBuild/cssCandidateCache.test.ts +0 -6
- package/frontendBuild/cssCandidateCache.ts +6 -41
- package/frontendBuild/cssCompiler.ts +29 -78
- package/frontendBuild/cssImportResolver.ts +5 -18
- package/frontendBuild/devChangePlanner.ts +7 -10
- package/frontendBuild/devGeneratedIndexSync.test.ts +12 -27
- package/frontendBuild/devGeneratedIndexSync.ts +1 -4
- package/frontendBuild/fontOptimizer.test.ts +5 -13
- package/frontendBuild/fontOptimizer.ts +15 -72
- package/frontendBuild/fontPruner.test.ts +5 -10
- package/frontendBuild/fontPruner.ts +5 -20
- package/frontendBuild/frontendBuild.test.ts +27 -52
- package/frontendBuild/hmrChangeClassifier.ts +2 -2
- package/frontendBuild/hmrWatcher.test.ts +7 -31
- package/frontendBuild/hmrWatcher.ts +14 -71
- package/frontendBuild/pagesBundleBuilder.ts +4 -29
- package/frontendBuild/precompressArtifacts.ts +5 -18
- package/frontendBuild/routeClientBuilder.ts +7 -47
- package/frontendBuild/routesManifestArtifactSerializer.ts +5 -36
- package/frontendBuild/sourceMtimeIndex.test.ts +37 -94
- package/frontendBuild/sourceMtimeIndex.ts +19 -130
- package/frontendBuild/ssrBaseArtifactBuilder.ts +2 -8
- package/frontendBuild/styleContract.ts +1 -8
- package/frontendBuild/themeValidator.ts +4 -26
- package/frontendBuild/watchRootResolver.test.ts +23 -42
- package/frontendBuild/watchRootResolver.ts +5 -27
- package/getRelatedCnsts.ts +12 -92
- package/incrementalBuilder/buildBatch.proc.ts +22 -57
- package/incrementalBuilder/buildBatchProtocol.ts +8 -36
- package/incrementalBuilder/buildBatchRunner.test.ts +34 -0
- package/incrementalBuilder/buildBatchRunner.ts +10 -31
- package/incrementalBuilder/builderChannel.test.ts +24 -49
- package/incrementalBuilder/builderChannel.ts +6 -37
- package/incrementalBuilder/builderRequestRouter.test.ts +0 -8
- package/incrementalBuilder/builderRequestRouter.ts +5 -24
- package/incrementalBuilder/devWatchBatch.test.ts +14 -18
- package/incrementalBuilder/incrementalBuilder.host.test.ts +18 -55
- package/incrementalBuilder/incrementalBuilder.host.ts +18 -76
- package/incrementalBuilder/incrementalBuilder.proc.test.ts +81 -0
- package/incrementalBuilder/incrementalBuilder.proc.ts +48 -161
- package/index.ts +3 -15
- package/integration/devResourceProbe.ts +50 -56
- package/integration/devStability.integration.test.ts +47 -194
- package/integration/devStabilityHarness.test.ts +9 -29
- package/integration/devStabilityHarness.ts +65 -330
- package/integration/ssrMemoryProbe.ts +41 -167
- package/libSource.test.ts +4 -15
- package/libSource.ts +19 -31
- package/lint/gritRules.test.ts +6 -23
- package/linter.test.ts +1 -2
- package/linter.ts +22 -115
- package/mobile/mobileTarget.ts +14 -13
- package/package.json +2 -2
- package/packageExports.test.ts +3 -18
- package/packageExportsMap.ts +3 -19
- package/piDependencyPins.test.ts +3 -14
- package/platformTest/IdleWatchedProcess.test.ts +5 -13
- package/platformTest/IdleWatchedProcess.ts +1 -5
- package/platformTest/LinuxDockerTarget.ts +20 -35
- package/platformTest/PlatformTestRun.test.ts +5 -9
- package/platformTest/PlatformTestTarget.ts +24 -2
- package/platformTest/TestSnapshot.test.ts +6 -14
- package/platformTest/TestSnapshot.ts +1 -5
- package/platformTest/WindowsSshTarget.ts +6 -22
- package/prompter.ts +3 -6
- package/qualityScanner.test.ts +6 -24
- package/qualityScanner.ts +196 -284
- package/recipeScanner.test.ts +7 -12
- package/recipeScanner.ts +9 -38
- package/repoIdentity.ts +2 -9
- package/routeSourceValidator.ts +7 -30
- package/scanInfo.ts +82 -214
- package/selectModel.ts +1 -2
- package/semver.ts +3 -5
- package/slicePlanner.test.ts +6 -24
- package/slicePlanner.ts +11 -32
- package/spinner.test.ts +3 -8
- package/spinner.ts +4 -16
- package/sshTunnel.ts +1 -6
- package/ssrScanner.test.ts +1 -6
- package/ssrScanner.ts +103 -113
- package/subspace.test.ts +6 -25
- package/subspace.ts +31 -109
- package/subspaceConfig.ts +3 -11
- package/testHelpers.ts +49 -34
- package/transforms/asyncDefaultExportDetector.ts +3 -11
- package/transforms/barrelAnalyzer.ts +43 -135
- package/transforms/barrelImportsPlugin.ts +38 -175
- package/transforms/externalizeFrameworkPlugin.test.ts +39 -118
- package/transforms/externalizeFrameworkPlugin.ts +13 -87
- package/transforms/moduleSyntax.ts +32 -0
- package/transforms/rscUseClientTransform.ts +35 -20
- package/transforms/transforms.test.ts +153 -44
- package/transforms/tsconfigPackageResolver.test.ts +6 -24
- package/transforms/useClientBundlePlugin.ts +2 -22
- package/typeChecker.ts +7 -63
- package/uploadRelease.ts +10 -25
- package/workflow/artifacts.ts +18 -0
- package/workflow/executor.test.ts +1 -12
- package/workflow/executor.ts +85 -146
- package/workflow/index.ts +0 -1
- package/workflow/moduleIndex.test.ts +4 -15
- package/workflow/moduleIndex.ts +9 -49
- package/workflow/plan.ts +18 -25
- package/workflow/render.ts +80 -96
- package/workflow/source.ts +33 -56
- package/workflow/sourceAst.ts +50 -0
- package/workspaceLayout.ts +4 -19
- package/commandDecorators/commandMeta.ts +0 -7
- package/commandDecorators/types.ts +0 -10
- package/getDirname.ts +0 -4
- package/local/optimize-resource/ipcprobe/child.ts +0 -1
- package/local/optimize-resource/ipcprobe/parent.ts +0 -11
- package/workflow/primitive.ts +0 -59
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { Logger } from "akanjs/common";
|
|
3
3
|
import { compile } from "tailwindcss";
|
|
4
|
+
import { SOURCE_EXTS } from "../akanApp/devHostPolicy";
|
|
4
5
|
import type { App } from "../commandDecorators";
|
|
5
6
|
import { BarrelAnalyzer } from "../transforms/barrelAnalyzer";
|
|
6
7
|
import { createTsconfigPackageResolver, rewriteBarrelImports } from "../transforms/barrelImportsPlugin";
|
|
7
8
|
import { CssCandidateCache } from "./cssCandidateCache";
|
|
8
9
|
import { CssImportResolver } from "./cssImportResolver";
|
|
9
10
|
|
|
10
|
-
const SOURCE_EXTS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"] as const;
|
|
11
11
|
const NON_SOURCE_EXT_RE = /\.(json|svg|png|jpe?g|webp|gif|avif|ico|woff2?|ttf|otf|mp3|mp4|wav)$/i;
|
|
12
12
|
const NODE_MODULES_RE = /[\\/]node_modules[\\/]/;
|
|
13
13
|
const AKANJS_NODE_MODULE_RE = /[\\/]node_modules[\\/]akanjs[\\/]/;
|
|
@@ -17,7 +17,6 @@ interface CssDiscovery {
|
|
|
17
17
|
sourcePaths: string[];
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
/** One `@import` target and the custom properties it declares, which is what proves it arrived downstream. */
|
|
21
20
|
export interface ImportedStylesheet {
|
|
22
21
|
cssPath: string;
|
|
23
22
|
declaredNames: string[];
|
|
@@ -32,32 +31,18 @@ export class CssCompiler {
|
|
|
32
31
|
this.#app = app;
|
|
33
32
|
}
|
|
34
33
|
|
|
35
|
-
|
|
36
|
-
* Beside the sources rather than in `dist`, because the cache is keyed on source mtimes: a build and
|
|
37
|
-
* the dev server describe the same files, so they should share it rather than each pay the first scan.
|
|
38
|
-
*/
|
|
34
|
+
// Beside the sources, not in `dist`: the build and the dev server scan the same files, so they share one cache.
|
|
39
35
|
get #candidateCachePath() {
|
|
40
36
|
return path.join(this.#app.cwdPath, ".akan/cache/cssCandidates.json");
|
|
41
37
|
}
|
|
42
38
|
|
|
43
39
|
#cssText: string | null = null;
|
|
44
40
|
#cssTextByBasePath: Record<string, string> | null = null;
|
|
45
|
-
|
|
46
|
-
* Import resolution memoised for the life of one compiler, which is one rebuild.
|
|
47
|
-
*
|
|
48
|
-
* The discovery BFS resolves the same specifier from every directory that imports it, and each miss
|
|
49
|
-
* costs up to 13 sequential `exists()` calls — the bare path, six extensions, six `index.*`. Nothing
|
|
50
|
-
* here outlives the rebuild, so there is no invalidation to get wrong.
|
|
51
|
-
*/
|
|
41
|
+
// Memoised for one compiler, i.e. one rebuild: nothing outlives it, so there is nothing to invalidate.
|
|
52
42
|
#fileExistsCache = new Map<string, Promise<boolean>>();
|
|
53
43
|
#resolvedFileCache = new Map<string, Promise<string | null>>();
|
|
54
44
|
#resolvedSpecifierCache = new Map<string, Promise<string | null>>();
|
|
55
|
-
/** Every stylesheet this compile reached, entry points and `@import` targets alike. */
|
|
56
45
|
#discoveredCssPaths = new Set<string>();
|
|
57
|
-
/**
|
|
58
|
-
* `@import` targets per base path, so whoever writes the asset can check the file it actually wrote — the
|
|
59
|
-
* compiled text and the written artifact are two different places a declaration can go missing.
|
|
60
|
-
*/
|
|
61
46
|
importedStylesheetsByBasePath: Record<string, ImportedStylesheet[]> = {};
|
|
62
47
|
|
|
63
48
|
#fileExists(absPath: string): Promise<boolean> {
|
|
@@ -107,41 +92,31 @@ export class CssCompiler {
|
|
|
107
92
|
const pageKeys = await this.#app.getPageKeys({ refresh });
|
|
108
93
|
const basePaths = [...akanConfig.basePaths];
|
|
109
94
|
const rootPageKeys = pageKeys.filter((pageKey) => getPageKeyBasePath(pageKey, basePaths) === null);
|
|
95
|
+
const compileBasePath = async (basePath: string, basePathPageKeys: string[], label = basePath) => {
|
|
96
|
+
if (basePathPageKeys.length === 0) return [basePath, ""] as const;
|
|
97
|
+
const started = Date.now();
|
|
98
|
+
const { cssPaths, sourcePaths } = await this.discoverCssAndSources({ refresh, pageKeys: basePathPageKeys });
|
|
99
|
+
const { css, imported } = await this.#compileWithImports(cssPaths, sourcePaths);
|
|
100
|
+
this.importedStylesheetsByBasePath[basePath] = imported;
|
|
101
|
+
this.#logger.verbose(
|
|
102
|
+
`css base=${label} paths=${cssPaths.length} sources=${sourcePaths.length} in ${Date.now() - started}ms`,
|
|
103
|
+
);
|
|
104
|
+
return [basePath, css] as const;
|
|
105
|
+
};
|
|
110
106
|
const cssEntries = await Promise.all([
|
|
111
|
-
(
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
`css base=root paths=${cssPaths.length} sources=${sourcePaths.length} in ${Date.now() - started}ms`,
|
|
119
|
-
);
|
|
120
|
-
return ["", css] as const;
|
|
121
|
-
})(),
|
|
122
|
-
...basePaths.map(async (basePath) => {
|
|
123
|
-
const basePathPageKeys = pageKeys.filter((pageKey) => getPageKeyBasePath(pageKey, basePaths) === basePath);
|
|
124
|
-
if (basePathPageKeys.length === 0) return [basePath, ""] as const;
|
|
125
|
-
const started = Date.now();
|
|
126
|
-
const { cssPaths, sourcePaths } = await this.discoverCssAndSources({ refresh, pageKeys: basePathPageKeys });
|
|
127
|
-
const { css, imported } = await this.#compileWithImports(cssPaths, sourcePaths);
|
|
128
|
-
this.importedStylesheetsByBasePath[basePath] = imported;
|
|
129
|
-
this.#logger.verbose(
|
|
130
|
-
`css base=${basePath} paths=${cssPaths.length} sources=${sourcePaths.length} in ${Date.now() - started}ms`,
|
|
131
|
-
);
|
|
132
|
-
return [basePath, css] as const;
|
|
133
|
-
}),
|
|
107
|
+
compileBasePath("", rootPageKeys, "root"),
|
|
108
|
+
...basePaths.map((basePath) =>
|
|
109
|
+
compileBasePath(
|
|
110
|
+
basePath,
|
|
111
|
+
pageKeys.filter((pageKey) => getPageKeyBasePath(pageKey, basePaths) === basePath),
|
|
112
|
+
),
|
|
113
|
+
),
|
|
134
114
|
]);
|
|
135
115
|
this.#cssTextByBasePath = Object.fromEntries(cssEntries);
|
|
136
116
|
await this.#warnUnreachableStylesheets();
|
|
137
117
|
return this.#cssTextByBasePath;
|
|
138
118
|
}
|
|
139
119
|
|
|
140
|
-
/**
|
|
141
|
-
* A stylesheet under `page/` reaches the build only by being imported from a route source. One that nothing
|
|
142
|
-
* imports compiles to nothing and reports success, which is indistinguishable from an empty theme — so say it
|
|
143
|
-
* out loud once per compile rather than leaving it to be noticed as unstyled elements in the browser.
|
|
144
|
-
*/
|
|
145
120
|
async #warnUnreachableStylesheets() {
|
|
146
121
|
const pageDir = path.join(this.#app.cwdPath, "page");
|
|
147
122
|
const glob = new Bun.Glob("**/*.css");
|
|
@@ -225,11 +200,7 @@ export class CssCompiler {
|
|
|
225
200
|
return { cssPaths, sourcePaths: [...sourceFiles] };
|
|
226
201
|
}
|
|
227
202
|
|
|
228
|
-
|
|
229
|
-
* `libs/<lib>/ui/tokens.css` of every lib the page graph reached, so a lib can own the fixed colours its own
|
|
230
|
-
* components need instead of each consuming app re-declaring them. Ordered ahead of the app's stylesheets:
|
|
231
|
-
* the app is the last word on any variable both declare.
|
|
232
|
-
*/
|
|
203
|
+
// Lib tokens go ahead of the app's stylesheets, so the app has the last word on a variable both declare.
|
|
233
204
|
async #libTokenStylesheets(sourceFiles: Set<string>): Promise<string[]> {
|
|
234
205
|
const libsRoot = path.join(this.#app.workspace.workspaceRoot, "libs");
|
|
235
206
|
const libNames = new Set<string>();
|
|
@@ -252,10 +223,7 @@ export class CssCompiler {
|
|
|
252
223
|
return css;
|
|
253
224
|
}
|
|
254
225
|
|
|
255
|
-
|
|
256
|
-
* The collector is per compile rather than per compiler instance: `getCssByBasePath` compiles every base path
|
|
257
|
-
* concurrently, so instance state would mix one base path's imports into another's check.
|
|
258
|
-
*/
|
|
226
|
+
// A per-compile collector: getCssByBasePath compiles base paths concurrently, so instance state would mix them.
|
|
259
227
|
async #compileWithImports(
|
|
260
228
|
cssPaths: string[],
|
|
261
229
|
sourcePaths: string[],
|
|
@@ -279,7 +247,6 @@ export class CssCompiler {
|
|
|
279
247
|
|
|
280
248
|
const sourceDirs = new Set<string>();
|
|
281
249
|
for (const entry of compilers) {
|
|
282
|
-
if (!entry) continue;
|
|
283
250
|
for (const s of entry.compiler.sources as { base: string }[]) sourceDirs.add(s.base);
|
|
284
251
|
}
|
|
285
252
|
const scanStarted = Date.now();
|
|
@@ -290,7 +257,6 @@ export class CssCompiler {
|
|
|
290
257
|
const parts: string[] = [];
|
|
291
258
|
const imported: ImportedStylesheet[] = [];
|
|
292
259
|
for (const entry of compilers) {
|
|
293
|
-
if (!entry) continue;
|
|
294
260
|
const part = entry.compiler.build(candidates);
|
|
295
261
|
parts.push(part);
|
|
296
262
|
for (const [cssPath, content] of entry.imported) {
|
|
@@ -317,28 +283,19 @@ export class CssCompiler {
|
|
|
317
283
|
return { path: p, base: path.dirname(p), content };
|
|
318
284
|
}
|
|
319
285
|
|
|
320
|
-
|
|
321
|
-
* Every specifier is verified here, path-shaped ones included. An `@import` the pipeline cannot resolve is
|
|
322
|
-
* a build error and never a no-op: the vocabulary closure means a component whose token declaration failed
|
|
323
|
-
* to load renders unstyled, which nothing downstream can distinguish from a design choice.
|
|
324
|
-
*/
|
|
286
|
+
// An unresolvable @import throws: under vocabulary closure a missing token file just renders unstyled.
|
|
325
287
|
async #resolveCssImport(id: string, fromBase: string): Promise<string> {
|
|
326
288
|
if (id.startsWith(".") || id.startsWith("/")) {
|
|
327
289
|
const filePath = path.resolve(fromBase, id);
|
|
328
290
|
if (await this.#fileExists(filePath)) return filePath;
|
|
329
291
|
throw new Error(`[css] failed to resolve stylesheet import "${id}" from ${fromBase} (no file at ${filePath})`);
|
|
330
292
|
}
|
|
331
|
-
|
|
332
|
-
const resolved = await
|
|
293
|
+
this.#cssImportResolver ??= await CssImportResolver.create(this.#app);
|
|
294
|
+
const resolved = await this.#cssImportResolver.resolve(id, fromBase);
|
|
333
295
|
if (resolved) return resolved;
|
|
334
296
|
throw new Error(`[css] failed to resolve stylesheet import "${id}" from ${fromBase}`);
|
|
335
297
|
}
|
|
336
298
|
|
|
337
|
-
async #getCssImportResolver() {
|
|
338
|
-
if (this.#cssImportResolver) return this.#cssImportResolver;
|
|
339
|
-
this.#cssImportResolver = await CssImportResolver.create(this.#app);
|
|
340
|
-
return this.#cssImportResolver;
|
|
341
|
-
}
|
|
342
299
|
async #loadModule(id: string, fromBase: string) {
|
|
343
300
|
const p = require.resolve(id, { paths: [fromBase] });
|
|
344
301
|
const mod = await import(p);
|
|
@@ -349,10 +306,7 @@ export class CssCompiler {
|
|
|
349
306
|
fromBase: string,
|
|
350
307
|
resolvePackage: Awaited<ReturnType<typeof createTsconfigPackageResolver>>,
|
|
351
308
|
): Promise<string | null> {
|
|
352
|
-
// Keyed by importer directory even for bare specifiers: the
|
|
353
|
-
// directory-independent, but the `Bun.resolveSync` / `require.resolve` fallbacks below are not.
|
|
354
|
-
// The expensive part — the `exists()` probes — is deduplicated by absolute path instead, which is
|
|
355
|
-
// shared across every importer.
|
|
309
|
+
// Keyed by importer directory even for bare specifiers: the Bun/require fallbacks are directory-dependent.
|
|
356
310
|
const cacheKey = `${fromBase}\0${id}`;
|
|
357
311
|
let cached = this.#resolvedSpecifierCache.get(cacheKey);
|
|
358
312
|
if (cached) return cached;
|
|
@@ -423,17 +377,14 @@ function resolveSourceWithRequire(id: string, fromBase: string): string | null {
|
|
|
423
377
|
}
|
|
424
378
|
|
|
425
379
|
function isSourceFile(filePath: string) {
|
|
426
|
-
return SOURCE_EXTS.
|
|
380
|
+
return SOURCE_EXTS.has(path.extname(filePath));
|
|
427
381
|
}
|
|
428
382
|
|
|
429
383
|
export function isIgnoredNodeModuleSource(filePath: string): boolean {
|
|
430
384
|
return NODE_MODULES_RE.test(filePath) && !AKANJS_NODE_MODULE_RE.test(filePath);
|
|
431
385
|
}
|
|
432
386
|
|
|
433
|
-
/**
|
|
434
|
-
* `@theme` blocks are stripped first: those variables are emitted only when a utility uses one, so their
|
|
435
|
-
* absence from a build says nothing about whether the stylesheet arrived.
|
|
436
|
-
*/
|
|
387
|
+
/** Skips `@theme` blocks: Tailwind emits those variables only when a utility uses one. */
|
|
437
388
|
export function declaredCustomProperties(css: string): string[] {
|
|
438
389
|
const withoutThemeBlocks = css.replace(/@theme[^{]*\{[^}]*\}/g, "");
|
|
439
390
|
return [...new Set([...withoutThemeBlocks.matchAll(/(?:^|[\s;{])(--[\w-]+)\s*:/g)].map(([, name]) => name))].filter(
|
|
@@ -25,8 +25,8 @@ export class CssImportResolver {
|
|
|
25
25
|
async resolve(id: string, fromBase: string): Promise<string | null> {
|
|
26
26
|
for (const resolve of [
|
|
27
27
|
() => this.#resolveWithTsconfig(id),
|
|
28
|
-
() => this.#
|
|
29
|
-
() => this.#
|
|
28
|
+
() => this.#resolveCssWith(fromBase, (base) => Bun.resolveSync(id, base)),
|
|
29
|
+
() => this.#resolveCssWith(fromBase, (base) => require.resolve(id, { paths: [base] })),
|
|
30
30
|
() => this.#resolvePackageStyle(id, fromBase),
|
|
31
31
|
]) {
|
|
32
32
|
const resolved = await resolve();
|
|
@@ -35,22 +35,10 @@ export class CssImportResolver {
|
|
|
35
35
|
return null;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
#
|
|
38
|
+
#resolveCssWith(fromBase: string, resolveFrom: (base: string) => string): string | null {
|
|
39
39
|
for (const base of this.#resolutionBases(fromBase)) {
|
|
40
40
|
try {
|
|
41
|
-
const resolved =
|
|
42
|
-
if (CssImportResolver.isCssFile(resolved)) return resolved;
|
|
43
|
-
} catch {
|
|
44
|
-
// Try the next known package resolution root.
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
#resolveWithRequire(id: string, fromBase: string): string | null {
|
|
51
|
-
for (const base of this.#resolutionBases(fromBase)) {
|
|
52
|
-
try {
|
|
53
|
-
const resolved = require.resolve(id, { paths: [base] });
|
|
41
|
+
const resolved = resolveFrom(base);
|
|
54
42
|
if (CssImportResolver.isCssFile(resolved)) return resolved;
|
|
55
43
|
} catch {
|
|
56
44
|
// Try the next known package resolution root.
|
|
@@ -111,8 +99,7 @@ export class CssImportResolver {
|
|
|
111
99
|
? exportValue
|
|
112
100
|
: exportValue?.style || exportValue?.import || exportValue?.default;
|
|
113
101
|
if (exportedEntry) return await this.#firstExisting(path.resolve(pkgDir, exportedEntry));
|
|
114
|
-
//* A subpath
|
|
115
|
-
//* style entry here would load a different stylesheet than the author asked for and report success.
|
|
102
|
+
//* A subpath resolves literally: the package's own style entry would silently load a different stylesheet.
|
|
116
103
|
if (subpath !== ".") return await this.#firstExisting(path.resolve(pkgDir, subpath));
|
|
117
104
|
return await this.#firstExisting(path.resolve(pkgDir, pkg.exports?.["."]?.style || pkg.style || "index.css"));
|
|
118
105
|
} catch {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import type { ChangeKind, DevChangeAction, DevChangePlan, DevChangeRole } from "akanjs/server";
|
|
3
|
+
import { SOURCE_EXTS } from "../akanApp/devHostPolicy";
|
|
3
4
|
|
|
4
|
-
const SOURCE_EXTS = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
5
5
|
const CONFIG_BASENAMES = new Set(["akan.config.ts", "bunfig.toml", "tsconfig.json", "package.json"]);
|
|
6
6
|
const BARREL_FACETS = new Set(["common", "srvkit", "ui", "webkit", "plugin"]);
|
|
7
7
|
const CLIENT_SUFFIXES = [".Template.tsx", ".Unit.tsx", ".Util.tsx", ".View.tsx", ".Zone.tsx", ".store.ts"];
|
|
@@ -93,15 +93,15 @@ export class DevChangePlanner {
|
|
|
93
93
|
roles.add("barrel");
|
|
94
94
|
reasons.add(isGenerated ? "generated-index" : "barrel-facet-child");
|
|
95
95
|
}
|
|
96
|
-
if (isSource && this.#isServerBiased(
|
|
96
|
+
if (isSource && this.#isServerBiased(base, parts)) {
|
|
97
97
|
roles.add("server");
|
|
98
98
|
reasons.add("server-path");
|
|
99
99
|
}
|
|
100
|
-
if (isSource && this.#isClientBiased(
|
|
100
|
+
if (isSource && this.#isClientBiased(base, parts)) {
|
|
101
101
|
roles.add("client");
|
|
102
102
|
reasons.add("client-path");
|
|
103
103
|
}
|
|
104
|
-
if (isSource && this.#isSharedBiased(
|
|
104
|
+
if (isSource && this.#isSharedBiased(base, parts)) {
|
|
105
105
|
roles.add("shared");
|
|
106
106
|
reasons.add("shared-path");
|
|
107
107
|
}
|
|
@@ -123,8 +123,7 @@ export class DevChangePlanner {
|
|
|
123
123
|
return roles;
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
#isServerBiased(
|
|
127
|
-
const base = path.basename(abs);
|
|
126
|
+
#isServerBiased(base: string, parts: string[]): boolean {
|
|
128
127
|
return (
|
|
129
128
|
parts.includes("srvkit") ||
|
|
130
129
|
SERVER_SUFFIXES.some((suffix) => base.endsWith(suffix)) ||
|
|
@@ -133,8 +132,7 @@ export class DevChangePlanner {
|
|
|
133
132
|
);
|
|
134
133
|
}
|
|
135
134
|
|
|
136
|
-
#isClientBiased(
|
|
137
|
-
const base = path.basename(abs);
|
|
135
|
+
#isClientBiased(base: string, parts: string[]): boolean {
|
|
138
136
|
return (
|
|
139
137
|
parts.includes("ui") ||
|
|
140
138
|
parts.includes("webkit") ||
|
|
@@ -143,8 +141,7 @@ export class DevChangePlanner {
|
|
|
143
141
|
);
|
|
144
142
|
}
|
|
145
143
|
|
|
146
|
-
#isSharedBiased(
|
|
147
|
-
const base = path.basename(abs);
|
|
144
|
+
#isSharedBiased(base: string, parts: string[]): boolean {
|
|
148
145
|
return (
|
|
149
146
|
parts.includes("common") ||
|
|
150
147
|
SHARED_SUFFIXES.some((suffix) => base.endsWith(suffix)) ||
|
|
@@ -1,16 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { mkdir,
|
|
3
|
-
import os from "node:os";
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
4
3
|
import path from "node:path";
|
|
4
|
+
import { tempDirs } from "../testHelpers";
|
|
5
5
|
import { DevGeneratedIndexSync } from "./devGeneratedIndexSync";
|
|
6
6
|
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
const makeTempRoot = async () => {
|
|
10
|
-
const root = await mkdtemp(path.join(os.tmpdir(), "akan-generated-index-"));
|
|
11
|
-
tempRoots.push(root);
|
|
12
|
-
return root;
|
|
13
|
-
};
|
|
7
|
+
const makeTempRoot = tempDirs("akan-generated-index-");
|
|
14
8
|
|
|
15
9
|
const seedFacet = async (root: string, facet: string, { files, dirs }: { files: string[]; dirs: string[] }) => {
|
|
16
10
|
const dir = path.join(root, "libs", "util", facet);
|
|
@@ -28,23 +22,19 @@ const barrelFor = async (root: string, facet: string, seed: { files: string[]; d
|
|
|
28
22
|
return readFile(path.join(dir, "index.ts"), "utf8");
|
|
29
23
|
};
|
|
30
24
|
|
|
31
|
-
afterEach(async () => {
|
|
32
|
-
await Promise.all(tempRoots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
|
|
33
|
-
});
|
|
34
|
-
|
|
35
25
|
describe("DevGeneratedIndexSync facet barrels", () => {
|
|
36
26
|
test("camelCase facets export only clean camelCase names", async () => {
|
|
37
27
|
const content = await barrelFor(await makeTempRoot(), "srvkit", {
|
|
38
28
|
files: [
|
|
39
29
|
"aes.ts",
|
|
40
30
|
"cloudflareApi.ts",
|
|
41
|
-
"cloudflareApi.helper.ts",
|
|
42
|
-
"pushNotificationServer.type.ts",
|
|
43
|
-
"PushNotificationServer.ts",
|
|
44
|
-
"my_snake.ts",
|
|
45
|
-
"kebab-case.ts",
|
|
31
|
+
"cloudflareApi.helper.ts",
|
|
32
|
+
"pushNotificationServer.type.ts",
|
|
33
|
+
"PushNotificationServer.ts",
|
|
34
|
+
"my_snake.ts",
|
|
35
|
+
"kebab-case.ts",
|
|
46
36
|
],
|
|
47
|
-
dirs: ["storageApi", "BadDir"],
|
|
37
|
+
dirs: ["storageApi", "BadDir"],
|
|
48
38
|
trigger: "aes.ts",
|
|
49
39
|
});
|
|
50
40
|
expect(content).toBe(`export * from "./aes";\nexport * from "./cloudflareApi";\nexport * from "./storageApi";\n`);
|
|
@@ -52,13 +42,8 @@ describe("DevGeneratedIndexSync facet barrels", () => {
|
|
|
52
42
|
|
|
53
43
|
test("ui facet exports only clean PascalCase names", async () => {
|
|
54
44
|
const content = await barrelFor(await makeTempRoot(), "ui", {
|
|
55
|
-
files: [
|
|
56
|
-
|
|
57
|
-
"AkanLogo.tsx",
|
|
58
|
-
"Globe_Dynamic.tsx", // underscore → skipped
|
|
59
|
-
"lowerStart.tsx", // camelCase in ui facet → skipped
|
|
60
|
-
],
|
|
61
|
-
dirs: ["Code", "badDir"], // camelCase dir → skipped
|
|
45
|
+
files: ["Globe.tsx", "AkanLogo.tsx", "Globe_Dynamic.tsx", "lowerStart.tsx"],
|
|
46
|
+
dirs: ["Code", "badDir"],
|
|
62
47
|
trigger: "Globe.tsx",
|
|
63
48
|
});
|
|
64
49
|
expect(content).toBe(`export * from "./AkanLogo";\nexport * from "./Code";\nexport * from "./Globe";\n`);
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { mkdir, readdir, readFile, rm, stat } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { capitalize } from "akanjs/common";
|
|
3
4
|
import { FileSys } from "../fileSys";
|
|
4
5
|
|
|
5
6
|
const BARREL_FACETS = new Set(["common", "srvkit", "ui", "webkit", "plugin"]);
|
|
6
7
|
const FACET_SOURCE_FILE_RE = /\.(ts|tsx)$/;
|
|
7
8
|
const FACET_EXCLUDED_FILE_RE = /(^index\.tsx?$|\.d\.ts$|\.(test|spec)\.(ts|tsx)$|\.css$|\.scss$|\.sass$)/;
|
|
8
|
-
// `ui` exports PascalCase names only; `common`/`srvkit`/`webkit` export camelCase names only. Names with
|
|
9
|
-
// dots, underscores, or hyphens (e.g. `foo.helper`, `Globe_Dynamic`, `kebab-case`) match neither and are skipped.
|
|
10
9
|
const FACET_PASCAL_CASE_RE = /^[A-Z][A-Za-z0-9]*$/;
|
|
11
10
|
const FACET_CAMEL_CASE_RE = /^[a-z][A-Za-z0-9]*$/;
|
|
12
11
|
const MODULE_UI_TYPES = ["Template", "Unit", "Util", "View", "Zone"] as const;
|
|
@@ -159,6 +158,4 @@ const exists = async (file: string) =>
|
|
|
159
158
|
.then(() => true)
|
|
160
159
|
.catch(() => false);
|
|
161
160
|
|
|
162
|
-
const capitalize = (value: string) => `${value.charAt(0).toUpperCase()}${value.slice(1)}`;
|
|
163
|
-
|
|
164
161
|
const formatError = (err: unknown) => (err instanceof Error ? err.message : String(err));
|
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { mkdir,
|
|
3
|
-
import os from "node:os";
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { mkdir, rm, stat, writeFile } from "node:fs/promises";
|
|
4
3
|
import path from "node:path";
|
|
5
4
|
import type { App } from "../commandDecorators";
|
|
5
|
+
import { tempDirs } from "../testHelpers";
|
|
6
6
|
import { FontOptimizer } from "./fontOptimizer";
|
|
7
7
|
|
|
8
8
|
const SOURCE_FONT = path.resolve(import.meta.dir, "../../../../libs/shared/public/fonts/Assistant-Regular.woff2");
|
|
9
9
|
|
|
10
|
-
const
|
|
10
|
+
const tempRoot = tempDirs("akan-devkit-font-");
|
|
11
11
|
|
|
12
12
|
const makeApp = async (layoutSource: string) => {
|
|
13
|
-
const root = await
|
|
14
|
-
tempRoots.push(root);
|
|
13
|
+
const root = await tempRoot();
|
|
15
14
|
const cwdPath = path.join(root, "apps/demo");
|
|
16
15
|
await mkdir(path.join(cwdPath, "page"), { recursive: true });
|
|
17
16
|
await mkdir(path.join(cwdPath, "public/fonts"), { recursive: true });
|
|
@@ -59,10 +58,6 @@ const chainFontList = (extra = "") => `[\n ${chainFontEntry(extra)},\n ]`;
|
|
|
59
58
|
|
|
60
59
|
const optimize = (app: App) => new FontOptimizer(app, "start").optimize();
|
|
61
60
|
|
|
62
|
-
afterEach(async () => {
|
|
63
|
-
await Promise.all(tempRoots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
|
|
64
|
-
});
|
|
65
|
-
|
|
66
61
|
describe("FontOptimizer cache", () => {
|
|
67
62
|
test("reuses subset output instead of resubsetting an unchanged font", async () => {
|
|
68
63
|
const { app } = await makeApp(layoutWith());
|
|
@@ -76,7 +71,6 @@ describe("FontOptimizer cache", () => {
|
|
|
76
71
|
expect(second.files).toEqual(first.files);
|
|
77
72
|
expect(second.css).toBe(first.css);
|
|
78
73
|
expect(second.fonts).toEqual(first.fonts);
|
|
79
|
-
// The output was reused, not rewritten — the whole point of the cache.
|
|
80
74
|
expect((await stat(second.files[0])).mtimeMs).toBe(writtenAt);
|
|
81
75
|
});
|
|
82
76
|
|
|
@@ -160,8 +154,6 @@ describe("FontOptimizer discovery", () => {
|
|
|
160
154
|
expect(second.css).toContain(".font-brand");
|
|
161
155
|
});
|
|
162
156
|
|
|
163
|
-
// A list the build cannot read subsets nothing while the runtime still preloads /_akan/fonts, so the 404s
|
|
164
|
-
// have to be announced at build time rather than found in a browser console.
|
|
165
157
|
test("warns when the chain is handed a font list it cannot read", async () => {
|
|
166
158
|
const { app, warnings } = await makeApp(chainLayoutWith("brandFonts"));
|
|
167
159
|
|