@flowdular/sdk 0.1.0 → 0.2.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/.ai/skills/test-hardening/SKILL.md +1 -1
- package/README.md +69 -12
- package/assets/flowdular-banner.png +0 -0
- package/modules/agents/package.json +1 -1
- package/modules/auth/package.json +1 -1
- package/modules/auth/src/client/AuthenticationCore.tsrx +3 -1
- package/modules/auth/src/server/endpoints.ts +3 -1
- package/modules/auth/src/server/runtime.ts +4 -0
- package/modules/automations/package.json +1 -1
- package/modules/automations-workflows-integration/package.json +1 -1
- package/modules/profile/package.json +1 -1
- package/modules/sandbox/package.json +1 -1
- package/modules/system/package.json +1 -1
- package/modules/users/package.json +1 -1
- package/modules/workflows/package.json +1 -1
- package/package.json +5 -13
- package/packages/ai-provider/package.json +1 -1
- package/packages/cli-protocol/package.json +1 -1
- package/packages/client/package.json +1 -1
- package/packages/client/src/routing.ts +39 -2
- package/packages/client/src/shell/BrandMark.tsrx +5 -1
- package/packages/client/src/shell/navigation.ts +3 -1
- package/packages/client/src/state.ts +6 -2
- package/packages/contracts/package.json +1 -1
- package/packages/contracts/schemas/project.schema.json +16 -42
- package/packages/database/package.json +1 -1
- package/packages/database-pglite/package.json +2 -2
- package/packages/database-pglite/src/driver.ts +21 -3
- package/packages/database-testing/package.json +1 -1
- package/packages/dev-console/package.json +1 -1
- package/packages/harness/package.json +1 -1
- package/packages/kernel/package.json +1 -1
- package/packages/server/package.json +1 -1
- package/packages/server/src/application-routes.ts +80 -8
- package/packages/server/src/index.ts +4 -1
- package/packages/server/src/web-html.ts +9 -5
- package/packages/server/src/web.ts +59 -7
- package/packages/ui/package.json +1 -1
- package/packages/coding-agent/package.json +0 -44
- package/packages/coding-agent/src/drivers/byok.ts +0 -322
- package/packages/coding-agent/src/drivers/claude-code.ts +0 -290
- package/packages/coding-agent/src/drivers/codex.ts +0 -281
- package/packages/coding-agent/src/index.ts +0 -56
- package/packages/coding-agent/src/registry.ts +0 -119
- package/packages/coding-agent/src/roles/contract.ts +0 -60
- package/packages/coding-agent/src/roles/defaults.ts +0 -134
- package/packages/coding-agent/src/roles/handoff.ts +0 -41
- package/packages/coding-agent/src/roles/registry.ts +0 -156
- package/packages/coding-agent/src/roles/skills.ts +0 -72
- package/packages/coding-agent/src/roles/sync.ts +0 -88
- package/packages/coding-agent/src/types.ts +0 -102
- package/packages/coding-agent/src/workspace.ts +0 -275
- package/packages/sandbox/README.md +0 -691
- package/packages/sandbox/bin/flowdular-sandbox.mjs +0 -393
- package/packages/sandbox/dist/client/assets/App-ClMOrpyg.js +0 -11
- package/packages/sandbox/dist/client/assets/PreviewHost-BHiHrfbQ.js +0 -1
- package/packages/sandbox/dist/client/assets/auto-buGEq6de.js +0 -3
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-500-normal-Ael50iVv.woff +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-500-normal-Bq9vWWag.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-500-normal-BIfNGwUT.woff +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-500-normal-BqneJy0T.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-500-normal-CAhNIIs5.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-500-normal-CZ70TYgx.woff +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-500-normal-C8zxqsMH.woff +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-500-normal-DZ4AoWbu.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-sans-cyrillic-ext-wght-normal-d45eAU9y.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-sans-cyrillic-wght-normal-BAAhND-U.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-sans-greek-wght-normal-CmyJS8uq.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-sans-latin-ext-wght-normal-CIII54If.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-sans-latin-wght-normal-IvpUvPa2.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-sans-vietnamese-wght-normal-Dg1JeJN0.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/index-CvwhlQ87.js +0 -4
- package/packages/sandbox/dist/client/assets/styles-BPWaGf55.js +0 -3
- package/packages/sandbox/dist/client/assets/styles-Dx1_KOag.css +0 -1
- package/packages/sandbox/dist/client/favicon.svg +0 -1
- package/packages/sandbox/dist/server/assets/auto-CVGNYyb3.js +0 -12785
- package/packages/sandbox/dist/server/assets/chunk-DDJLRBDX-3OUNo51g.js +0 -4125
- package/packages/sandbox/dist/server/assets/nodefs-DVSI7kG0.js +0 -24
- package/packages/sandbox/dist/server/assets/opfs-ahp-D56iokt1.js +0 -388
- package/packages/sandbox/dist/server/assets/token-Bvaq7Anz.js +0 -55
- package/packages/sandbox/dist/server/assets/token-util-BqSvdIPQ.js +0 -3
- package/packages/sandbox/dist/server/assets/token-util-CktFFT4z.js +0 -494
- package/packages/sandbox/dist/server/entry.js +0 -91671
- package/packages/sandbox/dist/server/favicon.svg +0 -1
- package/packages/sandbox/dist/server/index.html +0 -161
- package/packages/sandbox/index.html +0 -159
- package/packages/sandbox/octane.config.ts +0 -36
- package/packages/sandbox/package.json +0 -83
- package/packages/sandbox/public/favicon.svg +0 -1
- package/packages/sandbox/public/og.png +0 -0
- package/packages/sandbox/src/App.tsrx +0 -924
- package/packages/sandbox/src/client/ApprovalHandoff.tsrx +0 -39
- package/packages/sandbox/src/client/ChatPane.tsrx +0 -694
- package/packages/sandbox/src/client/ConnectView.tsrx +0 -86
- package/packages/sandbox/src/client/DiffPane.tsrx +0 -150
- package/packages/sandbox/src/client/EjectModal.tsrx +0 -571
- package/packages/sandbox/src/client/GateResults.tsrx +0 -60
- package/packages/sandbox/src/client/GitHubSettingsModal.tsrx +0 -219
- package/packages/sandbox/src/client/NewSessionForm.tsrx +0 -214
- package/packages/sandbox/src/client/SessionBar.tsrx +0 -151
- package/packages/sandbox/src/client/SessionPicker.tsrx +0 -472
- package/packages/sandbox/src/client/SpecEditor.tsrx +0 -109
- package/packages/sandbox/src/client/SpecReviewCard.tsrx +0 -321
- package/packages/sandbox/src/client/WorkCard.tsrx +0 -128
- package/packages/sandbox/src/client/WorkbenchModal.tsrx +0 -161
- package/packages/sandbox/src/client/WorkspaceMenu.tsrx +0 -100
- package/packages/sandbox/src/client/api.ts +0 -797
- package/packages/sandbox/src/client/attachments.ts +0 -73
- package/packages/sandbox/src/client/dashboard.ts +0 -46
- package/packages/sandbox/src/client/github-settings.ts +0 -48
- package/packages/sandbox/src/client/highlight.ts +0 -105
- package/packages/sandbox/src/client/i18n.ts +0 -32
- package/packages/sandbox/src/client/locales/en.json +0 -466
- package/packages/sandbox/src/client/locales/pl.json +0 -466
- package/packages/sandbox/src/client/session-labels.ts +0 -77
- package/packages/sandbox/src/client/state.ts +0 -108
- package/packages/sandbox/src/index.ts +0 -10
- package/packages/sandbox/src/preview/PreviewHost.tsrx +0 -373
- package/packages/sandbox/src/preview/load-module.ts +0 -16
- package/packages/sandbox/src/server/attachments.ts +0 -308
- package/packages/sandbox/src/server/auto-review.ts +0 -181
- package/packages/sandbox/src/server/checkpoints.ts +0 -90
- package/packages/sandbox/src/server/config.ts +0 -460
- package/packages/sandbox/src/server/dashboard.ts +0 -245
- package/packages/sandbox/src/server/delivery/configuration.ts +0 -193
- package/packages/sandbox/src/server/delivery/git-pr.ts +0 -1473
- package/packages/sandbox/src/server/delivery/index.ts +0 -83
- package/packages/sandbox/src/server/delivery/local.ts +0 -151
- package/packages/sandbox/src/server/delivery/official-modules.ts +0 -516
- package/packages/sandbox/src/server/delivery/plan.ts +0 -218
- package/packages/sandbox/src/server/delivery/policies.ts +0 -239
- package/packages/sandbox/src/server/delivery/record.ts +0 -41
- package/packages/sandbox/src/server/delivery/steps.ts +0 -464
- package/packages/sandbox/src/server/delivery/types.ts +0 -158
- package/packages/sandbox/src/server/dependencies.ts +0 -88
- package/packages/sandbox/src/server/diff.ts +0 -230
- package/packages/sandbox/src/server/gates.ts +0 -388
- package/packages/sandbox/src/server/index.ts +0 -139
- package/packages/sandbox/src/server/path-guard.ts +0 -282
- package/packages/sandbox/src/server/planning.ts +0 -749
- package/packages/sandbox/src/server/platform-client.ts +0 -189
- package/packages/sandbox/src/server/preview-database-host.ts +0 -340
- package/packages/sandbox/src/server/preview-database-protocol.ts +0 -163
- package/packages/sandbox/src/server/preview-database-proxy.ts +0 -356
- package/packages/sandbox/src/server/preview-database.ts +0 -109
- package/packages/sandbox/src/server/preview-ipc.ts +0 -15
- package/packages/sandbox/src/server/preview-modules.ts +0 -83
- package/packages/sandbox/src/server/preview-revision.ts +0 -44
- package/packages/sandbox/src/server/preview-runtime.ts +0 -467
- package/packages/sandbox/src/server/preview-worker-manager.ts +0 -436
- package/packages/sandbox/src/server/preview-worker.ts +0 -168
- package/packages/sandbox/src/server/reference.ts +0 -251
- package/packages/sandbox/src/server/routes.ts +0 -2258
- package/packages/sandbox/src/server/runtime.ts +0 -188
- package/packages/sandbox/src/server/session-owner.ts +0 -23
- package/packages/sandbox/src/server/sessions.ts +0 -849
- package/packages/sandbox/src/server/spec.ts +0 -471
- package/packages/sandbox/src/server/turn-lifecycle.ts +0 -56
- package/packages/sandbox/src/server/turns.ts +0 -993
- package/packages/sandbox/src/server/workspace-install.ts +0 -266
- package/packages/sandbox/src/server/workspace-root.ts +0 -73
- package/packages/sandbox/src/styles.css +0 -1573
- 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
|
-
}
|