@akanjs/devkit 3.0.2 → 3.1.0-rc.0
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 +46 -7
- package/akanApp/akanApp.host.ts +3 -2
- package/akanConfig/akanConfig.test.ts +76 -6
- package/akanConfig/akanConfig.ts +111 -13
- package/akanConfig/akanWorkspaceConfig.test.ts +79 -0
- package/akanConfig/akanWorkspaceConfig.ts +63 -0
- package/akanConfig/index.ts +1 -0
- package/akanConfig/types.ts +23 -1
- package/akanContext.ts +1 -1
- package/applicationBuildRunner.test.ts +1 -1
- package/applicationBuildRunner.ts +73 -84
- package/artifact/implicitRootLayout.test.ts +6 -2
- package/artifact/implicitRootLayout.ts +12 -7
- package/biome.base.json +7 -2
- package/cloud/cloudApi.ts +82 -0
- package/cloud/globalConfig.ts +4 -0
- package/cloud/index.ts +1 -0
- package/cloud/updatePackUpload.test.ts +195 -0
- package/cloud/updatePackUpload.ts +115 -0
- package/commandDecorators/command.ts +8 -2
- package/commandDecorators/targetMeta.ts +1 -0
- package/executors.test.ts +21 -0
- package/executors.ts +9 -2
- package/frontendBuild/csrArtifactBuilder.ts +10 -1
- package/frontendBuild/cssCandidateCache.test.ts +10 -0
- package/frontendBuild/cssCandidateCache.ts +4 -2
- package/frontendBuild/pagesBundleBuilder.ts +5 -1
- package/frontendBuild/ssrBaseArtifactBuilder.ts +6 -2
- package/incrementalBuilder/buildBatch.proc.ts +14 -2
- package/incrementalBuilder/buildBatchProtocol.ts +3 -2
- package/incrementalBuilder/incrementalBuilder.proc.test.ts +96 -0
- package/incrementalBuilder/incrementalBuilder.proc.ts +30 -1
- package/lint/__fixtures__/no-import-external-library-in-test/bad.tsx +6 -0
- package/lint/__fixtures__/no-import-external-library-in-test/good.tsx +6 -0
- package/lint/no-import-external-library-in-test.grit +24 -0
- package/mobile/desktopServerStage.test.ts +6 -0
- package/mobile/desktopServerStage.ts +25 -3
- package/mobile/index.ts +1 -0
- package/mobile/nativeApp.test.ts +1 -0
- package/mobile/nativeApp.ts +96 -34
- package/mobile/nativeConfig.test.ts +59 -1
- package/mobile/nativeConfig.ts +13 -3
- package/mobile/serverBundleSecrets.test.ts +61 -0
- package/mobile/serverBundleSecrets.ts +85 -0
- package/mobile/updateUploader.test.ts +49 -0
- package/mobile/updateUploader.ts +71 -0
- package/package.json +3 -2
- package/pipeline/PipelineCompiler.test.ts +631 -0
- package/pipeline/PipelineCompiler.ts +646 -0
- package/pipeline/PipelineDefinition.test.ts +89 -0
- package/pipeline/PipelineDefinition.ts +117 -0
- package/pipeline/PipelineDryRun.test.ts +93 -0
- package/pipeline/PipelineDryRun.ts +89 -0
- package/pipeline/PipelineInputReader.ts +739 -0
- package/pipeline/index.ts +4 -0
- package/qualityScanner.test.ts +9 -3
- package/qualityScanner.ts +5 -2
- package/scanInfo.ts +13 -6
- package/subspace.test.ts +108 -4
- package/subspace.ts +162 -10
- package/subspaceConfig.ts +11 -14
- package/testHelpers.ts +4 -0
- package/transforms/externalizeFrameworkPlugin.ts +1 -7
- package/transforms/serverEnvPlugin.test.ts +93 -2
- package/transforms/serverEnvPlugin.ts +71 -18
- package/transforms/serverEnvProfile.test.ts +82 -0
- package/transforms/serverEnvProfile.ts +114 -0
- package/typecheck/projectTypecheck.test.ts +32 -0
- package/typecheck/projectTypecheck.ts +71 -0
- package/workspaceLayout.test.ts +26 -3
- package/workspaceLayout.ts +15 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,44 @@
|
|
|
1
1
|
# @akanjs/devkit
|
|
2
2
|
|
|
3
|
+
## 3.0.2
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- be8ed50: New lint rules: `no-unguarded-endpoint` warns on an endpoint, named slice or root slice that declares no guards, and `no-double-brace-placeholder` refuses `{{name}}` in a dictionary error/translate entry. `no-throw-raw-error` also warns on a raw `Error` constructed without `throw`, and `no-inline-color` warns on color literals in SVG color attributes and `el.style` writes.
|
|
8
|
+
- af66669: Native builds moved from `apps/<app>/.akan/native/<target>` to `dist/native/<app>/<target>` (`build/<platform>`, `dev/<platform>`, `web`, `bin`, `updates`). It sits outside `dist/apps/<app>`, which every `akan build` empties, so a signed release waiting for upload survives the next build. Upload what is waiting in an old `.akan/native/<target>/updates` and delete the folder: `akan start` keeps it and warns until then. A CI step that collects installers reads `dist/native/<app>/*/build/*/` now, and `pack-update`'s default output is `dist/native/<app>/<target>/updates/<platform>`.
|
|
9
|
+
- af66669: The macOS dmg `build-desktop --installer true` makes opens on a drag-to-Applications window: no toolbar, sidebar or status bar, 128 pt icons of the app and the Applications link on a background with an arrow between them (1x and 2x in one multi-resolution TIFF), and the app's icon on the mounted disk (`.VolumeIcon.icns`). Its own files (`.background`, `.VolumeIcon.icns`, `.DS_Store`, `.fseventsd`) are hidden. The window is a `.DS_Store` written directly while a read-write image is mounted, never through Finder or AppleScript, so a headless CI builds the same image; the compressed image is made afterwards and is what gets signed, notarized and stapled. `native.desktop.dmg` (a target's too) replaces the background (`background`, `background2x`, or `false`) and moves the window and the icons (`window`, `iconSize`, `textSize`, `app`, `applications`, in points); the default background lines up with the default positions only.
|
|
10
|
+
- be8ed50: `akan sync` prunes a root-declared dependency nothing imports and realigns every root-declared entry to the root's version; `package.json#akan.keepDependencies`, `externalLibs` and `trustedDependencies` are always kept. `akan install-library` keeps the root's versions and only adds what the root lacks.
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- af66669: `akan build-desktop` builds for this computer's CPU without asking; `--arch arm64|x64` picks another one. Any CLI option declared both `enum` and `nullable` is now `null` when left out instead of prompting for one of its choices.
|
|
15
|
+
- af66669: Upgrading may stop `akan sync` (and every command that runs it, `build-desktop` included) with a scan-convention error on a hand-written `index.tsx`, `index.jsx` or `index.mts` at the root of `ui/`, `webkit/`, `srvkit/`, `common/` or `plugin/`. Bun loads that file ahead of the generated `index.ts` while TypeScript checks `index.ts`, so a name only the generated barrel exports typechecks and is undefined at runtime. Delete the file: the generated barrel re-exports every file and folder of the facet. A namespace it built belongs in `<facet>/<Folder>/index.tsx`, which the check leaves alone.
|
|
16
|
+
- be8ed50: Generated barrels, module indexes and `akan.*.json` are written exactly as Biome prints them, so `akan lint --no-fix` passes right after a sync.
|
|
17
|
+
- af66669: A native build's `--env` reaches its CSR bundle. The CSR builds passed `env: "AKAN_PUBLIC_*"` to `Bun.build`, which inlines the `.env` Bun read when the process started and ranks it above `define`, so `build-desktop --env debug` shipped the root `.env`'s `AKAN_PUBLIC_ENV=local` and called `http://localhost:8282`, and `akan build` baked back the operation mode and ports it had just dropped. Every `AKAN_PUBLIC_*` now comes from `process.env` through `define` alone, and `akan build` drops `AKAN_PUBLIC_CLIENT_PORT` / `AKAN_PUBLIC_SERVER_PORT` from `process.env` as well, so a port in `.env` is no longer baked in.
|
|
18
|
+
- af66669: A Linux desktop app's window is named after its app id (X11 `WM_CLASS`, Wayland `app_id`), so a dock or task bar finds its `<app id>.desktop` and shows that entry's name and icon instead of a generic one; the dock plugin's badge and progress name the same entry. The AppImage `build-desktop --installer true` makes carries `<app id>.desktop` with `StartupWMClass=<app id>` and `<app id>.png`, so the entry AppImageLauncher or appimaged installs under another file name still matches the window. The hidden entry that handles the app's deep links now shows the app's icon (installed as `~/.local/share/icons/hicolor/256x256/apps/<app id>.png`), and from an AppImage it starts the AppImage itself (`$APPIMAGE`) rather than the mount point it ran from, which is gone once the app quits, so a link opened after quitting the app no longer fails.
|
|
19
|
+
- af66669: A `--env local` release build (`build-*`, `release-*`, `start-* --release true`, `publish-update`) calls the app's own dev server: its `env.runtime.json` names `PUBLIC_AKAN_SERVER_URL=http://localhost:<dev port>`, the port `akan start` gives the app (`AKAN_DEV_PORT`, else its place among the apps). Before, it always called 8282, which in a workspace with several apps is another app or nothing. A target that carries its server keeps the URL its server hands the page at launch, and an `AKAN_PUBLIC_SERVER_URL` the bundle was built with still wins.
|
|
20
|
+
- be8ed50: `akan quality scan` no longer flags a non-exported interface or type declared beside a class.
|
|
21
|
+
- 73a81da: Let non-local single database mode development servers continue when the Redis SSH tunnel is unavailable, warning with the SSH target and underlying connection errors and setting an explicitly unavailable Redis host instead of falling back to a local Redis instance. Working tunnels remain available to custom Redis adaptors, while multiple and cluster modes still require Redis and local development remains unchanged.
|
|
22
|
+
- Updated dependencies [be8ed50]
|
|
23
|
+
- Updated dependencies [af66669]
|
|
24
|
+
- Updated dependencies [be8ed50]
|
|
25
|
+
- Updated dependencies [be8ed50]
|
|
26
|
+
- Updated dependencies [be8ed50]
|
|
27
|
+
- Updated dependencies [be8ed50]
|
|
28
|
+
- Updated dependencies [be8ed50]
|
|
29
|
+
- Updated dependencies [be8ed50]
|
|
30
|
+
- Updated dependencies [af66669]
|
|
31
|
+
- Updated dependencies [af66669]
|
|
32
|
+
- Updated dependencies [bd55713]
|
|
33
|
+
- Updated dependencies [be8ed50]
|
|
34
|
+
- Updated dependencies [be8ed50]
|
|
35
|
+
- Updated dependencies [be8ed50]
|
|
36
|
+
- Updated dependencies [be8ed50]
|
|
37
|
+
- Updated dependencies [a789952]
|
|
38
|
+
- Updated dependencies [be8ed50]
|
|
39
|
+
- Updated dependencies [be8ed50]
|
|
40
|
+
- akanjs@3.1.0
|
|
41
|
+
|
|
3
42
|
## 3.0.1
|
|
4
43
|
|
|
5
44
|
### Minor Changes
|
|
@@ -557,13 +596,13 @@
|
|
|
557
596
|
formatter, the rule set, and every grit plugin registration; a workspace `biome.json` is `extends` plus its own
|
|
558
597
|
`files.includes`. Biome resolves the specifier through node*modules (it does not consult the package `exports`
|
|
559
598
|
map), and `plugins` paths inside an extended config resolve from the entry config's directory, so the
|
|
560
|
-
`./node_modules/@akanjs/devkit/lint/*.grit`
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
599
|
+
`./node_modules/@akanjs/devkit/lint/*.grit`form the template already used is correct. Rule changes now reach a
|
|
600
|
+
workspace on`bun update`with no command to run. Two merge behaviours decided the split:`overrides`concatenate,
|
|
601
|
+
so the generated-file opt-out moved there from`files.includes`, which \_replaces* and would silently drop the base
|
|
602
|
+
list the moment a workspace added one exclusion of its own. Because Biome moves rules between groups across minors
|
|
603
|
+
(`noUnnecessaryConditions`is`nursery`at 2.4 and`suspicious`at 2.5, and the old position is a hard error), the
|
|
604
|
+
base config and the Biome version travel together:`biomeBase.ts`pins the version`createWorkspace`installs
|
|
605
|
+
instead of resolving`latest`.
|
|
567
606
|
|
|
568
607
|
**The conventions body moved into the managed block.** It now ships as the `conventions` guideline and renders
|
|
569
608
|
between the `akan:agent` markers, so `akan agent install` refreshes it. `AGENTS.md.template` carried a _second_,
|
package/akanApp/akanApp.host.ts
CHANGED
|
@@ -1289,8 +1289,9 @@ export class AkanAppHost {
|
|
|
1289
1289
|
void this.#wakeFromIdle(`${message.type} arrived while suspended`);
|
|
1290
1290
|
return;
|
|
1291
1291
|
}
|
|
1292
|
-
// In `env`, which every builder spawn re-reads, so a restarted builder stays armed for a mobile dev session.
|
|
1293
|
-
|
|
1292
|
+
// In `env`, which every builder spawn re-reads, so a restarted builder stays armed for a mobile dev session. A view's
|
|
1293
|
+
// shell is asked for per read, so it arms nothing.
|
|
1294
|
+
if (message.type === "build-csr" && !message.shell && this.env.AKAN_DEV_CSR_REBUILD !== "1") {
|
|
1294
1295
|
Object.assign(this.env, { AKAN_DEV_CSR_REBUILD: "1" });
|
|
1295
1296
|
this.logger.verbose(`[csr] armed dev CSR rebuilds (${message.reason})`);
|
|
1296
1297
|
}
|
|
@@ -342,7 +342,7 @@ describe("AkanAppConfig", () => {
|
|
|
342
342
|
const clusterConfig = configOf({ modes: ["cluster"] });
|
|
343
343
|
const edgeAndCloud = configOf({ modes: ["single", "cluster"] });
|
|
344
344
|
|
|
345
|
-
for (const driver of ["ioredis", "bullmq", "postgres", "
|
|
345
|
+
for (const driver of ["ioredis", "bullmq", "postgres", "protobufjs"])
|
|
346
346
|
expect(dependenciesOf(singleConfig)).not.toHaveProperty(driver);
|
|
347
347
|
expect(dependenciesOf(multipleConfig)).toMatchObject({
|
|
348
348
|
bullmq: runtimeDependencies.bullmq,
|
|
@@ -353,9 +353,9 @@ describe("AkanAppConfig", () => {
|
|
|
353
353
|
ioredis: runtimeDependencies.ioredis,
|
|
354
354
|
postgres: runtimeDependencies.postgres,
|
|
355
355
|
});
|
|
356
|
-
//
|
|
356
|
+
// Redis replaced protobuf on the wire, so it ships with no mode.
|
|
357
357
|
for (const config of [multipleConfig, clusterConfig])
|
|
358
|
-
|
|
358
|
+
expect(dependenciesOf(config)).not.toHaveProperty("protobufjs");
|
|
359
359
|
expect(dependenciesOf(multipleConfig)).not.toHaveProperty("postgres");
|
|
360
360
|
// One image for an edge site and a cloud cluster carries both modes' drivers, and says which it carries.
|
|
361
361
|
expect(dependenciesOf(edgeAndCloud)).toMatchObject({ postgres: runtimeDependencies.postgres });
|
|
@@ -477,7 +477,7 @@ describe("AkanAppConfig", () => {
|
|
|
477
477
|
).toThrow("unknown basePath");
|
|
478
478
|
});
|
|
479
479
|
|
|
480
|
-
test("a desktop server is true or { omit }, its
|
|
480
|
+
test("a desktop server is true or { omit, profile, modules }, its lists trimmed, deduplicated and sorted", () => {
|
|
481
481
|
const resolve = (server: unknown) =>
|
|
482
482
|
new AkanAppConfig(app, [], packageJson, { native: { desktop: { server } } } as never, baseDevEnv).native.targets
|
|
483
483
|
.default.desktop?.server;
|
|
@@ -487,6 +487,41 @@ describe("AkanAppConfig", () => {
|
|
|
487
487
|
expect(resolve({})).toEqual({ omit: [] });
|
|
488
488
|
expect(() => resolve({ omit: "rclnodejs" })).toThrow("native.desktop.server.omit in apps/");
|
|
489
489
|
expect(() => resolve({ exclude: ["rclnodejs"] })).toThrow("native.desktop.server.exclude in apps/");
|
|
490
|
+
expect(resolve({ profile: "edge", modules: ["@edge", " edgeAgent"] })).toEqual({
|
|
491
|
+
omit: [],
|
|
492
|
+
profile: "edge",
|
|
493
|
+
modules: ["@edge", "edgeAgent"],
|
|
494
|
+
});
|
|
495
|
+
expect(() => resolve({ profile: "../main" })).toThrow("native.desktop.server.profile in apps/");
|
|
496
|
+
expect(() => resolve({ modules: "@edge" })).toThrow("native.desktop.server.modules in apps/");
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
test("a desktop server's env, restart and watchdog: secret-free, what the stage owns refused, a watchdog defaulted", () => {
|
|
500
|
+
const resolve = (server: unknown) =>
|
|
501
|
+
new AkanAppConfig(app, [], packageJson, { native: { desktop: { server } } } as never, baseDevEnv).native.targets
|
|
502
|
+
.default.desktop?.server;
|
|
503
|
+
|
|
504
|
+
expect(
|
|
505
|
+
resolve({
|
|
506
|
+
env: { CLOUD_ORIGIN: "https://cloud.example", ENROLL_KEY: "fleet" },
|
|
507
|
+
restart: "forever",
|
|
508
|
+
watchdog: true,
|
|
509
|
+
}),
|
|
510
|
+
).toEqual({
|
|
511
|
+
omit: [],
|
|
512
|
+
env: { CLOUD_ORIGIN: "https://cloud.example", ENROLL_KEY: "fleet" },
|
|
513
|
+
restart: "forever",
|
|
514
|
+
watchdog: { intervalMs: 10_000, failures: 3 },
|
|
515
|
+
});
|
|
516
|
+
expect(resolve({ restart: 8, watchdog: { failures: 5 } })).toMatchObject({
|
|
517
|
+
restart: 8,
|
|
518
|
+
watchdog: { intervalMs: 10_000, failures: 5 },
|
|
519
|
+
});
|
|
520
|
+
expect(() => resolve({ env: { SLACK_WEBHOOK_URL: "https://hooks" } })).toThrow("SLACK_WEBHOOK_URL");
|
|
521
|
+
expect(() => resolve({ env: { AKAN_MODULES: "@edge" } })).toThrow("the carried server's stage owns");
|
|
522
|
+
expect(() => resolve({ env: { PORT: 1 } })).toThrow("must be an object of strings");
|
|
523
|
+
expect(() => resolve({ restart: 0 })).toThrow('must be "forever" or a count');
|
|
524
|
+
expect(() => resolve({ watchdog: { intervalMs: -1 } })).toThrow("watchdog in apps/");
|
|
490
525
|
});
|
|
491
526
|
|
|
492
527
|
test("an app without basePaths has one target, default, with no basePath and the section's settings", () => {
|
|
@@ -589,10 +624,10 @@ describe("AkanAppConfig", () => {
|
|
|
589
624
|
{ native: { targets: { pilot: { updates: { channel: "pilot" } } } } },
|
|
590
625
|
baseDevEnv,
|
|
591
626
|
),
|
|
592
|
-
).toThrow("native.targets.pilot.updates in apps/portal/akan.config.ts has no url or
|
|
627
|
+
).toThrow("native.targets.pilot.updates in apps/portal/akan.config.ts has no url or publicKeys");
|
|
593
628
|
expect(
|
|
594
629
|
() => new AkanAppConfig(app, [], packageJson, { native: { updates: { channel: "pilot" } } }, baseDevEnv),
|
|
595
|
-
).toThrow("native.updates in apps/portal/akan.config.ts has no url or
|
|
630
|
+
).toThrow("native.updates in apps/portal/akan.config.ts has no url or publicKeys.");
|
|
596
631
|
});
|
|
597
632
|
|
|
598
633
|
test("merges the desktop settings field by field, the target winning", () => {
|
|
@@ -895,3 +930,38 @@ describe("AkanAppConfig.importConfigModule", () => {
|
|
|
895
930
|
}
|
|
896
931
|
});
|
|
897
932
|
});
|
|
933
|
+
|
|
934
|
+
describe("pipeline and workspace keys in app and lib configs", () => {
|
|
935
|
+
test("an app's pipeline is dropped before the instance exists, so it never reaches akan.app.json", async () => {
|
|
936
|
+
const config = await loadExtAppConfig(
|
|
937
|
+
"akan-config-app-keys-",
|
|
938
|
+
'export default { externalLibs: ["ext-a"], pipeline: { stages: [{ use: "build" }] } };\n',
|
|
939
|
+
"export default {};\n",
|
|
940
|
+
);
|
|
941
|
+
expect(config.externalLibs).toContain("ext-a");
|
|
942
|
+
expect("pipeline" in config).toBe(false);
|
|
943
|
+
expect(Object.keys(JSON.parse(JSON.stringify(config)) as object)).not.toContain("pipeline");
|
|
944
|
+
});
|
|
945
|
+
|
|
946
|
+
test("an app refuses the workspace's subspace setting", async () => {
|
|
947
|
+
const loading = loadExtAppConfig(
|
|
948
|
+
"akan-config-subspace-",
|
|
949
|
+
"export default { subspace: { subspaces: [] } };\n",
|
|
950
|
+
"export default {};\n",
|
|
951
|
+
);
|
|
952
|
+
expect(loading).rejects.toThrow("a workspace setting");
|
|
953
|
+
});
|
|
954
|
+
|
|
955
|
+
test("a lib takes neither a pipeline nor a subspace", async () => {
|
|
956
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), "akan-config-lib-keys-"));
|
|
957
|
+
try {
|
|
958
|
+
const lib = { name: "extlib", cwdPath: root } as never;
|
|
959
|
+
fs.writeFileSync(path.join(root, "akan.config.ts"), "export default { pipeline: { stages: [] } };\n");
|
|
960
|
+
expect(AkanLibConfig.from(lib)).rejects.toThrow("which a lib does not take");
|
|
961
|
+
fs.writeFileSync(path.join(root, "akan.config.ts"), "export default { subspace: { subspaces: [] } };\n");
|
|
962
|
+
expect(AkanLibConfig.from(lib, { bustImportCache: true })).rejects.toThrow("a workspace setting");
|
|
963
|
+
} finally {
|
|
964
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
965
|
+
}
|
|
966
|
+
});
|
|
967
|
+
});
|
package/akanConfig/akanConfig.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { type AkanI18nConfig, resolveAkanI18nConfig } from "akanjs/common";
|
|
|
7
7
|
import type { AkanImageConfig } from "akanjs/server";
|
|
8
8
|
import type { App, Lib } from "../commandDecorators";
|
|
9
9
|
import { LibExecutor, WorkspaceExecutor } from "../executors";
|
|
10
|
+
import { ServerEnvProfile } from "../transforms/serverEnvProfile";
|
|
10
11
|
import type { BaseDevEnv, PackageJson } from "../types";
|
|
11
12
|
import { AkanBin } from "./akanBin";
|
|
12
13
|
import {
|
|
@@ -280,9 +281,9 @@ export class AkanAppConfig implements AppConfigResult {
|
|
|
280
281
|
if (basePath && !this.basePaths.has(basePath))
|
|
281
282
|
throw new Error(`Native target '${name}' uses unknown basePath '${basePath}' in ${configPath}`);
|
|
282
283
|
const { updates } = merged;
|
|
283
|
-
if (updates && (!updates.url || !updates.publicKey))
|
|
284
|
+
if (updates && (!updates.url || !(updates.publicKey || updates.publicKeys?.length)))
|
|
284
285
|
throw new Error(
|
|
285
|
-
`${where}.updates in ${configPath} has no url or
|
|
286
|
+
`${where}.updates in ${configPath} has no url or publicKeys${named ? "; give them in native.updates or the target's own" : ""}.`,
|
|
286
287
|
);
|
|
287
288
|
AkanAppConfig.#assertFiles(own ?? {}, where, configPath);
|
|
288
289
|
const {
|
|
@@ -330,7 +331,7 @@ export class AkanAppConfig implements AppConfigResult {
|
|
|
330
331
|
);
|
|
331
332
|
return { appName, appId, ...(root.fileName ? { fileName: root.fileName } : {}), version, buildNum, targets };
|
|
332
333
|
}
|
|
333
|
-
//? `true` or `{ omit }`:
|
|
334
|
+
//? `true` or `{ omit, profile, modules }`: lists trimmed once, so two targets that agree on them stage one server.
|
|
334
335
|
static #desktopServer<T extends { server?: AkanNativeDesktopConfig["server"] }>(
|
|
335
336
|
desktop: T,
|
|
336
337
|
where: string,
|
|
@@ -339,12 +340,74 @@ export class AkanAppConfig implements AppConfigResult {
|
|
|
339
340
|
const { server } = desktop;
|
|
340
341
|
if (server === undefined || typeof server === "boolean") return desktop;
|
|
341
342
|
if (!AkanAppConfig.#isPlainObject(server))
|
|
342
|
-
throw new Error(`${where} in ${configPath} must be true or { omit
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
343
|
+
throw new Error(`${where} in ${configPath} must be true or { omit, profile, modules }.`);
|
|
344
|
+
const names = (list: unknown, key: string, what: string) => {
|
|
345
|
+
if (list === undefined) return [];
|
|
346
|
+
if (!Array.isArray(list) || list.some((name) => typeof name !== "string" || !name.trim()))
|
|
347
|
+
throw new Error(`${where}.${key} in ${configPath} must be a list of ${what}.`);
|
|
348
|
+
return [...new Set(list.map((name: string) => name.trim()))].sort();
|
|
349
|
+
};
|
|
350
|
+
const { profile } = server;
|
|
351
|
+
if (profile !== undefined && (typeof profile !== "string" || !ServerEnvProfile.namePattern.test(profile)))
|
|
352
|
+
throw new Error(
|
|
353
|
+
`${where}.profile in ${configPath} names env/env.server.<env>.<profile>.ts: a lowercase-first name of letters, digits and dashes.`,
|
|
354
|
+
);
|
|
355
|
+
const modules = names(server.modules, "modules", "module names or @<operationMode> selectors");
|
|
356
|
+
const env = AkanAppConfig.#desktopServerEnv(server.env, where, configPath);
|
|
357
|
+
const { restart } = server;
|
|
358
|
+
if (restart !== undefined && restart !== "forever" && !(Number.isInteger(restart) && (restart as number) > 0))
|
|
359
|
+
throw new Error(`${where}.restart in ${configPath} must be "forever" or a count of crashes in a row from 1.`);
|
|
360
|
+
const watchdog = AkanAppConfig.#desktopServerWatchdog(server.watchdog, where, configPath);
|
|
361
|
+
return {
|
|
362
|
+
...desktop,
|
|
363
|
+
server: {
|
|
364
|
+
omit: names(server.omit, "omit", "package names"),
|
|
365
|
+
...(profile ? { profile } : {}),
|
|
366
|
+
...(modules.length ? { modules } : {}),
|
|
367
|
+
...(Object.keys(env).length ? { env } : {}),
|
|
368
|
+
...(restart !== undefined ? { restart } : {}),
|
|
369
|
+
...(watchdog ? { watchdog } : {}),
|
|
370
|
+
},
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
//? The stage owns these: the operation mode, the database mode and what is served are what make it the carried server.
|
|
374
|
+
static readonly #stagedServerEnv = new Set([
|
|
375
|
+
"AKAN_PUBLIC_OPERATION_MODE",
|
|
376
|
+
"AKAN_DATABASE_MODE",
|
|
377
|
+
"AKAN_DATABASE_MODES",
|
|
378
|
+
"AKAN_SSR",
|
|
379
|
+
"AKAN_CSR",
|
|
380
|
+
"AKAN_MODULES",
|
|
381
|
+
]);
|
|
382
|
+
static #desktopServerEnv(env: unknown, where: string, configPath: string): Record<string, string> {
|
|
383
|
+
if (env === undefined) return {};
|
|
384
|
+
if (!AkanAppConfig.#isPlainObject(env) || Object.values(env).some((value) => typeof value !== "string"))
|
|
385
|
+
throw new Error(`${where}.env in ${configPath} must be an object of strings.`);
|
|
386
|
+
const staged = Object.keys(env).filter((key) => AkanAppConfig.#stagedServerEnv.has(key.toUpperCase()));
|
|
387
|
+
if (staged.length)
|
|
388
|
+
throw new Error(
|
|
389
|
+
`${where}.env in ${configPath} sets ${staged.join(", ")}, which the carried server's stage owns (modules: desktop.server.modules).`,
|
|
390
|
+
);
|
|
391
|
+
const secrets = ServerEnvProfile.secretPaths(env);
|
|
392
|
+
if (secrets.length)
|
|
393
|
+
throw new Error(
|
|
394
|
+
`${where}.env in ${configPath} holds secret-named values (${secrets.join(", ")}), and the app carries its env where anyone on the computer reads it. Leave them out.`,
|
|
395
|
+
);
|
|
396
|
+
return env as Record<string, string>;
|
|
397
|
+
}
|
|
398
|
+
static #desktopServerWatchdog(
|
|
399
|
+
watchdog: unknown,
|
|
400
|
+
where: string,
|
|
401
|
+
configPath: string,
|
|
402
|
+
): { intervalMs: number; failures: number } | null {
|
|
403
|
+
if (watchdog === undefined || watchdog === false) return null;
|
|
404
|
+
const { intervalMs = 10_000, failures = 3 } = (watchdog === true ? {} : watchdog) as Record<string, unknown>;
|
|
405
|
+
const whole = (value: unknown) => Number.isInteger(value) && (value as number) > 0;
|
|
406
|
+
if ((watchdog !== true && !AkanAppConfig.#isPlainObject(watchdog)) || !whole(intervalMs) || !whole(failures))
|
|
407
|
+
throw new Error(
|
|
408
|
+
`${where}.watchdog in ${configPath} must be true or { intervalMs, failures }, whole numbers from 1.`,
|
|
409
|
+
);
|
|
410
|
+
return { intervalMs: intervalMs as number, failures: failures as number };
|
|
348
411
|
}
|
|
349
412
|
static #platformIndexPath<T extends { indexPath?: string }>(section: T): T {
|
|
350
413
|
const { indexPath: rawIndexPath, ...rest } = section;
|
|
@@ -356,7 +419,7 @@ export class AkanAppConfig implements AppConfigResult {
|
|
|
356
419
|
const merge = (kept: unknown, value: unknown, top: boolean, key: string): unknown => {
|
|
357
420
|
if (!AkanAppConfig.#isPlainObject(kept) || !AkanAppConfig.#isPlainObject(value)) return value;
|
|
358
421
|
//? An icon or splash given as an object is one value: its image and color go together.
|
|
359
|
-
if (top && (key === "icon" || key === "splash")) return value;
|
|
422
|
+
if (top && (key === "icon" || key === "splash" || key === "backgroundColor")) return value;
|
|
360
423
|
const out: Record<string, unknown> = { ...kept };
|
|
361
424
|
for (const [inner, next] of Object.entries(value))
|
|
362
425
|
if (next !== undefined) out[inner] = merge(out[inner], next, false, inner);
|
|
@@ -381,10 +444,12 @@ export class AkanAppConfig implements AppConfigResult {
|
|
|
381
444
|
"buildNum",
|
|
382
445
|
"icon",
|
|
383
446
|
"splash",
|
|
447
|
+
"backgroundColor",
|
|
384
448
|
"permissions",
|
|
385
449
|
"plugins",
|
|
386
450
|
"deepLinks",
|
|
387
451
|
"updates",
|
|
452
|
+
"devtools",
|
|
388
453
|
"ios",
|
|
389
454
|
"android",
|
|
390
455
|
"desktop",
|
|
@@ -401,8 +466,18 @@ export class AkanAppConfig implements AppConfigResult {
|
|
|
401
466
|
"activity",
|
|
402
467
|
"files",
|
|
403
468
|
],
|
|
404
|
-
desktop: [
|
|
405
|
-
|
|
469
|
+
desktop: [
|
|
470
|
+
"indexPath",
|
|
471
|
+
"server",
|
|
472
|
+
"recovery",
|
|
473
|
+
"window",
|
|
474
|
+
"screenCapture",
|
|
475
|
+
"webview2",
|
|
476
|
+
"publisher",
|
|
477
|
+
"entitlements",
|
|
478
|
+
"dmg",
|
|
479
|
+
],
|
|
480
|
+
desktopServer: ["omit", "profile", "modules", "env", "restart", "watchdog"],
|
|
406
481
|
dmg: ["background", "background2x", "window", "iconSize", "textSize", "app", "applications"],
|
|
407
482
|
deepLinks: ["schemes", "domains"],
|
|
408
483
|
} as const;
|
|
@@ -586,7 +661,19 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
|
|
|
586
661
|
app.workspace.getPackageJson(),
|
|
587
662
|
]);
|
|
588
663
|
const resolved = typeof configImp === "function" ? configImp(app) : configImp;
|
|
589
|
-
|
|
664
|
+
//* `pipeline` is read by `akan pipeline` alone, from the module itself: off the instance, it never reaches akan.app.json.
|
|
665
|
+
const {
|
|
666
|
+
plugins,
|
|
667
|
+
pipeline: _pipeline,
|
|
668
|
+
...config
|
|
669
|
+
} = (resolved ?? {}) as AppConfigDeclaration & {
|
|
670
|
+
plugins?: AkanPlugin[];
|
|
671
|
+
pipeline?: unknown;
|
|
672
|
+
};
|
|
673
|
+
if ("subspace" in config)
|
|
674
|
+
throw new Error(
|
|
675
|
+
`apps/${app.name}/akan.config.ts declares \`subspace\`, a workspace setting: it belongs in the workspace root akan.config.ts.`,
|
|
676
|
+
);
|
|
590
677
|
const libContributions = await AkanAppConfig.#collectLibContributions(app, libs, bustImportCache);
|
|
591
678
|
return new AkanAppConfig(app, libs, rootPackageJson, config, baseDevEnv, plugins ?? [], libContributions);
|
|
592
679
|
}
|
|
@@ -664,6 +751,9 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
|
|
|
664
751
|
.map((lib) => `${lib}@${this.#resolveProductionDependencyVersion(lib)}`);
|
|
665
752
|
}
|
|
666
753
|
get akanVersion() {
|
|
754
|
+
return AkanAppConfig.akanVersion;
|
|
755
|
+
}
|
|
756
|
+
static get akanVersion() {
|
|
667
757
|
return getAkanPackageJson().version;
|
|
668
758
|
}
|
|
669
759
|
getProductionPackageJson(data: Partial<PackageJson> = {}): PackageJson {
|
|
@@ -752,6 +842,14 @@ export class AkanLibConfig implements LibConfigResult {
|
|
|
752
842
|
const configImp = await AkanAppConfig.importConfigModule(lib.cwdPath, { bustImportCache });
|
|
753
843
|
const resolved = typeof configImp === "function" ? configImp(lib) : configImp;
|
|
754
844
|
const { plugins, ...config } = (resolved ?? {}) as LibConfigDeclaration & { plugins?: AkanPlugin[] };
|
|
845
|
+
if ("pipeline" in config)
|
|
846
|
+
throw new Error(
|
|
847
|
+
`libs/${lib.name}/akan.config.ts declares \`pipeline\`, which a lib does not take: pipelines belong to apps and the workspace root, and a lib's build-time work goes in an akan build plugin.`,
|
|
848
|
+
);
|
|
849
|
+
if ("subspace" in config)
|
|
850
|
+
throw new Error(
|
|
851
|
+
`libs/${lib.name}/akan.config.ts declares \`subspace\`, a workspace setting: it belongs in the workspace root akan.config.ts.`,
|
|
852
|
+
);
|
|
755
853
|
return new AkanLibConfig(lib, config, plugins ?? []);
|
|
756
854
|
}
|
|
757
855
|
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { GlobalConfig } from "../cloud/globalConfig";
|
|
6
|
+
import { WorkspaceExecutor } from "../executors";
|
|
7
|
+
import { AkanWorkspaceConfig } from "./akanWorkspaceConfig";
|
|
8
|
+
|
|
9
|
+
const withWorkspace = async (files: Record<string, string>, run: (workspace: WorkspaceExecutor) => Promise<void>) => {
|
|
10
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), "akan-workspace-config-"));
|
|
11
|
+
try {
|
|
12
|
+
for (const [relative, content] of Object.entries(files)) {
|
|
13
|
+
fs.mkdirSync(path.dirname(path.join(root, relative)), { recursive: true });
|
|
14
|
+
fs.writeFileSync(path.join(root, relative), content);
|
|
15
|
+
}
|
|
16
|
+
await run(WorkspaceExecutor.fromRoot({ workspaceRoot: root, repoName: "repo" }));
|
|
17
|
+
} finally {
|
|
18
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
describe("AkanWorkspaceConfig", () => {
|
|
23
|
+
test("a workspace without a root akan.config.ts has an empty one", async () => {
|
|
24
|
+
await withWorkspace({}, async (workspace) => {
|
|
25
|
+
const config = await AkanWorkspaceConfig.from(workspace);
|
|
26
|
+
expect(config.file).toBeNull();
|
|
27
|
+
expect(config.subspace).toBeNull();
|
|
28
|
+
expect(config.pipeline).toBeNull();
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("reads subspace and pipeline, and the root file is never taken for an app", async () => {
|
|
33
|
+
await withWorkspace(
|
|
34
|
+
{
|
|
35
|
+
"akan.config.ts":
|
|
36
|
+
'export default { subspace: { subspaces: [{ name: "a", repo: "a.git", apps: ["web"] }] }, pipeline: { before: [] } };\n',
|
|
37
|
+
"apps/web/akan.config.ts": "export default {};\n",
|
|
38
|
+
},
|
|
39
|
+
async (workspace) => {
|
|
40
|
+
const config = await workspace.getConfig();
|
|
41
|
+
expect(config.file).toBe(path.join(workspace.workspaceRoot, "akan.config.ts"));
|
|
42
|
+
expect(config.subspace?.subspaces.map((subspace) => subspace.name)).toEqual(["a"]);
|
|
43
|
+
expect(config.pipeline).toEqual({ before: [] });
|
|
44
|
+
expect(await workspace.getApps()).toEqual(["web"]);
|
|
45
|
+
},
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("cloud.host names the cloud the CLI talks to without --host, https unless it is this computer", async () => {
|
|
50
|
+
const hostOf = (cloud: unknown) => new AkanWorkspaceConfig(null, { cloud }).cloudHost;
|
|
51
|
+
expect(hostOf(undefined)).toBeNull();
|
|
52
|
+
expect(hostOf({})).toBeNull();
|
|
53
|
+
expect(hostOf({ host: "https://cloud.example.com/" })).toBe("https://cloud.example.com");
|
|
54
|
+
expect(hostOf({ host: "https://example.com/akasys//" })).toBe("https://example.com/akasys");
|
|
55
|
+
expect(hostOf({ host: "http://localhost:8283" })).toBe("http://localhost:8283");
|
|
56
|
+
expect(() => hostOf({ host: "http://cloud.example.com" })).toThrow("https only");
|
|
57
|
+
expect(() => hostOf({ host: "cloud.example.com" })).toThrow("is not a URL");
|
|
58
|
+
expect(() => hostOf({ host: "https://cloud.example.com?x=1" })).toThrow("is not a URL");
|
|
59
|
+
expect(() => hostOf({ host: "https://cloud.example.com", token: "x" })).toThrow("keys it does not take: token");
|
|
60
|
+
|
|
61
|
+
const workspace = (cloudHost: string | null) => ({ getConfig: async () => ({ cloudHost }) });
|
|
62
|
+
expect(await GlobalConfig.hostFor(workspace("https://akasys.example.com"), "https://given.example.com")).toBe(
|
|
63
|
+
"https://given.example.com",
|
|
64
|
+
);
|
|
65
|
+
expect(await GlobalConfig.hostFor(workspace("https://akasys.example.com"), null)).toBe(
|
|
66
|
+
"https://akasys.example.com",
|
|
67
|
+
);
|
|
68
|
+
expect(await GlobalConfig.hostFor(workspace(null))).toBe(GlobalConfig.akanCloudHost);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("refuses app settings and a default export that is not an object", async () => {
|
|
72
|
+
await withWorkspace({ "akan.config.ts": 'export default { docker: { image: "x" } };\n' }, async (workspace) => {
|
|
73
|
+
expect(AkanWorkspaceConfig.from(workspace)).rejects.toThrow("declares `docker`, which it does not take");
|
|
74
|
+
});
|
|
75
|
+
await withWorkspace({ "akan.config.ts": "export default () => ({});\n" }, async (workspace) => {
|
|
76
|
+
expect(AkanWorkspaceConfig.from(workspace)).rejects.toThrow("must default-export a WorkspaceConfig object");
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import type { SubspaceConfigInput, WorkspaceConfig, WorkspacePipelineConfig } from "akanjs";
|
|
3
|
+
import type { WorkspaceExecutor } from "../executors";
|
|
4
|
+
import { FileSys } from "../fileSys";
|
|
5
|
+
import { AkanAppConfig } from "./akanConfig";
|
|
6
|
+
|
|
7
|
+
/** The workspace root's own `akan.config.ts`: settings that belong to no app. Absent, it is an empty one. */
|
|
8
|
+
export class AkanWorkspaceConfig {
|
|
9
|
+
static readonly fileName = "akan.config.ts";
|
|
10
|
+
static readonly #keys = ["subspace", "pipeline", "cloud"];
|
|
11
|
+
static readonly #loopbackHosts = ["localhost", "127.0.0.1", "[::1]"];
|
|
12
|
+
|
|
13
|
+
static async from(workspace: WorkspaceExecutor, { bustImportCache = false }: { bustImportCache?: boolean } = {}) {
|
|
14
|
+
const file = path.join(workspace.workspaceRoot, AkanWorkspaceConfig.fileName);
|
|
15
|
+
if (!(await FileSys.fileExists(file))) return new AkanWorkspaceConfig(null, {});
|
|
16
|
+
const input = await AkanAppConfig.importConfigModule(workspace.workspaceRoot, { bustImportCache });
|
|
17
|
+
return new AkanWorkspaceConfig(file, input);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Absolute path, or null when the workspace has none. */
|
|
21
|
+
readonly file: string | null;
|
|
22
|
+
readonly subspace: SubspaceConfigInput | null;
|
|
23
|
+
readonly pipeline: WorkspacePipelineConfig | null;
|
|
24
|
+
/** `cloud.host` without a trailing slash, or null for Akan Cloud. */
|
|
25
|
+
readonly cloudHost: string | null;
|
|
26
|
+
|
|
27
|
+
constructor(file: string | null, input: unknown) {
|
|
28
|
+
this.file = file;
|
|
29
|
+
if (typeof input !== "object" || input === null || Array.isArray(input))
|
|
30
|
+
throw new Error("The workspace root akan.config.ts must default-export a WorkspaceConfig object.");
|
|
31
|
+
for (const key of Object.keys(input))
|
|
32
|
+
if (!AkanWorkspaceConfig.#keys.includes(key))
|
|
33
|
+
throw new Error(
|
|
34
|
+
`The workspace root akan.config.ts declares \`${key}\`, which it does not take: it holds \`subspace\`, \`pipeline\` and \`cloud\`, and app or lib settings belong in apps/<app>/akan.config.ts or libs/<lib>/akan.config.ts.`,
|
|
35
|
+
);
|
|
36
|
+
const config = input as WorkspaceConfig;
|
|
37
|
+
this.subspace = config.subspace ?? null;
|
|
38
|
+
this.pipeline = config.pipeline ?? null;
|
|
39
|
+
this.cloudHost = AkanWorkspaceConfig.#cloudHost(config.cloud);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
//? The CLI sends its cloud login token to this host, so plain http is refused anywhere but this computer.
|
|
43
|
+
static #cloudHost(cloud: unknown): string | null {
|
|
44
|
+
if (cloud === undefined) return null;
|
|
45
|
+
const refuse = (why: string): never => {
|
|
46
|
+
throw new Error(`The workspace root akan.config.ts has cloud ${why}.`);
|
|
47
|
+
};
|
|
48
|
+
if (typeof cloud !== "object" || cloud === null || Array.isArray(cloud)) return refuse("that is not an object");
|
|
49
|
+
const unknown = Object.keys(cloud).filter((key) => key !== "host");
|
|
50
|
+
if (unknown.length) return refuse(`keys it does not take: ${unknown.join(", ")} (only \`host\`)`);
|
|
51
|
+
const { host } = cloud as { host?: unknown };
|
|
52
|
+
if (host === undefined) return null;
|
|
53
|
+
const url = typeof host === "string" && URL.canParse(host) ? new URL(host) : null;
|
|
54
|
+
if (!url || url.search || url.hash || url.username || url.password)
|
|
55
|
+
return refuse(`.host ${JSON.stringify(host)}, which is not a URL like "https://cloud.example.com"`);
|
|
56
|
+
if (
|
|
57
|
+
url.protocol !== "https:" &&
|
|
58
|
+
!(url.protocol === "http:" && AkanWorkspaceConfig.#loopbackHosts.includes(url.hostname))
|
|
59
|
+
)
|
|
60
|
+
return refuse(`.host ${JSON.stringify(host)}: https only, or http on this computer`);
|
|
61
|
+
return `${url.origin}${url.pathname.replace(/\/+$/, "")}`;
|
|
62
|
+
}
|
|
63
|
+
}
|
package/akanConfig/index.ts
CHANGED
package/akanConfig/types.ts
CHANGED
|
@@ -30,6 +30,10 @@ export type {
|
|
|
30
30
|
AppConfig,
|
|
31
31
|
AppConfigInput,
|
|
32
32
|
AppConfigResult,
|
|
33
|
+
AppPipelineConfig,
|
|
34
|
+
AppPipelineContext,
|
|
35
|
+
AppPipelineInput,
|
|
36
|
+
AppPipelineStageInput,
|
|
33
37
|
AppScanResult,
|
|
34
38
|
Arch,
|
|
35
39
|
BinPlatform,
|
|
@@ -48,8 +52,26 @@ export type {
|
|
|
48
52
|
LibScanResult,
|
|
49
53
|
NativeEnv,
|
|
50
54
|
NativePermission,
|
|
55
|
+
PipelineActionStageInput,
|
|
56
|
+
PipelineContext,
|
|
57
|
+
PipelineDuration,
|
|
58
|
+
PipelineGlob,
|
|
59
|
+
PipelineMatrixStageInput,
|
|
60
|
+
PipelineParallelStageInput,
|
|
61
|
+
PipelinePostInput,
|
|
62
|
+
PipelineShellStageInput,
|
|
63
|
+
PipelineStageBase,
|
|
64
|
+
PipelineStepInput,
|
|
65
|
+
PipelineStepLike,
|
|
66
|
+
PipelineWhen,
|
|
51
67
|
PkgScanResult,
|
|
52
68
|
ScanResult,
|
|
69
|
+
SubspaceConfigInput,
|
|
70
|
+
SubspaceDeclaration,
|
|
71
|
+
WorkspaceConfig,
|
|
72
|
+
WorkspacePipelineConfig,
|
|
73
|
+
WorkspacePipelineInput,
|
|
74
|
+
WorkspacePipelineStageInput,
|
|
53
75
|
WorkspaceScanResult,
|
|
54
76
|
} from "akanjs";
|
|
55
|
-
export { archs, binPlatforms } from "akanjs";
|
|
77
|
+
export { archs, binPlatforms, defaultAppPipeline } from "akanjs";
|
package/akanContext.ts
CHANGED
|
@@ -282,7 +282,7 @@ const generatedFiles = [
|
|
|
282
282
|
const validationCommands = [
|
|
283
283
|
"akan sync <app-or-lib>",
|
|
284
284
|
"akan lint <app-or-lib-or-pkg>",
|
|
285
|
-
"akan typecheck <app-
|
|
285
|
+
"akan typecheck <app-or-lib>",
|
|
286
286
|
"akan test <app-or-lib-or-pkg>",
|
|
287
287
|
"akan build <app-name>",
|
|
288
288
|
"akan doctor --strict --format json",
|
|
@@ -10,7 +10,7 @@ import { Executor } from "./executors";
|
|
|
10
10
|
describe("ApplicationBuildRunner", () => {
|
|
11
11
|
test("externalizes Akan optional backend dependencies", () => {
|
|
12
12
|
expect(AKAN_OPTIONAL_BACKEND_EXTERNALS).toEqual(
|
|
13
|
-
expect.arrayContaining(["
|
|
13
|
+
expect.arrayContaining(["bullmq", "ioredis", "postgres", "protobufjs"]),
|
|
14
14
|
);
|
|
15
15
|
});
|
|
16
16
|
|