@cosmicdrift/kumiko-dev-server 0.368.0 → 0.370.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/build.d.ts +1 -1
- package/dist/cli.d.ts +3 -3
- package/dist/compose-stacks.d.ts +57 -3
- package/dist/compose-stacks.js +88 -9
- package/dist/host-dispatch-adapter.d.ts +7 -0
- package/dist/host-dispatch-adapter.js +31 -0
- package/dist/index.d.ts +21 -20
- package/dist/index.js +2 -1
- package/dist/init-deploy-cli.js +6 -0
- package/dist/managed-files/app-scripts.d.ts +13 -0
- package/dist/managed-files/app-scripts.js +40 -0
- package/dist/managed-files/check.d.ts +34 -0
- package/dist/managed-files/check.js +109 -0
- package/dist/managed-files/index.d.ts +8 -0
- package/dist/managed-files/index.js +8 -0
- package/dist/managed-files/jsonc.d.ts +1 -0
- package/dist/managed-files/jsonc.js +12 -0
- package/dist/managed-files/line-diff.d.ts +2 -0
- package/dist/managed-files/line-diff.js +60 -0
- package/dist/managed-files/marker.d.ts +17 -0
- package/dist/managed-files/marker.js +49 -0
- package/dist/managed-files/port-registry.d.ts +17 -0
- package/dist/managed-files/port-registry.js +31 -0
- package/dist/managed-files/registry.d.ts +51 -0
- package/dist/managed-files/registry.js +115 -0
- package/dist/managed-files/static-app-config.d.ts +16 -0
- package/dist/managed-files/static-app-config.js +99 -0
- package/dist/managed-files/sync.d.ts +19 -0
- package/dist/managed-files/sync.js +101 -0
- package/dist/run-dev-app.d.ts +6 -1
- package/dist/run-dev-app.js +21 -3
- package/dist/scaffold-app.d.ts +3 -3
- package/dist/scaffold-app.js +125 -199
- package/dist/scaffold-deploy.d.ts +9 -0
- package/dist/scaffold-deploy.js +34 -6
- package/dist/scaffold-feature.js +1 -0
- package/package.json +18 -9
- package/src/changes.json +19 -0
- package/templates/managed/cache-cleanup.yml.template +30 -0
- package/templates/managed/ci.yml.template +65 -0
- package/templates/managed/husky-pre-push.template +36 -0
- package/templates/managed/kumiko-schema.ts.template +12 -0
- package/templates/managed/release.yml.template +55 -0
package/dist/scaffold-deploy.js
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
14
14
|
import { dirname, join } from "node:path";
|
|
15
15
|
import { fileURLToPath } from "node:url";
|
|
16
|
+
import { parseJsonOrThrow } from "@cosmicdrift/kumiko-framework/utils";
|
|
16
17
|
import * as z from "zod";
|
|
17
18
|
import { isKebabSegment } from "./kebab.js";
|
|
18
19
|
const REGISTRY_CONFIG_FILES = ["bunfig.toml", ".npmrc"];
|
|
@@ -46,7 +47,7 @@ export function renderDeployFiles(options) {
|
|
|
46
47
|
// a `seeds/` directory (e.g. studio) crash in Docker-build with
|
|
47
48
|
// `failed to compute cache key: "/app/seeds": not found`.
|
|
48
49
|
const sourceDir = options.sourceDir ?? destinationRoot;
|
|
49
|
-
const detected = detectOptionalSurfaces(sourceDir, options.appName);
|
|
50
|
+
const detected = detectOptionalSurfaces(sourceDir, options.appName, options.ensuredRegistryConfigFiles ?? []);
|
|
50
51
|
if (detected.hasPrivateGhPackages && detected.registryConfigFiles.length === 0) {
|
|
51
52
|
// The Dockerfile only forwards the auth build-arg; without a registry
|
|
52
53
|
// config scope entry bun resolves @cosmicdriftgamestudio/* against npmjs
|
|
@@ -143,10 +144,17 @@ const LOCAL_DEP_SPEC_RE = /^(file:|workspace:|link:)/;
|
|
|
143
144
|
// full Postgres identifier grammar (no quoting support) since they are
|
|
144
145
|
// interpolated into a shell string, not passed through a driver's escaping.
|
|
145
146
|
const DB_IDENTIFIER_RE = /^[a-zA-Z0-9_][a-zA-Z0-9_-]{0,62}$/;
|
|
147
|
+
// Kubernetes object names (RFC 1123 label); they are interpolated into the
|
|
148
|
+
// generated release workflow, so nothing outside this charset may pass.
|
|
149
|
+
const K8S_NAME_RE = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
146
150
|
const kumikoDeployConfigSchema = z
|
|
147
151
|
.object({
|
|
148
152
|
dbUser: z.string().regex(DB_IDENTIFIER_RE).optional(),
|
|
149
153
|
dbName: z.string().regex(DB_IDENTIFIER_RE).optional(),
|
|
154
|
+
/** Namespace of the prod deployment; default = appName. */
|
|
155
|
+
namespace: z.string().regex(K8S_NAME_RE).optional(),
|
|
156
|
+
/** Name of the prod Deployment; default = appName. */
|
|
157
|
+
deployment: z.string().regex(K8S_NAME_RE).optional(),
|
|
150
158
|
// "directory" is the only behaviour left; the key stays valid so existing
|
|
151
159
|
// package.json files keep scaffolding.
|
|
152
160
|
stackNetwork: z
|
|
@@ -176,6 +184,25 @@ function parseDeployConfig(raw) {
|
|
|
176
184
|
: ["kumiko", "deploy", ...issue.path.map(String)].join(".");
|
|
177
185
|
throw new Error(`scaffoldDeploy: invalid package.json#${location} — ${issue?.message ?? "validation failed"}`);
|
|
178
186
|
}
|
|
187
|
+
/** Release target from `package.json#kumiko.deploy`; throws on an invalid deploy config. */
|
|
188
|
+
export function readReleaseTarget(sourceDir, appName) {
|
|
189
|
+
const pkgJsonPath = join(sourceDir, "package.json");
|
|
190
|
+
const raw = existsSync(pkgJsonPath)
|
|
191
|
+
? parseJsonOrThrow(readFileSync(pkgJsonPath, "utf-8"), pkgJsonPath)
|
|
192
|
+
: undefined;
|
|
193
|
+
const config = parseDeployConfig(extractDeployConfigRaw(raw));
|
|
194
|
+
const namespace = config.namespace ?? appName;
|
|
195
|
+
const deployment = config.deployment ?? appName;
|
|
196
|
+
for (const [field, value] of [
|
|
197
|
+
["namespace", namespace],
|
|
198
|
+
["deployment", deployment],
|
|
199
|
+
]) {
|
|
200
|
+
if (!K8S_NAME_RE.test(value)) {
|
|
201
|
+
throw new Error(`scaffoldDeploy: "${value}" cannot serve as the release ${field} (must match ${K8S_NAME_RE}); set package.json#kumiko.deploy.${field}`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return { namespace, deployment };
|
|
205
|
+
}
|
|
179
206
|
function resolveDbIdentifier(configured, appName, field, role) {
|
|
180
207
|
const effective = configured ?? appName;
|
|
181
208
|
// The default (appName) already passed isKebabSegment's charset check but
|
|
@@ -197,7 +224,7 @@ function resolveDeployConfig(deployConfigRaw, appName) {
|
|
|
197
224
|
dbName: resolveDbIdentifier(config.dbName, appName, "dbName", "DB name"),
|
|
198
225
|
};
|
|
199
226
|
}
|
|
200
|
-
function detectOptionalSurfaces(sourceDir, appName) {
|
|
227
|
+
function detectOptionalSurfaces(sourceDir, appName, ensuredRegistryConfigFiles) {
|
|
201
228
|
const hasSeeds = existsSync(join(sourceDir, "seeds"));
|
|
202
229
|
let hasPrivateGhPackages = false;
|
|
203
230
|
let installFromFullTree = false;
|
|
@@ -237,7 +264,7 @@ function detectOptionalSurfaces(sourceDir, appName) {
|
|
|
237
264
|
console.warn(message);
|
|
238
265
|
}
|
|
239
266
|
}
|
|
240
|
-
const registryConfigFiles = REGISTRY_CONFIG_FILES.filter((f) => existsSync(join(sourceDir, f)));
|
|
267
|
+
const registryConfigFiles = REGISTRY_CONFIG_FILES.filter((f) => ensuredRegistryConfigFiles.includes(f) || existsSync(join(sourceDir, f)));
|
|
241
268
|
return {
|
|
242
269
|
hasSeeds,
|
|
243
270
|
hasPrivateGhPackages,
|
|
@@ -247,10 +274,11 @@ function detectOptionalSurfaces(sourceDir, appName) {
|
|
|
247
274
|
};
|
|
248
275
|
}
|
|
249
276
|
// Unconsumed-mustache guard. After step 1+2 the only legitimate `{{`
|
|
250
|
-
// survivors are Docker/Go template tags (`{{.Name}}`, leading `.`)
|
|
277
|
+
// survivors are Docker/Go template tags (`{{.Name}}`, leading `.`) and GitHub
|
|
278
|
+
// Actions expressions (`${{ github.sha }}`, leading `$`). An
|
|
251
279
|
// orphan close-tag (`{{/foo}}` without an opener) matches neither step and
|
|
252
280
|
// would otherwise leak into the rendered file as a silent failure.
|
|
253
|
-
const ORPHAN_MUSTACHE_RE =
|
|
281
|
+
const ORPHAN_MUSTACHE_RE = /(?<!\$)\{\{(?!\.)[^}]*\}\}/;
|
|
254
282
|
export function render(source, subs, flags) {
|
|
255
283
|
// Step 1: handle mustache-style block conditionals `{{#flag}}...{{/flag}}`
|
|
256
284
|
// (multiline-aware via [\s\S]). When flag is truthy → keep inner content;
|
|
@@ -267,7 +295,7 @@ export function render(source, subs, flags) {
|
|
|
267
295
|
// narrow: lowercase-leading identifier followed by alphanumerics. That
|
|
268
296
|
// excludes Docker/Go template syntax like `{{.Name}}` (leading `.`)
|
|
269
297
|
// which appears verbatim in the migrate-step.sh shell snippet.
|
|
270
|
-
result = result.replace(
|
|
298
|
+
result = result.replace(/(?<!\$)\{\{([a-z][a-zA-Z0-9]*)\}\}/g, (full, key) => {
|
|
271
299
|
const value = subs[key];
|
|
272
300
|
if (value === undefined) {
|
|
273
301
|
throw new Error(`scaffoldDeploy.render: unknown placeholder "${full}"`);
|
package/dist/scaffold-feature.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-dev-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.370.0",
|
|
4
4
|
"description": "Dev-tooling for Kumiko apps: local dev-server bootstrap (runDevApp), scaffolding. Its compose-stacks/env-schema subpaths are consumed at prod boot too — see @cosmicdrift/kumiko-server-runtime for the prod runner itself.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"runtime": "dev",
|
|
19
19
|
"bunOnlyExports": [
|
|
20
20
|
".",
|
|
21
|
+
"./managed-files",
|
|
21
22
|
"./setup-test-stack-from-features"
|
|
22
23
|
]
|
|
23
24
|
},
|
|
@@ -38,6 +39,10 @@
|
|
|
38
39
|
"types": "./dist/env-schema.d.ts",
|
|
39
40
|
"default": "./dist/env-schema.js"
|
|
40
41
|
},
|
|
42
|
+
"./managed-files": {
|
|
43
|
+
"types": "./dist/managed-files/index.d.ts",
|
|
44
|
+
"default": "./dist/managed-files/index.js"
|
|
45
|
+
},
|
|
41
46
|
"./setup-test-stack-from-features": {
|
|
42
47
|
"types": "./dist/setup-test-stack-from-features.d.ts",
|
|
43
48
|
"default": "./dist/setup-test-stack-from-features.js"
|
|
@@ -67,14 +72,14 @@
|
|
|
67
72
|
"kumiko-upgrade": "./bin/kumiko-upgrade.ts"
|
|
68
73
|
},
|
|
69
74
|
"dependencies": {
|
|
70
|
-
"@cosmicdrift/kumiko-bundled-features": "0.
|
|
71
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
72
|
-
"@cosmicdrift/kumiko-headless": "0.
|
|
73
|
-
"@cosmicdrift/kumiko-renderer-mail-html": "0.
|
|
74
|
-
"@cosmicdrift/kumiko-renderer-web": "0.
|
|
75
|
-
"@cosmicdrift/kumiko-server-runtime": "0.
|
|
76
|
-
"@cosmicdrift/kumiko-testing": "0.
|
|
77
|
-
"@cosmicdrift/kumiko-types": "0.
|
|
75
|
+
"@cosmicdrift/kumiko-bundled-features": "0.370.0",
|
|
76
|
+
"@cosmicdrift/kumiko-framework": "0.370.0",
|
|
77
|
+
"@cosmicdrift/kumiko-headless": "0.370.0",
|
|
78
|
+
"@cosmicdrift/kumiko-renderer-mail-html": "0.370.0",
|
|
79
|
+
"@cosmicdrift/kumiko-renderer-web": "0.370.0",
|
|
80
|
+
"@cosmicdrift/kumiko-server-runtime": "0.370.0",
|
|
81
|
+
"@cosmicdrift/kumiko-testing": "0.370.0",
|
|
82
|
+
"@cosmicdrift/kumiko-types": "0.370.0",
|
|
78
83
|
"bun-types": "^1.3.13",
|
|
79
84
|
"ts-morph": "^28.0.0",
|
|
80
85
|
"zod": "^4.4.3"
|
|
@@ -99,6 +104,10 @@
|
|
|
99
104
|
"types": "./dist/env-schema.d.ts",
|
|
100
105
|
"default": "./dist/env-schema.js"
|
|
101
106
|
},
|
|
107
|
+
"./managed-files": {
|
|
108
|
+
"types": "./dist/managed-files/index.d.ts",
|
|
109
|
+
"default": "./dist/managed-files/index.js"
|
|
110
|
+
},
|
|
102
111
|
"./setup-test-stack-from-features": {
|
|
103
112
|
"types": "./dist/setup-test-stack-from-features.d.ts",
|
|
104
113
|
"default": "./dist/setup-test-stack-from-features.js"
|
package/src/changes.json
CHANGED
|
@@ -1,4 +1,23 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.370.0",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "runDevApp binds the MFA token secrets",
|
|
6
|
+
"detail": "When auth-mfa is mounted and JWT_SECRET is set, runDevApp binds the MFA token secrets the same way runProdApp does."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"version": "0.370.0",
|
|
10
|
+
"type": "improvement",
|
|
11
|
+
"title": "composeAuthStack, composeSaasStack and composeSecurityStack",
|
|
12
|
+
"detail": "composeAuthStack({ sessions?, mfa?, accountSecurity? }) mounts auth-foundation and sessions, with mfa also auth-mfa and account-security; the password login still comes from the auth option or composeFeatures. composeSaasStack({ billing: \"stripe\" | \"mollie\", tiers, caps?, waitlist?, billingFoundation? }) mounts billing-foundation and tier-engine, cap-counter plus cap-overview with caps, waitlist with waitlist. With \"stripe\" the runProdApp billing option mounts subscription-stripe and the tier-sync webhook route; with \"mollie\" the stack mounts subscription-mollie from the mollie options and the app adds the webhook route. composeSecurityStack({ includeSessions? }) mounts sessions, crypto-shredding, rate-limiting and audit. composeGdprStack({ selfService: { userDataRights? } }) adds sessions, user-data-rights and user-profile in the order dsgvoSelfServiceFeatures used. billing-foundation requires tenant-lifecycle and compliance-profiles, which composeGdprStack({ tenantLifecycle: true }) mounts. user-profile in selfService requires user-data-rights-defaults and auth-email-password; composeUserDataRightsStack mounts the defaults, and the login comes from the auth option. Recipe: samples/recipes/compose-stacks."
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"version": "0.370.0",
|
|
16
|
+
"type": "improvement",
|
|
17
|
+
"title": "Managed files: one template source for the scaffold, kumiko check managed and kumiko sync-managed",
|
|
18
|
+
"detail": "deploy/Dockerfile, deploy/Dockerfile.dockerignore, deploy/migrate-step.sh, .husky/pre-push, .github/workflows/{ci,release,cache-cleanup}.yml, the bunfigs and kumiko/schema.ts carry a kumiko-managed marker on line 1 (line 2 after a shebang or # syntax= line). kumiko new app writes them, plus kumiko.config.ts, bin/server.ts (was bin/dev.ts), bin/env-schema.ts, e2e/_helpers and src/__tests__/_setup.ts; bunfig.ci.toml is gone. kumiko/schema.ts is generated from kumiko.config.ts. kumiko-init-deploy prints a deprecation notice and goes away in the next release. Release targets come from package.json#kumiko.deploy.namespace and .deployment (default: the app name).",
|
|
19
|
+
"migration": "Existing apps: add kumiko.config.ts with defineAppConfig, then run kumiko sync-managed (dry run), kumiko sync-managed --write --adopt for files that are identical or should follow the template, and mark deliberate deviations with \"# kumiko-managed: local <reason>\" on line 2. Until kumiko.config.ts calls defineAppConfig, kumiko check only warns about drift and missing scripts."
|
|
20
|
+
},
|
|
2
21
|
{
|
|
3
22
|
"version": "0.368.0",
|
|
4
23
|
"type": "breaking",
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
name: cache-cleanup
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
schedule:
|
|
5
|
+
- cron: "0 3 * * 1"
|
|
6
|
+
workflow_dispatch: {}
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
cleanup:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
permissions:
|
|
12
|
+
actions: write
|
|
13
|
+
steps:
|
|
14
|
+
- name: Delete caches unused for 3+ days
|
|
15
|
+
# Actions cache is branch-scoped: every PR branch writes its own copy
|
|
16
|
+
# even with an identical key, and GitHub only auto-evicts on the
|
|
17
|
+
# per-repo 10GB cap or 7-day-unused rule. This keeps repos well under
|
|
18
|
+
# that cap instead of relying on eviction churn.
|
|
19
|
+
env:
|
|
20
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
21
|
+
REPO: ${{ github.repository }}
|
|
22
|
+
run: |
|
|
23
|
+
cutoff=$(date -u -d '3 days ago' +%s)
|
|
24
|
+
gh api --paginate "repos/$REPO/actions/caches?per_page=100" --jq '.actions_caches[] | "\(.id) \(.last_accessed_at)"' |
|
|
25
|
+
while read -r id ts; do
|
|
26
|
+
age=$(date -u -d "$ts" +%s)
|
|
27
|
+
if [ "$age" -lt "$cutoff" ]; then
|
|
28
|
+
gh api -X DELETE "repos/$REPO/actions/caches/$id" && echo "deleted $id"
|
|
29
|
+
fi
|
|
30
|
+
done
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# CI for {{appName}}: a thin orchestrator over the reusable workflows in
|
|
2
|
+
# cosmicdriftgamestudio/infra. Jobs only this app needs go into an unmanaged
|
|
3
|
+
# workflow next to this one (e.g. ci-extra.yml).
|
|
4
|
+
|
|
5
|
+
name: CI
|
|
6
|
+
|
|
7
|
+
on:
|
|
8
|
+
push:
|
|
9
|
+
branches: [main]
|
|
10
|
+
pull_request:
|
|
11
|
+
branches: [main]
|
|
12
|
+
|
|
13
|
+
concurrency:
|
|
14
|
+
group: ci-${{ github.ref }}
|
|
15
|
+
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
|
16
|
+
|
|
17
|
+
env:
|
|
18
|
+
HUSKY: "0"
|
|
19
|
+
|
|
20
|
+
jobs:
|
|
21
|
+
test:
|
|
22
|
+
uses: cosmicdriftgamestudio/infra/.github/workflows/_app-test.yml@main
|
|
23
|
+
with:
|
|
24
|
+
ui-guards: {{uiGuards}}
|
|
25
|
+
# The boot gate in kumiko-schema validate demands a provider for file
|
|
26
|
+
# fields; CI only checks that one is configured.
|
|
27
|
+
file-storage-provider: "inmemory"
|
|
28
|
+
secrets: inherit
|
|
29
|
+
|
|
30
|
+
integration:
|
|
31
|
+
uses: cosmicdriftgamestudio/infra/.github/workflows/_app-integration.yml@main
|
|
32
|
+
{{#minio}}
|
|
33
|
+
with:
|
|
34
|
+
minio: true
|
|
35
|
+
{{/minio}}
|
|
36
|
+
secrets: inherit
|
|
37
|
+
|
|
38
|
+
# merge_group included: as a required check in a merge queue, e2e would
|
|
39
|
+
# otherwise never report and block the merge.
|
|
40
|
+
e2e:
|
|
41
|
+
if: contains(fromJSON('["pull_request","merge_group"]'), github.event_name)
|
|
42
|
+
uses: cosmicdriftgamestudio/infra/.github/workflows/_app-e2e.yml@main
|
|
43
|
+
{{#e2eInputs}}
|
|
44
|
+
with:
|
|
45
|
+
{{/e2eInputs}}
|
|
46
|
+
{{#meilisearch}}
|
|
47
|
+
meilisearch: true
|
|
48
|
+
{{/meilisearch}}
|
|
49
|
+
{{#minio}}
|
|
50
|
+
minio: true
|
|
51
|
+
{{/minio}}
|
|
52
|
+
secrets: inherit
|
|
53
|
+
|
|
54
|
+
# Build on main only; the rollout runs through the manual release workflow.
|
|
55
|
+
build:
|
|
56
|
+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
57
|
+
needs: [test, integration]
|
|
58
|
+
permissions:
|
|
59
|
+
contents: read
|
|
60
|
+
packages: write
|
|
61
|
+
uses: cosmicdriftgamestudio/infra/.github/workflows/_docker-build-push.yml@main
|
|
62
|
+
with:
|
|
63
|
+
image-name: {{appName}}
|
|
64
|
+
dockerfile: deploy/Dockerfile
|
|
65
|
+
secrets: inherit
|
|
@@ -0,0 +1,36 @@
|
|
|
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
|
+
REPO_ROOT="$(git rev-parse --show-toplevel)"
|
|
9
|
+
if [ -z "$REPO_ROOT" ]; then
|
|
10
|
+
echo "[pre-push] FATAL: git rev-parse --show-toplevel failed — refusing push" >&2
|
|
11
|
+
exit 1
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
# Walk up to / so a worktree without its own node_modules resolves via the
|
|
15
|
+
# parent install; `[ -x ]` also skips a broken (uninstalled) symlink.
|
|
16
|
+
BIN=""
|
|
17
|
+
DIR="$REPO_ROOT"
|
|
18
|
+
while true; do
|
|
19
|
+
CANDIDATE="$DIR/node_modules/.bin/kumiko-pre-push"
|
|
20
|
+
if [ -x "$CANDIDATE" ]; then
|
|
21
|
+
BIN="$CANDIDATE"
|
|
22
|
+
break
|
|
23
|
+
fi
|
|
24
|
+
if [ "$DIR" = "/" ]; then
|
|
25
|
+
break
|
|
26
|
+
fi
|
|
27
|
+
DIR="$(dirname "$DIR")"
|
|
28
|
+
done
|
|
29
|
+
|
|
30
|
+
if [ -z "$BIN" ]; then
|
|
31
|
+
echo "[pre-push] kumiko-pre-push not found in any node_modules/.bin above $REPO_ROOT." >&2
|
|
32
|
+
echo "Run \`bun install\`, or set PRE_PUSH_SKIP=1 to bypass this hook." >&2
|
|
33
|
+
exit 1
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
cd "$REPO_ROOT" && exec "$BIN" "$@"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Input of the schema tooling (kumiko-schema generate|apply|validate and the
|
|
2
|
+
// boot step of kumiko check), derived from kumiko.config.ts. Edit the feature
|
|
3
|
+
// lists there.
|
|
4
|
+
|
|
5
|
+
import { allAppFeatures } from "@cosmicdrift/kumiko-framework/config";
|
|
6
|
+
import { collectTableMetas, type EntityTableMeta } from "@cosmicdrift/kumiko-framework/db";
|
|
7
|
+
import type { FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
|
|
8
|
+
import appConfig from "../kumiko.config";
|
|
9
|
+
|
|
10
|
+
export const FEATURES: readonly FeatureDefinition[] = allAppFeatures(appConfig);
|
|
11
|
+
|
|
12
|
+
export const ENTITY_METAS: readonly EntityTableMeta[] = collectTableMetas(FEATURES);
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
inputs:
|
|
6
|
+
sha:
|
|
7
|
+
description: "Full 40-char main SHA; image sha-<7> must exist"
|
|
8
|
+
required: true
|
|
9
|
+
type: string
|
|
10
|
+
|
|
11
|
+
concurrency:
|
|
12
|
+
group: release-{{appName}}
|
|
13
|
+
cancel-in-progress: false
|
|
14
|
+
|
|
15
|
+
permissions:
|
|
16
|
+
contents: read
|
|
17
|
+
actions: read
|
|
18
|
+
|
|
19
|
+
jobs:
|
|
20
|
+
verify:
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
timeout-minutes: 5
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@v7
|
|
25
|
+
with:
|
|
26
|
+
fetch-depth: 0
|
|
27
|
+
- name: Verify SHA
|
|
28
|
+
env:
|
|
29
|
+
SHA: ${{ inputs.sha }}
|
|
30
|
+
GH_TOKEN: ${{ github.token }}
|
|
31
|
+
run: |
|
|
32
|
+
set -euo pipefail
|
|
33
|
+
if ! [[ "$SHA" =~ ^[0-9a-f]{40}$ ]]; then
|
|
34
|
+
echo "::error::sha must be a full 40-char lowercase hex SHA."
|
|
35
|
+
exit 1
|
|
36
|
+
fi
|
|
37
|
+
if ! git merge-base --is-ancestor "$SHA" origin/main; then
|
|
38
|
+
echo "::error::$SHA is not an ancestor of origin/main."
|
|
39
|
+
exit 1
|
|
40
|
+
fi
|
|
41
|
+
count=$(gh api "repos/$GITHUB_REPOSITORY/actions/workflows/ci.yml/runs?head_sha=$SHA&event=push&status=success" --jq '.total_count')
|
|
42
|
+
if [ "${count:-0}" -le 0 ]; then
|
|
43
|
+
echo "::error::No successful CI push run found for $SHA."
|
|
44
|
+
exit 1
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
deploy:
|
|
48
|
+
needs: verify
|
|
49
|
+
uses: cosmicdriftgamestudio/infra/.github/workflows/_app-deploy.yml@main
|
|
50
|
+
with:
|
|
51
|
+
namespace: {{namespace}}
|
|
52
|
+
deployment: {{deployment}}
|
|
53
|
+
image-name: {{appName}}
|
|
54
|
+
git_sha: ${{ inputs.sha }}
|
|
55
|
+
secrets: inherit
|