@futdevpro/nts-dynamo 1.15.141 → 1.15.144
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/__documentations/2026-08-18-artifact-compiler.md +44 -0
- package/__documentations/2026-08-18-standalone-account-security-and-privacy-bedrock.md +1 -0
- package/build/_modules/artifact-compiler/_enums/artifact-compile-outcome.type-enum.d.ts +6 -0
- package/build/_modules/artifact-compiler/_enums/artifact-compile-outcome.type-enum.d.ts.map +1 -0
- package/build/_modules/artifact-compiler/_enums/artifact-compile-outcome.type-enum.js +10 -0
- package/build/_modules/artifact-compiler/_enums/artifact-compile-outcome.type-enum.js.map +1 -0
- package/build/_modules/artifact-compiler/_enums/artifact-compile-stage.type-enum.d.ts +9 -0
- package/build/_modules/artifact-compiler/_enums/artifact-compile-stage.type-enum.d.ts.map +1 -0
- package/build/_modules/artifact-compiler/_enums/artifact-compile-stage.type-enum.js +13 -0
- package/build/_modules/artifact-compiler/_enums/artifact-compile-stage.type-enum.js.map +1 -0
- package/build/_modules/artifact-compiler/_enums/artifact-retry-class.type-enum.d.ts +7 -0
- package/build/_modules/artifact-compiler/_enums/artifact-retry-class.type-enum.d.ts.map +1 -0
- package/build/_modules/artifact-compiler/_enums/artifact-retry-class.type-enum.js +11 -0
- package/build/_modules/artifact-compiler/_enums/artifact-retry-class.type-enum.js.map +1 -0
- package/build/_modules/artifact-compiler/_models/artifact-compile.interface.d.ts +33 -0
- package/build/_modules/artifact-compiler/_models/artifact-compile.interface.d.ts.map +1 -0
- package/build/_modules/artifact-compiler/_models/artifact-compile.interface.js +3 -0
- package/build/_modules/artifact-compiler/_models/artifact-compile.interface.js.map +1 -0
- package/build/_modules/artifact-compiler/artifact-compiler.control-service.d.ts +37 -0
- package/build/_modules/artifact-compiler/artifact-compiler.control-service.d.ts.map +1 -0
- package/build/_modules/artifact-compiler/artifact-compiler.control-service.js +274 -0
- package/build/_modules/artifact-compiler/artifact-compiler.control-service.js.map +1 -0
- package/build/_modules/artifact-compiler/index.d.ts +6 -0
- package/build/_modules/artifact-compiler/index.d.ts.map +1 -0
- package/build/_modules/artifact-compiler/index.js +12 -0
- package/build/_modules/artifact-compiler/index.js.map +1 -0
- package/package.json +15 -2
- package/pnpm-workspace.yaml +4 -3
- package/src/_modules/artifact-compiler/_enums/artifact-compile-outcome.type-enum.ts +5 -0
- package/src/_modules/artifact-compiler/_enums/artifact-compile-stage.type-enum.ts +8 -0
- package/src/_modules/artifact-compiler/_enums/artifact-retry-class.type-enum.ts +6 -0
- package/src/_modules/artifact-compiler/_models/artifact-compile.interface.ts +35 -0
- package/src/_modules/artifact-compiler/artifact-compiler.control-service.spec.ts +151 -0
- package/src/_modules/artifact-compiler/artifact-compiler.control-service.ts +356 -0
- package/src/_modules/artifact-compiler/index.ts +9 -0
- package/SKILLS.md +0 -75
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { DyNTS_ArtifactCompileOutcome_Type } from '../_enums/artifact-compile-outcome.type-enum';
|
|
2
|
+
import { DyNTS_ArtifactCompileStage_Type } from '../_enums/artifact-compile-stage.type-enum';
|
|
3
|
+
import { DyNTS_ArtifactRetryClass_Type } from '../_enums/artifact-retry-class.type-enum';
|
|
4
|
+
|
|
5
|
+
/** Versioned input for the deterministic TypeScript-to-browser bundle compiler. */
|
|
6
|
+
export interface DyNTS_ArtifactCompileRequest_Interface {
|
|
7
|
+
contractVersion: 'dynts-artifact-compiler/1';
|
|
8
|
+
source: string;
|
|
9
|
+
sourceFileName: string;
|
|
10
|
+
resolveDirectory: string;
|
|
11
|
+
allowedPackages: string[];
|
|
12
|
+
maxSourceBytes: number;
|
|
13
|
+
maxOutputBytes: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Content-free compiler finding. Source text and dependency paths are deliberately excluded. */
|
|
17
|
+
export interface DyNTS_ArtifactCompileFinding_Interface {
|
|
18
|
+
code: string;
|
|
19
|
+
stage: DyNTS_ArtifactCompileStage_Type;
|
|
20
|
+
line: number | null;
|
|
21
|
+
column: number | null;
|
|
22
|
+
retryClass: DyNTS_ArtifactRetryClass_Type;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** One self-contained JavaScript output or a fail-closed, content-free rejection. */
|
|
26
|
+
export interface DyNTS_ArtifactCompileResult_Interface {
|
|
27
|
+
contractVersion: 'dynts-artifact-compiler/1';
|
|
28
|
+
outcome: DyNTS_ArtifactCompileOutcome_Type;
|
|
29
|
+
code: string;
|
|
30
|
+
sourceChecksum: string;
|
|
31
|
+
bundleChecksum: string;
|
|
32
|
+
outputBytes: number;
|
|
33
|
+
bundleSource: string | null;
|
|
34
|
+
findings: DyNTS_ArtifactCompileFinding_Interface[];
|
|
35
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
|
|
3
|
+
import { DyNTS_ArtifactCompileOutcome_Type } from './_enums/artifact-compile-outcome.type-enum';
|
|
4
|
+
import {
|
|
5
|
+
DyNTS_ArtifactCompileRequest_Interface,
|
|
6
|
+
DyNTS_ArtifactCompileResult_Interface
|
|
7
|
+
} from './_models/artifact-compile.interface';
|
|
8
|
+
import { DyNTS_ArtifactCompiler_ControlService } from './artifact-compiler.control-service';
|
|
9
|
+
|
|
10
|
+
describe('| DyNTS_ArtifactCompiler_ControlService', (): void => {
|
|
11
|
+
const compiler_CS: DyNTS_ArtifactCompiler_ControlService = new DyNTS_ArtifactCompiler_ControlService();
|
|
12
|
+
|
|
13
|
+
it('| emits one deterministic self-contained classic-worker bundle without filesystem output', async (
|
|
14
|
+
): Promise<void> => {
|
|
15
|
+
const request: DyNTS_ArtifactCompileRequest_Interface = buildRequest(workerSource());
|
|
16
|
+
const first: DyNTS_ArtifactCompileResult_Interface = await compiler_CS.compile(request);
|
|
17
|
+
const second: DyNTS_ArtifactCompileResult_Interface = await compiler_CS.compile(request);
|
|
18
|
+
|
|
19
|
+
expect(first.outcome).toBe(DyNTS_ArtifactCompileOutcome_Type.compiled);
|
|
20
|
+
expect(first.bundleSource).toContain('self.onmessage');
|
|
21
|
+
expect(first.bundleSource).not.toContain('sourceMappingURL');
|
|
22
|
+
expect(first.bundleSource).not.toContain('dynts-in-memory-output');
|
|
23
|
+
expect(first.bundleChecksum).toMatch(/^[a-f0-9]{64}$/);
|
|
24
|
+
expect(first.outputBytes).toBeGreaterThan(0);
|
|
25
|
+
expect(second).toEqual(first);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('| bundles an explicitly allowed installed package into the output', async (): Promise<void> => {
|
|
29
|
+
const source: string = `import Decimal from 'decimal.js';
|
|
30
|
+
self.onmessage = (): void => {
|
|
31
|
+
const value: string = new Decimal(1).plus(2).toString();
|
|
32
|
+
const output: ArrayBuffer = new TextEncoder().encode(value).buffer;
|
|
33
|
+
self.postMessage({ type: 'complete', output: output }, [ output ]);
|
|
34
|
+
};`;
|
|
35
|
+
const request: DyNTS_ArtifactCompileRequest_Interface = buildRequest(source);
|
|
36
|
+
|
|
37
|
+
request.allowedPackages = [ 'decimal.js' ];
|
|
38
|
+
|
|
39
|
+
const result: DyNTS_ArtifactCompileResult_Interface = await compiler_CS.compile(request);
|
|
40
|
+
|
|
41
|
+
expect(result.outcome).toBe(DyNTS_ArtifactCompileOutcome_Type.compiled);
|
|
42
|
+
expect(result.bundleSource).not.toContain('from\'decimal.js\'');
|
|
43
|
+
expect(result.outputBytes).toBeGreaterThan(1_000);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('| rejects non-allowlisted direct imports without reflecting the source canary', async (): Promise<void> => {
|
|
47
|
+
const canary: string = 'SOURCE-CANARY-MUST-NOT-RETURN';
|
|
48
|
+
const request: DyNTS_ArtifactCompileRequest_Interface = buildRequest(
|
|
49
|
+
`import fs from 'node:fs';\nconst secret = '${canary}';\n${workerSource()}`
|
|
50
|
+
);
|
|
51
|
+
const result: DyNTS_ArtifactCompileResult_Interface = await compiler_CS.compile(request);
|
|
52
|
+
|
|
53
|
+
expect(result.outcome).toBe(DyNTS_ArtifactCompileOutcome_Type.rejected);
|
|
54
|
+
expect(result.findings[0].code).toBe('DYNTS|ARTIFACT_COMPILER|IMPORT_NOT_ALLOWED');
|
|
55
|
+
expect(JSON.stringify(result)).not.toContain(canary);
|
|
56
|
+
expect(result.bundleSource).toBeNull();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('| rejects re-exports, import-equals, require and dynamic import syntax before bundling', async (
|
|
60
|
+
): Promise<void> => {
|
|
61
|
+
const sources: string[] = [
|
|
62
|
+
'export { readFile } from \'node:fs\';',
|
|
63
|
+
'import fs = require(\'node:fs\');',
|
|
64
|
+
'const fs = require(\'node:fs\');',
|
|
65
|
+
'void import(\'decimal.js\');',
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
for (const source of sources) {
|
|
69
|
+
const result: DyNTS_ArtifactCompileResult_Interface = await compiler_CS.compile(buildRequest(source));
|
|
70
|
+
|
|
71
|
+
expect(result.code).toBe('DYNTS|ARTIFACT_COMPILER|IMPORT_REJECTED');
|
|
72
|
+
expect(result.findings[0].stage).toBe('source-policy');
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('| returns content-free compile diagnostics for invalid TypeScript', async (): Promise<void> => {
|
|
77
|
+
const canary: string = 'INVALID-CANARY-MUST-NOT-RETURN';
|
|
78
|
+
const result: DyNTS_ArtifactCompileResult_Interface = await compiler_CS.compile(
|
|
79
|
+
buildRequest(`const ${canary}: = ;`)
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
expect(result.outcome).toBe(DyNTS_ArtifactCompileOutcome_Type.rejected);
|
|
83
|
+
expect(result.findings[0].code).toBe('DYNTS|ARTIFACT_COMPILER|COMPILE_ERROR');
|
|
84
|
+
expect(result.findings[0].line).toBe(1);
|
|
85
|
+
expect(JSON.stringify(result)).not.toContain(canary);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('| rejects invalid contracts, duplicate packages and unsafe resolve inputs', async (): Promise<void> => {
|
|
89
|
+
const request: DyNTS_ArtifactCompileRequest_Interface = buildRequest(workerSource());
|
|
90
|
+
|
|
91
|
+
request.allowedPackages = [ 'decimal.js', 'decimal.js' ];
|
|
92
|
+
request.resolveDirectory = 'relative/path';
|
|
93
|
+
request.maxOutputBytes = 20_000_000;
|
|
94
|
+
|
|
95
|
+
const result: DyNTS_ArtifactCompileResult_Interface = await compiler_CS.compile(request);
|
|
96
|
+
|
|
97
|
+
expect(result.code).toBe('DYNTS|ARTIFACT_COMPILER|REQUEST_INVALID');
|
|
98
|
+
expect(result.findings[0].stage).toBe('request');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('| bounds allowlist cardinality and request path lengths', async (): Promise<void> => {
|
|
102
|
+
const packageLimited: DyNTS_ArtifactCompileRequest_Interface = buildRequest(workerSource());
|
|
103
|
+
const pathLimited: DyNTS_ArtifactCompileRequest_Interface = buildRequest(workerSource());
|
|
104
|
+
|
|
105
|
+
packageLimited.allowedPackages = Array.from({ length: 65 }, (_value: unknown, index: number): string =>
|
|
106
|
+
`package-${index}`);
|
|
107
|
+
pathLimited.sourceFileName = `${'a'.repeat(128)}.ts`;
|
|
108
|
+
|
|
109
|
+
const packageResult: DyNTS_ArtifactCompileResult_Interface = await compiler_CS.compile(packageLimited);
|
|
110
|
+
const pathResult: DyNTS_ArtifactCompileResult_Interface = await compiler_CS.compile(pathLimited);
|
|
111
|
+
|
|
112
|
+
expect(packageResult.code).toBe('DYNTS|ARTIFACT_COMPILER|REQUEST_INVALID');
|
|
113
|
+
expect(pathResult.code).toBe('DYNTS|ARTIFACT_COMPILER|REQUEST_INVALID');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('| enforces source and output byte budgets before returning bundle content', async (): Promise<void> => {
|
|
117
|
+
const sourceLimited: DyNTS_ArtifactCompileRequest_Interface = buildRequest(workerSource());
|
|
118
|
+
const outputLimited: DyNTS_ArtifactCompileRequest_Interface = buildRequest(workerSource());
|
|
119
|
+
|
|
120
|
+
sourceLimited.maxSourceBytes = 1;
|
|
121
|
+
outputLimited.maxOutputBytes = 1;
|
|
122
|
+
|
|
123
|
+
const sourceResult: DyNTS_ArtifactCompileResult_Interface = await compiler_CS.compile(sourceLimited);
|
|
124
|
+
const outputResult: DyNTS_ArtifactCompileResult_Interface = await compiler_CS.compile(outputLimited);
|
|
125
|
+
|
|
126
|
+
expect(sourceResult.code).toBe('DYNTS|ARTIFACT_COMPILER|REQUEST_INVALID');
|
|
127
|
+
expect(outputResult.code).toBe('DYNTS|ARTIFACT_COMPILER|OUTPUT_LIMIT');
|
|
128
|
+
expect(outputResult.bundleSource).toBeNull();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
/** Builds a valid request resolved against this package's installed dependencies. */
|
|
132
|
+
function buildRequest(source: string): DyNTS_ArtifactCompileRequest_Interface {
|
|
133
|
+
return {
|
|
134
|
+
contractVersion: 'dynts-artifact-compiler/1',
|
|
135
|
+
source: source,
|
|
136
|
+
sourceFileName: 'candidate.ts',
|
|
137
|
+
resolveDirectory: path.resolve(process.cwd()),
|
|
138
|
+
allowedPackages: [],
|
|
139
|
+
maxSourceBytes: 512_000,
|
|
140
|
+
maxOutputBytes: 8_000_000,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Returns a minimal classic Worker contract. */
|
|
145
|
+
function workerSource(): string {
|
|
146
|
+
return `self.onmessage = (event: MessageEvent<{ input: ArrayBuffer }>): void => {
|
|
147
|
+
const output: ArrayBuffer = event.data.input;
|
|
148
|
+
self.postMessage({ type: 'complete', output: output }, [ output ]);
|
|
149
|
+
};`;
|
|
150
|
+
}
|
|
151
|
+
});
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
BuildFailure,
|
|
6
|
+
BuildOptions,
|
|
7
|
+
BuildResult,
|
|
8
|
+
Message,
|
|
9
|
+
OnLoadResult,
|
|
10
|
+
OnResolveArgs,
|
|
11
|
+
OnResolveResult,
|
|
12
|
+
OutputFile,
|
|
13
|
+
Plugin,
|
|
14
|
+
PluginBuild,
|
|
15
|
+
build
|
|
16
|
+
} from 'esbuild';
|
|
17
|
+
import ts from 'typescript';
|
|
18
|
+
|
|
19
|
+
import { DyNTS_ArtifactCompileOutcome_Type } from './_enums/artifact-compile-outcome.type-enum';
|
|
20
|
+
import { DyNTS_ArtifactCompileStage_Type } from './_enums/artifact-compile-stage.type-enum';
|
|
21
|
+
import { DyNTS_ArtifactRetryClass_Type } from './_enums/artifact-retry-class.type-enum';
|
|
22
|
+
import {
|
|
23
|
+
DyNTS_ArtifactCompileFinding_Interface,
|
|
24
|
+
DyNTS_ArtifactCompileRequest_Interface,
|
|
25
|
+
DyNTS_ArtifactCompileResult_Interface
|
|
26
|
+
} from './_models/artifact-compile.interface';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Deterministically bundles one in-memory TypeScript program for a classic browser Worker.
|
|
30
|
+
* This compiler is not a sandbox: consumers must validate source policy before compilation and execute the result
|
|
31
|
+
* inside a separately enforced runtime boundary.
|
|
32
|
+
*/
|
|
33
|
+
export class DyNTS_ArtifactCompiler_ControlService {
|
|
34
|
+
public static readonly contractVersion: 'dynts-artifact-compiler/1' = 'dynts-artifact-compiler/1';
|
|
35
|
+
private readonly absoluteMaxSourceBytes: number = 1_048_576;
|
|
36
|
+
private readonly absoluteMaxOutputBytes: number = 16_777_216;
|
|
37
|
+
private readonly absoluteMaxAllowedPackages: number = 64;
|
|
38
|
+
private readonly absoluteMaxPackageNameLength: number = 128;
|
|
39
|
+
private readonly absoluteMaxSourceFileNameLength: number = 128;
|
|
40
|
+
private readonly absoluteMaxResolveDirectoryLength: number = 1_024;
|
|
41
|
+
private readonly packageNamePattern: RegExp =
|
|
42
|
+
/^(?:@[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._-]*|[a-z0-9][a-z0-9._-]*)$/;
|
|
43
|
+
private readonly sourceFileNamePattern: RegExp = /^[a-z0-9][a-z0-9._-]*\.ts$/;
|
|
44
|
+
|
|
45
|
+
/** Compiles without writing source, sourcemaps, metadata or output files to disk. */
|
|
46
|
+
public async compile(
|
|
47
|
+
request: DyNTS_ArtifactCompileRequest_Interface
|
|
48
|
+
): Promise<DyNTS_ArtifactCompileResult_Interface> {
|
|
49
|
+
const sourceChecksum: string = this.checksum(typeof request?.source === 'string' ? request.source : '');
|
|
50
|
+
const requestFinding: DyNTS_ArtifactCompileFinding_Interface | null = this.validateRequest(request);
|
|
51
|
+
|
|
52
|
+
if (requestFinding) {
|
|
53
|
+
return this.rejected(sourceChecksum, 'DYNTS|ARTIFACT_COMPILER|REQUEST_INVALID', [ requestFinding ]);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const importFinding: DyNTS_ArtifactCompileFinding_Interface | null = this.validateDirectImports(request);
|
|
57
|
+
|
|
58
|
+
if (importFinding) {
|
|
59
|
+
return this.rejected(sourceChecksum, 'DYNTS|ARTIFACT_COMPILER|IMPORT_REJECTED', [ importFinding ]);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const result: BuildResult = await build(this.buildOptions(request));
|
|
64
|
+
const outputFiles: OutputFile[] = result.outputFiles ?? [];
|
|
65
|
+
|
|
66
|
+
if (outputFiles.length !== 1 || !outputFiles[0].path.endsWith('.js')) {
|
|
67
|
+
return this.rejected(sourceChecksum, 'DYNTS|ARTIFACT_COMPILER|OUTPUT_INVALID', [{
|
|
68
|
+
code: 'DYNTS|ARTIFACT_COMPILER|OUTPUT_COUNT',
|
|
69
|
+
stage: DyNTS_ArtifactCompileStage_Type.output,
|
|
70
|
+
line: null,
|
|
71
|
+
column: null,
|
|
72
|
+
retryClass: DyNTS_ArtifactRetryClass_Type.deterministicToolFixable,
|
|
73
|
+
}]);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const bundleSource: string = outputFiles[0].text;
|
|
77
|
+
const outputBytes: number = Buffer.byteLength(bundleSource, 'utf8');
|
|
78
|
+
|
|
79
|
+
if (!outputBytes || outputBytes > request.maxOutputBytes) {
|
|
80
|
+
return this.rejected(sourceChecksum, 'DYNTS|ARTIFACT_COMPILER|OUTPUT_LIMIT', [{
|
|
81
|
+
code: 'DYNTS|ARTIFACT_COMPILER|OUTPUT_SIZE',
|
|
82
|
+
stage: DyNTS_ArtifactCompileStage_Type.output,
|
|
83
|
+
line: null,
|
|
84
|
+
column: null,
|
|
85
|
+
retryClass: DyNTS_ArtifactRetryClass_Type.agentFixable,
|
|
86
|
+
}]);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
contractVersion: DyNTS_ArtifactCompiler_ControlService.contractVersion,
|
|
91
|
+
outcome: DyNTS_ArtifactCompileOutcome_Type.compiled,
|
|
92
|
+
code: 'DYNTS|ARTIFACT_COMPILER|COMPILED',
|
|
93
|
+
sourceChecksum: sourceChecksum,
|
|
94
|
+
bundleChecksum: this.checksum(bundleSource),
|
|
95
|
+
outputBytes: outputBytes,
|
|
96
|
+
bundleSource: bundleSource,
|
|
97
|
+
findings: [],
|
|
98
|
+
};
|
|
99
|
+
} catch (error: unknown) {
|
|
100
|
+
return this.rejected(sourceChecksum, 'DYNTS|ARTIFACT_COMPILER|BUILD_FAILED', this.buildFindings(error));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private validateRequest(
|
|
105
|
+
request: DyNTS_ArtifactCompileRequest_Interface
|
|
106
|
+
): DyNTS_ArtifactCompileFinding_Interface | null {
|
|
107
|
+
const sourceBytes: number = typeof request?.source === 'string'
|
|
108
|
+
? Buffer.byteLength(request.source, 'utf8')
|
|
109
|
+
: 0;
|
|
110
|
+
const allowedPackages: string[] = Array.isArray(request?.allowedPackages) ? request.allowedPackages : [];
|
|
111
|
+
const uniquePackages: Set<string> = new Set<string>(allowedPackages);
|
|
112
|
+
const isPackageSetValid: boolean = allowedPackages.length <= this.absoluteMaxAllowedPackages
|
|
113
|
+
&& uniquePackages.size === allowedPackages.length
|
|
114
|
+
&& allowedPackages.every((packageName: string): boolean => packageName.length <= this.absoluteMaxPackageNameLength
|
|
115
|
+
&& this.packageNamePattern.test(packageName));
|
|
116
|
+
|
|
117
|
+
if (
|
|
118
|
+
request?.contractVersion !== DyNTS_ArtifactCompiler_ControlService.contractVersion
|
|
119
|
+
|| !sourceBytes
|
|
120
|
+
|| !Number.isInteger(request.maxSourceBytes)
|
|
121
|
+
|| request.maxSourceBytes < 1
|
|
122
|
+
|| request.maxSourceBytes > this.absoluteMaxSourceBytes
|
|
123
|
+
|| sourceBytes > request.maxSourceBytes
|
|
124
|
+
|| !Number.isInteger(request.maxOutputBytes)
|
|
125
|
+
|| request.maxOutputBytes < 1
|
|
126
|
+
|| request.maxOutputBytes > this.absoluteMaxOutputBytes
|
|
127
|
+
|| typeof request.sourceFileName !== 'string'
|
|
128
|
+
|| request.sourceFileName.length > this.absoluteMaxSourceFileNameLength
|
|
129
|
+
|| !this.sourceFileNamePattern.test(request.sourceFileName)
|
|
130
|
+
|| typeof request.resolveDirectory !== 'string'
|
|
131
|
+
|| request.resolveDirectory.length > this.absoluteMaxResolveDirectoryLength
|
|
132
|
+
|| !path.isAbsolute(request.resolveDirectory)
|
|
133
|
+
|| !isPackageSetValid
|
|
134
|
+
) {
|
|
135
|
+
return {
|
|
136
|
+
code: 'DYNTS|ARTIFACT_COMPILER|REQUEST_CONTRACT',
|
|
137
|
+
stage: DyNTS_ArtifactCompileStage_Type.request,
|
|
138
|
+
line: null,
|
|
139
|
+
column: null,
|
|
140
|
+
retryClass: DyNTS_ArtifactRetryClass_Type.deterministicToolFixable,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private buildOptions(request: DyNTS_ArtifactCompileRequest_Interface): BuildOptions {
|
|
148
|
+
return {
|
|
149
|
+
absWorkingDir: request.resolveDirectory,
|
|
150
|
+
bundle: true,
|
|
151
|
+
charset: 'utf8',
|
|
152
|
+
conditions: [ 'browser', 'default' ],
|
|
153
|
+
entryPoints: [ 'dynts-entry:candidate' ],
|
|
154
|
+
format: 'iife',
|
|
155
|
+
legalComments: 'none',
|
|
156
|
+
logLevel: 'silent',
|
|
157
|
+
mainFields: [ 'browser', 'module', 'main' ],
|
|
158
|
+
metafile: false,
|
|
159
|
+
minify: true,
|
|
160
|
+
outdir: 'dynts-in-memory-output',
|
|
161
|
+
platform: 'browser',
|
|
162
|
+
plugins: [ this.allowlistedEntryPlugin(request) ],
|
|
163
|
+
sourcemap: false,
|
|
164
|
+
target: [ 'es2022' ],
|
|
165
|
+
treeShaking: true,
|
|
166
|
+
write: false,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Validates every syntactic import before esbuild can tree-shake it. The plugin below is a second resolution gate,
|
|
172
|
+
* but it cannot be the only policy boundary because unused imports need not reach package resolution.
|
|
173
|
+
*/
|
|
174
|
+
private validateDirectImports(
|
|
175
|
+
request: DyNTS_ArtifactCompileRequest_Interface
|
|
176
|
+
): DyNTS_ArtifactCompileFinding_Interface | null {
|
|
177
|
+
const sourceFile: ts.SourceFile = ts.createSourceFile(
|
|
178
|
+
request.sourceFileName,
|
|
179
|
+
request.source,
|
|
180
|
+
ts.ScriptTarget.ES2022,
|
|
181
|
+
true,
|
|
182
|
+
ts.ScriptKind.TS
|
|
183
|
+
);
|
|
184
|
+
const allowedPackages: ReadonlySet<string> = new Set<string>(request.allowedPackages);
|
|
185
|
+
let finding: DyNTS_ArtifactCompileFinding_Interface | null = null;
|
|
186
|
+
|
|
187
|
+
/** Traverses the parsed source once and stops at the first forbidden module load. */
|
|
188
|
+
const inspectNode: (node: ts.Node) => void = (node: ts.Node): void => {
|
|
189
|
+
if (finding) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const moduleName: string | null = this.moduleName(node);
|
|
194
|
+
|
|
195
|
+
if (moduleName !== null && !allowedPackages.has(this.packageRoot(moduleName))) {
|
|
196
|
+
finding = this.importFinding(sourceFile, node);
|
|
197
|
+
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (this.isDynamicModuleLoad(node)) {
|
|
202
|
+
finding = this.importFinding(sourceFile, node);
|
|
203
|
+
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
ts.forEachChild(node, inspectNode);
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
inspectNode(sourceFile);
|
|
211
|
+
|
|
212
|
+
return finding;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private moduleName(node: ts.Node): string | null {
|
|
216
|
+
if ((ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) && node.moduleSpecifier) {
|
|
217
|
+
return ts.isStringLiteral(node.moduleSpecifier) ? node.moduleSpecifier.text : '';
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (
|
|
221
|
+
ts.isImportEqualsDeclaration(node)
|
|
222
|
+
&& ts.isExternalModuleReference(node.moduleReference)
|
|
223
|
+
&& node.moduleReference.expression
|
|
224
|
+
) {
|
|
225
|
+
return ts.isStringLiteral(node.moduleReference.expression) ? node.moduleReference.expression.text : '';
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
private isDynamicModuleLoad(node: ts.Node): boolean {
|
|
232
|
+
return ts.isCallExpression(node)
|
|
233
|
+
&& (node.expression.kind === ts.SyntaxKind.ImportKeyword
|
|
234
|
+
|| (ts.isIdentifier(node.expression) && node.expression.text === 'require'));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
private importFinding(
|
|
238
|
+
sourceFile: ts.SourceFile,
|
|
239
|
+
node: ts.Node
|
|
240
|
+
): DyNTS_ArtifactCompileFinding_Interface {
|
|
241
|
+
const location: ts.LineAndCharacter = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
code: 'DYNTS|ARTIFACT_COMPILER|IMPORT_NOT_ALLOWED',
|
|
245
|
+
stage: DyNTS_ArtifactCompileStage_Type.sourcePolicy,
|
|
246
|
+
line: location.line + 1,
|
|
247
|
+
column: location.character + 1,
|
|
248
|
+
retryClass: DyNTS_ArtifactRetryClass_Type.terminal,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
private allowlistedEntryPlugin(request: DyNTS_ArtifactCompileRequest_Interface): Plugin {
|
|
253
|
+
const allowedPackages: ReadonlySet<string> = new Set<string>(request.allowedPackages);
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
name: 'dynts-allowlisted-in-memory-entry',
|
|
257
|
+
setup: (pluginBuild: PluginBuild): void => {
|
|
258
|
+
pluginBuild.onResolve({ filter: /^dynts-entry:candidate$/ }, (): OnResolveResult => ({
|
|
259
|
+
path: 'candidate',
|
|
260
|
+
namespace: 'dynts-entry',
|
|
261
|
+
}));
|
|
262
|
+
pluginBuild.onLoad({ filter: /^candidate$/, namespace: 'dynts-entry' }, (): OnLoadResult => ({
|
|
263
|
+
contents: request.source,
|
|
264
|
+
loader: 'ts',
|
|
265
|
+
resolveDir: request.resolveDirectory,
|
|
266
|
+
}));
|
|
267
|
+
pluginBuild.onResolve({ filter: /.*/, namespace: 'dynts-entry' }, async (
|
|
268
|
+
args: OnResolveArgs
|
|
269
|
+
): Promise<OnResolveResult> => {
|
|
270
|
+
if (!allowedPackages.has(this.packageRoot(args.path))) {
|
|
271
|
+
return {
|
|
272
|
+
errors: [{ text: 'DYNTS|ARTIFACT_COMPILER|IMPORT_NOT_ALLOWED' }],
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return pluginBuild.resolve(args.path, {
|
|
277
|
+
importer: args.importer,
|
|
278
|
+
kind: args.kind,
|
|
279
|
+
namespace: 'file',
|
|
280
|
+
resolveDir: request.resolveDirectory,
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
},
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
private buildFindings(error: unknown): DyNTS_ArtifactCompileFinding_Interface[] {
|
|
288
|
+
if (!this.isBuildFailure(error)) {
|
|
289
|
+
return [ this.genericBuildFinding() ];
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const findings: DyNTS_ArtifactCompileFinding_Interface[] = error.errors.map((message: Message):
|
|
293
|
+
DyNTS_ArtifactCompileFinding_Interface => ({
|
|
294
|
+
code: message.text === 'DYNTS|ARTIFACT_COMPILER|IMPORT_NOT_ALLOWED'
|
|
295
|
+
? message.text
|
|
296
|
+
: 'DYNTS|ARTIFACT_COMPILER|COMPILE_ERROR',
|
|
297
|
+
stage: message.text === 'DYNTS|ARTIFACT_COMPILER|IMPORT_NOT_ALLOWED'
|
|
298
|
+
? DyNTS_ArtifactCompileStage_Type.resolve
|
|
299
|
+
: DyNTS_ArtifactCompileStage_Type.compile,
|
|
300
|
+
line: message.location?.line ?? null,
|
|
301
|
+
column: message.location ? message.location.column + 1 : null,
|
|
302
|
+
retryClass: message.text === 'DYNTS|ARTIFACT_COMPILER|IMPORT_NOT_ALLOWED'
|
|
303
|
+
? DyNTS_ArtifactRetryClass_Type.terminal
|
|
304
|
+
: DyNTS_ArtifactRetryClass_Type.agentFixable,
|
|
305
|
+
}));
|
|
306
|
+
|
|
307
|
+
return findings.length ? findings : [ this.genericBuildFinding() ];
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
private isBuildFailure(error: unknown): error is BuildFailure {
|
|
311
|
+
if (!error || typeof error !== 'object') {
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return 'errors' in error && Array.isArray(error.errors);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
private genericBuildFinding(): DyNTS_ArtifactCompileFinding_Interface {
|
|
319
|
+
return {
|
|
320
|
+
code: 'DYNTS|ARTIFACT_COMPILER|COMPILE_ERROR',
|
|
321
|
+
stage: DyNTS_ArtifactCompileStage_Type.compile,
|
|
322
|
+
line: null,
|
|
323
|
+
column: null,
|
|
324
|
+
retryClass: DyNTS_ArtifactRetryClass_Type.agentFixable,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
private rejected(
|
|
329
|
+
sourceChecksum: string,
|
|
330
|
+
code: string,
|
|
331
|
+
findings: DyNTS_ArtifactCompileFinding_Interface[]
|
|
332
|
+
): DyNTS_ArtifactCompileResult_Interface {
|
|
333
|
+
return {
|
|
334
|
+
contractVersion: DyNTS_ArtifactCompiler_ControlService.contractVersion,
|
|
335
|
+
outcome: DyNTS_ArtifactCompileOutcome_Type.rejected,
|
|
336
|
+
code: code,
|
|
337
|
+
sourceChecksum: sourceChecksum,
|
|
338
|
+
bundleChecksum: '',
|
|
339
|
+
outputBytes: 0,
|
|
340
|
+
bundleSource: null,
|
|
341
|
+
findings: findings,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
private packageRoot(moduleName: string): string {
|
|
346
|
+
if (moduleName.startsWith('@')) {
|
|
347
|
+
return moduleName.split('/').slice(0, 2).join('/');
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return moduleName.split('/')[0];
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
private checksum(value: string): string {
|
|
354
|
+
return createHash('sha256').update(value, 'utf8').digest('hex');
|
|
355
|
+
}
|
|
356
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { DyNTS_ArtifactCompiler_ControlService } from './artifact-compiler.control-service';
|
|
2
|
+
export { DyNTS_ArtifactCompileOutcome_Type } from './_enums/artifact-compile-outcome.type-enum';
|
|
3
|
+
export { DyNTS_ArtifactCompileStage_Type } from './_enums/artifact-compile-stage.type-enum';
|
|
4
|
+
export { DyNTS_ArtifactRetryClass_Type } from './_enums/artifact-retry-class.type-enum';
|
|
5
|
+
export {
|
|
6
|
+
DyNTS_ArtifactCompileFinding_Interface,
|
|
7
|
+
DyNTS_ArtifactCompileRequest_Interface,
|
|
8
|
+
DyNTS_ArtifactCompileResult_Interface
|
|
9
|
+
} from './_models/artifact-compile.interface';
|
package/SKILLS.md
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
# SKILLS.md — `dynamo-nts` tooling
|
|
2
|
-
|
|
3
|
-
Ez a fájl a projektben **ténylegesen használt** CLI-ket és eszközöket írja le, hogy a coding agent
|
|
4
|
-
(Codex / Claude Code) tudja, **mit mivel** kell futtatni. Kísérő fájlok: `CLAUDE.md` / `AGENTS.md`
|
|
5
|
-
(szabályok + architektúra). Rule: `core-agent-file-sync`.
|
|
6
|
-
|
|
7
|
-
> A `SKILLS:AUTO:BEGIN` … `SKILLS:AUTO:END` markerek közti rész **GENERÁLT** (forrás:
|
|
8
|
-
> `__agent/scripts/skills-md-generate.ps1`; adat: a projekt `package.json`-jai, config-fájljai,
|
|
9
|
-
> `__agent/scripts/` tartalma) — újrafuttatáskor **felülíródik**.
|
|
10
|
-
> Az azon **kívüli** rész kézi és megőrződik: oda írd a projekt-specifikus jegyzeteket.
|
|
11
|
-
|
|
12
|
-
<!-- SKILLS:AUTO:BEGIN -->
|
|
13
|
-
## 1. Flotta-szintű CLI-k (mindenhol elérhetők)
|
|
14
|
-
|
|
15
|
-
| CLI | Csomag | Mire való | Tipikus hívás |
|
|
16
|
-
|---|---|---|---|
|
|
17
|
-
| `dc` / `dyn-cli` | `@futdevpro/cli-dynamo` | Dynamo fejlesztői CLI: projekt-/kód-generálás, pipeline-futtatás, review, konvenció-validálás | `dc im` (interaktív), `dc cdp`, `dc ldp`, `dc rev --json` |
|
|
18
|
-
| `fdp` / `fdp-cli` | `@futdevpro/fdp-cli` | DevOps + Overseer lekérdezés: deploy, runner, build-report, logok, errorok | `fdp build-detail --project <p>`, `fdp errors --range 24h`, `fdp deploy-service --services <n>` |
|
|
19
|
-
| `dye2e` | `@futdevpro/dynamo-e2e` | E2E generátor + Visual-QA review-bundle | `dye2e generate-review-bundle`, `dye2e validate-manifest` |
|
|
20
|
-
| `fam` | `fdp-agent-memory` (MCP `:39265`) | Tudás-keresés / recall / szabály-lekérés — **MINDEN feladat elején** (rule: `fam-use-preferentially`) | MCP `read` a `rules` / `documents` / `codebase` táron |
|
|
21
|
-
| `pnpm` | — | **Az egyetlen** csomagkezelő a flottában (npm-kompatibilis) | `pnpm run prep`, `pnpm test` |
|
|
22
|
-
|
|
23
|
-
**Alap-szabályok a CLI-kre** (kanonikus: `fdp-documentations/rules/fdp-global/`):
|
|
24
|
-
- `fdp-use-existing-tooling` — a meglévő scriptet/CLI-t használd, ne írj sajátot mellé.
|
|
25
|
-
- `fdp-cli-only` — a DevOps-műveletek az `fdp` CLI-n mennek, nem kézi docker/ssh parancsokon.
|
|
26
|
-
- **Az NPM-scripteket ne bontsd szét** — a `pnpm test` már tartalmazza a build-lépést is.
|
|
27
|
-
|
|
28
|
-
**Pipeline-ok:**
|
|
29
|
-
- `dc ldp` — Live Dev Pipeline: felhúzza a LOKÁL instance-t és az ellen futtat (`pipeline.config.json`);
|
|
30
|
-
státusz: `server/logs/live-dev-pipeline/status.json`.
|
|
31
|
-
- `dc cdp` — CI/CD Pipeline: a **deployolt test-szerver** ellen fut (`pipeline.cicd.config.json`).
|
|
32
|
-
A CI-t az **Overseer** vezérli, nem a GitHub Actions (az csak webhook-trigger).
|
|
33
|
-
|
|
34
|
-
## 2. Ebben a projektben — MÉRT adatok
|
|
35
|
-
|
|
36
|
-
> Forrás: a projekt `package.json`-jai, config-fájljai és `__agent/scripts/` tartalma, beolvasva a generáláskor.
|
|
37
|
-
|
|
38
|
-
### 2.1 gyökér — `@futdevpro/nts-dynamo`
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
| Script | Parancs |
|
|
42
|
-
|---|---|
|
|
43
|
-
| `pnpm run audit:eslintrc` | `dynamo-eslintrc-audit` |
|
|
44
|
-
| `pnpm run build` | `npm run build-base && jasmine && npm publish` |
|
|
45
|
-
| `pnpm run build-base` | `rimraf ./build && npx tsc` |
|
|
46
|
-
| `pnpm run build-tgz` | `npm run build-base && jasmine && npm pack && move ./*.tgz ../tgz-collection/dynamo-nts` |
|
|
47
|
-
| `pnpm run deploy` | `npm run build` |
|
|
48
|
-
| `pnpm run ldp` | `dc ldp` |
|
|
49
|
-
| `pnpm run lint` | `eslint src --ext .ts` |
|
|
50
|
-
| `pnpm run lint:fix` | `eslint src --ext .ts --fix` |
|
|
51
|
-
| `pnpm run prep` | `npm i -g pnpm rimraf nodemon && pnpm i` |
|
|
52
|
-
| `pnpm run start` | `npm run prep && nodemon` |
|
|
53
|
-
| `pnpm run start-clean` | `npm run clean && npm run start` |
|
|
54
|
-
| `pnpm run test` | `npm run build-base && jasmine` |
|
|
55
|
-
| `pnpm run test:coverage` | `npx c8 --reporter=html --reporter=text --reporter=lcov node scripts/run-coverage-tests.js` |
|
|
56
|
-
| `pnpm run validate:imports` | `dynamo-validate-imports` |
|
|
57
|
-
| `pnpm run validate:naming` | `dynamo-validate-naming` |
|
|
58
|
-
|
|
59
|
-
**További scriptek (csak név):** `build-all` · `build-clean` · `build-n-test` · `build-simple` · `build-tgz-cheap` · `clean` · `clean-deploy` · `fix` · `fix:return-types` · `ldp:clear` · `ldp:status` · `ldp:stop` · `nodemon-run` · `prepare` · `preprep-setup-safechain-linux` · `preprep-setup-safechain-windows` · `start-first` · `test-clean`
|
|
60
|
-
|
|
61
|
-
**Felismert eszközök a dependency-kből:** Dynamo ESLint konfiguráció (`@futdevpro/dynamo-eslint`) · ESLint (`eslint`) · Jasmine (unit teszt) (`jasmine`) · Socket.IO (`socket.io`) · TypeScript (`tsc`) (`typescript`)
|
|
62
|
-
|
|
63
|
-
### Konfigurációk és belépési pontok (jelenlévő fájlok)
|
|
64
|
-
|
|
65
|
-
- `.dynamo` — Dynamo CLI projekt-konfiguráció
|
|
66
|
-
- `.github/workflows` — GitHub Actions (Overseer-migrált projektnél csak webhook-trigger)
|
|
67
|
-
- `.husky` — Git hookok (pre-commit gate)
|
|
68
|
-
- `__documentations` — Projekt-dokumentáció (ARCHITECTURE / DECISIONS / BEDROCK-FRS / CHANGELOG)
|
|
69
|
-
<!-- SKILLS:AUTO:END -->
|
|
70
|
-
|
|
71
|
-
## 3. Projekt-specifikus jegyzetek (KÉZI — a generátor nem írja felül)
|
|
72
|
-
|
|
73
|
-
_Ide jön minden, amit mérésből nem lehet kiolvasni: buktatók, kötelező sorrendek, környezeti előfeltételek,
|
|
74
|
-
credential-lelőhely, „ezt sose futtasd" figyelmeztetések. Ha itt üres, az annyit jelent: még nincs feljegyezve —
|
|
75
|
-
NEM azt, hogy nincs ilyen (`core-no-guessing`)._
|