@keystrokehq/cli 0.0.165 → 0.0.166
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 +11 -0
- package/dist/dist-BMPry9tj.mjs +577 -0
- package/dist/dist-BMPry9tj.mjs.map +1 -0
- package/dist/dist-CegYAHE0.mjs +3 -0
- package/dist/{schemas-Bq8SXmZk.mjs → dist-D_0UmqFb.mjs} +2113 -6
- package/dist/dist-D_0UmqFb.mjs.map +1 -0
- package/dist/{dist-CgV0MxBq.mjs → dist-WoxxHzM6.mjs} +123 -25
- package/dist/dist-WoxxHzM6.mjs.map +1 -0
- package/dist/index.mjs +147 -37
- package/dist/index.mjs.map +1 -1
- package/dist/{maybe-auto-update-CIcDJRrg.mjs → maybe-auto-update-B03C4E8i.mjs} +2 -2
- package/dist/{maybe-auto-update-CIcDJRrg.mjs.map → maybe-auto-update-B03C4E8i.mjs.map} +1 -1
- package/dist/skills-bundle/_AGENTS.md +7 -3
- package/dist/skills-bundle/skills/keystroke-actions/SKILL.md +1 -1
- package/dist/skills-bundle/skills/keystroke-actions/references/catalog-and-imports.md +2 -3
- package/dist/skills-bundle/skills/keystroke-agents/references/tools-mcp-codemode.md +1 -1
- package/dist/skills-bundle/skills/keystroke-cli/SKILL.md +4 -2
- package/dist/skills-bundle/skills/keystroke-deploy/SKILL.md +92 -0
- package/dist/skills-bundle/skills/keystroke-deploy/references/build-and-full-deploy.md +28 -0
- package/dist/skills-bundle/skills/keystroke-deploy/references/filtered-deploy.md +49 -0
- package/dist/skills-bundle/skills/keystroke-deploy/references/wip-ignore.md +32 -0
- package/dist/skills-bundle/skills/keystroke-gateways/SKILL.md +5 -6
- package/dist/skills-bundle/skills/keystroke-gateways/references/slack-setup.md +0 -11
- package/dist/templates/hello-world/README.md +1 -1
- package/dist/templates/hello-world/keystroke.config.ts +1 -5
- package/dist/{version-DdbxgFWa.mjs → version-FrEOAEU7.mjs} +2 -2
- package/dist/{version-DdbxgFWa.mjs.map → version-FrEOAEU7.mjs.map} +1 -1
- package/package.json +1 -1
- package/dist/discovery-DV7FkTRA-JYiC_9cY.mjs +0 -71
- package/dist/discovery-DV7FkTRA-JYiC_9cY.mjs.map +0 -1
- package/dist/dist-CgV0MxBq.mjs.map +0 -1
- package/dist/dist-DtqmE5R_.mjs +0 -2113
- package/dist/dist-DtqmE5R_.mjs.map +0 -1
- package/dist/dist-sXdjEZpP.mjs +0 -293
- package/dist/dist-sXdjEZpP.mjs.map +0 -1
- package/dist/schemas-Bq8SXmZk.mjs.map +0 -1
- package/dist/walk-project-171B4cvO-DKtupJ6Z.mjs +0 -102
- package/dist/walk-project-171B4cvO-DKtupJ6Z.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -106,6 +106,17 @@ keystroke build
|
|
|
106
106
|
keystroke build --dir ./my-app
|
|
107
107
|
```
|
|
108
108
|
|
|
109
|
+
### `deploy`
|
|
110
|
+
|
|
111
|
+
Build, upload, and promote a project artifact on the platform. Requires `keystroke auth login` and `--project <slug>`.
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
keystroke deploy
|
|
115
|
+
keystroke deploy --filter workflows/ping --filter agents/world
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`--filter` matches exact build entry keys (`agents/foo`, `workflows/bar`, …). Repeat the flag for multiple modules. Targeted deploy downloads the active artifact, overlays rebuilt module files, merges `route-manifest.json`, and uploads a full tarball — the first deploy for a project must be a full deploy without `--filter`. Config and bundled assets are carried forward from the active artifact; skill/asset drift is not validated in v1.
|
|
119
|
+
|
|
109
120
|
### `dev`
|
|
110
121
|
|
|
111
122
|
Watch `src/`, rebuild `dist/` on change, and restart the API. **Not true hot reload** — each change reruns tsdown and boots a fresh server process so discovery picks up new agents/workflows/triggers.
|
|
@@ -0,0 +1,577 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { cn as object, in as array, ln as string } from "./dist-D_0UmqFb.mjs";
|
|
3
|
+
import { L as entryIdFromFile, R as readKeystrokeIgnoreDirective, a as buildStoredRouteManifestForProject, z as shouldSkipKeystrokeModuleFile } from "./dist-WoxxHzM6.mjs";
|
|
4
|
+
import { isBuiltin } from "node:module";
|
|
5
|
+
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
6
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import "node:fs/promises";
|
|
9
|
+
import { createHash } from "node:crypto";
|
|
10
|
+
import { build, mergeConfig } from "tsdown";
|
|
11
|
+
//#region ../../packages/build/dist/walk-project-eZ95LOUW.mjs
|
|
12
|
+
function toPosix$1(path) {
|
|
13
|
+
return path.split(sep).join("/");
|
|
14
|
+
}
|
|
15
|
+
function projectRelativePath(root, filePath) {
|
|
16
|
+
return toPosix$1(relative(root, normalizeBuildFilePath(filePath)));
|
|
17
|
+
}
|
|
18
|
+
/** Normalize paths so /var and /private/var compare equal on macOS. */
|
|
19
|
+
function normalizeBuildFilePath(filePath) {
|
|
20
|
+
const absolute = resolve(filePath);
|
|
21
|
+
try {
|
|
22
|
+
return realpathSync.native(absolute);
|
|
23
|
+
} catch {
|
|
24
|
+
return absolute;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function normalizeModuleId(id, root) {
|
|
28
|
+
if (id.startsWith("\0")) return null;
|
|
29
|
+
const normalized = normalizeBuildFilePath(id.startsWith("file://") ? fileURLToPath(id) : id);
|
|
30
|
+
if (!normalized.startsWith(normalizeBuildFilePath(root))) return null;
|
|
31
|
+
return normalized;
|
|
32
|
+
}
|
|
33
|
+
function formatImportGuardError(root, ignoredPath, importer) {
|
|
34
|
+
const ignored = projectRelativePath(root, ignoredPath);
|
|
35
|
+
if (importer) return `Cannot import "${ignored}" (@keystroke ignore) from "${projectRelativePath(root, importer)}". Remove the directive or stop importing it from a shipped module.`;
|
|
36
|
+
return `Cannot import "${ignored}" (@keystroke ignore). Remove the directive or stop importing it from a shipped module.`;
|
|
37
|
+
}
|
|
38
|
+
/** Throw when a built module imports a `@keystroke ignore` source file. */
|
|
39
|
+
function keystrokeIgnoreGuardPlugin(options) {
|
|
40
|
+
const root = normalizeBuildFilePath(options.root);
|
|
41
|
+
const ignoredFiles = new Set(options.ignoredFiles.map((filePath) => normalizeBuildFilePath(filePath)));
|
|
42
|
+
function assertNotIgnored(filePath, importer) {
|
|
43
|
+
const normalized = normalizeBuildFilePath(filePath);
|
|
44
|
+
if (!ignoredFiles.has(normalized)) return;
|
|
45
|
+
throw new Error(formatImportGuardError(root, normalized, importer));
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
name: "keystroke-ignore-guard",
|
|
49
|
+
async resolveId(source, importer, resolveOptions) {
|
|
50
|
+
if (resolveOptions?.isEntry || !importer) return null;
|
|
51
|
+
const resolved = await this.resolve(source, importer, {
|
|
52
|
+
...resolveOptions,
|
|
53
|
+
skipSelf: true
|
|
54
|
+
});
|
|
55
|
+
if (!resolved) return null;
|
|
56
|
+
assertNotIgnored(typeof resolved === "string" ? resolved : resolved.id, importer);
|
|
57
|
+
return null;
|
|
58
|
+
},
|
|
59
|
+
load(id) {
|
|
60
|
+
const normalized = normalizeModuleId(id, root);
|
|
61
|
+
if (!normalized) return null;
|
|
62
|
+
assertNotIgnored(normalized);
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const IGNORED_DIRS = new Set([
|
|
68
|
+
"node_modules",
|
|
69
|
+
"dist",
|
|
70
|
+
".git",
|
|
71
|
+
".turbo",
|
|
72
|
+
"build",
|
|
73
|
+
"coverage",
|
|
74
|
+
".keystroke",
|
|
75
|
+
".cache",
|
|
76
|
+
"tmp"
|
|
77
|
+
]);
|
|
78
|
+
const IGNORED_ENV_FILE = /\.env/;
|
|
79
|
+
const IGNORED_LOG_FILE = /\.log$/;
|
|
80
|
+
function isIgnoredFile(name) {
|
|
81
|
+
return IGNORED_ENV_FILE.test(name) || IGNORED_LOG_FILE.test(name);
|
|
82
|
+
}
|
|
83
|
+
function toPosix$2(path) {
|
|
84
|
+
return path.split(sep).join("/");
|
|
85
|
+
}
|
|
86
|
+
function isProbablyBinary(buffer) {
|
|
87
|
+
const sampleLength = Math.min(buffer.length, 8e3);
|
|
88
|
+
for (let index = 0; index < sampleLength; index += 1) if (buffer[index] === 0) return true;
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
function classifyBuildEntry(root, srcRoot, filePath) {
|
|
92
|
+
const relativePath = toPosix$2(relative(root, filePath));
|
|
93
|
+
const agentsPrefix = `${toPosix$2(relative(root, join(srcRoot, "agents")))}/`;
|
|
94
|
+
const workflowsPrefix = `${toPosix$2(relative(root, join(srcRoot, "workflows")))}/`;
|
|
95
|
+
const triggersPrefix = `${toPosix$2(relative(root, join(srcRoot, "triggers")))}/`;
|
|
96
|
+
if (relativePath.startsWith(agentsPrefix) && /\.(ts|mts)$/.test(relativePath) && !/\.(int\.)?test\.(ts|mts)$/.test(relativePath)) {
|
|
97
|
+
const id = entryIdFromFile(join(srcRoot, "agents"), filePath, { nestedEntry: "agent" });
|
|
98
|
+
return id ? {
|
|
99
|
+
entryKey: `agents/${id}`,
|
|
100
|
+
entryPath: filePath
|
|
101
|
+
} : null;
|
|
102
|
+
}
|
|
103
|
+
if (relativePath.startsWith(workflowsPrefix) && /\.(ts|mts)$/.test(relativePath) && !/\.(int\.)?test\.(ts|mts)$/.test(relativePath)) {
|
|
104
|
+
const id = entryIdFromFile(join(srcRoot, "workflows"), filePath, { nestedEntry: "workflow" });
|
|
105
|
+
return id ? {
|
|
106
|
+
entryKey: `workflows/${id}`,
|
|
107
|
+
entryPath: filePath
|
|
108
|
+
} : null;
|
|
109
|
+
}
|
|
110
|
+
if (relativePath.startsWith(triggersPrefix) && /\.(ts|mts)$/.test(relativePath) && !/\.(int\.)?test\.(ts|mts)$/.test(relativePath)) {
|
|
111
|
+
const id = entryIdFromFile(join(srcRoot, "triggers"), filePath, { nestedEntry: "trigger" });
|
|
112
|
+
return id ? {
|
|
113
|
+
entryKey: `triggers/${id}`,
|
|
114
|
+
entryPath: filePath
|
|
115
|
+
} : null;
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
function shouldSkipIgnoredModule(filePath, phase) {
|
|
120
|
+
return shouldSkipKeystrokeModuleFile(readKeystrokeIgnoreDirective(filePath), phase);
|
|
121
|
+
}
|
|
122
|
+
function isSrcTypeScriptFile(srcRoot, filePath) {
|
|
123
|
+
const relativePath = toPosix$2(relative(srcRoot, filePath));
|
|
124
|
+
if (relativePath.startsWith("..")) return false;
|
|
125
|
+
return /\.(ts|mts)$/.test(relativePath);
|
|
126
|
+
}
|
|
127
|
+
function walkTree(root, dir, srcRoot, phase, collectSources, sourceFiles, buildEntries, ignoredFiles, totals) {
|
|
128
|
+
for (const name of readdirSync(dir).sort()) {
|
|
129
|
+
if (collectSources && (sourceFiles.length >= 2e3 || totals.bytes >= 8388608)) return;
|
|
130
|
+
const absolute = join(dir, name);
|
|
131
|
+
const stats = statSync(absolute);
|
|
132
|
+
if (stats.isDirectory()) {
|
|
133
|
+
if (IGNORED_DIRS.has(name)) continue;
|
|
134
|
+
walkTree(root, absolute, srcRoot, phase, collectSources, sourceFiles, buildEntries, ignoredFiles, totals);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (!stats.isFile()) continue;
|
|
138
|
+
const isUnderSrc = isSrcTypeScriptFile(srcRoot, absolute);
|
|
139
|
+
const skipIgnored = isUnderSrc ? shouldSkipIgnoredModule(absolute, phase) : false;
|
|
140
|
+
if (isUnderSrc && skipIgnored) ignoredFiles.push(normalizeBuildFilePath(absolute));
|
|
141
|
+
const buildEntry = classifyBuildEntry(root, srcRoot, absolute);
|
|
142
|
+
if (buildEntry && !skipIgnored) buildEntries[buildEntry.entryKey] = buildEntry.entryPath;
|
|
143
|
+
if (!collectSources) continue;
|
|
144
|
+
if (stats.size > 262144 || isIgnoredFile(name)) continue;
|
|
145
|
+
const buffer = readFileSync(absolute);
|
|
146
|
+
if (isProbablyBinary(buffer)) continue;
|
|
147
|
+
totals.bytes += buffer.byteLength;
|
|
148
|
+
sourceFiles.push({
|
|
149
|
+
path: toPosix$2(relative(root, absolute)),
|
|
150
|
+
contents: buffer.toString("utf8"),
|
|
151
|
+
hash: createHash("sha256").update(buffer).digest("hex")
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* One full-tree walk of the project: classifies tsdown entries and optionally
|
|
157
|
+
* collects deploy source files (path + contents + hash) under snapshot limits.
|
|
158
|
+
*/
|
|
159
|
+
function walkProject(root, srcDirOrOptions = "src", maybeOptions) {
|
|
160
|
+
const srcDir = typeof srcDirOrOptions === "string" ? srcDirOrOptions : srcDirOrOptions.srcDir ?? "src";
|
|
161
|
+
const options = typeof srcDirOrOptions === "string" ? maybeOptions ?? {} : srcDirOrOptions;
|
|
162
|
+
const collectSources = options.collectSources ?? false;
|
|
163
|
+
const phase = options.phase ?? "build";
|
|
164
|
+
const sourceFiles = [];
|
|
165
|
+
const buildEntries = {};
|
|
166
|
+
const ignoredFiles = [];
|
|
167
|
+
walkTree(root, root, join(root, srcDir), phase, collectSources, sourceFiles, buildEntries, ignoredFiles, { bytes: 0 });
|
|
168
|
+
return {
|
|
169
|
+
sourceFiles,
|
|
170
|
+
buildEntries,
|
|
171
|
+
ignoredFiles
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
//#endregion
|
|
175
|
+
//#region ../../packages/sandbox/dist/files-B0H9_dP4.mjs
|
|
176
|
+
const SandboxFileContentSchema = object({
|
|
177
|
+
path: string(),
|
|
178
|
+
file: string()
|
|
179
|
+
});
|
|
180
|
+
/** Destination path under `/workspace`. For `dir`, used as a prefix. */
|
|
181
|
+
const SandboxFileSchema = object({
|
|
182
|
+
path: string(),
|
|
183
|
+
/** Single file body — typically from a build-time import (`import doc from "./doc.md"`). */
|
|
184
|
+
file: string().optional(),
|
|
185
|
+
/** Directory contents — each path is relative to `path`. */
|
|
186
|
+
dir: array(SandboxFileContentSchema).optional()
|
|
187
|
+
});
|
|
188
|
+
object({
|
|
189
|
+
key: string().optional(),
|
|
190
|
+
files: array(SandboxFileSchema).optional()
|
|
191
|
+
});
|
|
192
|
+
const SKIP_DIRS$1 = new Set([".git", "node_modules"]);
|
|
193
|
+
/** Recursively read a host file or directory into sandbox-relative `{ path, file }` entries. */
|
|
194
|
+
function packDirFromDisk(rootPath) {
|
|
195
|
+
const stat = statSync(rootPath);
|
|
196
|
+
if (stat.isFile()) return [{
|
|
197
|
+
path: basename(rootPath),
|
|
198
|
+
file: readFileSync(rootPath, "utf8")
|
|
199
|
+
}];
|
|
200
|
+
if (!stat.isDirectory()) throw new Error(`Expected a file or directory at ${rootPath}`);
|
|
201
|
+
const files = [];
|
|
202
|
+
walkDir(rootPath, rootPath, files);
|
|
203
|
+
return files;
|
|
204
|
+
}
|
|
205
|
+
function walkDir(rootPath, currentPath, files) {
|
|
206
|
+
for (const name of readdirSync(currentPath)) {
|
|
207
|
+
if (SKIP_DIRS$1.has(name)) continue;
|
|
208
|
+
const entryPath = join(currentPath, name);
|
|
209
|
+
const stat = statSync(entryPath);
|
|
210
|
+
if (stat.isDirectory()) {
|
|
211
|
+
walkDir(rootPath, entryPath, files);
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (!stat.isFile()) continue;
|
|
215
|
+
files.push({
|
|
216
|
+
path: relative(rootPath, entryPath),
|
|
217
|
+
file: readFileSync(entryPath, "utf8")
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
const SKIP_DIRS = new Set([".git", "node_modules"]);
|
|
222
|
+
/** Pack every subdir under `src/skills/` and `src/files/` into an asset manifest. */
|
|
223
|
+
function packAssetDirs(appRoot, srcDir = "src") {
|
|
224
|
+
return {
|
|
225
|
+
skills: packAssetRoot(join(appRoot, srcDir, "skills")),
|
|
226
|
+
files: packAssetRoot(join(appRoot, srcDir, "files"))
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
function packAssetRoot(rootPath) {
|
|
230
|
+
if (!statSync(rootPath, { throwIfNoEntry: false })?.isDirectory()) return {};
|
|
231
|
+
const manifest = {};
|
|
232
|
+
for (const name of readdirSync(rootPath)) {
|
|
233
|
+
if (SKIP_DIRS.has(name)) continue;
|
|
234
|
+
const entryPath = join(rootPath, name);
|
|
235
|
+
if (!statSync(entryPath).isDirectory()) continue;
|
|
236
|
+
manifest[name] = packDirFromDisk(entryPath);
|
|
237
|
+
}
|
|
238
|
+
return manifest;
|
|
239
|
+
}
|
|
240
|
+
//#endregion
|
|
241
|
+
//#region ../../packages/tsdown-config/deps.js
|
|
242
|
+
/** Native addons kept external — resolved from the CLI runtime at app start. */
|
|
243
|
+
const NATIVE_RUNTIME_DEPS = [
|
|
244
|
+
"pg",
|
|
245
|
+
"better-sqlite3",
|
|
246
|
+
"@parcel/watcher"
|
|
247
|
+
];
|
|
248
|
+
/** Kept external so .d.ts emission does not inline internal/inferred types. */
|
|
249
|
+
const LIBRARY_EXTERNAL_DEPS = [
|
|
250
|
+
"better-auth",
|
|
251
|
+
/^@better-auth\//,
|
|
252
|
+
/^better-auth\//
|
|
253
|
+
];
|
|
254
|
+
/** Kept external when bundling published entry points (cli, keystroke, platform). */
|
|
255
|
+
const BUNDLED_ENTRY_EXTERNAL_DEPS = [...[
|
|
256
|
+
...NATIVE_RUNTIME_DEPS,
|
|
257
|
+
"dockerode",
|
|
258
|
+
"ssh2",
|
|
259
|
+
"cpu-features",
|
|
260
|
+
"microsandbox",
|
|
261
|
+
/^@aws-sdk\//,
|
|
262
|
+
/^@napi-rs\//,
|
|
263
|
+
"hono",
|
|
264
|
+
/^@hono\//,
|
|
265
|
+
"undici"
|
|
266
|
+
], ...LIBRARY_EXTERNAL_DEPS];
|
|
267
|
+
/** Bundle into CLI — device login uses Better Auth client only. */
|
|
268
|
+
const BETTER_AUTH_CLIENT_BUNDLE_PATTERNS = [/^better-auth\/client$/, /^better-auth\/client\//];
|
|
269
|
+
function isNativeRuntimeDep(id) {
|
|
270
|
+
return NATIVE_RUNTIME_DEPS.some((dep) => id === dep || id.startsWith(`${dep}/`));
|
|
271
|
+
}
|
|
272
|
+
function packageName(id) {
|
|
273
|
+
if (id.startsWith("@")) {
|
|
274
|
+
const [scope, name] = id.split("/");
|
|
275
|
+
return name ? `${scope}/${name}` : id;
|
|
276
|
+
}
|
|
277
|
+
return id.split("/")[0] ?? id;
|
|
278
|
+
}
|
|
279
|
+
function isRuntimeExternal(id) {
|
|
280
|
+
const name = packageName(id);
|
|
281
|
+
if (isNativeRuntimeDep(name)) return true;
|
|
282
|
+
if (name.startsWith("@keystrokehq/")) return true;
|
|
283
|
+
if (name === "better-auth" || name.startsWith("@better-auth/")) return true;
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
/** Library packages resolve npm deps from node_modules at runtime. */
|
|
287
|
+
function libraryBuildDepsConfig() {
|
|
288
|
+
return {
|
|
289
|
+
alwaysBundle: (_id) => null,
|
|
290
|
+
neverBundle: [
|
|
291
|
+
...NATIVE_RUNTIME_DEPS,
|
|
292
|
+
/^@keystrokehq\//,
|
|
293
|
+
...LIBRARY_EXTERNAL_DEPS
|
|
294
|
+
],
|
|
295
|
+
onlyBundle: false
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
/** Bundle npm deps into dist; resolve @keystrokehq/* and natives from the runtime. */
|
|
299
|
+
function userAppBuildDepsConfig() {
|
|
300
|
+
return {
|
|
301
|
+
alwaysBundle: (id, _importer) => {
|
|
302
|
+
if (id.startsWith(".") || id.startsWith("/") || isBuiltin(id)) return null;
|
|
303
|
+
return isRuntimeExternal(id) ? null : true;
|
|
304
|
+
},
|
|
305
|
+
neverBundle: [
|
|
306
|
+
...NATIVE_RUNTIME_DEPS,
|
|
307
|
+
/^@keystrokehq\//,
|
|
308
|
+
...LIBRARY_EXTERNAL_DEPS
|
|
309
|
+
],
|
|
310
|
+
onlyBundle: false
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Published entry packages (cli, keystroke, platform): declare bundled @keystrokehq/*
|
|
315
|
+
* in dependencies (changesets release graph ignores devDependencies). Inlining is
|
|
316
|
+
* driven by alwaysBundle below, not by dep kind. Runtime npm deps stay external.
|
|
317
|
+
*/
|
|
318
|
+
function bundledEntryDepsConfig() {
|
|
319
|
+
return {
|
|
320
|
+
alwaysBundle: [/^@keystrokehq\//],
|
|
321
|
+
neverBundle: [...BUNDLED_ENTRY_EXTERNAL_DEPS],
|
|
322
|
+
onlyBundle: false
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
[...BETTER_AUTH_CLIENT_BUNDLE_PATTERNS];
|
|
326
|
+
//#endregion
|
|
327
|
+
//#region ../../packages/tsdown-config/index.js
|
|
328
|
+
const baseTsdownConfig = {
|
|
329
|
+
format: ["esm", "cjs"],
|
|
330
|
+
dts: true,
|
|
331
|
+
clean: true,
|
|
332
|
+
sourcemap: true,
|
|
333
|
+
target: "node20",
|
|
334
|
+
deps: libraryBuildDepsConfig()
|
|
335
|
+
};
|
|
336
|
+
bundledEntryDepsConfig();
|
|
337
|
+
//#endregion
|
|
338
|
+
//#region ../../packages/build/dist/index.mjs
|
|
339
|
+
function packageRoot(nodeModules, scope, name) {
|
|
340
|
+
const direct = scope ? join(nodeModules, scope, name) : join(nodeModules, name);
|
|
341
|
+
if (existsSync(join(direct, "package.json"))) return direct;
|
|
342
|
+
const pnpmDir = join(nodeModules, ".pnpm");
|
|
343
|
+
if (!existsSync(pnpmDir)) return null;
|
|
344
|
+
for (const entry of readdirSync(pnpmDir)) {
|
|
345
|
+
const candidate = scope ? join(pnpmDir, entry, "node_modules", scope, name) : join(pnpmDir, entry, "node_modules", name);
|
|
346
|
+
if (existsSync(join(candidate, "package.json"))) return candidate;
|
|
347
|
+
}
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
350
|
+
/** Resolve a file shipped with `@keystrokehq/build` inside the CLI runtime. */
|
|
351
|
+
function resolveRuntimeBuildArtifact(runtimeNodeModules, relativePath) {
|
|
352
|
+
const pkgRoot = packageRoot(runtimeNodeModules, "@keystrokehq", "build");
|
|
353
|
+
if (!pkgRoot) throw new Error("Keystroke runtime is missing @keystrokehq/build");
|
|
354
|
+
const artifact = join(pkgRoot, relativePath);
|
|
355
|
+
if (!existsSync(artifact)) throw new Error(`Keystroke runtime artifact not found: ${relativePath}`);
|
|
356
|
+
return artifact;
|
|
357
|
+
}
|
|
358
|
+
/** Resolve exact build-entry keys for a filtered deploy build. */
|
|
359
|
+
function resolveBuildFilter(filter, buildEntries) {
|
|
360
|
+
if (filter.length === 0) throw new Error("At least one --filter entry is required");
|
|
361
|
+
const matched = {};
|
|
362
|
+
for (const key of [...new Set(filter)]) {
|
|
363
|
+
const entryPath = buildEntries[key];
|
|
364
|
+
if (!entryPath) throw new Error(`Unknown build entry "${key}". Use keys like agents/foo or workflows/bar from walkProject.`);
|
|
365
|
+
matched[key] = entryPath;
|
|
366
|
+
}
|
|
367
|
+
return matched;
|
|
368
|
+
}
|
|
369
|
+
function toPosix(path) {
|
|
370
|
+
return path.replace(/\\/g, "/");
|
|
371
|
+
}
|
|
372
|
+
function distRelativePath(entryKey) {
|
|
373
|
+
return `${entryKey}.mjs`;
|
|
374
|
+
}
|
|
375
|
+
async function buildSingleEntry(root, entryKey, entryPath, ignoredFiles) {
|
|
376
|
+
mkdirSync(join(root, ".keystroke"), { recursive: true });
|
|
377
|
+
const scratchRoot = mkdtempSync(join(root, ".keystroke", "filter-build-"));
|
|
378
|
+
const outDir = join(scratchRoot, "dist");
|
|
379
|
+
const relativePath = distRelativePath(entryKey);
|
|
380
|
+
await build(mergeConfig(baseTsdownConfig, {
|
|
381
|
+
entry: { [entryKey]: entryPath },
|
|
382
|
+
format: ["esm"],
|
|
383
|
+
outDir,
|
|
384
|
+
clean: true,
|
|
385
|
+
dts: false,
|
|
386
|
+
loader: { ".md": "text" },
|
|
387
|
+
deps: userAppBuildDepsConfig(),
|
|
388
|
+
plugins: [keystrokeIgnoreGuardPlugin({
|
|
389
|
+
root,
|
|
390
|
+
ignoredFiles
|
|
391
|
+
})]
|
|
392
|
+
}));
|
|
393
|
+
return {
|
|
394
|
+
scratchRoot,
|
|
395
|
+
relativePath
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
async function manifestEntriesForBuiltModule(root, entryPath, relativePath, contents, sourceMap) {
|
|
399
|
+
const moduleFile = toPosix(relative(root, entryPath));
|
|
400
|
+
const distPath = join(root, "dist", relativePath);
|
|
401
|
+
const distMapPath = `${distPath}.map`;
|
|
402
|
+
const previousDist = existsSync(distPath) ? readFileSync(distPath) : void 0;
|
|
403
|
+
const hadMap = existsSync(distMapPath);
|
|
404
|
+
const previousMap = hadMap ? readFileSync(distMapPath) : void 0;
|
|
405
|
+
mkdirSync(dirname(distPath), { recursive: true });
|
|
406
|
+
writeFileSync(distPath, contents);
|
|
407
|
+
if (sourceMap) writeFileSync(distMapPath, sourceMap);
|
|
408
|
+
try {
|
|
409
|
+
return (await buildStoredRouteManifestForProject(root, { reloadModules: true })).entries.filter((entry) => "moduleFile" in entry && entry.moduleFile === moduleFile);
|
|
410
|
+
} finally {
|
|
411
|
+
if (previousDist) writeFileSync(distPath, previousDist);
|
|
412
|
+
else rmSync(distPath, { force: true });
|
|
413
|
+
if (previousMap) writeFileSync(distMapPath, previousMap);
|
|
414
|
+
else if (sourceMap || hadMap) rmSync(distMapPath, { force: true });
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
/** Build selected modules in isolation for a filtered deploy merge. */
|
|
418
|
+
async function buildFilteredApp(options) {
|
|
419
|
+
const root = options.root ?? process.cwd();
|
|
420
|
+
const srcDir = options.srcDir ?? "src";
|
|
421
|
+
const phase = options.phase ?? "deploy";
|
|
422
|
+
const collectSources = options.collectSources ?? false;
|
|
423
|
+
const previous = process.cwd();
|
|
424
|
+
const walked = walkProject(root, {
|
|
425
|
+
srcDir,
|
|
426
|
+
collectSources,
|
|
427
|
+
phase
|
|
428
|
+
});
|
|
429
|
+
const matched = resolveBuildFilter(options.filter, walked.buildEntries);
|
|
430
|
+
const files = [];
|
|
431
|
+
const manifestEntries = [];
|
|
432
|
+
try {
|
|
433
|
+
process.chdir(root);
|
|
434
|
+
for (const [entryKey, entryPath] of Object.entries(matched)) {
|
|
435
|
+
const { scratchRoot, relativePath } = await buildSingleEntry(root, entryKey, entryPath, walked.ignoredFiles);
|
|
436
|
+
try {
|
|
437
|
+
const builtPath = join(scratchRoot, "dist", relativePath);
|
|
438
|
+
const mapPath = `${builtPath}.map`;
|
|
439
|
+
const contents = readFileSync(builtPath);
|
|
440
|
+
const sourceMap = existsSync(mapPath) ? readFileSync(mapPath) : void 0;
|
|
441
|
+
files.push({
|
|
442
|
+
entryKey,
|
|
443
|
+
relativePath,
|
|
444
|
+
contents,
|
|
445
|
+
sourceMap
|
|
446
|
+
});
|
|
447
|
+
manifestEntries.push(...await manifestEntriesForBuiltModule(root, entryPath, relativePath, contents, sourceMap));
|
|
448
|
+
} finally {
|
|
449
|
+
rmSync(scratchRoot, {
|
|
450
|
+
recursive: true,
|
|
451
|
+
force: true
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
} finally {
|
|
456
|
+
process.chdir(previous);
|
|
457
|
+
}
|
|
458
|
+
return {
|
|
459
|
+
files,
|
|
460
|
+
manifestEntries,
|
|
461
|
+
sourceFiles: walked.sourceFiles
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
const ASSETS_MODULE = "@keystrokehq/assets";
|
|
465
|
+
const VIRTUAL_PREFIX = `\0${ASSETS_MODULE}:`;
|
|
466
|
+
function renderAssetModule(manifest) {
|
|
467
|
+
return `export default ${JSON.stringify(manifest)};\n`;
|
|
468
|
+
}
|
|
469
|
+
/** Rolldown plugin: pack src/skills + src/files and expose `@keystrokehq/assets`. */
|
|
470
|
+
function agentAssetsPlugin(options) {
|
|
471
|
+
const srcDir = options.srcDir ?? "src";
|
|
472
|
+
let manifest = null;
|
|
473
|
+
return {
|
|
474
|
+
name: "keystroke-assets",
|
|
475
|
+
buildStart() {
|
|
476
|
+
manifest = packAssetDirs(options.root, srcDir);
|
|
477
|
+
},
|
|
478
|
+
resolveId(source) {
|
|
479
|
+
if (source !== ASSETS_MODULE) return null;
|
|
480
|
+
return `${VIRTUAL_PREFIX}manifest`;
|
|
481
|
+
},
|
|
482
|
+
load(id) {
|
|
483
|
+
if (id !== `${VIRTUAL_PREFIX}manifest` || !manifest) return null;
|
|
484
|
+
return renderAssetModule(manifest);
|
|
485
|
+
},
|
|
486
|
+
buildEnd() {
|
|
487
|
+
if (!manifest) return;
|
|
488
|
+
const outDir = join(options.root, options.outDir ?? "dist", ".keystroke");
|
|
489
|
+
mkdirSync(outDir, { recursive: true });
|
|
490
|
+
writeFileSync(join(outDir, "assets.mjs"), renderAssetModule(manifest));
|
|
491
|
+
}
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
/** Full tsdown config for user keystroke apps (config + discovered modules only). */
|
|
495
|
+
function createAppBuildConfig(options = {}, walked) {
|
|
496
|
+
const root = options.root ?? process.cwd();
|
|
497
|
+
const srcDir = options.srcDir ?? "src";
|
|
498
|
+
const outDir = options.outDir ?? "dist";
|
|
499
|
+
const configEntry = existsSync(join(root, "keystroke.config.ts")) ? join(root, "keystroke.config.ts") : void 0;
|
|
500
|
+
const walkedResult = walked ?? walkProject(root, {
|
|
501
|
+
srcDir,
|
|
502
|
+
phase: options.phase
|
|
503
|
+
});
|
|
504
|
+
const discovered = walkedResult.buildEntries;
|
|
505
|
+
const ignoredFiles = walkedResult.ignoredFiles;
|
|
506
|
+
const entries = {
|
|
507
|
+
...configEntry ? { config: configEntry } : {},
|
|
508
|
+
...discovered
|
|
509
|
+
};
|
|
510
|
+
if (Object.keys(entries).length === 0) throw new Error("Nothing to build — add keystroke.config.ts or modules under src/");
|
|
511
|
+
return mergeConfig(baseTsdownConfig, {
|
|
512
|
+
entry: entries,
|
|
513
|
+
format: ["esm"],
|
|
514
|
+
outDir: join(root, outDir),
|
|
515
|
+
clean: options.clean ?? true,
|
|
516
|
+
dts: false,
|
|
517
|
+
loader: { ".md": "text" },
|
|
518
|
+
deps: userAppBuildDepsConfig(),
|
|
519
|
+
plugins: [keystrokeIgnoreGuardPlugin({
|
|
520
|
+
root,
|
|
521
|
+
ignoredFiles
|
|
522
|
+
}), agentAssetsPlugin({
|
|
523
|
+
root,
|
|
524
|
+
srcDir,
|
|
525
|
+
outDir
|
|
526
|
+
})]
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
/** Build user agents, workflows, triggers, and config to `dist/`. */
|
|
530
|
+
async function buildApp(options = {}) {
|
|
531
|
+
const root = options.root ?? process.cwd();
|
|
532
|
+
const srcDir = options.srcDir ?? "src";
|
|
533
|
+
const emitManifest = options.emitManifest ?? true;
|
|
534
|
+
const collectSources = options.collectSources ?? false;
|
|
535
|
+
const phase = options.phase ?? "build";
|
|
536
|
+
const previous = process.cwd();
|
|
537
|
+
const walked = walkProject(root, {
|
|
538
|
+
srcDir,
|
|
539
|
+
collectSources,
|
|
540
|
+
phase
|
|
541
|
+
});
|
|
542
|
+
try {
|
|
543
|
+
process.chdir(root);
|
|
544
|
+
await build(createAppBuildConfig({
|
|
545
|
+
...options,
|
|
546
|
+
root
|
|
547
|
+
}, {
|
|
548
|
+
buildEntries: walked.buildEntries,
|
|
549
|
+
ignoredFiles: walked.ignoredFiles
|
|
550
|
+
}));
|
|
551
|
+
if (emitManifest) {
|
|
552
|
+
const { emitStoredRouteManifestForProject } = await import("./dist-CegYAHE0.mjs");
|
|
553
|
+
await emitStoredRouteManifestForProject(root);
|
|
554
|
+
}
|
|
555
|
+
} finally {
|
|
556
|
+
process.chdir(previous);
|
|
557
|
+
}
|
|
558
|
+
return { sourceFiles: walked.sourceFiles };
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Watch `src/` and rebuild `dist/` on change.
|
|
562
|
+
*
|
|
563
|
+
* NOT true hot reload — each change runs a full tsdown rebuild. Callers should
|
|
564
|
+
* restart the API server in `onRebuild` so discovery picks up new/changed modules.
|
|
565
|
+
*/
|
|
566
|
+
async function watchApp(options = {}) {
|
|
567
|
+
const { runWatchApp } = await import("./watch-app-DTIeKrbl-CSpsNzVG.mjs");
|
|
568
|
+
await runWatchApp((watchOptions) => buildApp({
|
|
569
|
+
...watchOptions,
|
|
570
|
+
emitManifest: false,
|
|
571
|
+
collectSources: false
|
|
572
|
+
}), options);
|
|
573
|
+
}
|
|
574
|
+
//#endregion
|
|
575
|
+
export { buildApp, buildFilteredApp, resolveRuntimeBuildArtifact, watchApp };
|
|
576
|
+
|
|
577
|
+
//# sourceMappingURL=dist-BMPry9tj.mjs.map
|