@monster-orange/den 0.0.0-stage → 0.1.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 +67 -0
- package/LICENSE +21 -0
- package/README.md +166 -2
- package/bin/den +61 -0
- package/docs/distributions.md +256 -0
- package/make/demo.mk +82 -0
- package/make/den.mk +80 -0
- package/make/journeys.mk +57 -0
- package/make/local_dev.mk +154 -0
- package/make/security.mk +59 -0
- package/make/test.mk +405 -0
- package/package.json +104 -4
- package/scripts/auth-configure.ts +415 -0
- package/scripts/heal-config.ts +92 -0
- package/scripts/jsonl-playwright-reporter.js +70 -0
- package/scripts/manifest-read.cjs +91 -0
- package/scripts/setup-1password.ts +570 -0
- package/scripts/setup-secrets.ts +149 -0
- package/scripts/view-results.js +156 -0
- package/service/install.sh +112 -0
- package/src/pillars/api/fixtures/index.ts +128 -0
- package/src/pillars/api/fixtures/openapi-validator.ts +111 -0
- package/src/pillars/api/global-setup.ts +103 -0
- package/src/pillars/api/playwright.config.ts +105 -0
- package/src/pillars/api/schemas/example-api.json +110 -0
- package/src/pillars/api/tests/health/health.spec.ts +19 -0
- package/src/pillars/api/tests/health/manifest.yml +6 -0
- package/src/pillars/browser/fixtures/index.ts +232 -0
- package/src/pillars/browser/fixtures/package.json +10 -0
- package/src/pillars/browser/global-setup.ts +140 -0
- package/src/pillars/browser/global-teardown.ts +48 -0
- package/src/pillars/browser/playwright.config.ts +212 -0
- package/src/pillars/browser/setup.ts +219 -0
- package/src/pillars/journeys/app-config.ts +237 -0
- package/src/pillars/journeys/discover.ts +160 -0
- package/src/pillars/journeys/hooks/deny-shell.cjs +44 -0
- package/src/pillars/journeys/observability/events.ts +288 -0
- package/src/pillars/journeys/observability/sdk-mapper.ts +306 -0
- package/src/pillars/journeys/report/friction.ts +304 -0
- package/src/pillars/journeys/report/junit.ts +105 -0
- package/src/pillars/journeys/report/render.ts +273 -0
- package/src/pillars/journeys/report/results.ts +323 -0
- package/src/pillars/journeys/run.ts +673 -0
- package/src/pillars/journeys/runner.ts +1025 -0
- package/src/pillars/journeys/sandbox.ts +406 -0
- package/src/pillars/journeys/spec/loader.ts +152 -0
- package/src/pillars/journeys/spec/schema.ts +208 -0
- package/src/pillars/journeys/teardown.ts +98 -0
- package/src/pillars/journeys/tools/browser-session.ts +219 -0
- package/src/pillars/journeys/tools/browser-tool.ts +914 -0
- package/src/pillars/journeys/tools/http-tool.ts +564 -0
- package/src/pillars/journeys/tools/ledger.ts +278 -0
- package/src/pillars/journeys/tools/snapshot-store.ts +117 -0
- package/src/pillars/journeys/verify/assertions.ts +502 -0
- package/src/pillars/journeys/verify/verdict.ts +71 -0
- package/src/pillars/perf/build-report.ts +36 -0
- package/src/pillars/perf/k6.config.js +112 -0
- package/src/pillars/perf/run.ts +345 -0
- package/src/pillars/perf/scenarios/load-profile.js +97 -0
- package/src/pillars/perf/staging.ts +73 -0
- package/src/pillars/security/discover.ts +108 -0
- package/src/pillars/security/findings.ts +151 -0
- package/src/pillars/security/plan.ts +355 -0
- package/src/pillars/security/run.ts +729 -0
- package/src/pillars/smoke/admin-probe.ts +68 -0
- package/src/pillars/smoke/base-url.ts +56 -0
- package/src/pillars/smoke/cleanup.ts +8 -0
- package/src/pillars/smoke/discover.ts +237 -0
- package/src/pillars/smoke/ensure-smoke-teardown.ts +40 -0
- package/src/pillars/smoke/fixture-manager.ts +217 -0
- package/src/pillars/smoke/fixtures.ts +468 -0
- package/src/pillars/smoke/global-setup-all.ts +262 -0
- package/src/pillars/smoke/global-setup-global.ts +262 -0
- package/src/pillars/smoke/global-setup.ts +273 -0
- package/src/pillars/smoke/global-teardown-all.ts +8 -0
- package/src/pillars/smoke/global-teardown-global.ts +8 -0
- package/src/pillars/smoke/global-teardown.ts +139 -0
- package/src/pillars/smoke/playwright-all.config.ts +147 -0
- package/src/pillars/smoke/playwright-global.config.ts +141 -0
- package/src/pillars/smoke/playwright.config.ts +162 -0
- package/src/pillars/smoke/setup-env.ts +132 -0
- package/src/pillars/smoke/setup-global.ts +126 -0
- package/src/pillars/smoke/setup.ts +115 -0
- package/src/pillars/smoke/smoke-repo.ts +16 -0
- package/src/pillars/smoke/staging.ts +49 -0
- package/src/pillars/suites/run.ts +147 -0
- package/src/scripts/configure-aws-profiles.ts +114 -0
- package/src/scripts/register-repos.ts +307 -0
- package/src/scripts/setup-default-workspace.ts +203 -0
- package/src/shared/applications.ts +138 -0
- package/src/shared/auth/api-token.ts +416 -0
- package/src/shared/auth/auth0-helper.ts +284 -0
- package/src/shared/auth/headless-login.ts +265 -0
- package/src/shared/auth/jwt-helper.ts +186 -0
- package/src/shared/auth/sudo-vault.ts +69 -0
- package/src/shared/auth/test-accounts.ts +98 -0
- package/src/shared/auth/token-injection.ts +184 -0
- package/src/shared/auth/token-providers/auth0.ts +80 -0
- package/src/shared/auth/token-providers/index.ts +182 -0
- package/src/shared/auth/token-refresh.ts +273 -0
- package/src/shared/banner.ts +15 -0
- package/src/shared/brand.ts +79 -0
- package/src/shared/cli-tabs.ts +131 -0
- package/src/shared/constants.ts +128 -0
- package/src/shared/den-config.ts +566 -0
- package/src/shared/dev-metrics/emit.ts +354 -0
- package/src/shared/edge/config.ts +229 -0
- package/src/shared/edge/render.ts +221 -0
- package/src/shared/edge/runtime.ts +268 -0
- package/src/shared/edge/types.ts +62 -0
- package/src/shared/engine.ts +67 -0
- package/src/shared/environment/aws-profiles.ts +49 -0
- package/src/shared/environment/env-selector.ts +161 -0
- package/src/shared/environment/playwright-env.ts +82 -0
- package/src/shared/environment-manager/catalog.ts +77 -0
- package/src/shared/environment-manager/clone-guard.ts +163 -0
- package/src/shared/environment-manager/dag.ts +286 -0
- package/src/shared/environment-manager/default-workspace.ts +182 -0
- package/src/shared/environment-manager/derivation/alias.ts +90 -0
- package/src/shared/environment-manager/derivation/cache.ts +97 -0
- package/src/shared/environment-manager/derivation/derive-graph.ts +421 -0
- package/src/shared/environment-manager/derivation/derived-up.ts +354 -0
- package/src/shared/environment-manager/derivation/load-alias.ts +11 -0
- package/src/shared/environment-manager/derivation/rules-compose.ts +42 -0
- package/src/shared/environment-manager/derivation/rules-events.ts +50 -0
- package/src/shared/environment-manager/derivation/rules-http.ts +247 -0
- package/src/shared/environment-manager/derivation/rules-infra.ts +64 -0
- package/src/shared/environment-manager/derivation/rules-readiness.ts +96 -0
- package/src/shared/environment-manager/derivation/scan.ts +160 -0
- package/src/shared/environment-manager/derivation/text.ts +40 -0
- package/src/shared/environment-manager/derivation/types.ts +69 -0
- package/src/shared/environment-manager/health.ts +589 -0
- package/src/shared/environment-manager/index.ts +6 -0
- package/src/shared/environment-manager/load-applications.ts +78 -0
- package/src/shared/environment-manager/log-tail.ts +212 -0
- package/src/shared/environment-manager/make-targets.ts +78 -0
- package/src/shared/environment-manager/opctl.ts +334 -0
- package/src/shared/environment-manager/readiness.ts +93 -0
- package/src/shared/environment-manager/register-local.ts +72 -0
- package/src/shared/environment-manager/secrets-check.ts +260 -0
- package/src/shared/environment-manager/service-env.ts +110 -0
- package/src/shared/environment-manager/service-runner.ts +1588 -0
- package/src/shared/environment-manager/up-plan.ts +302 -0
- package/src/shared/environment-manager/workspace-manager.ts +905 -0
- package/src/shared/environment-manager/workspaces.ts +860 -0
- package/src/shared/manifest/deps.ts +27 -0
- package/src/shared/manifest/migrate-manifest.ts +101 -0
- package/src/shared/manifest/read-manifest.ts +160 -0
- package/src/shared/manifest/schema.ts +299 -0
- package/src/shared/manifest/service-links.ts +93 -0
- package/src/shared/manifest/validate-manifest.ts +140 -0
- package/src/shared/manifest/workspace-manifests.ts +92 -0
- package/src/shared/open-url.ts +122 -0
- package/src/shared/org.ts +44 -0
- package/src/shared/paths.ts +178 -0
- package/src/shared/perf/optional-fields.ts +105 -0
- package/src/shared/perf/report-normalizer.ts +404 -0
- package/src/shared/perf/report-paths.ts +19 -0
- package/src/shared/perf/report-store.ts +110 -0
- package/src/shared/perf/report-types.ts +70 -0
- package/src/shared/perf/test-paths.ts +25 -0
- package/src/shared/process/log-rotation.ts +147 -0
- package/src/shared/process/spawn-streaming.ts +229 -0
- package/src/shared/product.ts +92 -0
- package/src/shared/profile.ts +141 -0
- package/src/shared/repo-kind.ts +32 -0
- package/src/shared/reporting/jsonl-reporter.ts +131 -0
- package/src/shared/reporting/slack-reporter.ts +340 -0
- package/src/shared/results/result-reader.ts +266 -0
- package/src/shared/results/result-writer.ts +168 -0
- package/src/shared/secrets/doppler.ts +126 -0
- package/src/shared/secrets/env.ts +48 -0
- package/src/shared/secrets/index.ts +197 -0
- package/src/shared/secrets/keys.ts +33 -0
- package/src/shared/secrets/onepassword.ts +75 -0
- package/src/shared/secrets/types.ts +52 -0
- package/src/shared/service-url.ts +119 -0
- package/src/shared/smoke/branch-resolver.ts +141 -0
- package/src/shared/smoke/git-remote.ts +70 -0
- package/src/shared/smoke/repo-cloner.ts +151 -0
- package/src/shared/stacks/config.ts +137 -0
- package/src/shared/stacks/manifest-commands.ts +88 -0
- package/src/shared/stacks/render.ts +253 -0
- package/src/shared/stacks/runtime.ts +334 -0
- package/src/shared/stacks/types.ts +53 -0
- package/src/shared/terminal/tabs.ts +212 -0
- package/src/shared/test-discovery/module-scanner.ts +569 -0
- package/src/shared/types/index.ts +678 -0
- package/src/shared/ui-marker.ts +19 -0
- package/src/ui/cli/applications-command.ts +116 -0
- package/src/ui/cli/config-command.ts +68 -0
- package/src/ui/cli/create-main.ts +9 -0
- package/src/ui/cli/create.ts +360 -0
- package/src/ui/cli/edge-command.ts +91 -0
- package/src/ui/cli/flag-parser.ts +246 -0
- package/src/ui/cli/graph-command.ts +524 -0
- package/src/ui/cli/health-display.ts +63 -0
- package/src/ui/cli/index.ts +3656 -0
- package/src/ui/cli/init.ts +855 -0
- package/src/ui/cli/invocation-target.ts +175 -0
- package/src/ui/cli/logs-command.ts +104 -0
- package/src/ui/cli/module-selection.ts +31 -0
- package/src/ui/cli/prompts.ts +767 -0
- package/src/ui/cli/results-viewer.ts +436 -0
- package/src/ui/cli/secrets-preflight.ts +91 -0
- package/src/ui/cli/stack-command.ts +144 -0
- package/src/ui/cli/stalled-setup-recovery.ts +129 -0
- package/src/ui/cli/test-runner.ts +1312 -0
- package/src/ui/cli/web-command.ts +1073 -0
- package/src/ui/server/changelog-parser.ts +94 -0
- package/src/ui/server/daemon-reaper.ts +129 -0
- package/src/ui/server/editor-manager.ts +301 -0
- package/src/ui/server/github-service.ts +223 -0
- package/src/ui/server/http-helpers.ts +98 -0
- package/src/ui/server/index.ts +3236 -0
- package/src/ui/server/log-history.ts +452 -0
- package/src/ui/server/perf-report.ts +42 -0
- package/src/ui/server/public/environments.html +494 -0
- package/src/ui/server/public/favicon.ico +0 -0
- package/src/ui/server/public/header.js +1371 -0
- package/src/ui/server/public/index.html +2595 -0
- package/src/ui/server/public/local-dev.html +7405 -0
- package/src/ui/server/public/logs.html +1489 -0
- package/src/ui/server/public/perf-report.js +359 -0
- package/src/ui/server/public/release.html +2111 -0
- package/src/ui/server/public/services.html +1098 -0
- package/src/ui/server/public/workspace-picker.js +69 -0
- package/src/ui/server/release-deploy-session.ts +246 -0
- package/src/ui/server/release-service.ts +1125 -0
- package/src/ui/server/sudo-store.ts +111 -0
- package/src/ui/server/workspace-routes.ts +2154 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub integration service.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from server/index.ts to isolate GitHub API calls, caching,
|
|
5
|
+
* and org detection from the HTTP handler layer.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { execFileSync, execSync } from "child_process";
|
|
9
|
+
import * as path from "path";
|
|
10
|
+
import { readDenConfig } from "../../shared/den-config.js";
|
|
11
|
+
import { readCatalog } from "../../shared/environment-manager/catalog.js";
|
|
12
|
+
import {
|
|
13
|
+
GH_API_TIMEOUT_MS,
|
|
14
|
+
GIT_REMOTE_TIMEOUT_MS,
|
|
15
|
+
BRANCH_CACHE_TTL_MS,
|
|
16
|
+
REPO_CACHE_TTL_MS,
|
|
17
|
+
} from "../../shared/constants.js";
|
|
18
|
+
import { getDistRoot } from "../../shared/paths.js";
|
|
19
|
+
|
|
20
|
+
import { product } from "../../shared/product.js";
|
|
21
|
+
// Anchored to the den root, NOT process.cwd(): "this repo" identification
|
|
22
|
+
// (basename + origin remote) must not depend on where the server was started.
|
|
23
|
+
const ROOT = getDistRoot();
|
|
24
|
+
|
|
25
|
+
/** Strict owner/repo format — rejects shell metacharacters. */
|
|
26
|
+
const SLUG_RE = /^[a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+$/;
|
|
27
|
+
|
|
28
|
+
export interface GitHubRepo {
|
|
29
|
+
name: string;
|
|
30
|
+
nameWithOwner: string;
|
|
31
|
+
defaultBranch: string;
|
|
32
|
+
hasDen: boolean;
|
|
33
|
+
isArchived: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class GitHubService {
|
|
37
|
+
private branchesCache = new Map<
|
|
38
|
+
string,
|
|
39
|
+
{ names: string[]; fetchedAt: number }
|
|
40
|
+
>();
|
|
41
|
+
private reposCache: { data: GitHubRepo[]; fetchedAt: number } | null = null;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* List branches for a GitHub repo slug ("owner/repo").
|
|
45
|
+
* Cached per slug for BRANCH_CACHE_TTL_MS.
|
|
46
|
+
*/
|
|
47
|
+
listBranches(gh: string): string[] {
|
|
48
|
+
if (!gh || !SLUG_RE.test(gh)) return [];
|
|
49
|
+
|
|
50
|
+
const cached = this.branchesCache.get(gh);
|
|
51
|
+
if (cached && Date.now() - cached.fetchedAt < BRANCH_CACHE_TTL_MS) {
|
|
52
|
+
return cached.names;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const raw = execFileSync(
|
|
57
|
+
"gh",
|
|
58
|
+
[
|
|
59
|
+
"api",
|
|
60
|
+
"--paginate",
|
|
61
|
+
`repos/${gh}/branches?per_page=100`,
|
|
62
|
+
"--jq",
|
|
63
|
+
"[.[].name]",
|
|
64
|
+
],
|
|
65
|
+
{
|
|
66
|
+
encoding: "utf-8",
|
|
67
|
+
timeout: GH_API_TIMEOUT_MS * 2,
|
|
68
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
69
|
+
},
|
|
70
|
+
).trim();
|
|
71
|
+
const names = raw
|
|
72
|
+
.split("\n")
|
|
73
|
+
.filter(Boolean)
|
|
74
|
+
.flatMap((line) => JSON.parse(line) as string[]);
|
|
75
|
+
this.branchesCache.set(gh, { names, fetchedAt: Date.now() });
|
|
76
|
+
return names;
|
|
77
|
+
} catch {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* List the repos of every org Den knows (see resolveOrgs), with a hasDen
|
|
84
|
+
* flag. One org failing (no access, SSO) leaves the others listed.
|
|
85
|
+
* Cached for REPO_CACHE_TTL_MS unless forceRefresh is true.
|
|
86
|
+
*/
|
|
87
|
+
listOrgRepos(forceRefresh = false): GitHubRepo[] {
|
|
88
|
+
if (
|
|
89
|
+
!forceRefresh &&
|
|
90
|
+
this.reposCache &&
|
|
91
|
+
Date.now() - this.reposCache.fetchedAt < REPO_CACHE_TTL_MS
|
|
92
|
+
) {
|
|
93
|
+
return this.reposCache.data;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const orgs = this.resolveOrgs();
|
|
97
|
+
if (orgs.length === 0) {
|
|
98
|
+
console.warn(
|
|
99
|
+
"[github-service] listOrgRepos: no GitHub org resolvable — " +
|
|
100
|
+
`add \`github_org: <slug>\` to ${product().configFile}, or register at least ` +
|
|
101
|
+
"one catalog entry with a slashed `gh:` field",
|
|
102
|
+
);
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
const data = orgs.flatMap((org) => this.listOneOrg(org));
|
|
106
|
+
this.reposCache = { data, fetchedAt: Date.now() };
|
|
107
|
+
return data;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private listOneOrg(org: string): GitHubRepo[] {
|
|
111
|
+
try {
|
|
112
|
+
const repoListCmd = `gh repo list "${org}" --json name,nameWithOwner,defaultBranchRef,isArchived --limit 200`;
|
|
113
|
+
const denSearchCmd = `gh api "search/code?q=filename:${product().manifestYml}+org:${org}&per_page=100" --jq '[.items[].repository.name]'`;
|
|
114
|
+
|
|
115
|
+
let denRaw: string;
|
|
116
|
+
|
|
117
|
+
const raw = execSync(repoListCmd, {
|
|
118
|
+
encoding: "utf-8",
|
|
119
|
+
timeout: GH_API_TIMEOUT_MS,
|
|
120
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
denRaw = execSync(denSearchCmd, {
|
|
125
|
+
encoding: "utf-8",
|
|
126
|
+
timeout: GH_API_TIMEOUT_MS,
|
|
127
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
128
|
+
});
|
|
129
|
+
} catch (e) {
|
|
130
|
+
console.warn(
|
|
131
|
+
`[github-service] listOrgRepos: ${product().manifestYml} search failed (has${product().title} flags will be false): ${(e as Error).message}`,
|
|
132
|
+
);
|
|
133
|
+
denRaw = "[]";
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const parsed = JSON.parse(raw) as Array<{
|
|
137
|
+
name: string;
|
|
138
|
+
nameWithOwner: string;
|
|
139
|
+
defaultBranchRef?: { name: string } | null;
|
|
140
|
+
isArchived?: boolean;
|
|
141
|
+
}>;
|
|
142
|
+
const denRepos = new Set<string>(JSON.parse(denRaw) as string[]);
|
|
143
|
+
|
|
144
|
+
const thisRepo = path.basename(ROOT);
|
|
145
|
+
const data = parsed
|
|
146
|
+
.filter((r) => r.name !== thisRepo)
|
|
147
|
+
.map((r) => ({
|
|
148
|
+
name: r.name,
|
|
149
|
+
nameWithOwner: r.nameWithOwner,
|
|
150
|
+
defaultBranch: r.defaultBranchRef?.name ?? "main",
|
|
151
|
+
hasDen: denRepos.has(r.name),
|
|
152
|
+
isArchived: r.isArchived ?? false,
|
|
153
|
+
}));
|
|
154
|
+
|
|
155
|
+
return data;
|
|
156
|
+
} catch (e) {
|
|
157
|
+
const err = e as Error & { stderr?: Buffer | string };
|
|
158
|
+
const stderr =
|
|
159
|
+
err.stderr instanceof Buffer
|
|
160
|
+
? err.stderr.toString().trim()
|
|
161
|
+
: (err.stderr ?? "").toString().trim();
|
|
162
|
+
console.warn(
|
|
163
|
+
`[github-service] listOrgRepos: \`gh repo list ${org}\` failed — ` +
|
|
164
|
+
`${err.message}${stderr ? ` | stderr: ${stderr}` : ""}. ` +
|
|
165
|
+
`Check: (1) \`gh auth status\`, (2) \`gh\` is on the PATH of the process running \`make web\`, ` +
|
|
166
|
+
`(3) your PAT is SSO-authorized for ${org}.`,
|
|
167
|
+
);
|
|
168
|
+
return [];
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Every GitHub org Den lists repos from: `github_org`, then the orgs in
|
|
174
|
+
* config/den.yml `orgs:`, then every owner in the catalog (several orgs'
|
|
175
|
+
* repos can sit side by side). With none of those, fall back to
|
|
176
|
+
* the den repo's own `origin` URL — makes upgraded installs Just
|
|
177
|
+
* Work without anyone editing config/den.yml. Existing users
|
|
178
|
+
* who pre-date `github_org` or had it commented out still get
|
|
179
|
+
* the right value as long as den itself was cloned from the
|
|
180
|
+
* same org as their services (the common case).
|
|
181
|
+
*/
|
|
182
|
+
private resolveOrgs(): string[] {
|
|
183
|
+
const config = readDenConfig();
|
|
184
|
+
const orgs = new Set<string>();
|
|
185
|
+
if (config.github_org) orgs.add(config.github_org);
|
|
186
|
+
for (const id of Object.keys(config.orgs ?? {})) orgs.add(id);
|
|
187
|
+
for (const entry of Object.values(readCatalog() ?? {})) {
|
|
188
|
+
if (entry?.gh && entry.gh.includes("/")) {
|
|
189
|
+
orgs.add(entry.gh.slice(0, entry.gh.indexOf("/")));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// Org names reach a shell command: GitHub owners are letters, digits and
|
|
193
|
+
// hyphens, so anything else is dropped.
|
|
194
|
+
const valid = [...orgs].filter((o) => /^[A-Za-z0-9][A-Za-z0-9-]*$/.test(o));
|
|
195
|
+
if (valid.length > 0) return valid;
|
|
196
|
+
const fallback = this.originOrg();
|
|
197
|
+
return fallback ? [fallback] : [];
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private originOrg(): string | null {
|
|
201
|
+
// Fall back to the den repo's origin. Works for:
|
|
202
|
+
// SSH: git@github.com:ORG/den.git
|
|
203
|
+
// HTTPS: https://github.com/ORG/den.git
|
|
204
|
+
// https://github.com/ORG/den
|
|
205
|
+
try {
|
|
206
|
+
const url = execFileSync(
|
|
207
|
+
"git",
|
|
208
|
+
["-C", ROOT, "config", "--get", "remote.origin.url"],
|
|
209
|
+
{
|
|
210
|
+
encoding: "utf-8",
|
|
211
|
+
timeout: GIT_REMOTE_TIMEOUT_MS,
|
|
212
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
213
|
+
},
|
|
214
|
+
).trim();
|
|
215
|
+
const m = url.match(/[:/]([^/:]+)\/[^/]+?(?:\.git)?$/);
|
|
216
|
+
if (m) return m[1];
|
|
217
|
+
} catch {
|
|
218
|
+
/* no git remote available */
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared HTTP helpers for the den web server.
|
|
3
|
+
*
|
|
4
|
+
* Extracted so route handlers in separate files can import them
|
|
5
|
+
* without depending on index.ts.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as http from "http";
|
|
9
|
+
import * as fs from "fs";
|
|
10
|
+
import * as path from "path";
|
|
11
|
+
import { brandHtmlTitle, readBrand } from "../../shared/brand.js";
|
|
12
|
+
import { fillProductTokens } from "../../shared/product.js";
|
|
13
|
+
import { addDenUiMarker } from "../../shared/ui-marker.js";
|
|
14
|
+
|
|
15
|
+
// ── MIME types ────────────────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
const MIME: Record<string, string> = {
|
|
18
|
+
".html": "text/html; charset=utf-8",
|
|
19
|
+
".js": "application/javascript",
|
|
20
|
+
".mjs": "application/javascript",
|
|
21
|
+
".css": "text/css",
|
|
22
|
+
".json": "application/json",
|
|
23
|
+
".png": "image/png",
|
|
24
|
+
".jpg": "image/jpeg",
|
|
25
|
+
".svg": "image/svg+xml",
|
|
26
|
+
".webp": "image/webp",
|
|
27
|
+
".ico": "image/x-icon",
|
|
28
|
+
".woff2": "font/woff2",
|
|
29
|
+
".woff": "font/woff",
|
|
30
|
+
".zip": "application/zip",
|
|
31
|
+
".txt": "text/plain",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function mime(filePath: string): string {
|
|
35
|
+
return (
|
|
36
|
+
MIME[path.extname(filePath).toLowerCase()] ?? "application/octet-stream"
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
export function readBody(req: http.IncomingMessage): Promise<string> {
|
|
43
|
+
return new Promise((resolve, reject) => {
|
|
44
|
+
let body = "";
|
|
45
|
+
req.on("data", (chunk: Buffer) => {
|
|
46
|
+
body += chunk.toString();
|
|
47
|
+
});
|
|
48
|
+
req.on("end", () => resolve(body));
|
|
49
|
+
req.on("error", reject);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function sendJson(
|
|
54
|
+
res: http.ServerResponse,
|
|
55
|
+
data: unknown,
|
|
56
|
+
status = 200,
|
|
57
|
+
): void {
|
|
58
|
+
const body = JSON.stringify(data);
|
|
59
|
+
res.writeHead(status, {
|
|
60
|
+
"Content-Type": "application/json",
|
|
61
|
+
// API responses are live state (health dots, workspace config) — never
|
|
62
|
+
// let a browser or proxy serve yesterday's answer.
|
|
63
|
+
"Cache-Control": "no-store",
|
|
64
|
+
});
|
|
65
|
+
res.end(body);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function serveFile(res: http.ServerResponse, filePath: string): void {
|
|
69
|
+
try {
|
|
70
|
+
let data: Buffer | string = fs.readFileSync(filePath);
|
|
71
|
+
// Pages are static and product-neutral: their product names are
|
|
72
|
+
// placeholders (__DEN_CLI__, …) filled from the running profile.
|
|
73
|
+
if (filePath.endsWith(".html") || filePath.endsWith(".js"))
|
|
74
|
+
data = fillProductTokens(data.toString("utf-8"));
|
|
75
|
+
if (filePath.endsWith(".html"))
|
|
76
|
+
data = addDenUiMarker(brandHtmlTitle(String(data), readBrand()));
|
|
77
|
+
const headers: Record<string, string> = { "Content-Type": mime(filePath) };
|
|
78
|
+
// App code must never be stale (no ETag/Last-Modified validators are sent,
|
|
79
|
+
// so any heuristic caching would have no way to revalidate).
|
|
80
|
+
if (filePath.endsWith(".html") || filePath.endsWith(".js"))
|
|
81
|
+
headers["Cache-Control"] = "no-store";
|
|
82
|
+
res.writeHead(200, headers);
|
|
83
|
+
res.end(data);
|
|
84
|
+
} catch {
|
|
85
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
86
|
+
res.end("Not found");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function send400(res: http.ServerResponse, msg: string): void {
|
|
91
|
+
res.writeHead(400, { "Content-Type": "text/plain" });
|
|
92
|
+
res.end(msg);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function send404(res: http.ServerResponse): void {
|
|
96
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
97
|
+
res.end("not found");
|
|
98
|
+
}
|