@flowdular/sdk 0.1.0 → 0.2.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.
Files changed (174) hide show
  1. package/.ai/skills/test-hardening/SKILL.md +1 -1
  2. package/README.md +69 -12
  3. package/assets/flowdular-banner.png +0 -0
  4. package/modules/agents/package.json +1 -1
  5. package/modules/auth/package.json +1 -1
  6. package/modules/auth/src/client/AuthenticationCore.tsrx +3 -1
  7. package/modules/auth/src/server/endpoints.ts +3 -1
  8. package/modules/auth/src/server/runtime.ts +4 -0
  9. package/modules/automations/package.json +1 -1
  10. package/modules/automations-workflows-integration/package.json +1 -1
  11. package/modules/profile/package.json +1 -1
  12. package/modules/sandbox/package.json +1 -1
  13. package/modules/system/package.json +1 -1
  14. package/modules/users/package.json +1 -1
  15. package/modules/workflows/package.json +1 -1
  16. package/package.json +5 -13
  17. package/packages/ai-provider/package.json +1 -1
  18. package/packages/cli-protocol/package.json +1 -1
  19. package/packages/client/package.json +1 -1
  20. package/packages/client/src/routing.ts +39 -2
  21. package/packages/client/src/shell/BrandMark.tsrx +5 -1
  22. package/packages/client/src/shell/navigation.ts +3 -1
  23. package/packages/client/src/state.ts +6 -2
  24. package/packages/contracts/package.json +1 -1
  25. package/packages/contracts/schemas/project.schema.json +16 -42
  26. package/packages/database/package.json +1 -1
  27. package/packages/database-pglite/package.json +1 -1
  28. package/packages/database-testing/package.json +1 -1
  29. package/packages/dev-console/package.json +1 -1
  30. package/packages/harness/package.json +1 -1
  31. package/packages/kernel/package.json +1 -1
  32. package/packages/server/package.json +1 -1
  33. package/packages/server/src/application-routes.ts +80 -8
  34. package/packages/server/src/index.ts +4 -1
  35. package/packages/server/src/web-html.ts +9 -5
  36. package/packages/server/src/web.ts +59 -7
  37. package/packages/ui/package.json +1 -1
  38. package/packages/coding-agent/package.json +0 -44
  39. package/packages/coding-agent/src/drivers/byok.ts +0 -322
  40. package/packages/coding-agent/src/drivers/claude-code.ts +0 -290
  41. package/packages/coding-agent/src/drivers/codex.ts +0 -281
  42. package/packages/coding-agent/src/index.ts +0 -56
  43. package/packages/coding-agent/src/registry.ts +0 -119
  44. package/packages/coding-agent/src/roles/contract.ts +0 -60
  45. package/packages/coding-agent/src/roles/defaults.ts +0 -134
  46. package/packages/coding-agent/src/roles/handoff.ts +0 -41
  47. package/packages/coding-agent/src/roles/registry.ts +0 -156
  48. package/packages/coding-agent/src/roles/skills.ts +0 -72
  49. package/packages/coding-agent/src/roles/sync.ts +0 -88
  50. package/packages/coding-agent/src/types.ts +0 -102
  51. package/packages/coding-agent/src/workspace.ts +0 -275
  52. package/packages/sandbox/README.md +0 -691
  53. package/packages/sandbox/bin/flowdular-sandbox.mjs +0 -393
  54. package/packages/sandbox/dist/client/assets/App-ClMOrpyg.js +0 -11
  55. package/packages/sandbox/dist/client/assets/PreviewHost-BHiHrfbQ.js +0 -1
  56. package/packages/sandbox/dist/client/assets/auto-buGEq6de.js +0 -3
  57. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2 +0 -0
  58. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff +0 -0
  59. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-500-normal-Ael50iVv.woff +0 -0
  60. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-500-normal-Bq9vWWag.woff2 +0 -0
  61. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff +0 -0
  62. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2 +0 -0
  63. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-500-normal-BIfNGwUT.woff +0 -0
  64. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-500-normal-BqneJy0T.woff2 +0 -0
  65. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
  66. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  67. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
  68. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
  69. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2 +0 -0
  70. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff +0 -0
  71. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-500-normal-CAhNIIs5.woff2 +0 -0
  72. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-500-normal-CZ70TYgx.woff +0 -0
  73. package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2 +0 -0
  74. package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff +0 -0
  75. package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-500-normal-C8zxqsMH.woff +0 -0
  76. package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-500-normal-DZ4AoWbu.woff2 +0 -0
  77. package/packages/sandbox/dist/client/assets/ibm-plex-sans-cyrillic-ext-wght-normal-d45eAU9y.woff2 +0 -0
  78. package/packages/sandbox/dist/client/assets/ibm-plex-sans-cyrillic-wght-normal-BAAhND-U.woff2 +0 -0
  79. package/packages/sandbox/dist/client/assets/ibm-plex-sans-greek-wght-normal-CmyJS8uq.woff2 +0 -0
  80. package/packages/sandbox/dist/client/assets/ibm-plex-sans-latin-ext-wght-normal-CIII54If.woff2 +0 -0
  81. package/packages/sandbox/dist/client/assets/ibm-plex-sans-latin-wght-normal-IvpUvPa2.woff2 +0 -0
  82. package/packages/sandbox/dist/client/assets/ibm-plex-sans-vietnamese-wght-normal-Dg1JeJN0.woff2 +0 -0
  83. package/packages/sandbox/dist/client/assets/index-CvwhlQ87.js +0 -4
  84. package/packages/sandbox/dist/client/assets/styles-BPWaGf55.js +0 -3
  85. package/packages/sandbox/dist/client/assets/styles-Dx1_KOag.css +0 -1
  86. package/packages/sandbox/dist/client/favicon.svg +0 -1
  87. package/packages/sandbox/dist/server/assets/auto-CVGNYyb3.js +0 -12785
  88. package/packages/sandbox/dist/server/assets/chunk-DDJLRBDX-3OUNo51g.js +0 -4125
  89. package/packages/sandbox/dist/server/assets/nodefs-DVSI7kG0.js +0 -24
  90. package/packages/sandbox/dist/server/assets/opfs-ahp-D56iokt1.js +0 -388
  91. package/packages/sandbox/dist/server/assets/token-Bvaq7Anz.js +0 -55
  92. package/packages/sandbox/dist/server/assets/token-util-BqSvdIPQ.js +0 -3
  93. package/packages/sandbox/dist/server/assets/token-util-CktFFT4z.js +0 -494
  94. package/packages/sandbox/dist/server/entry.js +0 -91671
  95. package/packages/sandbox/dist/server/favicon.svg +0 -1
  96. package/packages/sandbox/dist/server/index.html +0 -161
  97. package/packages/sandbox/index.html +0 -159
  98. package/packages/sandbox/octane.config.ts +0 -36
  99. package/packages/sandbox/package.json +0 -83
  100. package/packages/sandbox/public/favicon.svg +0 -1
  101. package/packages/sandbox/public/og.png +0 -0
  102. package/packages/sandbox/src/App.tsrx +0 -924
  103. package/packages/sandbox/src/client/ApprovalHandoff.tsrx +0 -39
  104. package/packages/sandbox/src/client/ChatPane.tsrx +0 -694
  105. package/packages/sandbox/src/client/ConnectView.tsrx +0 -86
  106. package/packages/sandbox/src/client/DiffPane.tsrx +0 -150
  107. package/packages/sandbox/src/client/EjectModal.tsrx +0 -571
  108. package/packages/sandbox/src/client/GateResults.tsrx +0 -60
  109. package/packages/sandbox/src/client/GitHubSettingsModal.tsrx +0 -219
  110. package/packages/sandbox/src/client/NewSessionForm.tsrx +0 -214
  111. package/packages/sandbox/src/client/SessionBar.tsrx +0 -151
  112. package/packages/sandbox/src/client/SessionPicker.tsrx +0 -472
  113. package/packages/sandbox/src/client/SpecEditor.tsrx +0 -109
  114. package/packages/sandbox/src/client/SpecReviewCard.tsrx +0 -321
  115. package/packages/sandbox/src/client/WorkCard.tsrx +0 -128
  116. package/packages/sandbox/src/client/WorkbenchModal.tsrx +0 -161
  117. package/packages/sandbox/src/client/WorkspaceMenu.tsrx +0 -100
  118. package/packages/sandbox/src/client/api.ts +0 -797
  119. package/packages/sandbox/src/client/attachments.ts +0 -73
  120. package/packages/sandbox/src/client/dashboard.ts +0 -46
  121. package/packages/sandbox/src/client/github-settings.ts +0 -48
  122. package/packages/sandbox/src/client/highlight.ts +0 -105
  123. package/packages/sandbox/src/client/i18n.ts +0 -32
  124. package/packages/sandbox/src/client/locales/en.json +0 -466
  125. package/packages/sandbox/src/client/locales/pl.json +0 -466
  126. package/packages/sandbox/src/client/session-labels.ts +0 -77
  127. package/packages/sandbox/src/client/state.ts +0 -108
  128. package/packages/sandbox/src/index.ts +0 -10
  129. package/packages/sandbox/src/preview/PreviewHost.tsrx +0 -373
  130. package/packages/sandbox/src/preview/load-module.ts +0 -16
  131. package/packages/sandbox/src/server/attachments.ts +0 -308
  132. package/packages/sandbox/src/server/auto-review.ts +0 -181
  133. package/packages/sandbox/src/server/checkpoints.ts +0 -90
  134. package/packages/sandbox/src/server/config.ts +0 -460
  135. package/packages/sandbox/src/server/dashboard.ts +0 -245
  136. package/packages/sandbox/src/server/delivery/configuration.ts +0 -193
  137. package/packages/sandbox/src/server/delivery/git-pr.ts +0 -1473
  138. package/packages/sandbox/src/server/delivery/index.ts +0 -83
  139. package/packages/sandbox/src/server/delivery/local.ts +0 -151
  140. package/packages/sandbox/src/server/delivery/official-modules.ts +0 -516
  141. package/packages/sandbox/src/server/delivery/plan.ts +0 -218
  142. package/packages/sandbox/src/server/delivery/policies.ts +0 -239
  143. package/packages/sandbox/src/server/delivery/record.ts +0 -41
  144. package/packages/sandbox/src/server/delivery/steps.ts +0 -464
  145. package/packages/sandbox/src/server/delivery/types.ts +0 -158
  146. package/packages/sandbox/src/server/dependencies.ts +0 -88
  147. package/packages/sandbox/src/server/diff.ts +0 -230
  148. package/packages/sandbox/src/server/gates.ts +0 -388
  149. package/packages/sandbox/src/server/index.ts +0 -139
  150. package/packages/sandbox/src/server/path-guard.ts +0 -282
  151. package/packages/sandbox/src/server/planning.ts +0 -749
  152. package/packages/sandbox/src/server/platform-client.ts +0 -189
  153. package/packages/sandbox/src/server/preview-database-host.ts +0 -340
  154. package/packages/sandbox/src/server/preview-database-protocol.ts +0 -163
  155. package/packages/sandbox/src/server/preview-database-proxy.ts +0 -356
  156. package/packages/sandbox/src/server/preview-database.ts +0 -109
  157. package/packages/sandbox/src/server/preview-ipc.ts +0 -15
  158. package/packages/sandbox/src/server/preview-modules.ts +0 -83
  159. package/packages/sandbox/src/server/preview-revision.ts +0 -44
  160. package/packages/sandbox/src/server/preview-runtime.ts +0 -467
  161. package/packages/sandbox/src/server/preview-worker-manager.ts +0 -436
  162. package/packages/sandbox/src/server/preview-worker.ts +0 -168
  163. package/packages/sandbox/src/server/reference.ts +0 -251
  164. package/packages/sandbox/src/server/routes.ts +0 -2258
  165. package/packages/sandbox/src/server/runtime.ts +0 -188
  166. package/packages/sandbox/src/server/session-owner.ts +0 -23
  167. package/packages/sandbox/src/server/sessions.ts +0 -849
  168. package/packages/sandbox/src/server/spec.ts +0 -471
  169. package/packages/sandbox/src/server/turn-lifecycle.ts +0 -56
  170. package/packages/sandbox/src/server/turns.ts +0 -993
  171. package/packages/sandbox/src/server/workspace-install.ts +0 -266
  172. package/packages/sandbox/src/server/workspace-root.ts +0 -73
  173. package/packages/sandbox/src/styles.css +0 -1573
  174. package/packages/sandbox/vite.config.ts +0 -133
