cabloy 5.1.220 → 5.1.222

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/.cabloy-version +1 -1
  2. package/.github/workflows/agent-governance.yml +2 -2
  3. package/.github/workflows/create-cabloy.yml +2 -2
  4. package/.github/workflows/docs-pages.yml +2 -2
  5. package/.github/workflows/playwright-e2e.yml +4 -4
  6. package/.github/workflows/vona-cov-pg.yml +2 -2
  7. package/.github/workflows/vona-test-crud.yml +2 -2
  8. package/.github/workflows/vona-test-mysql.yml +2 -2
  9. package/.github/workflows/vona-test-pg.yml +2 -2
  10. package/.github/workflows/vona-test-sqlite3.yml +2 -2
  11. package/.github/workflows/vona-tsc.yml +2 -2
  12. package/.github/workflows/zova-ui.yml +2 -2
  13. package/CHANGELOG.md +22 -0
  14. package/package.json +4 -3
  15. package/repo-agent-governance/managed-assets.json +15 -15
  16. package/repo-agent-governance/policies/repository.md +1 -0
  17. package/repo-agent-governance/skills/cabloy-backend-scaffold/SKILL.md +5 -1
  18. package/repo-agent-governance/skills/cabloy-backend-scaffold/references/follow-up-checklist.md +7 -5
  19. package/repo-agent-governance/skills/cabloy-frontend-scaffold/SKILL.md +5 -1
  20. package/repo-agent-governance/skills/cabloy-frontend-scaffold/references/follow-up-checklist.md +6 -4
  21. package/repo-docs/backend/unit-testing.md +5 -1
  22. package/repo-docs/frontend/openapi-sdk-guide.md +2 -0
  23. package/repo-docs/frontend/scripts.md +8 -0
  24. package/repo-docs/reference/repo-scripts.md +5 -1
  25. package/scripts/generateOpenapiIsolated.ts +18 -1
  26. package/scripts/runIsolatedTest.test.ts +9 -1
  27. package/scripts/runIsolatedTest.ts +81 -18
  28. package/scripts/strictGenerationDiagnostics.test.ts +1009 -0
  29. package/scripts/strictGenerationDiagnostics.ts +61 -0
  30. package/vona/package.original.json +3 -2
  31. package/vona/packages-cli/cabloy-cli/package.json +1 -1
  32. package/vona/packages-cli/cli/package.json +1 -1
  33. package/vona/packages-cli/cli-set-api/package.json +1 -1
  34. package/vona/packages-cli/cli-set-api/src/lib/bean/cli.bin.dev.ts +14 -4
  35. package/vona/packages-cli/cli-set-api/src/lib/bean/toolsBin/generateEntryFiles.ts +6 -1
  36. package/vona/packages-cli/cli-set-api/src/lib/bean/toolsBin/generateVonaMeta.ts +2 -10
  37. package/vona/packages-cli/cli-set-api/src/lib/bean/toolsBin/validateGenerationPaths.ts +27 -0
  38. package/vona/packages-cli/cli-set-api/test/binDevLifecycle.test.ts +1 -1
  39. package/vona/packages-cli/cli-set-api/test/strictGenerationPrivacy.test.ts +439 -0
  40. package/vona/packages-utils/process-helper/package.json +1 -1
  41. package/vona/packages-utils/process-helper/src/index.test.ts +62 -0
  42. package/vona/packages-utils/process-helper/src/index.ts +9 -4
  43. package/vona/packages-vona/vona/package.json +1 -1
  44. package/vona/packages-vona/vona-core/package.json +1 -1
  45. package/vona/packages-vona/vona-core/src/lib/core/completedReadiness.test.ts +558 -0
  46. package/vona/packages-vona/vona-core/src/lib/core/completedReadiness.ts +389 -0
  47. package/vona/packages-vona/vona-core/src/lib/core/context.ts +3 -0
  48. package/vona/packages-vona/vona-core/src/lib/core/index.ts +11 -0
  49. package/vona/packages-vona/vona-core/src/lib/core/logger/appLogger.ts +10 -2
  50. package/vona/packages-vona/vona-core/src/lib/core/logger/diagnosticLogger.test.ts +268 -68
  51. package/vona/packages-vona/vona-core/src/lib/core/meta.ts +5 -0
  52. package/vona/packages-vona/vona-core/src/lib/core/testFreshness.ts +3 -9
  53. package/vona/packages-vona/vona-core/src/lib/core/testStrictMode.ts +9 -0
  54. package/vona/packages-vona/vona-core/src/lib/framework/start.test.ts +10 -0
  55. package/vona/packages-vona/vona-core/src/lib/framework/start.ts +8 -0
  56. package/vona/packages-vona/vona-mock/package.json +1 -1
  57. package/vona/pnpm-lock.yaml +542 -236
  58. package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
  59. package/vona/src/suite-vendor/a-vona/modules/a-executor/package.json +1 -1
  60. package/vona/src/suite-vendor/a-vona/modules/a-executor/src/bean/bean.executor.ts +124 -4
  61. package/vona/src/suite-vendor/a-vona/modules/a-executor/src/types/executor.ts +7 -0
  62. package/vona/src/suite-vendor/a-vona/modules/a-executor/test/readyExisting.test.ts +423 -0
  63. package/vona/src/suite-vendor/a-vona/modules/a-executor/test/readyExistingCommit.test.ts +174 -0
  64. package/vona/src/suite-vendor/a-vona/modules/a-instance/package.json +1 -1
  65. package/vona/src/suite-vendor/a-vona/modules/a-instance/src/bean/bean.instance.ts +3 -1
  66. package/vona/src/suite-vendor/a-vona/modules/a-instance/src/lib/existingReadyAdmission.ts +125 -0
  67. package/vona/src/suite-vendor/a-vona/modules/a-instance/src/service/instance.ts +72 -25
  68. package/vona/src/suite-vendor/a-vona/modules/a-instance/test/existingReadyAdmission.test.ts +949 -0
  69. package/vona/src/suite-vendor/a-vona/modules/a-logger/package.json +1 -1
  70. package/vona/src/suite-vendor/a-vona/modules/a-logger/test/middlewareSystem.httpLog.test.ts +61 -59
  71. package/vona/src/suite-vendor/a-vona/modules/a-mail/package.json +1 -1
  72. package/vona/src/suite-vendor/a-vona/modules/a-mail/src/service/mail.ts +5 -0
  73. package/vona/src/suite-vendor/a-vona/modules/a-ormdialect/package.json +1 -1
  74. package/vona/src/suite-vendor/a-vona/modules/a-ormdialect/src/index.ts +11 -0
  75. package/vona/src/suite-vendor/a-vona/modules/a-ormdialect/src/lib/protectedSqliteRegistry.ts +356 -0
  76. package/vona/src/suite-vendor/a-vona/modules/a-startup/package.json +1 -1
  77. package/vona/src/suite-vendor/a-vona/modules/a-startup/test/devSeed.test.ts +61 -2
  78. package/vona/src/suite-vendor/a-vona/package.json +1 -1
  79. package/zova/package.original.json +3 -3
  80. package/zova/packages-cli/cli/package.json +4 -4
  81. package/zova/packages-cli/cli-set-front/package.json +2 -2
  82. package/zova/packages-cli/cli-set-front/src/lib/common/restDependencyVersions.ts +22 -1
  83. package/zova/packages-cli/cli-set-front/test/restDependencies.test.ts +137 -0
  84. package/zova/packages-zova/zova/package.json +2 -2
  85. package/zova/pnpm-lock.yaml +448 -641
  86. package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +2 -2
  87. package/zova/src/suite-vendor/a-zova/package.json +2 -2
