proteum 2.2.2-1 → 2.2.3
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/AGENTS.md +2 -2
- package/README.md +4 -1
- package/agents/project/AGENTS.md +3 -3
- package/agents/project/diagnostics.md +3 -3
- package/agents/project/root/AGENTS.md +3 -3
- package/agents/project/tests/AGENTS.md +2 -2
- package/cli/app/index.ts +19 -9
- package/cli/commands/check.ts +7 -3
- package/cli/commands/configure.ts +14 -9
- package/cli/commands/e2e.ts +204 -0
- package/cli/commands/typecheck.ts +7 -3
- package/cli/presentation/commands.ts +37 -7
- package/cli/runtime/command.ts +2 -2
- package/cli/runtime/commands.ts +59 -0
- package/cli/scaffold/index.ts +1 -1
- package/cli/utils/agents.ts +175 -80
- package/cli/utils/check.ts +32 -4
- package/docs/dev-sessions.md +11 -2
- package/docs/diagnostics.md +2 -1
- package/package.json +1 -1
- package/scripts/update-codex-agents.ts +2 -2
package/AGENTS.md
CHANGED
|
@@ -82,8 +82,8 @@ Do not stop at static analysis for routing, controllers, generated code, SSR, cl
|
|
|
82
82
|
- When validating a concrete route, controller path, or failing page on a running dev server, prefer `proteum diagnose <path> --port <port>` first. Use raw `proteum trace ...` output when you need lower-level event detail beyond the diagnose summary.
|
|
83
83
|
- When the issue is latency, CPU, SQL cost, render cost, or memory drift, inspect `proteum perf top`, `proteum perf request`, `proteum perf compare`, or `proteum perf memory` against the running dev server before adding custom instrumentation.
|
|
84
84
|
- When a framework change can affect shipped client code size, run `proteum build --prod --analyze` for static bundle artifacts or `proteum build --prod --analyze --analyze-serve --analyze-port auto` when you need a local analyzer URL.
|
|
85
|
-
- For protected browser or API flows in dev, prefer `npx proteum session <email> --role <role>`
|
|
86
|
-
- When a task needs browser execution instead of the higher-level verifier, prefer `npx proteum verify browser <path>` or
|
|
85
|
+
- For protected browser or API flows in dev, prefer `npx proteum session <email> --role <role>` or `npx proteum e2e --session-email <email> --session-role <role>` instead of automating the login UI. Use the login UI only when login itself is the feature under test.
|
|
86
|
+
- When a task needs browser execution instead of the higher-level verifier, prefer `npx proteum verify browser <path>` or `npx proteum e2e --port <port>` for Playwright suites. Keep auth sourced from Proteum session helpers, not UI login or shared browser state.
|
|
87
87
|
- For request-time behavior, arm traces with `proteum trace arm --capture deep`, reproduce once, then inspect `proteum trace latest` or `proteum trace show <requestId>`.
|
|
88
88
|
- When the framework-facing workflow itself changed, verify the CLI surface too with `proteum verify framework-change --crosspath-port <port> --product-port <port> --website-port <port>`.
|
|
89
89
|
- Only the final verifier agent should usually run browser flows. Other agents should stay on `orient`, `verify owner`, `verify request`, and command-level checks unless browser execution is the only trustworthy surface.
|
package/README.md
CHANGED
|
@@ -344,6 +344,7 @@ Proteum ships with a compact CLI focused on the real app lifecycle:
|
|
|
344
344
|
| `proteum trace` | Inspect live dev-only request traces from the running SSR server |
|
|
345
345
|
| `proteum command` | Run a dev-only internal command locally or against a running dev server |
|
|
346
346
|
| `proteum session` | Mint a dev-only auth session token and Playwright-ready cookie payload |
|
|
347
|
+
| `proteum e2e` | Run Playwright with Proteum-managed `E2E_*` values instead of shell-leading env assignments |
|
|
347
348
|
| `proteum verify` | Validate framework-facing workflows across one or more running dev apps; `framework-change` is the built-in cross-reference-app check |
|
|
348
349
|
| `proteum init` | Scaffold a new Proteum app with built-in deterministic templates |
|
|
349
350
|
| `proteum configure agents` | Interactively configure Proteum-managed instruction symlinks and confirm overwrites for standalone or monorepo apps |
|
|
@@ -386,6 +387,7 @@ proteum command proteum/diagnostics/ping
|
|
|
386
387
|
proteum command proteum/diagnostics/ping --port 3101
|
|
387
388
|
proteum session admin@example.com --role ADMIN --port 3101
|
|
388
389
|
proteum session god@example.com --role GOD --json
|
|
390
|
+
proteum e2e --port 3101 --session-email admin@example.com --session-role ADMIN tests/e2e/features/admin.spec.ts
|
|
389
391
|
proteum trace requests
|
|
390
392
|
proteum trace arm --capture deep
|
|
391
393
|
proteum trace latest
|
|
@@ -523,6 +525,7 @@ Proteum answers those questions with explicit artifacts:
|
|
|
523
525
|
- the profiler `Explain`, `Doctor`, `Diagnose`, and `Perf` tabs for a human-readable view over the same diagnostics and trace-derived perf contracts
|
|
524
526
|
- `proteum command ...` plus the profiler `Commands` tab for dev-only internal execution
|
|
525
527
|
- `proteum session ...` for explicit authenticated dev browser or API bootstrapping without login UI automation
|
|
528
|
+
- `proteum e2e ...` for Playwright runs that need `E2E_BASE_URL`, `E2E_PORT`, or `E2E_AUTH_TOKEN` without shell-leading env assignments
|
|
526
529
|
|
|
527
530
|
If you are an LLM or automation agent, start here:
|
|
528
531
|
|
|
@@ -533,7 +536,7 @@ If you are an LLM or automation agent, start here:
|
|
|
533
536
|
5. Inspect `server/controllers/**` for request entrypoints.
|
|
534
537
|
6. Inspect `server/services/**` for business logic.
|
|
535
538
|
7. Inspect `client/pages/**` for SSR routes and page data contracts.
|
|
536
|
-
8. If the task touches a protected route or controller in dev and login UX is not the feature under test, use `proteum session <email> --role <role>`
|
|
539
|
+
8. If the task touches a protected route or controller in dev and login UX is not the feature under test, use `proteum e2e --session-email <email> --session-role <role>` for Playwright suites or `proteum session <email> --role <role>` before direct HTTP calls.
|
|
537
540
|
|
|
538
541
|
For implementation rules in a real Proteum app, treat the local `AGENTS.md` files plus `proteum explain`, `proteum doctor`, `proteum diagnose`, `proteum perf`, and `proteum trace` as the task contract. This README is the framework overview, not the project-local instruction layer.
|
|
539
542
|
|
package/agents/project/AGENTS.md
CHANGED
|
@@ -56,7 +56,7 @@ Coding style source of truth: root-level `CODING_STYLE.md`.
|
|
|
56
56
|
- When starting a long-lived dev server for an agent task, always request elevated permissions and run `npx proteum dev` outside the sandbox. Use an explicit task/thread-scoped session file such as `var/run/proteum/dev/agents/<task>.json`, inspect `npx proteum dev list --json` plus current listeners first, for example with `lsof -nP -iTCP -sTCP:LISTEN`, then choose a port that is not currently used before starting `npx proteum dev --session-file <path> --port <port>`. After the server is ready, print the live server URL as a clickable Markdown link.
|
|
57
57
|
- Use `--replace-existing` only when restarting the exact session file started by the current thread/task. Never replace another live session that belongs to a user, another thread, or an unknown owner.
|
|
58
58
|
- If the current app depends on local `file:` connected projects, boot every connected producer app too, each with its own task-scoped session file and free port, and run every one of those `proteum dev` processes with elevated permissions outside the sandbox before starting or verifying the consumer app.
|
|
59
|
-
- For raw browser automation, use `npx proteum verify browser` when it matches the task, or
|
|
59
|
+
- For raw browser automation, use `npx proteum verify browser` when it matches the task, or `npx proteum e2e --port <port>` for targeted/full Playwright suites. Bootstrap protected browser state through `npx proteum e2e --session-email <email> --session-role <role>` or `npx proteum session`.
|
|
60
60
|
- Current CLI banner contract: only the bare `proteum build` and bare `proteum dev` commands print the welcome banner and include the active Proteum installation method. Any extra argument or option skips the banner. Only `proteum dev` clears the interactive terminal before rendering, exposes `CTRL+R` reload plus `CTRL+C` shutdown hotkeys in its session UI, and reports connected app names plus successful connected `/ping` checks in the ready banner. When the app root is missing `AGENTS.md`, the bare interactive `proteum dev` start offers to launch `proteum configure agents` before the dev loop begins.
|
|
61
61
|
|
|
62
62
|
### Before Finishing
|
|
@@ -179,8 +179,8 @@ Verify at the correct layer:
|
|
|
179
179
|
- Router or plugin changes: verify request context, auth, redirects, metrics, and validation on a running app.
|
|
180
180
|
- New features or feature-behavior changes: use the cheapest trustworthy verification while iterating, then update the relevant end-to-end coverage and finish by running the full Playwright suite.
|
|
181
181
|
- Generated, connected, or ownership-ambiguous changes: start with `npx proteum orient <query>` and prefer `npx proteum verify owner <query>` before broad global checks.
|
|
182
|
-
- Browser-visible issues: prefer `npx proteum verify browser <path>` or the narrowest
|
|
183
|
-
- Raw browser execution beyond `npx proteum verify browser`: use direct Playwright with a disposable profile
|
|
182
|
+
- Browser-visible issues: prefer `npx proteum verify browser <path>` or the narrowest `npx proteum e2e --port <port> ...` Playwright pass only after request-level verification is insufficient.
|
|
183
|
+
- Raw browser execution beyond `npx proteum verify browser`: use `npx proteum e2e --port <port>` first, then direct Playwright with a disposable profile only when the wrapper cannot express the needed control. Keep that step for the final verifier agent unless a narrower surface cannot reproduce the issue.
|
|
184
184
|
- For trace-first reproduction, session-based auth setup, temporary logs, and post-fix surface checks, follow root-level `diagnostics.md`.
|
|
185
185
|
|
|
186
186
|
## Implementation Rules
|
|
@@ -17,7 +17,7 @@ This file is the canonical source of truth for diagnostics, temporary instrument
|
|
|
17
17
|
- Only the bare `npx proteum build` and bare `npx proteum dev` commands print the welcome banner and active Proteum installation method. Any extra argument or option skips the banner. Only `npx proteum dev` clears the interactive terminal before rendering and reports connected app names plus successful connected `/ping` checks in the ready banner; keep that in mind when capturing or comparing command logs during diagnosis. When the app root is missing `AGENTS.md`, the bare interactive `npx proteum dev` start offers to launch `npx proteum configure agents` before the dev loop begins.
|
|
18
18
|
- For ownership or repo discovery questions, start with `npx proteum orient <query>` instead of jumping straight into source searches.
|
|
19
19
|
- For request-time issues in dev, start with `npx proteum diagnose <path> --port <port>` when you have a concrete failing route, page, controller path, or request target. It combines owner lookup, manifest diagnostics, contract diagnostics, matching trace data, and buffered server logs in one pass.
|
|
20
|
-
- Prefer focused verification before global checks: `npx proteum verify owner <query>`, `npx proteum verify request <path>`, and only then `npx proteum verify browser <path>` or
|
|
20
|
+
- Prefer focused verification before global checks: `npx proteum verify owner <query>`, `npx proteum verify request <path>`, and only then `npx proteum verify browser <path>` or `npx proteum e2e --port <port> ...` when the bug is browser-visible.
|
|
21
21
|
- When diagnosing a consumer app that depends on local `file:` connected projects, boot every connected producer app too, each on its own free port and task-scoped session file, and run every one of those `proteum dev` processes with elevated permissions outside the sandbox before reproducing the consumer issue.
|
|
22
22
|
- For connected-project failures, confirm the consumer app resolves the expected `connect.<Namespace>.source` and `connect.<Namespace>.urlInternal` values, the producer app exposes `GET /api/__proteum/connected/ping`, and the imported controller entries show `scope=connected` in `proteum explain`.
|
|
23
23
|
- Use `npx proteum explain owner <query>` when you need a fast ownership graph for a route, controller path, source file, or generated artifact before reading code.
|
|
@@ -28,7 +28,7 @@ This file is the canonical source of truth for diagnostics, temporary instrument
|
|
|
28
28
|
- If existing traces are insufficient, arm `npx proteum trace arm --capture deep`, reproduce once, then inspect the new request with `npx proteum trace latest` or `npx proteum trace show <requestId>`.
|
|
29
29
|
- Inspect browser console errors and warnings for frontend, SSR, hydration, and controller-call issues.
|
|
30
30
|
- Inspect server startup and runtime errors.
|
|
31
|
-
- For protected browser or API flows in dev, prefer `npx proteum session <email> --role <role>` over driving the login UI. Feed that auth into `npx proteum verify browser
|
|
31
|
+
- For protected browser or API flows in dev, prefer `npx proteum session <email> --role <role>` over driving the login UI. Feed that auth into `npx proteum verify browser ...`, or use `npx proteum e2e --session-email <email> --session-role <role>` so Playwright receives the auth token through the child process environment. Use the login UI only when auth UX itself is under test.
|
|
32
32
|
|
|
33
33
|
## Temporary Instrumentation
|
|
34
34
|
|
|
@@ -53,7 +53,7 @@ This file is the canonical source of truth for diagnostics, temporary instrument
|
|
|
53
53
|
- For compile-time or type-safety issues, start with the relevant targeted typecheck or build command. Do not run them by default for unrelated runtime, copy, docs, or local refactor changes.
|
|
54
54
|
- For request/runtime issues, verify through the real page, route, generated controller call, or command on a running app.
|
|
55
55
|
- Start the smallest trustworthy runtime surface first: `npx proteum orient <query>`, then the relevant real URL, generated controller call, command, or `npx proteum diagnose <path> --port <port>`. Add targeted Playwright coverage only when request-level verification is insufficient or the change is browser-visible.
|
|
56
|
-
-
|
|
56
|
+
- When `npx proteum verify browser` is insufficient, use `npx proteum e2e --port <port>` for targeted or full Playwright suites. Use direct Playwright with a disposable profile only when the wrapper cannot express the needed browser control. Do not launch raw browser automation against a shared persistent profile.
|
|
57
57
|
- Focused verification should treat unrelated global diagnostics as visible but non-blocking by default. Use `--strict-global` only when the task explicitly requires broad clean-room validation.
|
|
58
58
|
- For browser regressions, prefer a real browser repro first and add targeted Playwright coverage only when the user asks for automated coverage, when a stable regression path needs automation, or when manual/browser verification is insufficient.
|
|
59
59
|
- Only the final verifier agent should usually run browser flows. Earlier agents should stay on `orient`, `verify owner`, `verify request`, `diagnose`, and command-level checks unless browser execution is the only trustworthy reproducer.
|
|
@@ -46,7 +46,7 @@ Coding style source of truth: root-level `CODING_STYLE.md`.
|
|
|
46
46
|
- When starting a long-lived dev server for an agent task, always request elevated permissions and run `npx proteum dev` outside the sandbox. Use an explicit task/thread-scoped session file such as `var/run/proteum/dev/agents/<task>.json`, inspect `npx proteum dev list --json` plus current listeners first, for example with `lsof -nP -iTCP -sTCP:LISTEN`, then choose a port that is not currently used before starting `npx proteum dev --session-file <path> --port <port>`. After the server is ready, print the live server URL as a clickable Markdown link.
|
|
47
47
|
- Use `--replace-existing` only when restarting the exact session file started by the current thread/task. Never replace another live session that belongs to a user, another thread, or an unknown owner.
|
|
48
48
|
- If the current app depends on local `file:` connected projects, boot every connected producer app too, each with its own task-scoped session file and free port, and run every one of those `proteum dev` processes with elevated permissions outside the sandbox before starting or verifying the consumer app.
|
|
49
|
-
- For raw browser automation, use `npx proteum verify browser` when it matches the task, or
|
|
49
|
+
- For raw browser automation, use `npx proteum verify browser` when it matches the task, or `npx proteum e2e --port <port>` for targeted/full Playwright suites. Bootstrap protected browser state through `npx proteum e2e --session-email <email> --session-role <role>` or `npx proteum session`.
|
|
50
50
|
- Current CLI banner contract: only the bare `proteum build` and bare `proteum dev` commands print the welcome banner and include the active Proteum installation method. Any extra argument or option skips the banner. Only `proteum dev` clears the interactive terminal before rendering, exposes `CTRL+R` reload plus `CTRL+C` shutdown hotkeys in its session UI, and reports connected app names plus successful connected `/ping` checks in the ready banner. When the app root is missing `AGENTS.md`, the bare interactive `proteum dev` start offers to launch `proteum configure agents` before the dev loop begins.
|
|
51
51
|
|
|
52
52
|
### Before Finishing
|
|
@@ -169,8 +169,8 @@ Verify at the correct layer:
|
|
|
169
169
|
- Router or plugin changes: verify request context, auth, redirects, metrics, and validation on a running app.
|
|
170
170
|
- New features or feature-behavior changes: use the cheapest trustworthy verification while iterating, then update the relevant end-to-end coverage and finish by running the full Playwright suite.
|
|
171
171
|
- Generated, connected, or ownership-ambiguous changes: start with `npx proteum orient <query>` and prefer `npx proteum verify owner <query>` before broad global checks.
|
|
172
|
-
- Browser-visible issues: prefer `npx proteum verify browser <path>` or the narrowest
|
|
173
|
-
- Raw browser execution beyond `npx proteum verify browser`: use direct Playwright with a disposable profile
|
|
172
|
+
- Browser-visible issues: prefer `npx proteum verify browser <path>` or the narrowest `npx proteum e2e --port <port> ...` Playwright pass only after request-level verification is insufficient.
|
|
173
|
+
- Raw browser execution beyond `npx proteum verify browser`: use `npx proteum e2e --port <port>` first, then direct Playwright with a disposable profile only when the wrapper cannot express the needed control. Keep that step for the final verifier agent unless a narrower surface cannot reproduce the issue.
|
|
174
174
|
- For trace-first reproduction, session-based auth setup, temporary logs, and post-fix surface checks, follow root-level `diagnostics.md`.
|
|
175
175
|
|
|
176
176
|
## Implementation Rules
|
|
@@ -10,7 +10,7 @@ Diagnostics source of truth: root-level `diagnostics.md`.
|
|
|
10
10
|
- Understand the real user flow and the main feature branches before writing tests.
|
|
11
11
|
- Test the current controller/page runtime model, not legacy `@Route` or `api.fetch(...)` behavior.
|
|
12
12
|
- Verify routing, controllers, SSR, and router plugins against a running app when behavior depends on real request handling.
|
|
13
|
-
- After implementing a new feature or changing existing feature behavior, update the end-to-end coverage for that behavior and run the full Playwright suite before finishing. Use a real browser repro against a running app during iteration when it is the fastest trustworthy loop.
|
|
13
|
+
- After implementing a new feature or changing existing feature behavior, update the end-to-end coverage for that behavior and run the full Playwright suite before finishing. Prefer `npx proteum e2e --port <port>` for Playwright runs so base URLs and auth tokens are passed through Proteum-managed child env instead of shell-leading environment assignments. Use a real browser repro against a running app during iteration when it is the fastest trustworthy loop.
|
|
14
14
|
- Exercise real URLs, generated controller calls, or real browser flows instead of re-deriving framework internals in tests.
|
|
15
15
|
- Organize end-to-end tests following the Crosspath platform layout under `tests/e2e/**`.
|
|
16
16
|
- Put runnable scenario entrypoints in `tests/e2e/features/**`, `tests/e2e/specs/<domain>/**`, or `tests/e2e/journeys/**` depending on scope.
|
|
@@ -22,4 +22,4 @@ Diagnostics source of truth: root-level `diagnostics.md`.
|
|
|
22
22
|
- Add `data-testid` where needed instead of relying on brittle selectors.
|
|
23
23
|
- Keep end-to-end tests clean, well organized, and non-redundant. Prefer extending or reshaping the most relevant existing scenario over duplicating coverage, and remove or consolidate overlap when the suite becomes repetitive.
|
|
24
24
|
- Reuse root catalog files from `/client/catalogs/**`, `/server/catalogs/**`, or `/common/catalogs/**` instead of duplicating catalog constants in tests.
|
|
25
|
-
- For protected dev flows, prefer `npx proteum session <email> --role <role>` over automating login unless the login flow itself is under test.
|
|
25
|
+
- For protected dev flows, prefer `npx proteum e2e --session-email <email> --session-role <role>` or `npx proteum session <email> --role <role>` over automating login unless the login flow itself is under test.
|
package/cli/app/index.ts
CHANGED
|
@@ -36,6 +36,12 @@ const parseRouterPortOverride = (rawPort: string | boolean | string[] | undefine
|
|
|
36
36
|
|
|
37
37
|
const normalizeModulePath = (value: string) => value.replace(/\\/g, '/').replace(/\/$/, '');
|
|
38
38
|
|
|
39
|
+
const resolveSideTsconfig = (appRoot: string, side: TAppSide) => {
|
|
40
|
+
const candidates = [path.join(appRoot, side, 'tsconfig.json'), path.join(appRoot, side, 'app.tsconfig.json')];
|
|
41
|
+
|
|
42
|
+
return candidates.find((candidate) => fs.existsSync(candidate));
|
|
43
|
+
};
|
|
44
|
+
|
|
39
45
|
const resolveTranspileModuleDirectories = ({
|
|
40
46
|
moduleNames,
|
|
41
47
|
resolvePackageRoot,
|
|
@@ -183,17 +189,21 @@ export class App {
|
|
|
183
189
|
----------------------------------*/
|
|
184
190
|
|
|
185
191
|
public aliases = {
|
|
186
|
-
client:
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
192
|
+
client: this.createSideAliases('client'),
|
|
193
|
+
server: this.createSideAliases('server'),
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
private createSideAliases(side: TAppSide) {
|
|
197
|
+
const tsconfigFilepath = resolveSideTsconfig(this.paths.root, side);
|
|
198
|
+
|
|
199
|
+
if (!tsconfigFilepath) return new TsAlias({ aliases: [] });
|
|
200
|
+
|
|
201
|
+
return new TsAlias({
|
|
202
|
+
rootDir: tsconfigFilepath,
|
|
193
203
|
modulesDir: [cli.paths.framework.appNodeModulesRoot, cli.paths.framework.frameworkNodeModulesRoot],
|
|
194
204
|
debug: false,
|
|
195
|
-
})
|
|
196
|
-
}
|
|
205
|
+
});
|
|
206
|
+
}
|
|
197
207
|
|
|
198
208
|
private loadPkg() {
|
|
199
209
|
return fs.readJSONSync(this.paths.root + '/package.json');
|
package/cli/commands/check.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import cli from '..';
|
|
2
|
-
import { refreshGeneratedTypings, runAppLint, runAppTypecheck } from '../utils/check';
|
|
2
|
+
import { hasAppConfig, refreshGeneratedTypings, runAppLint, runAppTypecheck } from '../utils/check';
|
|
3
3
|
import { renderRows } from '../presentation/layout';
|
|
4
4
|
import { renderStep, renderSuccess, renderTitle } from '../presentation/ink';
|
|
5
5
|
|
|
@@ -23,8 +23,12 @@ export const run = async (): Promise<void> => {
|
|
|
23
23
|
renderRows([{ label: 'app', value: cli.paths.appRoot === process.cwd() ? '.' : cli.paths.appRoot }]),
|
|
24
24
|
].join('\n\n'),
|
|
25
25
|
);
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
if (hasAppConfig()) {
|
|
27
|
+
console.info(await renderStep('[1/3]', 'Refreshing generated typings.'));
|
|
28
|
+
await refreshGeneratedTypings();
|
|
29
|
+
} else {
|
|
30
|
+
console.info(await renderStep('[1/3]', 'Skipping generated typings: no Proteum app config found.'));
|
|
31
|
+
}
|
|
28
32
|
console.info(await renderStep('[2/3]', 'Running TypeScript typechecking.'));
|
|
29
33
|
await runAppTypecheck();
|
|
30
34
|
console.info(await renderStep('[3/3]', 'Running ESLint.'));
|
|
@@ -13,7 +13,7 @@ import cli from '..';
|
|
|
13
13
|
import { renderRows } from '../presentation/layout';
|
|
14
14
|
import { isLikelyProteumAppRoot } from '../presentation/commands';
|
|
15
15
|
import { renderStep, renderSuccess, renderTitle, renderWarning } from '../presentation/ink';
|
|
16
|
-
import {
|
|
16
|
+
import { configureProjectAgentInstructions, type TConfigureProjectAgentInstructionsResult } from '../utils/agents';
|
|
17
17
|
|
|
18
18
|
/*----------------------------------
|
|
19
19
|
- HELPERS
|
|
@@ -93,7 +93,12 @@ const promptBlockedOverwritePaths = async (blockedPaths: string[]) => {
|
|
|
93
93
|
if (blockedPaths.length === 0) return [];
|
|
94
94
|
|
|
95
95
|
console.info(await renderWarning('Proteum found existing non-managed instruction paths.'));
|
|
96
|
-
console.info(
|
|
96
|
+
console.info(
|
|
97
|
+
[
|
|
98
|
+
'Choose whether to overwrite each path with a Proteum-managed instruction stub:',
|
|
99
|
+
...blockedPaths.map((entry) => `- ${entry}`),
|
|
100
|
+
].join('\n'),
|
|
101
|
+
);
|
|
97
102
|
|
|
98
103
|
const overwriteBlockedPaths: string[] = [];
|
|
99
104
|
|
|
@@ -118,7 +123,7 @@ const promptBlockedOverwritePaths = async (blockedPaths: string[]) => {
|
|
|
118
123
|
return overwriteBlockedPaths;
|
|
119
124
|
};
|
|
120
125
|
|
|
121
|
-
const renderConfigureResultSections = (result:
|
|
126
|
+
const renderConfigureResultSections = (result: TConfigureProjectAgentInstructionsResult) => {
|
|
122
127
|
const sections: string[] = [];
|
|
123
128
|
|
|
124
129
|
sections.push(
|
|
@@ -178,7 +183,7 @@ export const runConfigureAgentsWizard = async ({
|
|
|
178
183
|
: undefined;
|
|
179
184
|
console.info(
|
|
180
185
|
[
|
|
181
|
-
await renderTitle('PROTEUM CONFIGURE AGENTS', 'Configure Proteum-managed instruction
|
|
186
|
+
await renderTitle('PROTEUM CONFIGURE AGENTS', 'Configure Proteum-managed instruction stubs.'),
|
|
182
187
|
renderRows([{ label: 'app', value: appRoot === process.cwd() ? '.' : appRoot }]),
|
|
183
188
|
].join('\n\n'),
|
|
184
189
|
);
|
|
@@ -204,7 +209,7 @@ export const runConfigureAgentsWizard = async ({
|
|
|
204
209
|
})
|
|
205
210
|
: undefined;
|
|
206
211
|
|
|
207
|
-
const preview =
|
|
212
|
+
const preview = configureProjectAgentInstructions({
|
|
208
213
|
appRoot,
|
|
209
214
|
coreRoot,
|
|
210
215
|
dryRun: true,
|
|
@@ -216,12 +221,12 @@ export const runConfigureAgentsWizard = async ({
|
|
|
216
221
|
await renderStep(
|
|
217
222
|
'[1/1]',
|
|
218
223
|
isMonorepo
|
|
219
|
-
? `Writing monorepo-aware instruction
|
|
220
|
-
: 'Writing standalone instruction
|
|
224
|
+
? `Writing monorepo-aware instruction stubs using ${monorepoRoot}.`
|
|
225
|
+
: 'Writing standalone instruction stubs.',
|
|
221
226
|
),
|
|
222
227
|
);
|
|
223
228
|
|
|
224
|
-
const result =
|
|
229
|
+
const result = configureProjectAgentInstructions({
|
|
225
230
|
appRoot,
|
|
226
231
|
coreRoot,
|
|
227
232
|
monorepoRoot,
|
|
@@ -229,7 +234,7 @@ export const runConfigureAgentsWizard = async ({
|
|
|
229
234
|
});
|
|
230
235
|
const sections = renderConfigureResultSections(result);
|
|
231
236
|
|
|
232
|
-
console.info(await renderSuccess('Proteum-managed instruction
|
|
237
|
+
console.info(await renderSuccess('Proteum-managed instruction stubs are configured.'));
|
|
233
238
|
|
|
234
239
|
if (sections.length > 0) console.info(`\n${sections.join('\n\n')}`);
|
|
235
240
|
};
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import dotenv from 'dotenv';
|
|
3
|
+
import fs from 'fs-extra';
|
|
4
|
+
import got from 'got';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import { UsageError } from 'clipanion';
|
|
7
|
+
|
|
8
|
+
import cli from '..';
|
|
9
|
+
import type { TDevSessionErrorResponse, TDevSessionStartResponse } from '../../common/dev/session';
|
|
10
|
+
|
|
11
|
+
type TPlaywrightInvocation = {
|
|
12
|
+
command: string;
|
|
13
|
+
args: string[];
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const normalizeBaseUrl = (value: string) => value.replace(/\/+$/, '');
|
|
17
|
+
|
|
18
|
+
const getRouterPortFromManifest = () => {
|
|
19
|
+
const manifestFilepath = path.join(cli.args.workdir as string, '.proteum', 'manifest.json');
|
|
20
|
+
if (!fs.existsSync(manifestFilepath)) return undefined;
|
|
21
|
+
|
|
22
|
+
const manifest = fs.readJsonSync(manifestFilepath, { throws: false }) as
|
|
23
|
+
| { env?: { resolved?: { routerPort?: number } } }
|
|
24
|
+
| undefined;
|
|
25
|
+
const port = manifest?.env?.resolved?.routerPort;
|
|
26
|
+
|
|
27
|
+
if (typeof port !== 'number' || port <= 0) return undefined;
|
|
28
|
+
|
|
29
|
+
return String(port);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const getRouterPort = () => {
|
|
33
|
+
const overridePort = typeof cli.args.port === 'string' && cli.args.port ? cli.args.port : '';
|
|
34
|
+
if (overridePort) return overridePort;
|
|
35
|
+
|
|
36
|
+
const manifestPort = getRouterPortFromManifest();
|
|
37
|
+
if (manifestPort) return manifestPort;
|
|
38
|
+
|
|
39
|
+
return '';
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const getBaseUrlCandidates = () => {
|
|
43
|
+
const explicitUrl = typeof cli.args.url === 'string' && cli.args.url ? cli.args.url.trim() : '';
|
|
44
|
+
if (explicitUrl) return [normalizeBaseUrl(explicitUrl)];
|
|
45
|
+
|
|
46
|
+
const port = getRouterPort();
|
|
47
|
+
if (!port) return [];
|
|
48
|
+
|
|
49
|
+
return [...new Set([`http://localhost:${port}`, `http://127.0.0.1:${port}`, `http://[::1]:${port}`])];
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const getSessionErrorMessage = (body: TDevSessionErrorResponse | object | string | undefined, statusCode: number) => {
|
|
53
|
+
if (typeof body === 'object' && body !== null && 'error' in body && typeof body.error === 'string') {
|
|
54
|
+
return body.error;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return `Session request failed with status ${statusCode}.`;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const hasStructuredSessionError = (body: TDevSessionErrorResponse | object | string | undefined): body is TDevSessionErrorResponse =>
|
|
61
|
+
typeof body === 'object' && body !== null && 'error' in body && typeof body.error === 'string';
|
|
62
|
+
|
|
63
|
+
const requestSession = async ({ email, role }: { email: string; role: string }) => {
|
|
64
|
+
const attempts: string[] = [];
|
|
65
|
+
|
|
66
|
+
for (const baseUrl of getBaseUrlCandidates()) {
|
|
67
|
+
try {
|
|
68
|
+
const response = await got(`${baseUrl}/__proteum/session/start`, {
|
|
69
|
+
method: 'POST',
|
|
70
|
+
json: role ? { email, role } : { email },
|
|
71
|
+
responseType: 'json',
|
|
72
|
+
throwHttpErrors: false,
|
|
73
|
+
retry: { limit: 0 },
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
if (response.statusCode >= 400) {
|
|
77
|
+
if (response.statusCode === 404 && !hasStructuredSessionError(response.body as TDevSessionErrorResponse | object | string | undefined)) {
|
|
78
|
+
attempts.push(`${baseUrl}/__proteum/session/start: returned 404`);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
throw new UsageError(
|
|
83
|
+
getSessionErrorMessage(response.body as TDevSessionErrorResponse | object | string | undefined, response.statusCode),
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
baseUrl,
|
|
89
|
+
token: (response.body as TDevSessionStartResponse).session.token,
|
|
90
|
+
};
|
|
91
|
+
} catch (error) {
|
|
92
|
+
if (error instanceof UsageError) throw error;
|
|
93
|
+
|
|
94
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
95
|
+
attempts.push(`${baseUrl}/__proteum/session/start: ${message}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
throw new UsageError(
|
|
100
|
+
[
|
|
101
|
+
'Could not reach the Proteum session server.',
|
|
102
|
+
...attempts.map((attempt) => `- ${attempt}`),
|
|
103
|
+
'Start the app with `proteum dev`, then pass --port or --url to `proteum e2e`.',
|
|
104
|
+
].join('\n'),
|
|
105
|
+
);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const resolveBaseUrl = (sessionBaseUrl?: string) => {
|
|
109
|
+
if (sessionBaseUrl) return sessionBaseUrl;
|
|
110
|
+
|
|
111
|
+
const [baseUrl] = getBaseUrlCandidates();
|
|
112
|
+
if (baseUrl) return baseUrl;
|
|
113
|
+
|
|
114
|
+
throw new UsageError('Could not determine E2E_BASE_URL. Pass --port or --url to `proteum e2e`.');
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const parseEnvPair = (value: string) => {
|
|
118
|
+
const separatorIndex = value.indexOf('=');
|
|
119
|
+
if (separatorIndex <= 0) {
|
|
120
|
+
throw new UsageError(`Invalid --env value "${value}". Expected KEY=value.`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const key = value.slice(0, separatorIndex).trim();
|
|
124
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
|
|
125
|
+
throw new UsageError(`Invalid --env key "${key}".`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return { key, value: value.slice(separatorIndex + 1) };
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const readEnvFiles = (filepaths: string[]) => {
|
|
132
|
+
const env: Record<string, string> = {};
|
|
133
|
+
|
|
134
|
+
for (const filepath of filepaths) {
|
|
135
|
+
const absoluteFilepath = path.resolve(cli.args.workdir as string, filepath);
|
|
136
|
+
|
|
137
|
+
if (!fs.existsSync(absoluteFilepath)) {
|
|
138
|
+
throw new UsageError(`Env file does not exist: ${absoluteFilepath}`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
Object.assign(env, dotenv.parse(fs.readFileSync(absoluteFilepath)));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return env;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const resolvePlaywrightInvocation = (appRoot: string): TPlaywrightInvocation => {
|
|
148
|
+
const binaryName = process.platform === 'win32' ? 'playwright.cmd' : 'playwright';
|
|
149
|
+
const localBinary = path.join(appRoot, 'node_modules', '.bin', binaryName);
|
|
150
|
+
|
|
151
|
+
if (fs.existsSync(localBinary)) {
|
|
152
|
+
return { command: localBinary, args: ['test'] };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return { command: 'npx', args: ['playwright', 'test'] };
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const runPlaywright = async ({ env, playwrightArgs }: { env: Record<string, string>; playwrightArgs: string[] }) => {
|
|
159
|
+
const appRoot = cli.args.workdir as string;
|
|
160
|
+
const invocation = resolvePlaywrightInvocation(appRoot);
|
|
161
|
+
|
|
162
|
+
return await new Promise<number | null>((resolve, reject) => {
|
|
163
|
+
const child = spawn(invocation.command, [...invocation.args, ...playwrightArgs], {
|
|
164
|
+
cwd: appRoot,
|
|
165
|
+
env: {
|
|
166
|
+
...process.env,
|
|
167
|
+
...env,
|
|
168
|
+
},
|
|
169
|
+
stdio: 'inherit',
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
child.once('error', reject);
|
|
173
|
+
child.once('close', (exitCode) => resolve(exitCode));
|
|
174
|
+
});
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
export const run = async () => {
|
|
178
|
+
const sessionEmail = typeof cli.args.sessionEmail === 'string' ? cli.args.sessionEmail.trim() : '';
|
|
179
|
+
const sessionRole = typeof cli.args.sessionRole === 'string' ? cli.args.sessionRole.trim() : '';
|
|
180
|
+
const envFilepaths = Array.isArray(cli.args.envFile) ? cli.args.envFile : [];
|
|
181
|
+
const envPairs = Array.isArray(cli.args.env) ? cli.args.env : [];
|
|
182
|
+
const playwrightArgs = Array.isArray(cli.args.playwrightArgs) ? cli.args.playwrightArgs : [];
|
|
183
|
+
const explicitPort = getRouterPort();
|
|
184
|
+
|
|
185
|
+
const explicitEnv = readEnvFiles(envFilepaths);
|
|
186
|
+
for (const pair of envPairs) {
|
|
187
|
+
const parsed = parseEnvPair(pair);
|
|
188
|
+
explicitEnv[parsed.key] = parsed.value;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const session = sessionEmail ? await requestSession({ email: sessionEmail, role: sessionRole }) : undefined;
|
|
192
|
+
const baseUrl = resolveBaseUrl(session?.baseUrl);
|
|
193
|
+
const exitCode = await runPlaywright({
|
|
194
|
+
env: {
|
|
195
|
+
...explicitEnv,
|
|
196
|
+
E2E_BASE_URL: baseUrl,
|
|
197
|
+
...(explicitPort ? { E2E_PORT: explicitPort } : {}),
|
|
198
|
+
...(session?.token ? { E2E_AUTH_TOKEN: session.token } : {}),
|
|
199
|
+
},
|
|
200
|
+
playwrightArgs,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
return exitCode ?? 1;
|
|
204
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import cli from '..';
|
|
2
|
-
import { refreshGeneratedTypings, runAppTypecheck } from '../utils/check';
|
|
2
|
+
import { hasAppConfig, refreshGeneratedTypings, runAppTypecheck } from '../utils/check';
|
|
3
3
|
import { renderRows } from '../presentation/layout';
|
|
4
4
|
import { renderStep, renderSuccess, renderTitle } from '../presentation/ink';
|
|
5
5
|
|
|
@@ -23,8 +23,12 @@ export const run = async (): Promise<void> => {
|
|
|
23
23
|
renderRows([{ label: 'app', value: cli.paths.appRoot === process.cwd() ? '.' : cli.paths.appRoot }]),
|
|
24
24
|
].join('\n\n'),
|
|
25
25
|
);
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
if (hasAppConfig()) {
|
|
27
|
+
console.info(await renderStep('[1/2]', 'Refreshing generated typings.'));
|
|
28
|
+
await refreshGeneratedTypings();
|
|
29
|
+
} else {
|
|
30
|
+
console.info(await renderStep('[1/2]', 'Skipping generated typings: no Proteum app config found.'));
|
|
31
|
+
}
|
|
28
32
|
console.info(await renderStep('[2/2]', 'Running TypeScript typechecking.'));
|
|
29
33
|
await runAppTypecheck();
|
|
30
34
|
console.info(await renderSuccess('Typecheck passed.'));
|
|
@@ -14,6 +14,7 @@ export const proteumCommandNames = [
|
|
|
14
14
|
'typecheck',
|
|
15
15
|
'lint',
|
|
16
16
|
'check',
|
|
17
|
+
'e2e',
|
|
17
18
|
'connect',
|
|
18
19
|
'doctor',
|
|
19
20
|
'explain',
|
|
@@ -55,7 +56,7 @@ export const proteumRecommendedFlow: TRow[] = [
|
|
|
55
56
|
|
|
56
57
|
export const proteumCommandGroups: Array<{ title: string; names: TProteumCommandName[] }> = [
|
|
57
58
|
{ title: 'Daily workflow', names: ['dev', 'refresh', 'build'] },
|
|
58
|
-
{ title: 'Quality gates', names: ['typecheck', 'lint', 'check'] },
|
|
59
|
+
{ title: 'Quality gates', names: ['typecheck', 'lint', 'check', 'e2e'] },
|
|
59
60
|
{ title: 'Manifest and contracts', names: ['connect', 'doctor', 'explain', 'orient', 'diagnose', 'perf', 'trace', 'command', 'session', 'verify'] },
|
|
60
61
|
{ title: 'Project scaffolding', names: ['init', 'configure', 'create', 'migrate'] },
|
|
61
62
|
];
|
|
@@ -112,22 +113,22 @@ export const proteumCommands: Record<TProteumCommandName, TProteumCommandDoc> =
|
|
|
112
113
|
configure: {
|
|
113
114
|
name: 'configure',
|
|
114
115
|
category: 'Project scaffolding',
|
|
115
|
-
summary: 'Interactively configure Proteum-managed instruction
|
|
116
|
+
summary: 'Interactively configure Proteum-managed instruction stubs for a standalone app or monorepo app root.',
|
|
116
117
|
usage: 'proteum configure agents',
|
|
117
118
|
bestFor:
|
|
118
|
-
'Creating or switching the managed `AGENTS.md` instruction layout intentionally instead of having `init` or `dev` write
|
|
119
|
+
'Creating or switching the managed `AGENTS.md` instruction layout intentionally instead of having `init` or `dev` write instruction files implicitly.',
|
|
119
120
|
examples: [
|
|
120
121
|
{
|
|
121
|
-
description: 'Configure instruction
|
|
122
|
+
description: 'Configure instruction stubs for the current standalone app',
|
|
122
123
|
command: 'proteum configure agents',
|
|
123
124
|
},
|
|
124
125
|
],
|
|
125
126
|
notes: [
|
|
126
|
-
'This command is interactive. It asks whether the current Proteum app belongs to a monorepo and, if so, which ancestor path should receive the reusable root `AGENTS.md`
|
|
127
|
+
'This command is interactive. It asks whether the current Proteum app belongs to a monorepo and, if so, which ancestor path should receive the reusable root `AGENTS.md` stub.',
|
|
127
128
|
'Standalone mode writes the full app-root instruction set into the current Proteum app root.',
|
|
128
129
|
'Monorepo mode writes the reusable root `AGENTS.md` into the chosen monorepo root and switches the current app root `AGENTS.md` to the app-root addendum.',
|
|
129
|
-
'If a target path already contains a non-managed file or foreign symlink, the interactive flow asks whether to overwrite it with the Proteum-managed
|
|
130
|
-
'Declined non-managed paths are left untouched; Proteum still creates missing
|
|
130
|
+
'If a target path already contains a non-managed file or foreign symlink, the interactive flow asks whether to overwrite it with the Proteum-managed stub.',
|
|
131
|
+
'Declined non-managed paths are left untouched; Proteum still creates missing stubs and updates stubs or symlinks it already manages.',
|
|
131
132
|
],
|
|
132
133
|
status: 'experimental',
|
|
133
134
|
},
|
|
@@ -276,6 +277,35 @@ export const proteumCommands: Record<TProteumCommandName, TProteumCommandDoc> =
|
|
|
276
277
|
notes: ['This command executes refresh, typecheck, then lint in that order.'],
|
|
277
278
|
status: 'stable',
|
|
278
279
|
},
|
|
280
|
+
e2e: {
|
|
281
|
+
name: 'e2e',
|
|
282
|
+
category: 'Quality gates',
|
|
283
|
+
summary: 'Run app Playwright tests with Proteum-managed E2E environment values.',
|
|
284
|
+
usage:
|
|
285
|
+
'proteum e2e [--cwd <path>] [--port <port>|--url <url>] [--session-email <email>] [--session-role <role>] [--env KEY=value] [--env-file <path>] [--grep <text>] [--project <name>] [specs...]',
|
|
286
|
+
bestFor:
|
|
287
|
+
'Running targeted or full Playwright suites without shell-leading environment assignments for base URLs, auth tokens, or per-run values.',
|
|
288
|
+
examples: [
|
|
289
|
+
{
|
|
290
|
+
description: 'Run the full suite against a local dev server',
|
|
291
|
+
command: 'proteum e2e --port 3101',
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
description: 'Run one spec with a dev auth token minted internally',
|
|
295
|
+
command: 'proteum e2e --port 3101 --session-email admin@example.com --session-role ADMIN tests/e2e/features/admin.spec.ts',
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
description: 'Load extra dotenv values before Playwright starts',
|
|
299
|
+
command: 'proteum e2e --url http://localhost:3101 --env-file .proteum/e2e.env --grep smoke',
|
|
300
|
+
},
|
|
301
|
+
],
|
|
302
|
+
notes: [
|
|
303
|
+
'`proteum e2e` spawns Playwright with `E2E_BASE_URL`, optional `E2E_PORT`, optional `E2E_AUTH_TOKEN`, and any `--env`/`--env-file` values in the child process environment.',
|
|
304
|
+
'Common Playwright flags are exposed directly: `--config`, `--debug`, `--grep`, `--headed`, `--list`, `--project`, `--reporter`, `--retries`, `--timeout`, `--ui`, and `--workers`.',
|
|
305
|
+
'The shell command itself stays `proteum e2e ...`, so Codex does not need to run `FOO=bar npx playwright test ...`.',
|
|
306
|
+
],
|
|
307
|
+
status: 'experimental',
|
|
308
|
+
},
|
|
279
309
|
connect: {
|
|
280
310
|
name: 'connect',
|
|
281
311
|
category: 'Manifest and contracts',
|
package/cli/runtime/command.ts
CHANGED
|
@@ -4,11 +4,11 @@ import cli, { type TArgsObject } from '../context';
|
|
|
4
4
|
import { createClipanionUsage, proteumCommands, type TProteumCommandName } from '../presentation/commands';
|
|
5
5
|
import { createArgs } from './argv';
|
|
6
6
|
|
|
7
|
-
type TRunModule = { run: () => Promise<void> };
|
|
7
|
+
type TRunModule = { run: () => Promise<number | void> };
|
|
8
8
|
|
|
9
9
|
export const runCommandModule = async (loader: () => Promise<TRunModule>) => {
|
|
10
10
|
const module = await loader();
|
|
11
|
-
await module.run();
|
|
11
|
+
return await module.run();
|
|
12
12
|
};
|
|
13
13
|
|
|
14
14
|
export abstract class ProteumCommand extends Command {
|