@treeseed/sdk 0.3.4 → 0.4.1
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/README.md +22 -3
- package/dist/fixture-support.d.ts +24 -0
- package/dist/fixture-support.js +337 -0
- package/dist/index.d.ts +1 -7
- package/dist/index.js +0 -6
- package/dist/operations/runtime.js +1 -1
- package/dist/operations/services/config-runtime.d.ts +5 -5
- package/dist/operations/services/config-runtime.js +1 -1
- package/dist/operations/services/deploy.js +1 -1
- package/dist/operations/services/runtime-paths.d.ts +1 -0
- package/dist/operations/services/runtime-paths.js +3 -1
- package/dist/operations/services/runtime-tools.js +1 -1
- package/dist/operations/services/template-registry.d.ts +3 -3
- package/dist/operations/services/template-registry.js +5 -4
- package/dist/platform/books-data.d.ts +29 -1
- package/dist/platform/books-data.js +82 -1
- package/dist/platform/deploy-config.d.ts +4 -1
- package/dist/platform/deploy-config.js +222 -1
- package/dist/platform/deploy-runtime.js +1 -1
- package/dist/platform/environment.d.ts +1 -1
- package/dist/platform/environment.js +3 -3
- package/dist/platform/plugin.d.ts +51 -2
- package/dist/platform/plugin.js +3 -1
- package/dist/platform/plugins/constants.d.ts +1 -1
- package/dist/platform/plugins/constants.js +1 -1
- package/dist/platform/plugins/runtime.d.ts +1 -1
- package/dist/platform/plugins/runtime.js +5 -5
- package/dist/platform/plugins.d.ts +2 -2
- package/dist/platform/plugins.js +1 -1
- package/dist/platform/tenant/runtime-config.js +1 -1
- package/dist/platform/tenant-config.d.ts +7 -1
- package/dist/platform/tenant-config.js +153 -1
- package/dist/plugin-default.d.ts +25 -0
- package/dist/plugin-default.js +37 -0
- package/dist/scripts/aggregate-book.js +1 -1
- package/dist/scripts/build-tenant-worker.js +2 -2
- package/dist/scripts/tenant-destroy.js +1 -1
- package/dist/scripts/tenant-dev.js +1 -1
- package/dist/treeseed/template-catalog/templates/starter-basic/template/package.json +1 -0
- package/dist/treeseed/template-catalog/templates/starter-basic/template/treeseed.site.yaml +1 -1
- package/dist/treeseed/template-catalog/templates/starter-basic/template.config.json +6 -0
- package/package.json +9 -25
- package/dist/platform/deploy/config.d.ts +0 -4
- package/dist/platform/deploy/config.js +0 -222
- package/dist/platform/plugins/plugin.d.ts +0 -51
- package/dist/platform/plugins/plugin.js +0 -6
- package/dist/platform/tenant/config.d.ts +0 -9
- package/dist/platform/tenant/config.js +0 -154
- package/dist/platform/utils/books-data.d.ts +0 -29
- package/dist/platform/utils/books-data.js +0 -82
- package/dist/utils/agents/contracts/messages.d.ts +0 -88
- package/dist/utils/agents/contracts/messages.js +0 -139
- package/dist/utils/agents/contracts/run.d.ts +0 -20
- package/dist/utils/agents/contracts/run.js +0 -0
- package/dist/utils/agents/runtime-types.d.ts +0 -117
- package/dist/utils/agents/runtime-types.js +0 -4
|
@@ -1 +1,82 @@
|
|
|
1
|
-
|
|
1
|
+
import { readFileSync, readdirSync, statSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { parse as parseYaml } from "yaml";
|
|
4
|
+
import { getTenantContentRoot } from "./tenant-config.js";
|
|
5
|
+
import { RUNTIME_PROJECT_ROOT, RUNTIME_TENANT } from "./tenant/runtime-config.js";
|
|
6
|
+
function sortPaths(paths) {
|
|
7
|
+
return [...paths].sort((left, right) => left.localeCompare(right, void 0, { numeric: true, sensitivity: "base" }));
|
|
8
|
+
}
|
|
9
|
+
function collectMarkdownFiles(rootPath) {
|
|
10
|
+
const stats = statSync(rootPath);
|
|
11
|
+
if (stats.isFile()) {
|
|
12
|
+
return [rootPath];
|
|
13
|
+
}
|
|
14
|
+
return sortPaths(
|
|
15
|
+
readdirSync(rootPath, { withFileTypes: true }).flatMap((entry) => {
|
|
16
|
+
const fullPath = path.join(rootPath, entry.name);
|
|
17
|
+
if (entry.isDirectory()) {
|
|
18
|
+
return collectMarkdownFiles(fullPath);
|
|
19
|
+
}
|
|
20
|
+
if (entry.isFile() && (entry.name.endsWith(".md") || entry.name.endsWith(".mdx"))) {
|
|
21
|
+
return [fullPath];
|
|
22
|
+
}
|
|
23
|
+
return [];
|
|
24
|
+
})
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
function parseFrontmatter(filePath) {
|
|
28
|
+
const raw = readFileSync(filePath, "utf8");
|
|
29
|
+
const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
30
|
+
if (!match) {
|
|
31
|
+
throw new Error(`Book content entry is missing frontmatter: ${filePath}`);
|
|
32
|
+
}
|
|
33
|
+
return parseYaml(match[1]);
|
|
34
|
+
}
|
|
35
|
+
function inferDocsLibraryDownload(book) {
|
|
36
|
+
const title = book?.title ? `${book.title} Library` : "Knowledge Library";
|
|
37
|
+
return {
|
|
38
|
+
downloadFileName: "treeseed-knowledge.md",
|
|
39
|
+
downloadHref: "/books/treeseed-knowledge.md",
|
|
40
|
+
downloadTitle: title
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function buildTenantBookRuntime(tenantConfig, options = {}) {
|
|
44
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
45
|
+
const booksContentRoot = path.resolve(projectRoot, getTenantContentRoot(tenantConfig, "books"));
|
|
46
|
+
const books = collectMarkdownFiles(booksContentRoot).map((filePath) => {
|
|
47
|
+
const frontmatter = parseFrontmatter(filePath);
|
|
48
|
+
return {
|
|
49
|
+
...frontmatter,
|
|
50
|
+
id: path.basename(filePath, path.extname(filePath))
|
|
51
|
+
};
|
|
52
|
+
}).sort((left, right) => left.order - right.order);
|
|
53
|
+
const docsHomePath = options.docsHomePath ?? "/knowledge/";
|
|
54
|
+
const docsLibraryDownload = options.docsLibraryDownload ?? inferDocsLibraryDownload(tenantConfig);
|
|
55
|
+
return {
|
|
56
|
+
BOOKS: books,
|
|
57
|
+
BOOKS_LINK: {
|
|
58
|
+
label: "Books",
|
|
59
|
+
link: docsHomePath
|
|
60
|
+
},
|
|
61
|
+
TREESEED_LINKS: {
|
|
62
|
+
home: docsHomePath
|
|
63
|
+
},
|
|
64
|
+
TREESEED_LIBRARY_DOWNLOAD: docsLibraryDownload
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const runtime = buildTenantBookRuntime(RUNTIME_TENANT, {
|
|
68
|
+
projectRoot: RUNTIME_PROJECT_ROOT,
|
|
69
|
+
docsLibraryDownload: {
|
|
70
|
+
downloadFileName: "treeseed-knowledge.md",
|
|
71
|
+
downloadHref: "/books/treeseed-knowledge.md",
|
|
72
|
+
downloadTitle: "TreeSeed Knowledge Library"
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
const { BOOKS, BOOKS_LINK, TREESEED_LINKS, TREESEED_LIBRARY_DOWNLOAD } = runtime;
|
|
76
|
+
export {
|
|
77
|
+
BOOKS,
|
|
78
|
+
BOOKS_LINK,
|
|
79
|
+
TREESEED_LIBRARY_DOWNLOAD,
|
|
80
|
+
TREESEED_LINKS,
|
|
81
|
+
buildTenantBookRuntime
|
|
82
|
+
};
|
|
@@ -1 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import type { TreeseedDeployConfig } from './contracts.ts';
|
|
2
|
+
export declare function resolveTreeseedDeployConfigPath(configPath?: string): string;
|
|
3
|
+
export declare function deriveCloudflareWorkerName(config: TreeseedDeployConfig): string;
|
|
4
|
+
export declare function loadTreeseedDeployConfig(configPath?: string): TreeseedDeployConfig;
|
|
@@ -1 +1,222 @@
|
|
|
1
|
-
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { parse as parseYaml } from "yaml";
|
|
4
|
+
import { normalizeAliasedRecord } from "../field-aliases.js";
|
|
5
|
+
import { resolveTreeseedTenantRoot } from "./tenant-config.js";
|
|
6
|
+
import {
|
|
7
|
+
TREESEED_DEFAULT_PLUGIN_REFERENCES,
|
|
8
|
+
TREESEED_DEFAULT_PROVIDER_SELECTIONS
|
|
9
|
+
} from "./plugins/constants.js";
|
|
10
|
+
const deployConfigFieldAliases = {
|
|
11
|
+
siteUrl: { key: "siteUrl", aliases: ["site_url"] },
|
|
12
|
+
contactEmail: { key: "contactEmail", aliases: ["contact_email"] }
|
|
13
|
+
};
|
|
14
|
+
const cloudflareFieldAliases = {
|
|
15
|
+
accountId: { key: "accountId", aliases: ["account_id"] },
|
|
16
|
+
workerName: { key: "workerName", aliases: ["worker_name"] }
|
|
17
|
+
};
|
|
18
|
+
const CLOUDFLARE_ACCOUNT_ID_PLACEHOLDER = "replace-with-cloudflare-account-id";
|
|
19
|
+
function expectString(value, label) {
|
|
20
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
21
|
+
throw new Error(`Invalid deploy config: expected ${label} to be a non-empty string.`);
|
|
22
|
+
}
|
|
23
|
+
return value.trim();
|
|
24
|
+
}
|
|
25
|
+
function optionalString(value) {
|
|
26
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
27
|
+
return void 0;
|
|
28
|
+
}
|
|
29
|
+
return value.trim();
|
|
30
|
+
}
|
|
31
|
+
function optionalCloudflareAccountId(value) {
|
|
32
|
+
const accountId = optionalString(value);
|
|
33
|
+
return accountId === CLOUDFLARE_ACCOUNT_ID_PLACEHOLDER ? void 0 : accountId;
|
|
34
|
+
}
|
|
35
|
+
function optionalBoolean(value, label) {
|
|
36
|
+
if (value === void 0) {
|
|
37
|
+
return void 0;
|
|
38
|
+
}
|
|
39
|
+
if (typeof value !== "boolean") {
|
|
40
|
+
throw new Error(`Invalid deploy config: expected ${label} to be a boolean when provided.`);
|
|
41
|
+
}
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
function optionalRecord(value, label) {
|
|
45
|
+
if (value === void 0 || value === null) {
|
|
46
|
+
return void 0;
|
|
47
|
+
}
|
|
48
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
49
|
+
throw new Error(`Invalid deploy config: expected ${label} to be an object when provided.`);
|
|
50
|
+
}
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
53
|
+
function parsePluginReferences(value) {
|
|
54
|
+
if (value === void 0) {
|
|
55
|
+
return [...TREESEED_DEFAULT_PLUGIN_REFERENCES];
|
|
56
|
+
}
|
|
57
|
+
if (!Array.isArray(value)) {
|
|
58
|
+
throw new Error("Invalid deploy config: expected plugins to be an array.");
|
|
59
|
+
}
|
|
60
|
+
return value.map((entry, index) => {
|
|
61
|
+
const record = optionalRecord(entry, `plugins[${index}]`);
|
|
62
|
+
return {
|
|
63
|
+
package: expectString(record?.package, `plugins[${index}].package`),
|
|
64
|
+
enabled: record?.enabled === void 0 ? true : optionalBoolean(record.enabled, `plugins[${index}].enabled`),
|
|
65
|
+
config: record?.config === void 0 ? {} : optionalRecord(record.config, `plugins[${index}].config`)
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
function parseProviderSelections(value) {
|
|
70
|
+
const record = optionalRecord(value, "providers");
|
|
71
|
+
if (!record) {
|
|
72
|
+
return structuredClone(TREESEED_DEFAULT_PROVIDER_SELECTIONS);
|
|
73
|
+
}
|
|
74
|
+
const agentProviders = optionalRecord(record.agents, "providers.agents") ?? {};
|
|
75
|
+
const contentProviders = optionalRecord(record.content, "providers.content") ?? {};
|
|
76
|
+
return {
|
|
77
|
+
forms: expectString(record.forms ?? TREESEED_DEFAULT_PROVIDER_SELECTIONS.forms, "providers.forms"),
|
|
78
|
+
operations: expectString(record.operations ?? TREESEED_DEFAULT_PROVIDER_SELECTIONS.operations, "providers.operations"),
|
|
79
|
+
agents: {
|
|
80
|
+
execution: expectString(
|
|
81
|
+
agentProviders.execution ?? TREESEED_DEFAULT_PROVIDER_SELECTIONS.agents.execution,
|
|
82
|
+
"providers.agents.execution"
|
|
83
|
+
),
|
|
84
|
+
mutation: expectString(
|
|
85
|
+
agentProviders.mutation ?? TREESEED_DEFAULT_PROVIDER_SELECTIONS.agents.mutation,
|
|
86
|
+
"providers.agents.mutation"
|
|
87
|
+
),
|
|
88
|
+
repository: expectString(
|
|
89
|
+
agentProviders.repository ?? TREESEED_DEFAULT_PROVIDER_SELECTIONS.agents.repository,
|
|
90
|
+
"providers.agents.repository"
|
|
91
|
+
),
|
|
92
|
+
verification: expectString(
|
|
93
|
+
agentProviders.verification ?? TREESEED_DEFAULT_PROVIDER_SELECTIONS.agents.verification,
|
|
94
|
+
"providers.agents.verification"
|
|
95
|
+
),
|
|
96
|
+
notification: expectString(
|
|
97
|
+
agentProviders.notification ?? TREESEED_DEFAULT_PROVIDER_SELECTIONS.agents.notification,
|
|
98
|
+
"providers.agents.notification"
|
|
99
|
+
),
|
|
100
|
+
research: expectString(
|
|
101
|
+
agentProviders.research ?? TREESEED_DEFAULT_PROVIDER_SELECTIONS.agents.research,
|
|
102
|
+
"providers.agents.research"
|
|
103
|
+
)
|
|
104
|
+
},
|
|
105
|
+
deploy: expectString(record.deploy ?? TREESEED_DEFAULT_PROVIDER_SELECTIONS.deploy, "providers.deploy"),
|
|
106
|
+
content: {
|
|
107
|
+
docs: expectString(
|
|
108
|
+
contentProviders.docs ?? TREESEED_DEFAULT_PROVIDER_SELECTIONS.content.docs,
|
|
109
|
+
"providers.content.docs"
|
|
110
|
+
)
|
|
111
|
+
},
|
|
112
|
+
site: expectString(record.site ?? TREESEED_DEFAULT_PROVIDER_SELECTIONS.site, "providers.site")
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function parseServiceEnvironmentConfig(value, label) {
|
|
116
|
+
const record = optionalRecord(value, label) ?? {};
|
|
117
|
+
return {
|
|
118
|
+
baseUrl: optionalString(record.baseUrl),
|
|
119
|
+
domain: optionalString(record.domain),
|
|
120
|
+
railwayEnvironment: optionalString(record.railwayEnvironment)
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function parseManagedServiceConfig(value, label) {
|
|
124
|
+
const record = optionalRecord(value, label);
|
|
125
|
+
if (!record) {
|
|
126
|
+
return void 0;
|
|
127
|
+
}
|
|
128
|
+
const railway = optionalRecord(record.railway, `${label}.railway`) ?? {};
|
|
129
|
+
const environments = optionalRecord(record.environments, `${label}.environments`) ?? {};
|
|
130
|
+
return {
|
|
131
|
+
enabled: record.enabled === void 0 ? void 0 : optionalBoolean(record.enabled, `${label}.enabled`),
|
|
132
|
+
provider: optionalString(record.provider),
|
|
133
|
+
rootDir: optionalString(record.rootDir),
|
|
134
|
+
publicBaseUrl: optionalString(record.publicBaseUrl),
|
|
135
|
+
railway: {
|
|
136
|
+
projectId: optionalString(railway.projectId),
|
|
137
|
+
projectName: optionalString(railway.projectName),
|
|
138
|
+
serviceId: optionalString(railway.serviceId),
|
|
139
|
+
serviceName: optionalString(railway.serviceName),
|
|
140
|
+
rootDir: optionalString(railway.rootDir),
|
|
141
|
+
buildCommand: optionalString(railway.buildCommand),
|
|
142
|
+
startCommand: optionalString(railway.startCommand)
|
|
143
|
+
},
|
|
144
|
+
environments: {
|
|
145
|
+
local: parseServiceEnvironmentConfig(environments.local, `${label}.environments.local`),
|
|
146
|
+
staging: parseServiceEnvironmentConfig(environments.staging, `${label}.environments.staging`),
|
|
147
|
+
prod: parseServiceEnvironmentConfig(environments.prod, `${label}.environments.prod`)
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
function parseManagedServicesConfig(value) {
|
|
152
|
+
const record = optionalRecord(value, "services");
|
|
153
|
+
if (!record) {
|
|
154
|
+
return void 0;
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
api: parseManagedServiceConfig(record.api, "services.api"),
|
|
158
|
+
agents: parseManagedServiceConfig(record.agents, "services.agents")
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function parseDeployConfig(raw) {
|
|
162
|
+
const parsed = normalizeAliasedRecord(
|
|
163
|
+
deployConfigFieldAliases,
|
|
164
|
+
parseYaml(raw) ?? {}
|
|
165
|
+
);
|
|
166
|
+
const cloudflare = normalizeAliasedRecord(
|
|
167
|
+
cloudflareFieldAliases,
|
|
168
|
+
optionalRecord(parsed.cloudflare, "cloudflare") ?? {}
|
|
169
|
+
);
|
|
170
|
+
const smtp = optionalRecord(parsed.smtp, "smtp") ?? {};
|
|
171
|
+
const turnstile = optionalRecord(parsed.turnstile, "turnstile") ?? {};
|
|
172
|
+
optionalBoolean(turnstile.enabled, "turnstile.enabled");
|
|
173
|
+
return {
|
|
174
|
+
name: expectString(parsed.name, "name"),
|
|
175
|
+
slug: expectString(parsed.slug, "slug"),
|
|
176
|
+
siteUrl: expectString(parsed.siteUrl, "siteUrl"),
|
|
177
|
+
contactEmail: expectString(parsed.contactEmail, "contactEmail"),
|
|
178
|
+
cloudflare: {
|
|
179
|
+
accountId: optionalCloudflareAccountId(cloudflare.accountId) ?? optionalCloudflareAccountId(process.env.CLOUDFLARE_ACCOUNT_ID) ?? CLOUDFLARE_ACCOUNT_ID_PLACEHOLDER,
|
|
180
|
+
workerName: optionalString(cloudflare.workerName)
|
|
181
|
+
},
|
|
182
|
+
plugins: parsePluginReferences(parsed.plugins),
|
|
183
|
+
providers: parseProviderSelections(parsed.providers),
|
|
184
|
+
services: parseManagedServicesConfig(parsed.services),
|
|
185
|
+
smtp: {
|
|
186
|
+
enabled: optionalBoolean(smtp.enabled, "smtp.enabled")
|
|
187
|
+
},
|
|
188
|
+
turnstile: {
|
|
189
|
+
enabled: true
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
function resolveTreeseedDeployConfigPath(configPath = "treeseed.site.yaml") {
|
|
194
|
+
const tenantRoot = resolveTreeseedTenantRoot();
|
|
195
|
+
const candidate = resolve(tenantRoot, configPath);
|
|
196
|
+
if (!existsSync(candidate)) {
|
|
197
|
+
throw new Error(`Unable to resolve Treeseed deploy config at "${candidate}".`);
|
|
198
|
+
}
|
|
199
|
+
return candidate;
|
|
200
|
+
}
|
|
201
|
+
function deriveCloudflareWorkerName(config) {
|
|
202
|
+
return config.cloudflare.workerName?.trim() || config.slug;
|
|
203
|
+
}
|
|
204
|
+
function loadTreeseedDeployConfig(configPath = "treeseed.site.yaml") {
|
|
205
|
+
const resolvedConfigPath = resolveTreeseedDeployConfigPath(configPath);
|
|
206
|
+
const tenantRoot = dirname(resolvedConfigPath);
|
|
207
|
+
const parsed = parseDeployConfig(readFileSync(resolvedConfigPath, "utf8"));
|
|
208
|
+
Object.defineProperty(parsed, "__tenantRoot", {
|
|
209
|
+
value: tenantRoot,
|
|
210
|
+
enumerable: false
|
|
211
|
+
});
|
|
212
|
+
Object.defineProperty(parsed, "__configPath", {
|
|
213
|
+
value: resolvedConfigPath,
|
|
214
|
+
enumerable: false
|
|
215
|
+
});
|
|
216
|
+
return parsed;
|
|
217
|
+
}
|
|
218
|
+
export {
|
|
219
|
+
deriveCloudflareWorkerName,
|
|
220
|
+
loadTreeseedDeployConfig,
|
|
221
|
+
resolveTreeseedDeployConfigPath
|
|
222
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { loadTreeseedDeployConfig } from "./deploy
|
|
1
|
+
import { loadTreeseedDeployConfig } from "./deploy-config.js";
|
|
2
2
|
import { TREESEED_DEFAULT_PLUGIN_REFERENCES, TREESEED_DEFAULT_PROVIDER_SELECTIONS } from "./plugins/constants.js";
|
|
3
3
|
let cachedDeployConfig = null;
|
|
4
4
|
function defaultDeployConfig() {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { TreeseedDeployConfig, TreeseedTenantConfig } from './contracts.ts';
|
|
2
|
-
import { type LoadedTreeseedPluginEntry } from './plugins
|
|
2
|
+
import { type LoadedTreeseedPluginEntry } from './plugins.ts';
|
|
3
3
|
export declare const TREESEED_ENVIRONMENT_SCOPES: readonly ["local", "staging", "prod"];
|
|
4
4
|
export declare const TREESEED_ENVIRONMENT_REQUIREMENTS: readonly ["required", "conditional", "optional"];
|
|
5
5
|
export declare const TREESEED_ENVIRONMENT_TARGETS: readonly ["local-file", "wrangler-dev-vars", "github-secret", "github-variable", "cloudflare-secret", "cloudflare-var", "railway-secret", "config-file"];
|
|
@@ -3,9 +3,9 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
3
3
|
import { dirname, resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { parse as parseYaml } from "yaml";
|
|
6
|
-
import { loadTreeseedDeployConfig } from "./deploy
|
|
7
|
-
import { loadTreeseedPlugins } from "./plugins
|
|
8
|
-
import { loadTreeseedManifest } from "./tenant
|
|
6
|
+
import { loadTreeseedDeployConfig } from "./deploy-config.js";
|
|
7
|
+
import { loadTreeseedPlugins } from "./plugins.js";
|
|
8
|
+
import { loadTreeseedManifest } from "./tenant-config.js";
|
|
9
9
|
const TREESEED_ENVIRONMENT_SCOPES = ["local", "staging", "prod"];
|
|
10
10
|
const TREESEED_ENVIRONMENT_REQUIREMENTS = ["required", "conditional", "optional"];
|
|
11
11
|
const TREESEED_ENVIRONMENT_TARGETS = [
|
|
@@ -1,2 +1,51 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import type { TreeseedDeployConfig, TreeseedTenantConfig } from './contracts.ts';
|
|
2
|
+
import type { TreeseedEnvironmentRegistryOverlay } from './environment.ts';
|
|
3
|
+
import type { SdkGraphRankingProvider } from '../sdk-types.ts';
|
|
4
|
+
export type TreeseedSiteLayerDefinition = {
|
|
5
|
+
root: string;
|
|
6
|
+
kinds?: Array<'pages' | 'styles' | 'components'>;
|
|
7
|
+
};
|
|
8
|
+
export type TreeseedSiteRouteContribution = {
|
|
9
|
+
pattern: string;
|
|
10
|
+
entrypoint?: string;
|
|
11
|
+
resourcePath?: string;
|
|
12
|
+
};
|
|
13
|
+
export type TreeseedSiteExtensionContribution = {
|
|
14
|
+
routes?: TreeseedSiteRouteContribution[];
|
|
15
|
+
starlightComponents?: Record<string, string>;
|
|
16
|
+
customCss?: string[];
|
|
17
|
+
remarkPlugins?: unknown[];
|
|
18
|
+
rehypePlugins?: unknown[];
|
|
19
|
+
envSchema?: Record<string, unknown>;
|
|
20
|
+
vitePlugins?: unknown[];
|
|
21
|
+
integrations?: unknown[];
|
|
22
|
+
routeMiddleware?: unknown[];
|
|
23
|
+
};
|
|
24
|
+
export type TreeseedPluginSiteContext = {
|
|
25
|
+
projectRoot: string;
|
|
26
|
+
tenantConfig: TreeseedTenantConfig;
|
|
27
|
+
siteConfig?: unknown;
|
|
28
|
+
deployConfig?: TreeseedDeployConfig;
|
|
29
|
+
pluginConfig: Record<string, unknown>;
|
|
30
|
+
};
|
|
31
|
+
export type TreeseedPluginEnvironmentContext = {
|
|
32
|
+
projectRoot: string;
|
|
33
|
+
tenantConfig?: TreeseedTenantConfig;
|
|
34
|
+
deployConfig?: TreeseedDeployConfig;
|
|
35
|
+
pluginConfig: Record<string, unknown>;
|
|
36
|
+
};
|
|
37
|
+
export type TreeseedGraphRankingProviderContribution = SdkGraphRankingProvider | ((context: TreeseedPluginEnvironmentContext) => SdkGraphRankingProvider | undefined);
|
|
38
|
+
export interface TreeseedPlugin {
|
|
39
|
+
id?: string;
|
|
40
|
+
provides?: Record<string, any> & {
|
|
41
|
+
operations?: string[];
|
|
42
|
+
};
|
|
43
|
+
operationProviders?: Record<string, unknown>;
|
|
44
|
+
siteProviders?: Record<string, TreeseedSiteExtensionContribution | ((context: TreeseedPluginSiteContext) => TreeseedSiteExtensionContribution)>;
|
|
45
|
+
siteHooks?: TreeseedSiteExtensionContribution | ((context: TreeseedPluginSiteContext) => TreeseedSiteExtensionContribution);
|
|
46
|
+
siteLayers?: TreeseedSiteLayerDefinition[] | ((context: TreeseedPluginSiteContext) => TreeseedSiteLayerDefinition[] | undefined);
|
|
47
|
+
environmentRegistry?: TreeseedEnvironmentRegistryOverlay | ((context: TreeseedPluginEnvironmentContext) => TreeseedEnvironmentRegistryOverlay | undefined);
|
|
48
|
+
graphRankingProviders?: Record<string, TreeseedGraphRankingProviderContribution>;
|
|
49
|
+
[key: string]: unknown;
|
|
50
|
+
}
|
|
51
|
+
export declare function defineTreeseedPlugin<T extends TreeseedPlugin>(plugin: T): T;
|
package/dist/platform/plugin.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const TREESEED_DEFAULT_PLUGIN_PACKAGE = "@treeseed/
|
|
1
|
+
export declare const TREESEED_DEFAULT_PLUGIN_PACKAGE = "@treeseed/sdk/plugin-default";
|
|
2
2
|
export declare const TREESEED_DEFAULT_PROVIDER_SELECTIONS: {
|
|
3
3
|
forms: string;
|
|
4
4
|
operations: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { TreeseedDeployConfig } from '../contracts.ts';
|
|
2
|
-
import type { TreeseedPluginEnvironmentContext } from '
|
|
2
|
+
import type { TreeseedPluginEnvironmentContext } from '../plugin.ts';
|
|
3
3
|
import type { SdkGraphRankingProvider } from '../../sdk-types.ts';
|
|
4
4
|
type LoadedPluginEntry = {
|
|
5
5
|
package: string;
|
|
@@ -2,7 +2,7 @@ import { existsSync } from "node:fs";
|
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { loadTreeseedDeployConfig } from "../deploy
|
|
5
|
+
import { loadTreeseedDeployConfig } from "../deploy-config.js";
|
|
6
6
|
import { TREESEED_DEFAULT_PLUGIN_PACKAGE } from "./constants.js";
|
|
7
7
|
const require2 = createRequire(import.meta.url);
|
|
8
8
|
const runtimeDir = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -18,18 +18,18 @@ function isPathLikePluginReference(packageName) {
|
|
|
18
18
|
}
|
|
19
19
|
function resolveLocalDefaultPluginPath() {
|
|
20
20
|
const candidates = [
|
|
21
|
-
path.resolve(runtimeDir, "
|
|
22
|
-
path.resolve(runtimeDir, "../../../../dist/plugin-default.js")
|
|
21
|
+
path.resolve(runtimeDir, "../../../dist/plugin-default.js"),
|
|
22
|
+
path.resolve(runtimeDir, "../../../../sdk/dist/plugin-default.js")
|
|
23
23
|
];
|
|
24
24
|
let current = runtimeDir;
|
|
25
25
|
while (true) {
|
|
26
|
+
candidates.push(path.resolve(current, "..", "sdk", "dist", "plugin-default.js"));
|
|
26
27
|
const packageJsonPath = path.resolve(current, "package.json");
|
|
27
28
|
if (existsSync(packageJsonPath)) {
|
|
28
29
|
try {
|
|
29
30
|
const packageJson = require2(packageJsonPath);
|
|
30
|
-
if (packageJson?.name === "@treeseed/
|
|
31
|
+
if (packageJson?.name === "@treeseed/sdk") {
|
|
31
32
|
candidates.push(path.resolve(current, "dist/plugin-default.js"));
|
|
32
|
-
break;
|
|
33
33
|
}
|
|
34
34
|
} catch {
|
|
35
35
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { TREESEED_DEFAULT_PLUGIN_PACKAGE, TREESEED_DEFAULT_PLUGIN_REFERENCES, TREESEED_DEFAULT_PROVIDER_SELECTIONS, } from './plugins/constants.ts';
|
|
2
2
|
export { getTreeseedAgentProviderSelections, getTreeseedDeployConfig, getTreeseedDeployProvider, getTreeseedDocsProvider, getTreeseedFormsProvider, getTreeseedOperationsProvider, getTreeseedSiteProvider, isTreeseedSmtpEnabled, isTreeseedTurnstileEnabled, resetTreeseedDeployConfigForTests, } from './deploy-runtime.ts';
|
|
3
|
-
export { defineTreeseedPlugin } from './
|
|
4
|
-
export type * from './
|
|
3
|
+
export { defineTreeseedPlugin } from './plugin.ts';
|
|
4
|
+
export type * from './plugin.ts';
|
|
5
5
|
export { loadTreeseedPluginRuntime, loadTreeseedPlugins, resolveTreeseedGraphRankingProvider } from './plugins/runtime.ts';
|
|
6
6
|
export type { LoadedTreeseedPluginEntry } from './plugins/runtime.ts';
|
package/dist/platform/plugins.js
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
isTreeseedTurnstileEnabled,
|
|
16
16
|
resetTreeseedDeployConfigForTests
|
|
17
17
|
} from "./deploy-runtime.js";
|
|
18
|
-
import { defineTreeseedPlugin } from "./
|
|
18
|
+
import { defineTreeseedPlugin } from "./plugin.js";
|
|
19
19
|
import { loadTreeseedPluginRuntime, loadTreeseedPlugins, resolveTreeseedGraphRankingProvider } from "./plugins/runtime.js";
|
|
20
20
|
export {
|
|
21
21
|
TREESEED_DEFAULT_PLUGIN_PACKAGE,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
|
-
import { loadTreeseedManifest } from "
|
|
2
|
+
import { loadTreeseedManifest } from "../tenant-config.js";
|
|
3
3
|
import { parseSiteConfig } from "../utils/site-config-schema.js";
|
|
4
4
|
const injectedTenantConfig = typeof __TREESEED_TENANT_CONFIG__ !== "undefined" ? __TREESEED_TENANT_CONFIG__ : null;
|
|
5
5
|
const injectedProjectRoot = typeof __TREESEED_PROJECT_ROOT__ !== "undefined" ? __TREESEED_PROJECT_ROOT__ : null;
|
|
@@ -1 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
import type { TreeseedTenantConfig } from './contracts.ts';
|
|
2
|
+
export declare function resolveTreeseedTenantRoot(): string;
|
|
3
|
+
export declare function defineTreeseedTenant<T>(tenantConfig: T): T;
|
|
4
|
+
export declare function loadTreeseedManifest(manifestPath?: string): TreeseedTenantConfig;
|
|
5
|
+
export declare const loadTreeseedTenantManifest: typeof loadTreeseedManifest;
|
|
6
|
+
export declare function getTenantContentRoot(tenantConfig: Pick<TreeseedTenantConfig, 'content'>, collectionName: string): string;
|
|
7
|
+
export declare function tenantFeatureEnabled(tenantConfig: Pick<TreeseedTenantConfig, 'features'>, featureName: string): boolean;
|
|
@@ -1 +1,153 @@
|
|
|
1
|
-
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { parse as parseYaml } from "yaml";
|
|
5
|
+
import { normalizeAliasedRecord } from "../field-aliases.js";
|
|
6
|
+
function resolvePackageRoot() {
|
|
7
|
+
const moduleUrl = typeof import.meta?.url === "string" ? import.meta.url : null;
|
|
8
|
+
if (!moduleUrl) {
|
|
9
|
+
return process.cwd();
|
|
10
|
+
}
|
|
11
|
+
return resolve(dirname(fileURLToPath(moduleUrl)), "../..");
|
|
12
|
+
}
|
|
13
|
+
const packageRoot = resolvePackageRoot();
|
|
14
|
+
const packageFixtureRoot = resolve(packageRoot, ".fixtures", "treeseed-fixtures", "sites", "working-site");
|
|
15
|
+
const explicitTenantRoot = process.env.TREESEED_TENANT_ROOT ? resolve(process.env.TREESEED_TENANT_ROOT) : null;
|
|
16
|
+
const manifestFieldAliases = {
|
|
17
|
+
siteConfigPath: { key: "siteConfigPath", aliases: ["site_config_path"] }
|
|
18
|
+
};
|
|
19
|
+
const manifestContentFieldAliases = {
|
|
20
|
+
pages: { key: "pages", aliases: ["page_root", "pages_root"] },
|
|
21
|
+
notes: { key: "notes", aliases: ["notes_root"] },
|
|
22
|
+
questions: { key: "questions", aliases: ["questions_root"] },
|
|
23
|
+
objectives: { key: "objectives", aliases: ["objectives_root"] },
|
|
24
|
+
people: { key: "people", aliases: ["people_root"] },
|
|
25
|
+
agents: { key: "agents", aliases: ["agents_root"] },
|
|
26
|
+
books: { key: "books", aliases: ["books_root"] },
|
|
27
|
+
docs: { key: "docs", aliases: ["knowledge", "knowledge_root", "docs_root"] }
|
|
28
|
+
};
|
|
29
|
+
const manifestOverrideFieldAliases = {
|
|
30
|
+
pagesRoot: { key: "pagesRoot", aliases: ["pages_root"] },
|
|
31
|
+
stylesRoot: { key: "stylesRoot", aliases: ["styles_root"] },
|
|
32
|
+
componentsRoot: { key: "componentsRoot", aliases: ["components_root"] }
|
|
33
|
+
};
|
|
34
|
+
function pathWithin(parent, candidate) {
|
|
35
|
+
const normalizedParent = resolve(parent);
|
|
36
|
+
const normalizedCandidate = resolve(candidate);
|
|
37
|
+
return normalizedCandidate === normalizedParent || normalizedCandidate.startsWith(`${normalizedParent}/`);
|
|
38
|
+
}
|
|
39
|
+
function collectTenantRootCandidates(start) {
|
|
40
|
+
const candidates = [];
|
|
41
|
+
let current = resolve(start);
|
|
42
|
+
while (true) {
|
|
43
|
+
candidates.push(
|
|
44
|
+
current,
|
|
45
|
+
resolve(current, ".fixtures", "treeseed-fixtures", "sites", "working-site"),
|
|
46
|
+
resolve(current, "fixture")
|
|
47
|
+
);
|
|
48
|
+
const parent = resolve(current, "..");
|
|
49
|
+
if (parent === current) {
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
current = parent;
|
|
53
|
+
}
|
|
54
|
+
return candidates;
|
|
55
|
+
}
|
|
56
|
+
function uniqueCandidates(entries) {
|
|
57
|
+
return [...new Set(entries.map((entry) => resolve(entry)))];
|
|
58
|
+
}
|
|
59
|
+
function tenantRootCandidates() {
|
|
60
|
+
const cwd = resolve(process.cwd());
|
|
61
|
+
const cwdCandidates = collectTenantRootCandidates(cwd);
|
|
62
|
+
const packageCandidates = collectTenantRootCandidates(packageRoot);
|
|
63
|
+
if (explicitTenantRoot) {
|
|
64
|
+
return uniqueCandidates([explicitTenantRoot, ...cwdCandidates, packageFixtureRoot, ...packageCandidates]);
|
|
65
|
+
}
|
|
66
|
+
if (pathWithin(packageRoot, cwd)) {
|
|
67
|
+
return uniqueCandidates([packageFixtureRoot, ...cwdCandidates, ...packageCandidates]);
|
|
68
|
+
}
|
|
69
|
+
return uniqueCandidates([...cwdCandidates, packageFixtureRoot, ...packageCandidates]);
|
|
70
|
+
}
|
|
71
|
+
function resolveTenantPath(manifestPath) {
|
|
72
|
+
if (existsSync(manifestPath)) {
|
|
73
|
+
return resolve(manifestPath);
|
|
74
|
+
}
|
|
75
|
+
const candidates = tenantRootCandidates().map((root) => resolve(root, manifestPath));
|
|
76
|
+
for (const candidate of candidates) {
|
|
77
|
+
if (existsSync(candidate)) {
|
|
78
|
+
return candidate;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
throw new Error(
|
|
82
|
+
`Unable to resolve Treeseed tenant manifest at "${manifestPath}" from ${process.cwd()} or ${packageFixtureRoot}.`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
function resolveTreeseedTenantRoot() {
|
|
86
|
+
const candidates = tenantRootCandidates();
|
|
87
|
+
for (const candidate of candidates) {
|
|
88
|
+
if (existsSync(resolve(candidate, "src/manifest.yaml"))) {
|
|
89
|
+
return candidate;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
throw new Error(
|
|
93
|
+
`Unable to resolve a Treeseed tenant root from ${process.cwd()} or ${packageFixtureRoot}.`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
function defineTreeseedTenant(tenantConfig) {
|
|
97
|
+
return tenantConfig;
|
|
98
|
+
}
|
|
99
|
+
function loadTreeseedManifest(manifestPath = "./src/manifest.yaml") {
|
|
100
|
+
const resolvedManifestPath = resolveTenantPath(manifestPath);
|
|
101
|
+
const tenantRoot = resolve(dirname(resolvedManifestPath), "..");
|
|
102
|
+
const parsed = normalizeAliasedRecord(
|
|
103
|
+
manifestFieldAliases,
|
|
104
|
+
parseYaml(readFileSync(resolvedManifestPath, "utf8"))
|
|
105
|
+
);
|
|
106
|
+
const content = normalizeAliasedRecord(
|
|
107
|
+
manifestContentFieldAliases,
|
|
108
|
+
parsed.content ?? {}
|
|
109
|
+
);
|
|
110
|
+
const overrides = parsed.overrides ? normalizeAliasedRecord(
|
|
111
|
+
manifestOverrideFieldAliases,
|
|
112
|
+
parsed.overrides
|
|
113
|
+
) : void 0;
|
|
114
|
+
const tenantConfig = defineTreeseedTenant({
|
|
115
|
+
...parsed,
|
|
116
|
+
siteConfigPath: resolve(tenantRoot, parsed.siteConfigPath),
|
|
117
|
+
content: Object.fromEntries(
|
|
118
|
+
Object.entries(content ?? {}).map(([collectionName, rootPath]) => [
|
|
119
|
+
collectionName,
|
|
120
|
+
resolve(tenantRoot, String(rootPath))
|
|
121
|
+
])
|
|
122
|
+
),
|
|
123
|
+
overrides: overrides ? {
|
|
124
|
+
pagesRoot: overrides.pagesRoot ? resolve(tenantRoot, overrides.pagesRoot) : void 0,
|
|
125
|
+
stylesRoot: overrides.stylesRoot ? resolve(tenantRoot, overrides.stylesRoot) : void 0,
|
|
126
|
+
componentsRoot: overrides.componentsRoot ? resolve(tenantRoot, overrides.componentsRoot) : void 0
|
|
127
|
+
} : void 0
|
|
128
|
+
});
|
|
129
|
+
Object.defineProperty(tenantConfig, "__tenantRoot", {
|
|
130
|
+
value: tenantRoot,
|
|
131
|
+
enumerable: false
|
|
132
|
+
});
|
|
133
|
+
return tenantConfig;
|
|
134
|
+
}
|
|
135
|
+
const loadTreeseedTenantManifest = loadTreeseedManifest;
|
|
136
|
+
function getTenantContentRoot(tenantConfig, collectionName) {
|
|
137
|
+
const root = tenantConfig.content[collectionName];
|
|
138
|
+
if (!root) {
|
|
139
|
+
throw new Error(`Unknown tenant content collection: ${collectionName}`);
|
|
140
|
+
}
|
|
141
|
+
return root;
|
|
142
|
+
}
|
|
143
|
+
function tenantFeatureEnabled(tenantConfig, featureName) {
|
|
144
|
+
return tenantConfig.features?.[featureName] !== false;
|
|
145
|
+
}
|
|
146
|
+
export {
|
|
147
|
+
defineTreeseedTenant,
|
|
148
|
+
getTenantContentRoot,
|
|
149
|
+
loadTreeseedManifest,
|
|
150
|
+
loadTreeseedTenantManifest,
|
|
151
|
+
resolveTreeseedTenantRoot,
|
|
152
|
+
tenantFeatureEnabled
|
|
153
|
+
};
|