cabloy 5.1.219 → 5.1.221
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/.cabloy-version +1 -1
- package/.github/workflows/agent-governance.yml +2 -2
- package/.github/workflows/create-cabloy.yml +3 -3
- package/.github/workflows/docs-pages.yml +2 -2
- package/.github/workflows/playwright-e2e.yml +4 -4
- package/.github/workflows/vona-cov-pg.yml +2 -2
- package/.github/workflows/vona-test-crud.yml +2 -2
- package/.github/workflows/vona-test-mysql.yml +2 -2
- package/.github/workflows/vona-test-pg.yml +2 -2
- package/.github/workflows/vona-test-sqlite3.yml +2 -2
- package/.github/workflows/vona-tsc.yml +2 -2
- package/.github/workflows/zova-ui.yml +2 -2
- package/CHANGELOG.md +23 -0
- package/README.md +1 -1
- package/package.json +4 -3
- package/repo-docs/frontend/openapi-sdk-guide.md +2 -0
- package/repo-docs/fullstack/quickstart.md +3 -3
- package/repo-docs/reference/repo-scripts.md +16 -0
- package/scripts/generateOpenapiIsolated.test.ts +25 -0
- package/scripts/generateOpenapiIsolated.ts +46 -12
- package/scripts/runIsolatedTest.test.ts +165 -45
- package/scripts/runIsolatedTest.ts +111 -24
- package/scripts/strictGenerationDiagnostics.test.ts +1009 -0
- package/scripts/strictGenerationDiagnostics.ts +61 -0
- package/scripts/strictGenerationStartup.test.ts +212 -0
- package/vona/package.original.json +3 -2
- package/vona/packages-cli/cli/package.json +1 -1
- package/vona/packages-cli/cli-set-api/package.json +1 -1
- package/vona/packages-cli/cli-set-api/src/lib/bean/cli.bin.dev.ts +14 -4
- package/vona/packages-cli/cli-set-api/src/lib/bean/toolsBin/generateEntryFiles.ts +6 -1
- package/vona/packages-cli/cli-set-api/src/lib/bean/toolsBin/generateVonaMeta.ts +2 -10
- package/vona/packages-cli/cli-set-api/src/lib/bean/toolsBin/validateGenerationPaths.ts +27 -0
- package/vona/packages-cli/cli-set-api/test/binDevLifecycle.test.ts +1 -1
- package/vona/packages-cli/cli-set-api/test/strictGenerationPrivacy.test.ts +439 -0
- package/vona/packages-vona/vona/package.json +1 -1
- package/vona/packages-vona/vona-core/package.json +1 -1
- package/vona/packages-vona/vona-core/src/lib/bean/beanBase.ts +4 -0
- package/vona/packages-vona/vona-core/src/lib/core/asyncLocalStorage.ts +84 -12
- package/vona/packages-vona/vona-core/src/lib/core/index.ts +14 -0
- package/vona/packages-vona/vona-core/src/lib/core/logger/appLogger.ts +19 -3
- package/vona/packages-vona/vona-core/src/lib/core/logger/diagnosticLogger.test.ts +346 -0
- package/vona/packages-vona/vona-core/src/lib/core/logger/diagnosticLogger.ts +138 -0
- package/vona/packages-vona/vona-core/src/lib/core/logger/index.ts +1 -0
- package/vona/packages-vona/vona-core/src/lib/core/logger/utils.ts +25 -5
- package/vona/packages-vona/vona-core/src/lib/core/requestBoundary.ts +89 -0
- package/vona/packages-vona/vona-core/src/lib/core/testFreshness.test.ts +379 -0
- package/vona/packages-vona/vona-core/src/lib/core/testFreshness.ts +232 -0
- package/vona/packages-vona/vona-core/src/lib/core/testStrictMode.ts +9 -0
- package/vona/packages-vona/vona-core/src/lib/framework/start.test.ts +4 -3
- package/vona/packages-vona/vona-core/src/lib/framework/start.ts +2 -0
- package/vona/packages-vona/vona-mock/package.json +1 -1
- package/vona/pnpm-lock.yaml +691 -356
- package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-executor/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-executor/src/bean/bean.executor.ts +111 -60
- package/vona/src/suite-vendor/a-vona/modules/a-executor/src/service/executor.ts +6 -6
- package/vona/src/suite-vendor/a-vona/modules/a-executor/src/types/executor.ts +13 -1
- package/vona/src/suite-vendor/a-vona/modules/a-jwt/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-jwt/src/lib/jwtClientLogging.test.ts +182 -0
- package/vona/src/suite-vendor/a-vona/modules/a-jwt/src/service/jwtClient.ts +2 -8
- package/vona/src/suite-vendor/a-vona/modules/a-logger/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-logger/src/bean/aopMethod.log.ts +4 -1
- package/vona/src/suite-vendor/a-vona/modules/a-logger/src/bean/bean.logger.ts +9 -0
- package/vona/src/suite-vendor/a-vona/modules/a-logger/src/bean/middlewareSystem.httpLog.ts +27 -14
- package/vona/src/suite-vendor/a-vona/modules/a-logger/src/lib/redactHttpLog.test.ts +207 -0
- package/vona/src/suite-vendor/a-vona/modules/a-logger/src/lib/redactHttpLog.ts +30 -0
- package/vona/src/suite-vendor/a-vona/modules/a-logger/test/middlewareSystem.httpLog.test.ts +33 -1
- package/vona/src/suite-vendor/a-vona/modules/a-ormdialect/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-ormdialect/src/index.ts +11 -0
- package/vona/src/suite-vendor/a-vona/modules/a-ormdialect/src/lib/protectedSqliteRegistry.ts +356 -0
- package/vona/src/suite-vendor/a-vona/modules/a-startup/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-startup/src/service/startup.ts +6 -0
- package/vona/src/suite-vendor/a-vona/modules/a-startup/test/devSeed.test.ts +2 -2
- package/vona/src/suite-vendor/a-vona/modules/a-telemetry/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/bean/bean.telemetry.ts +8 -1
- package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/bean/middlewareSystem.trace.ts +9 -14
- package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/lib/spanDiagnostics.ts +70 -0
- package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/service/telemetry.ts +37 -23
- package/vona/src/suite-vendor/a-vona/modules/a-version/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-version/src/service/database.ts +31 -4
- package/vona/src/suite-vendor/a-vona/package.json +2 -2
- package/zova/package.original.json +3 -2
- package/zova/packages-cli/cli/package.json +2 -2
- package/zova/packages-cli/cli-set-front/package.json +1 -1
- package/zova/packages-cli/cli-set-front/src/lib/bean/cli.bin.buildRest.ts +11 -101
- package/zova/packages-cli/cli-set-front/src/lib/common/restDependencies.ts +78 -0
- package/zova/packages-cli/cli-set-front/src/lib/common/restDependencyVersions.ts +252 -0
- package/zova/packages-cli/cli-set-front/test/restDependencies.test.ts +568 -0
- package/zova/pnpm-lock.yaml +442 -564
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import type { Context, Span, SpanOptions } from '@opentelemetry/api';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
context,
|
|
5
|
+
INVALID_SPAN_CONTEXT,
|
|
6
|
+
propagation,
|
|
7
|
+
ROOT_CONTEXT,
|
|
8
|
+
SpanKind,
|
|
9
|
+
trace,
|
|
10
|
+
} from '@opentelemetry/api';
|
|
4
11
|
import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks';
|
|
5
12
|
import { W3CTraceContextPropagator } from '@opentelemetry/core';
|
|
6
13
|
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
|
|
@@ -11,12 +18,15 @@ import {
|
|
|
11
18
|
TraceIdRatioBasedSampler,
|
|
12
19
|
} from '@opentelemetry/sdk-trace-base';
|
|
13
20
|
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
|
|
14
|
-
import { BeanBase } from 'vona';
|
|
21
|
+
import { BeanBase, captureRequestBoundary, getRequestBoundary } from 'vona';
|
|
15
22
|
import { Service } from 'vona-module-a-bean';
|
|
16
23
|
|
|
17
24
|
import type { ITelemetryCarrier } from '../types/telemetry.ts';
|
|
18
25
|
|
|
19
26
|
import { createIngressTrustChecker } from '../lib/ingress.ts';
|
|
27
|
+
import { endHttpSpan, recordSpanException } from '../lib/spanDiagnostics.ts';
|
|
28
|
+
|
|
29
|
+
const nonRecordingSpan = trace.wrapSpanContext(INVALID_SPAN_CONTEXT);
|
|
20
30
|
|
|
21
31
|
const carrierGetter = {
|
|
22
32
|
get(carrier: ITelemetryCarrier, key: string) {
|
|
@@ -44,10 +54,12 @@ export class ServiceTelemetry extends BeanBase {
|
|
|
44
54
|
private _enabled = false;
|
|
45
55
|
|
|
46
56
|
get enabled() {
|
|
57
|
+
if (getRequestBoundary()) return false;
|
|
47
58
|
return this._enabled;
|
|
48
59
|
}
|
|
49
60
|
|
|
50
61
|
init() {
|
|
62
|
+
if (getRequestBoundary()) return;
|
|
51
63
|
if (this._provider || this._enabled) return;
|
|
52
64
|
const config = this.scope.config;
|
|
53
65
|
this._isTrustedIngress = createIngressTrustChecker(config.ingress);
|
|
@@ -96,64 +108,64 @@ export class ServiceTelemetry extends BeanBase {
|
|
|
96
108
|
}
|
|
97
109
|
|
|
98
110
|
get activeContext() {
|
|
111
|
+
if (getRequestBoundary()) return ROOT_CONTEXT;
|
|
99
112
|
return this.ctx?.state.telemetry?.context ?? context.active();
|
|
100
113
|
}
|
|
101
114
|
|
|
102
115
|
extractCarrier(carrier?: ITelemetryCarrier) {
|
|
116
|
+
if (getRequestBoundary()) return ROOT_CONTEXT;
|
|
103
117
|
if (!carrier) return context.active();
|
|
104
118
|
return propagation.extract(context.active(), carrier, carrierGetter);
|
|
105
119
|
}
|
|
106
120
|
|
|
107
121
|
isTrustedIngress(peerAddress?: string, internalHeaderValue?: string) {
|
|
122
|
+
if (getRequestBoundary()) return false;
|
|
108
123
|
this._isTrustedIngress ??= createIngressTrustChecker(this.scope.config.ingress);
|
|
109
124
|
return this._isTrustedIngress(peerAddress, internalHeaderValue);
|
|
110
125
|
}
|
|
111
126
|
|
|
112
127
|
extractHttpIngressCarrier(trusted: boolean, carrier: ITelemetryCarrier) {
|
|
128
|
+
if (getRequestBoundary()) return ROOT_CONTEXT;
|
|
113
129
|
return trusted ? this.extractCarrier(carrier) : this.extractCarrier();
|
|
114
130
|
}
|
|
115
131
|
|
|
116
|
-
injectCarrier(source
|
|
132
|
+
injectCarrier(source?: Context): ITelemetryCarrier {
|
|
133
|
+
if (getRequestBoundary()) return { version: 1 };
|
|
117
134
|
const carrier: ITelemetryCarrier = { version: 1 };
|
|
118
|
-
propagation.inject(source, carrier, carrierSetter);
|
|
135
|
+
propagation.inject(source === undefined ? this.activeContext : source, carrier, carrierSetter);
|
|
119
136
|
return carrier;
|
|
120
137
|
}
|
|
121
138
|
|
|
122
|
-
startSpan(name: string, options?: SpanOptions, parent
|
|
123
|
-
|
|
139
|
+
startSpan(name: string, options?: SpanOptions, parent?: Context) {
|
|
140
|
+
if (getRequestBoundary()) return nonRecordingSpan;
|
|
141
|
+
return trace
|
|
142
|
+
.getTracer('vona-module-a-telemetry')
|
|
143
|
+
.startSpan(name, options, parent === undefined ? this.activeContext : parent);
|
|
124
144
|
}
|
|
125
145
|
|
|
126
146
|
withSpan<RESULT>(span: Span, fn: () => RESULT): RESULT {
|
|
147
|
+
if (getRequestBoundary()) return fn();
|
|
127
148
|
const active = trace.setSpan(this.activeContext, span);
|
|
128
149
|
return context.with(active, fn);
|
|
129
150
|
}
|
|
130
151
|
|
|
131
|
-
createContext(span: Span, parent
|
|
132
|
-
|
|
152
|
+
createContext(span: Span, parent?: Context) {
|
|
153
|
+
if (getRequestBoundary()) return ROOT_CONTEXT;
|
|
154
|
+
return trace.setSpan(parent === undefined ? this.activeContext : parent, span);
|
|
133
155
|
}
|
|
134
156
|
|
|
135
157
|
recordException(span: Span, error: unknown) {
|
|
136
|
-
if (
|
|
137
|
-
|
|
138
|
-
span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
span.setStatus({ code: SpanStatusCode.ERROR, message: String(error) });
|
|
158
|
+
if (getRequestBoundary()) return;
|
|
159
|
+
recordSpanException(span, error, captureRequestBoundary());
|
|
142
160
|
}
|
|
143
161
|
|
|
144
162
|
endHttpSpan(span: Span, statusCode: number, aborted = false) {
|
|
145
|
-
if (
|
|
146
|
-
|
|
147
|
-
code: SpanStatusCode.ERROR,
|
|
148
|
-
message: aborted ? 'client aborted' : undefined,
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
span.setAttribute('http.response.status_code', statusCode);
|
|
152
|
-
if (aborted) span.setAttribute('vona.http.aborted', true);
|
|
153
|
-
span.end();
|
|
163
|
+
if (getRequestBoundary()) return;
|
|
164
|
+
endHttpSpan(span, statusCode, aborted);
|
|
154
165
|
}
|
|
155
166
|
|
|
156
167
|
getLogContext() {
|
|
168
|
+
if (getRequestBoundary()) return;
|
|
157
169
|
if (!this._enabled) return;
|
|
158
170
|
const spanContext = trace.getSpanContext(this.activeContext);
|
|
159
171
|
if (!spanContext || !trace.isSpanContextValid(spanContext)) return;
|
|
@@ -166,6 +178,7 @@ export class ServiceTelemetry extends BeanBase {
|
|
|
166
178
|
}
|
|
167
179
|
|
|
168
180
|
createServerSpan(method: string, parent: Context) {
|
|
181
|
+
if (getRequestBoundary()) return nonRecordingSpan;
|
|
169
182
|
return this.startSpan(`HTTP ${method}`, { kind: SpanKind.SERVER }, parent);
|
|
170
183
|
}
|
|
171
184
|
|
|
@@ -175,6 +188,7 @@ export class ServiceTelemetry extends BeanBase {
|
|
|
175
188
|
controllerBeanFullName: string,
|
|
176
189
|
action: string,
|
|
177
190
|
) {
|
|
191
|
+
if (getRequestBoundary()) return;
|
|
178
192
|
const span = this.ctx.state.telemetry?.serverSpan;
|
|
179
193
|
if (!span) return;
|
|
180
194
|
span.updateName(`HTTP ${method.toUpperCase()} ${routePath}`);
|
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
BeanBase,
|
|
10
10
|
assertDisposableDatabaseName as assertDisposableDatabaseNameCore,
|
|
11
11
|
assertIsolatedTestPath,
|
|
12
|
+
assertFreshTestResourcesReady,
|
|
13
|
+
requiresFreshTestResources,
|
|
12
14
|
assertTestRunOwner,
|
|
13
15
|
resolveVonaLocalPaths,
|
|
14
16
|
withIsolatedTestDatabaseDiscovery,
|
|
@@ -39,6 +41,7 @@ interface IDevDatabasePreparation {
|
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
const devDatabasePreparations = new WeakMap<VonaApplication, IDevDatabasePreparation>();
|
|
44
|
+
const freshDatabaseCreations = new WeakMap<VonaApplication, Map<string, string>>();
|
|
42
45
|
|
|
43
46
|
export function assertDisposableDatabaseName(
|
|
44
47
|
databaseName: string,
|
|
@@ -318,7 +321,7 @@ export class ServiceDatabase extends BeanBase {
|
|
|
318
321
|
// client
|
|
319
322
|
const client = this.bean.database.current.client;
|
|
320
323
|
// get current database name
|
|
321
|
-
const
|
|
324
|
+
const currentDatabaseName = client.getDatabaseName();
|
|
322
325
|
const sqliteRoot =
|
|
323
326
|
client.db.dialectName === 'better-sqlite3'
|
|
324
327
|
? resolveVonaLocalPaths(
|
|
@@ -327,9 +330,12 @@ export class ServiceDatabase extends BeanBase {
|
|
|
327
330
|
this.app.meta.env.VONA_TEST_RUN_ID,
|
|
328
331
|
).sqliteRoot
|
|
329
332
|
: undefined;
|
|
330
|
-
const currentFilename = sqliteRoot
|
|
333
|
+
const currentFilename = sqliteRoot
|
|
334
|
+
? path.basename(currentDatabaseName, '.db')
|
|
335
|
+
: currentDatabaseName;
|
|
331
336
|
const isTestDatabase = currentFilename.startsWith(databasePrefix);
|
|
332
|
-
if (isTestDatabase)
|
|
337
|
+
if (isTestDatabase)
|
|
338
|
+
assertDisposableDatabaseName(currentDatabaseName, databasePrefix, sqliteRoot);
|
|
333
339
|
if (
|
|
334
340
|
this.app.meta.isTest &&
|
|
335
341
|
this.app.meta.env.VONA_TEST_RUN_ID &&
|
|
@@ -337,11 +343,32 @@ export class ServiceDatabase extends BeanBase {
|
|
|
337
343
|
!isTestDatabase
|
|
338
344
|
) {
|
|
339
345
|
throw new Error(
|
|
340
|
-
`Isolated test database is outside its disposable namespace: ${
|
|
346
|
+
`Isolated test database is outside its disposable namespace: ${currentDatabaseName}`,
|
|
341
347
|
);
|
|
342
348
|
}
|
|
343
349
|
// test db
|
|
344
350
|
if (this.app.meta.isTest) {
|
|
351
|
+
if (requiresFreshTestResources()) {
|
|
352
|
+
assertFreshTestResourcesReady(this.app);
|
|
353
|
+
const key = `${client.clientName}\0${databasePrefix}`;
|
|
354
|
+
const created = freshDatabaseCreations.get(this.app);
|
|
355
|
+
if (isTestDatabase) {
|
|
356
|
+
if (created?.get(key) !== currentDatabaseName) {
|
|
357
|
+
throw new Error('Strict test cannot reuse an unowned database');
|
|
358
|
+
}
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
if ((await this.__fetchDatabases(client, instanceName, configInstanceBase)).length !== 0) {
|
|
362
|
+
throw new Error('Strict test database namespace is no longer fresh');
|
|
363
|
+
}
|
|
364
|
+
const name = await this.__createDatabase(client, instanceName, configInstanceBase);
|
|
365
|
+
assertDisposableDatabaseName(name, databasePrefix, sqliteRoot);
|
|
366
|
+
const creations = created ?? new Map<string, string>();
|
|
367
|
+
creations.set(key, name);
|
|
368
|
+
freshDatabaseCreations.set(this.app, creations);
|
|
369
|
+
await client.changeConfigConnectionAndReloadWorker(name);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
345
372
|
// check
|
|
346
373
|
if (isTestDatabase) {
|
|
347
374
|
// donothing
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vona-suite-a-vona",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.103",
|
|
4
4
|
"gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
|
|
5
5
|
"description": "",
|
|
6
6
|
"author": "",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"vona-module-a-static": "workspace:^",
|
|
55
55
|
"vona-module-a-summer": "workspace:^",
|
|
56
56
|
"vona-module-a-swagger": "workspace:^",
|
|
57
|
-
"vona-module-a-telemetry": "^5.0.
|
|
57
|
+
"vona-module-a-telemetry": "^5.0.3",
|
|
58
58
|
"vona-module-a-upload": "workspace:^",
|
|
59
59
|
"vona-module-a-user": "workspace:^",
|
|
60
60
|
"vona-module-a-validation": "workspace:^",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"url": "git+https://github.com/zovajs/zova.git"
|
|
15
15
|
},
|
|
16
16
|
"type": "module",
|
|
17
|
-
"packageManager": "pnpm@
|
|
17
|
+
"packageManager": "pnpm@12.14.0",
|
|
18
18
|
"scripts": {
|
|
19
19
|
"zova": "node ./packages-cli/cli/src/bin/zova.ts",
|
|
20
20
|
"dev": "npm run dev:ssr:admin",
|
|
@@ -96,6 +96,7 @@
|
|
|
96
96
|
"oxlint": "^1.77.0"
|
|
97
97
|
},
|
|
98
98
|
"engines": {
|
|
99
|
-
"node": ">=
|
|
99
|
+
"node": ">=24.4.0",
|
|
100
|
+
"pnpm": ">=11.5.2"
|
|
100
101
|
}
|
|
101
102
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zova-cli",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.117",
|
|
4
4
|
"gitHead": "6f675a8cc46d596142c591c28a40cc4d82fcc6cc",
|
|
5
5
|
"description": "zova cli",
|
|
6
6
|
"keywords": [
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"@cabloy/process-helper": "^3.1.8",
|
|
45
45
|
"fs-extra": "^11.3.5",
|
|
46
46
|
"semver": "^7.6.2",
|
|
47
|
-
"zova-cli-set-front": "^1.2.
|
|
47
|
+
"zova-cli-set-front": "^1.2.115"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"clean-package": "^2.2.0",
|
|
@@ -12,9 +12,10 @@ import fse from 'fs-extra';
|
|
|
12
12
|
import path from 'node:path';
|
|
13
13
|
import { rimraf } from 'rimraf';
|
|
14
14
|
import { build } from 'tsdown';
|
|
15
|
-
import yaml from 'yaml';
|
|
16
15
|
import { createConfigUtils, saveJSONFile } from 'zova-vite';
|
|
17
16
|
|
|
17
|
+
import { extractRestDependencies } from '../common/restDependencies.ts';
|
|
18
|
+
import { extractRestDependencyVersions } from '../common/restDependencyVersions.ts';
|
|
18
19
|
import { loadJSONFile } from '../common/utils.ts';
|
|
19
20
|
import { __ThisSetName__ } from '../this.ts';
|
|
20
21
|
|
|
@@ -61,6 +62,7 @@ interface IBinBuildRestContext {
|
|
|
61
62
|
outDir: string;
|
|
62
63
|
bundleModules?: string[];
|
|
63
64
|
sideEffectImportModuleNames?: string[];
|
|
65
|
+
moduleRoots?: string[];
|
|
64
66
|
packageJson?: IBinBuildRestPackageJson;
|
|
65
67
|
}
|
|
66
68
|
|
|
@@ -116,11 +118,13 @@ export class CliBinBuildRest extends BeanCliBase {
|
|
|
116
118
|
const modules: string[] = [];
|
|
117
119
|
const bundleModules: string[] = [];
|
|
118
120
|
const sideEffectImportModuleNames: string[] = [];
|
|
121
|
+
const moduleRoots: string[] = [];
|
|
119
122
|
for (const module of modulesMeta.modulesArray) {
|
|
120
123
|
const moduleName = module.info.fullName;
|
|
121
124
|
modules.push(`import '${moduleName}';`);
|
|
122
125
|
modules.push(`export * from '${moduleName}';`);
|
|
123
126
|
const moduleRoot = module.root.replaceAll('\\', '/');
|
|
127
|
+
moduleRoots.push(moduleRoot);
|
|
124
128
|
if (moduleRoot.includes('/src/module/') || moduleRoot.includes('/src/suite/')) {
|
|
125
129
|
bundleModules.push(moduleName);
|
|
126
130
|
} else {
|
|
@@ -129,6 +133,7 @@ export class CliBinBuildRest extends BeanCliBase {
|
|
|
129
133
|
}
|
|
130
134
|
context.bundleModules = bundleModules;
|
|
131
135
|
context.sideEffectImportModuleNames = sideEffectImportModuleNames;
|
|
136
|
+
context.moduleRoots = moduleRoots;
|
|
132
137
|
argv.Modules = modules.join('\n');
|
|
133
138
|
// Name/Version
|
|
134
139
|
argv.Name = bundleName;
|
|
@@ -181,8 +186,7 @@ export class CliBinBuildRest extends BeanCliBase {
|
|
|
181
186
|
tsconfig: 'tsconfig.rest.json',
|
|
182
187
|
clean: false,
|
|
183
188
|
dts: {
|
|
184
|
-
|
|
185
|
-
resolver: 'tsc',
|
|
189
|
+
resolver: 'oxc',
|
|
186
190
|
tsgo: true,
|
|
187
191
|
eager: true,
|
|
188
192
|
tsconfig: 'tsconfig.rest.json',
|
|
@@ -213,13 +217,13 @@ export class CliBinBuildRest extends BeanCliBase {
|
|
|
213
217
|
}
|
|
214
218
|
|
|
215
219
|
async _build(buildContext: IBinBuildRestContext) {
|
|
216
|
-
const { projectPath, flavor, bundleNameCopy, outDir, packageJson } = buildContext;
|
|
220
|
+
const { projectPath, flavor, bundleNameCopy, outDir, packageJson, moduleRoots } = buildContext;
|
|
217
221
|
// build
|
|
218
222
|
await this._buildTs(buildContext);
|
|
219
223
|
await this._buildDts(buildContext);
|
|
220
224
|
// deps
|
|
221
225
|
const deps = await _extractDeps(path.join(outDir, 'index.d.mts'));
|
|
222
|
-
const depsVersion = await
|
|
226
|
+
const depsVersion = await extractRestDependencyVersions(projectPath, deps, moduleRoots);
|
|
223
227
|
// package.json
|
|
224
228
|
const pkgContent = {
|
|
225
229
|
...packageJson!,
|
|
@@ -256,100 +260,6 @@ function _copyToTarget(
|
|
|
256
260
|
}
|
|
257
261
|
|
|
258
262
|
async function _extractDeps(filePath: string): Promise<string[]> {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
content = content.substring(0, pos);
|
|
262
|
-
const packageNames = new Set();
|
|
263
|
-
const re = /import\s+(?:[\s\S]*?\s+from\s+)?['"]([^'"]+)['"]/g;
|
|
264
|
-
|
|
265
|
-
let match: any;
|
|
266
|
-
while (true) {
|
|
267
|
-
match = re.exec(content);
|
|
268
|
-
if (!match) break;
|
|
269
|
-
let pkg: string = match[1];
|
|
270
|
-
if (pkg.startsWith('.') || pkg.startsWith('/')) continue;
|
|
271
|
-
if (pkg.startsWith('@')) {
|
|
272
|
-
if (pkg.split('/').length > 2) {
|
|
273
|
-
pkg = pkg.split('/').slice(0, 2).join('/');
|
|
274
|
-
}
|
|
275
|
-
} else {
|
|
276
|
-
if (pkg.split('/').length > 1) {
|
|
277
|
-
pkg = pkg.split('/').slice(0, 1).join('/');
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
packageNames.add(pkg);
|
|
281
|
-
}
|
|
282
|
-
return Array.from(packageNames).sort() as string[];
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
async function _extractDepsVersion(
|
|
286
|
-
projectPath: string,
|
|
287
|
-
deps: string[],
|
|
288
|
-
): Promise<Record<string, string>> {
|
|
289
|
-
const lockfilePath = path.join(projectPath, 'pnpm-lock.yaml');
|
|
290
|
-
const lockfileContent = (await fse.readFile(lockfilePath)).toString();
|
|
291
|
-
const parsedLockfile = yaml.parse(lockfileContent);
|
|
292
|
-
const depsVersion: Record<string, string> = {};
|
|
293
|
-
for (const dep of deps) {
|
|
294
|
-
let dependencyRange = getPackageDependencyRangeFromLock(parsedLockfile, dep);
|
|
295
|
-
if (!dependencyRange) {
|
|
296
|
-
const version = await getPackageVersionFromNodeModules(projectPath, dep);
|
|
297
|
-
dependencyRange = version ? `^${version}` : null;
|
|
298
|
-
}
|
|
299
|
-
if (!dependencyRange) {
|
|
300
|
-
console.warn('dep version not found: ', dep);
|
|
301
|
-
continue;
|
|
302
|
-
}
|
|
303
|
-
depsVersion[dep] = dependencyRange;
|
|
304
|
-
}
|
|
305
|
-
return depsVersion;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
function getPackageDependencyRangeFromLock(
|
|
309
|
-
parsedLockfile: any,
|
|
310
|
-
packageName: string,
|
|
311
|
-
): string | null {
|
|
312
|
-
const importer = parsedLockfile.importers?.['.'];
|
|
313
|
-
const sections = ['dependencies', 'devDependencies', 'optionalDependencies'];
|
|
314
|
-
for (const section of sections) {
|
|
315
|
-
const dependencyRange = getImporterDependencyRange(importer?.[section]?.[packageName]);
|
|
316
|
-
if (dependencyRange) return dependencyRange;
|
|
317
|
-
}
|
|
318
|
-
return null;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
function getImporterDependencyRange(
|
|
322
|
-
importerDependency: { specifier?: string; version?: string } | undefined,
|
|
323
|
-
): string | null {
|
|
324
|
-
if (!importerDependency) return null;
|
|
325
|
-
|
|
326
|
-
const specifier = importerDependency.specifier;
|
|
327
|
-
if (specifier?.startsWith('npm:')) return specifier;
|
|
328
|
-
|
|
329
|
-
const version = normalizeLockfileVersion(importerDependency.version);
|
|
330
|
-
if (!version) return null;
|
|
331
|
-
return `^${version}`;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
function normalizeLockfileVersion(version: string | undefined): string | null {
|
|
335
|
-
if (!version) return null;
|
|
336
|
-
if (version.startsWith('link:') || version.startsWith('file:')) return null;
|
|
337
|
-
|
|
338
|
-
const versionWithoutPeers = version.split('(')[0];
|
|
339
|
-
if (!versionWithoutPeers) return null;
|
|
340
|
-
if (/^\d/.test(versionWithoutPeers)) return versionWithoutPeers;
|
|
341
|
-
|
|
342
|
-
const aliasSeparator = versionWithoutPeers.lastIndexOf('@');
|
|
343
|
-
if (aliasSeparator === -1) return null;
|
|
344
|
-
|
|
345
|
-
const aliasedVersion = versionWithoutPeers.slice(aliasSeparator + 1);
|
|
346
|
-
if (!aliasedVersion || !/^\d/.test(aliasedVersion)) return null;
|
|
347
|
-
return aliasedVersion;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
async function getPackageVersionFromNodeModules(projectPath: string, packageName: string) {
|
|
351
|
-
const pkgFile = path.join(projectPath, 'node_modules', packageName, 'package.json');
|
|
352
|
-
if (!fse.existsSync(pkgFile)) return null;
|
|
353
|
-
const pkgContent = await loadJSONFile(pkgFile);
|
|
354
|
-
return pkgContent.version;
|
|
263
|
+
const content = await fse.readFile(filePath, 'utf8');
|
|
264
|
+
return extractRestDependencies(content);
|
|
355
265
|
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { isBuiltin } from 'node:module';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import ts from 'typescript';
|
|
4
|
+
|
|
5
|
+
export function extractRestDependencies(content: string): string[] {
|
|
6
|
+
const source = ts.createSourceFile('index.d.mts', content, ts.ScriptTarget.Latest);
|
|
7
|
+
// createSourceFile exposes syntax diagnostics at runtime, but not in its public return type.
|
|
8
|
+
const { parseDiagnostics } = source as ts.SourceFile & {
|
|
9
|
+
parseDiagnostics: readonly ts.Diagnostic[];
|
|
10
|
+
};
|
|
11
|
+
if (parseDiagnostics.length) {
|
|
12
|
+
const diagnostic = parseDiagnostics[0];
|
|
13
|
+
const { line, character } = source.getLineAndCharacterOfPosition(diagnostic.start ?? 0);
|
|
14
|
+
throw new Error(
|
|
15
|
+
`Invalid REST declaration at ${source.fileName}:${line + 1}:${character + 1}: ${ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n')}`,
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
for (const kind of ['path', 'types'] as const) {
|
|
19
|
+
const references = kind === 'path' ? source.referencedFiles : source.typeReferenceDirectives;
|
|
20
|
+
if (references.length) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
`Unsupported REST declaration reference directive: ${kind} ${JSON.stringify(references[0].fileName)}. ` +
|
|
23
|
+
'The current REST bundling workflow does not support this directive.',
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
// lib references are compiler-owned, not package dependencies.
|
|
28
|
+
const dependencies = new Set<string>();
|
|
29
|
+
|
|
30
|
+
function addDependency(specifier: ts.Node | undefined) {
|
|
31
|
+
if (!specifier) return;
|
|
32
|
+
if (!ts.isStringLiteral(specifier)) {
|
|
33
|
+
throw new Error('Unsupported REST declaration module specifier');
|
|
34
|
+
}
|
|
35
|
+
const id = specifier.text;
|
|
36
|
+
if (
|
|
37
|
+
id.startsWith('.') ||
|
|
38
|
+
id.startsWith('#') ||
|
|
39
|
+
id.startsWith('\0') ||
|
|
40
|
+
path.isAbsolute(id) ||
|
|
41
|
+
path.win32.isAbsolute(id) ||
|
|
42
|
+
/^[a-z][a-z\d+.-]*:/i.test(id) ||
|
|
43
|
+
isBuiltin(id)
|
|
44
|
+
) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (!id || id.includes(':')) {
|
|
48
|
+
throw new Error(`Unsupported REST declaration package reference: ${id}`);
|
|
49
|
+
}
|
|
50
|
+
const parts = id.split('/');
|
|
51
|
+
if (id.startsWith('@') && (!parts[0].slice(1) || !parts[1])) {
|
|
52
|
+
throw new Error(`Invalid REST declaration package reference: ${id}`);
|
|
53
|
+
}
|
|
54
|
+
dependencies.add(parts.slice(0, id.startsWith('@') ? 2 : 1).join('/'));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function visit(node: ts.Node) {
|
|
58
|
+
if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) {
|
|
59
|
+
addDependency(node.moduleSpecifier);
|
|
60
|
+
} else if (ts.isImportTypeNode(node)) {
|
|
61
|
+
if (!ts.isLiteralTypeNode(node.argument)) {
|
|
62
|
+
throw new Error('Unsupported REST declaration import type');
|
|
63
|
+
}
|
|
64
|
+
addDependency(node.argument.literal);
|
|
65
|
+
} else if (
|
|
66
|
+
ts.isImportEqualsDeclaration(node) &&
|
|
67
|
+
ts.isExternalModuleReference(node.moduleReference)
|
|
68
|
+
) {
|
|
69
|
+
addDependency(node.moduleReference.expression);
|
|
70
|
+
} else if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword) {
|
|
71
|
+
throw new Error('Unsupported REST declaration dynamic import');
|
|
72
|
+
}
|
|
73
|
+
ts.forEachChild(node, visit);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
visit(source);
|
|
77
|
+
return Array.from(dependencies).sort();
|
|
78
|
+
}
|