@@ -1,10 +1,22 @@
1
1
  import assert from 'node:assert/strict';
2
- import { test } from 'node:test';
2
+ import { afterEach, beforeEach, test } from 'node:test';
3
3
  import * as Winston from 'winston';
4
4
  import Transport from 'winston-transport';
5
5
 
6
6
  import { createRequestBoundary, runWithRequestBoundary } from '../requestBoundary.ts';
7
+ import { requiresFreshTestResources, TEST_REQUIRE_FRESH_RESOURCES_ENV } from '../testStrictMode.ts';
7
8
  import { AppLogger } from './appLogger.ts';
9
+ import { getSuppressedDiagnosticLogger } from './diagnosticLogger.ts';
10
+
11
+ let previousStrictFlag: string | undefined;
12
+ beforeEach(() => {
13
+ previousStrictFlag = process.env[TEST_REQUIRE_FRESH_RESOURCES_ENV];
14
+ delete process.env[TEST_REQUIRE_FRESH_RESOURCES_ENV];
15
+ });
16
+ afterEach(() => {
17
+ if (previousStrictFlag === undefined) delete process.env[TEST_REQUIRE_FRESH_RESOURCES_ENV];
18
+ else process.env[TEST_REQUIRE_FRESH_RESOURCES_ENV] = previousStrictFlag;
19
+ });
8
20
 