@@ -1,464 +0,0 @@
1
- import { spawn } from 'node:child_process';
2
- import { cp, mkdir, readFile, readdir, rm, stat } from 'node:fs/promises';
3
- import { createRequire } from 'node:module';
4
- import { dirname, join, relative } from 'node:path';
5
- import { GATE_IDS, type GateResult } from '../gates.ts';
6
- import { inspectAutoReview } from '../auto-review.ts';
7
- import { modulePathOf, type SessionPaths } from '../sessions.ts';
8
- import { SandboxSetupError } from '../workspace-root.ts';
9
- import type {
10
- DeliveryContext,
11
- DeliveryEmit,
12
- DeliveryModulePlan,
13
- DeliveryStepResult,
14
- } from './types.ts';
15
-
16
- export interface CommandResult {
17
- readonly code: number | null;
18
- readonly output: string;
19
- }
20
-
21
- export interface CommandOptions {
22
- /* Replaces the inherited environment entirely when given. */
23
- readonly env?: NodeJS.ProcessEnv;
24
- }
25
-
26
- export type CommandRunner = (
27
- command: string,
28
- args: readonly string[],
29
- cwd: string,
30
- options?: CommandOptions,
31
- ) => Promise<CommandResult>;
32
-
33
- export interface StepResult {
34
- readonly ok: boolean;
35
- readonly output: string;
36
- /* One line worth showing next to a passed step (a URL, a count). */
37
- readonly detail?: string;
38
- }
39
-
40
- export class DeliveryError extends SandboxSetupError {
41
- constructor(
42
- code: string,
43
- message: string,
44
- readonly output: string = '',
45
- ) {
46
- super(code, message);
47
- this.name = 'DeliveryError';
48
- }
49
- }
50
-
51
- const OUTPUT_LIMIT = 8_000;
52
-
53
- export const spawnCommand: CommandRunner = (command, args, cwd, options) =>
54
- new Promise((resolvePromise) => {
55
- const child = spawn(command, [...args], {
56
- cwd,
57
- env: options?.env ?? { ...process.env, FORCE_COLOR: '0' },
58
- stdio: ['ignore', 'pipe', 'pipe'],
59
- });
60
- let output = '';
61
- const append = (chunk: string) => {
62
- output = (output + chunk).slice(-OUTPUT_LIMIT);
63
- };
64
- child.stdout.setEncoding('utf8');
65
- child.stderr.setEncoding('utf8');
66
- child.stdout.on('data', append);
67
- child.stderr.on('data', append);
68
- child.on('error', (error) =>
69
- resolvePromise({ code: null, output: error.message }),
70
- );
71
- child.on('close', (code) => resolvePromise({ code, output }));
72
- });
73
-
74
- /* Only module sources travel. Dependencies, the reference material, and the
75
- session's own scratch state never enter the workspace. */
76
- const EXCLUDED = new Set(['node_modules', 'dist', '.turbo', '.git']);
77
-
78
- export async function listModuleFiles(
79
- root: string,
80
- directory = root,
81
- ): Promise<string[]> {
82
- const files: string[] = [];
83
- let entries;
84
- try {
85
- entries = await readdir(directory, { withFileTypes: true });
86
- } catch {
87
- return files;
88
- }
89
- for (const entry of entries) {
90
- if (EXCLUDED.has(entry.name)) continue;
91
- if (entry.isSymbolicLink()) continue;
92
- const path = join(directory, entry.name);
93
- if (entry.isDirectory()) files.push(...(await listModuleFiles(root, path)));
94
- else files.push(relative(root, path));
95
- }
96
- return files.sort();
97
- }
98
-
99
- export async function exists(path: string): Promise<boolean> {
100
- try {
101
- await stat(path);
102
- return true;
103
- } catch {
104
- return false;
105
- }
106
- }
107
-
108
- /* What one module's delivery changes in the workspace: every draft file, the
109
- ones that differ from what is there, and the files the session deleted
110
- relative to the copy it started from. */
111
- export async function planModuleFiles(
112
- sourcePath: string,
113
- targetPath: string,
114
- basePath: string | null,
115
- ): Promise<{
116
- readonly files: readonly string[];
117
- readonly additions: readonly string[];
118
- readonly overwrites: readonly string[];
119
- readonly removes: readonly string[];
120
- }> {
121
- const files = await listModuleFiles(sourcePath);
122
- const additions: string[] = [];
123
- const overwrites: string[] = [];
124
- for (const file of files) {
125
- const current = join(targetPath, file);
126
- if (!(await exists(current))) {
127
- additions.push(file);
128
- continue;
129
- }
130
- const [before, after] = await Promise.all([
131
- readFile(current, 'utf8').catch(() => null),
132
- readFile(join(sourcePath, file), 'utf8').catch(() => null),
133
- ]);
134
- if (before !== after) overwrites.push(file);
135
- }
136
- const present = new Set(files);
137
- const removes = basePath
138
- ? (await listModuleFiles(basePath)).filter((file) => !present.has(file))
139
- : [];
140
- return { files, additions, overwrites, removes };
141
- }
142
-
143
- async function resolutionBases(
144
- workspaceRoot: string,
145
- targetPath: string,
146
- ): Promise<readonly string[]> {
147
- const bases = [targetPath];
148
- try {
149
- for (const entry of await readdir(join(workspaceRoot, 'modules'))) {
150
- bases.push(join(workspaceRoot, 'modules', entry));
151
- }
152
- } catch {
153
- /* A workspace with no modules yet resolves from its root only. */
154
- }
155
- bases.push(workspaceRoot);
156
- return bases;
157
- }
158
-
159
- /* A package counts as present when any module of this workspace can already
160
- resolve it, because pnpm links the same store entry for the new module. */
161
- export async function newPackages(
162
- workspaceRoot: string,
163
- modulePath: string,
164
- targetPath: string,
165
- ): Promise<readonly string[]> {
166
- let declared: Record<string, string> = {};
167
- try {
168
- const manifest = JSON.parse(
169
- await readFile(join(modulePath, 'package.json'), 'utf8'),
170
- ) as { dependencies?: Record<string, string> };
171
- declared = manifest.dependencies ?? {};
172
- } catch {
173
- return [];
174
- }
175
- const bases = await resolutionBases(workspaceRoot, targetPath);
176
- const resolvers = bases.map((base) => createRequire(join(base, 'index.js')));
177
- const missing: string[] = [];
178
- for (const [name, range] of Object.entries(declared)) {
179
- if (range.startsWith('workspace:')) continue;
180
- const resolved = resolvers.some((resolver) => {
181
- try {
182
- resolver.resolve(`${name}/package.json`);
183
- return true;
184
- } catch {
185
- try {
186
- resolver.resolve(name);
187
- return true;
188
- } catch {
189
- return false;
190
- }
191
- }
192
- });
193
- if (!resolved) missing.push(`${name}@${range}`);
194
- }
195
- return missing.sort();
196
- }
197
-
198
- /* Gates run one at a time so the screen shows which one is running; a failing
199
- gate stops the delivery before any file is written. */
200
- export async function runDeliveryGates(
201
- context: DeliveryContext,
202
- gates: readonly string[],
203
- emit: DeliveryEmit,
204
- ): Promise<readonly GateResult[]> {
205
- const results: GateResult[] = [];
206
- for (const gate of new Set([...gates, ...GATE_IDS])) {
207
- emit('gate.started', { id: gate });
208
- const checked = await context.runGates([gate]);
209
- const targets =
210
- gate === 'spec-schema' || gate === 'module-schema'
211
- ? [undefined]
212
- : context.session.modules.map((module) => module.directory);
213
- if (
214
- targets.length === 0 ||
215
- targets.some(
216
- (module) =>
217
- !checked.some(
218
- (result) => result.id === gate && result.module === module,
219
- ),
220
- )
221
- ) {
222
- throw new DeliveryError(
223
- 'EJECT_GATES_MISSING',
224
- `No result for required gate ${gate}.`,
225
- );
226
- }
227
- for (const result of checked) {
228
- results.push(result);
229
- emit('gate.completed', result);
230
- }
231
- assertGatesPassed(checked);
232
- }
233
- assertGatesPassed(results);
234
- return results;
235
- }
236
-
237
- /* Copies every module of the plan under `targetRoot`, then removes what the
238
- session deleted, reporting counts as the workspace target always has. */
239
- export async function stageModules(
240
- paths: SessionPaths,
241
- targetRoot: string,
242
- modules: readonly DeliveryModulePlan[],
243
- emit: DeliveryEmit,
244
- ): Promise<{ readonly copied: number; readonly removed: number }> {
245
- for (const module of modules) {
246
- const review = await inspectAutoReview(paths, module);
247
- if (!review.passed)
248
- throw new DeliveryError('EJECT_REVIEW_REQUIRED', review.output);
249
- }
250
- let copied = 0;
251
- let removed = 0;
252
- for (const module of modules) {
253
- const sourcePath = modulePathOf(paths, module.directory);
254
- const targetPath = join(targetRoot, module.targetPath);
255
- emit('copy.started', { module: module.id, files: module.files.length });
256
- copied += (await stageModuleFiles(sourcePath, targetPath, module.files))
257
- .length;
258
- emit('copy.completed', { module: module.id, files: copied });
259
- if (module.removes.length > 0) {
260
- emit('remove.started', {
261
- module: module.id,
262
- files: module.removes.length,
263
- });
264
- removed += (await removeModuleFiles(targetPath, module.removes)).length;
265
- emit('remove.completed', { module: module.id, files: removed });
266
- }
267
- }
268
- return { copied, removed };
269
- }
270
-
271
- export function assertGatesPassed(gates: readonly GateResult[]): void {
272
- const failed = gates.filter((gate) => gate.status !== 'passed');
273
- if (failed.length > 0) {
274
- throw new DeliveryError(
275
- 'EJECT_GATES_FAILED',
276
- `Eject is blocked by failing gates: ${failed
277
- .map((gate) => (gate.module ? `${gate.id} (${gate.module})` : gate.id))
278
- .join(', ')}.`,
279
- failed.map((gate) => gate.output).join('\n\n'),
280
- );
281
- }
282
- }
283
-
284
- export async function stageModuleFiles(
285
- sourcePath: string,
286
- targetPath: string,
287
- files: readonly string[],
288
- ): Promise<readonly string[]> {
289
- for (const file of files) {
290
- const destination = join(targetPath, file);
291
- await mkdir(dirname(destination), { recursive: true });
292
- await cp(join(sourcePath, file), destination);
293
- }
294
- return files;
295
- }
296
-
297
- /* Removes what the session deleted, then the directories that became empty,
298
- so an update never leaves a stale file the draft no longer has. */
299
- export async function removeModuleFiles(
300
- targetPath: string,
301
- files: readonly string[],
302
- ): Promise<readonly string[]> {
303
- for (const file of files) {
304
- await rm(join(targetPath, file), { force: true });
305
- let directory = dirname(join(targetPath, file));
306
- while (directory !== targetPath && directory.startsWith(targetPath)) {
307
- try {
308
- if ((await readdir(directory)).length > 0) break;
309
- await rm(directory, { recursive: true });
310
- } catch {
311
- break;
312
- }
313
- directory = dirname(directory);
314
- }
315
- }
316
- return files;
317
- }
318
-
319
- export function step(
320
- result: CommandResult,
321
- detail?: string,
322
- limit = 2_000,
323
- ): StepResult {
324
- return {
325
- ok: result.code === 0,
326
- output: result.output.slice(-limit),
327
- ...(detail ? { detail } : {}),
328
- };
329
- }
330
-
331
- export interface StepRecorder {
332
- readonly steps: readonly DeliveryStepResult[];
333
- /* Emits `<id>.completed` and throws on the first failing step, so a target
334
- never continues past a failure. */
335
- record(id: string, result: StepResult): void;
336
- }
337
-
338
- export function createStepRecorder(emit: DeliveryEmit): StepRecorder {
339
- const steps: DeliveryStepResult[] = [];
340
- return {
341
- steps,
342
- record(id, result) {
343
- steps.push({ id, ...result });
344
- emit(`${id}.completed`, result);
345
- if (!result.ok) {
346
- throw new DeliveryError(
347
- 'EJECT_STEP_FAILED',
348
- `The ${id} step failed, so the delivery stopped there. ${result.output.trim().split('\n').at(-1) ?? ''}`.trim(),
349
- result.output,
350
- );
351
- }
352
- },
353
- };
354
- }
355
-
356
- /* A worktree starts without node_modules. The store already holds everything
357
- the workspace resolved, so the install is offline first and only reaches the
358
- registry when a module declares something new. */
359
- export async function installWorktree(
360
- run: CommandRunner,
361
- worktreeRoot: string,
362
- ): Promise<StepResult> {
363
- const offline = await run(
364
- 'pnpm',
365
- ['--dir', worktreeRoot, 'install', '--offline'],
366
- worktreeRoot,
367
- );
368
- if (offline.code === 0) return step(offline);
369
- return step(
370
- await run(
371
- 'pnpm',
372
- ['--dir', worktreeRoot, 'install', '--prefer-offline'],
373
- worktreeRoot,
374
- ),
375
- );
376
- }
377
-
378
- /* A copied module brings its own dependencies. Linking them before anything
379
- else runs is what keeps the workspace importable right after an eject. */
380
- export async function installWorkspace(
381
- run: CommandRunner,
382
- workspaceRoot: string,
383
- ): Promise<StepResult> {
384
- return step(
385
- await run(
386
- 'pnpm',
387
- ['--dir', workspaceRoot, 'install', '--silent'],
388
- workspaceRoot,
389
- ),
390
- );
391
- }
392
-
393
- export async function enableModule(
394
- run: CommandRunner,
395
- workspaceRoot: string,
396
- moduleId: string,
397
- ): Promise<StepResult> {
398
- return step(
399
- await run(
400
- 'pnpm',
401
- [
402
- '--dir',
403
- workspaceRoot,
404
- '--silent',
405
- 'flowdular',
406
- 'module',
407
- 'enable',
408
- moduleId,
409
- '--apply',
410
- '--json',
411
- ],
412
- workspaceRoot,
413
- ),
414
- );
415
- }
416
-
417
- /* A module brings its own scopes. Without this step it is installed, enabled,
418
- and invisible, because nobody holds the permission its navigation requires. */
419
- export async function syncModuleScopes(
420
- run: CommandRunner,
421
- workspaceRoot: string,
422
- moduleId: string,
423
- ): Promise<StepResult> {
424
- return step(
425
- await run(
426
- 'pnpm',
427
- [
428
- '--dir',
429
- workspaceRoot,
430
- '--silent',
431
- 'flowdular',
432
- 'auth',
433
- 'sync-scopes',
434
- '--module',
435
- moduleId,
436
- '--apply',
437
- '--json',
438
- ],
439
- workspaceRoot,
440
- ),
441
- );
442
- }
443
-
444
- export async function verifyPlatform(
445
- run: CommandRunner,
446
- workspaceRoot: string,
447
- ): Promise<StepResult> {
448
- return step(
449
- await run(
450
- 'pnpm',
451
- ['--dir', workspaceRoot, '--filter', '@flowdular/platform', 'typecheck'],
452
- workspaceRoot,
453
- ),
454
- );
455
- }
456
-
457
- export async function buildWorkspace(
458
- run: CommandRunner,
459
- workspaceRoot: string,
460
- ): Promise<StepResult> {
461
- return step(
462
- await run('pnpm', ['--dir', workspaceRoot, 'build'], workspaceRoot),
463
- );
464
- }
@@ -1,158 +0,0 @@
1
- import type { GateResult } from '../gates.ts';
2
- import type { SandboxSession } from '../sessions.ts';
3
- import type { DeliveryConfiguration } from './configuration.ts';
4
- import type { CommandRunner } from './steps.ts';
5
-
6
- /* Where a delivery lands. The request names the target; the sandbox resolves
7
- it to the implementation below. 'workspace' writes into this working tree;
8
- 'git-pr' commits the same change on a branch and opens a pull request. */
9
- export type EjectTarget = 'workspace' | 'git-pr' | 'official-modules';
10
-
11
- export type DeliveryTargetId = 'local' | 'git-pr' | 'official-modules';
12
-
13
- export interface DeliveryModulePlan {
14
- readonly id: string;
15
- readonly directory: string;
16
- readonly kind: 'new' | 'edit';
17
- /* Relative to the workspace root. */
18
- readonly targetPath: string;
19
- readonly files: readonly string[];
20
- /* Files the target does not have yet. */
21
- readonly additions: readonly string[];
22
- readonly overwrites: readonly string[];
23
- /* Files present in the base copy the session started from and gone from the
24
- draft. They are removed from the workspace after the copy. */
25
- readonly removes: readonly string[];
26
- /* Packages the module declares that this workspace cannot resolve yet. They
27
- are installed by the eject, and a human sees them before that happens. */
28
- readonly newPackages: readonly string[];
29
- readonly enable: boolean;
30
- }
31
-
32
- export interface DeliveryBudget {
33
- readonly maxChangedFiles: number;
34
- readonly maxNewDependencies: number;
35
- }
36
-
37
- export interface DeliveryGuardrails {
38
- readonly ok: boolean;
39
- readonly reasons: readonly string[];
40
- }
41
-
42
- export type DeliveryProvider = 'github' | 'none';
43
- export type GitDeliveryMode = 'direct' | 'fork';
44
-
45
- export interface GitDeliveryPlan {
46
- readonly remote: string;
47
- /* GitHub owner/name that receives the pull request, when known. */
48
- readonly repository: string | null;
49
- readonly baseBranch: string;
50
- readonly branch: string;
51
- readonly deliveryMode: GitDeliveryMode;
52
- /* Human-safe destination. A credential-bearing URL is never exposed. */
53
- readonly pushTarget: string;
54
- readonly forkOwner: string | null;
55
- readonly forkRequired: boolean;
56
- /* Pull request head, branch for direct delivery or owner:branch for a fork. */
57
- readonly head: string;
58
- /* Relative to the workspace root. Created for the delivery and removed when
59
- it ends, whatever the outcome. */
60
- readonly worktreePath: string;
61
- /* Globs the commit may touch. Anything else left in the worktree fails the
62
- guardrail step before a commit exists. */
63
- readonly allowedPaths: readonly string[];
64
- readonly budget: DeliveryBudget;
65
- /* Estimated from the session; the guardrail step counts the real worktree. */
66
- readonly changedFiles: number;
67
- readonly newDependencies: readonly string[];
68
- readonly owners: readonly string[];
69
- readonly requireReviewer: boolean;
70
- readonly reviewers: readonly string[];
71
- readonly guardrails: DeliveryGuardrails;
72
- readonly provider: DeliveryProvider;
73
- readonly providerNote: string;
74
- readonly compareUrl: string | null;
75
- }
76
-
77
- export interface DeliveryPlan {
78
- readonly target: EjectTarget;
79
- readonly deliveredBy: DeliveryTargetId;
80
- /* The primary module, repeated at the top level for the current screen. */
81
- readonly moduleId: string;
82
- readonly targetPath: string;
83
- readonly files: readonly string[];
84
- readonly overwrites: readonly string[];
85
- readonly removes: readonly string[];
86
- readonly newPackages: readonly string[];
87
- readonly enable: boolean;
88
- /* Every module of the session, primary first. One delivery applies all. */
89
- readonly modules: readonly DeliveryModulePlan[];
90
- /* Files created, overwritten, or removed across every module. */
91
- readonly changedFiles: number;
92
- readonly gates: readonly string[];
93
- /* True when the connected application runs on this machine, so the
94
- delivery lands in the workspace that application is serving. */
95
- readonly platformLocal: boolean;
96
- readonly restartRequired: boolean;
97
- readonly notes: readonly string[];
98
- readonly git?: GitDeliveryPlan;
99
- readonly applied: false;
100
- }
101
-
102
- export interface DeliveryStepResult {
103
- readonly id: string;
104
- readonly ok: boolean;
105
- readonly output: string;
106
- readonly detail?: string;
107
- }
108
-
109
- export type DeliveryEmit = (event: string, payload: unknown) => void;
110
-
111
- export interface DeliveryContext {
112
- readonly workspaceRoot: string;
113
- readonly session: SandboxSession;
114
- /* Capabilities of the acting principal, never of the stored connection. */
115
- readonly capabilities: readonly string[];
116
- readonly platformUrl: string;
117
- readonly build?: boolean;
118
- /* The sandbox.delivery block of flowdular.json; defaults when absent. */
119
- readonly delivery?: DeliveryConfiguration;
120
- /* Opens the sealed pull-request provider token, if one is configured. */
121
- readonly gitProviderToken?: () => Promise<string | null>;
122
- runGates(gates: readonly string[]): Promise<readonly GateResult[]>;
123
- readonly commands: CommandRunner;
124
- }
125
-
126
- export interface DeliveryOutcome {
127
- readonly moduleId: string;
128
- readonly targetPath: string;
129
- readonly files: number;
130
- readonly removed: number;
131
- readonly enabled: boolean;
132
- readonly gates: readonly GateResult[];
133
- readonly steps: readonly DeliveryStepResult[];
134
- readonly restartRequired: boolean;
135
- readonly branch?: string;
136
- readonly pullRequestUrl?: string | null;
137
- readonly compareUrl?: string | null;
138
- }
139
-
140
- export interface DeliveryAvailability {
141
- readonly available: boolean;
142
- readonly reason: string | null;
143
- }
144
-
145
- /* A delivery target plans before it writes and streams every step while it
146
- applies. plan() never touches the workspace; apply() stops at the first
147
- failing step and throws, so a session is marked delivered only when every
148
- step passed. */
149
- export interface DeliveryTarget {
150
- readonly id: DeliveryTargetId;
151
- available(context: DeliveryContext): Promise<DeliveryAvailability>;
152
- plan(context: DeliveryContext): Promise<DeliveryPlan>;
153
- apply(
154
- context: DeliveryContext,
155
- plan: DeliveryPlan,
156
- emit: DeliveryEmit,
157
- ): Promise<DeliveryOutcome>;
158
- }
@@ -1,88 +0,0 @@
1
- import { readFile, readdir } from 'node:fs/promises';
2
- import { isBuiltin } from 'node:module';
3
- import { join } from 'node:path';
4
-
5
- const SOURCE = /\.(ts|tsx|tsrx|mts|cts)$/;
6
- const IMPORT =
7
- /(?:^|\n)\s*(?:import|export)[\s\S]*?from\s*['"]([^'"]+)['"]|import\(\s*['"]([^'"]+)['"]\s*\)/g;
8
- const IGNORED_DIRECTORIES = new Set(['node_modules', 'dist', 'tests']);
9
-
10
- export interface DependencyReport {
11
- readonly declared: readonly string[];
12
- readonly imported: readonly string[];
13
- readonly missing: readonly string[];
14
- }
15
-
16
- function packageOf(specifier: string): string | null {
17
- if (specifier.startsWith('.') || specifier.startsWith('/')) return null;
18
- if (isBuiltin(specifier)) return null;
19
- const parts = specifier.split('/');
20
- return specifier.startsWith('@')
21
- ? `${parts[0]}/${parts[1] ?? ''}`
22
- : (parts[0] ?? null);
23
- }
24
-
25
- async function collectImports(
26
- directory: string,
27
- found: Set<string>,
28
- ): Promise<void> {
29
- let entries;
30
- try {
31
- entries = await readdir(directory, { withFileTypes: true });
32
- } catch {
33
- return;
34
- }
35
- for (const entry of entries) {
36
- if (IGNORED_DIRECTORIES.has(entry.name)) continue;
37
- const path = join(directory, entry.name);
38
- if (entry.isDirectory()) {
39
- await collectImports(path, found);
40
- continue;
41
- }
42
- if (!SOURCE.test(entry.name)) continue;
43
- const source = await readFile(path, 'utf8');
44
- IMPORT.lastIndex = 0;
45
- for (;;) {
46
- const match = IMPORT.exec(source);
47
- if (!match) break;
48
- const name = packageOf(match[1] ?? match[2] ?? '');
49
- if (name) found.add(name);
50
- }
51
- }
52
- }
53
-
54
- /* A module that imports a package it does not declare typechecks inside the
55
- sandbox, because the session borrows a sibling module's dependency tree, and
56
- then fails the moment it is ejected. The gate compares what the sources
57
- import with what the manifest promises. */
58
- export async function checkDeclaredDependencies(
59
- modulePath: string,
60
- ): Promise<DependencyReport> {
61
- const imported = new Set<string>();
62
- await collectImports(join(modulePath, 'src'), imported);
63
-
64
- let declared: readonly string[] = [];
65
- try {
66
- const manifest = JSON.parse(
67
- await readFile(join(modulePath, 'package.json'), 'utf8'),
68
- ) as {
69
- dependencies?: Record<string, string>;
70
- devDependencies?: Record<string, string>;
71
- peerDependencies?: Record<string, string>;
72
- };
73
- declared = [
74
- ...Object.keys(manifest.dependencies ?? {}),
75
- ...Object.keys(manifest.devDependencies ?? {}),
76
- ...Object.keys(manifest.peerDependencies ?? {}),
77
- ];
78
- } catch {
79
- declared = [];
80
- }
81
-
82
- const known = new Set(declared);
83
- return {
84
- declared: [...known].sort(),
85
- imported: [...imported].sort(),
86
- missing: [...imported].filter((name) => !known.has(name)).sort(),
87
- };
88
- }