@akanjs/devkit 3.1.2-rc.4 → 3.2.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/CHANGELOG.md +353 -0
- package/abstractDoc.ts +1 -1
- package/agentAssets.ts +2 -2
- package/akanConfig/akanConfig.ts +2 -2
- package/akanMcpContract.ts +1 -1
- package/apiReference.test.ts +37 -0
- package/apiReference.ts +240 -0
- package/applicationBuildReporter.test.ts +25 -3
- package/applicationBuildReporter.ts +8 -1
- package/applicationBuildRunner.test.ts +34 -14
- package/applicationBuildRunner.ts +76 -14
- package/artifact/implicitRootLayout.test.ts +42 -0
- package/artifact/implicitRootLayout.ts +5 -6
- package/artifact/storeBootstrap.ts +24 -0
- package/cloud/cloudApi.test.ts +70 -7
- package/cloud/cloudApi.ts +10 -4
- package/cloud/constants.ts +3 -0
- package/cloud/globalConfig.test.ts +36 -0
- package/cloud/globalConfig.ts +10 -1
- package/codeAgent/skills/akan-debug/SKILL.md +2 -1
- package/codeAgent/skills/akan-page/SKILL.md +16 -7
- package/codeAgent/skills/akan-service/SKILL.md +48 -19
- package/commandDecorators/command.ts +55 -9
- package/commandDecorators/commandDecorators.test.ts +131 -2
- package/createTunnel.test.ts +12 -2
- package/declarationReader.ts +211 -0
- package/executors.test.ts +65 -8
- package/executors.ts +5 -3
- package/frameworkGraph.test.ts +121 -0
- package/frameworkGraph.ts +165 -0
- package/frontendBuild/allRoutesBuilder.test.ts +84 -0
- package/frontendBuild/allRoutesBuilder.ts +32 -12
- package/frontendBuild/buildRouteClient.test.ts +192 -2
- package/frontendBuild/bundleDefine.ts +4 -0
- package/frontendBuild/clientBuildTypes.ts +7 -1
- package/frontendBuild/clientEntriesBundler.test.ts +82 -0
- package/frontendBuild/clientEntriesBundler.ts +55 -4
- package/frontendBuild/csrArtifactBuilder.test.ts +23 -0
- package/frontendBuild/csrArtifactBuilder.ts +2 -1
- package/frontendBuild/fontOptimizer.test.ts +54 -0
- package/frontendBuild/fontOptimizer.ts +5 -4
- package/frontendBuild/index.ts +3 -0
- package/frontendBuild/routeClientBuilder.ts +6 -2
- package/frontendBuild/routeModulePreloads.test.ts +59 -0
- package/frontendBuild/routeModulePreloads.ts +52 -0
- package/frontendBuild/ssrBaseArtifactBuilder.ts +35 -10
- package/frontendBuild/ssrDevBundler.test.ts +26 -0
- package/frontendBuild/ssrDevBundler.ts +2 -1
- package/frontendBuild/vendorImportScanner.test.ts +71 -0
- package/frontendBuild/vendorImportScanner.ts +80 -0
- package/frontendBuild/vendorNarrowing.test.ts +103 -0
- package/frontendBuild/vendorNarrowing.ts +64 -0
- package/frontendBuild/vendorSpecifiers.ts +13 -0
- package/gitRevision.ts +6 -2
- package/incrementalBuilder/builderGraphPayloads.ts +60 -0
- package/incrementalBuilder/incrementalBuilder.proc.test.ts +65 -1
- package/incrementalBuilder/incrementalBuilder.proc.ts +5 -36
- package/integration/devStability.integration.test.ts +1 -1
- package/integration/devStabilityHarness.test.ts +1 -1
- package/lint/no-deep-internal-import.grit +1 -1
- package/maintainabilityBaseline.ts +7 -2
- package/maintainabilityContract.test.ts +183 -0
- package/maintainabilityContract.ts +145 -30
- package/maintainabilityExceptionScanner.test.ts +13 -2
- package/maintainabilityExceptionScanner.ts +16 -6
- package/maintainabilityLimits.ts +12 -0
- package/maintainabilityReport.test.ts +35 -0
- package/maintainabilityReport.ts +29 -8
- package/mobile/nativePublicEnv.test.ts +5 -1
- package/package.json +3 -3
- package/preview/PreviewChrome.ts +3 -3
- package/preview/PreviewView.ts +26 -8
- package/preview/previewView.test.ts +87 -3
- package/routeJsBudget.test.ts +177 -0
- package/routeJsBudget.ts +222 -0
- package/structureScanner.test.ts +167 -0
- package/structureScanner.ts +164 -0
- package/transforms/barrelAnalyzer.ts +10 -3
- package/transforms/barrelImportsPlugin.ts +13 -7
- package/transforms/serverEnvPlugin.test.ts +1 -1
- package/transforms/serverEnvPlugin.ts +1 -1
- package/transforms/transforms.test.ts +64 -12
- package/transforms/tsconfigPackageResolver.test.ts +12 -0
- package/workflow/plan.ts +79 -17
- package/workflow/types.ts +1 -0
- package/workspaceGraph.test.ts +58 -0
- package/workspaceGraph.ts +32 -3
- package/workspaceLayout.ts +1 -0
- /package/integration/{devStabilityHarness.ts → devStabilityHarness.fixture.ts} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,358 @@
|
|
|
1
1
|
# @akanjs/devkit
|
|
2
2
|
|
|
3
|
+
## 3.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 29756e1: `akan lint`, `akan lint-all` and `akan verify` now hold each package under a workspace's `pkgs/` to a structure budget, as they already hold apps and libs: a file up to 800 lines, a function up to 150 lines (`akan.size.function`) and a class up to 60 members (`akan.size.class`). Tests and their fixtures (`*.test`, `*.spec`, `*.fixture`, `*.instance` and `*.bench` files, and `test/`, `tests/`, `__tests__/`, `__fixtures__/` and `__mocks__/` folders), `.d.ts` files, re-export barrels and a package's `examples/` are left out, and a package in a git repository is listed through git, so ignored files are never measured. Each package keeps its own `akan.baseline.json`; `akan quality baseline` now seeds one for each package too, `akan quality scan` reports its exceptions, and packing a package that would publish one fails.
|
|
8
|
+
|
|
9
|
+
A baseline only shrinks from the default branch's copy. Where the default branch has none, an app's, lib's or package's baseline may hold only the breaches the default branch's code already has, so a new one starts empty instead of skipping the check. A file's line count no longer includes the newline ending its last line, so a recorded size may drop by one on the next `akan lint`.
|
|
10
|
+
|
|
11
|
+
Upgrading: in a workspace whose packages are over the budget, run `akan quality baseline` once to record what they hold today.
|
|
12
|
+
|
|
13
|
+
`akan test <target> --coverage` measures coverage: a summary on stdout and `lcov.info` under `local/coverage/<target path>`. CI runs each package's tests this way and keeps the report as an artifact.
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- bc792ab: What a coding agent needs to build its first app is now written where it looks, and two messages say what to do next.
|
|
18
|
+
|
|
19
|
+
- **A query or mutation that answers `null` without `nullable: true`** now fails with `query <name> answered null, but its return is not nullable: answer a value, or declare { nullable: true } and answer null`. It used to fail with `Required Any value: null`, or `Invalid Value (Nullable)` for a model, which named neither the endpoint nor the fix.
|
|
20
|
+
- **`akan build` for the `local` environment** ends its summary with one line: such a build serves the dev routes to every caller, and a host others can reach needs `AKAN_PUBLIC_ENV=main`, `AKAN_PUBLIC_REPO_NAME` and `AKAN_PUBLIC_SERVE_DOMAIN`. A build for any other environment prints nothing new, and the check that refuses a deployed build with stand-in values is unchanged.
|
|
21
|
+
- **The `akan-page` skill** carries a one-screen API summary: a data-driven `<title>`, an async render, `router.notFound()` and a layout's `.notFound()` / `.error()`, `Link`, and a shallow `router.replace` for a client filter kept in the URL. The page rule names the shallow replace too.
|
|
22
|
+
- **The `akan-service` skill** carries a recipe for pages backed by an external HTTP API: an `adapt()` adapter in `srvkit/` that reads its base URL at boot, a `lib/_<service>` service, a `Public` `query(Any, { nullable: true })` endpoint and its dictionary label, and the page that calls it.
|
|
23
|
+
- **The README's "Inside an existing repository"** runs the creator through `npx -y bun@1.4.4 x …` on a host with only Node and npm, so nothing global is written. It also gives copy-ready `build` and `start` scripts, says a fresh clone builds without a committed env file, and says which environment a `.env`-less build is for and what a deployed host sets instead.
|
|
24
|
+
|
|
25
|
+
- 61fbd7d: `get_graph` takes `since`, a git revision, and returns the Change Map: the Akan Graph of what changed since HEAD forked from that revision, working tree and untracked files included. Every node, field and edge is marked `added`, `removed` or `changed`. The unchanged nodes a changed one calls, or is called by, stay in as context, and `unmapped` lists the changed files under `apps/` or `libs/` that no node covers, each marked the same way. `since: "HEAD"` maps uncommitted work only. Files `.gitignore` covers stay out, a renamed file reads as removed and added, and a CRLF checkout of an unchanged file reads as unchanged. A revision git does not know returns a `graph-since-unknown` diagnostic, and so does one HEAD shares no commit with (a shallow clone, say) instead of comparing with its tip. A revision that starts with `-` also returns it and never reaches git.
|
|
26
|
+
|
|
27
|
+
- **`AkanGraphDiff.between(base, head)`** in `@akanjs/language` diffs two graphs. `index.graph(readText, { digests: true })` gives each member, query, component and field a hash of its body text, so an edit inside a method, or to a field's options, marks it changed and moving it does not.
|
|
28
|
+
- **The change types** (`AkanGraphChange`, `AkanChangeMap`, `AkanChangedFile`) live in `akanjs/common`.
|
|
29
|
+
|
|
30
|
+
- a707354: Akan Studio maps the framework itself, and `pkgs/akanjs/ARCHITECTURE.md` keeps its tables from the source.
|
|
31
|
+
|
|
32
|
+
- **The Framework view** (`/_akan/studio?view=framework`, `akan studio <app> --view framework`) lays the `akanjs` facets and `@akanjs/devkit` out by layer, with their largest classes, and draws value, lazy and type-only imports apart. Under it, one table per process channel names who sends and who handles each message, each linked to its source. It reads `pkgs/akanjs` in this repository and the installed `akanjs` in a consumer workspace (without its bundled `vendor/` and `types/`), and never joins the app's own graph or `get_graph`. The dev builder rebuilds it only when the installed version changes or a save lands under `pkgs/`.
|
|
33
|
+
- **`FrameworkMapBuilder`, `FrameworkChannelReader` and `FrameworkMapDoc`** in `@akanjs/language` build the map with the TypeScript compiler API: facets and their import layers, and for each channel union the classes that write a message's `type` literal or branch on it.
|
|
34
|
+
- **`pkgs/akanjs/ARCHITECTURE.md`** pairs a hand-kept walk through the processes and a request with a generated part between markers. `akan sync-architecture` regenerates that part, and a devkit test fails when it is stale, when a channel is gone, when two facets load values from each other, when a message lacks a recognised sender or handler outside a short list whose other end lies beyond the framework source, or when the hand-kept part names a class the source no longer has.
|
|
35
|
+
- **`AkanIpcMessage` no longer lists `backend-ready`.** The gateway sends it to the dev host, which reads it as a `BuilderMessage`, where it stays; no gateway or child ever handled it.
|
|
36
|
+
|
|
37
|
+
- 0351ad4: The Akan Graph keys each guard by the app or lib that declares it, `guard:libs/shared:Admin`, and follows the import that names it there, so `Admin as AdminGuard` reaches `Admin`, `srvkit.Admin` reaches the lib's class, and two libs' `Admin` stay two nodes. A guard read out of an object map, `SSO.Kakao`, resolves through the imports of the file that declares the map. A name a lib only re-exports reaches the class it re-exports. A guard a factory makes is a guard too, whether the factory sits in the same file, wraps another factory there, or is imported from another lib. A guard node carries its source location, `doc` (the comment above its declaration that says what it checks) and, in `detail`, its `scope` (`account` or `resource`). A factory-made guard takes the factory's scope, and takes its doc when no guard of that factory has its own. With digests, an edit to a guard or to the factory that makes it marks the guard changed in the Change Map. Every guard a workspace declares is a node, referenced or not. `Public` and `None` come from `akanjs`, carry no location and say what they do. Studio shows a node's `doc` in its tooltip.
|
|
38
|
+
|
|
39
|
+
Ids and details that change for a saved graph or a Change Map base:
|
|
40
|
+
|
|
41
|
+
- A guard id names its owner: `guard:Admin` is now `guard:libs/shared:Admin`, and the framework's `guard:Public` is `guard::Public`.
|
|
42
|
+
- An endpoint's `detail` names a guard as written: `srvkit.Admin` and `SSO.Kakao` where it read `Admin` and `Kakao`.
|
|
43
|
+
- A slice map entry that lists several guards, `cru: [Owner, None]`, now reads `cru Owner` and `cru None` in `detail`, and links each of them, where it read `cru ?` and linked none.
|
|
44
|
+
|
|
45
|
+
- **`FileFacts.imports`, `guards`, `guardFactories` and `guardCalls`** in `@akanjs/language`: the reader records each class that implements `Guard` (under any name it is imported as), each factory that returns one or calls such a factory in the same file, and each const that a factory makes, a local one or an imported one. `AkanIndex.setFile` reads them from every non-test `srvkit/` file and every other non-test file that names `Guard` and `canPass`.
|
|
46
|
+
- **`AkanGraphNode.doc`** in `akanjs/common`: an optional one-paragraph description.
|
|
47
|
+
|
|
48
|
+
- ba417c6: The Akan Graph draws a module's jobs and the adaptors its services plug. Each key of an `internal()` class is an `internal` node, `internal:apps/demo:memo.sweepMemos` labelled `MemoInternal.sweepMemos`, whose `detail` reads its type, its schedule (a cron expression, or an interval or timeout such as `5m`), its `serverMode` and `operationMode`, `disabled` for `enabled: false`, `once` for `once: true` and `unlocked` for `lock: false`; a job links to the service methods its `exec` calls, as an endpoint does. A `resolveField` is a field resolver, not a job, and stays out. Each `adapt()` or `dangerouslyAdapt()` class is an `adaptor` node keyed by the app or lib that declares it, with its registered name in `detail`, its location, the comment above it and a digest; anything a service plugs from `akanjs`, such as `DatabaseAdaptorRole`, is `adaptor::DatabaseAdaptorRole` with no location. A service method that calls a plugged field (`this.storageApi.deleteData(url)`) links to that adaptor rather than to whatever else shares the method's name, and a `plugs` edge runs from an adaptor to each adaptor it plugs. Studio's signals view shows jobs in its "Endpoints, slices & jobs" column and adaptors in "Queries, models & adaptors".
|
|
49
|
+
|
|
50
|
+
- **`internal`, `adaptor` and `plugs`** join `AkanGraphNodeKind` and `AkanGraphEdgeKind` in `akanjs/common`.
|
|
51
|
+
- **`FileFacts.internals` and `FileFacts.adaptors`** in `@akanjs/language`, with `ServiceClassFact.plugs`; `AkanIndex.setFile` also reads non-test files that call `adapt` or `dangerouslyAdapt`.
|
|
52
|
+
|
|
53
|
+
- bb40785: The Akan Graph maps `page/`. Each route file is a `route` node named by its URL pattern after the locale (`route:apps/demo:/memo/:memoId`, groups dropped as the router drops them), and each `_layout.tsx` is a `layout` node keyed by its folder (`layout:apps/demo:/(app)`, `root` for a `rootLayout()`). A `wraps` edge runs from each layout to the routes and layouts directly inside it, located at the wrapped file. A route or layout whose default export runs `.guard()` reads `guard` and links each function its guard calls that the file imports or declares: `.guard(() => signedIn())` reaches `signedIn` in `@libs/shared/webkit`, with its location and the comment above it, and a function declared in the page itself is keyed by that file (`guard:apps/demo:page/pin.tsx#isOwn`). A call to anything else (`Boolean`, akanjs's `redirect`, a package's `dayjs`) is looked through, a helper handed to a guard (`isOwn(isId(id))`) is not a guard, and an imported guard is looked up under its import path first. A layout's guard gates every route it wraps; the routes themselves do not repeat it, so a guard added to a layout marks only that layout changed in the Change Map. A route's default export, or the const it names (`const route = page()…; export default route`), is the route node itself, so the slices and actions a page uses hang off its URL. `_overrides.tsx` and the git-ignored `page/(libs)/` links are left out; a lib's own pages are its routes. A route's digest covers its whole file, so the Change Map marks an edited page changed instead of listing it as unmapped.
|
|
54
|
+
|
|
55
|
+
An unscoped `get_graph` still answers a graph of up to 600 nodes whole. Past that, it first drops the routes and layouts that neither changed nor use a change, and the route-only guards they leave unlinked, before it asks for a scope; an `app` or `module` scope brings them back. Studio's first column reads "Pages & components".
|
|
56
|
+
|
|
57
|
+
- **`route`, `layout` and `wraps`** join `AkanGraphNodeKind` and `AkanGraphEdgeKind` in `akanjs/common`.
|
|
58
|
+
- **The fact reader** in `@akanjs/language` now also exposes `importsIn`, `docOf` and `extentOf`.
|
|
59
|
+
|
|
60
|
+
- d2bd9b7: A new read-only MCP tool, `get_graph`, returns the Akan Graph, read from source with no dev server. It holds the models with their fields and relations, scalars and enums, slices and endpoints with their guards, and the service methods and queries each one calls. It also holds the store actions that fetch them and the components and pages that use them. Every node and edge carries its file, line and column. A workspace over 600 nodes asks for `app` and/or `module`; a scope keeps its own nodes and their direct neighbours. Files under `env/` are never read.
|
|
61
|
+
|
|
62
|
+
- **`AkanIndex.graph(readText)`** in `@akanjs/language` builds the graph from the same index that answers definitions. An app module that extends a lib module of the same name links to the app's own node. A component links only through `fetch`, `st` and `sig`, so `option.orgId` is not read as model `org`.
|
|
63
|
+
- **The graph types** (`AkanGraph`, `AkanGraphNode`, `AkanGraphEdge` and their kinds) live in `akanjs/common`.
|
|
64
|
+
|
|
65
|
+
- 2d357d6: Editors can now jump from a name Akan.js derives to the key it comes from. `fetch.initMemoInPublic`, `st.do.setNameOnMemo` and `this.memoService.listByName` had no definition before, because TypeScript drops the declaration of a key a mapped type renames. A new package, `@akanjs/language`, indexes those names from source text, and an editor extension serves them. The extension (`pkgs/@akanjs/vscode`) is not on a marketplace yet; build it with `bun pkgs/@akanjs/vscode/build.ts --vsix`.
|
|
66
|
+
|
|
67
|
+
- **`@akanjs/language`** is an editor-neutral reverse index over a workspace's constant, document, service, signal and store files. It needs no type checker, and the host passes in its own `typescript`. For a derived name it answers definition and hover; for a source key it answers references, the keys to put a CodeLens on, and the edits of a rename. It covers slice names (`initMemoInPublic`, `slice.memoInPublic`, `memoListInPublic`), Filter queries (`listByName`), model CRUD and state (`getMemo`, `viewMemo`, `memoForm`), form setters across the `via(...)` chain (`setNameOnMemo` lands on `MemoInput.name`), store state setters, `subscribe`/`listen` endpoints, and module references (`this.memoService`, `srv.memo`, `sig.memo`). A name resolves within its own app and every lib, never another app. It ships as ES module and CommonJS with no runtime dependency.
|
|
68
|
+
- **The extension** adds those definitions and hovers next to TypeScript's, with a `Go to slice` link in the hover. A CodeLens on each slice key, Filter query key, constant field, store state key and pubsub or message endpoint shows its use count, plus the guards and `live` of a slice. Find References from a source key lists every derived use. F2 on a slice or Filter query key (or `Akan: Rename the slice or query key with its derived names`) renames the key, every name derived from it and its dictionary entry; a use that could also be another model's is left for TypeScript to report, and F2 elsewhere stays TypeScript's. In a trusted workspace it loads the workspace's own `@akanjs/language` when one is installed, so its rules match the framework version.
|
|
69
|
+
- **One copy of the name rules.** The templates behind the names apps call now live in `akanjs/common` (`sliceNames`, `queryNames`, `modelNames`, `databaseStateNames`, `formSetterNames`, `stateSetterName`, `endpointNames`, `moduleNames`). The runtime, devkit's scanners and scaffolds, and `@akanjs/language` all read them, and a type test fails when a mapped type and its table disagree. `databaseStateNames` and `formSetterNames` now live in `akanjs/common`; `akanjs/store` keeps them as deprecated aliases.
|
|
70
|
+
- devkit's write-placement and SSR checks read signals through the same reader, and its form-setter check now builds `set<Field>On<Model>` names forward from each module's constant instead of matching a pattern, so a slice setter such as `setPageOfProductOnSale` no longer reads as a field setter.
|
|
71
|
+
|
|
72
|
+
- 69ed07e: Akan Studio draws the Change Map.
|
|
73
|
+
|
|
74
|
+
- **The Changes view** (`/_akan/studio?view=changes`) draws what changed since HEAD forked from the default branch, or since `since=<rev>`, uncommitted work included.
|
|
75
|
+
- Components, store actions, signals, service methods, queries, models, scalars, enums and guards are laid out in flow order and outlined as added, removed or changed. The fields that changed are listed inside their model.
|
|
76
|
+
- The unchanged nodes a change calls, or is called by, are drawn dimmed. The changed files no node covers are listed under the diagram, each marked added, removed or changed.
|
|
77
|
+
- Only this app's and the libraries' nodes are drawn. `module=` scopes the map to one module.
|
|
78
|
+
- A revision git does not know is shown as a message on the page. The `since` you picked stays when you switch views.
|
|
79
|
+
- The dev builder keeps the last map until the next save, so reloading the page does not read the workspace again.
|
|
80
|
+
- **`akan studio <app> --view changes [--since <rev>]`** prints its URL. `--since` without `--view changes` is an error.
|
|
81
|
+
- **Akan: Open the Change Map** in the VS Code / Cursor extension opens it beside the editor. The extension offers to start the app only when no dev server answers, not on any other `akan studio` failure.
|
|
82
|
+
|
|
83
|
+
- 3f231a7: `akan start` now serves Akan Studio at `/_akan/studio`, a read-only view of the Akan Graph for the running app and the libs it reaches.
|
|
84
|
+
|
|
85
|
+
- **ERD:** models, scalars and enums with their fields. Each relation is drawn from the field that names it, and each referenced type sits to the right of the model that names it.
|
|
86
|
+
- **Signals:** one module laid out from left to right: components and pages, store actions, endpoints and slices with their guards, service methods, then queries and models.
|
|
87
|
+
- **Interaction:**
|
|
88
|
+
- Hovering a box highlights its edges and neighbours, and clicking it pins the highlight.
|
|
89
|
+
- The filter dims boxes whose name doesn't match.
|
|
90
|
+
- Every name opens its source in VS Code or Cursor.
|
|
91
|
+
- `/_akan/studio/graph` returns the same graph as JSON.
|
|
92
|
+
- **How it's built:**
|
|
93
|
+
- The page is server-rendered SVG with a small inline script, and loads no client bundle.
|
|
94
|
+
- The dev builder draws the graph on request through a new `build-graph` builder RPC, and keeps it until the next save.
|
|
95
|
+
- It sits behind the same gate as the other `/_akan` devtools routes, and `akan tunnel` / `akan start --share` refuse it.
|
|
96
|
+
|
|
97
|
+
- 0504209: `akan build <app> --env <env>` builds for an environment other than the workspace `.env`'s, the way the desktop and mobile builds already do. Before, the shell's `AKAN_PUBLIC_ENV` lost to the `.env`, so a new workspace could only build `local` without editing it. The local-build notice, the image guard and the README now say what a `local` build changes in a built server: no endpoint rate limits, and local defaults for Redis and the database mode. They no longer say the dev routes are served, which a built server never does.
|
|
98
|
+
- bf872ce: A variable the shell sets now outranks the workspace `.env` when `akan` builds or runs an app, as dotenv and Bun's own `.env` loading do: `AKAN_PUBLIC_ENV=main akan build <app>` builds `main` where the `.env` says `local`. The order is `--env` (for `AKAN_PUBLIC_ENV`), then the process environment (the shell, and the `.env.local` and `.env.<NODE_ENV>` Bun loads into it), then the `.env`, then the defaults; an empty export counts as unset. The bundles, the bundled server env, `akan.env.json` and the Dockerfile still take one set of values. Where an exported value outranks the `.env`, `akan build` now warns with both values instead of the old warning that listed the values it ignored, and the server-env message, the `--env` help, the local-build notice, the README and the CLI reference state the new order.
|
|
99
|
+
|
|
100
|
+
**Behavior change:** where the shell exports a value the workspace `.env` also sets, such as `AKAN_PUBLIC_ENV`, `AKAN_PUBLIC_REPO_NAME` or `AKAN_PUBLIC_SERVE_DOMAIN`, `akan` now takes the shell's. Unset it, or match the `.env`, to build what you built before.
|
|
101
|
+
|
|
102
|
+
- e65172d: The CLI refreshes a cloud sign-in once less than an hour, or half the token's lifetime, is left, whichever is shorter. Before, a token that lived an hour or less read as due the moment it arrived, so every command rotated the refresh token and then called the cloud unsigned. Any unexpired token is now sent with the request, except the refresh call, which carries none. `akan login` signs in again when a session is due for refresh and has no refresh token left.
|
|
103
|
+
- c3b5b3c: A web page no longer loads `@akanjs/native/core`. `isNativeApp()` and the other host checks read the platform the shell writes before the bundle runs, and a native shell still starts core with the page. On the minimal app this takes 15 KB raw (5.6 KB brotli) off the vendor floor and 20 KB raw (6.6 KB brotli) off every route entry. `akan build` also resolves the `production` export condition for the browser, SSR-client, CSR and MCP App view bundles, so a package such as Lexical ships its production build alone. On the minimal app the Agent editor chunk drops from 91 KB to 63 KB brotli. `akan start` is unchanged.
|
|
104
|
+
- 6d5b544: A database service now has `create<Model>Many(rows)`. It creates the rows on the same path as `create<Model>`: `_preCreate` and `_postCreate`, the document's save and create hooks, and the live and pubsub events each row's commit fires. Every row's `_preCreate` and document pre hooks run first, so a check there that queries the table does not see the batch's own rows. The rows are then written in one transaction, joining the caller's when one is open, with one multi-row INSERT per 500 rows instead of one statement per row; if the database refuses one row, none is written. Every row's `_postCreate` runs once the transaction commits, as `create<Model>`'s runs once its row is stored, so a `_postCreate` that throws leaves the rows written. A service that already declares a method of that name, itself or in a lib service it extends, keeps its own at runtime, but its signature no longer typechecks against the generated one: rename it.
|
|
105
|
+
|
|
106
|
+
A store can list actions in `static silent = ["refreshToken"] satisfies (keyof UserStore)[]`. When a silent action fails, it shows no toast and logs at verbose instead of error. It still rejects with the original error, so a background write such as a token refresh is awaited by its caller, which handles the failure. Generated actions can be listed, and a store built on a lib store keeps the lib's silent actions. A name in `static silent` that is not an action of the store is reported when the store registers.
|
|
107
|
+
|
|
108
|
+
`akan lint`'s `akan.service.call-in-loop` fix and the agent guidelines name `create<Model>Many` as the way to write a set once.
|
|
109
|
+
|
|
110
|
+
- cb44cc0: `akan create-workspace` and `bunx create-akan-workspace` can now create a workspace in a folder you already have, leave out the sample code, and make the first commit on a machine with no git identity. Before, a coding agent that had to put the workspace in a given folder created it elsewhere, copied it over and deleted the sample by hand.
|
|
111
|
+
|
|
112
|
+
- **In place.** Name the workspace `.` to create it in the current folder, or in `--dir` itself, instead of a new `<dir>/<name>` subfolder. The workspace takes that folder's name.
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
bunx create-akan-workspace . --app web # the current folder
|
|
116
|
+
bunx create-akan-workspace . --app web --dir app # ./app
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The folder may already hold files the workspace does not write, such as `.git`, `.vscode/launch.json` or notes. When it holds one the workspace would overwrite, such as `package.json`, `README.md`, `.gitignore` or `apps/<app>`, the command writes nothing and lists them. The same check now guards `<dir>/<name>`, which was overwritten without a word before. It also refuses your home folder and a filesystem root, where the first commit would take in every file and the agent and MCP settings would overwrite your global ones. And it refuses a folder whose path ends the way a path it writes begins, such as `apps`, `patches`, or `apps/web` with `--app web`, since the files would land in the wrong place.
|
|
120
|
+
|
|
121
|
+
- **`--sample false`** creates the first app without the sample: the `task` module and its pages, the `_noti` service, the `workHistory` scalar, and the sample helpers in `common/`, `webkit/`, `srvkit/` and `ui/`. It defaults to `true`, as before.
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
bunx create-akan-workspace my-company --app web --sample false
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
- **Git.** When git has no identity configured, as in many containers and CI images, the first commit is made as `Akan.js <noreply@akanjs.com>` instead of failing; once `user.name` and `user.email` are set, `git commit --amend --reset-author` makes it yours. In a folder another git repository tracks, the scaffold no longer runs `git init`, which made the outer repository record a gitlink instead of the files, and it commits nothing there: the new files are left for you to commit in that repository. A folder that repository ignores, as `app/` or `app/*` does, still gets its own repository and commit.
|
|
128
|
+
- **`@akanjs/devkit`**: `applyTemplate` takes `dryRun` to list the files a template would write without writing them, and `WorkspaceExecutor.commit` takes an `identity` to commit under.
|
|
129
|
+
|
|
130
|
+
- 1870fcd: `akan verify` adds a `fix:` line to a module file's two-parent import that names the layer barrel to import from instead, such as `import from "@apps/<app>/common"`. The lint message itself now points at the layer barrel too.
|
|
131
|
+
- 245d318: A font declared on a root layout now gets its metric-adjusted fallback faces: `@font-face` rules named `"<font> fallback"` that draw a local system font with `size-adjust`, `ascent-override` and `descent-override` matched to the web font. The variable `--font-<name>` already named that family, but the build never wrote it, because fontaine's `readMetrics` takes a URL and returned nothing for the file path it was given. While a web font loaded (`font-display: swap`), text drew in the browser's default font, usually a serif, and moved when the web font arrived. The metrics are read from the source font, not the subset, so a subset without Latin letters (`subsets: ["ks-x-1001"]`, or only `subsetText`) still sizes the fallback correctly. A font whose metrics cannot be read keeps its face and gets no fallback, as before. Set `adjustFontFallback: false` on a font to keep the old output. Cached font output from `akan start` is regenerated once.
|
|
132
|
+
- 6f6cc2a: A fresh clone of a workspace now builds and starts after `bun install` alone. The env files that hold secrets stay gitignored, so a clone has none of them: before, `akan typecheck`, `akan build` and `akan verify` failed with `Cannot find module './env.server.local'`, and with no `.env` the CLI picked the `debug` environment, so `akan build` stopped asking for `AKAN_PUBLIC_REPO_NAME` and `AKAN_PUBLIC_SERVE_DOMAIN`.
|
|
133
|
+
|
|
134
|
+
- **Missing env files are written on sync.** `akan sync`, and every command that syncs first (`build`, `start`, `typecheck`, `verify`, `test`), writes each per-environment file that `env/env.server.ts` or `env/env.client.ts` imports and the checkout lacks, plus `env/env.server.testing.ts`, in apps and libs alike. A file comes from the folder's tracked `env.server.example.ts` or `env.client.example.ts` when there is one, otherwise with the defaults a new workspace starts with. A file that exists is never touched, and sync logs the files it wrote. Put this machine's values in them, or fetch the team's with `akan download-env`, which overwrites them.
|
|
135
|
+
- **No `.env` means the local environment.** A workspace command run where there is no `.env` takes `AKAN_PUBLIC_ENV=local`, the environment a new workspace's `.env` names and `env/env.server.ts` falls back to, unless the shell sets `AKAN_PUBLIC_ENV`. A `.env` that exists is read as before, so a CI job that writes one, or sets the variable, builds what it did.
|
|
136
|
+
- **New workspaces** also track `env.client.example.ts`: its `.gitignore` no longer ignores it, as it already did not ignore `env.server.example.ts`.
|
|
137
|
+
|
|
138
|
+
**For an existing workspace** nothing is required. To give a fresh clone values other than the empty defaults, commit an `env/env.server.example.ts` (and `env.client.example.ts`) without secrets in it; in an older workspace, add `!**/env.client.example.ts` to `.gitignore` for the client one. If you link env files into a new worktree, link them before the first `akan` command there, or replace the defaults it wrote.
|
|
139
|
+
|
|
140
|
+
- e2b809a: The typecheck behind `akan typecheck`, `akan verify` and `akan build` now uses its incremental cache. It wrote a `.tsbuildinfo` on every run, then checked every file again anyway, so a run with nothing changed cost as much as the first: about 8 seconds on a 2,400-file project. Now a run re-checks only the files that changed and the files they affect, and reads the rest from the cache. The same project's second run takes about 1.2 seconds. The diagnostics are the same, in the same order, as a full check: options, global, syntax, type and declaration errors, an edited file's new errors and the errors it causes in files that import it. `--clean` still clears the cache first.
|
|
141
|
+
- bf28968: An app can now serve its default locale at unprefixed paths. Set `i18n.localePrefix` to `"as-needed"` in `akan.config.ts`, and `/books/OL45804W` is the English page itself. Before, every page lived under `/<locale>/…`, and an unprefixed path answered with a 307 to the locale the browser asked for. An app with one language, or with URLs fixed from outside (OAuth callbacks, QR codes, links in mail, a migrated site), could only meet them with raw `<a href>`s, which reloaded the whole page and went through that 307 on every click.
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
const config: AppConfig = {
|
|
145
|
+
i18n: {
|
|
146
|
+
defaultLocale: "en",
|
|
147
|
+
locales: ["en", "ko"],
|
|
148
|
+
localePrefix: "as-needed",
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
- **The default stays `"always"`**, so an app that sets nothing serves and links exactly as before.
|
|
154
|
+
- **Under `"as-needed"` the default locale drops the prefix and the other locales keep it.** `<Link href="/types">` renders `/types` on an English page and `/ko/types` on a Korean one, and still navigates in place. So do `router.push`, a server `router.redirect()`, `setLang`, active-link matching, the 404 page's home link, `hreflang` alternates and `/sitemap.xml`. `setLang` to the default locale drops the prefix. A single-locale app has no prefix anywhere.
|
|
155
|
+
- **An unprefixed URL always means the default locale.** No Accept-Language redirect runs, so one URL is one locale for caches and search engines.
|
|
156
|
+
- **`/<defaultLocale>/x` answers with a 308 to `/x`**, so links from the `"always"` days keep working and land on the one canonical URL. The 308 is cached for a day, so an app that goes back to `"always"` stops redirecting within that time.
|
|
157
|
+
- **Native and CSR boots keep their locale paths.** Their routes match on the `/:lang` segment, so a `?csr=true` request without one is redirected to the default locale's prefixed path. An Android app link limited to a basePath lists the default locale's unprefixed path as well as its prefixed one, so links from before the switch still open the app.
|
|
158
|
+
- `akan preview` keeps an unprefixed path unprefixed under `"as-needed"`, except with `--csr`. The value reaches bundles as `AKAN_PUBLIC_LOCALE_PREFIX`, and the image env carries it when it is `"as-needed"`. `LocalePath` (`akanjs/common`) and a fourth `localePrefix` argument to `getPathInfo` build the same URLs in app code.
|
|
159
|
+
|
|
160
|
+
- 9b5bcee: Every SSR page now loads 72K less framework JavaScript (22K less after Brotli).
|
|
161
|
+
|
|
162
|
+
- **`akan build` narrows the vendor entries to the names the page code imports.** The `akanjs/*` and `tailwind-merge` entries in the page's import map used to re-export a whole facet. Each one now exports only the names the built route bundles import, lazy chunks included. On the minimal app, the framework floor, meaning the runtime plus the vendor entries every page loads, drops from 630.2K raw / 189.4K br to 558.4K raw / 167.4K br. `akan start` keeps whole facets, so a newly saved import never misses a name.
|
|
163
|
+
- **The agent runtime moved to `akanjs/store/agent`, which has its own vendor entry.** This covers `AgentContext`, `AgentVisual`, `AgentPrompts`, `StoreSurfaceSource`, the `Screen*` readers and the rest. A page now loads that entry (28.6K raw / 9.4K br) only when it mounts `Agent.Chat`, `Agent.Zone` or `Agent.Dock`. `akanjs/store` still re-exports these names for one minor, marked `@deprecated`. A build whose pages import none of them keeps the runtime out of the store entry. `ToolReturn`, `AgentAbort` and `AgentProgress` stay in `akanjs/store`.
|
|
164
|
+
- **A tab left open across a deploy loads its next page as a document when the import map changed.** Narrowed vendor entries differ from build to build, so a newer route chunk could import a name the tab's vendor entries lack. Each document now names its import map, and the server answers a navigation from another import map with 409. The client then loads the target as a document, with the current import map.
|
|
165
|
+
- **A narrowed build fails when route code imports a name its facet does not export**, and names the missing names. Before, the page threw a `SyntaxError` in the browser.
|
|
166
|
+
|
|
167
|
+
- 4561152: The skill examples now use one neutral domain, books from a public read-only API: a `BookApi` adapter in `srvkit/`, a `lib/_openlibrary/` service with a `book` query, `fetch.book(id)` and `/books/[id]` pages. This covers the `akan-service` external-API recipe, the `akan-page` API summary, the `akanApi` import block, the `config` rule's `localePrefix` example and the README's as-needed path. The recipes teach the same steps as before.
|
|
168
|
+
- 53eb6f1: A command whose app-or-lib target is left out no longer stops at a prompt error when stdin is not a terminal, as in a coding agent's shell or CI. In a workspace with a single app, lib or package, `akan sync`, `akan lint`, `akan test` and `akan create-module <name>` run on it and say so on stderr, so a new workspace's `bun run sync`, `bun run lint` and `bun run test` work there. Otherwise every target command (app, lib, package, module) exits at once with the valid targets and the line to rerun, such as `akan sync <app-or-lib>`. A library, package or module is still never picked unasked, since `akan remove-library` and its kin would delete the only one. A terminal is asked as before.
|
|
169
|
+
- b24fef3: An app that draws no Akan UI component now builds and runs without the UI's optional peers. Before, `akan build` failed with `Could not resolve: "react-icons/ai"` or `"react-spring"` in an app that imported nothing from `akanjs/ui`: the System UI every root layout mounts drew react-icons glyphs and reached the model, list and dialog components through the `Load` and `Button` modules, and the CSR build, on by default, bundled the whole `akanjs/ui` barrel.
|
|
170
|
+
|
|
171
|
+
**Upgrade note.** A workspace can drop `react-spring`, `@react-spring/web`, `@use-gesture/react`, `react-refresh`, `scheduler` and `tailwind-scrollbar` from its `package.json`, unless its own code imports them. `akanjs` now depends on `@react-spring/web`, `@use-gesture/react`, `react-refresh`, `scheduler` and `tailwind-scrollbar` itself, because the System UI, the dev runtime and `akanjs/ui/styles.css` always use them, and nothing uses `react-spring` any more. New workspaces no longer list them. A workspace whose own code keeps `react-spring` or `@react-spring/web` must move it to major 10: an older one installs a second copy under `akanjs`, and a spring value the app makes no longer typechecks on an `animated.*` element from `akanjs/ui`.
|
|
172
|
+
|
|
173
|
+
- **`react-icons` stays an optional peer.** Install it when the app draws a component that shows its icons, as most form, list and dialog components do. Under `akan start`, a CSR page (`?csr=true` or a native shell) of an app that imports anything from `akanjs/ui` still needs it: the dev module registry loads that barrel whole.
|
|
174
|
+
- **`chance`** stays an optional peer that only `akanjs/test` needs.
|
|
175
|
+
- **The System UI draws its own icons.** The Button's processing and done states, the toasts and the reconnect overlay render inline SVGs with the same markup react-icons drew.
|
|
176
|
+
- **The generated root layout and `_overrides.tsx` wrapper** import `System` and `UiOverrideProvider` from their own files instead of the `akanjs/ui` barrel.
|
|
177
|
+
- **The CSR build and its MCP App views** rewrite `akanjs/ui` imports to the components a file names, as the SSR route builds already did, so a page that imports `Link` no longer bundles every component.
|
|
178
|
+
- **Animations use `@react-spring/web`** instead of the `react-spring` umbrella package, which also installed the native, three.js, konva and zdog renderers.
|
|
179
|
+
|
|
180
|
+
- 686e0b8: `akan preview` and the code agent's page preview bound a page only once the browser is up. Before, a process's first page shared its `timeoutMs` with starting the browser. On a cold machine where Chrome took longer than that to start, the first page failed as `Timed out loading …; the dev server may be rebuilding or stuck.`
|
|
181
|
+
|
|
182
|
+
Starting the browser now has its own bound of 120 s, `startTimeoutMs` on `PreviewView.probe`. Running out of it reads `Timed out starting the <engine> browser after 120 s.`, where the engine is `chrome` or `webkit`. A browser that misses its bound may still come up late, so later pages in that process try again, but each waits only its own `timeoutMs` for the browser. Once the browser answers, the full bound applies again. So a browser that never starts holds the first page for 120 s, where before it held it for `timeoutMs` (60 s for `akan preview`, 30 s for the code agent's check).
|
|
183
|
+
|
|
184
|
+
- eefd9cb: `akan preview <app> /_akan/studio` opens Studio instead of a 404. Preview added the default locale to every path, and the server routes `/_akan/*` without one, so `/en/_akan/studio` matched no page. A path under `/_akan`, `/__csr`, `/__rsc`, `/.well-known` or the app's API prefix, a file the app's `public/` holds, and the server's own `/favicon.ico`, `/robots.txt`, `sitemap.xml` and `manifest.webmanifest` now open as written: no locale, and no CSR boot query under `--csr`. Every other path, including one that already starts with a locale or ends in a dotted segment such as `/release/1.2.3`, is a page and gets the default locale as before. The `akan-debug` skill and the `akan preview` reference say so.
|
|
185
|
+
- 4fcf44f: A new `rename-key` workflow renames a slice key or a Filter query key everywhere it is spelled. That covers the key itself, every name derived from it (`initMemoInPublic`, `memoListInPublic`, `queryByName`) in the app and every lib, and its module dictionary entry. It is the same rename the editor extension's F2 runs, so agents get it through `plan_workflow` / `apply_workflow` and the CLI through `akan workflow plan rename-key --app <app> --module <module> --slice <key> --to <newKey>` (or `--query <key>`). The plan lists each file it will change. A use that could also be another model's is left alone, and `akan verify` then reports it.
|
|
186
|
+
- 9a9e1fa: A server page from `akan build` now preloads its client modules from `<head>`, so a cold load stops waiting on chains of imports.
|
|
187
|
+
|
|
188
|
+
- **What is preloaded:** the rsc client and the chunks it imports, the chunks every client reference row (`I` row) in the shell's flight names, and the importmap vendors those chunks import, each with its own chunks. A client URL that only the page's own text carries is never preloaded. Before, only the rsc client and the locale dictionary were preloaded. The browser found the rest one import level at a time, after the document finished.
|
|
189
|
+
- **Measured on `apps/minimal`:** `/en/explore` preloads 45 modules and its flight names 18 client chunks, all of them preloaded. `/en/lab/buttons` preloads 112 for 85. Preloading every client component each route's page and layouts import would have meant 237 modules for `/en/explore`, so the preloads follow the flight instead.
|
|
190
|
+
- **A boundary that resolves after the shell** still loads its chunks when its flight rows arrive. `akan start` preloads nothing new, because a dev page loads its client code from the SSR dev registry.
|
|
191
|
+
- `routes-manifest.json` carries the preload table as `modulePreloads`: the runtime chunks, each vendor's chunks, and the vendors each browser chunk imports.
|
|
192
|
+
|
|
193
|
+
- 9a9e1fa: `akan build` measures the JavaScript each server route loads, and fails a route that outgrows the budget the app commits.
|
|
194
|
+
|
|
195
|
+
- **The measure:** the runtime, every client module statically reachable from the route's page and layouts, and their vendors, counted in the brotli bytes the server sends. A chunk loaded by a dynamic `import()` is left out, and so is the locale dictionary. A new `Measuring route JS` step names the largest route, and the summary lists the ten largest with their brotli and raw sizes. On `apps/minimal` the largest is `/:lang/explore` at 499.1KB brotli (1.3MB raw).
|
|
196
|
+
- **The budget:** `akan build <app> --update-budget` writes `apps/<app>/akan.budget.json` with the environment it built for and one `routeJsBrotliKB` entry per route. After that, `akan build` fails a route that passes its entry by more than the larger of 1% and 4KB, as the bundle size gate does, and names the route, its bytes and its limit, so CI stops the growth. The slack absorbs the `AKAN_PUBLIC_*` values a build inlines, so a budget recorded for one environment holds for another, and a failure says when the file was recorded for another one. The update adds a new route, lowers an entry its route now fits under and drops a route that is gone. It never raises an entry: to raise one, edit it by hand in the change that grows the route.
|
|
197
|
+
- **Where it fails:** `akan build` fails on an overage or an unreadable file, and `--update-budget` rewrites an unreadable file. `akan verify` warns of each route over its entry and of an unreadable file, one line each, but never fails on them. A mobile or desktop build skips the step.
|
|
198
|
+
- **Opt-in:** an app without the file gets the report and nothing fails. A route without an entry is reported, not failed. `akan sync` accepts `akan.budget.json` at an app root.
|
|
199
|
+
|
|
200
|
+
- 27440aa: A production build now runs without Docker: `cd dist/apps/<app> && bun main.js` serves the app with no env set. Until now the env the built server needs (the app and repo names, the serve domain, locales, route prefixes, database modes and the rest) existed only as `ENV` lines in the generated Dockerfile. On a host without the image, such as a PaaS or a test harness, the server stopped at boot with `getEnv() cannot run at build time` unless a start script copied every value by hand.
|
|
201
|
+
|
|
202
|
+
**Upgrade note.** Run `akan agent install` and commit what it writes: the `akan-runtime` skill, the `config` path rule and `runtimeRule` now say how to run a build without Docker.
|
|
203
|
+
|
|
204
|
+
- **`akan build` writes `akan.env.json` beside `main.js`**, with the same values as the Dockerfile's `ENV` lines. None of them is a secret.
|
|
205
|
+
- **`main.js` reads it before any other module loads** and fills in only the variables the process was not started with. A variable set in the environment wins, even an empty one, so `PORT=3000 bun main.js` and a host's own settings still apply, and an image built from the generated Dockerfile runs exactly as before. The file is found beside `main.js` from any working directory, but start the server from its own folder, because the runtime reads its artifacts and writes `runtime/` and SQLite files relative to the working directory.
|
|
206
|
+
- **An app that writes its own Dockerfile** (`docker` as a string) and copies the build into its image now gets these values too, for whatever its Dockerfile leaves unset: among them `NODE_ENV=production`, `AKAN_PUBLIC_OPERATION_MODE=cloud` and `AKAN_LOG_TO_FILE=0`, so set those in the Dockerfile if the image needs other values.
|
|
207
|
+
- **A desktop app's carried server leaves `akan.env.json` behind.** Its launcher hands it its env as before, and the file would otherwise set the `PORT` and `AKAN_LOG_TO_FILE` the launcher leaves out on purpose.
|
|
208
|
+
|
|
209
|
+
- e3de9ef: The CLI's MCP tools, tunnels, env download, local registry and generated Dockerfile now stay narrower by default. Each changed default below names how to get the old behaviour back.
|
|
210
|
+
|
|
211
|
+
- **Workflow and repair commands run without a shell.** A command taken from a plan file, a run report or an MCP argument now runs as `bun run akan <args>`. It runs only when it is an `akan` subcommand whose arguments are plain words or flags; anything else is not run and fails with exit code 2. `akan workflow validate` and `apply` (and the `apply_workflow` / `run_validation` MCP tools) rebuild the validation commands from the workflow's own spec instead of reading them from the file.
|
|
212
|
+
- **Workflow inputs are plain names.** An `app`, `module`, `scalar`, `field`, `slice`, `mutation` or `interaction` input may hold letters, digits, `-` and `_`, with no dots, slashes or spaces. Planning reports anything else as `workflow-input-invalid`, and `apply` refuses such a plan with `workflow-plan-inputs-invalid`. The `app` must be one of the workspace's apps or libraries (`workflow-target-missing`), and the `repair_*` tools check their target the same way (`repair-input-unknown`).
|
|
213
|
+
- **MCP workflow files stay in `.akan/workflows/`.** `plan_workflow`'s `out`, `apply_workflow`'s `planPath` and the `runIdOrPlan` of `run_validation` and `doctor_workspace` must resolve inside the workspace's `.akan/workflows/` folder. A path elsewhere, or a link that leads out of it, is refused. A bare run id still means `.akan/workflows/runs/<id>.json`.
|
|
214
|
+
- **The cluster database tunnel listens on loopback.** The Redis or Postgres tunnel `akan start` opens to a cluster environment now listens on `127.0.0.1` instead of every interface. Set `SSH_TUNNEL_BIND_HOST=0.0.0.0` to listen on every interface again; the tunnel then logs a warning on each start.
|
|
215
|
+
- **The tunnel checks the SSH host key.** When `SSH_TUNNEL_HOST_FINGERPRINT` lists `SHA256:` fingerprints (comma-separated), only those keys are accepted. Otherwise the first key a host shows is kept in `~/.akan/ssh_known_hosts.json`, written with mode 0600, and a different key from that host is refused. The refusal names the host and the file entry to remove after a rebuild. `SSH_TUNNEL_KNOWN_HOSTS` points at another file, and `SSH_TUNNEL_HOST_FINGERPRINT=*` accepts any key, as before.
|
|
216
|
+
- **The tunnel signs in with a key when given one.** `SSH_TUNNEL_PRIVATE_KEY_PATH` (with `SSH_TUNNEL_PASSPHRASE` if the key has one) or `SSH_TUNNEL_PASSWORD` sets how the tunnel signs in. Without either, it still uses the default shared credential, as before, and logs a warning.
|
|
217
|
+
- **A repository's dotenv file cannot loosen the tunnel or pick the registry.** `SSH_TUNNEL_HOST_FINGERPRINT`, `SSH_TUNNEL_KNOWN_HOSTS`, `SSH_TUNNEL_BIND_HOST`, `SSH_TUNNEL_PRIVATE_KEY_PATH`, `SSH_TUNNEL_PASSPHRASE` and `AKAN_NPM_REGISTRY` are ignored, with a warning, when a dotenv file in the working directory (`.env`, `.env.local`, `.env.<mode>`, `.env.<mode>.local`) names them, in `KEY=value` or `KEY: value` form, since Bun loads those files before the CLI runs; `create-akan-workspace` does the same for `AKAN_NPM_REGISTRY`. Export them in the shell instead. `SSH_TUNNEL_USERNAME`, `SSH_TUNNEL_PASSWORD` and `SSH_TUNNEL_PORT` are still read from a dotenv file, and `--registry` is unaffected.
|
|
218
|
+
- **`akan download-env` copies only env and secret files.** The archive is unpacked into a temporary folder under `local/`. Only `apps/<app>/env/env.{client,server}.*.ts`, `libs/<lib>/env/env.{client,server}.*.ts` and the files an app's `secrets` patterns name are copied into the workspace. Every other entry, links included, is left out and listed in a warning.
|
|
219
|
+
- **The local registry listens on loopback.** `akan start-registry` now publishes Verdaccio's port as `127.0.0.1:<port>`, taking the port from `--registry`. A registry URL with an IP address (`--registry http://0.0.0.0:4873`) binds to that address instead. A running `akan-verdaccio` container that already listens on every interface gets a warning naming the command that recreates it.
|
|
220
|
+
- **The local registry takes anonymous publishes for the akan packages only.** The bundled `verdaccio.yaml` still lets anyone publish the Akan.js packages. Every other name, the ones proxied from npm, now needs a signed-in user, and sign-up stays off, so a local publish cannot stand in for an npm package. Give those entries `publish: $all` again to restore anonymous publishing.
|
|
221
|
+
- **Subspace names are plain folder names.** A subspace `name` takes letters, digits, `.`, `_` and `-`, and starts with a letter or digit. Any other name is refused when the config is read and when the clone is set up.
|
|
222
|
+
- **A Dockerfile built for the `local` environment stops `docker build`.** The generated Dockerfile of an image built with `AKAN_PUBLIC_ENV=local` fails right after its `FROM` line unless `--build-arg AKAN_ALLOW_LOCAL_IMAGE=1` is passed. Images built for any other environment, and a Dockerfile given verbatim in `akan.config.ts`, are unchanged.
|
|
223
|
+
- **The sample app's task writes need a signed-in user outside local and testing.** The workspace sample's `task.signal.ts` now guards its create, update and task actions with a new `LocalOrSignedIn` guard in `srvkit/guards.ts`. A guest still writes under `AKAN_PUBLIC_ENV` `local` or `testing`, so the sample's own tests keep running. Existing workspaces are unchanged.
|
|
224
|
+
- **`akan login` prefers the device-code answer.** When the cloud answers both ways, the CLI takes the session answered for its device code. A cloud that answers only for the sign-in URL's id still signs you in, and the CLI logs a warning.
|
|
225
|
+
- **`akan tunnel` keeps the dev-only routes local.** On opening a share, the CLI now says that anyone with the URL reaches the app without signing in. Through the share, devtools (`/_akan/devtools`, `/_akan/signal`, `/_akan/constant`, `/_akan/deps`, `/_akan/dictionary`), the HMR socket `/_akan/hmr`, `/_akan/app/*`, `/_akan/ops/*` and every `*.map` file answer 404, and an `x-akan-debug` header is dropped. Pages, their assets and the app's websocket pass through unchanged. `akan start --share` opens its shares through the same filter and prints the same notice. `akan tunnel --dev-routes` (`-d`) shares everything, as before.
|
|
226
|
+
- **The `no-unguarded-endpoint` lint and the agent guidelines describe the new defaults.** The lint says an endpoint without guards is refused with 403 and stops the boot outside local operation, and the guidelines and skills cover the ops channel's limits, snapshot encryption, the agent's write confirmation and request caps.
|
|
227
|
+
|
|
228
|
+
- e3de9ef: `akan code` hardening:
|
|
229
|
+
|
|
230
|
+
- Every profile's deny list now also covers per-environment server env files (`env.server.{debug,develop,main,testing,local}*.ts`; the `env.server.ts` loader and the type file stay readable), Kubernetes and Jenkins credential files, `.akan/code/mcp.json` and `.akan/code/extensions/`, and the home credential stores `~/.akan`, `~/.ssh`, `~/.aws`, `~/.kube`, `~/.docker/config.json`, `~/.netrc` and `~/.git-credentials`, for read, write and the shell. A path is checked the way the engine reads it (`@`, `~`, `$HOME` and `file://` expand) and again through its real path, so a link cannot give a denied file another name. A profile passed to `CodeAgent.create` with its own `paths.deny` still decides its list.
|
|
231
|
+
- The `local` profile asks before a `web_fetch` to a host off a short list of documentation and registry hosts, and before a shell command that reaches the network (`curl`, `wget`, `ssh`, `scp`, `rsync`, `nc`, …); loopback is exempt. Where nobody can answer, such as a sub-agent or a one-shot run, the call is refused instead. `akan code --no-ask-network` turns the question off. A fetch the profile makes without asking follows a redirect only to a host it would also reach without asking. Other profiles are unchanged.
|
|
232
|
+
- `akan code` prints at start that the `local` profile is not a sandbox.
|
|
233
|
+
- A one-shot `akan code "<prompt>"` no longer offers approvals or questions, since nobody answers them during the run. `AKAN_CODE_CAN_PROMPT=1` restores them.
|
|
234
|
+
- MCP sign-in tokens in `~/.akan/code/mcpAuth.json` are bound to the server's origin instead of its name, so a server declared under a known name at another url gets no token. Tokens an older version stored by name move to the origin they were minted for; one whose origin cannot be read is dropped and signs in again.
|
|
235
|
+
- This repo's own `.akan/code/mcp.json` servers and `.akan/code/extensions/` start only after the user approves them once at a terminal. The approval is kept in `~/.akan/code/trust.json` (mode 0600) and asked again when that content changes. An extensions folder past 10,000 files or 64 MiB, or with a link that leads out of it, is not read through and its approval is not kept, so it is asked about at every start. With nobody to ask they are not started, and the start notice says why. `akan code --trust-workspace` (`-T`) starts them for one run without asking. Servers in `~/.akan/code/mcp.json`, where `/mcp add` writes, need no approval; `/mcp add --local` writes this repo's file and keeps the approval it had, so a server added while that file waits for approval waits with it.
|
|
236
|
+
- `akan code --rpc-listen tcp:…` asks each connection to open with `{"auth":"<token>"}` on its first line and answers `{"type":"reply","id":"auth","ok":true}`. The token is generated at start and written to `~/.akan/code/rpc/<port>.token` (mode 0600), or read from the file `AKAN_CODE_RPC_TOKEN_FILE` names. A connection whose first byte is not `{`, or that does not authenticate within five seconds, is closed, and it never displaces the client already attached. `--no-rpc-token` serves without a token. A non-loopback bind is refused unless `--rpc-bind-any` is given. A `unix:` socket is created with mode 0600 and asks for no token, though a client that sends one is answered. Without a token, under `--no-rpc-token` or on a `unix:` socket, a connection whose first byte is not `{`, or that sends nothing within five seconds, is still closed.
|
|
237
|
+
- `AKAN_CODE_HOME`, `AKAN_CODE_CAN_PROMPT`, `AKAN_CODE_PROXY_*` and `AKAN_CODE_RPC_TOKEN_FILE` are ignored, with a start warning, when a dotenv file in the working directory (`.env`, `.env.local`, `.env.<mode>`, `.env.<mode>.local`) names them, because Bun loads those files before the CLI runs. Export them in the shell instead. `~/.akan` and `~/.akan/code` are created with mode 0700, and an existing `~/.akan/code` the user owns with a looser mode is tightened.
|
|
238
|
+
|
|
239
|
+
- e3de9ef: Native and desktop builds now ship tighter defaults for page content, external links, temporary files and installed updates.
|
|
240
|
+
|
|
241
|
+
- **A content security policy by default.** An Akan app's native build puts a Content-Security-Policy in its `index.html`: scripts from `'self'` only (inline ones by hash), connections to `'self'` and any `https:` or `wss:` origin, `https:` images, media, fonts, stylesheets and frames, inline styles, and the plain-`http:` origins the build knows of (an `http:` `AKAN_PUBLIC_SERVER_URL`, `http://localhost:*` in local mode, the carried server's loopback port). The policy also sets `form-action 'self'`, `base-uri 'self'` and `object-src 'none'`, so a form that posts to another origin (an outside payment page, say) no longer submits, and `<object>`/`<embed>` content does not load; add the origin in a map (`{ "form-action": ["https://pay.example.com"] }`). A directive map in `native.csp` adds sources to that policy (`{ "script-src": ["https://cdn.example.com"] }`), a string replaces it, and `native.csp: false` ships no policy, as before; `native.csp: ""` is a build error. In a map, directive names ignore case, a `default-src` entry is added to every fetch directive of the policy but `object-src`, and a directive the policy lacks starts from the one CSP falls back to (`script-src-elem` from `script-src`, `manifest-src` from `default-src`). akan-native hashes inline scripts and styles into `script-src-elem` and `style-src-elem` too when a policy has them.
|
|
242
|
+
- **External links open `https:`, `mailto:` and `tel:` by default.** `opener.openUrl`, and the links and `window.open` calls the iOS, Android and desktop shells hand to the system, open only URLs that the app's `opener` permissions allow: plain `http:` links are not opened unless the app lists them, and an akan-native app whose `capabilities` grant no `opener` hands no link to the system. `native.openUrls` names the URL patterns; `native.openUrls: ["*"]` restores the previous scope. When the app refuses a link this way, `Link` and the router no longer retry it with `window.open`.
|
|
243
|
+
- **Private temporary folders on Linux.** `file-picker` copies, the `filesystem` plugin's `temp` folder and `external-view` captures live under `$XDG_CACHE_HOME/<appId>/tmp` (`~/.cache/<appId>/tmp` when unset), in folders only the user can open, instead of `/tmp/<appId>`. Unlike `/tmp`, nothing empties it at a restart: `file-picker` and `external-view` empty their own folders at launch, and a file an app writes to `temp` stays until the app removes it. macOS and Windows keep their per-user temporary folder. Set `XDG_CACHE_HOME` to move it.
|
|
244
|
+
- **single-instance on Linux without `XDG_RUNTIME_DIR`.** The socket and lock move into a per-user `akan-native-<uid>` folder that only the user can open, and an existing folder another user owns or others can read is not used. A private `XDG_RUNTIME_DIR` is used as before.
|
|
245
|
+
- **A downloaded desktop release is checked again before it is installed.** `updates.apply()` repeats the code-signature check (macOS) and the signer check (Windows) on the staged bundle. The install at quit installs a bundle only once that check passed in this run, outside the time the quit hooks share: as the release was downloaded or the app started, and again when a quit is asked for (Cmd+Q, the last window closed, a logout), which waits up to 10 s for it. A bundle that no longer passes is removed and has to be downloaded again; one whose check has not finished stays for the next quit.
|
|
246
|
+
- **`updates.windowsSigners`** (new, Windows): certificate thumbprints a release's executable must be signed by. Unset keeps the existing check that a release is signed by the same certificate as the running app.
|
|
247
|
+
- **`updates.expiresIn`** (new): `akan publish-update` signs an `expiresAt` that many seconds ahead (at least 3600) into the release manifest, and desktop, iOS and Android apps refuse a manifest past its `expiresAt`. Unset publishes no expiry, and a manifest without one is accepted as before.
|
|
248
|
+
- **`desktop.screenCapture: "auto"` on Windows denies camera and microphone requests**, which the setting would otherwise grant without asking. `"picker"`, the default, keeps the usual prompts.
|
|
249
|
+
- **`native.csp`, `native.openUrls`, `updates.windowsSigners` and `updates.expiresIn` are typed in `akan.config.ts`**, and `csp` and `openUrls` given in the `native` section reach the native build, as a target's own did.
|
|
250
|
+
- **A release build with `devtools` on prints a warning**, since the web inspector then reaches every plugin the page can call.
|
|
251
|
+
- **Paths with a `.` or `..` segment are answered by the app shell** on every platform, literal or percent-encoded, and a navigation to one is not loaded as a page, as `/__akan_native/*` paths already were.
|
|
252
|
+
- **A public env value holding a private key is refused** when the native build records its `AKAN_PUBLIC_*` values, as names with `SECRET`, `TOKEN`, `PASSWORD`, `PRIVATE` or `CREDENTIAL` already were.
|
|
253
|
+
- **akan-native only warns about `'unsafe-inline'` in `script-src`.** Inline styles under `style-src 'unsafe-inline'` no longer produce a warning; inline scripts are still left unhashed when `script-src` allows them.
|
|
254
|
+
|
|
255
|
+
- 5e08722: A coding agent now finds the imports and signatures a first app needs in its skills, instead of reading the akanjs sources.
|
|
256
|
+
|
|
257
|
+
- **The `akan-service` and `akan-page` recipes** start each file with its import lines, and the `akan-service` rules say what `serverMode: "batch"` does: the module, endpoints included, mounts only in a `batch` or `all` process, so a page-facing service leaves it off.
|
|
258
|
+
- **A new `akanApi` guideline** lists every import path and the exact signatures of the route stages, `router`, `Link`, `serve`, `adapt`, the endpoint builders, `SignalOption`, `serviceDictionary`, and the keys of `akan.config.ts` with the `web` and `i18n` shapes. It is generated from the akanjs types of the same release, and a test fails when the two drift apart. `akan agent install` ships it as a reference of both skills, and `get_guideline` / `akan guideline show akanApi` print it.
|
|
259
|
+
- **`ServiceOptions.serverMode`** carries a doc comment, so an editor shows the same explanation on hover.
|
|
260
|
+
|
|
261
|
+
- 58f1c64: A route's client code no longer carries the UI of every module in the app and in each lib its store merges.
|
|
262
|
+
|
|
263
|
+
- **Bootstrap.** Each route's `"use client"` entry, every generated root layout and the SSR dev registry start with `import "@apps/<app>/lib/useClient"` and `import "@apps/<app>/lib/st"`, which load the client runtime and the store. They used to import `@apps/<app>/client` bare, and that bundled the whole barrel.
|
|
264
|
+
- **Narrowed barrel imports.** A named import from an app or lib `client` barrel is still narrowed to the files that define the names. This includes `import { st } from "@apps/<app>/client"` in a component and the generated `import { RootStore as <lib> } from "@libs/<lib>/client"` in `lib/st.ts`.
|
|
265
|
+
- What the rewrite keeps of the barrel is now the files that define its `usePage` and `st`, normally `lib/useClient` and `lib/st`. It used to keep a bare import of the barrel itself, which put every module's UI of the app and of each merged lib on every route.
|
|
266
|
+
- A barrel that defines neither name keeps its bare import, as before.
|
|
267
|
+
- **Namespace re-exports.** A name the barrel re-exports as a namespace (`export * as cnst`, `export * as store`) is now narrowed too. `import { cnst } from "@libs/<lib>/client"` becomes `import * as cnst from "@libs/<lib>/lib/cnst"`, where it used to load the whole barrel.
|
|
268
|
+
- **Discovery and Tailwind.** Client entry discovery and Tailwind's candidate scan follow the same imports. A lib `"use client"` component is no longer a client entry of every route, and a module UI file that no route imports is no longer scanned.
|
|
269
|
+
- **Size.** Production build of `apps/minimal`:
|
|
270
|
+
- A route that renders only `Link` ships 53.1K raw / 17.2K br of route code, down from 226.6K / 67.1K.
|
|
271
|
+
- With `libs/shared` merged into the store, the `Link` route ships 147.0K / 37.9K, down from 645.6K / 172.8K. A route rendering shared's `Field` ships 416.0K / 113.9K, down from 645.6K / 172.5K. A route rendering shared's `Banner.Zone` ships 454.3K / 123.2K, down from 658.9K / 175.7K.
|
|
272
|
+
- A route still carries whatever its UI imports. On minimal, whose only module is `memo`, the `Memo.Zone` route stays at 243.8K / 72K.
|
|
273
|
+
- **Evaluation order.** A module UI file now runs only on the pages that import it, and only when an import reaches it. It used to run up front, in barrel order, as soon as anything imported the barrel.
|
|
274
|
+
- A UI file whose top level registers something or writes a global must be imported where that side effect is needed.
|
|
275
|
+
- A UI file that reads another UI file's export at its top level, through an import cycle, can now find it uninitialised and throw a TDZ error. Move that read into the component.
|
|
276
|
+
- Generated module `index.ts` files only collect namespaces, so they are unaffected.
|
|
277
|
+
|
|
278
|
+
- bd4d45a: `akan verify` now fixes or explains three lint failures that were costly to resolve by hand.
|
|
279
|
+
|
|
280
|
+
- **A daisyUI component class your own CSS defines is no longer refused.**
|
|
281
|
+
- `no-daisyui-legacy-class` reports a component name such as `stat-value` or `card-body` as a warning, since the rule cannot read the app's CSS. `akan verify` drops the warning when a stylesheet in the app or a lib defines that class, as a selector or an `@utility`.
|
|
282
|
+
- A dropped daisyUI colour slot such as `bg-base-100` is still an error. In a string that carries both, the colour-slot error is reported first, so the warning cannot hide it.
|
|
283
|
+
- **`akan verify` renames `#private` methods in `*.{constant,document,service,store}.ts`.**
|
|
284
|
+
- `#name()` becomes `private name()`, and each call or other reference to it is renamed to match.
|
|
285
|
+
- A method is left to the diagnostic when its class already has a member with that name, or when a `#name in obj` check reads it.
|
|
286
|
+
- `#private` fields are not touched. A call on one, such as `this.#cache.get(key)`, is no longer reported as a private method call.
|
|
287
|
+
- The rule no longer suggests an underscore prefix: a service runs `_preCreate`-style names as lifecycle hooks.
|
|
288
|
+
- The rule's own rewrite is removed. Under `--unsafe` it renamed a `#field` where it was called but not where it was declared.
|
|
289
|
+
- **A raw `Error` in an `srvkit/` adapter names its fix.** `akan verify` prints the `Err` import path from that file, and where the key goes: the app's or lib's own service dictionary, `lib/_<name>/<name>.dictionary.ts`, as `<name>.error.<key>`.
|
|
290
|
+
|
|
291
|
+
- adcdf3b: `akan workflow plan` and the `plan_workflow` MCP tool refuse a module the target app or library lacks, for every workflow that writes into an existing module (`add-field`, `add-enum-field`, `add-mutation`, `add-slice`, `create-ui`, `rename-key`). The `workflow-module-missing` error names the module, lists the modules the target has, and points at the module whose name differs only in case or by a leading `_`, at a scalar of that name, or at the app or library that holds it; only when none exists does it point at `create-module`. Before, the plan passed and its dry run reported `passed`. `add-mutation` used to plan a missing module with a warning so that a `create-module` plan could precede it; it now refuses too, so plan it after applying `create-module`. Every workflow now refuses an unknown `app` at plan time, listing the workspace's apps and libraries. `apply` and `--dry-run` check the target again before anything else, so a plan written earlier for a module that is gone fails instead of passing. `plan_workflow` now inspects the MCP server's workspace rather than the process directory.
|
|
292
|
+
- Updated dependencies [bc792ab]
|
|
293
|
+
- Updated dependencies [61fbd7d]
|
|
294
|
+
- Updated dependencies [b3f85fa]
|
|
295
|
+
- Updated dependencies [a707354]
|
|
296
|
+
- Updated dependencies [0351ad4]
|
|
297
|
+
- Updated dependencies [ba417c6]
|
|
298
|
+
- Updated dependencies [bb40785]
|
|
299
|
+
- Updated dependencies [d2bd9b7]
|
|
300
|
+
- Updated dependencies [b391491]
|
|
301
|
+
- Updated dependencies [2d357d6]
|
|
302
|
+
- Updated dependencies [039ad3b]
|
|
303
|
+
- Updated dependencies [69ed07e]
|
|
304
|
+
- Updated dependencies [29ef72f]
|
|
305
|
+
- Updated dependencies [7c85c42]
|
|
306
|
+
- Updated dependencies [ddd3d96]
|
|
307
|
+
- Updated dependencies [3f231a7]
|
|
308
|
+
- Updated dependencies [96f9ff6]
|
|
309
|
+
- Updated dependencies [1ebc57a]
|
|
310
|
+
- Updated dependencies [c3b5b3c]
|
|
311
|
+
- Updated dependencies [6d5b544]
|
|
312
|
+
- Updated dependencies [958f175]
|
|
313
|
+
- Updated dependencies [9f30e93]
|
|
314
|
+
- Updated dependencies [d9eaacc]
|
|
315
|
+
- Updated dependencies [d9eaacc]
|
|
316
|
+
- Updated dependencies [c334caf]
|
|
317
|
+
- Updated dependencies [e143263]
|
|
318
|
+
- Updated dependencies [e4f7876]
|
|
319
|
+
- Updated dependencies [cb0fd5b]
|
|
320
|
+
- Updated dependencies [c48d780]
|
|
321
|
+
- Updated dependencies [3537448]
|
|
322
|
+
- Updated dependencies [c882491]
|
|
323
|
+
- Updated dependencies [f3def22]
|
|
324
|
+
- Updated dependencies [8424863]
|
|
325
|
+
- Updated dependencies [bf28968]
|
|
326
|
+
- Updated dependencies [9b5bcee]
|
|
327
|
+
- Updated dependencies [4561152]
|
|
328
|
+
- Updated dependencies [73e833b]
|
|
329
|
+
- Updated dependencies [b24fef3]
|
|
330
|
+
- Updated dependencies [5cd1fc3]
|
|
331
|
+
- Updated dependencies [f47cc82]
|
|
332
|
+
- Updated dependencies [1f93507]
|
|
333
|
+
- Updated dependencies [5d3cb3e]
|
|
334
|
+
- Updated dependencies [9a9e1fa]
|
|
335
|
+
- Updated dependencies [3537448]
|
|
336
|
+
- Updated dependencies [6c3a303]
|
|
337
|
+
- Updated dependencies [27440aa]
|
|
338
|
+
- Updated dependencies [e3de9ef]
|
|
339
|
+
- Updated dependencies [e3de9ef]
|
|
340
|
+
- Updated dependencies [e3de9ef]
|
|
341
|
+
- Updated dependencies [e3de9ef]
|
|
342
|
+
- Updated dependencies [e3de9ef]
|
|
343
|
+
- Updated dependencies [e3de9ef]
|
|
344
|
+
- Updated dependencies [e3de9ef]
|
|
345
|
+
- Updated dependencies [99fda20]
|
|
346
|
+
- Updated dependencies [5e08722]
|
|
347
|
+
- Updated dependencies [df32c46]
|
|
348
|
+
- Updated dependencies [5f809e8]
|
|
349
|
+
- Updated dependencies [206bb69]
|
|
350
|
+
- Updated dependencies [6291fec]
|
|
351
|
+
- Updated dependencies [0c480c6]
|
|
352
|
+
- Updated dependencies [d9eaacc]
|
|
353
|
+
- akanjs@3.2.0
|
|
354
|
+
- @akanjs/language@3.2.0
|
|
355
|
+
|
|
3
356
|
## 3.1.1
|
|
4
357
|
|
|
5
358
|
### Patch Changes
|
package/abstractDoc.ts
CHANGED
|
@@ -14,7 +14,7 @@ export class AbstractDoc {
|
|
|
14
14
|
return filePath.endsWith(AbstractDoc.suffix);
|
|
15
15
|
}
|
|
16
16
|
static lineCountOf(content: string) {
|
|
17
|
-
return content.split(/\r?\n/).length;
|
|
17
|
+
return content.split(/\r?\n/).length - (content.endsWith("\n") ? 1 : 0);
|
|
18
18
|
}
|
|
19
19
|
static kindOf(filePath: string): AbstractKind {
|
|
20
20
|
const [root, folder, ...rest] = filePath.split("/");
|
package/agentAssets.ts
CHANGED
|
@@ -31,7 +31,7 @@ export class AgentAssets {
|
|
|
31
31
|
"akan-scaffold": ["workspaceRecipes"],
|
|
32
32
|
"akan-module": ["moduleOverview", "modelConstant", "modelDictionary", "fieldRule"],
|
|
33
33
|
"akan-scalar": ["scalarModule", "scalarConstant", "scalarDictionary"],
|
|
34
|
-
"akan-service": ["modelService", "modelSignal", "modelDocument"],
|
|
34
|
+
"akan-service": ["modelService", "modelSignal", "modelDocument", "akanApi"],
|
|
35
35
|
"akan-store": ["modelStore"],
|
|
36
36
|
"akan-ui": [
|
|
37
37
|
"ssrRule",
|
|
@@ -48,7 +48,7 @@ export class AgentAssets {
|
|
|
48
48
|
"akan-err": [],
|
|
49
49
|
"akan-deps": [],
|
|
50
50
|
"akan-debug": [],
|
|
51
|
-
"akan-page": ["pageRule"],
|
|
51
|
+
"akan-page": ["pageRule", "akanApi"],
|
|
52
52
|
"akan-query": ["queryRule"],
|
|
53
53
|
"akan-transport": ["transportRule"],
|
|
54
54
|
"akan-runtime": ["runtimeRule"],
|
package/akanConfig/akanConfig.ts
CHANGED
|
@@ -638,10 +638,10 @@ export class AkanAppConfig implements AppConfigResult {
|
|
|
638
638
|
...(this.web.csr ? {} : { AKAN_CSR: "false" }),
|
|
639
639
|
};
|
|
640
640
|
}
|
|
641
|
-
//? An image runs where the build cannot see, and AKAN_PUBLIC_ENV=local
|
|
641
|
+
//? An image runs where the build cannot see, and AKAN_PUBLIC_ENV=local turns off its rate limits and boot checks there.
|
|
642
642
|
static readonly localImageGuard = [
|
|
643
643
|
"ARG AKAN_ALLOW_LOCAL_IMAGE",
|
|
644
|
-
`RUN if [ "$AKAN_ALLOW_LOCAL_IMAGE" != "1" ]; then echo "This build ran with AKAN_PUBLIC_ENV=local,
|
|
644
|
+
`RUN if [ "$AKAN_ALLOW_LOCAL_IMAGE" != "1" ]; then echo "This build ran with AKAN_PUBLIC_ENV=local, so the server runs without endpoint rate limits and on local defaults for Redis and the database mode. Build for the environment it deploys to with akan build --env, or pass --build-arg AKAN_ALLOW_LOCAL_IMAGE=1." >&2; exit 1; fi`,
|
|
645
645
|
].join("\n");
|
|
646
646
|
dockerfileFor(environment: string, { lockfile = true }: { lockfile?: boolean } = {}): string {
|
|
647
647
|
return this.#makeDockerfile(environment, lockfile);
|
package/akanMcpContract.ts
CHANGED
|
@@ -324,7 +324,7 @@ export const readonlyMcpTools: McpToolDefinition[] = [
|
|
|
324
324
|
{
|
|
325
325
|
name: "get_graph",
|
|
326
326
|
description:
|
|
327
|
-
"Read-only Akan Graph from source, no dev server needed: models with their fields and relations, slices and endpoints with guards, the service methods and queries each calls, the store actions that fetch them, and the components and
|
|
327
|
+
"Read-only Akan Graph from source, no dev server needed: models with their fields and relations, slices and endpoints with the guards they name (each guard keyed by the app or lib that declares it, with its scope and the comment that says what it checks), the service methods and queries each calls, the store actions that fetch them, and the components and routes that use them. A module's jobs (internal: interval, cron, timeout, initialize, destroy, process) read their schedule and server or operation modes and call service methods like endpoints do; each adaptor a service plugs (an adapt() class, or an akanjs role) is a node, a method that calls it links to it, and an adaptor links to the adaptors it plugs itself. Each page/ file is a route (its URL pattern after the locale) or a layout, layouts wrap what they contain, and a route or layout that runs .guard() reads `guard` and links each function its guard calls; a layout's guard gates every route it wraps. A graph too large to answer unscoped drops first the routes and layouts that neither changed nor use a change; app or module brings them back. Every node and edge carries its source location. Pass app and/or module to scope a large workspace. Pass since (a git revision such as main, or HEAD for uncommitted work) for the Change Map instead: what changed since HEAD forked from it, working tree included, with each node, field and edge marked added, removed or changed, the unchanged neighbours they call or are called by, and the changed files no node covers, each marked the same way.",
|
|
328
328
|
inputSchema: { type: "object", properties: { app: stringProperty, module: stringProperty, since: stringProperty } },
|
|
329
329
|
},
|
|
330
330
|
{
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { AgentAssets } from "./agentAssets";
|
|
4
|
+
import { ApiReference } from "./apiReference";
|
|
5
|
+
|
|
6
|
+
const akanjsDir = path.resolve(import.meta.dir, "../../akanjs");
|
|
7
|
+
const target = ApiReference.target(path.resolve(import.meta.dir, "../cli"));
|
|
8
|
+
|
|
9
|
+
describe("ApiReference", () => {
|
|
10
|
+
const rendered = ApiReference.render(akanjsDir);
|
|
11
|
+
|
|
12
|
+
test("the committed guideline matches the akanjs sources; regenerate with bun pkgs/@akanjs/devkit/apiReference.ts", async () => {
|
|
13
|
+
expect(await Bun.file(target).text()).toBe(rendered);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("fits one skill reference part", () => {
|
|
17
|
+
expect(AgentAssets.split(rendered)).toHaveLength(1);
|
|
18
|
+
expect(rendered.length).toBeLessThanOrEqual(AgentAssets.referenceLimit);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("prints builder parameters, the import block and documented options", () => {
|
|
22
|
+
expect(rendered).toContain('import { adapt, serve } from "akanjs/service";');
|
|
23
|
+
expect(rendered).toContain('import { layout, page, rootLayout, router } from "akanjs/client";');
|
|
24
|
+
expect(rendered).toMatch(/^query<.+>\(returnRef: Returns, signalOption\?: SignalOption<Returns, Nullable>\)$/m);
|
|
25
|
+
expect(rendered).toMatch(/^\.param<.+>\(name: .+, type: Type, option\?: RouteArgOption\)$/m);
|
|
26
|
+
expect(rendered).toMatch(/^\.render\(render: \(args: RouteBaseArgs & LayoutExtra\) => .+\)$/m);
|
|
27
|
+
expect(rendered).toMatch(/children: ReactNode/);
|
|
28
|
+
expect(rendered).toMatch(/^\/\*\* .+ \*\/\nserverMode\?: "batch" \| "federation";$/m);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("lists the keys of akan.config.ts, so the web and i18n shapes have a field to belong to", () => {
|
|
32
|
+
const appConfig = rendered.slice(rendered.indexOf("### `AppConfigInput`"));
|
|
33
|
+
expect(appConfig).toMatch(/^web\?: AkanWebOption;$/m);
|
|
34
|
+
expect(appConfig).toMatch(/^i18n\?: DeepPartial<AkanI18nConfig>;$/m);
|
|
35
|
+
expect(appConfig.indexOf("### `AkanWebOption`")).toBeGreaterThan(appConfig.indexOf("web?: AkanWebOption;"));
|
|
36
|
+
});
|
|
37
|
+
});
|