9
21
  class MemoryTransport extends Transport {
10
22
  count = 0;
@@ -58,87 +70,275 @@ function payloadProbe() {
58
70
  };
59
71
  }
60
72
 
61
- test('cached diagnostic handlers never expose raw Winston to a request boundary', async () => {
73
+ function throwingProbe() {
74
+ let reads = 0;
75
+ const read = () => {
76
+ reads++;
77
+ throw new Error('Synthetic logger input must not be evaluated');
78
+ };
79
+ return {
80
+ value: new Proxy({}, { get: read, ownKeys: read }),
81
+ read,
82
+ reads: () => reads,
83
+ };
84
+ }
85
+
86
+ test(
87
+ 'cached diagnostic handlers never expose raw Winston to a request boundary',
88
+ { concurrency: false },
89
+ async () => {
90
+ const local = fixture();
91
+ const { raw, logger, sink } = local;
92
+ const exceptions = logger.exceptions;
93
+ const rejections = logger.rejections;
94
+ const probe = payloadProbe();
95
+ try {
96
+ assert.equal(local.factory.get(), logger);
97
+ assert.equal(local.creations(), 1);
98
+ assert.equal(logger.exceptions, exceptions);
99
+ assert.equal(logger.rejections, rejections);
100
+ for (const handler of [exceptions, rejections]) {
101
+ assert.equal(handler.logger, logger);
102
+ assert.equal(Reflect.set(handler, 'logger', raw), false);
103
+ }
104
+ assert.equal(raw.exceptions.logger, raw);
105
+ assert.equal(raw.rejections.logger, raw);
106
+ runWithRequestBoundary(createRequestBoundary(), () => {
107
+ assert.equal(local.factory.get(), getSuppressedDiagnosticLogger());
108
+ assert.equal(local.factory.get('unconfigured' as any), getSuppressedDiagnosticLogger());
109
+ assert.equal(local.factory.child('synthetic' as any), getSuppressedDiagnosticLogger());
110
+ logger.error(probe.payload);
111
+ logger.exceptions.logger.error(probe.payload);
112
+ logger.rejections.logger.error(probe.payload);
113
+ exceptions.logger.error(probe.payload);
114
+ rejections.logger.error(probe.payload);
115
+ });
116
+ assert.equal(probe.reads(), 0);
117
+ assert.equal(sink.count, 0);
118
+ for (const handler of [exceptions, rejections]) {
119
+ const before = probe.reads();
120
+ handler.logger.error(probe.payload);
121
+ assert.ok(probe.reads() > before);
122
+ }
123
+ assert.equal(sink.count, 2);
124
+ const before = probe.reads();
125
+ raw.error(probe.payload);
126
+ assert.ok(probe.reads() > before);
127
+ assert.equal(sink.count, 3);
128
+ } finally {
129
+ await local.close();
130
+ }
131
+ },
132
+ );
133
+
134
+ test(
135
+ 'diagnostic handler methods keep their raw receiver and map fluent returns',
136
+ { concurrency: false },
137
+ async () => {
138
+ const local = fixture();
139
+ try {
140
+ for (const key of ['exceptions', 'rejections'] as const) {
141
+ const rawHandler = local.raw[key];
142
+ const handler = local.logger[key];
143
+ const marker = {};
144
+ const original = rawHandler.getAllInfo;
145
+ Object.defineProperty(rawHandler, 'getAllInfo', {
146
+ configurable: true,
147
+ value(this: typeof rawHandler, value: unknown) {
148
+ assert.equal(this, rawHandler);
149
+ assert.equal(value, marker);
150
+ return this;
151
+ },
152
+ });
153
+ try {
154
+ assert.equal(handler.handlers, rawHandler.handlers);
155
+ assert.equal(handler.getAllInfo(marker as any), handler);
156
+ assert.equal(handler.logger, local.logger);
157
+ } finally {
158
+ Object.defineProperty(rawHandler, 'getAllInfo', {
159
+ configurable: true,
160
+ value: original,
161
+ });
162
+ }
163
+ }
164
+ const exceptions = local.logger.exceptions;
165
+ const rejections = local.logger.rejections;
166
+ local.logger.configure({ transports: [local.sink] });
167
+ assert.notEqual(local.logger.exceptions, exceptions);
168
+ assert.notEqual(local.logger.rejections, rejections);
169
+ assert.equal(local.logger.exceptions.logger, local.logger);
170
+ assert.equal(local.logger.rejections.logger, local.logger);
171
+ assert.equal(exceptions.logger, local.logger);
172
+ assert.equal(rejections.logger, local.logger);
173
+ runWithRequestBoundary(createRequestBoundary(), () => {
174
+ exceptions.logger.error('local');
175
+ rejections.logger.error('local');
176
+ });
177
+ assert.equal(local.sink.count, 0);
178
+ } finally {
179
+ await local.close();
180
+ }
181
+ },
182
+ );
183
+
184
+ test('strict logger bypasses cached clients and payloads', { concurrency: false }, async () => {
62
185
  const local = fixture();
63
- const { raw, logger, sink } = local;
64
- const exceptions = logger.exceptions;
65
- const rejections = logger.rejections;
66
- const probe = payloadProbe();
186
+ const probe = throwingProbe();
67
187
  try {
68
- assert.equal(local.factory.get(), logger);
188
+ process.env[TEST_REQUIRE_FRESH_RESOURCES_ENV] = 'true';
189
+ const suppressed = getSuppressedDiagnosticLogger();
190
+ assert.equal(local.factory.get(), suppressed);
191
+ assert.equal(local.factory.get('unconfigured' as any), suppressed);
192
+ assert.equal(local.factory.child('synthetic' as any), suppressed);
193
+ assert.equal(local.factory.child('synthetic' as any, 'unconfigured' as any), suppressed);
194
+ assert.notEqual(suppressed, local.logger);
195
+ assert.equal(suppressed.error(probe.value), suppressed);
196
+ assert.equal(suppressed.info(probe.value), suppressed);
197
+ assert.equal(suppressed.log(probe.value as any), suppressed);
198
+ assert.equal(suppressed.child(probe.value), suppressed);
199
+ assert.equal(suppressed.startTimer().done(probe.value), suppressed);
200
+ let completions = 0;
201
+ assert.equal(
202
+ suppressed.write(probe.value, () => completions++),
203
+ true,
204
+ );
205
+ assert.equal(completions, 1);
206
+ assert.equal(probe.reads(), 0);
69
207
  assert.equal(local.creations(), 1);
70
- assert.equal(logger.exceptions, exceptions);
71
- assert.equal(logger.rejections, rejections);
72
- for (const handler of [exceptions, rejections]) {
73
- assert.equal(handler.logger, logger);
74
- assert.equal(Reflect.set(handler, 'logger', raw), false);
208
+ assert.equal(local.sink.count, 0);
209
+ delete process.env[TEST_REQUIRE_FRESH_RESOURCES_ENV];
210
+ assert.equal(local.factory.get(), local.logger);
211
+ local.factory.child().info('ordinary logging remains available');
212
+ assert.equal(local.sink.count, 1);
213
+ } finally {
214
+ await local.close();
215
+ }
216
+ });
217
+
218
+ test('strict entry guards precede cache and app access', { concurrency: false }, async () => {
219
+ const local = fixture();
220
+ const probe = throwingProbe();
221
+ const descriptors = Object.getOwnPropertyDescriptors(local.factory);
222
+ const cacheKeys = Object.getOwnPropertySymbols(local.factory);
223
+ try {
224
+ assert.equal(cacheKeys.length, 2);
225
+ for (const key of cacheKeys) {
226
+ Object.defineProperty(local.factory, key, { configurable: true, get: probe.read });
75
227
  }
76
- assert.equal(raw.exceptions.logger, raw);
77
- assert.equal(raw.rejections.logger, raw);
78
- runWithRequestBoundary(createRequestBoundary(), () => {
79
- logger.error(probe.payload);
80
- logger.exceptions.logger.error(probe.payload);
81
- logger.rejections.logger.error(probe.payload);
82
- exceptions.logger.error(probe.payload);
83
- rejections.logger.error(probe.payload);
84
- });
228
+ Object.defineProperty(local.factory, 'app', { configurable: true, get: probe.read });
229
+ process.env[TEST_REQUIRE_FRESH_RESOURCES_ENV] = 'true';
230
+ assert.equal(local.factory.get(), getSuppressedDiagnosticLogger());
231
+ assert.equal(local.factory.get(probe.value as any), getSuppressedDiagnosticLogger());
232
+ Object.defineProperty(local.factory, 'get', { configurable: true, get: probe.read });
233
+ assert.equal(local.factory.child(), getSuppressedDiagnosticLogger());
234
+ assert.equal(
235
+ local.factory.child(probe.value as any, probe.value as any),
236
+ getSuppressedDiagnosticLogger(),
237
+ );
238
+ assert.equal(local.creations(), 1);
85
239
  assert.equal(probe.reads(), 0);
86
- assert.equal(sink.count, 0);
87
- for (const handler of [exceptions, rejections]) {
88
- const before = probe.reads();
89
- handler.logger.error(probe.payload);
90
- assert.ok(probe.reads() > before);
91
- }
92
- assert.equal(sink.count, 2);
93
- const before = probe.reads();
94
- raw.error(probe.payload);
95
- assert.ok(probe.reads() > before);
96
- assert.equal(sink.count, 3);
240
+ assert.equal(local.sink.count, 0);
97
241
  } finally {
242
+ delete (local.factory as any).get;
243
+ delete (local.factory as any).app;
244
+ Object.defineProperties(local.factory, descriptors);
98
245
  await local.close();
99
246
  }
100
247
  });
101
248
 
102
- test('diagnostic handler methods keep their raw receiver and map fluent returns', async () => {
249
+ test('strict logger skips real configuration factories', { concurrency: false }, async () => {
250
+ const factory = new AppLogger();
251
+ const probe = throwingProbe();
252
+ let configurations = 0;
253
+ Object.defineProperty(factory, 'app', {
254
+ value: {
255
+ config: {
256
+ logger: {
257
+ base() {
258
+ configurations++;
259
+ return probe.read();
260
+ },
261
+ clients: {
262
+ default() {
263
+ configurations++;
264
+ return probe.read();
265
+ },
266
+ },
267
+ get baseDir() {
268
+ return probe.read();
269
+ },
270
+ get rotate() {
271
+ return probe.read();
272
+ },
273
+ },
274
+ },
275
+ },
276
+ });
277
+ try {
278
+ process.env[TEST_REQUIRE_FRESH_RESOURCES_ENV] = 'true';
279
+ assert.equal(factory.get(), getSuppressedDiagnosticLogger());
280
+ assert.equal(factory.child('synthetic' as any), getSuppressedDiagnosticLogger());
281
+ assert.equal(configurations, 0);
282
+ assert.equal(probe.reads(), 0);
283
+ } finally {
284
+ await factory.dispose();
285
+ }
286
+ });
287
+
288
+ test('strict file transport fails before any input access', { concurrency: false }, () => {
289
+ const factory = new AppLogger();
290
+ const probe = throwingProbe();
291
+ const args = [probe.value, probe.value, probe.value] as unknown as Parameters<
292
+ AppLogger['createTransportFile']
293
+ >;
294
+ Object.defineProperty(factory, 'app', { get: probe.read });
295
+ process.env[TEST_REQUIRE_FRESH_RESOURCES_ENV] = 'true';
296
+ assert.throws(() => factory.createTransportFile(...args), {
297
+ message: 'Strict test file logger transport is disabled',
298
+ });
299
+ runWithRequestBoundary(createRequestBoundary(), () => {
300
+ assert.throws(() => factory.createTransportFile(...args), {
301
+ message: 'Strict test file logger transport is disabled',
302
+ });
303
+ });
304
+ assert.equal(probe.reads(), 0);
305
+ });
306
+
307
+ test('invalid strict flags never fall back to loggers', { concurrency: false }, async () => {
103
308
  const local = fixture();
309
+ const probe = throwingProbe();
310
+ Object.defineProperty(local.factory, 'app', { get: probe.read });
311
+ const args = [probe.value, probe.value, probe.value] as unknown as Parameters<
312
+ AppLogger['createTransportFile']
313
+ >;
104
314
  try {
105
- for (const key of ['exceptions', 'rejections'] as const) {
106
- const rawHandler = local.raw[key];
107
- const handler = local.logger[key];
108
- const marker = {};
109
- const original = rawHandler.getAllInfo;
110
- Object.defineProperty(rawHandler, 'getAllInfo', {
111
- configurable: true,
112
- value(this: typeof rawHandler, value: unknown) {
113
- assert.equal(this, rawHandler);
114
- assert.equal(value, marker);
115
- return this;
116
- },
117
- });
118
- try {
119
- assert.equal(handler.handlers, rawHandler.handlers);
120
- assert.equal(handler.getAllInfo(marker as any), handler);
121
- assert.equal(handler.logger, local.logger);
122
- } finally {
123
- Object.defineProperty(rawHandler, 'getAllInfo', {
124
- configurable: true,
125
- value: original,
126
- });
315
+ const env: NodeJS.ProcessEnv = { NODE_ENV: 'test', META_FLAVOR: 'normal', META_MODE: 'test' };
316
+ assert.equal(requiresFreshTestResources(), false);
317
+ assert.equal(requiresFreshTestResources(env), false);
318
+ assert.equal(
319
+ requiresFreshTestResources({ ...env, [TEST_REQUIRE_FRESH_RESOURCES_ENV]: 'true' }),
320
+ true,
321
+ );
322
+ for (const flag of ['', 'false', 'TRUE', 'synthetic-invalid-flag']) {
323
+ process.env[TEST_REQUIRE_FRESH_RESOURCES_ENV] = flag;
324
+ const expected = { message: 'Invalid strict test resource freshness flag' };
325
+ assert.throws(() => requiresFreshTestResources(), expected);
326
+ assert.throws(
327
+ () => requiresFreshTestResources({ ...env, [TEST_REQUIRE_FRESH_RESOURCES_ENV]: flag }),
328
+ expected,
329
+ );
330
+ const calls = [
331
+ () => local.factory.get(),
332
+ () => local.factory.child('synthetic' as any),
333
+ () => local.factory.createTransportFile(...args),
334
+ ];
335
+ for (const call of calls) {
336
+ assert.throws(call, expected);
337
+ runWithRequestBoundary(createRequestBoundary(), () => assert.throws(call, expected));
127
338
  }
128
339
  }
129
- const exceptions = local.logger.exceptions;
130
- const rejections = local.logger.rejections;
131
- local.logger.configure({ transports: [local.sink] });
132
- assert.notEqual(local.logger.exceptions, exceptions);
133
- assert.notEqual(local.logger.rejections, rejections);
134
- assert.equal(local.logger.exceptions.logger, local.logger);
135
- assert.equal(local.logger.rejections.logger, local.logger);
136
- assert.equal(exceptions.logger, local.logger);
137
- assert.equal(rejections.logger, local.logger);
138
- runWithRequestBoundary(createRequestBoundary(), () => {
139
- exceptions.logger.error('local');
140
- rejections.logger.error('local');
141
- });
340
+ assert.equal(probe.reads(), 0);
341
+ assert.equal(local.creations(), 1);
142
342
  assert.equal(local.sink.count, 0);
143
343
  } finally {
144
344
  await local.close();
@@ -15,6 +15,7 @@ import type { AppResource } from './resource.ts';
15
15
  import { EnumAppEvent } from '../../types/index.ts';
16
16
  import { BeanSimple } from '../bean/beanSimple.ts';
17
17
  import { AppLocale, BeanScopeContainer } from '../bean/index.ts';
18
+ import { closeCompletedReadiness, createCompletedReadiness } from './completedReadiness.ts';
18
19
  import { CtxCounter } from './ctxCounter.ts';
19
20
  import { AppHmr } from './hmr.ts';
20
21
  import { AppLogger } from './logger/appLogger.ts';
@@ -51,6 +52,8 @@ export class AppMeta extends BeanSimple {
51
52
  hmrCacheLocaleModules: TypeModuleResourceLocaleModules;
52
53
  hmrCacheConfigModules: IBeanScopeConfig;
53
54
  //
55
+ completedReadiness: ReturnType<typeof createCompletedReadiness>;
56
+
54
57
  appReady: boolean;
55
58
  appReadyInstances: Record<string, boolean>;
56
59
  //
@@ -61,6 +64,7 @@ export class AppMeta extends BeanSimple {
61
64
  appClosed: boolean;
62
65
 
63
66
  protected __init__(env: VonaConfigEnv) {
67
+ this.completedReadiness = createCompletedReadiness(this.app);
64
68
  // env
65
69
  this.env = env;
66
70
  this._prepareEnv();
@@ -121,6 +125,7 @@ export class AppMeta extends BeanSimple {
121
125
  }
122
126
 
123
127
  async close() {
128
+ closeCompletedReadiness(this.completedReadiness);
124
129
  if (!this[SymbolClosePromise]) {
125
130
  this[SymbolClosePromise] = this._closeInner();
126
131
  }
@@ -12,17 +12,11 @@ import {
12
12
  mergeTestClaimRedisOptions,
13
13
  } from './testClaim.ts';
14
14
  import { resolveVonaLocalPaths } from './testIsolation.ts';
15
+ import { requiresFreshTestResources } from './testStrictMode.ts';
15
16
 
16
- export const TEST_REQUIRE_FRESH_RESOURCES_ENV = 'VONA_TEST_REQUIRE_FRESH_RESOURCES';
17
- const freshApps = new WeakSet<VonaApplication>();
17
+ export { requiresFreshTestResources, TEST_REQUIRE_FRESH_RESOURCES_ENV } from './testStrictMode.ts';
18
18
 
19
- export function requiresFreshTestResources() {
20
- const value = process.env[TEST_REQUIRE_FRESH_RESOURCES_ENV];
21
- if (value !== undefined && value !== 'true') {
22
- throw new Error('Invalid strict test resource freshness flag');
23
- }
24
- return value === 'true';
25
- }
19
+ const freshApps = new WeakSet<VonaApplication>();
26
20
 
27
21
  export function assertFreshTestResourcesReady(app: VonaApplication) {
28
22
  if (!requiresFreshTestResources()) return;
@@ -0,0 +1,9 @@
1
+ export const TEST_REQUIRE_FRESH_RESOURCES_ENV = 'VONA_TEST_REQUIRE_FRESH_RESOURCES';
2
+
3
+ export function requiresFreshTestResources(env: NodeJS.ProcessEnv = process.env) {
4
+ const value = env[TEST_REQUIRE_FRESH_RESOURCES_ENV];
5
+ if (value !== undefined && value !== 'true') {
6
+ throw new Error('Invalid strict test resource freshness flag');
7
+ }
8
+ return value === 'true';
9
+ }
@@ -31,6 +31,7 @@ const lifecycleSource = `
31
31
  return nextResolve(specifier, context);
32
32
  },
33
33
  });
34
+ const { createCompletedReadiness } = await import(new URL('../core/completedReadiness.ts', startUrl).href);
34
35
  const { Start } = await import(startUrl);
35
36
  const [projectPath, lockPath, lockContents, failingPhase] = process.argv.slice(1);
36
37
  const phases = [];
@@ -39,6 +40,13 @@ const lifecycleSource = `
39
40
  globalThis.phase = async phase => {
40
41
  assert.equal(await readFile(lockPath, 'utf8'), lockContents);
41
42
  phases.push(phase);
43
+ assert.equal(app.meta.completedReadiness.snapshot().application, 'starting');
44
+ if (phase === 'Started') {
45
+ assert.equal(app.meta.appReady, true);
46
+ assert.equal(app.meta.appStarted, true);
47
+ await Promise.resolve();
48
+ assert.equal(app.meta.completedReadiness.snapshot().application, 'starting');
49
+ }
42
50
  if (phase === failingPhase) throw failure;
43
51
  };
44
52
  const config = {
@@ -62,6 +70,7 @@ const lifecycleSource = `
62
70
  events.push({ event, error, phases: [...phases] });
63
71
  },
64
72
  };
73
+ app.meta.completedReadiness = createCompletedReadiness(app);
65
74
  const start = new Start(app);
66
75
  const order = ['Config', 'Freshness', 'Load', 'Start', 'Ready', 'Started'];
67
76
  if (failingPhase) {
@@ -76,6 +85,7 @@ const lifecycleSource = `
76
85
  assert.deepEqual(phases, order);
77
86
  assert.equal(app.meta.appStartError, undefined);
78
87
  }
88
+ assert.equal(app.meta.completedReadiness.snapshot().application, failingPhase ? 'failed' : 'ready');
79
89
  const reachesStarted = !failingPhase || failingPhase === 'Started';
80
90
  assert.equal(app.meta.appStarted, reachesStarted ? true : undefined);
81
91
  const reachesReady = reachesStarted || failingPhase === 'Ready';
@@ -2,6 +2,10 @@ import type { VonaConfig, VonaConfigOptional } from '../../types/index.ts';
2
2
  import type { VonaApplication } from '../core/application.ts';
3
3
 
4
4
  import { EnumAppEvent } from '../../types/index.ts';
5
+ import {
6
+ beginCompletedApplication,
7
+ finishCompletedApplication,
8
+ } from '../core/completedReadiness.ts';
5
9
  import { assertIsolatedTestConfig } from '../core/isolationGuard.ts';
6
10
  import { registerTestClaimResourceInventory } from '../core/testClaim.ts';
7
11
  import { assertFreshTestResources } from '../core/testFreshness.ts';
@@ -17,6 +21,8 @@ export class Start {
17
21
 
18
22
  async start() {
19
23
  const app = this.app;
24
+ const owner = app.meta.completedReadiness;
25
+ const attempt = beginCompletedApplication(owner);
20
26
  try {
21
27
  await this._start_appConfig();
22
28
  await assertFreshTestResources(app);
@@ -24,7 +30,9 @@ export class Start {
24
30
  await this._start_appStart();
25
31
  await this._start_appReady();
26
32
  await this._start_appStarted();
33
+ finishCompletedApplication(owner, attempt, true);
27
34
  } catch (err) {
35
+ finishCompletedApplication(owner, attempt, false);
28
36
  // record
29
37
  app.meta.appStartError = err as Error;
30
38
  // event: appReadyError
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-mock",
3
- "version": "6.1.48",
3
+ "version": "6.1.50",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "vona mock",
6
6
  "keywords": [