@akanjs/cli 3.0.0-beta.18 → 3.0.0-beta.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.build-stamp +1 -1
- package/{CodeAgent-0035exh3.js → CodeAgent-6cm97kay.js} +2 -2
- package/{CodeAgentRpcHost-qvwfv0n9.js → CodeAgentRpcHost-p7h6n2hp.js} +1 -1
- package/CodeAgentRpcListener-7p7z5dd7.js +97 -0
- package/{CodeTui-eweg1hep.js → CodeTui-za4jsm41.js} +1 -1
- package/{TunnelShare-r5tc2qk5.js → TunnelShare-2j6s511d.js} +2 -2
- package/{agent.command-65zqfkg0.js → agent.command-e0wpbbyg.js} +5 -5
- package/akanCode.js +4 -4
- package/{application.command-2wcj5da3.js → application.command-8wyxqc8q.js} +15 -9
- package/{applicationBuildRunner-m0hhgwsx.js → applicationBuildRunner-n9gzaxd0.js} +31 -7
- package/{applicationReleasePackager-vykfzgx3.js → applicationReleasePackager-vpggfqgq.js} +1 -1
- package/buildBatch.proc.js +3 -3
- package/{capacitorApp-2daqhb5h.js → capacitorApp-q3z0qht0.js} +2 -2
- package/{cloud.command-960bgbf4.js → cloud.command-4zwa5dsx.js} +34 -12
- package/cloud.script-fwx3vb34.js +20 -0
- package/{code.command-4feh3ef7.js → code.command-468c9d7s.js} +41 -19
- package/commandManifest.json +1 -1
- package/{context.command-kzehkx48.js → context.command-dsnw0jby.js} +13 -13
- package/context.runner-rs4rj15d.js +22 -0
- package/{dependencyScanner-shs82ynp.js → dependencyScanner-0z2h2tks.js} +1 -1
- package/{guideline.command-3w2g20x4.js → guideline.command-vhw13pqr.js} +2 -2
- package/guidelines/agentRule/agentRule.instruction.md +27 -10
- package/guidelines/conventions/conventions.instruction.md +16 -4
- package/guidelines/queryRule/queryRule.instruction.md +16 -6
- package/guidelines/runtimeRule/runtimeRule.instruction.md +74 -0
- package/incrementalBuilder.proc.js +3 -2
- package/{index-dw943k93.js → index-10dkrrww.js} +4 -4
- package/{index-rpgxs245.js → index-18zybgkc.js} +2 -2
- package/{index-mt2154s2.js → index-22xtgmhn.js} +1 -1
- package/{index-yw6g3bbv.js → index-2446k7b4.js} +1 -1
- package/{index-cjw1sj1x.js → index-3jrp517k.js} +10 -10
- package/{index-q9c3c0hy.js → index-4m929vvn.js} +2 -2
- package/{index-ef228gzf.js → index-53176s7j.js} +3 -3
- package/{index-jwrpsrg3.js → index-667w0yah.js} +1 -1
- package/{index-6q589y9t.js → index-7ep8qtvp.js} +2 -2
- package/{index-w8kmx1b6.js → index-89hy31kf.js} +30 -7
- package/{index-jd01kj5r.js → index-9d6nvkz2.js} +1 -1
- package/{index-zq0mr3hh.js → index-a1w6934z.js} +3 -3
- package/{index-sbfk9daf.js → index-ab0c38ea.js} +4 -4
- package/index-bc88j1ry.js +1166 -0
- package/{index-h94faz96.js → index-bftkz6cp.js} +274 -60
- package/{index-kcxmv48c.js → index-bhtmj233.js} +4 -1
- package/{index-m41ptvat.js → index-bwf965h6.js} +3 -3
- package/{index-bwxpk2cj.js → index-crmeqnmf.js} +52 -26
- package/{index-8k16atqy.js → index-d2242ksy.js} +23 -6
- package/{index-jwexzh42.js → index-e4v6vhxq.js} +222 -56
- package/{index-pn8mhpps.js → index-esk82rvk.js} +4 -4
- package/{index-4r42ext5.js → index-kwaq3n9n.js} +1 -1
- package/{index-gm76f0qb.js → index-n3cv0tm4.js} +1 -1
- package/{index-ycy302an.js → index-qdgzsnhg.js} +1 -1
- package/{index-wcg582a8.js → index-sq2dzkzm.js} +35 -10
- package/{index-myj01w9r.js → index-srmay5jw.js} +15 -2
- package/{index-zmj143x7.js → index-txr7dj2g.js} +3 -3
- package/{index-erfc1x40.js → index-vqx8bdxz.js} +1 -1
- package/{index-d9fjz06h.js → index-zqgj2rt2.js} +4 -4
- package/index.js +21 -21
- package/{library.command-fp0nhr62.js → library.command-cqr175dy.js} +3 -3
- package/{localRegistry.command-8y0ecxb9.js → localRegistry.command-49z55qqz.js} +9 -9
- package/{module.command-kqkn48t2.js → module.command-9p8mm3b0.js} +5 -5
- package/{package.command-qverjh22.js → package.command-wxyz909t.js} +4 -4
- package/package.json +2 -2
- package/{page.command-94bwhd0g.js → page.command-gsrsfqqs.js} +3 -3
- package/{primitive.command-2ydvtah2.js → primitive.command-ns1mptje.js} +6 -6
- package/{quality.command-fa0gth4p.js → quality.command-e76k2q9k.js} +17 -15
- package/{repair.command-4qng2hpm.js → repair.command-pkrsm3cw.js} +5 -5
- package/{scalar.command-pb2y95sh.js → scalar.command-0cpt3fc8.js} +4 -4
- package/{subspace.command-vkxmwp74.js → subspace.command-fktpwp0g.js} +20 -10
- package/templates/appSample/lib/task/task.signal.test.ts +1 -1
- package/templates/localDev/docker-compose.yaml.template +3 -14
- package/{tunnel.command-02cvp9nf.js → tunnel.command-d5jy9gk8.js} +4 -4
- package/{workflow.command-132txnrh.js → workflow.command-6tkcysmx.js} +9 -9
- package/{workspace.command-d5wkswek.js → workspace.command-gv84eh1p.js} +24 -23
- package/cloud.script-qhcnqt1n.js +0 -20
- package/context.runner-z0rrcz7b.js +0 -22
- package/index-3hpxeys1.js +0 -462
|
@@ -96,15 +96,21 @@ apps and libs never import it directly (`no-import-external-library`) — everyt
|
|
|
96
96
|
nothing about, and it stays text-only until `option.setLlm({ accepts })` says otherwise — handing bytes to a
|
|
97
97
|
model that cannot decode them kills the whole turn on a 400, where text-only degrades them to a note the model
|
|
98
98
|
can repeat back.
|
|
99
|
-
- **A refusal is
|
|
100
|
-
rides in `reason` and the hostname in `provider
|
|
101
|
-
|
|
102
|
-
|
|
99
|
+
- **A refusal is `agent.error.llmRequestFailed`, and it names the host** — unless the prompt did not fit. The
|
|
100
|
+
provider's own sentence rides in `reason` and the hostname in `provider`. `LlmOverflow.refusal(host, status,
|
|
101
|
+
reason)` builds it, and reads the one refusal a chat can undo — a prompt past the window, recognized from each
|
|
102
|
+
provider's own sentence — as `agent.error.contextOverflow` with the `limit` it named, which the relay flags so
|
|
103
|
+
the chat compacts and asks again. An adaptor an app wrote throws through the same function, so its refusals
|
|
104
|
+
translate and recover the way the shipped ones do without a key in the framework's dictionary it cannot add.
|
|
103
105
|
- **`accepts` is answered per model, through `option.setLlm({ accepts })`.** An adaptor answers for an API and one
|
|
104
106
|
API serves models that differ, so the override rides beside the `model` it is a fact about. It is not a table
|
|
105
107
|
the framework keeps: a table is a claim about models that ship after it, and getting this wrong is the worst
|
|
106
108
|
failure available — a provider handed bytes it cannot decode either refuses the turn or accepts it having seen
|
|
107
109
|
nothing, and the model then answers confidently about a file it never read.
|
|
110
|
+
- **`contextWindow` is declared the same way: `option.setLlm({ contextWindow })`.** Every turn relays it with the
|
|
111
|
+
answer ceiling the adaptor actually sends (`LlmAdaptor.limits` — `AnthropicLlm` always sends one, `OpenaiLlm`
|
|
112
|
+
none), and the chat guards its window with it. It is not a table either; a model id is retired or renamed long
|
|
113
|
+
before a table notices.
|
|
108
114
|
- **An adaptor answers `null` for "not configured" and *throws* for a refusal it can explain.** The two are
|
|
109
115
|
different things to be told: collapsing both into `null`, the way the adapter convention otherwise reads, left a
|
|
110
116
|
user reading `llmUnavailable` — "no model is configured" — about a conversation that had merely outgrown the
|
|
@@ -539,18 +545,29 @@ apps and libs never import it directly (`no-import-external-library`) — everyt
|
|
|
539
545
|
- **A long conversation summarizes itself, because nothing else is keeping it inside the model's window.** The
|
|
540
546
|
loop runs in the browser and the relay holds no session, so an uncompacted chat grows until the provider
|
|
541
547
|
refuses the whole request — a refusal, never a shorter answer, which is why compaction runs *before* the turn
|
|
542
|
-
that would have overflowed
|
|
543
|
-
characters to a token, over the JSON the turn posts
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
548
|
+
that would have overflowed. **Two triggers, each before every assistant turn.** `compact.at` is a ceiling on
|
|
549
|
+
cost: past that many estimated transcript tokens (four characters to a token, over the JSON the turn posts —
|
|
550
|
+
except an inlined picture, counted at the ~1,600 tokens a provider bills for its pixels rather than its base64;
|
|
551
|
+
24k by default) the history compacts, because the relay resends the whole transcript every turn and the app
|
|
552
|
+
pays for each one. The **window guard** compacts once the prompt nears the window the server reports —
|
|
553
|
+
`window − answer ceiling (8,192 when unreported) − compact.buffer (13,000, the margin Claude Code leaves)` —
|
|
554
|
+
measured by the provider's own count of the last turn (`done.usage`, kept on that assistant message) plus the
|
|
555
|
+
four-character estimate for what arrived since, so a Korean transcript the character rule reads as small is
|
|
556
|
+
still caught. The window comes from `option.setLlm({ contextWindow })` or from the first refusal that names it;
|
|
557
|
+
with neither, only the ceiling runs. **A refusal for length is answered once**: a relay flags it `overflow` on
|
|
558
|
+
the wire, and the session compacts and sends the same turn again, and a second refusal in the same send fails
|
|
559
|
+
with `agent.error.contextOverflow`. Either way the history above the last `keep` messages
|
|
560
|
+
becomes one message standing in for it, flagged `summary` on the wire — `<Agent.Chat compact={{ at, keep, buffer }} />`
|
|
561
|
+
tunes it, `{ at: Infinity }` leaves only the window guard, `{ at: 0 }` turns off the ceiling, the guard and the recovery alike, and `/compact` does
|
|
562
|
+
the same on demand keeping nothing. The header draws the estimate against whichever trigger is nearer.
|
|
547
563
|
**The cut never lands on a `tool` message**: the kept half may not open with a result whose call was summarized
|
|
548
564
|
away, a shape every provider dialect rejects. A user message is preferred where the tail holds one — everything
|
|
549
565
|
above it is settled — but one assistant turn that ran ten tools has none, and that is the transcript that
|
|
550
566
|
outgrows the window, so a cut there opens on the assistant message instead. The summarizing
|
|
551
567
|
turn carries no tools and no screen context — it summarizes the conversation, it does not act on it — and it is
|
|
552
568
|
fed a *bounded* digest rather than the transcript itself, since the transcript being summarized is the one that
|
|
553
|
-
no longer fits.
|
|
569
|
+
no longer fits. The previous summary is the exception: it rides the digest whole, under its own label and outside
|
|
570
|
+
the bound, because it is the only record of what was compacted before it. A summary that cannot be produced leaves the transcript alone, the turn goes out as it would
|
|
554
571
|
have, and the next one asks again — a summarizer that was momentarily unreachable says nothing about whether
|
|
555
572
|
this transcript can shrink. One that *landed* and shrank nothing does, so that one is not retried until another
|
|
556
573
|
threshold's worth has been added. On the
|
|
@@ -28,7 +28,7 @@ you fetch on demand — `get_guideline` with the name, or `akan guideline show <
|
|
|
28
28
|
| name | covers |
|
|
29
29
|
|---|---|
|
|
30
30
|
| `ssrRule` | server-share targets, the `akan.ssr.*` warnings, the client-boundary playbook |
|
|
31
|
-
| `runtimeRule` | `web` / `csr` surfaces, gateway vs solo processes, logging, the generated image, shipped assets |
|
|
31
|
+
| `runtimeRule` | `web` / `csr` surfaces, gateway vs solo processes, logging, the generated image, shipped assets, database modes |
|
|
32
32
|
| `queryRule` | slices and hydration, the generated filter methods, full-text search, cascade removal |
|
|
33
33
|
| `transportRule` | guards across HTTP and websocket, socket identity and cleanup, binary pubsub, mutation verbs |
|
|
34
34
|
| `mcpRule` | MCP configuration, wire behaviour, resource URIs, OAuth metadata, protocol revisions |
|
|
@@ -337,6 +337,14 @@ Full contract: `get_guideline` with `runtimeRule`, or `akan guideline show runti
|
|
|
337
337
|
- **`assets: { pruneFonts, keepFonts }`** trims from the `dist` copy of `public/` the fonts nothing reads; source
|
|
338
338
|
trees are never touched. A font with `optimize` on is a build input, not a runtime asset. `keepFonts` belongs to
|
|
339
339
|
the `akan.config.ts` that owns the font, written against that scope's own `public/`.
|
|
340
|
+
- **Database modes are `database: { modes: [...] }`** — `single` (SQLite + Solid), `multiple` (one SQLite file on
|
|
341
|
+
a host volume + Redis), `cluster` (Postgres + Redis). The build carries the drivers of every declared mode and a
|
|
342
|
+
deployment picks one with `AKAN_DATABASE_MODE` — a declared one, named whenever the build declares several.
|
|
343
|
+
Where the data lives (`POSTGRES_URL`, `REDIS_URI`, `SQLITE_DATABASE_PATH`) is the deployment's env, ahead of
|
|
344
|
+
`env.server.ts`. `q.raw` is written in the dialect's own SQL, so an app running in two modes avoids it.
|
|
345
|
+
- **Backups run through the image's own `main.js`**: `bun main.js ops snapshot` / `ops restore` (offline), and
|
|
346
|
+
`/_akan/ops/*` for a control plane, mounted only when `AKAN_OPS_PUBLIC_KEY` is set and authenticated by a short
|
|
347
|
+
EdDSA token that key verifies. `GET /_akan/app/info` is public. `operationMode` gates none of it.
|
|
340
348
|
|
|
341
349
|
## React Components And Styling (`**/*.tsx`)
|
|
342
350
|
|
|
@@ -359,7 +367,7 @@ Full contract: `get_guideline` with `runtimeRule`, or `akan guideline show runti
|
|
|
359
367
|
nothing — they covered five control types, keyed on the translated label, and restored over server data.
|
|
360
368
|
- Static class strings stay plain strings. Reach for `cn` only for a conditional or to merge an incoming `className`, and merge the caller last: `cn("base classes", cond && "extra", className)`. `cn` comes from `akanjs/client` (token-aware tailwind-merge) and is the only class-combining function — no `clsx` (removed), no raw `twMerge` imports, no object syntax (`{ x: cond }` → `cond && "x"`).
|
|
361
369
|
- Multi-slot components take extra named props (`wrapperClassName`, `bodyClassName`), never a `classNames` object.
|
|
362
|
-
- Hoist enum→class lookups to a module-scope `as const` map typed `{ [key in cnst.XStatus["value"]]: string }`. Do not use `Record<...>`.
|
|
370
|
+
- Hoist enum→class lookups to a module-scope `as const` map typed `{ [key in cnst.XStatus["value"]]: string }`. Do not use `Record<...>`. When a second module needs it, the classes become a variant axis of a recipe in `ui/Recipe/`; only a map from the enum to another recipe's variant name (`BadgeVariants["variant"]`) moves to `webkit/`.
|
|
363
371
|
- Use `<Link>` from `akanjs/ui` for internal navigation; `<a>` only for `mailto:` and external links.
|
|
364
372
|
|
|
365
373
|
## Naming And Language
|
|
@@ -793,7 +801,8 @@ Full contract — the trigger-maintained mirror, tokenizer changes, `AKAN_SEARCH
|
|
|
793
801
|
backstop, including for a `text` field *underneath* one of those. Do not work around either.
|
|
794
802
|
- The role works on a relation (`image: field(File, { text: "thumb" })`) and on an array; an array of objects
|
|
795
803
|
indexes by leaf key. A field inside a `Map` indexes nothing — there is no fixed path to extract it from.
|
|
796
|
-
- Search runs
|
|
804
|
+
- Search runs in every database mode — fts5 on SQLite/libSQL, a weighted `tsvector` (`pg_trgm` for `trigram`) on
|
|
805
|
+
Postgres. The same text matches the same documents; only the order may differ on Postgres.
|
|
797
806
|
|
|
798
807
|
### Image & File Fields
|
|
799
808
|
|
|
@@ -918,10 +927,13 @@ export default page()
|
|
|
918
927
|
| `common/` | pure, isomorphic, zero-dependency; may import only sibling `common/*` and `akanjs/base`. Cannot import `Err`, so keep throwing code out of it. | camelCase file, filename equals the single export |
|
|
919
928
|
| `webkit/` | touches `window` / `navigator` / Capacitor, or is a React hook | `use<Thing>.tsx` — `.tsx` even with no JSX |
|
|
920
929
|
| `srvkit/` | touches `node:*`, `Bun`, `process.env`, a secret, or a server SDK | camelCase file, PascalCase class |
|
|
921
|
-
| `ui/` | renders JSX and is not bound to one model | PascalCase component, camelCase sidecar (`swipeCard.util.ts`) |
|
|
930
|
+
| `ui/` | renders JSX, or defines a look (a recipe in `Recipe/`, a lib's `tokens.css`), and is not bound to one model | PascalCase component, camelCase sidecar (`swipeCard.util.ts`), `Recipe/<name>.ts` |
|
|
922
931
|
| `plugin/` | build- or CLI-time `AkanPlugin` | `<name>.plugin.ts`, registered in `akan.config.ts` |
|
|
923
932
|
|
|
924
933
|
- Hooks return a named object of async closures, never a tuple.
|
|
934
|
+
- `ui/` is the presentation layer — markup and the looks it is built from — so a recipe lives there although it is
|
|
935
|
+
neither a component nor a hook. Not `webkit/`: its hooks are `"use client"`, the opposite signal for a function
|
|
936
|
+
server components call. Not `common/`: it cannot import `akanjs/ui`, where `recipe` / `tv` come from.
|
|
925
937
|
- `libs/<lib>/ui/tokens.css` is the one CSS file a lib owns: plain `:root` custom properties for colors that must **not** follow the theme (a vendor brand color, a fixed surface). Every app whose pages reach that lib compiles it automatically, ahead of the app's own stylesheets, so nothing is imported by hand and no app can forget it. Reference them as `bg-[var(--kakao)]`; `@theme` extensions stay in the app stylesheet, because the color vocabulary is closed per stylesheet. Theme-following colors are the app's, not the lib's.
|
|
926
938
|
- A layer-root `index.ts` is generated, but a `ui/<Folder>/index.tsx` that builds a namespace is hand-written source. The distinguishing test is that a generated barrel contains nothing but `export * from "./X";` lines.
|
|
927
939
|
- `ui/<Folder>/index_.tsx` (trailing underscore) is the `"use client"` + `lazy()` boundary, with a server-safe `index.tsx` beside it. Collapsing the pair into one file breaks RSC.
|
|
@@ -90,14 +90,15 @@ with it. `conventions` carries the invariants — this is the full contract behi
|
|
|
90
90
|
- **A slice endpoint never reaches "unspecified".** The resolver fills `latest` before the query is built, so a
|
|
91
91
|
client asking for the score order has to name `relevance`; leaving `sort` off gets `latest`, not relevance.
|
|
92
92
|
- Scope a search with `columns` (`q.search(text, { columns: ["title"] })`) and re-weight with `weights`, a tuple of
|
|
93
|
-
finite numbers positional over `["title", "desc", "tag", "filter"]
|
|
93
|
+
finite, non-negative numbers positional over `["title", "desc", "tag", "filter"]` — Postgres ranks by weight
|
|
94
|
+
class and has no reading for a negative one.
|
|
94
95
|
|
|
95
96
|
|
|
96
97
|
## Text Search Fields — the `text` role
|
|
97
98
|
|
|
98
99
|
- A field joins the full-text index by declaring one of five roles: `field(String, { text: "title" })`, and likewise
|
|
99
100
|
`"desc"`, `"tag"`, `"thumb"`, `"filter"`. Nothing else opts a field in, and there is no per-model switch.
|
|
100
|
-
- Pick the role by what the value *is*, because
|
|
101
|
+
- Pick the role by what the value *is*, because the rank weights them positionally (`title` 10, `tag` 3, `desc` 1,
|
|
101
102
|
`filter` 0): `title` is the one line a human scans for, `desc` is prose, `tag` is a keyword list, `filter` is a
|
|
102
103
|
scoping value (status, owner, role) that must be matchable but must never outrank a real title hit.
|
|
103
104
|
- `thumb` is mirrored for rendering a hit and is **not** indexed — never expect it to match.
|
|
@@ -114,15 +115,24 @@ with it. `conventions` carries the invariants — this is the full contract behi
|
|
|
114
115
|
- Declaring roles is all the wiring there is. Mirror rows are maintained by SQL triggers — not document hooks —
|
|
115
116
|
because `updateOneByQuery` and friends fire no hooks, and most searchable-field mutations go through exactly that
|
|
116
117
|
path.
|
|
117
|
-
- Search runs on
|
|
118
|
+
- Search runs in every database mode: fts5 on SQLite/libSQL, a weighted `tsvector` under a GIN index on Postgres.
|
|
119
|
+
For the same text the same documents match — punctuation splits words the way unicode61 does, and a phrase never
|
|
120
|
+
runs from one column into the next — but the order may differ, since Postgres ranks without document frequency.
|
|
121
|
+
Assert on the match set, never on a Postgres order the weights alone do not decide.
|
|
122
|
+
- Postgres reads `AKAN_SEARCH_TOKENIZER` as `unicode61 [remove_diacritics 0|1|2]` (`simple`, with `unaccent` unless
|
|
123
|
+
`0`) or `trigram [case_sensitive 0|1]` (`pg_trgm`); any other spelling is refused. The extension is created when
|
|
124
|
+
the role may, otherwise a role that may runs `CREATE EXTENSION`. It indexes the first 20,000 characters of
|
|
125
|
+
`title`, `tag` and `filter` and 200,000 of `desc` — a tsvector refuses more than 1MB, and the refusal would fail
|
|
126
|
+
the model write. A database whose `LC_CTYPE` is `C` keeps non-ASCII case apart; use a UTF-8 ctype.
|
|
118
127
|
- `AKAN_SEARCH_ENABLED=0` switches the index off process-wide; unset means on. It never deletes mirror data, and
|
|
119
128
|
re-enabling reconciles every ref. **Give every process the same value** — a process cannot drop triggers for models
|
|
120
129
|
it does not mount, so a mixed fleet leaves stale triggers behind.
|
|
121
130
|
- The tokenizer is `AKAN_SEARCH_TOKENIZER` (or `database.search.tokenizer`, which wins), defaulting to
|
|
122
131
|
`unicode61 remove_diacritics 2`. Changing it rebuilds the index from the mirror on the next boot — the model
|
|
123
|
-
tables are never re-read — so it is a safe knob, unlike a `text` role change, which re-reads every row. The
|
|
124
|
-
|
|
125
|
-
|
|
132
|
+
tables are never re-read — so it is a safe knob, unlike a `text` role change, which re-reads every row. The schema
|
|
133
|
+
is ensured in one write turn — a SQLite write transaction, a Postgres schema lock — so of a fleet restarted at
|
|
134
|
+
once the first rebuilds and the rest find it current. A SQLite process waits for that turn up to its
|
|
135
|
+
`busy_timeout`, so stagger the restart when the mirror is large.
|
|
126
136
|
|
|
127
137
|
## Cascade Remove — the `cascade` option
|
|
128
138
|
|
|
@@ -184,6 +184,80 @@ const config: AppConfig = { docker: "FROM oven/bun:1-slim\n…" }; // verbatim,
|
|
|
184
184
|
- `AkanAppConfig.docker` is the resolved declaration; `AkanAppConfig.dockerfile` is the text `akan build` writes
|
|
185
185
|
to `dist/apps/<app>/Dockerfile`.
|
|
186
186
|
|
|
187
|
+
## Database Modes — `database` In `akan.config.ts`
|
|
188
|
+
|
|
189
|
+
| mode | database | cache · queue · pubsub | runs as |
|
|
190
|
+
|---|---|---|---|
|
|
191
|
+
| `single` | SQLite file | SQLite files (Solid) | one container |
|
|
192
|
+
| `multiple` | one SQLite file (WAL) on a host volume every container on that host opens | Redis | containers on one host |
|
|
193
|
+
| `cluster` | Postgres | Redis | pods on several servers |
|
|
194
|
+
|
|
195
|
+
- **`database: { modes: ["single", "cluster"] }` is what the build can run in**; the first is what `akan start`
|
|
196
|
+
runs. `akan build` carries the drivers of every declared mode (`multiple` → `bullmq`, `ioredis`; `cluster` adds
|
|
197
|
+
`postgres`).
|
|
198
|
+
- **A deployment names its mode with `AKAN_DATABASE_MODE`**, and only a declared one. With one declared mode it may
|
|
199
|
+
leave it out; with several it must name one — the fallback would be a SQLite file inside each pod. `akan start`,
|
|
200
|
+
`build`, `script` and `console` resolve it the same way from the shell. One image can serve an edge site in
|
|
201
|
+
`single` and a cloud cluster in `cluster`: declare both.
|
|
202
|
+
- **Where the data lives is the deployment's env, ahead of `env.server.ts`**: `SQLITE_DATABASE_PATH`,
|
|
203
|
+
`AKAN_SOLID_DB_PATH`, `POSTGRES_URL` (or `POSTGRES_HOST`/`PORT`/`DATABASE`/`USER`/`PASSWORD`),
|
|
204
|
+
`POSTGRES_INSIGHT_URL`, `LIBSQL_URL`. `REDIS_URI` is required outside local development. Pool size, SSL and
|
|
205
|
+
prepared statements ride the Postgres URL (`?max=20&ssl=require`, `prepare=false` behind PgBouncer).
|
|
206
|
+
- **Uploads**: a deployed `multiple`/`cluster` app uses object storage, or a volume every instance mounts with
|
|
207
|
+
`AKAN_STORAGE_SHARED=true`.
|
|
208
|
+
- **`runAdminSql` on Postgres reads as its own login role** holding base-column `SELECT` and nothing else:
|
|
209
|
+
`CREATE ROLE <name> LOGIN PASSWORD '…'` with no other role granted, `POSTGRES_INSIGHT_URL` logging in as it, and
|
|
210
|
+
`GRANT USAGE ON SCHEMA` from the DBA when the app does not own its schema. Every model table grants the role its
|
|
211
|
+
base columns at boot. Without it the console refuses on Postgres; `_doc` is never readable either way.
|
|
212
|
+
- **`akan dbup`** starts what the workspace's apps declare (`--mode multiple` is Redis, `cluster` adds Postgres).
|
|
213
|
+
**`akan db-export <app>` / `akan db-import <app>`** move every model table as stored rows through NDJSON files
|
|
214
|
+
(`--dir`, default `local/transfer`) between whatever the shell's `AKAN_DATABASE_MODE` points at; a rerun
|
|
215
|
+
replaces rows, search is rebuilt after, and sessions, queued jobs and uploaded files do not move.
|
|
216
|
+
- **`q.raw(sql)` is the dialect's own SQL** — `json_extract("_doc", '$.f')` on SQLite, `("_doc" #>> '{f}')` on
|
|
217
|
+
Postgres. An app that runs in both modes avoids it.
|
|
218
|
+
|
|
219
|
+
## Operations — Snapshots, Restore And The `/_akan/ops` Channel
|
|
220
|
+
|
|
221
|
+
`GET /_akan/app/info` answers `{ appName, repoName, environment, operationMode }` on every deployment — the same
|
|
222
|
+
`AKAN_PUBLIC_*` values the client bundle already carries — so a control plane can check which app a host is
|
|
223
|
+
serving without a key. Everything else is behind the ops channel.
|
|
224
|
+
|
|
225
|
+
| surface | what it does |
|
|
226
|
+
|---|---|
|
|
227
|
+
| `bun main.js ops snapshot [--id] [--out] [--include-solid] [--json]` | `VACUUM INTO` a consistent copy, `integrity_check`, sha256, gzip, optional age encryption, `manifest.json` |
|
|
228
|
+
| `bun main.js ops restore <manifest.json> [--identity <file>]` | offline only: verify, stage, keep the old file as `<db>.pre-restore-<ts>` (its `-wal`/`-shm` beside it), swap |
|
|
229
|
+
| `GET /_akan/ops/info` | the public info plus `akanVersion`, `buildId`, `serverMode`, `databaseMode`, `solo`, `startedAt`, `replicaIdx` |
|
|
230
|
+
| `POST /_akan/ops/snapshot` `{ id, includeSolid?, uploadUrls: { main, solid?, manifest } }` | 202; a child process snapshots, then PUTs each file and the manifest **last** |
|
|
231
|
+
| `GET /_akan/ops/snapshot/:id` | `running` → `uploading` → `done` \| `failed`; kept in memory, so a restart answers 404 |
|
|
232
|
+
|
|
233
|
+
- **The entry is the image's own `main.js`** — a production image carries no `akan` CLI. `AkanApp.start()` hands
|
|
234
|
+
`argv[2] === "ops"` to the command before it boots anything. It reads the container's env — the `AKAN_PUBLIC_*`
|
|
235
|
+
identity and the database paths — so run it with that env (`docker compose exec`), never a bare shell's.
|
|
236
|
+
- **Restore refuses while an app answers `/_akan/app/health` on `PORT`** and names it by its `/_akan/app/info`;
|
|
237
|
+
`--force` is for a port some other app holds.
|
|
238
|
+
- **The channel exists only when `AKAN_OPS_PUBLIC_KEY` is set** (Ed25519: PEM, JWK, base64 SPKI or raw 32 bytes).
|
|
239
|
+
The control plane signs a short EdDSA JWS — `aud`, `iat`, `exp` at most 300s after `iat`, a `jti` the process
|
|
240
|
+
refuses twice — and the host holds only the public half, so a machine someone carries off mints nothing.
|
|
241
|
+
`aud` is `<appName>/<environment>`, or `<appName>/<environment>/<AKAN_OPS_INSTANCE>` when that is set, and then
|
|
242
|
+
only that: a token minted for the app as a whole would replay across every host sharing the name. 30 calls a
|
|
243
|
+
minute per process; every refusal is the same 401. A malformed key leaves the app serving with the channel off.
|
|
244
|
+
- **The app holds no storage credential.** It PUTs to the presigned URLs it was handed (`application/octet-stream`,
|
|
245
|
+
the manifest `application/json`), plain http only to loopback. The manifest landing is the commit marker.
|
|
246
|
+
- **Snapshots cover the SQLite modes only.** `single` copies the main file and, with `includeSolid`, the Solid file;
|
|
247
|
+
`multiple` copies the main file; `cluster` is refused. Paths follow the adaptors' env precedence
|
|
248
|
+
(`SQLITE_DATABASE_PATH`, `AKAN_SOLID_DB_PATH`, `AKAN_SQLITE_DIR`); a solo process uses the path its adaptor
|
|
249
|
+
actually opened, so a gateway deployment that moved the file in `env.server.ts` names it in the env too.
|
|
250
|
+
- **`AKAN_BACKUP_RECIPIENT=age1…[,age1…]` encrypts before upload** — age v1 X25519, so `age -d -i key.txt` opens
|
|
251
|
+
it. Restore takes `--identity` / `AKAN_BACKUP_IDENTITY_FILE`, or a file an operator already decrypted with
|
|
252
|
+
`age -d -o <name without .age>`.
|
|
253
|
+
- **The host keeps the newest `AKAN_OPS_SNAPSHOT_KEEP` (default 2) snapshots** under `AKAN_OPS_SNAPSHOT_DIR`
|
|
254
|
+
(default `<db dir>/snapshots/<id>/`) — the copy an edge still has while its uplink or the bucket is down.
|
|
255
|
+
- **`buildId` is `AKAN_BUILD_ID` when the deployment sets it**, else the git sha `akan build` wrote into
|
|
256
|
+
`akan.build.json` beside `main.js` (`-dirty` when the tree was).
|
|
257
|
+
- **`operationMode` does not gate any of this** — the key does. `edge` means a production host outside the
|
|
258
|
+
cluster: the console refuses it like `cloud`, and `init` / `interval` / `cron` take `operationMode: ["edge"]` to
|
|
259
|
+
run only there. Everything else behaves as `cloud`; `local` is a developer machine.
|
|
260
|
+
|
|
187
261
|
## Shipped Assets — `assets` In `akan.config.ts`
|
|
188
262
|
|
|
189
263
|
`akan build` copies the whole `public/` tree into `dist`, lib assets dereferenced, and that copy is the image.
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
CodegenLock,
|
|
4
4
|
WorkspaceExecutor,
|
|
5
5
|
AppExecutor
|
|
6
|
-
} from "./index-
|
|
6
|
+
} from "./index-crmeqnmf.js";
|
|
7
7
|
import"./index-q0y2jq9j.js";
|
|
8
8
|
import"./index-6wwzs1bc.js";
|
|
9
9
|
import {
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
AutoImportSync,
|
|
13
13
|
DevChangePlanner,
|
|
14
14
|
DevGeneratedIndexSync
|
|
15
|
-
} from "./index-
|
|
15
|
+
} from "./index-89hy31kf.js";
|
|
16
16
|
import {
|
|
17
17
|
HmrWatcher,
|
|
18
18
|
WatchRootResolver
|
|
@@ -173,6 +173,7 @@ class IncrementalBuilder {
|
|
|
173
173
|
app: this.#app,
|
|
174
174
|
routeId: msg.routeId,
|
|
175
175
|
seeds: msg.seeds,
|
|
176
|
+
graphSeeds: msg.graphSeeds,
|
|
176
177
|
artifact: this.#artifact,
|
|
177
178
|
knownEntries: new Set(msg.knownEntries),
|
|
178
179
|
discovery: this.#discovery
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
3
|
jsonText
|
|
4
|
-
} from "./index-
|
|
4
|
+
} from "./index-2446k7b4.js";
|
|
5
5
|
import {
|
|
6
6
|
akanMcpInstallTargets
|
|
7
|
-
} from "./index-
|
|
7
|
+
} from "./index-18zybgkc.js";
|
|
8
8
|
import {
|
|
9
9
|
script
|
|
10
|
-
} from "./index-
|
|
10
|
+
} from "./index-qdgzsnhg.js";
|
|
11
11
|
import {
|
|
12
12
|
ContextRunner2
|
|
13
|
-
} from "./index-
|
|
13
|
+
} from "./index-3jrp517k.js";
|
|
14
14
|
|
|
15
15
|
// pkgs/@akanjs/cli/context/context.script.ts
|
|
16
16
|
import { Logger } from "akanjs/common";
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
isScannedRootEntry,
|
|
8
8
|
AppExecutor,
|
|
9
9
|
LibExecutor
|
|
10
|
-
} from "./index-
|
|
10
|
+
} from "./index-crmeqnmf.js";
|
|
11
11
|
import {
|
|
12
12
|
FileSys
|
|
13
13
|
} from "./index-q0y2jq9j.js";
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
import {
|
|
20
20
|
workflowRunArtifactPath,
|
|
21
21
|
workflowSyncDir
|
|
22
|
-
} from "./index-
|
|
22
|
+
} from "./index-2446k7b4.js";
|
|
23
23
|
|
|
24
24
|
// pkgs/@akanjs/devkit/akanContext.ts
|
|
25
25
|
import { readdir } from "fs/promises";
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
3
|
AppExecutor
|
|
4
|
-
} from "./index-
|
|
4
|
+
} from "./index-crmeqnmf.js";
|
|
5
5
|
import {
|
|
6
6
|
jsonText,
|
|
7
7
|
createWorkflowBaselineSummary,
|
|
8
8
|
buildAkanModuleContextIndex,
|
|
9
9
|
createWorkflowStepRegistry,
|
|
10
10
|
toolingRolloutGate
|
|
11
|
-
} from "./index-
|
|
11
|
+
} from "./index-2446k7b4.js";
|
|
12
12
|
import {
|
|
13
13
|
buildResourceList,
|
|
14
14
|
codexMcpConfigPath,
|
|
@@ -18,32 +18,32 @@ import {
|
|
|
18
18
|
upsertCodexMcpServerBlock,
|
|
19
19
|
renderDoctorText,
|
|
20
20
|
AkanContextAnalyzer
|
|
21
|
-
} from "./index-
|
|
21
|
+
} from "./index-18zybgkc.js";
|
|
22
22
|
import {
|
|
23
23
|
isPlaceholderAppId2
|
|
24
|
-
} from "./index-
|
|
24
|
+
} from "./index-vqx8bdxz.js";
|
|
25
25
|
import {
|
|
26
26
|
CommandContainer,
|
|
27
27
|
runner
|
|
28
|
-
} from "./index-
|
|
28
|
+
} from "./index-qdgzsnhg.js";
|
|
29
29
|
import {
|
|
30
30
|
Prompter2
|
|
31
31
|
} from "./index-s5rhm04y.js";
|
|
32
32
|
import {
|
|
33
33
|
RepairRunner
|
|
34
|
-
} from "./index-
|
|
34
|
+
} from "./index-53176s7j.js";
|
|
35
35
|
import {
|
|
36
36
|
WorkflowRunner
|
|
37
|
-
} from "./index-
|
|
37
|
+
} from "./index-txr7dj2g.js";
|
|
38
38
|
import {
|
|
39
39
|
ModuleScript
|
|
40
|
-
} from "./index-
|
|
40
|
+
} from "./index-esk82rvk.js";
|
|
41
41
|
import {
|
|
42
42
|
PrimitiveScript
|
|
43
|
-
} from "./index-
|
|
43
|
+
} from "./index-zqgj2rt2.js";
|
|
44
44
|
import {
|
|
45
45
|
ScalarScript
|
|
46
|
-
} from "./index-
|
|
46
|
+
} from "./index-4m929vvn.js";
|
|
47
47
|
import {
|
|
48
48
|
getMobileTargets
|
|
49
49
|
} from "./index-47pj3aee.js";
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
import {
|
|
3
3
|
runner,
|
|
4
4
|
script
|
|
5
|
-
} from "./index-
|
|
5
|
+
} from "./index-qdgzsnhg.js";
|
|
6
6
|
import {
|
|
7
7
|
generatedFilesForSync,
|
|
8
8
|
createPassedPrimitiveReport,
|
|
9
9
|
scalarChangedFiles
|
|
10
|
-
} from "./index-
|
|
10
|
+
} from "./index-2446k7b4.js";
|
|
11
11
|
import {
|
|
12
12
|
pluralizeName
|
|
13
13
|
} from "./index-nz8prx9g.js";
|
|
@@ -5,13 +5,13 @@ import {
|
|
|
5
5
|
writeGeneratedSyncState,
|
|
6
6
|
createRepairReport,
|
|
7
7
|
renderRepairReport
|
|
8
|
-
} from "./index-
|
|
8
|
+
} from "./index-2446k7b4.js";
|
|
9
9
|
import {
|
|
10
10
|
AkanContextAnalyzer
|
|
11
|
-
} from "./index-
|
|
11
|
+
} from "./index-18zybgkc.js";
|
|
12
12
|
import {
|
|
13
13
|
runner
|
|
14
|
-
} from "./index-
|
|
14
|
+
} from "./index-qdgzsnhg.js";
|
|
15
15
|
|
|
16
16
|
// pkgs/@akanjs/cli/repair/repair.runner.ts
|
|
17
17
|
var commandForShell = (command) => command.startsWith("akan ") ? `bun run ${command}` : command;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// pkgs/@akanjs/devkit/codeAgent/agent/akanCodeModel.ts
|
|
3
|
-
var akanCodeDefaultModel2 = { provider: "deepseek", id: "deepseek-
|
|
3
|
+
var akanCodeDefaultModel2 = { provider: "deepseek", id: "deepseek-flash" };
|
|
4
4
|
var catalogueMissesImages = new Set(["deepseek/deepseek-v4-flash"]);
|
|
5
5
|
var correctInputs = (model) => {
|
|
6
6
|
if (!model || model.input.includes("image"))
|
|
@@ -1147,6 +1147,7 @@ var SSR_CLIENT_ALIAS_EXTERNALS = [
|
|
|
1147
1147
|
class RouteClientBuilder {
|
|
1148
1148
|
#app;
|
|
1149
1149
|
#seeds;
|
|
1150
|
+
#graphSeeds;
|
|
1150
1151
|
#knownEntries;
|
|
1151
1152
|
#command;
|
|
1152
1153
|
#discovery;
|
|
@@ -1154,20 +1155,22 @@ class RouteClientBuilder {
|
|
|
1154
1155
|
constructor(options) {
|
|
1155
1156
|
this.#app = options.app;
|
|
1156
1157
|
this.#seeds = options.seeds;
|
|
1158
|
+
this.#graphSeeds = options.graphSeeds;
|
|
1157
1159
|
this.#knownEntries = options.knownEntries ?? new Set;
|
|
1158
1160
|
this.#command = options.command ?? "start";
|
|
1159
1161
|
this.#discovery = options.discovery;
|
|
1160
1162
|
this.#entries = options.entries;
|
|
1161
1163
|
}
|
|
1162
1164
|
async build() {
|
|
1163
|
-
const discovered = this.#entries ?? await (
|
|
1164
|
-
|
|
1165
|
-
if (entries.length === 0)
|
|
1165
|
+
const discovered = this.#entries ?? await (await this.#getDiscovery()).discover(this.#seeds);
|
|
1166
|
+
if (discovered.every((e) => this.#knownEntries.has(e)))
|
|
1166
1167
|
return this.#emptyResult(discovered);
|
|
1168
|
+
const entries = this.#graphSeeds ? [...new Set([...discovered, ...await (await this.#getDiscovery()).discover(this.#graphSeeds)])].sort() : discovered.filter((e) => !this.#knownEntries.has(e));
|
|
1167
1169
|
const bootstrapEntries = await this.#createBootstrapEntries(entries);
|
|
1168
1170
|
const browserBundle = await this.#buildBrowserBundle(bootstrapEntries);
|
|
1169
1171
|
const ssrBundle = await this.#buildSsrBundle(bootstrapEntries);
|
|
1170
1172
|
const acceptedEntries = new Set(entries);
|
|
1173
|
+
const routeEntries = new Set(discovered);
|
|
1171
1174
|
const manifestDelta = {};
|
|
1172
1175
|
const ssrModuleMap = {};
|
|
1173
1176
|
const clientDeps = new Set;
|
|
@@ -1195,18 +1198,23 @@ class RouteClientBuilder {
|
|
|
1195
1198
|
deps.add(path6.resolve(dep));
|
|
1196
1199
|
const sortedDeps = [...deps].sort();
|
|
1197
1200
|
clientDepsByEntry[originalEntry] = sortedDeps;
|
|
1198
|
-
|
|
1199
|
-
|
|
1201
|
+
if (routeEntries.has(originalEntry))
|
|
1202
|
+
for (const dep of sortedDeps)
|
|
1203
|
+
clientDeps.add(dep);
|
|
1200
1204
|
}
|
|
1201
1205
|
return {
|
|
1202
1206
|
manifestDelta,
|
|
1203
1207
|
ssrManifestDelta: { moduleLoading: null, moduleMap: ssrModuleMap },
|
|
1204
|
-
newEntries: entries,
|
|
1208
|
+
newEntries: entries.filter((e) => !this.#knownEntries.has(e)),
|
|
1205
1209
|
discoveredEntries: discovered,
|
|
1206
1210
|
clientDeps: [...clientDeps].sort(),
|
|
1207
1211
|
clientDepsByEntry
|
|
1208
1212
|
};
|
|
1209
1213
|
}
|
|
1214
|
+
async#getDiscovery() {
|
|
1215
|
+
this.#discovery ??= await GraphClientEntryDiscovery.create(this.#app);
|
|
1216
|
+
return this.#discovery;
|
|
1217
|
+
}
|
|
1210
1218
|
#emptyResult(discoveredEntries = []) {
|
|
1211
1219
|
return {
|
|
1212
1220
|
manifestDelta: {},
|
|
@@ -1440,6 +1448,7 @@ var LIB_BARRELS = {
|
|
|
1440
1448
|
cnst: { barrel: "cnst", kind: "namespace" },
|
|
1441
1449
|
srv: { barrel: "srv", kind: "namespace" }
|
|
1442
1450
|
};
|
|
1451
|
+
var GLOBAL_NAMES = new Set(["fetch"]);
|
|
1443
1452
|
var DOMAIN_DENYLIST = new Set([
|
|
1444
1453
|
"Map",
|
|
1445
1454
|
"Set",
|
|
@@ -1642,10 +1651,11 @@ var transformSource = (source, fileName, ctx, resolveExtra) => {
|
|
|
1642
1651
|
const sf = ts2.createSourceFile(fileName, source, ts2.ScriptTarget.Latest, true, scriptKindFor(fileName));
|
|
1643
1652
|
const bound = collectBoundNames(sf);
|
|
1644
1653
|
const used = collectUsedReferences(sf);
|
|
1654
|
+
const bareGlobals = collectBareGlobalReferences(sf);
|
|
1645
1655
|
const namedBySpecifier = new Map;
|
|
1646
1656
|
const namespaceImports = [];
|
|
1647
1657
|
for (const name of used) {
|
|
1648
|
-
if (bound.has(name))
|
|
1658
|
+
if (bound.has(name) || bareGlobals.has(name))
|
|
1649
1659
|
continue;
|
|
1650
1660
|
const target = targetFor(name, ctx) ?? resolveExtra?.(name) ?? null;
|
|
1651
1661
|
if (!target)
|
|
@@ -1743,6 +1753,19 @@ var collectUsedReferences = (sf) => {
|
|
|
1743
1753
|
ts2.forEachChild(sf, visit);
|
|
1744
1754
|
return used;
|
|
1745
1755
|
};
|
|
1756
|
+
var collectBareGlobalReferences = (sf) => {
|
|
1757
|
+
const bare = new Set;
|
|
1758
|
+
const visit = (node) => {
|
|
1759
|
+
if (ts2.isIdentifier(node) && GLOBAL_NAMES.has(node.text) && isReferencePosition(node)) {
|
|
1760
|
+
const parent = node.parent;
|
|
1761
|
+
if (!(parent && ts2.isPropertyAccessExpression(parent) && parent.expression === node))
|
|
1762
|
+
bare.add(node.text);
|
|
1763
|
+
}
|
|
1764
|
+
ts2.forEachChild(node, visit);
|
|
1765
|
+
};
|
|
1766
|
+
ts2.forEachChild(sf, visit);
|
|
1767
|
+
return bare;
|
|
1768
|
+
};
|
|
1746
1769
|
var isReferencePosition = (node) => {
|
|
1747
1770
|
const parent = node.parent;
|
|
1748
1771
|
if (!parent)
|
|
@@ -112,7 +112,7 @@ class SubagentPool2 {
|
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
114
|
async#run(id, kind, prompt, signal) {
|
|
115
|
-
const { CodeAgent } = await import("./CodeAgent-
|
|
115
|
+
const { CodeAgent } = await import("./CodeAgent-6cm97kay.js");
|
|
116
116
|
const budget = this.#options.parent.tools.subagent;
|
|
117
117
|
const child = SubagentPool2.childOf(this.#options.parent, kind);
|
|
118
118
|
const agent = await CodeAgent.create({
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
AsyncDefaultExportDetector,
|
|
4
4
|
resolveSsrPageEntriesForApp
|
|
5
|
-
} from "./index-
|
|
5
|
+
} from "./index-crmeqnmf.js";
|
|
6
6
|
import {
|
|
7
7
|
BarrelAnalyzer,
|
|
8
8
|
createBarrelImportsPlugin,
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
ClientEntriesBundler,
|
|
14
14
|
VENDOR_SPECIFIERS,
|
|
15
15
|
RouteClientBuilder
|
|
16
|
-
} from "./index-
|
|
16
|
+
} from "./index-89hy31kf.js";
|
|
17
17
|
var __require = import.meta.require;
|
|
18
18
|
|
|
19
19
|
// pkgs/@akanjs/devkit/frontendBuild/allRoutesBuilder.ts
|
|
@@ -419,7 +419,7 @@ class CssImportResolver {
|
|
|
419
419
|
}
|
|
420
420
|
async#firstExisting(basePath) {
|
|
421
421
|
for (const suffix of CSS_IMPORT_EXTS) {
|
|
422
|
-
const candidate = `${basePath}${suffix}
|
|
422
|
+
const candidate = path4.normalize(`${basePath}${suffix}`);
|
|
423
423
|
if (await Bun.file(candidate).exists())
|
|
424
424
|
return candidate;
|
|
425
425
|
}
|