@cosmicdrift/kumiko-dev-server 0.374.0 → 0.377.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/dist/compose-stacks.d.ts +3 -0
- package/dist/compose-stacks.js +11 -4
- package/dist/create-kumiko-server.d.ts +4 -2
- package/dist/create-kumiko-server.js +23 -25
- package/dist/managed-files/app-jobs.d.ts +4 -0
- package/dist/managed-files/app-jobs.js +68 -0
- package/dist/managed-files/check.d.ts +2 -0
- package/dist/managed-files/check.js +4 -2
- package/dist/managed-files/index.d.ts +2 -4
- package/dist/managed-files/index.js +2 -4
- package/dist/managed-files/registry.d.ts +7 -1
- package/dist/managed-files/registry.js +37 -5
- package/dist/managed-files/sync.js +10 -1
- package/dist/run-dev-app.d.ts +26 -10
- package/dist/run-dev-app.js +108 -37
- package/dist/scaffold-app-feature.js +1 -1
- package/dist/scaffold-app.d.ts +3 -0
- package/dist/scaffold-app.js +6 -7
- package/dist/scaffold-demo-tasks.js +1 -1
- package/dist/scaffold-deploy.d.ts +6 -0
- package/dist/scaffold-deploy.js +16 -1
- package/dist/testing.d.ts +17 -0
- package/dist/testing.js +46 -0
- package/package.json +19 -10
- package/src/changes.json +18 -0
- package/templates/managed/ci-public.yml.template +134 -0
- package/templates/managed/ci.yml.template +9 -6
- package/templates/managed/husky-pre-push.template +1 -7
- package/templates/managed/kumiko-schema.ts.template +16 -1
- package/templates/managed/release.yml.template +17 -1
- package/dist/managed-files/port-registry.d.ts +0 -17
- package/dist/managed-files/port-registry.js +0 -31
- package/dist/managed-files/static-app-config.d.ts +0 -16
- package/dist/managed-files/static-app-config.js +0 -99
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# CI for {{appName}}: a public repo cannot call the private reusable workflows in
|
|
2
|
+
# cosmicdriftgamestudio/infra, so every job here is self-contained on GitHub-hosted
|
|
3
|
+
# runners. The rollout is manual: see release.yml. Jobs only this app needs: name
|
|
4
|
+
# them `app-<name>` in this file (they survive `kumiko sync-managed`).
|
|
5
|
+
|
|
6
|
+
name: CI
|
|
7
|
+
|
|
8
|
+
on:
|
|
9
|
+
push:
|
|
10
|
+
branches: [main]
|
|
11
|
+
pull_request:
|
|
12
|
+
branches: [main]
|
|
13
|
+
|
|
14
|
+
concurrency:
|
|
15
|
+
group: ci-${{ github.ref }}
|
|
16
|
+
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
|
17
|
+
|
|
18
|
+
env:
|
|
19
|
+
HUSKY: "0"
|
|
20
|
+
|
|
21
|
+
jobs:
|
|
22
|
+
test:
|
|
23
|
+
if: github.event_name == 'pull_request'
|
|
24
|
+
runs-on: ubuntu-latest
|
|
25
|
+
timeout-minutes: 20
|
|
26
|
+
permissions:
|
|
27
|
+
contents: read
|
|
28
|
+
steps:
|
|
29
|
+
- uses: actions/checkout@v7
|
|
30
|
+
with:
|
|
31
|
+
fetch-depth: 0
|
|
32
|
+
- uses: oven-sh/setup-bun@v2
|
|
33
|
+
with:
|
|
34
|
+
bun-version: "1.4.0"
|
|
35
|
+
- run: bun install --frozen-lockfile
|
|
36
|
+
- run: bun run typecheck
|
|
37
|
+
- run: bun run lint
|
|
38
|
+
- run: bun kumiko check
|
|
39
|
+
- name: Schema + boot validate
|
|
40
|
+
env:
|
|
41
|
+
FILE_STORAGE_PROVIDER: "inmemory"
|
|
42
|
+
run: bun kumiko-schema validate
|
|
43
|
+
{{#uiGuards}}
|
|
44
|
+
- name: UI guards
|
|
45
|
+
run: bun kumiko-guards ui
|
|
46
|
+
{{/uiGuards}}
|
|
47
|
+
- run: bun run test
|
|
48
|
+
|
|
49
|
+
integration:
|
|
50
|
+
if: github.event_name == 'pull_request'
|
|
51
|
+
runs-on: ubuntu-latest
|
|
52
|
+
timeout-minutes: 30
|
|
53
|
+
permissions:
|
|
54
|
+
contents: read
|
|
55
|
+
services:
|
|
56
|
+
postgres:
|
|
57
|
+
image: postgres:18.4-alpine
|
|
58
|
+
env:
|
|
59
|
+
POSTGRES_USER: kumiko
|
|
60
|
+
POSTGRES_PASSWORD: kumiko
|
|
61
|
+
POSTGRES_DB: kumiko_dev
|
|
62
|
+
ports:
|
|
63
|
+
- 15432:5432
|
|
64
|
+
options: >-
|
|
65
|
+
--health-cmd "pg_isready -U kumiko -d kumiko_dev"
|
|
66
|
+
--health-interval 2s --health-timeout 3s --health-retries 30
|
|
67
|
+
redis:
|
|
68
|
+
image: redis:8-alpine
|
|
69
|
+
ports:
|
|
70
|
+
- 16379:6379
|
|
71
|
+
{{#meilisearch}}
|
|
72
|
+
meilisearch:
|
|
73
|
+
image: getmeili/meilisearch:v1.12
|
|
74
|
+
env:
|
|
75
|
+
MEILI_MASTER_KEY: kumiko-dev-key
|
|
76
|
+
MEILI_ENV: development
|
|
77
|
+
ports:
|
|
78
|
+
- 17700:7700
|
|
79
|
+
{{/meilisearch}}
|
|
80
|
+
steps:
|
|
81
|
+
- uses: actions/checkout@v7
|
|
82
|
+
- uses: oven-sh/setup-bun@v2
|
|
83
|
+
with:
|
|
84
|
+
bun-version: "1.4.0"
|
|
85
|
+
- run: bun install --frozen-lockfile
|
|
86
|
+
- name: Create test database
|
|
87
|
+
env:
|
|
88
|
+
PGPASSWORD: kumiko
|
|
89
|
+
run: psql -h localhost -p 15432 -U kumiko -d kumiko_dev -c "CREATE DATABASE kumiko_test;"
|
|
90
|
+
- name: Run integration tests
|
|
91
|
+
env:
|
|
92
|
+
NODE_ENV: test
|
|
93
|
+
KUMIKO_INSTANCE_ID: test-instance
|
|
94
|
+
DATABASE_URL: postgresql://kumiko:kumiko@localhost:15432/kumiko_dev
|
|
95
|
+
TEST_DATABASE_URL: postgresql://kumiko:kumiko@localhost:15432/kumiko_test
|
|
96
|
+
REDIS_URL: redis://localhost:16379
|
|
97
|
+
JWT_SECRET: testtesttesttesttesttesttesttest
|
|
98
|
+
run: bun run test:integration
|
|
99
|
+
|
|
100
|
+
# Build on main only; the rollout runs through the manual release workflow.
|
|
101
|
+
build:
|
|
102
|
+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
103
|
+
runs-on: ubuntu-26.04-arm
|
|
104
|
+
timeout-minutes: 30
|
|
105
|
+
permissions:
|
|
106
|
+
contents: read
|
|
107
|
+
packages: write
|
|
108
|
+
steps:
|
|
109
|
+
- uses: actions/checkout@v7
|
|
110
|
+
- uses: docker/setup-buildx-action@v4
|
|
111
|
+
- uses: docker/login-action@v4
|
|
112
|
+
with:
|
|
113
|
+
registry: ghcr.io
|
|
114
|
+
username: ${{ github.actor }}
|
|
115
|
+
password: ${{ secrets.GITHUB_TOKEN }}
|
|
116
|
+
- id: meta
|
|
117
|
+
uses: docker/metadata-action@v6
|
|
118
|
+
with:
|
|
119
|
+
images: ghcr.io/cosmicdriftgamestudio/{{appName}}
|
|
120
|
+
tags: |
|
|
121
|
+
type=raw,value=latest
|
|
122
|
+
type=sha,prefix=sha-,format=short
|
|
123
|
+
- uses: docker/build-push-action@v7
|
|
124
|
+
with:
|
|
125
|
+
context: .
|
|
126
|
+
file: deploy/Dockerfile
|
|
127
|
+
push: true
|
|
128
|
+
tags: ${{ steps.meta.outputs.tags }}
|
|
129
|
+
labels: ${{ steps.meta.outputs.labels }}
|
|
130
|
+
cache-from: type=gha
|
|
131
|
+
cache-to: type=gha,mode=max
|
|
132
|
+
platforms: linux/arm64
|
|
133
|
+
build-args: |
|
|
134
|
+
BUILD_VERSION=${{ github.sha }}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# CI for {{appName}}: a thin orchestrator over the reusable workflows in
|
|
2
|
-
# cosmicdriftgamestudio/infra. Jobs only this app needs
|
|
3
|
-
#
|
|
2
|
+
# cosmicdriftgamestudio/infra. Jobs only this app needs: name them `app-<name>`
|
|
3
|
+
# in this file (they survive `kumiko sync-managed`) or use an unmanaged workflow.
|
|
4
4
|
|
|
5
5
|
name: CI
|
|
6
6
|
|
|
@@ -19,16 +19,19 @@ env:
|
|
|
19
19
|
|
|
20
20
|
jobs:
|
|
21
21
|
test:
|
|
22
|
-
uses: cosmicdriftgamestudio/infra/.github/workflows/_app-test.yml@
|
|
22
|
+
uses: cosmicdriftgamestudio/infra/.github/workflows/_app-test.yml@{{infraRef}}
|
|
23
23
|
with:
|
|
24
24
|
ui-guards: {{uiGuards}}
|
|
25
25
|
# The boot gate in kumiko-schema validate demands a provider for file
|
|
26
26
|
# fields; CI only checks that one is configured.
|
|
27
27
|
file-storage-provider: "inmemory"
|
|
28
|
+
{{#docStatusGuardOff}}
|
|
29
|
+
doc-status-guard: false
|
|
30
|
+
{{/docStatusGuardOff}}
|
|
28
31
|
secrets: inherit
|
|
29
32
|
|
|
30
33
|
integration:
|
|
31
|
-
uses: cosmicdriftgamestudio/infra/.github/workflows/_app-integration.yml@
|
|
34
|
+
uses: cosmicdriftgamestudio/infra/.github/workflows/_app-integration.yml@{{infraRef}}
|
|
32
35
|
{{#minio}}
|
|
33
36
|
with:
|
|
34
37
|
minio: true
|
|
@@ -39,7 +42,7 @@ jobs:
|
|
|
39
42
|
# otherwise never report and block the merge.
|
|
40
43
|
e2e:
|
|
41
44
|
if: contains(fromJSON('["pull_request","merge_group"]'), github.event_name)
|
|
42
|
-
uses: cosmicdriftgamestudio/infra/.github/workflows/_app-e2e.yml@
|
|
45
|
+
uses: cosmicdriftgamestudio/infra/.github/workflows/_app-e2e.yml@{{infraRef}}
|
|
43
46
|
{{#e2eInputs}}
|
|
44
47
|
with:
|
|
45
48
|
{{/e2eInputs}}
|
|
@@ -58,7 +61,7 @@ jobs:
|
|
|
58
61
|
permissions:
|
|
59
62
|
contents: read
|
|
60
63
|
packages: write
|
|
61
|
-
uses: cosmicdriftgamestudio/infra/.github/workflows/_docker-build-push.yml@
|
|
64
|
+
uses: cosmicdriftgamestudio/infra/.github/workflows/_docker-build-push.yml@{{infraRef}}
|
|
62
65
|
with:
|
|
63
66
|
image-name: {{appName}}
|
|
64
67
|
dockerfile: deploy/Dockerfile
|
|
@@ -1,10 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env sh
|
|
2
|
-
# PRE_PUSH_SKIP is checked here too so the bypass works even without node_modules.
|
|
3
|
-
if [ "${PRE_PUSH_SKIP:-0}" = "1" ]; then
|
|
4
|
-
echo "[pre-push] PRE_PUSH_SKIP=1 — hook skipped"
|
|
5
|
-
exit 0
|
|
6
|
-
fi
|
|
7
|
-
|
|
8
2
|
REPO_ROOT="$(git rev-parse --show-toplevel)"
|
|
9
3
|
if [ -z "$REPO_ROOT" ]; then
|
|
10
4
|
echo "[pre-push] FATAL: git rev-parse --show-toplevel failed — refusing push" >&2
|
|
@@ -33,7 +27,7 @@ done
|
|
|
33
27
|
|
|
34
28
|
if [ -z "$BIN" ]; then
|
|
35
29
|
echo "[pre-push] kumiko-pre-push not found in any node_modules/.bin above $REPO_ROOT." >&2
|
|
36
|
-
echo "Run \`bun install
|
|
30
|
+
echo "Run \`bun install\`." >&2
|
|
37
31
|
exit 1
|
|
38
32
|
fi
|
|
39
33
|
|
|
@@ -5,9 +5,24 @@
|
|
|
5
5
|
import { allAppFeatures } from "@cosmicdrift/kumiko-framework/config";
|
|
6
6
|
import { collectTableMetas, type EntityTableMeta } from "@cosmicdrift/kumiko-framework/db";
|
|
7
7
|
import type { FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
|
|
8
|
+
import { composeFeatures } from "@cosmicdrift/kumiko-server-runtime/compose-features";
|
|
8
9
|
import appConfig from "../kumiko.config";
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
const CONFIGURED_FEATURES = allAppFeatures(appConfig);
|
|
12
|
+
|
|
13
|
+
// Auth apps mount config/user/tenant/auth-email-password through composeFeatures at boot;
|
|
14
|
+
// without them here the generated schema would miss their tables. Composing an already
|
|
15
|
+
// composed list is a no-op (app-side duplicates of bundled names are dropped).
|
|
16
|
+
// kumiko.config.ts `includeBundled` wins; otherwise an auth-foundation or sessions feature marks an auth app.
|
|
17
|
+
const HAS_AUTH =
|
|
18
|
+
appConfig.includeBundled ??
|
|
19
|
+
CONFIGURED_FEATURES.some(
|
|
20
|
+
(feature) => feature.name === "auth-foundation" || feature.name === "sessions",
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
export const FEATURES: readonly FeatureDefinition[] = composeFeatures([...CONFIGURED_FEATURES], {
|
|
24
|
+
includeBundled: HAS_AUTH,
|
|
25
|
+
});
|
|
11
26
|
|
|
12
27
|
// `kumiko check` validates the boot with the options kumiko.config.ts declares.
|
|
13
28
|
export const VALIDATE_BOOT_OPTIONS = appConfig.validateBootOptions;
|
|
@@ -44,12 +44,28 @@ jobs:
|
|
|
44
44
|
exit 1
|
|
45
45
|
fi
|
|
46
46
|
|
|
47
|
+
{{#privateDeploy}}
|
|
47
48
|
deploy:
|
|
48
49
|
needs: verify
|
|
49
|
-
uses: cosmicdriftgamestudio/infra/.github/workflows/_app-deploy.yml@
|
|
50
|
+
uses: cosmicdriftgamestudio/infra/.github/workflows/_app-deploy.yml@{{infraRef}}
|
|
50
51
|
with:
|
|
51
52
|
namespace: {{namespace}}
|
|
52
53
|
deployment: {{deployment}}
|
|
53
54
|
image-name: {{appName}}
|
|
54
55
|
git_sha: ${{ inputs.sha }}
|
|
55
56
|
secrets: inherit
|
|
57
|
+
{{/privateDeploy}}
|
|
58
|
+
{{#publicRollout}}
|
|
59
|
+
# A public repo cannot call the private infra workflows; the rollout runs from infra.
|
|
60
|
+
rollout-hint:
|
|
61
|
+
needs: verify
|
|
62
|
+
runs-on: ubuntu-latest
|
|
63
|
+
timeout-minutes: 5
|
|
64
|
+
steps:
|
|
65
|
+
- name: Print rollout command
|
|
66
|
+
env:
|
|
67
|
+
SHA: ${{ inputs.sha }}
|
|
68
|
+
run: |
|
|
69
|
+
echo "SHA verified. Roll out from a machine with access to the infra repo:"
|
|
70
|
+
echo "gh workflow run public-app-rollout.yml --repo cosmicdriftgamestudio/infra -f app={{appName}} -f git_sha=$SHA"
|
|
71
|
+
{{/publicRollout}}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { type AppPortRole } from "@cosmicdrift/kumiko-framework/config";
|
|
2
|
-
import { type StaticAppConfig } from "./static-app-config.ts";
|
|
3
|
-
export type ScannedApp = {
|
|
4
|
-
readonly app: string;
|
|
5
|
-
readonly config: StaticAppConfig;
|
|
6
|
-
};
|
|
7
|
-
export type PortClaim = {
|
|
8
|
-
readonly app: string;
|
|
9
|
-
readonly role: AppPortRole;
|
|
10
|
-
};
|
|
11
|
-
export type PortCollision = {
|
|
12
|
-
readonly port: number;
|
|
13
|
-
readonly claims: readonly PortClaim[];
|
|
14
|
-
};
|
|
15
|
-
/** Direct children of `root` with a kumiko.config.ts; worktrees and dot-dirs are skipped. */
|
|
16
|
-
export declare function scanWorkspaceApps(root: string): readonly ScannedApp[];
|
|
17
|
-
export declare function findPortCollisions(apps: readonly ScannedApp[]): readonly PortCollision[];
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
// Port registry across the apps of a workspace: every app declares its ports
|
|
2
|
-
// in kumiko.config.ts, and two apps (or two roles of one app) must never claim
|
|
3
|
-
// the same port, otherwise their local dev and E2E runs block each other.
|
|
4
|
-
import { existsSync, readdirSync } from "node:fs";
|
|
5
|
-
import { join } from "node:path";
|
|
6
|
-
import { APP_CONFIG_FILE, listAppPorts, } from "@cosmicdrift/kumiko-framework/config";
|
|
7
|
-
import { readStaticAppConfig } from "./static-app-config.js";
|
|
8
|
-
/** Direct children of `root` with a kumiko.config.ts; worktrees and dot-dirs are skipped. */
|
|
9
|
-
export function scanWorkspaceApps(root) {
|
|
10
|
-
return readdirSync(root, { withFileTypes: true })
|
|
11
|
-
.filter((entry) => entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules")
|
|
12
|
-
.filter((entry) => existsSync(join(root, entry.name, APP_CONFIG_FILE)))
|
|
13
|
-
.map((entry) => ({ app: entry.name, config: readStaticAppConfig(join(root, entry.name)) }))
|
|
14
|
-
.sort((a, b) => a.app.localeCompare(b.app));
|
|
15
|
-
}
|
|
16
|
-
export function findPortCollisions(apps) {
|
|
17
|
-
const claimsByPort = new Map();
|
|
18
|
-
for (const { app, config } of apps) {
|
|
19
|
-
if (config.kind !== "defined")
|
|
20
|
-
continue;
|
|
21
|
-
for (const { role, port } of listAppPorts(config.ports)) {
|
|
22
|
-
const claims = claimsByPort.get(port) ?? [];
|
|
23
|
-
claims.push({ app, role });
|
|
24
|
-
claimsByPort.set(port, claims);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
return [...claimsByPort.entries()]
|
|
28
|
-
.filter(([, claims]) => claims.length > 1)
|
|
29
|
-
.map(([port, claims]) => ({ port, claims }))
|
|
30
|
-
.sort((a, b) => a.port - b.port);
|
|
31
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { type AppPorts } from "@cosmicdrift/kumiko-framework/config";
|
|
2
|
-
export type StaticAppConfig = {
|
|
3
|
-
readonly kind: "missing";
|
|
4
|
-
}
|
|
5
|
-
/** A config without defineAppConfig: the app has not adopted the standard yet. */
|
|
6
|
-
| {
|
|
7
|
-
readonly kind: "legacy";
|
|
8
|
-
} | {
|
|
9
|
-
readonly kind: "invalid";
|
|
10
|
-
readonly message: string;
|
|
11
|
-
} | {
|
|
12
|
-
readonly kind: "defined";
|
|
13
|
-
readonly ports: AppPorts;
|
|
14
|
-
};
|
|
15
|
-
export declare function parseStaticAppConfig(source: string): StaticAppConfig;
|
|
16
|
-
export declare function readStaticAppConfig(appRoot: string): StaticAppConfig;
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
// Reads the ports out of kumiko.config.ts without importing it: importing
|
|
2
|
-
// would execute every feature module of the app, and the port registry scans
|
|
3
|
-
// sibling apps whose dependencies may not even be installed.
|
|
4
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
5
|
-
import { join } from "node:path";
|
|
6
|
-
import { APP_CONFIG_FILE, findAppPortProblems, } from "@cosmicdrift/kumiko-framework/config";
|
|
7
|
-
import { Project, SyntaxKind } from "ts-morph";
|
|
8
|
-
const INFRA_KEYS = [
|
|
9
|
-
"postgres",
|
|
10
|
-
"redis",
|
|
11
|
-
"meilisearch",
|
|
12
|
-
"s3",
|
|
13
|
-
];
|
|
14
|
-
function isInfraKey(name) {
|
|
15
|
-
return INFRA_KEYS.some((key) => key === name);
|
|
16
|
-
}
|
|
17
|
-
class StaticConfigError extends Error {
|
|
18
|
-
}
|
|
19
|
-
function propertyName(node) {
|
|
20
|
-
if (node.isKind(SyntaxKind.PropertyAssignment))
|
|
21
|
-
return node.getName().replace(/^["']|["']$/g, "");
|
|
22
|
-
return undefined;
|
|
23
|
-
}
|
|
24
|
-
function objectProperty(object, name) {
|
|
25
|
-
for (const property of object.getProperties()) {
|
|
26
|
-
if (property.isKind(SyntaxKind.SpreadAssignment) ||
|
|
27
|
-
property.isKind(SyntaxKind.ShorthandPropertyAssignment)) {
|
|
28
|
-
throw new StaticConfigError(`${name}: spread and shorthand properties cannot be read statically`);
|
|
29
|
-
}
|
|
30
|
-
if (propertyName(property) === name && property.isKind(SyntaxKind.PropertyAssignment)) {
|
|
31
|
-
return property.getInitializerOrThrow();
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
return undefined;
|
|
35
|
-
}
|
|
36
|
-
function numericLiteral(node, path) {
|
|
37
|
-
if (node === undefined)
|
|
38
|
-
throw new StaticConfigError(`${path} is missing`);
|
|
39
|
-
if (!node.isKind(SyntaxKind.NumericLiteral)) {
|
|
40
|
-
throw new StaticConfigError(`${path} must be a number literal so tooling can read it, got "${node.getText()}"`);
|
|
41
|
-
}
|
|
42
|
-
return node.getLiteralValue();
|
|
43
|
-
}
|
|
44
|
-
function objectLiteral(node, path) {
|
|
45
|
-
if (node === undefined)
|
|
46
|
-
throw new StaticConfigError(`${path} is missing`);
|
|
47
|
-
if (!node.isKind(SyntaxKind.ObjectLiteralExpression)) {
|
|
48
|
-
throw new StaticConfigError(`${path} must be an object literal, got "${node.getText()}"`);
|
|
49
|
-
}
|
|
50
|
-
return node;
|
|
51
|
-
}
|
|
52
|
-
function readInfraPorts(node) {
|
|
53
|
-
if (node === undefined)
|
|
54
|
-
return undefined;
|
|
55
|
-
const object = objectLiteral(node, "ports.infra");
|
|
56
|
-
const infra = {};
|
|
57
|
-
for (const property of object.getProperties()) {
|
|
58
|
-
const name = propertyName(property);
|
|
59
|
-
if (name === undefined || !isInfraKey(name)) {
|
|
60
|
-
throw new StaticConfigError(`ports.infra: unknown entry "${property.getText()}", expected ${INFRA_KEYS.join(", ")}`);
|
|
61
|
-
}
|
|
62
|
-
infra[name] = numericLiteral(objectProperty(object, name), `ports.infra.${name}`);
|
|
63
|
-
}
|
|
64
|
-
return infra;
|
|
65
|
-
}
|
|
66
|
-
export function parseStaticAppConfig(source) {
|
|
67
|
-
const project = new Project({ useInMemoryFileSystem: true, skipFileDependencyResolution: true });
|
|
68
|
-
const file = project.createSourceFile(APP_CONFIG_FILE, source);
|
|
69
|
-
const call = file
|
|
70
|
-
.getDescendantsOfKind(SyntaxKind.CallExpression)
|
|
71
|
-
.find((candidate) => candidate.getExpression().getText() === "defineAppConfig");
|
|
72
|
-
if (call === undefined)
|
|
73
|
-
return { kind: "legacy" };
|
|
74
|
-
try {
|
|
75
|
-
const config = objectLiteral(call.getArguments()[0], "defineAppConfig argument");
|
|
76
|
-
const ports = objectLiteral(objectProperty(config, "ports"), "ports");
|
|
77
|
-
const infra = readInfraPorts(objectProperty(ports, "infra"));
|
|
78
|
-
const parsed = {
|
|
79
|
-
dev: numericLiteral(objectProperty(ports, "dev"), "ports.dev"),
|
|
80
|
-
e2e: numericLiteral(objectProperty(ports, "e2e"), "ports.e2e"),
|
|
81
|
-
...(infra === undefined ? {} : { infra }),
|
|
82
|
-
};
|
|
83
|
-
const problems = findAppPortProblems(parsed);
|
|
84
|
-
if (problems.length > 0)
|
|
85
|
-
return { kind: "invalid", message: problems.join("; ") };
|
|
86
|
-
return { kind: "defined", ports: parsed };
|
|
87
|
-
}
|
|
88
|
-
catch (error) {
|
|
89
|
-
if (error instanceof StaticConfigError)
|
|
90
|
-
return { kind: "invalid", message: error.message };
|
|
91
|
-
throw error;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
export function readStaticAppConfig(appRoot) {
|
|
95
|
-
const path = join(appRoot, APP_CONFIG_FILE);
|
|
96
|
-
if (!existsSync(path))
|
|
97
|
-
return { kind: "missing" };
|
|
98
|
-
return parseStaticAppConfig(readFileSync(path, "utf-8"));
|
|
99
|
-
}
|