proteum 2.4.3 → 2.5.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.
- package/README.md +60 -55
- package/agents/project/AGENTS.md +112 -31
- package/agents/project/CODING_STYLE.md +2 -2
- package/agents/project/app-root/AGENTS.md +1 -3
- package/agents/project/client/AGENTS.md +1 -1
- package/agents/project/client/pages/AGENTS.md +21 -9
- package/agents/project/diagnostics.md +2 -2
- package/agents/project/optimizations.md +1 -1
- package/agents/project/root/AGENTS.md +105 -22
- package/agents/project/server/routes/AGENTS.md +30 -1
- package/agents/project/tests/AGENTS.md +1 -1
- package/cli/commands/doctor.ts +54 -3
- package/cli/commands/runtime.ts +6 -0
- package/cli/commands/worktree.ts +116 -0
- package/cli/compiler/artifacts/controllers.ts +16 -15
- package/cli/compiler/artifacts/discovery.ts +129 -17
- package/cli/compiler/artifacts/routing.ts +0 -5
- package/cli/compiler/artifacts/services.ts +253 -76
- package/cli/compiler/common/controllers.ts +159 -57
- package/cli/compiler/common/generatedRouteModules.ts +457 -363
- package/cli/mcp/router.ts +47 -3
- package/cli/presentation/commands.ts +25 -15
- package/cli/runtime/commands.ts +39 -12
- package/cli/runtime/worktreeBootstrap.ts +608 -0
- package/cli/scaffold/index.ts +28 -18
- package/cli/scaffold/templates.ts +44 -33
- package/cli/utils/agents.ts +14 -1
- package/client/services/router/index.tsx +23 -3
- package/client/services/router/request/api.ts +14 -4
- package/common/dev/contractsDoctor.ts +1 -1
- package/common/dev/mcpPayloads.ts +8 -1
- package/common/env/proteumEnv.ts +14 -2
- package/common/router/contracts.ts +1 -1
- package/common/router/definitions.ts +177 -0
- package/common/router/index.ts +23 -12
- package/common/router/pageData.ts +5 -5
- package/common/router/register.ts +2 -2
- package/common/router/request/api.ts +12 -2
- package/docs/agent-routing.md +5 -2
- package/docs/diagnostics.md +2 -0
- package/docs/mcp.md +6 -3
- package/eslint.js +36 -1
- package/package.json +1 -1
- package/server/app/commands.ts +5 -1
- package/server/app/container/console/http-client-error-context.test.cjs +10 -1
- package/server/app/container/console/index.ts +2 -1
- package/server/app/controller/index.ts +98 -40
- package/server/app/index.ts +92 -1
- package/server/app/service/index.ts +5 -1
- package/server/index.ts +6 -2
- package/server/services/router/index.ts +47 -38
- package/server/services/router/response/index.ts +2 -2
- package/tests/agents-utils.test.cjs +14 -1
- package/tests/cli-mcp-command.test.cjs +84 -0
- package/tests/definition-contracts.test.cjs +453 -0
- package/tests/dev-transpile-watch.test.cjs +37 -28
- package/tests/eslint-rules.test.cjs +39 -1
- package/tests/mcp.test.cjs +90 -0
- package/tests/worktree-bootstrap.test.cjs +206 -0
- package/types/aliases.d.ts +0 -5
- package/types/controller-input.test.ts +23 -17
- package/types/controller-request-context.test.ts +10 -11
- package/cli/commands/migrate.ts +0 -51
- package/cli/migrate/pageContract.ts +0 -516
- package/docs/migrate-from-2.1.3.md +0 -396
- package/scripts/cleanup-generated-controllers.ts +0 -62
- package/scripts/fix-reference-app-typing.ts +0 -490
- package/scripts/format-router-registrations.ts +0 -119
- package/scripts/migrate-explicit-controllers-and-request.ts +0 -423
- package/scripts/refactor-client-app-imports.ts +0 -244
- package/scripts/refactor-client-pages.ts +0 -587
- package/scripts/refactor-server-controllers.ts +0 -471
- package/scripts/refactor-server-runtime-aliases.ts +0 -360
- package/scripts/restore-client-app-import-files.ts +0 -41
|
@@ -14,12 +14,12 @@ const { v4: uuid } = require('uuid') as { v4: () => string };
|
|
|
14
14
|
import got from 'got';
|
|
15
15
|
import hInterval from 'human-interval';
|
|
16
16
|
import type express from 'express';
|
|
17
|
-
import type { Request, Response, NextFunction } from 'express';
|
|
18
17
|
import zod, { ZodError } from 'zod';
|
|
19
18
|
export { default as schema } from 'zod';
|
|
20
19
|
|
|
21
20
|
// Core
|
|
22
21
|
import type { Application } from '@server/app/index';
|
|
22
|
+
import { runControllerAction, type TControllerActionDefinition } from '@server/app/controller';
|
|
23
23
|
import Service, { TServiceArgs } from '@server/app/service';
|
|
24
24
|
import context from '@server/context';
|
|
25
25
|
import type DisksManager from '@server/services/disks';
|
|
@@ -30,9 +30,12 @@ import BaseRouter, {
|
|
|
30
30
|
TErrorRoute,
|
|
31
31
|
TRouteModule,
|
|
32
32
|
TRouteOptions,
|
|
33
|
+
type TRouteDefinition,
|
|
34
|
+
type TRouteMetadata,
|
|
33
35
|
defaultOptions,
|
|
34
36
|
matchRoute,
|
|
35
37
|
buildUrl,
|
|
38
|
+
withRouteMetadata,
|
|
36
39
|
} from '@common/router';
|
|
37
40
|
import type { TSsrUnresolvedRoute, TRegisterPageArgs } from '@common/router/contracts';
|
|
38
41
|
import { buildRegex, getRegisterPageArgs } from '@common/router/register';
|
|
@@ -76,7 +79,7 @@ type TGeneratedControllerDefinition = {
|
|
|
76
79
|
path: string;
|
|
77
80
|
filepath: string;
|
|
78
81
|
sourceLocation: { line: number; column: number };
|
|
79
|
-
|
|
82
|
+
action: TControllerActionDefinition<any, any, any, any>;
|
|
80
83
|
method: string;
|
|
81
84
|
};
|
|
82
85
|
|
|
@@ -385,7 +388,7 @@ export default class ServerRouter<
|
|
|
385
388
|
}
|
|
386
389
|
}
|
|
387
390
|
|
|
388
|
-
this.
|
|
391
|
+
this.refreshRouteRegistration();
|
|
389
392
|
}
|
|
390
393
|
|
|
391
394
|
private registerControllers(definitions: TGeneratedControllerDefinition[]) {
|
|
@@ -393,10 +396,8 @@ export default class ServerRouter<
|
|
|
393
396
|
const route: TRoute<TRouterContext<this>> = {
|
|
394
397
|
method: 'POST',
|
|
395
398
|
path: definition.path,
|
|
396
|
-
controller: (requestContext: TRouterContext<this>) =>
|
|
397
|
-
|
|
398
|
-
return controller[definition.method]();
|
|
399
|
-
},
|
|
399
|
+
controller: (requestContext: TRouterContext<this>) =>
|
|
400
|
+
runControllerAction(definition.action, requestContext as any),
|
|
400
401
|
options: { ...defaultOptions, filepath: definition.filepath, sourceLocation: definition.sourceLocation },
|
|
401
402
|
};
|
|
402
403
|
|
|
@@ -419,7 +420,31 @@ export default class ServerRouter<
|
|
|
419
420
|
- REGISTER
|
|
420
421
|
----------------------------------*/
|
|
421
422
|
|
|
422
|
-
public
|
|
423
|
+
public registerRouteDefinition(definition: TRouteDefinition, metadata: TRouteMetadata = {}) {
|
|
424
|
+
if (definition.kind === 'page') {
|
|
425
|
+
return this.page(
|
|
426
|
+
definition.path,
|
|
427
|
+
withRouteMetadata(definition.options, metadata),
|
|
428
|
+
definition.data,
|
|
429
|
+
definition.render,
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (definition.kind === 'error') {
|
|
434
|
+
return this.error(definition.code, withRouteMetadata(definition.options, metadata), definition.render);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const method = definition.method === '*' ? '*' : (definition.method.toUpperCase() as TRouteHttpMethod);
|
|
438
|
+
|
|
439
|
+
return this.registerApi(
|
|
440
|
+
method,
|
|
441
|
+
definition.path,
|
|
442
|
+
withRouteMetadata(definition.options, metadata),
|
|
443
|
+
definition.handler as TServerController<this>,
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
protected page(...args: TRegisterPageArgs<any, TRouteOptions>) {
|
|
423
448
|
const { path, options, data, renderer, layout } = getRegisterPageArgs(...args);
|
|
424
449
|
|
|
425
450
|
const { regex, keys } = buildRegex(path);
|
|
@@ -442,7 +467,7 @@ export default class ServerRouter<
|
|
|
442
467
|
return this;
|
|
443
468
|
}
|
|
444
469
|
|
|
445
|
-
|
|
470
|
+
protected error(
|
|
446
471
|
code: number,
|
|
447
472
|
options: Partial<TRouteOptions>,
|
|
448
473
|
renderer: TFrontRenderer<{}, { message: string }>,
|
|
@@ -461,34 +486,13 @@ export default class ServerRouter<
|
|
|
461
486
|
this.errors[code] = route;
|
|
462
487
|
}
|
|
463
488
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
public express(
|
|
473
|
-
middleware: (req: Request, res: Response, next: NextFunction, requestContext: TRouterContext<this>) => void,
|
|
474
|
-
) {
|
|
475
|
-
return (context: TRouterContext<this>) =>
|
|
476
|
-
new Promise((resolve) => {
|
|
477
|
-
context.request.res.on('finish', function () {
|
|
478
|
-
//console.log('the response has been sent', request.res.statusCode);
|
|
479
|
-
resolve(true);
|
|
480
|
-
});
|
|
481
|
-
|
|
482
|
-
middleware(
|
|
483
|
-
context.request.req,
|
|
484
|
-
context.request.res,
|
|
485
|
-
() => {
|
|
486
|
-
resolve(true);
|
|
487
|
-
},
|
|
488
|
-
context,
|
|
489
|
-
);
|
|
490
|
-
});
|
|
491
|
-
}
|
|
489
|
+
protected all = (...args: TApiRegisterArgs<this>) => this.registerApi('*', ...args);
|
|
490
|
+
protected options = (...args: TApiRegisterArgs<this>) => this.registerApi('OPTIONS', ...args);
|
|
491
|
+
protected get = (...args: TApiRegisterArgs<this>) => this.registerApi('GET', ...args);
|
|
492
|
+
protected post = (...args: TApiRegisterArgs<this>) => this.registerApi('POST', ...args);
|
|
493
|
+
protected put = (...args: TApiRegisterArgs<this>) => this.registerApi('PUT', ...args);
|
|
494
|
+
protected patch = (...args: TApiRegisterArgs<this>) => this.registerApi('PATCH', ...args);
|
|
495
|
+
protected delete = (...args: TApiRegisterArgs<this>) => this.registerApi('DELETE', ...args);
|
|
492
496
|
|
|
493
497
|
protected registerApi(method: TRouteHttpMethod, ...args: TApiRegisterArgs<this>): this {
|
|
494
498
|
let path: string;
|
|
@@ -565,7 +569,11 @@ export default class ServerRouter<
|
|
|
565
569
|
);
|
|
566
570
|
}
|
|
567
571
|
|
|
568
|
-
|
|
572
|
+
public refreshRouteRegistration() {
|
|
573
|
+
this.afterRegister();
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
private afterRegister() {
|
|
569
577
|
// Ordonne par ordre de priorité
|
|
570
578
|
this.config.debug && console.info('Loading routes ...');
|
|
571
579
|
this.routes.sort((r1, r2) => {
|
|
@@ -579,6 +587,7 @@ export default class ServerRouter<
|
|
|
579
587
|
return 0;
|
|
580
588
|
});
|
|
581
589
|
// - Génère les définitions de route pour le client
|
|
590
|
+
this.ssrRoutes = [];
|
|
582
591
|
this.config.debug && console.info(`Registered routes:`);
|
|
583
592
|
for (const route of this.routes) {
|
|
584
593
|
const chunkId = route.options.id;
|
|
@@ -60,6 +60,7 @@ export type TRouterContext<TRouter extends TServerRouter> =
|
|
|
60
60
|
// Request context
|
|
61
61
|
{
|
|
62
62
|
app: TServerRouterApplication<TRouter>;
|
|
63
|
+
services: TServerRouterApplication<TRouter>;
|
|
63
64
|
context: TRouterContext<TRouter>; // = this
|
|
64
65
|
request: ServerRequest<TRouter>;
|
|
65
66
|
api: ServerRequest<TRouter>['api'];
|
|
@@ -155,14 +156,12 @@ export default class ServerResponse<
|
|
|
155
156
|
const contextStore = context.getStore() as
|
|
156
157
|
| {
|
|
157
158
|
requestContext?: TRouterContext<TAnyRouter>;
|
|
158
|
-
inputSchemaUsed?: boolean;
|
|
159
159
|
ownerLabel?: string;
|
|
160
160
|
ownerFilepath?: string;
|
|
161
161
|
}
|
|
162
162
|
| undefined;
|
|
163
163
|
if (contextStore) {
|
|
164
164
|
contextStore.requestContext = requestContext;
|
|
165
|
-
contextStore.inputSchemaUsed = false;
|
|
166
165
|
contextStore.ownerLabel = getRouteTraceTarget(route as TAnyRoute<TRouterContext<TServerRouter>>);
|
|
167
166
|
contextStore.ownerFilepath = route.options.filepath || undefined;
|
|
168
167
|
}
|
|
@@ -229,6 +228,7 @@ export default class ServerResponse<
|
|
|
229
228
|
const requestContext: TRouterContext<TRouter> = {
|
|
230
229
|
// Router context
|
|
231
230
|
app: this.app,
|
|
231
|
+
services: this.app,
|
|
232
232
|
context: undefined!,
|
|
233
233
|
request: this.request,
|
|
234
234
|
response: this,
|
|
@@ -104,11 +104,23 @@ test('standalone configure creates tracked instruction files with routing contra
|
|
|
104
104
|
assert.match(agentsContent, /\/__proteum\/mcp/);
|
|
105
105
|
assert.match(agentsContent, /central MCP ready banner/);
|
|
106
106
|
assert.match(agentsContent, /proteum-mcp-v1/);
|
|
107
|
+
assert.match(agentsContent, /## Explicit App-Building Contract/);
|
|
108
|
+
assert.match(agentsContent, /defineApplication\(\{ services, router, models, commands \}\)/);
|
|
109
|
+
assert.match(agentsContent, /definePageRoute\(\{ path, options, data, render \}\)/);
|
|
110
|
+
assert.match(agentsContent, /defineController\(\{ path, actions \}\)/);
|
|
111
|
+
assert.match(agentsContent, /Never import `@app` in page, route, or controller files/);
|
|
112
|
+
assert.match(agentsContent, /Never call top-level `Router\.page\(\.\.\.\)`/);
|
|
107
113
|
assert.match(agentsContent, /## Triggered Instruction Reads/);
|
|
114
|
+
assert.match(agentsContent, /Worktree Preflight/);
|
|
115
|
+
assert.match(agentsContent, /npx proteum worktree init --source <source-app-root>/);
|
|
116
|
+
assert.match(agentsContent, /--skip-deps --reason/);
|
|
108
117
|
assert.match(agentsContent, /Git lifecycle/);
|
|
109
118
|
assert.match(agentsContent, /read Root contract fallback before any git write/);
|
|
119
|
+
assert.match(agentsContent, /Before git writes after a bug fix, behavior change, decision change, or docs-relevant production change/);
|
|
110
120
|
assert.match(agentsContent, /add or update focused unit tests/);
|
|
111
|
-
assert.match(agentsContent, /read Root contract fallback, `CODING_STYLE\.md`, `tests\/AGENTS\.md`/);
|
|
121
|
+
assert.match(agentsContent, /read Root contract fallback, `DOCUMENTATION\.md`, `CODING_STYLE\.md`, `tests\/AGENTS\.md`/);
|
|
122
|
+
assert.match(agentsContent, /Bug fixes, regressions, incidents, broken public routes, auth\/OAuth failures/);
|
|
123
|
+
assert.match(agentsContent, /docs\/fixes\/YYYY-MM-DD-short-bug-name\.md/);
|
|
112
124
|
assert.match(agentsContent, /GEO\/SEO\/crawler\/structured-data\/AI-source changes/);
|
|
113
125
|
assert.match(agentsContent, /MCP-selected previews are enough/);
|
|
114
126
|
assert.doesNotMatch(agentsContent, /Conventional Commits/);
|
|
@@ -234,6 +246,7 @@ test('monorepo configure writes root and app instruction files', () => {
|
|
|
234
246
|
assert.match(fs.readFileSync(path.join(monorepoRoot, 'AGENTS.md'), 'utf8'), /## Known Proteum Apps/);
|
|
235
247
|
assert.match(fs.readFileSync(path.join(monorepoRoot, 'AGENTS.md'), 'utf8'), /apps\/product/);
|
|
236
248
|
assert.match(fs.readFileSync(path.join(monorepoRoot, 'AGENTS.md'), 'utf8'), /Do not start `npx proteum dev` from this root/);
|
|
249
|
+
assert.match(fs.readFileSync(path.join(monorepoRoot, 'AGENTS.md'), 'utf8'), /Worktree Preflight/);
|
|
237
250
|
assert.match(fs.readFileSync(path.join(monorepoRoot, 'CODING_STYLE.md'), 'utf8'), /## Source: CODING_STYLE\.md/);
|
|
238
251
|
assert.match(fs.readFileSync(path.join(monorepoRoot, 'DOCUMENTATION.md'), 'utf8'), /## Source: DOCUMENTATION\.md/);
|
|
239
252
|
assert.match(fs.readFileSync(path.join(monorepoRoot, 'diagnostics.md'), 'utf8'), /## Source: diagnostics\.md/);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const assert = require('node:assert/strict');
|
|
2
|
+
const crypto = require('node:crypto');
|
|
2
3
|
const { spawn, spawnSync } = require('node:child_process');
|
|
3
4
|
const fs = require('node:fs');
|
|
4
5
|
const http = require('node:http');
|
|
@@ -55,6 +56,42 @@ const createProteumApp = (appRoot, { routerPort = 3020 } = {}) => {
|
|
|
55
56
|
);
|
|
56
57
|
};
|
|
57
58
|
|
|
59
|
+
const hashFile = (filepath) => crypto.createHash('sha256').update(fs.readFileSync(filepath)).digest('hex');
|
|
60
|
+
|
|
61
|
+
const writeFreshWorktreeBootstrapMarker = (appRoot) => {
|
|
62
|
+
const timestamp = new Date().toISOString();
|
|
63
|
+
fs.mkdirSync(path.join(appRoot, 'node_modules'), { recursive: true });
|
|
64
|
+
writeFile(path.join(appRoot, '.env'), 'PORT=3020\n');
|
|
65
|
+
writeFile(path.join(appRoot, 'package-lock.json'), '{"lockfileVersion":3}\n');
|
|
66
|
+
writeFile(path.join(appRoot, 'AGENTS.md'), '# Agents\n');
|
|
67
|
+
|
|
68
|
+
writeFile(
|
|
69
|
+
path.join(appRoot, '.proteum', 'worktree-bootstrap.json'),
|
|
70
|
+
JSON.stringify(
|
|
71
|
+
{
|
|
72
|
+
version: 1,
|
|
73
|
+
createdAt: timestamp,
|
|
74
|
+
updatedAt: timestamp,
|
|
75
|
+
proteumVersion: require('../package.json').version,
|
|
76
|
+
packageLockHash: hashFile(path.join(appRoot, 'package-lock.json')),
|
|
77
|
+
proteumConfigHash: hashFile(path.join(appRoot, 'proteum.config.ts')),
|
|
78
|
+
agentsHash: hashFile(path.join(appRoot, 'AGENTS.md')),
|
|
79
|
+
env: { present: true, copied: false },
|
|
80
|
+
refresh: { status: 'ok', ranAt: timestamp },
|
|
81
|
+
dependencies: {
|
|
82
|
+
status: 'up-to-date',
|
|
83
|
+
ranAt: timestamp,
|
|
84
|
+
nodeModulesPresent: true,
|
|
85
|
+
packageLockHash: hashFile(path.join(appRoot, 'package-lock.json')),
|
|
86
|
+
},
|
|
87
|
+
runtimeStatus: { status: 'ok', checkedAt: timestamp, summary: 'runtime ok' },
|
|
88
|
+
},
|
|
89
|
+
null,
|
|
90
|
+
2,
|
|
91
|
+
),
|
|
92
|
+
);
|
|
93
|
+
};
|
|
94
|
+
|
|
58
95
|
const listen = async (server, port = 0) =>
|
|
59
96
|
await new Promise((resolve, reject) => {
|
|
60
97
|
server.once('error', reject);
|
|
@@ -150,6 +187,7 @@ test('top-level help lists the machine-scope mcp router', () => {
|
|
|
150
187
|
|
|
151
188
|
assert.equal(result.status, 0);
|
|
152
189
|
assert.match(result.stdout, /proteum mcp\b/);
|
|
190
|
+
assert.match(result.stdout, /proteum worktree\b/);
|
|
153
191
|
assert.match(result.stdout, /machine-scope MCP router/);
|
|
154
192
|
});
|
|
155
193
|
|
|
@@ -287,6 +325,52 @@ test('runtime status manifest guard points to explain manifest', () => {
|
|
|
287
325
|
assert.match(payload.nextActions[0].command, /proteum explain --manifest/);
|
|
288
326
|
});
|
|
289
327
|
|
|
328
|
+
test('runtime status blocks unbootstrapped Codex worktrees', () => {
|
|
329
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'proteum-cli-worktree-block-'));
|
|
330
|
+
const appRoot = path.join(root, '.codex', 'worktrees', 'product');
|
|
331
|
+
createProteumApp(appRoot);
|
|
332
|
+
|
|
333
|
+
const result = spawnSync(process.execPath, [cliBin, 'runtime', 'status'], {
|
|
334
|
+
cwd: appRoot,
|
|
335
|
+
encoding: 'utf8',
|
|
336
|
+
});
|
|
337
|
+
const payload = JSON.parse(result.stdout);
|
|
338
|
+
|
|
339
|
+
assert.equal(result.status, 1);
|
|
340
|
+
assert.equal(payload.ok, false);
|
|
341
|
+
assert.match(payload.summary, /has not completed Proteum worktree bootstrap/);
|
|
342
|
+
assert.match(payload.nextActions[0].command, /proteum worktree init --source <source-app-root>/);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
test('runtime status allows fresh Codex worktree bootstrap markers and reports stale refresh action', () => {
|
|
346
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'proteum-cli-worktree-fresh-'));
|
|
347
|
+
const appRoot = path.join(root, '.codex', 'worktrees', 'product');
|
|
348
|
+
createProteumApp(appRoot);
|
|
349
|
+
writeFreshWorktreeBootstrapMarker(appRoot);
|
|
350
|
+
|
|
351
|
+
const fresh = spawnSync(process.execPath, [cliBin, 'runtime', 'status'], {
|
|
352
|
+
cwd: appRoot,
|
|
353
|
+
encoding: 'utf8',
|
|
354
|
+
});
|
|
355
|
+
const freshPayload = JSON.parse(fresh.stdout);
|
|
356
|
+
|
|
357
|
+
assert.equal(fresh.status, 0);
|
|
358
|
+
assert.equal(freshPayload.ok, true);
|
|
359
|
+
assert.equal(freshPayload.data.worktreeBootstrap.state, 'fresh');
|
|
360
|
+
|
|
361
|
+
writeFile(path.join(appRoot, 'package-lock.json'), '{"lockfileVersion":3,"changed":true}\n');
|
|
362
|
+
|
|
363
|
+
const stale = spawnSync(process.execPath, [cliBin, 'runtime', 'status'], {
|
|
364
|
+
cwd: appRoot,
|
|
365
|
+
encoding: 'utf8',
|
|
366
|
+
});
|
|
367
|
+
const stalePayload = JSON.parse(stale.stdout);
|
|
368
|
+
|
|
369
|
+
assert.equal(stale.status, 1);
|
|
370
|
+
assert.equal(stalePayload.ok, false);
|
|
371
|
+
assert.match(stalePayload.nextActions[0].command, /--refresh/);
|
|
372
|
+
});
|
|
373
|
+
|
|
290
374
|
test('runtime status reports occupied configured port without probing page bodies', async () => {
|
|
291
375
|
const repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'proteum-cli-port-'));
|
|
292
376
|
const otherRoot = path.join(repoRoot, 'apps', 'other');
|