@treeseed/core 0.8.19 → 0.9.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/components/ui/shell/AppShell.astro +16 -4
- package/dist/components/ui/shell/BottomNav.astro +10 -3
- package/dist/components/ui/shell/RailNav.astro +10 -3
- package/dist/components/ui/shell/ShellIconLink.astro +30 -0
- package/dist/content-config.d.ts +1 -0
- package/dist/content.js +19 -2
- package/dist/dev.d.ts +10 -0
- package/dist/dev.js +283 -51
- package/dist/layouts/AuthoredEntryLayout.astro +136 -76
- package/dist/layouts/MainLayout.astro +2 -0
- package/dist/pages/decisions/[slug].astro +9 -1
- package/dist/pages/notes/[slug].astro +17 -4
- package/dist/pages/objectives/[slug].astro +4 -0
- package/dist/pages/proposals/[slug].astro +6 -0
- package/dist/pages/questions/[slug].astro +4 -0
- package/dist/scripts/build-dist.js +1 -1
- package/dist/scripts/dev-platform.js +23 -0
- package/dist/site.js +3 -0
- package/dist/styles/app-shell.css +26 -1
- package/dist/styles/theme.css +8 -8
- package/package.json +2 -2
|
@@ -5,9 +5,11 @@ import '../../../styles/ui.css';
|
|
|
5
5
|
import '../../../styles/forms.css';
|
|
6
6
|
import '../../../styles/app-shell.css';
|
|
7
7
|
import ThemeScript from '../theme/ThemeScript.astro';
|
|
8
|
+
import ThemeMenu from '../theme/ThemeMenu.astro';
|
|
8
9
|
import RailNav from './RailNav.astro';
|
|
9
10
|
import BottomNav from './BottomNav.astro';
|
|
10
11
|
import TopBar from './TopBar.astro';
|
|
12
|
+
import ShellIconLink from './ShellIconLink.astro';
|
|
11
13
|
import Button from '../forms/Button.astro';
|
|
12
14
|
import DevWatchReload from '../../DevWatchReload.astro';
|
|
13
15
|
import type { ButtonAction } from '../types.js';
|
|
@@ -64,11 +66,13 @@ const {
|
|
|
64
66
|
<a class="ts-skip-link" href="#main-content">Skip to content</a>
|
|
65
67
|
<div class="ts-app-shell">
|
|
66
68
|
<aside class="ts-app-shell__rail">
|
|
67
|
-
<
|
|
68
|
-
|
|
69
|
-
<
|
|
69
|
+
<div class="ts-app-shell__rail-scroll">
|
|
70
|
+
<TopBar brand={brand} />
|
|
71
|
+
<div class="ts-app-shell__rail-context">
|
|
72
|
+
<slot name="railContext" />
|
|
73
|
+
</div>
|
|
74
|
+
<RailNav items={navItems} currentPath={currentPath} />
|
|
70
75
|
</div>
|
|
71
|
-
<RailNav items={navItems} currentPath={currentPath} />
|
|
72
76
|
{quickActions.length > 0 ? (
|
|
73
77
|
<div class="ts-app-shell__quick-actions">
|
|
74
78
|
<p class="ts-app-shell__eyebrow">Quick actions</p>
|
|
@@ -91,6 +95,10 @@ const {
|
|
|
91
95
|
</aside>
|
|
92
96
|
<main class="ts-app-shell__main" id="main-content">
|
|
93
97
|
<TopBar brand={brand} class="ts-app-shell__mobile-top">
|
|
98
|
+
<Fragment slot="actions">
|
|
99
|
+
<ThemeMenu selectedScheme={appearance.scheme} selectedMode={appearance.mode} />
|
|
100
|
+
<ShellIconLink href="/" label="Book home" icon="book" />
|
|
101
|
+
</Fragment>
|
|
94
102
|
</TopBar>
|
|
95
103
|
<header class="ts-app-shell__header">
|
|
96
104
|
<div class="ts-app-shell__title">
|
|
@@ -98,6 +106,10 @@ const {
|
|
|
98
106
|
<p>{description}</p>
|
|
99
107
|
</div>
|
|
100
108
|
<div class="ts-app-shell__header-actions">
|
|
109
|
+
<div class="ts-shell-utility-actions">
|
|
110
|
+
<ThemeMenu selectedScheme={appearance.scheme} selectedMode={appearance.mode} />
|
|
111
|
+
<ShellIconLink href="/" label="Book home" icon="book" />
|
|
112
|
+
</div>
|
|
101
113
|
<slot name="headerAction" />
|
|
102
114
|
</div>
|
|
103
115
|
</header>
|
|
@@ -19,10 +19,17 @@ const {
|
|
|
19
19
|
class: className,
|
|
20
20
|
} = Astro.props as Props;
|
|
21
21
|
|
|
22
|
+
function normalizePath(path: string) {
|
|
23
|
+
if (path.length <= 1) return path;
|
|
24
|
+
return path.replace(/\/+$/u, '');
|
|
25
|
+
}
|
|
26
|
+
|
|
22
27
|
function isCurrentPath(href: string) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
28
|
+
const current = normalizePath(currentPath);
|
|
29
|
+
const target = normalizePath(href);
|
|
30
|
+
if (target === '/') return current === '/';
|
|
31
|
+
if (target === '/app') return current === '/app';
|
|
32
|
+
return current === target || current.startsWith(`${target}/`);
|
|
26
33
|
}
|
|
27
34
|
---
|
|
28
35
|
|
|
@@ -19,10 +19,17 @@ const {
|
|
|
19
19
|
class: className,
|
|
20
20
|
} = Astro.props as Props;
|
|
21
21
|
|
|
22
|
+
function normalizePath(path: string) {
|
|
23
|
+
if (path.length <= 1) return path;
|
|
24
|
+
return path.replace(/\/+$/u, '');
|
|
25
|
+
}
|
|
26
|
+
|
|
22
27
|
function isCurrentPath(href: string) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
28
|
+
const current = normalizePath(currentPath);
|
|
29
|
+
const target = normalizePath(href);
|
|
30
|
+
if (target === '/') return current === '/';
|
|
31
|
+
if (target === '/app') return current === '/app';
|
|
32
|
+
return current === target || current.startsWith(`${target}/`);
|
|
26
33
|
}
|
|
27
34
|
---
|
|
28
35
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
interface Props {
|
|
3
|
+
href: string;
|
|
4
|
+
label: string;
|
|
5
|
+
icon: 'book' | 'manager';
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const { href, label, icon } = Astro.props as Props;
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
<a
|
|
12
|
+
href={href}
|
|
13
|
+
aria-label={label}
|
|
14
|
+
title={label}
|
|
15
|
+
class="ts-public-shell__icon-link ts-public-shell__icon-link--stroke"
|
|
16
|
+
>
|
|
17
|
+
{icon === 'manager' ? (
|
|
18
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
|
|
19
|
+
<path d="M12 15.25a3.25 3.25 0 1 0 0-6.5 3.25 3.25 0 0 0 0 6.5Z" />
|
|
20
|
+
<path d="M19.43 12.98c.04-.32.07-.65.07-.98s-.02-.66-.07-.98l2.05-1.6-2-3.46-2.42.98a7.65 7.65 0 0 0-1.7-.98L15 3.38h-4l-.36 2.58c-.6.24-1.17.57-1.7.98l-2.42-.98-2 3.46 2.05 1.6c-.04.32-.07.65-.07.98s.02.66.07.98l-2.05 1.6 2 3.46 2.42-.98c.52.41 1.09.74 1.7.98l.36 2.58h4l.36-2.58c.6-.24 1.17-.57 1.7-.98l2.42.98 2-3.46-2.05-1.6Z" />
|
|
21
|
+
</svg>
|
|
22
|
+
) : (
|
|
23
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
|
|
24
|
+
<path d="M4.75 5.75A2.75 2.75 0 0 1 7.5 3h11.75v16.25H7.5a2.75 2.75 0 0 0-2.75 2.75V5.75Z" />
|
|
25
|
+
<path d="M7.5 19.25h11.75" />
|
|
26
|
+
<path d="M8.25 7.25h7.5" />
|
|
27
|
+
<path d="M8.25 10.75h6" />
|
|
28
|
+
</svg>
|
|
29
|
+
)}
|
|
30
|
+
</a>
|
package/dist/content-config.d.ts
CHANGED
package/dist/content.js
CHANGED
|
@@ -2,6 +2,7 @@ import { defineCollection, reference } from "astro:content";
|
|
|
2
2
|
import { z } from "astro/zod";
|
|
3
3
|
import { glob } from "astro/loaders";
|
|
4
4
|
import { existsSync, readdirSync } from "node:fs";
|
|
5
|
+
import { dirname, resolve } from "node:path";
|
|
5
6
|
import { AGENT_CLI_ALLOW_TOOLS } from "@treeseed/sdk/types/agents";
|
|
6
7
|
import { loadTreeseedPluginRuntime } from "@treeseed/sdk/platform/plugins";
|
|
7
8
|
import { loadTreeseedDeployConfig } from "@treeseed/sdk/platform/deploy-config";
|
|
@@ -23,7 +24,7 @@ const statusValues = ["live", "in progress", "exploratory", "planned", "speculat
|
|
|
23
24
|
const pageLayoutValues = ["article", "bridge"];
|
|
24
25
|
const questionTypeValues = ["research", "implementation", "strategy", "evaluation"];
|
|
25
26
|
const proposalTypeValues = ["strategy", "policy", "implementation", "research"];
|
|
26
|
-
const decisionTypeValues = ["approved", "rejected", "deferred", "superseded"];
|
|
27
|
+
const decisionTypeValues = ["approved", "rejected", "deferred", "request_changes", "superseded"];
|
|
27
28
|
const timeHorizonValues = ["near-term", "mid-term", "long-term"];
|
|
28
29
|
const runtimeStatusValues = ["active", "experimental", "dormant"];
|
|
29
30
|
const agentTriggerTypeValues = ["schedule", "message", "follow", "startup"];
|
|
@@ -362,6 +363,15 @@ function createTreeseedCollections(tenantConfig, { docsLoader, docsSchema }) {
|
|
|
362
363
|
outputs: agentOutputSchema.default({}),
|
|
363
364
|
governance: agentGovernanceSchema.optional()
|
|
364
365
|
}));
|
|
366
|
+
const agentTestSchema = z.object({
|
|
367
|
+
id: z.string(),
|
|
368
|
+
agent: z.string(),
|
|
369
|
+
kind: z.enum(["spec", "handler", "message_chain", "manager_worker", "workday", "api", "ui"]),
|
|
370
|
+
fixture: z.string().optional(),
|
|
371
|
+
trigger: z.record(z.any()).default({}),
|
|
372
|
+
expect: z.record(z.any()).default({}),
|
|
373
|
+
tags: z.array(z.string()).default([])
|
|
374
|
+
});
|
|
365
375
|
const bookSchema = z.preprocess((value) => preprocessAliasedRecord(bookFieldAliases, value), z.object({
|
|
366
376
|
order: z.number().int().nonnegative(),
|
|
367
377
|
slug: z.string(),
|
|
@@ -450,7 +460,7 @@ function createTreeseedCollections(tenantConfig, { docsLoader, docsSchema }) {
|
|
|
450
460
|
completedAt: z.coerce.date().optional(),
|
|
451
461
|
lastErrorCode: z.string().nullable().optional(),
|
|
452
462
|
lastErrorMessage: z.string().nullable().optional(),
|
|
453
|
-
lastEventKind: z.string().optional(),
|
|
463
|
+
lastEventKind: z.string().nullable().optional(),
|
|
454
464
|
outputCount: z.number().int().optional(),
|
|
455
465
|
changedFiles: z.array(z.string()).default([])
|
|
456
466
|
});
|
|
@@ -528,6 +538,13 @@ function createTreeseedCollections(tenantConfig, { docsLoader, docsSchema }) {
|
|
|
528
538
|
schema: docsCollectionProvider.schema
|
|
529
539
|
})
|
|
530
540
|
};
|
|
541
|
+
const agentTestsRoot = resolve(dirname(tenantConfig.content.agents), "agent-tests");
|
|
542
|
+
if (existsSync(agentTestsRoot)) {
|
|
543
|
+
collections.agent_tests = defineCollection({
|
|
544
|
+
loader: optionalMarkdownGlob(agentTestsRoot),
|
|
545
|
+
schema: agentTestSchema
|
|
546
|
+
});
|
|
547
|
+
}
|
|
531
548
|
if (tenantConfig.content.workdays) {
|
|
532
549
|
collections.workdays = defineCollection({
|
|
533
550
|
loader: optionalMarkdownGlob(tenantConfig.content.workdays),
|
package/dist/dev.d.ts
CHANGED
|
@@ -32,11 +32,13 @@ export type TreeseedIntegratedDevOptions = {
|
|
|
32
32
|
webPort?: number;
|
|
33
33
|
apiHost?: string;
|
|
34
34
|
apiPort?: number;
|
|
35
|
+
webRuntime?: TreeseedLocalRuntimeMode;
|
|
35
36
|
setupMode?: TreeseedIntegratedDevSetupMode;
|
|
36
37
|
feedbackMode?: TreeseedIntegratedDevFeedbackMode;
|
|
37
38
|
openMode?: TreeseedIntegratedDevOpenMode;
|
|
38
39
|
plan?: boolean;
|
|
39
40
|
reset?: boolean;
|
|
41
|
+
force?: boolean;
|
|
40
42
|
json?: boolean;
|
|
41
43
|
includeServices?: boolean;
|
|
42
44
|
projectId?: string;
|
|
@@ -97,6 +99,7 @@ export type TreeseedIntegratedDevPlan = {
|
|
|
97
99
|
readyChecks: TreeseedIntegratedDevReadinessCheck[];
|
|
98
100
|
watchEntries: TreeseedIntegratedDevWatchEntry[];
|
|
99
101
|
commands: TreeseedIntegratedDevCommand[];
|
|
102
|
+
logPath: string;
|
|
100
103
|
localRuntimes: Record<string, TreeseedLocalRuntimeSelection>;
|
|
101
104
|
restartPolicy: {
|
|
102
105
|
initialBackoffMs: number;
|
|
@@ -127,6 +130,13 @@ type TreeseedIntegratedDevDependencies = {
|
|
|
127
130
|
startWatch: WatchStarter;
|
|
128
131
|
removePath: (path: string) => void;
|
|
129
132
|
stopMailpitContainers: () => boolean;
|
|
133
|
+
inspectPortOwners: (ports: readonly number[]) => TreeseedDevPortOwner[];
|
|
134
|
+
};
|
|
135
|
+
export type TreeseedDevPortOwner = {
|
|
136
|
+
port: number;
|
|
137
|
+
pid: number | null;
|
|
138
|
+
processName?: string;
|
|
139
|
+
detail: string;
|
|
130
140
|
};
|
|
131
141
|
export declare function createTreeseedIntegratedDevResetPlan(options: {
|
|
132
142
|
tenantRoot: string;
|
package/dist/dev.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { spawn, spawnSync } from "node:child_process";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { dirname, isAbsolute, resolve, sep } from "node:path";
|
|
@@ -32,7 +32,8 @@ const TREESEED_DEFAULT_LOCAL_SMTP_HOST = "127.0.0.1";
|
|
|
32
32
|
const TREESEED_DEFAULT_LOCAL_SMTP_PORT = 1025;
|
|
33
33
|
const TREESEED_DEFAULT_MAILPIT_UI_PORT = 8025;
|
|
34
34
|
const DEV_RELOAD_FILE = "public/__treeseed/dev-reload.json";
|
|
35
|
-
const
|
|
35
|
+
const DEV_RUNTIME_DIR = ".treeseed/generated/dev";
|
|
36
|
+
const DEV_RUNTIME_LEGACY_FILE = ".treeseed/generated/dev/runtime.json";
|
|
36
37
|
const DEFAULT_READINESS_TIMEOUT_MS = 9e4;
|
|
37
38
|
const DEFAULT_SETUP_STEP_TIMEOUT_MS = 3e5;
|
|
38
39
|
const DEFAULT_PROCESS_READY_GRACE_MS = 1200;
|
|
@@ -132,10 +133,10 @@ function fallbackWebProviderFromDeployConfig(deployConfig) {
|
|
|
132
133
|
const record = deployConfig && typeof deployConfig === "object" ? deployConfig : {};
|
|
133
134
|
return normalizeProvider(record.providers?.deploy, "local");
|
|
134
135
|
}
|
|
135
|
-
function selectWebLocalRuntime(surfaceConfig, providerFallback = "local") {
|
|
136
|
+
function selectWebLocalRuntime(surfaceConfig, providerFallback = "local", overrideRuntime) {
|
|
136
137
|
const record = surfaceConfig && typeof surfaceConfig === "object" ? surfaceConfig : {};
|
|
137
138
|
const provider = normalizeProvider(record.provider, providerFallback);
|
|
138
|
-
const requested = normalizeLocalRuntimeMode(record.local?.runtime);
|
|
139
|
+
const requested = overrideRuntime ?? normalizeLocalRuntimeMode(record.local?.runtime);
|
|
139
140
|
if (provider === "cloudflare" && requested !== "local") {
|
|
140
141
|
return {
|
|
141
142
|
requested,
|
|
@@ -151,7 +152,7 @@ function selectWebLocalRuntime(surfaceConfig, providerFallback = "local") {
|
|
|
151
152
|
requested,
|
|
152
153
|
provider,
|
|
153
154
|
selected: "astro-local",
|
|
154
|
-
reason: requested === "local" ? "Configured to use the full local Astro runtime." : `Provider "${provider}" has no provider-local web runtime; using Astro local.`
|
|
155
|
+
reason: overrideRuntime === "local" ? "CLI override selected the full local Astro runtime for faster UI development." : requested === "local" ? "Configured to use the full local Astro runtime." : `Provider "${provider}" has no provider-local web runtime; using Astro local.`
|
|
155
156
|
};
|
|
156
157
|
}
|
|
157
158
|
function loadDevDeployConfig(tenantRoot) {
|
|
@@ -271,6 +272,30 @@ function resolveSeededLocalProjectId(persistTo, projectSlug = "market") {
|
|
|
271
272
|
db?.close();
|
|
272
273
|
}
|
|
273
274
|
}
|
|
275
|
+
function resolveSeededLocalTeamId(persistTo, projectId, teamSlug = "treeseed") {
|
|
276
|
+
const sqlitePath = resolveLocalD1SqlitePath(persistTo);
|
|
277
|
+
if (!sqlitePath) return null;
|
|
278
|
+
let db = null;
|
|
279
|
+
try {
|
|
280
|
+
db = new DatabaseSync(sqlitePath, { readOnly: true });
|
|
281
|
+
if (projectId) {
|
|
282
|
+
const projectRow = db.prepare(
|
|
283
|
+
`SELECT team_id FROM projects WHERE id = ? LIMIT 1`
|
|
284
|
+
).get(projectId);
|
|
285
|
+
if (typeof projectRow?.team_id === "string" && projectRow.team_id.trim()) {
|
|
286
|
+
return projectRow.team_id.trim();
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
const teamRow = db.prepare(
|
|
290
|
+
`SELECT id FROM teams WHERE LOWER(slug) = LOWER(?) ORDER BY created_at ASC LIMIT 1`
|
|
291
|
+
).get(teamSlug);
|
|
292
|
+
return typeof teamRow?.id === "string" && teamRow.id.trim() ? teamRow.id.trim() : null;
|
|
293
|
+
} catch {
|
|
294
|
+
return null;
|
|
295
|
+
} finally {
|
|
296
|
+
db?.close();
|
|
297
|
+
}
|
|
298
|
+
}
|
|
274
299
|
function createTreeseedIntegratedDevResetPlan(options) {
|
|
275
300
|
if (!options.enabled) {
|
|
276
301
|
return null;
|
|
@@ -529,10 +554,13 @@ function createTreeseedIntegratedDevPlan(options = {}) {
|
|
|
529
554
|
const agentPackageRoot = resolvePackageRootEnvOverride(mergedEnv, "TREESEED_AGENT_PACKAGE_ROOT", tenantRoot) ?? resolveOptionalPackageRoot("@treeseed/agent", tenantRoot);
|
|
530
555
|
const cliPackageRoot = resolveOptionalPackageRoot("@treeseed/cli", tenantRoot);
|
|
531
556
|
const deployConfig = loadDevDeployConfig(tenantRoot);
|
|
532
|
-
const webLocalRuntime = selectWebLocalRuntime(deployConfig?.surfaces?.web, fallbackWebProviderFromDeployConfig(deployConfig));
|
|
557
|
+
const webLocalRuntime = selectWebLocalRuntime(deployConfig?.surfaces?.web, fallbackWebProviderFromDeployConfig(deployConfig), options.webRuntime);
|
|
533
558
|
const usesCloudflareWebRuntime = webLocalRuntime.selected === "cloudflare-wrangler-local";
|
|
534
|
-
const
|
|
559
|
+
const usesGeneratedLocalD1State = usesCloudflareWebRuntime || webLocalRuntime.provider === "cloudflare" || selectedCommandIds.some((id) => id !== "web");
|
|
560
|
+
const localD1PersistTo = mergedEnv.TREESEED_API_D1_LOCAL_PERSIST_TO ?? (usesGeneratedLocalD1State ? resolve(tenantRoot, ".treeseed", "generated", "environments", "local", ".wrangler", "state", "v3", "d1") : resolve(tenantRoot, ".wrangler", "state", "v3", "d1"));
|
|
535
561
|
const projectId = options.projectId ?? mergedEnv.TREESEED_PROJECT_ID ?? resolveSeededLocalProjectId(localD1PersistTo);
|
|
562
|
+
const resolvedHostingTeamId = teamId ?? mergedEnv.TREESEED_HOSTING_TEAM_ID;
|
|
563
|
+
const resolvedTeamId = mergedEnv.TREESEED_TEAM_ID ?? resolvedHostingTeamId ?? resolveSeededLocalTeamId(localD1PersistTo, projectId ?? null);
|
|
536
564
|
const webEntrypoint = resolveNodeEntrypoint(
|
|
537
565
|
sdkPackageRoot,
|
|
538
566
|
"scripts/tenant-astro-command.ts",
|
|
@@ -557,13 +585,14 @@ function createTreeseedIntegratedDevPlan(options = {}) {
|
|
|
557
585
|
const resetRequested = options.reset === true;
|
|
558
586
|
const sharedEnv = {
|
|
559
587
|
...mergedEnv,
|
|
560
|
-
TREESEED_LOCAL_DEV_MODE: mergedEnv.TREESEED_LOCAL_DEV_MODE ?? "cloudflare",
|
|
588
|
+
TREESEED_LOCAL_DEV_MODE: usesCloudflareWebRuntime ? mergedEnv.TREESEED_LOCAL_DEV_MODE ?? "cloudflare" : void 0,
|
|
561
589
|
TREESEED_SITE_URL: mergedEnv.TREESEED_SITE_URL ?? webUrl,
|
|
562
590
|
BETTER_AUTH_URL: mergedEnv.BETTER_AUTH_URL ?? webUrl,
|
|
563
591
|
TREESEED_API_BASE_URL: apiBaseUrl,
|
|
564
592
|
TREESEED_MARKET_API_BASE_URL: mergedEnv.TREESEED_MARKET_API_BASE_URL ?? apiBaseUrl,
|
|
565
593
|
TREESEED_PROJECT_ID: projectId ?? mergedEnv.TREESEED_PROJECT_ID,
|
|
566
|
-
|
|
594
|
+
TREESEED_TEAM_ID: resolvedTeamId ?? mergedEnv.TREESEED_TEAM_ID,
|
|
595
|
+
TREESEED_HOSTING_TEAM_ID: resolvedHostingTeamId ?? mergedEnv.TREESEED_HOSTING_TEAM_ID,
|
|
567
596
|
TREESEED_API_D1_DATABASE_NAME: mergedEnv.TREESEED_API_D1_DATABASE_NAME ?? "SITE_DATA_DB",
|
|
568
597
|
SITE_DATA_DB: mergedEnv.SITE_DATA_DB ?? "SITE_DATA_DB",
|
|
569
598
|
TREESEED_API_D1_LOCAL_PERSIST_TO: localD1PersistTo,
|
|
@@ -645,6 +674,7 @@ function createTreeseedIntegratedDevPlan(options = {}) {
|
|
|
645
674
|
readyChecks,
|
|
646
675
|
watchEntries,
|
|
647
676
|
commands,
|
|
677
|
+
logPath: resolve(tenantRoot, ".treeseed", "logs", `dev-${runtimeScopeKey(commands.map((command) => command.id))}.jsonl`),
|
|
648
678
|
localRuntimes: {
|
|
649
679
|
...commands.some((command) => command.id === "web") ? { web: webLocalRuntime } : {},
|
|
650
680
|
...commands.some((command) => command.id === "api") ? { api: nodeLocalRuntime("Treeseed API") } : {},
|
|
@@ -686,6 +716,29 @@ function defaultProcessIsAlive(pid) {
|
|
|
686
716
|
return false;
|
|
687
717
|
}
|
|
688
718
|
}
|
|
719
|
+
function defaultInspectPortOwners(ports) {
|
|
720
|
+
const uniquePorts = [...new Set(ports.filter((port) => Number.isInteger(port) && port > 0))];
|
|
721
|
+
if (uniquePorts.length === 0) return [];
|
|
722
|
+
const result = spawnSync("ss", ["-ltnp"], { encoding: "utf8" });
|
|
723
|
+
if ((result.status ?? 1) !== 0) return [];
|
|
724
|
+
const lines = String(result.stdout ?? "").split(/\r?\n/u);
|
|
725
|
+
const owners = [];
|
|
726
|
+
for (const port of uniquePorts) {
|
|
727
|
+
const portPattern = new RegExp(`:${port}\\b`, "u");
|
|
728
|
+
for (const line of lines) {
|
|
729
|
+
if (!portPattern.test(line)) continue;
|
|
730
|
+
const pidMatch = line.match(/pid=(\d+)/u);
|
|
731
|
+
const nameMatch = line.match(/users:\(\("([^"]+)"/u);
|
|
732
|
+
owners.push({
|
|
733
|
+
port,
|
|
734
|
+
pid: pidMatch ? Number(pidMatch[1]) : null,
|
|
735
|
+
processName: nameMatch?.[1],
|
|
736
|
+
detail: line.trim()
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
return owners;
|
|
741
|
+
}
|
|
689
742
|
function defaultRemovePath(path) {
|
|
690
743
|
rmSync(path, { recursive: true, force: true });
|
|
691
744
|
}
|
|
@@ -782,44 +835,131 @@ function resolveLocalMachineEnv(tenantRoot) {
|
|
|
782
835
|
return {};
|
|
783
836
|
}
|
|
784
837
|
}
|
|
785
|
-
function
|
|
786
|
-
return resolve(tenantRoot,
|
|
838
|
+
function devRuntimeStateDir(tenantRoot) {
|
|
839
|
+
return resolve(tenantRoot, DEV_RUNTIME_DIR);
|
|
840
|
+
}
|
|
841
|
+
function devRuntimeStatePath(tenantRoot, key) {
|
|
842
|
+
return resolve(devRuntimeStateDir(tenantRoot), `runtime-${key}.json`);
|
|
843
|
+
}
|
|
844
|
+
function legacyDevRuntimeStatePath(tenantRoot) {
|
|
845
|
+
return resolve(tenantRoot, DEV_RUNTIME_LEGACY_FILE);
|
|
787
846
|
}
|
|
788
|
-
function
|
|
847
|
+
function runtimeScopeKey(commandIds) {
|
|
848
|
+
const selected = CANONICAL_COMMAND_IDS.filter((id) => commandIds.includes(id));
|
|
849
|
+
return selected.length > 0 ? selected.join("-") : "integrated";
|
|
850
|
+
}
|
|
851
|
+
function readDevRuntimeStateFile(path) {
|
|
789
852
|
try {
|
|
790
|
-
const parsed = JSON.parse(readFileSync(
|
|
853
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
791
854
|
if (!Number.isInteger(parsed.pid) || typeof parsed.tenantRoot !== "string" || typeof parsed.startedAt !== "string") {
|
|
792
855
|
return null;
|
|
793
856
|
}
|
|
857
|
+
const commandIds = Array.isArray(parsed.commandIds) ? parsed.commandIds.filter((id) => CANONICAL_COMMAND_IDS.includes(id)) : void 0;
|
|
794
858
|
return {
|
|
795
859
|
pid: parsed.pid,
|
|
796
860
|
tenantRoot: parsed.tenantRoot,
|
|
797
|
-
startedAt: parsed.startedAt
|
|
861
|
+
startedAt: parsed.startedAt,
|
|
862
|
+
...commandIds ? { commandIds } : {},
|
|
863
|
+
statePath: path
|
|
798
864
|
};
|
|
799
865
|
} catch {
|
|
800
866
|
return null;
|
|
801
867
|
}
|
|
802
868
|
}
|
|
803
|
-
function
|
|
804
|
-
const
|
|
869
|
+
function listDevRuntimeStates(tenantRoot) {
|
|
870
|
+
const states = [];
|
|
871
|
+
const legacy = readDevRuntimeStateFile(legacyDevRuntimeStatePath(tenantRoot));
|
|
872
|
+
if (legacy) {
|
|
873
|
+
states.push(legacy);
|
|
874
|
+
}
|
|
875
|
+
try {
|
|
876
|
+
for (const entry of readdirSync(devRuntimeStateDir(tenantRoot))) {
|
|
877
|
+
if (!entry.startsWith("runtime-") || !entry.endsWith(".json")) {
|
|
878
|
+
continue;
|
|
879
|
+
}
|
|
880
|
+
const state = readDevRuntimeStateFile(resolve(devRuntimeStateDir(tenantRoot), entry));
|
|
881
|
+
if (state) {
|
|
882
|
+
states.push(state);
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
} catch {
|
|
886
|
+
}
|
|
887
|
+
return states;
|
|
888
|
+
}
|
|
889
|
+
function runtimeStateOverlaps(state, commandIds) {
|
|
890
|
+
if (!state.commandIds || state.commandIds.length === 0) {
|
|
891
|
+
return true;
|
|
892
|
+
}
|
|
893
|
+
return state.commandIds.some((id) => commandIds.includes(id));
|
|
894
|
+
}
|
|
895
|
+
function listLiveOverlappingDevRuntimeStates(tenantRoot, commandIds, processIsAlive) {
|
|
896
|
+
const live = [];
|
|
897
|
+
for (const state of listDevRuntimeStates(tenantRoot)) {
|
|
898
|
+
const statePath = state.statePath;
|
|
899
|
+
if (!statePath || !runtimeStateOverlaps(state, commandIds)) {
|
|
900
|
+
continue;
|
|
901
|
+
}
|
|
902
|
+
if (state.pid === process.pid) {
|
|
903
|
+
continue;
|
|
904
|
+
}
|
|
905
|
+
if (!processIsAlive(state.pid)) {
|
|
906
|
+
rmSync(statePath, { force: true });
|
|
907
|
+
continue;
|
|
908
|
+
}
|
|
909
|
+
live.push(state);
|
|
910
|
+
}
|
|
911
|
+
return live;
|
|
912
|
+
}
|
|
913
|
+
function parsePortFromUrl(value) {
|
|
914
|
+
if (!value) return null;
|
|
915
|
+
try {
|
|
916
|
+
const url = new URL(value);
|
|
917
|
+
const port = Number(url.port || (url.protocol === "https:" ? 443 : 80));
|
|
918
|
+
return Number.isInteger(port) && port > 0 ? port : null;
|
|
919
|
+
} catch {
|
|
920
|
+
return null;
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
function requiredDevPorts(plan) {
|
|
924
|
+
const ports = [];
|
|
925
|
+
for (const command of plan.commands) {
|
|
926
|
+
if (command.id === "web") {
|
|
927
|
+
const port = parsePortFromUrl(plan.webUrl ?? void 0);
|
|
928
|
+
if (port) ports.push(port);
|
|
929
|
+
}
|
|
930
|
+
if (command.id === "api") {
|
|
931
|
+
const port = parsePortFromUrl(plan.apiBaseUrl);
|
|
932
|
+
if (port) ports.push(port);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
return [...new Set(ports)];
|
|
936
|
+
}
|
|
937
|
+
function formatPortOwner(owner) {
|
|
938
|
+
return `port ${owner.port}${owner.pid ? ` pid ${owner.pid}` : ""}${owner.processName ? ` (${owner.processName})` : ""}`;
|
|
939
|
+
}
|
|
940
|
+
function writeCurrentDevRuntimeState(tenantRoot, commandIds) {
|
|
941
|
+
const outputPath = devRuntimeStatePath(tenantRoot, runtimeScopeKey(commandIds));
|
|
805
942
|
mkdirSync(dirname(outputPath), { recursive: true });
|
|
806
943
|
writeFileSync(
|
|
807
944
|
outputPath,
|
|
808
945
|
`${JSON.stringify({
|
|
809
946
|
pid: process.pid,
|
|
810
947
|
tenantRoot,
|
|
948
|
+
commandIds,
|
|
811
949
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
812
950
|
}, null, 2)}
|
|
813
951
|
`,
|
|
814
952
|
"utf8"
|
|
815
953
|
);
|
|
954
|
+
return outputPath;
|
|
816
955
|
}
|
|
817
|
-
function removeCurrentDevRuntimeState(tenantRoot) {
|
|
818
|
-
const
|
|
956
|
+
function removeCurrentDevRuntimeState(tenantRoot, commandIds) {
|
|
957
|
+
const statePath = devRuntimeStatePath(tenantRoot, runtimeScopeKey(commandIds));
|
|
958
|
+
const state = readDevRuntimeStateFile(statePath);
|
|
819
959
|
if (!state || state.pid !== process.pid) {
|
|
820
960
|
return;
|
|
821
961
|
}
|
|
822
|
-
rmSync(
|
|
962
|
+
rmSync(statePath, { force: true });
|
|
823
963
|
}
|
|
824
964
|
async function waitForProcessExit(pid, processIsAlive, timeoutMs) {
|
|
825
965
|
const startedAt = Date.now();
|
|
@@ -831,38 +971,106 @@ async function waitForProcessExit(pid, processIsAlive, timeoutMs) {
|
|
|
831
971
|
}
|
|
832
972
|
return !processIsAlive(pid);
|
|
833
973
|
}
|
|
834
|
-
async function
|
|
835
|
-
const state
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
974
|
+
async function stopPreviousDevRuntimes(tenantRoot, commandIds, options, deps) {
|
|
975
|
+
for (const state of listLiveOverlappingDevRuntimeStates(tenantRoot, commandIds, deps.processIsAlive)) {
|
|
976
|
+
const statePath = state.statePath;
|
|
977
|
+
if (!statePath) continue;
|
|
978
|
+
emitEvent(options, deps.write, {
|
|
979
|
+
type: "replace",
|
|
980
|
+
message: `Stopping previous Treeseed dev runtime (${state.pid}) before starting overlapping surfaces.`,
|
|
981
|
+
detail: { pid: state.pid, startedAt: state.startedAt, commandIds: state.commandIds ?? null }
|
|
982
|
+
});
|
|
983
|
+
try {
|
|
984
|
+
deps.killProcess(state.pid, "SIGTERM");
|
|
985
|
+
} catch {
|
|
986
|
+
}
|
|
987
|
+
if (await waitForProcessExit(state.pid, deps.processIsAlive, options.shutdownGraceMs ?? DEFAULT_SHUTDOWN_GRACE_MS)) {
|
|
988
|
+
rmSync(statePath, { force: true });
|
|
989
|
+
continue;
|
|
990
|
+
}
|
|
991
|
+
try {
|
|
992
|
+
deps.killProcess(state.pid, "SIGKILL");
|
|
993
|
+
} catch {
|
|
994
|
+
}
|
|
995
|
+
await waitForProcessExit(state.pid, deps.processIsAlive, DEFAULT_KILL_GRACE_MS);
|
|
844
996
|
rmSync(statePath, { force: true });
|
|
845
|
-
return;
|
|
846
997
|
}
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
998
|
+
}
|
|
999
|
+
async function stopPortOwners(owners, options, deps) {
|
|
1000
|
+
const pids = [...new Set(owners.map((owner) => owner.pid).filter((pid) => Number.isInteger(pid) && pid > 0 && pid !== process.pid))];
|
|
1001
|
+
for (const pid of pids) {
|
|
1002
|
+
emitEvent(options, deps.write, {
|
|
1003
|
+
type: "replace",
|
|
1004
|
+
message: `Stopping service on required dev port (pid ${pid}).`,
|
|
1005
|
+
detail: owners.filter((owner) => owner.pid === pid)
|
|
1006
|
+
});
|
|
1007
|
+
try {
|
|
1008
|
+
deps.killProcess(pid, "SIGTERM");
|
|
1009
|
+
} catch {
|
|
1010
|
+
}
|
|
1011
|
+
if (await waitForProcessExit(pid, deps.processIsAlive, options.shutdownGraceMs ?? DEFAULT_SHUTDOWN_GRACE_MS)) {
|
|
1012
|
+
continue;
|
|
1013
|
+
}
|
|
1014
|
+
try {
|
|
1015
|
+
deps.killProcess(pid, "SIGKILL");
|
|
1016
|
+
} catch {
|
|
1017
|
+
}
|
|
1018
|
+
await waitForProcessExit(pid, deps.processIsAlive, DEFAULT_KILL_GRACE_MS);
|
|
855
1019
|
}
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
1020
|
+
}
|
|
1021
|
+
async function prepareDevRuntimeSlots(plan, options, deps) {
|
|
1022
|
+
const commandIds = plan.commands.map((command) => command.id);
|
|
1023
|
+
const liveRuntimeStates = listLiveOverlappingDevRuntimeStates(plan.tenantRoot, commandIds, deps.processIsAlive);
|
|
1024
|
+
const ports = requiredDevPorts(plan);
|
|
1025
|
+
const portOwners = deps.inspectPortOwners(ports).filter((owner) => owner.pid !== process.pid);
|
|
1026
|
+
if (options.force !== true) {
|
|
1027
|
+
if (liveRuntimeStates.length > 0 || portOwners.length > 0) {
|
|
1028
|
+
emitEvent(options, deps.write, {
|
|
1029
|
+
type: "error",
|
|
1030
|
+
status: "existing-service",
|
|
1031
|
+
message: [
|
|
1032
|
+
"Treeseed dev found an existing runtime or service on a required port.",
|
|
1033
|
+
"Stop it first, or rerun with --force to terminate overlapping Treeseed dev services and port owners."
|
|
1034
|
+
].join(" "),
|
|
1035
|
+
detail: {
|
|
1036
|
+
runtimes: liveRuntimeStates.map((state) => ({
|
|
1037
|
+
pid: state.pid,
|
|
1038
|
+
startedAt: state.startedAt,
|
|
1039
|
+
commandIds: state.commandIds ?? null,
|
|
1040
|
+
statePath: state.statePath ?? null
|
|
1041
|
+
})),
|
|
1042
|
+
ports: portOwners.map((owner) => ({ ...owner, label: formatPortOwner(owner) }))
|
|
1043
|
+
}
|
|
1044
|
+
});
|
|
1045
|
+
return false;
|
|
1046
|
+
}
|
|
1047
|
+
return true;
|
|
859
1048
|
}
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
1049
|
+
await stopPreviousDevRuntimes(plan.tenantRoot, commandIds, options, deps);
|
|
1050
|
+
if (portOwners.length > 0) {
|
|
1051
|
+
const ownersWithoutPid = portOwners.filter((owner) => owner.pid == null);
|
|
1052
|
+
if (ownersWithoutPid.length > 0) {
|
|
1053
|
+
emitEvent(options, deps.write, {
|
|
1054
|
+
type: "error",
|
|
1055
|
+
status: "existing-service",
|
|
1056
|
+
message: `Cannot force-stop required dev ports because some listeners did not expose process ids: ${ownersWithoutPid.map(formatPortOwner).join(", ")}.`,
|
|
1057
|
+
detail: ownersWithoutPid
|
|
1058
|
+
});
|
|
1059
|
+
return false;
|
|
1060
|
+
}
|
|
1061
|
+
await stopPortOwners(portOwners, options, deps);
|
|
863
1062
|
}
|
|
864
|
-
|
|
865
|
-
|
|
1063
|
+
const remainingPortOwners = deps.inspectPortOwners(ports).filter((owner) => owner.pid !== process.pid);
|
|
1064
|
+
if (remainingPortOwners.length > 0) {
|
|
1065
|
+
emitEvent(options, deps.write, {
|
|
1066
|
+
type: "error",
|
|
1067
|
+
status: "existing-service",
|
|
1068
|
+
message: `Required dev ports are still occupied after --force: ${remainingPortOwners.map(formatPortOwner).join(", ")}.`,
|
|
1069
|
+
detail: remainingPortOwners
|
|
1070
|
+
});
|
|
1071
|
+
return false;
|
|
1072
|
+
}
|
|
1073
|
+
return true;
|
|
866
1074
|
}
|
|
867
1075
|
function emitEvent(options, write, event, stream = event.type === "error" ? "stderr" : "stdout") {
|
|
868
1076
|
if (options.json) {
|
|
@@ -875,6 +1083,20 @@ function emitEvent(options, write, event, stream = event.type === "error" ? "std
|
|
|
875
1083
|
write(`${surface} ${String(message)}
|
|
876
1084
|
`, stream);
|
|
877
1085
|
}
|
|
1086
|
+
function createDevLogWrite(baseWrite, logPath) {
|
|
1087
|
+
mkdirSync(dirname(logPath), { recursive: true });
|
|
1088
|
+
appendFileSync(logPath, `${JSON.stringify({
|
|
1089
|
+
schemaVersion: 1,
|
|
1090
|
+
kind: "treeseed.dev.log",
|
|
1091
|
+
type: "start",
|
|
1092
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1093
|
+
})}
|
|
1094
|
+
`, "utf8");
|
|
1095
|
+
return (line, stream) => {
|
|
1096
|
+
baseWrite(line, stream);
|
|
1097
|
+
appendFileSync(logPath, line, "utf8");
|
|
1098
|
+
};
|
|
1099
|
+
}
|
|
878
1100
|
function runTreeseedIntegratedDevReset(reset, options, deps) {
|
|
879
1101
|
if (!reset?.enabled) {
|
|
880
1102
|
return null;
|
|
@@ -979,6 +1201,8 @@ function writePlan(plan, options, write) {
|
|
|
979
1201
|
`, "stdout");
|
|
980
1202
|
}
|
|
981
1203
|
write(`api: ${plan.apiBaseUrl}
|
|
1204
|
+
`, "stdout");
|
|
1205
|
+
write(`log: ${plan.logPath}
|
|
982
1206
|
`, "stdout");
|
|
983
1207
|
for (const [name, runtime] of Object.entries(plan.localRuntimes)) {
|
|
984
1208
|
write(`runtime ${name}: ${runtime.selected} (${runtime.provider}, requested ${runtime.requested})${runtime.reason ? ` - ${runtime.reason}` : ""}
|
|
@@ -1216,7 +1440,7 @@ function failedSetupMessage(failed) {
|
|
|
1216
1440
|
}
|
|
1217
1441
|
async function runTreeseedIntegratedDev(options = {}, deps = {}) {
|
|
1218
1442
|
const tenantRoot = resolve(options.cwd ?? process.cwd());
|
|
1219
|
-
|
|
1443
|
+
let write = deps.write ?? defaultWrite;
|
|
1220
1444
|
const spawnProcess = deps.spawn ?? spawn;
|
|
1221
1445
|
const spawnSyncProcess = deps.spawnSync ?? spawnSync;
|
|
1222
1446
|
const onSignal = deps.onSignal ?? defaultSignalRegistrar;
|
|
@@ -1228,6 +1452,7 @@ async function runTreeseedIntegratedDev(options = {}, deps = {}) {
|
|
|
1228
1452
|
const prepareEnvironment = deps.prepareEnvironment ?? defaultPrepareEnvironment;
|
|
1229
1453
|
const removePath = deps.removePath ?? defaultRemovePath;
|
|
1230
1454
|
const stopMailpit = deps.stopMailpitContainers ?? stopKnownMailpitContainers;
|
|
1455
|
+
const inspectPortOwners = deps.inspectPortOwners ?? defaultInspectPortOwners;
|
|
1231
1456
|
prepareEnvironment(tenantRoot);
|
|
1232
1457
|
const plan = createTreeseedIntegratedDevPlan({
|
|
1233
1458
|
...options,
|
|
@@ -1241,8 +1466,15 @@ async function runTreeseedIntegratedDev(options = {}, deps = {}) {
|
|
|
1241
1466
|
writePlan(plan, options, write);
|
|
1242
1467
|
return 0;
|
|
1243
1468
|
}
|
|
1244
|
-
|
|
1245
|
-
|
|
1469
|
+
const commandIds = plan.commands.map((command) => command.id);
|
|
1470
|
+
write = createDevLogWrite(write, plan.logPath);
|
|
1471
|
+
emitEvent(options, write, {
|
|
1472
|
+
type: "log",
|
|
1473
|
+
message: `Writing Treeseed dev logs to ${plan.logPath}.`,
|
|
1474
|
+
detail: { logPath: plan.logPath }
|
|
1475
|
+
});
|
|
1476
|
+
if (!await prepareDevRuntimeSlots(plan, options, { write, killProcess, processIsAlive, inspectPortOwners })) {
|
|
1477
|
+
return 1;
|
|
1246
1478
|
}
|
|
1247
1479
|
const resetResults = runTreeseedIntegratedDevReset(plan.reset, options, {
|
|
1248
1480
|
write,
|
|
@@ -1258,7 +1490,7 @@ async function runTreeseedIntegratedDev(options = {}, deps = {}) {
|
|
|
1258
1490
|
});
|
|
1259
1491
|
return 1;
|
|
1260
1492
|
}
|
|
1261
|
-
writeCurrentDevRuntimeState(tenantRoot);
|
|
1493
|
+
writeCurrentDevRuntimeState(tenantRoot, commandIds);
|
|
1262
1494
|
const children = /* @__PURE__ */ new Map();
|
|
1263
1495
|
const commandsById = new Map(plan.commands.map((command) => [command.id, command]));
|
|
1264
1496
|
const requiredSurfaceIds = new Set(plan.readyChecks.filter((check) => check.required).map((check) => check.id));
|
|
@@ -1311,7 +1543,7 @@ async function runTreeseedIntegratedDev(options = {}, deps = {}) {
|
|
|
1311
1543
|
for (const dispose of disposers) {
|
|
1312
1544
|
dispose();
|
|
1313
1545
|
}
|
|
1314
|
-
removeCurrentDevRuntimeState(tenantRoot);
|
|
1546
|
+
removeCurrentDevRuntimeState(tenantRoot, commandIds);
|
|
1315
1547
|
emitEvent(
|
|
1316
1548
|
options,
|
|
1317
1549
|
write,
|
|
@@ -9,16 +9,53 @@ type LocalContributor = {
|
|
|
9
9
|
};
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
+
type MetadataItem = {
|
|
13
|
+
label: string;
|
|
14
|
+
value?: unknown;
|
|
15
|
+
href?: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
12
18
|
function entryTitle(entry: RuntimeReferenceEntry) {
|
|
13
19
|
return entry.data.title ?? entry.data.name ?? entry.id;
|
|
14
20
|
}
|
|
15
21
|
|
|
22
|
+
function displayValue(value: unknown) {
|
|
23
|
+
if (value instanceof Date) return value.toISOString().slice(0, 10);
|
|
24
|
+
if (Array.isArray(value)) return value.map((entry) => displayValue(entry)).filter(Boolean).join(', ');
|
|
25
|
+
if (typeof value === 'string') return value.trim();
|
|
26
|
+
if (typeof value === 'number' || typeof value === 'boolean') return String(value);
|
|
27
|
+
return '';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function metadataItem(label: string, value: unknown, href?: string): MetadataItem | null {
|
|
31
|
+
const display = displayValue(value);
|
|
32
|
+
return display ? { label, value: display, href } : null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function metadataKey(item: MetadataItem) {
|
|
36
|
+
return `${item.label.toLowerCase()}::${displayValue(item.value).toLowerCase()}::${item.href ?? ''}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function uniqueMetadata(items: MetadataItem[]) {
|
|
40
|
+
const seen = new Set<string>();
|
|
41
|
+
return items.filter((item) => {
|
|
42
|
+
const key = metadataKey(item);
|
|
43
|
+
if (seen.has(key)) return false;
|
|
44
|
+
seen.add(key);
|
|
45
|
+
return true;
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
16
49
|
const {
|
|
17
50
|
entry,
|
|
18
51
|
currentPath,
|
|
19
52
|
contributor,
|
|
53
|
+
contentTypeLabel = 'Content',
|
|
54
|
+
contentId,
|
|
55
|
+
collectionLabel,
|
|
20
56
|
metaLabel,
|
|
21
57
|
metaValue,
|
|
58
|
+
metadataItems = [],
|
|
22
59
|
relatedQuestions = [],
|
|
23
60
|
relatedObjectives = [],
|
|
24
61
|
relatedNotes = [],
|
|
@@ -35,11 +72,17 @@ const {
|
|
|
35
72
|
date: Date;
|
|
36
73
|
motivation?: string;
|
|
37
74
|
tags: string[];
|
|
75
|
+
canonicalRoute?: string;
|
|
76
|
+
author?: string;
|
|
38
77
|
};
|
|
39
78
|
currentPath: string;
|
|
40
79
|
contributor: LocalContributor | RuntimeReferenceEntry | undefined | null;
|
|
80
|
+
contentTypeLabel?: string;
|
|
81
|
+
contentId?: string;
|
|
82
|
+
collectionLabel?: string;
|
|
41
83
|
metaLabel?: string;
|
|
42
84
|
metaValue?: string;
|
|
85
|
+
metadataItems?: MetadataItem[];
|
|
43
86
|
relatedQuestions?: RuntimeReferenceEntry[];
|
|
44
87
|
relatedObjectives?: RuntimeReferenceEntry[];
|
|
45
88
|
relatedNotes?: RuntimeReferenceEntry[];
|
|
@@ -48,88 +91,105 @@ const {
|
|
|
48
91
|
relatedBooks?: RuntimeReferenceEntry[];
|
|
49
92
|
introText?: string;
|
|
50
93
|
};
|
|
94
|
+
|
|
95
|
+
const contributorName = contributor?.data.name ?? entry.author ?? '';
|
|
96
|
+
const normalizedTypeLabel = contentTypeLabel.trim() || 'Content';
|
|
97
|
+
const contentKind = displayValue(metaValue);
|
|
98
|
+
const recordMetadata = uniqueMetadata([
|
|
99
|
+
metadataItem('ID', contentId),
|
|
100
|
+
metadataItem('Collection', collectionLabel ?? (currentPath.replaceAll('/', '') || undefined)),
|
|
101
|
+
metadataItem(metaLabel ?? '', metaValue),
|
|
102
|
+
metadataItem('Route', entry.canonicalRoute, entry.canonicalRoute),
|
|
103
|
+
...metadataItems.map((item) => metadataItem(item.label, item.value, item.href)).filter(Boolean),
|
|
104
|
+
].filter(Boolean) as MetadataItem[]);
|
|
105
|
+
|
|
106
|
+
const relationSections = [
|
|
107
|
+
{ label: 'Questions', hrefBase: '/questions', items: relatedQuestions },
|
|
108
|
+
{ label: 'Objectives', hrefBase: '/objectives', items: relatedObjectives },
|
|
109
|
+
{ label: 'Notes', hrefBase: '/notes', items: relatedNotes },
|
|
110
|
+
{ label: 'Proposals', hrefBase: '/proposals', items: relatedProposals },
|
|
111
|
+
{ label: 'Decisions', hrefBase: '/decisions', items: relatedDecisions },
|
|
112
|
+
{ label: 'Books', hrefBase: '/books', items: relatedBooks },
|
|
113
|
+
].filter((section) => section.items.length > 0);
|
|
114
|
+
|
|
115
|
+
const relationCount = relationSections.reduce((total, section) => total + section.items.length, 0);
|
|
116
|
+
const plainIntroText = displayValue(introText ?? entry.motivation);
|
|
51
117
|
---
|
|
52
118
|
|
|
53
119
|
<MainLayout title={entry.title} description={entry.description} currentPath={currentPath}>
|
|
54
|
-
<article class="max-w-
|
|
55
|
-
<
|
|
56
|
-
<div class="
|
|
57
|
-
<
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
120
|
+
<article class="max-w-6xl space-y-8">
|
|
121
|
+
<header class="grid gap-6 border-b border-[color:var(--ts-color-border)] pb-7 lg:grid-cols-[minmax(0,1fr)_18rem]">
|
|
122
|
+
<div class="space-y-4">
|
|
123
|
+
<div class="flex flex-wrap items-center gap-2.5">
|
|
124
|
+
<p class="rounded-full border border-[color:var(--ts-color-border)] px-3 py-1 text-xs font-semibold uppercase text-[color:var(--ts-color-info-text)]">{normalizedTypeLabel}</p>
|
|
125
|
+
{contentKind && <p class="rounded-full bg-[color:var(--ts-color-surface-muted)] px-3 py-1 text-xs font-semibold uppercase text-[color:var(--ts-color-text-subtle)]">{contentKind}</p>}
|
|
126
|
+
<StatusBadge status={entry.status} />
|
|
127
|
+
<p class="text-sm font-medium text-[color:var(--ts-color-text-subtle)]">{entry.date.toISOString().slice(0, 10)}</p>
|
|
128
|
+
{contributorName && <p class="text-sm text-[color:var(--ts-color-text-subtle)]">{contributorName}</p>}
|
|
129
|
+
</div>
|
|
130
|
+
<h1 class="max-w-4xl font-serif text-4xl font-bold tracking-normal text-[color:var(--ts-color-text)] md:text-5xl">{entry.title}</h1>
|
|
131
|
+
<p class="max-w-3xl text-lg leading-8 text-[color:var(--ts-color-text-muted)] md:text-xl md:leading-9">{entry.summary}</p>
|
|
132
|
+
{plainIntroText && (
|
|
133
|
+
<div class="max-w-3xl border-l-2 border-[color:var(--ts-color-border-strong)] pl-4">
|
|
134
|
+
<p class="text-xs font-semibold uppercase tracking-[0.14em] text-[color:var(--ts-color-info-text)]">Why it matters</p>
|
|
135
|
+
<p class="mt-1 text-sm leading-7 text-[color:var(--ts-color-text-muted)]">{plainIntroText}</p>
|
|
136
|
+
</div>
|
|
137
|
+
)}
|
|
138
|
+
{entry.tags.length > 0 && (
|
|
139
|
+
<ul class="flex flex-wrap gap-2" aria-label="Tags">
|
|
140
|
+
{entry.tags.map((tag) => (
|
|
141
|
+
<li class="rounded-full bg-[color:var(--ts-color-surface-muted)] px-3 py-1 text-xs font-semibold uppercase text-[color:var(--ts-color-accent-strong)]">{tag}</li>
|
|
142
|
+
))}
|
|
143
|
+
</ul>
|
|
144
|
+
)}
|
|
61
145
|
</div>
|
|
62
|
-
<
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
146
|
+
<aside class="self-start border-l border-[color:var(--ts-color-border)] pl-5">
|
|
147
|
+
<div class="flex items-center justify-between gap-3 border-b border-[color:var(--ts-color-border)] pb-3">
|
|
148
|
+
<div>
|
|
149
|
+
<p class="text-xs font-semibold uppercase tracking-[0.14em] text-[color:var(--ts-color-info-text)]">Record</p>
|
|
150
|
+
<p class="mt-1 text-sm text-[color:var(--ts-color-text-muted)]">{relationCount} linked record{relationCount === 1 ? '' : 's'}</p>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
<dl class="mt-2 text-sm">
|
|
154
|
+
{recordMetadata.map((item) => (
|
|
155
|
+
<div class="grid grid-cols-[5.75rem_minmax(0,1fr)] gap-3 py-2.5">
|
|
156
|
+
<dt class="text-[color:var(--ts-color-text-subtle)]">{item.label}</dt>
|
|
157
|
+
<dd class="min-w-0 break-words font-medium text-[color:var(--ts-color-text)]">
|
|
158
|
+
{item.href ? <a class="underline decoration-[color:var(--ts-color-border-strong)] underline-offset-4 hover:text-[color:var(--ts-color-accent-strong)]" href={item.href}>{displayValue(item.value)}</a> : displayValue(item.value)}
|
|
159
|
+
</dd>
|
|
160
|
+
</div>
|
|
161
|
+
))}
|
|
162
|
+
</dl>
|
|
163
|
+
</aside>
|
|
164
|
+
</header>
|
|
165
|
+
<div class="prose-karyon max-w-3xl">
|
|
70
166
|
<slot />
|
|
71
167
|
</div>
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
<div>
|
|
75
|
-
|
|
76
|
-
<
|
|
77
|
-
{relatedQuestions.map((question) => (
|
|
78
|
-
<li><a href={`/questions/${question.id}/`} class="hover:text-[color:var(--ts-color-text)]">{entryTitle(question)}</a></li>
|
|
79
|
-
))}
|
|
80
|
-
</ul>
|
|
81
|
-
</div>
|
|
82
|
-
)}
|
|
83
|
-
{relatedObjectives.length > 0 && (
|
|
84
|
-
<div>
|
|
85
|
-
<p class="text-sm font-semibold uppercase tracking-[0.14em] text-[color:var(--ts-color-info-text)]">Related objectives</p>
|
|
86
|
-
<ul class="mt-3 space-y-2 text-[color:var(--ts-color-text-muted)]">
|
|
87
|
-
{relatedObjectives.map((objective) => (
|
|
88
|
-
<li><a href={`/objectives/${objective.id}/`} class="hover:text-[color:var(--ts-color-text)]">{entryTitle(objective)}</a></li>
|
|
89
|
-
))}
|
|
90
|
-
</ul>
|
|
168
|
+
{relationSections.length > 0 && (
|
|
169
|
+
<section class="border-t border-[color:var(--ts-color-border)] pt-7">
|
|
170
|
+
<div class="mb-4 flex flex-wrap items-end justify-between gap-3">
|
|
171
|
+
<div>
|
|
172
|
+
<p class="text-xs font-semibold uppercase tracking-[0.14em] text-[color:var(--ts-color-info-text)]">Connections</p>
|
|
91
173
|
</div>
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
<
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
)}
|
|
113
|
-
{relatedDecisions.length > 0 && (
|
|
114
|
-
<div>
|
|
115
|
-
<p class="text-sm font-semibold uppercase tracking-[0.14em] text-[color:var(--ts-color-info-text)]">Related decisions</p>
|
|
116
|
-
<ul class="mt-3 space-y-2 text-[color:var(--ts-color-text-muted)]">
|
|
117
|
-
{relatedDecisions.map((decision) => (
|
|
118
|
-
<li><a href={`/decisions/${decision.id}/`} class="hover:text-[color:var(--ts-color-text)]">{entryTitle(decision)}</a></li>
|
|
119
|
-
))}
|
|
120
|
-
</ul>
|
|
121
|
-
</div>
|
|
122
|
-
)}
|
|
123
|
-
{relatedBooks.length > 0 && (
|
|
124
|
-
<div>
|
|
125
|
-
<p class="text-sm font-semibold uppercase tracking-[0.14em] text-[color:var(--ts-color-info-text)]">Related books</p>
|
|
126
|
-
<ul class="mt-3 space-y-2 text-[color:var(--ts-color-text-muted)]">
|
|
127
|
-
{relatedBooks.map((book) => (
|
|
128
|
-
<li><a href={`/books/${book.id}/`} class="hover:text-[color:var(--ts-color-text)]">{entryTitle(book)}</a></li>
|
|
129
|
-
))}
|
|
130
|
-
</ul>
|
|
131
|
-
</div>
|
|
132
|
-
)}
|
|
133
|
-
</div>
|
|
174
|
+
</div>
|
|
175
|
+
<div class="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
|
|
176
|
+
{relationSections.map((section) => (
|
|
177
|
+
<div class="border-l border-[color:var(--ts-color-border)] pl-4">
|
|
178
|
+
<p class="text-xs font-semibold uppercase tracking-[0.12em] text-[color:var(--ts-color-text-subtle)]">{section.label}</p>
|
|
179
|
+
<ul class="mt-2 space-y-2">
|
|
180
|
+
{section.items.map((item) => (
|
|
181
|
+
<li>
|
|
182
|
+
<a href={`${section.hrefBase}/${item.id}/`} class="block text-[color:var(--ts-color-text)] hover:text-[color:var(--ts-color-accent-strong)]">
|
|
183
|
+
<span class="block text-sm font-semibold">{entryTitle(item)}</span>
|
|
184
|
+
<span class="block text-xs text-[color:var(--ts-color-text-subtle)]">{item.id}</span>
|
|
185
|
+
</a>
|
|
186
|
+
</li>
|
|
187
|
+
))}
|
|
188
|
+
</ul>
|
|
189
|
+
</div>
|
|
190
|
+
))}
|
|
191
|
+
</div>
|
|
192
|
+
</section>
|
|
193
|
+
)}
|
|
134
194
|
</article>
|
|
135
195
|
</MainLayout>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
import '../styles/global.css';
|
|
3
3
|
import PublicShell from '../components/ui/shell/PublicShell.astro';
|
|
4
|
+
import ShellIconLink from '../components/ui/shell/ShellIconLink.astro';
|
|
4
5
|
import { SITE_NAV_GROUPS } from '../utils/routes';
|
|
5
6
|
import { SITE } from '../utils/seo';
|
|
6
7
|
import { SITE_THEME_CSS } from '../utils/site-config';
|
|
@@ -36,6 +37,7 @@ const navItems = SITE_NAV_GROUPS.flatMap((group) => group.items);
|
|
|
36
37
|
{SITE_THEME_CSS && <style is:global>{SITE_THEME_CSS}</style>}
|
|
37
38
|
</Fragment>
|
|
38
39
|
<Fragment slot="actions">
|
|
40
|
+
<ShellIconLink href="/app/" label="Manager" icon="manager" />
|
|
39
41
|
<a
|
|
40
42
|
href={SITE.githubRepository}
|
|
41
43
|
target="_blank"
|
|
@@ -57,9 +57,17 @@ const supersededDecisions = publishedRuntime
|
|
|
57
57
|
entry={decision.data}
|
|
58
58
|
currentPath="/decisions/"
|
|
59
59
|
contributor={contributor}
|
|
60
|
+
contentTypeLabel="Decision"
|
|
61
|
+
contentId={decision.id}
|
|
62
|
+
collectionLabel="decisions"
|
|
60
63
|
metaLabel="Decision type"
|
|
61
64
|
metaValue={String(decision.data.decisionType ?? '')}
|
|
62
|
-
introText={
|
|
65
|
+
introText={decision.data.rationale}
|
|
66
|
+
metadataItems={[
|
|
67
|
+
{ label: 'Authority', value: decision.data.authority },
|
|
68
|
+
{ label: 'Route', value: decision.data.canonicalRoute, href: decision.data.canonicalRoute },
|
|
69
|
+
{ label: 'Implements', value: decision.data.implements },
|
|
70
|
+
]}
|
|
63
71
|
relatedObjectives={relatedObjectives}
|
|
64
72
|
relatedQuestions={relatedQuestions}
|
|
65
73
|
relatedNotes={relatedNotes}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { getCollection, render } from 'astro:content';
|
|
3
|
-
import
|
|
3
|
+
import AuthoredEntryLayout from '../../layouts/AuthoredEntryLayout.astro';
|
|
4
4
|
import PublishedContentBody from '../../components/site/PublishedContentBody.astro';
|
|
5
5
|
import RouteNotFound from '../../components/site/RouteNotFound.astro';
|
|
6
|
-
import { isPublishedRuntimeContentMode, loadPublishedEntry } from '../../utils/site-content-runtime';
|
|
6
|
+
import { isPublishedRuntimeContentMode, loadPublishedEntry, metadataFromPublishedContent } from '../../utils/site-content-runtime';
|
|
7
7
|
|
|
8
8
|
export const prerender = false;
|
|
9
9
|
|
|
@@ -13,6 +13,7 @@ const notes = publishedRuntime ? [] : await getCollection('notes', ({ data }) =>
|
|
|
13
13
|
const localNote = publishedRuntime ? null : notes.find((candidate) => candidate.id === slug) ?? null;
|
|
14
14
|
const publishedNote = publishedRuntime ? await loadPublishedEntry(Astro.locals, 'notes', slug) : null;
|
|
15
15
|
const note = publishedRuntime ? publishedNote?.entry ?? null : localNote;
|
|
16
|
+
const metadata = publishedRuntime ? metadataFromPublishedContent(publishedNote?.content) : null;
|
|
16
17
|
if (!note) {
|
|
17
18
|
Astro.response.status = 404;
|
|
18
19
|
}
|
|
@@ -24,8 +25,20 @@ const Content = rendered?.Content ?? null;
|
|
|
24
25
|
!note || (!Content && !publishedNote?.html) ? (
|
|
25
26
|
<RouteNotFound title="Note not found" description="The requested note could not be found in this Treeseed." currentPath="/notes/" />
|
|
26
27
|
) : (
|
|
27
|
-
<
|
|
28
|
+
<AuthoredEntryLayout
|
|
29
|
+
entry={note.data}
|
|
30
|
+
currentPath="/notes/"
|
|
31
|
+
contributor={null}
|
|
32
|
+
contentTypeLabel="Note"
|
|
33
|
+
contentId={note.id}
|
|
34
|
+
collectionLabel="notes"
|
|
35
|
+
metaLabel="Author"
|
|
36
|
+
metaValue={String(note.data.author ?? metadata?.author ?? '')}
|
|
37
|
+
metadataItems={[
|
|
38
|
+
{ label: 'Route', value: note.data.canonicalRoute ?? metadata?.canonicalRoute, href: note.data.canonicalRoute ?? metadata?.canonicalRoute },
|
|
39
|
+
]}
|
|
40
|
+
>
|
|
28
41
|
{publishedRuntime ? <PublishedContentBody html={publishedNote?.html ?? ''} /> : <Content />}
|
|
29
|
-
</
|
|
42
|
+
</AuthoredEntryLayout>
|
|
30
43
|
)
|
|
31
44
|
}
|
|
@@ -45,8 +45,12 @@ const relatedBooks = publishedRuntime
|
|
|
45
45
|
entry={objective.data}
|
|
46
46
|
currentPath="/objectives/"
|
|
47
47
|
contributor={contributor}
|
|
48
|
+
contentTypeLabel="Objective"
|
|
49
|
+
contentId={objective.id}
|
|
50
|
+
collectionLabel="objectives"
|
|
48
51
|
metaLabel="Time horizon"
|
|
49
52
|
metaValue={String(objective.data.timeHorizon ?? '')}
|
|
53
|
+
metadataItems={[]}
|
|
50
54
|
relatedQuestions={relatedQuestions}
|
|
51
55
|
relatedBooks={relatedBooks}
|
|
52
56
|
>
|
|
@@ -57,8 +57,14 @@ const supersededProposals = publishedRuntime
|
|
|
57
57
|
entry={proposal.data}
|
|
58
58
|
currentPath="/proposals/"
|
|
59
59
|
contributor={contributor}
|
|
60
|
+
contentTypeLabel="Proposal"
|
|
61
|
+
contentId={proposal.id}
|
|
62
|
+
collectionLabel="proposals"
|
|
60
63
|
metaLabel="Proposal type"
|
|
61
64
|
metaValue={String(proposal.data.proposalType ?? '')}
|
|
65
|
+
metadataItems={[
|
|
66
|
+
{ label: 'Route', value: proposal.data.canonicalRoute, href: proposal.data.canonicalRoute },
|
|
67
|
+
]}
|
|
62
68
|
relatedObjectives={relatedObjectives}
|
|
63
69
|
relatedQuestions={relatedQuestions}
|
|
64
70
|
relatedNotes={relatedNotes}
|
|
@@ -45,8 +45,12 @@ const relatedBooks = publishedRuntime
|
|
|
45
45
|
entry={question.data}
|
|
46
46
|
currentPath="/questions/"
|
|
47
47
|
contributor={contributor}
|
|
48
|
+
contentTypeLabel="Question"
|
|
49
|
+
contentId={question.id}
|
|
50
|
+
collectionLabel="questions"
|
|
48
51
|
metaLabel="Question type"
|
|
49
52
|
metaValue={String(question.data.questionType ?? '')}
|
|
53
|
+
metadataItems={[]}
|
|
50
54
|
relatedObjectives={relatedObjectives}
|
|
51
55
|
relatedBooks={relatedBooks}
|
|
52
56
|
>
|
|
@@ -391,7 +391,7 @@ async function main() {
|
|
|
391
391
|
writeCompatibilityEntrypoint(resolve(distRoot, 'config.d.ts'), "export declare function createTreeseedTenantSite(manifestPath?: string): import('astro').AstroUserConfig<never, never, never>;");
|
|
392
392
|
writeCompatibilityEntrypoint(resolve(distRoot, 'content.d.ts'), "export declare function createTreeseedCollections(tenantConfig: any, dependencies: any): Record<string, any>;");
|
|
393
393
|
writeCompatibilityEntrypoint(resolve(distRoot, 'content-config.js'), "import { loadTreeseedManifest } from '@treeseed/sdk/platform/tenant-config';\nimport { docsLoader } from './vendor/starlight/loaders.js';\nimport { docsSchema } from './vendor/starlight/schema.js';\nimport { createTreeseedCollections } from './content.js';\n\nexport function createTreeseedTenantCollections(manifestPath) {\n\tconst tenant = loadTreeseedManifest(manifestPath);\n\treturn createTreeseedCollections(tenant, { docsLoader, docsSchema });\n}");
|
|
394
|
-
writeCompatibilityEntrypoint(resolve(distRoot, 'content-config.d.ts'), "export declare function createTreeseedTenantCollections(manifestPath?: string): {\n\tpages: any;\n\tnotes: any;\n\tquestions: any;\n\tobjectives: any;\n\tpeople: any;\n\tagents: any;\n\tbooks: any;\n\tdocs: any;\n\tworkdays?: any;\n};");
|
|
394
|
+
writeCompatibilityEntrypoint(resolve(distRoot, 'content-config.d.ts'), "export declare function createTreeseedTenantCollections(manifestPath?: string): {\n\tpages: any;\n\tnotes: any;\n\tquestions: any;\n\tobjectives: any;\n\tpeople: any;\n\tagents: any;\n\tagent_tests?: any;\n\tbooks: any;\n\tdocs: any;\n\tworkdays?: any;\n};");
|
|
395
395
|
writeCompatibilityEntrypoint(resolve(distRoot, 'utils/forms/service.d.ts'), "import type { APIContext } from 'astro';\nimport type { SubmitResult } from '../../types/forms.js';\nexport declare function handleTokenRequest(context: APIContext): Promise<Response>;\nexport declare function handleFormSubmission(context: APIContext): Promise<SubmitResult>;");
|
|
396
396
|
rmSync(resolve(distRoot, 'config.d.js'), { force: true });
|
|
397
397
|
rmSync(resolve(distRoot, 'content-config.d.js'), { force: true });
|
|
@@ -50,6 +50,26 @@ function parseOpenMode(value) {
|
|
|
50
50
|
}
|
|
51
51
|
return undefined;
|
|
52
52
|
}
|
|
53
|
+
function parseLocalRuntimeMode(value) {
|
|
54
|
+
if (value === 'auto' || value === 'provider' || value === 'local') {
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
function readForwardedEnvironment() {
|
|
60
|
+
const keys = [
|
|
61
|
+
'TREESEED_DOCS_AUTOMATION_MODE',
|
|
62
|
+
'TREESEED_WORKDAY_ID',
|
|
63
|
+
'TREESEED_CAPACITY_BUDGET',
|
|
64
|
+
'TREESEED_WORKDAY_TASK_CREDIT_BUDGET',
|
|
65
|
+
'TREESEED_APPROVAL_POLICY',
|
|
66
|
+
'TREESEED_MANAGER_CONSOLE_SUMMARY',
|
|
67
|
+
'TREESEED_WORKER_CONSOLE_SUMMARY',
|
|
68
|
+
];
|
|
69
|
+
return Object.fromEntries(keys
|
|
70
|
+
.map((key) => [key, process.env[key]])
|
|
71
|
+
.filter((entry) => typeof entry[1] === 'string' && entry[1].length > 0));
|
|
72
|
+
}
|
|
53
73
|
const exitCode = await runTreeseedIntegratedDev({
|
|
54
74
|
surface: parseSurface(readOption('--surface')),
|
|
55
75
|
surfaces: readOption('--surfaces'),
|
|
@@ -58,13 +78,16 @@ const exitCode = await runTreeseedIntegratedDev({
|
|
|
58
78
|
webPort: readNumberOption('--port'),
|
|
59
79
|
apiHost: readOption('--api-host'),
|
|
60
80
|
apiPort: readNumberOption('--api-port'),
|
|
81
|
+
webRuntime: parseLocalRuntimeMode(readOption('--web-runtime')),
|
|
61
82
|
setupMode: parseSetupMode(readOption('--setup')),
|
|
62
83
|
feedbackMode: parseFeedbackMode(readOption('--feedback')),
|
|
63
84
|
openMode: parseOpenMode(readOption('--open')),
|
|
64
85
|
plan: readFlag('--plan'),
|
|
65
86
|
reset: readFlag('--reset'),
|
|
87
|
+
force: readFlag('--force'),
|
|
66
88
|
json: readFlag('--json'),
|
|
67
89
|
projectId: readOption('--project-id'),
|
|
68
90
|
teamId: readOption('--team-id'),
|
|
91
|
+
env: readForwardedEnvironment(),
|
|
69
92
|
});
|
|
70
93
|
process.exit(exitCode);
|
package/dist/site.js
CHANGED
|
@@ -313,6 +313,9 @@ function createTreeseedSite(tenantConfig, { starlight }) {
|
|
|
313
313
|
__TREESEED_DEPLOY_CONFIG__: injectedDeployConfig,
|
|
314
314
|
__TREESEED_BOOK_RUNTIME__: injectedBookRuntime
|
|
315
315
|
},
|
|
316
|
+
optimizeDeps: {
|
|
317
|
+
exclude: ["libsodium-wrappers-sumo"]
|
|
318
|
+
},
|
|
316
319
|
plugins: [
|
|
317
320
|
createTenantThemeVitePlugin(tenantThemeCss),
|
|
318
321
|
tailwindcss(),
|
|
@@ -27,15 +27,25 @@
|
|
|
27
27
|
align-self: start;
|
|
28
28
|
background: var(--ts-color-canvas-subtle);
|
|
29
29
|
border-right: 1px solid var(--ts-color-border);
|
|
30
|
+
box-sizing: border-box;
|
|
30
31
|
display: grid;
|
|
31
32
|
gap: var(--ts-space-3);
|
|
33
|
+
grid-template-rows: minmax(0, 1fr) auto;
|
|
32
34
|
height: 100vh;
|
|
33
|
-
overflow:
|
|
35
|
+
overflow: hidden;
|
|
34
36
|
padding: var(--ts-space-3);
|
|
35
37
|
position: sticky;
|
|
36
38
|
top: 0;
|
|
37
39
|
}
|
|
38
40
|
|
|
41
|
+
.ts-app-shell__rail-scroll {
|
|
42
|
+
display: grid;
|
|
43
|
+
gap: var(--ts-space-3);
|
|
44
|
+
min-height: 0;
|
|
45
|
+
overflow: auto;
|
|
46
|
+
padding-right: 0.15rem;
|
|
47
|
+
}
|
|
48
|
+
|
|
39
49
|
.ts-shell-brand {
|
|
40
50
|
align-items: center;
|
|
41
51
|
color: var(--ts-color-text);
|
|
@@ -109,6 +119,12 @@
|
|
|
109
119
|
min-width: 0;
|
|
110
120
|
}
|
|
111
121
|
|
|
122
|
+
.ts-shell-utility-actions {
|
|
123
|
+
align-items: center;
|
|
124
|
+
display: inline-flex;
|
|
125
|
+
gap: var(--ts-space-1);
|
|
126
|
+
}
|
|
127
|
+
|
|
112
128
|
.ts-app-shell__rail-context,
|
|
113
129
|
.ts-app-shell__quick-actions {
|
|
114
130
|
border-top: 1px solid var(--ts-color-border);
|
|
@@ -117,6 +133,11 @@
|
|
|
117
133
|
padding-top: var(--ts-space-2);
|
|
118
134
|
}
|
|
119
135
|
|
|
136
|
+
.ts-app-shell__quick-actions {
|
|
137
|
+
background: var(--ts-color-canvas-subtle);
|
|
138
|
+
padding-bottom: 0.1rem;
|
|
139
|
+
}
|
|
140
|
+
|
|
120
141
|
.ts-app-shell__eyebrow {
|
|
121
142
|
color: var(--ts-color-text-subtle);
|
|
122
143
|
font-size: 0.75rem;
|
|
@@ -568,6 +589,10 @@
|
|
|
568
589
|
width: 100%;
|
|
569
590
|
}
|
|
570
591
|
|
|
592
|
+
.ts-app-shell__header-actions .ts-shell-utility-actions {
|
|
593
|
+
display: none;
|
|
594
|
+
}
|
|
595
|
+
|
|
571
596
|
.ts-bottom-nav {
|
|
572
597
|
display: grid;
|
|
573
598
|
}
|
package/dist/styles/theme.css
CHANGED
|
@@ -20,13 +20,13 @@ body {
|
|
|
20
20
|
|
|
21
21
|
.ts-theme-menu__trigger {
|
|
22
22
|
align-items: center;
|
|
23
|
-
background:
|
|
24
|
-
border: 1px solid
|
|
23
|
+
background: transparent;
|
|
24
|
+
border: 1px solid transparent;
|
|
25
25
|
border-radius: var(--ts-radius-md);
|
|
26
26
|
color: var(--ts-color-text-muted);
|
|
27
27
|
cursor: pointer;
|
|
28
28
|
display: inline-flex;
|
|
29
|
-
height:
|
|
29
|
+
height: 2.25rem;
|
|
30
30
|
justify-content: center;
|
|
31
31
|
list-style: none;
|
|
32
32
|
padding: 0;
|
|
@@ -34,7 +34,7 @@ body {
|
|
|
34
34
|
background-color 140ms ease,
|
|
35
35
|
border-color 140ms ease,
|
|
36
36
|
color 140ms ease;
|
|
37
|
-
width:
|
|
37
|
+
width: 2.25rem;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
.ts-theme-menu__trigger::-webkit-details-marker {
|
|
@@ -43,8 +43,8 @@ body {
|
|
|
43
43
|
|
|
44
44
|
.ts-theme-menu__trigger:hover,
|
|
45
45
|
.ts-theme-menu[open] .ts-theme-menu__trigger {
|
|
46
|
-
background: var(--ts-color-surface-
|
|
47
|
-
border-color: var(--ts-color-border
|
|
46
|
+
background: var(--ts-color-surface-raised);
|
|
47
|
+
border-color: var(--ts-color-border);
|
|
48
48
|
color: var(--ts-color-text);
|
|
49
49
|
}
|
|
50
50
|
|
|
@@ -56,8 +56,8 @@ body {
|
|
|
56
56
|
.ts-theme-menu__trigger svg {
|
|
57
57
|
display: block;
|
|
58
58
|
fill: currentColor;
|
|
59
|
-
height:
|
|
60
|
-
width:
|
|
59
|
+
height: 1.15rem;
|
|
60
|
+
width: 1.15rem;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
.ts-theme-menu__panel {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@treeseed/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Treeseed web framework package for Astro/Starlight site runtimes.",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"repository": {
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
"@astrojs/sitemap": "3.7.0",
|
|
71
71
|
"@astrojs/starlight": "0.37.6",
|
|
72
72
|
"@tailwindcss/vite": "^4.1.4",
|
|
73
|
-
"@treeseed/sdk": "0.
|
|
73
|
+
"@treeseed/sdk": "0.9.0",
|
|
74
74
|
"astro": "^5.6.1",
|
|
75
75
|
"esbuild": "^0.28.0",
|
|
76
76
|
"katex": "^0.16.22",
|