@astrale-os/sdk 0.5.0-beta.43 → 0.5.0-beta.45
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/dist/platform/schema/index.d.ts +10 -2
- package/dist/platform/schema/index.js +4 -1
- package/dist/tooling/packaging/admission.js +1 -1
- package/dist/tooling/packaging/compile.d.ts +6 -4
- package/dist/tooling/packaging/compile.js +131 -68
- package/dist/tooling/packaging/declarations/normalize.js +6 -1
- package/dist/tooling/packaging/exports.js +9 -9
- package/dist/tooling/packaging/javascript.d.ts +2 -0
- package/dist/tooling/packaging/{runtime.js → javascript.js} +20 -18
- package/dist/tooling/packaging/packaging.d.ts +1 -1
- package/dist/tooling/packaging/packaging.js +102 -74
- package/package.json +1 -1
- package/dist/tooling/packaging/runtime.d.ts +0 -4
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import { KernelSchema as CoreKernelSchema } from '@astrale-os/kernel-core/schema';
|
|
2
2
|
export * from '@astrale-os/kernel-dsl/v1';
|
|
3
3
|
export { patterns } from '@astrale-os/kernel-dsl/v1/addressing';
|
|
4
4
|
export type { CallablePolicyBuilder, CallablePolicyContext, CallableProjection, AuthoredMethodProjection, AuthoredPropertyProjection, ClassIdentityProjection, ClassOwnerProjection, CoreEdge, CoreEndpoint, CoreNode, DefinedSchema, DirectedEdgeClass, ClassReferenceProjection, ClassProjection, FunctionProjection, EdgeClass, Function, Method, MethodProjection, Lazy, NodeClass, Policy, Property, PropertyProjection, ResolvedDomainOfInput, View, } from '@astrale-os/kernel-dsl/v1/builder';
|
|
@@ -6,5 +6,13 @@ export type { Array, Object, Value } from '@astrale-os/kernel-dsl/value';
|
|
|
6
6
|
export type { ClassKind, DirectedEdgeContract, EdgeClassPolicies, NodeClassDefinition, PolicyCheckObject, PropertyFacet, UndirectedEdgeContract, } from '@astrale-os/kernel-dsl/v1/language';
|
|
7
7
|
export type { DomainSchema } from '@astrale-os/kernel-dsl/v1/schema';
|
|
8
8
|
/** Kernel-owned Schema values used by authored product Schemas and graph operations. */
|
|
9
|
-
export { AuthorityOperationOrder, AuthorityOperations, AuthorityRelations, K, KernelRootFunctions,
|
|
9
|
+
export { AuthorityOperationOrder, AuthorityOperations, AuthorityRelations, K, KernelRootFunctions, OperationCore, operationOf, } from '@astrale-os/kernel-core/schema';
|
|
10
10
|
export type { AuthorityOperation, AuthorityRelation, Kernel } from '@astrale-os/kernel-core/schema';
|
|
11
|
+
/**
|
|
12
|
+
* Re-admit the Kernel Schema into the SDK's DSL instance. Package-manager trees may retain a
|
|
13
|
+
* different DSL instance below Kernel Core; acceptance context is intentionally process-local.
|
|
14
|
+
* The SDK-owned interface also gives downstream declaration emit a portable public type name.
|
|
15
|
+
*/
|
|
16
|
+
export interface KernelSchema extends CoreKernelSchema {
|
|
17
|
+
}
|
|
18
|
+
export declare const KernelSchema: KernelSchema;
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { KernelSchema as CoreKernelSchema } from '@astrale-os/kernel-core/schema';
|
|
1
2
|
/** Canonical Schema authoring, compilation, loading, and resolved Domain surface. */
|
|
3
|
+
import { schema as language } from '@astrale-os/kernel-dsl/v1';
|
|
2
4
|
export * from '@astrale-os/kernel-dsl/v1';
|
|
3
5
|
export { patterns } from '@astrale-os/kernel-dsl/v1/addressing';
|
|
4
6
|
/** Kernel-owned Schema values used by authored product Schemas and graph operations. */
|
|
5
|
-
export { AuthorityOperationOrder, AuthorityOperations, AuthorityRelations, K, KernelRootFunctions,
|
|
7
|
+
export { AuthorityOperationOrder, AuthorityOperations, AuthorityRelations, K, KernelRootFunctions, OperationCore, operationOf, } from '@astrale-os/kernel-core/schema';
|
|
8
|
+
export const KernelSchema = language.accept(CoreKernelSchema);
|
|
@@ -66,7 +66,7 @@ function moduleSpecifier(node) {
|
|
|
66
66
|
return node.argument.literal.text;
|
|
67
67
|
}
|
|
68
68
|
if (ts.isCallExpression(node) &&
|
|
69
|
-
node.arguments.length
|
|
69
|
+
node.arguments.length > 0 &&
|
|
70
70
|
ts.isStringLiteral(node.arguments[0]) &&
|
|
71
71
|
(node.expression.kind === ts.SyntaxKind.ImportKeyword ||
|
|
72
72
|
(ts.isIdentifier(node.expression) && node.expression.text === 'require'))) {
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
export interface DomainPackageCompilation {
|
|
2
2
|
readonly root: string;
|
|
3
|
+
readonly typeClosure: readonly DomainPackageTypeClosure[];
|
|
3
4
|
}
|
|
4
|
-
export interface
|
|
5
|
-
readonly
|
|
5
|
+
export interface DomainPackageTypeClosure {
|
|
6
|
+
readonly source: string;
|
|
7
|
+
readonly target: string;
|
|
6
8
|
}
|
|
7
|
-
/** Compile one Domain's
|
|
8
|
-
export declare function compileDomainPackage(projectDir: string
|
|
9
|
+
/** Compile one Domain's admitted Schema contract with SDK-owned emit policy. */
|
|
10
|
+
export declare function compileDomainPackage(projectDir: string): DomainPackageCompilation;
|
|
@@ -1,70 +1,55 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
|
-
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, writeFileSync, } from 'node:fs';
|
|
3
3
|
import { createRequire } from 'node:module';
|
|
4
4
|
import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
5
5
|
import ts from 'typescript';
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
const entrypoint = 'schema/index.ts';
|
|
7
|
+
/** Compile one Domain's admitted Schema contract with SDK-owned emit policy. */
|
|
8
|
+
export function compileDomainPackage(projectDir) {
|
|
8
9
|
const project = resolve(projectDir);
|
|
9
|
-
const
|
|
10
|
-
|
|
10
|
+
const root = resolve(project, 'dist');
|
|
11
|
+
rmSync(root, { recursive: true, force: true });
|
|
12
|
+
const source = resolve(project, entrypoint);
|
|
13
|
+
if (!existsSync(source))
|
|
14
|
+
throw new TypeError(`Domain contract entrypoint is absent: ${entrypoint}.`);
|
|
15
|
+
const configurationPath = resolve(project, 'tsconfig.json');
|
|
11
16
|
const loaded = ts.readConfigFile(configurationPath, ts.sys.readFile);
|
|
12
17
|
if (loaded.error !== undefined)
|
|
13
18
|
failDiagnostics([loaded.error], project);
|
|
14
19
|
const parsed = ts.parseJsonConfigFileContent(loaded.config, ts.sys, project, undefined, configurationPath);
|
|
15
|
-
const root = parsed.options.outDir && resolve(parsed.options.outDir);
|
|
16
|
-
if (root !== undefined)
|
|
17
|
-
confineOutput(project, root);
|
|
18
20
|
if (errors(parsed.errors).length > 0)
|
|
19
21
|
failDiagnostics(parsed.errors, project);
|
|
20
|
-
if (root === undefined)
|
|
21
|
-
throw new TypeError(`${configuration} must declare compilerOptions.outDir.`);
|
|
22
|
-
if (parsed.options.noEmit || !parsed.options.declaration || parsed.options.emitDeclarationOnly) {
|
|
23
|
-
throw new TypeError(`${configuration} must emit both JavaScript and declarations.`);
|
|
24
|
-
}
|
|
25
|
-
if (parsed.options.declarationDir !== undefined &&
|
|
26
|
-
resolve(parsed.options.declarationDir) !== root) {
|
|
27
|
-
throw new TypeError(`${configuration} must emit JavaScript and declarations into one outDir.`);
|
|
28
|
-
}
|
|
29
22
|
if (parsed.fileNames.length === 0)
|
|
30
|
-
throw new TypeError(
|
|
31
|
-
if (root !== resolve(project, 'dist')) {
|
|
32
|
-
throw new TypeError('Domain package output must be the project dist directory.');
|
|
33
|
-
}
|
|
34
|
-
const declarationFiles = input.declarations.map((entrypoint) => declarationSource(project, root, parsed.options.rootDir, entrypoint));
|
|
23
|
+
throw new TypeError('tsconfig.json compiles no source files.');
|
|
35
24
|
const temporary = mkdtempSync(join(project, '.astrale-package-'));
|
|
36
|
-
const
|
|
37
|
-
writeFileSync(
|
|
25
|
+
const packageConfiguration = resolve(temporary, 'tsconfig.json');
|
|
26
|
+
writeFileSync(packageConfiguration, JSON.stringify({
|
|
38
27
|
extends: configurationPath,
|
|
39
28
|
compilerOptions: {
|
|
29
|
+
rootDir: project,
|
|
30
|
+
outDir: root,
|
|
40
31
|
noEmit: false,
|
|
41
32
|
declaration: true,
|
|
42
33
|
declarationMap: false,
|
|
43
34
|
sourceMap: false,
|
|
44
|
-
|
|
35
|
+
inlineSourceMap: false,
|
|
36
|
+
inlineSources: false,
|
|
37
|
+
allowImportingTsExtensions: false,
|
|
38
|
+
emitDeclarationOnly: false,
|
|
39
|
+
noEmitOnError: true,
|
|
40
|
+
composite: false,
|
|
41
|
+
incremental: false,
|
|
45
42
|
},
|
|
46
|
-
files:
|
|
43
|
+
files: [source],
|
|
47
44
|
include: [],
|
|
45
|
+
exclude: [],
|
|
48
46
|
}));
|
|
49
47
|
try {
|
|
50
|
-
|
|
48
|
+
const typeClosure = admitSchemaClosure(project, packageConfiguration);
|
|
51
49
|
runCompiler(project, ['-p', configurationPath, '--noEmit']);
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
configurationPath,
|
|
56
|
-
'--noEmit',
|
|
57
|
-
'false',
|
|
58
|
-
'--declaration',
|
|
59
|
-
'false',
|
|
60
|
-
'--declarationMap',
|
|
61
|
-
'false',
|
|
62
|
-
'--sourceMap',
|
|
63
|
-
'false',
|
|
64
|
-
'--emitDeclarationOnly',
|
|
65
|
-
'false',
|
|
66
|
-
]);
|
|
67
|
-
runCompiler(project, ['-p', declarationConfiguration]);
|
|
50
|
+
runCompiler(project, ['-p', packageConfiguration]);
|
|
51
|
+
relocateTypeClosure(root, typeClosure);
|
|
52
|
+
return Object.freeze({ root, typeClosure });
|
|
68
53
|
}
|
|
69
54
|
catch (cause) {
|
|
70
55
|
rmSync(root, { recursive: true, force: true });
|
|
@@ -73,7 +58,109 @@ export function compileDomainPackage(projectDir, input) {
|
|
|
73
58
|
finally {
|
|
74
59
|
rmSync(temporary, { recursive: true, force: true });
|
|
75
60
|
}
|
|
76
|
-
|
|
61
|
+
}
|
|
62
|
+
function admitSchemaClosure(project, configuration) {
|
|
63
|
+
const loaded = ts.readConfigFile(configuration, ts.sys.readFile);
|
|
64
|
+
if (loaded.error !== undefined)
|
|
65
|
+
failDiagnostics([loaded.error], project);
|
|
66
|
+
const parsed = ts.parseJsonConfigFileContent(loaded.config, ts.sys, project, undefined, configuration);
|
|
67
|
+
if (errors(parsed.errors).length > 0)
|
|
68
|
+
failDiagnostics(parsed.errors, project);
|
|
69
|
+
const program = ts.createProgram(parsed.fileNames, parsed.options);
|
|
70
|
+
const local = new Map();
|
|
71
|
+
for (const document of program.getSourceFiles()) {
|
|
72
|
+
const path = relative(project, document.fileName);
|
|
73
|
+
if (path === '..' ||
|
|
74
|
+
path.startsWith(`..${sep}`) ||
|
|
75
|
+
isAbsolute(path) ||
|
|
76
|
+
path === 'node_modules' ||
|
|
77
|
+
path.startsWith(`node_modules${sep}`)) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
local.set(resolve(document.fileName), document);
|
|
81
|
+
}
|
|
82
|
+
for (const document of local.values()) {
|
|
83
|
+
const owner = relative(project, document.fileName).replaceAll('\\', '/');
|
|
84
|
+
if (!owner.startsWith('schema/'))
|
|
85
|
+
continue;
|
|
86
|
+
const visit = (node) => {
|
|
87
|
+
const specifier = referencedModule(node);
|
|
88
|
+
if (specifier !== undefined) {
|
|
89
|
+
const resolved = ts.resolveModuleName(specifier, document.fileName, parsed.options, ts.sys)
|
|
90
|
+
.resolvedModule?.resolvedFileName;
|
|
91
|
+
if (resolved !== undefined) {
|
|
92
|
+
const dependency = local.get(resolve(resolved));
|
|
93
|
+
if (dependency !== undefined) {
|
|
94
|
+
const path = relative(project, dependency.fileName).replaceAll('\\', '/');
|
|
95
|
+
if (!path.startsWith('schema/') && !isTypeOnlyReference(node)) {
|
|
96
|
+
throw new TypeError(`Domain Schema contract imports non-Schema source: ${path}.`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
ts.forEachChild(node, visit);
|
|
102
|
+
};
|
|
103
|
+
visit(document);
|
|
104
|
+
}
|
|
105
|
+
return Object.freeze([...local.values()]
|
|
106
|
+
.map((document) => relative(project, document.fileName).replaceAll('\\', '/'))
|
|
107
|
+
.filter((path) => !path.startsWith('schema/'))
|
|
108
|
+
.sort()
|
|
109
|
+
.map((source) => Object.freeze({
|
|
110
|
+
source,
|
|
111
|
+
target: `schema/.types/${source.replace(/\.(?:tsx?|mts|cts)$/u, '.js')}`,
|
|
112
|
+
})));
|
|
113
|
+
}
|
|
114
|
+
function referencedModule(node) {
|
|
115
|
+
if ((ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) &&
|
|
116
|
+
node.moduleSpecifier !== undefined &&
|
|
117
|
+
ts.isStringLiteral(node.moduleSpecifier)) {
|
|
118
|
+
return node.moduleSpecifier.text;
|
|
119
|
+
}
|
|
120
|
+
if (ts.isImportTypeNode(node) &&
|
|
121
|
+
ts.isLiteralTypeNode(node.argument) &&
|
|
122
|
+
ts.isStringLiteral(node.argument.literal)) {
|
|
123
|
+
return node.argument.literal.text;
|
|
124
|
+
}
|
|
125
|
+
if (ts.isCallExpression(node) &&
|
|
126
|
+
node.arguments.length > 0 &&
|
|
127
|
+
ts.isStringLiteral(node.arguments[0]) &&
|
|
128
|
+
(node.expression.kind === ts.SyntaxKind.ImportKeyword ||
|
|
129
|
+
(ts.isIdentifier(node.expression) && node.expression.text === 'require'))) {
|
|
130
|
+
return node.arguments[0].text;
|
|
131
|
+
}
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
function isTypeOnlyReference(node) {
|
|
135
|
+
if (ts.isImportTypeNode(node))
|
|
136
|
+
return true;
|
|
137
|
+
if (ts.isExportDeclaration(node))
|
|
138
|
+
return node.isTypeOnly;
|
|
139
|
+
if (!ts.isImportDeclaration(node))
|
|
140
|
+
return false;
|
|
141
|
+
const clause = node.importClause;
|
|
142
|
+
if (clause?.isTypeOnly)
|
|
143
|
+
return true;
|
|
144
|
+
if (clause?.name !== undefined || clause?.namedBindings === undefined)
|
|
145
|
+
return false;
|
|
146
|
+
return (ts.isNamedImports(clause.namedBindings) &&
|
|
147
|
+
clause.namedBindings.elements.length > 0 &&
|
|
148
|
+
clause.namedBindings.elements.every((element) => element.isTypeOnly));
|
|
149
|
+
}
|
|
150
|
+
function relocateTypeClosure(root, closure) {
|
|
151
|
+
for (const { source, target } of closure) {
|
|
152
|
+
const emitted = resolve(root, source.replace(/\.(?:tsx?|mts|cts)$/u, ''));
|
|
153
|
+
const declaration = `${emitted}.d.ts`;
|
|
154
|
+
if (!existsSync(declaration)) {
|
|
155
|
+
throw new TypeError(`Domain contract type closure declaration is absent: ${source}.`);
|
|
156
|
+
}
|
|
157
|
+
const destination = resolve(root, target.replace(/\.js$/u, '.d.ts'));
|
|
158
|
+
mkdirSync(dirname(destination), { recursive: true });
|
|
159
|
+
renameSync(declaration, destination);
|
|
160
|
+
for (const extension of ['.js', '.mjs', '.cjs', '.jsx']) {
|
|
161
|
+
rmSync(`${emitted}${extension}`, { force: true });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
77
164
|
}
|
|
78
165
|
function runCompiler(project, arguments_) {
|
|
79
166
|
const compiler = resolveCompiler();
|
|
@@ -113,30 +200,6 @@ function resolveCompiler() {
|
|
|
113
200
|
}
|
|
114
201
|
return compiler;
|
|
115
202
|
}
|
|
116
|
-
function declarationSource(project, output, rootDir, entrypoint) {
|
|
117
|
-
const emitted = relative(output, resolve(project, entrypoint));
|
|
118
|
-
if (emitted === '..' || emitted.startsWith(`..${sep}`) || isAbsolute(emitted)) {
|
|
119
|
-
throw new TypeError(`Published declaration entrypoint escapes package output: ${entrypoint}.`);
|
|
120
|
-
}
|
|
121
|
-
const source = emitted
|
|
122
|
-
.replace(/\.d\.mts$/u, '.mts')
|
|
123
|
-
.replace(/\.d\.cts$/u, '.cts')
|
|
124
|
-
.replace(/\.d\.ts$/u, '.ts');
|
|
125
|
-
if (source === emitted) {
|
|
126
|
-
throw new TypeError(`Published declaration entrypoint is not a declaration: ${entrypoint}.`);
|
|
127
|
-
}
|
|
128
|
-
const file = resolve(rootDir === undefined ? project : rootDir, source);
|
|
129
|
-
if (!existsSync(file)) {
|
|
130
|
-
throw new TypeError(`Published declaration source is absent: ${source}.`);
|
|
131
|
-
}
|
|
132
|
-
return file;
|
|
133
|
-
}
|
|
134
|
-
function confineOutput(project, output) {
|
|
135
|
-
const path = relative(project, output);
|
|
136
|
-
if (path === '' || path === '..' || path.startsWith(`..${sep}`) || isAbsolute(path)) {
|
|
137
|
-
throw new TypeError('Domain package output must be a child of the project root.');
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
203
|
function errors(diagnostics) {
|
|
141
204
|
return diagnostics.filter(({ category }) => category === ts.DiagnosticCategory.Error);
|
|
142
205
|
}
|
|
@@ -8,6 +8,7 @@ export async function normalizeDeclarations(input) {
|
|
|
8
8
|
const root = resolve(input.root);
|
|
9
9
|
const output = new Map();
|
|
10
10
|
const admittedFacades = new Map();
|
|
11
|
+
const admittedImports = admitPackageImports(input.imports ?? {});
|
|
11
12
|
let replacements = 0;
|
|
12
13
|
const files = reachableDeclarations(root, input.entrypoints, input.imports);
|
|
13
14
|
const unreachable = allDeclarations(root).filter((file) => !files.includes(file));
|
|
@@ -17,6 +18,10 @@ export async function normalizeDeclarations(input) {
|
|
|
17
18
|
if (specifier.startsWith('.')) {
|
|
18
19
|
return runtimeSpecifier(file, resolveLocal(file, specifier));
|
|
19
20
|
}
|
|
21
|
+
if (specifier.startsWith('#')) {
|
|
22
|
+
const target = resolvePackageImport(specifier, admittedImports);
|
|
23
|
+
return runtimeSpecifier(file, resolveImportTarget(root, target));
|
|
24
|
+
}
|
|
20
25
|
const key = JSON.stringify([specifier, symbols]);
|
|
21
26
|
if (admittedFacades.has(key))
|
|
22
27
|
return admittedFacades.get(key);
|
|
@@ -107,7 +112,7 @@ function runtimeSpecifier(containing, declaration) {
|
|
|
107
112
|
.replace(/\.d\.cts$/u, '.cjs')
|
|
108
113
|
.replace(/\.d\.ts$/u, '.js');
|
|
109
114
|
const path = relative(dirname(containing), runtime).replaceAll('\\', '/');
|
|
110
|
-
return path.startsWith('.') ? path : `./${path}`;
|
|
115
|
+
return path.startsWith('./') || path.startsWith('../') ? path : `./${path}`;
|
|
111
116
|
}
|
|
112
117
|
function confined(root, file) {
|
|
113
118
|
const path = relative(realpathSync(root), realpathSync(file));
|
|
@@ -4,14 +4,14 @@ import { isAbsolute, relative, resolve, sep } from 'node:path';
|
|
|
4
4
|
export function verifyPublishedJavaScript(projectDir, manifest) {
|
|
5
5
|
const project = resolve(projectDir);
|
|
6
6
|
const targets = new Set();
|
|
7
|
-
|
|
7
|
+
collectJavaScriptTarget(manifest.publishConfig?.main ?? manifest.main, targets, false);
|
|
8
8
|
collectExports(manifest.publishConfig?.exports ?? manifest.exports, targets);
|
|
9
9
|
if (targets.size === 0) {
|
|
10
|
-
throw new TypeError('Published Domain
|
|
10
|
+
throw new TypeError('Published Domain contract declares no JavaScript export.');
|
|
11
11
|
}
|
|
12
12
|
for (const target of [...targets].sort()) {
|
|
13
13
|
if (!target.startsWith('./dist/') || !/\.(?:c|m)?js$/u.test(target)) {
|
|
14
|
-
throw new TypeError(`Published
|
|
14
|
+
throw new TypeError(`Published contract export must target emitted JavaScript under ./dist: ${target}.`);
|
|
15
15
|
}
|
|
16
16
|
const file = resolve(project, target);
|
|
17
17
|
const path = relative(project, file);
|
|
@@ -23,7 +23,7 @@ export function verifyPublishedJavaScript(projectDir, manifest) {
|
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
-
function
|
|
26
|
+
function collectJavaScriptTarget(value, targets, types) {
|
|
27
27
|
if (typeof value === 'string') {
|
|
28
28
|
if (!types)
|
|
29
29
|
targets.add(value);
|
|
@@ -31,24 +31,24 @@ function collectRuntimeTarget(value, targets, types) {
|
|
|
31
31
|
}
|
|
32
32
|
if (Array.isArray(value)) {
|
|
33
33
|
for (const nested of value)
|
|
34
|
-
|
|
34
|
+
collectJavaScriptTarget(nested, targets, types);
|
|
35
35
|
return;
|
|
36
36
|
}
|
|
37
37
|
if (value === null || typeof value !== 'object')
|
|
38
38
|
return;
|
|
39
39
|
for (const [condition, nested] of Object.entries(value)) {
|
|
40
|
-
|
|
40
|
+
collectJavaScriptTarget(nested, targets, types || condition === 'types');
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
function collectExports(value, targets) {
|
|
44
44
|
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
45
|
-
|
|
45
|
+
collectJavaScriptTarget(value, targets, false);
|
|
46
46
|
return;
|
|
47
47
|
}
|
|
48
48
|
const entries = Object.entries(value);
|
|
49
49
|
const subpaths = entries.filter(([key]) => key.startsWith('.'));
|
|
50
50
|
if (subpaths.length === 0) {
|
|
51
|
-
|
|
51
|
+
collectJavaScriptTarget(value, targets, false);
|
|
52
52
|
return;
|
|
53
53
|
}
|
|
54
54
|
if (subpaths.length !== entries.length) {
|
|
@@ -56,6 +56,6 @@ function collectExports(value, targets) {
|
|
|
56
56
|
}
|
|
57
57
|
for (const [subpath, target] of subpaths) {
|
|
58
58
|
if (subpath !== './package.json')
|
|
59
|
-
|
|
59
|
+
collectJavaScriptTarget(target, targets, false);
|
|
60
60
|
}
|
|
61
61
|
}
|
|
@@ -1,32 +1,30 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { readFileSync, readdirSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
3
|
import ts from 'typescript';
|
|
4
|
-
/**
|
|
5
|
-
export function
|
|
6
|
-
|
|
4
|
+
/** Verify emitted Schema JavaScript and return its complete external package set. */
|
|
5
|
+
export function verifyContractJavaScript(root) {
|
|
6
|
+
const packages = new Set();
|
|
7
|
+
for (const file of javascriptFiles(root)) {
|
|
7
8
|
const source = readFileSync(file, 'utf8');
|
|
8
9
|
const document = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true);
|
|
9
10
|
visit(document);
|
|
10
11
|
function visit(node) {
|
|
11
12
|
const specifier = moduleSpecifier(node);
|
|
12
13
|
if (specifier?.startsWith('@astrale-os/kernel-')) {
|
|
13
|
-
throw new TypeError(`Domain
|
|
14
|
+
throw new TypeError(`Domain contract imports a Kernel package directly: ${specifier} in ${file}.`);
|
|
15
|
+
}
|
|
16
|
+
if (specifier !== undefined &&
|
|
17
|
+
!specifier.startsWith('.') &&
|
|
18
|
+
!specifier.startsWith('#') &&
|
|
19
|
+
!specifier.startsWith('node:')) {
|
|
20
|
+
packages.add(packageName(specifier));
|
|
14
21
|
}
|
|
15
22
|
ts.forEachChild(node, visit);
|
|
16
23
|
}
|
|
17
24
|
}
|
|
25
|
+
return Object.freeze([...packages].sort());
|
|
18
26
|
}
|
|
19
|
-
|
|
20
|
-
export function writeRuntimeDeclaration(root, entrypoint) {
|
|
21
|
-
const file = resolve(root, entrypoint);
|
|
22
|
-
const path = relative(resolve(root), file);
|
|
23
|
-
if (path === '' || path === '..' || path.startsWith(`..${sep}`) || isAbsolute(path)) {
|
|
24
|
-
throw new TypeError('Runtime declaration entrypoint escapes package output.');
|
|
25
|
-
}
|
|
26
|
-
mkdirSync(dirname(file), { recursive: true });
|
|
27
|
-
writeFileSync(file, "import type { Runtime } from '@astrale-os/sdk/runtime'\n\ndeclare const runtime: Runtime\nexport default runtime\n");
|
|
28
|
-
}
|
|
29
|
-
function runtimeFiles(root) {
|
|
27
|
+
function javascriptFiles(root) {
|
|
30
28
|
const files = [];
|
|
31
29
|
const visit = (directory) => {
|
|
32
30
|
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
@@ -46,7 +44,7 @@ function moduleSpecifier(node) {
|
|
|
46
44
|
ts.isStringLiteral(node.moduleSpecifier)) {
|
|
47
45
|
return node.moduleSpecifier.text;
|
|
48
46
|
}
|
|
49
|
-
if (!ts.isCallExpression(node) || node.arguments.length
|
|
47
|
+
if (!ts.isCallExpression(node) || node.arguments.length === 0)
|
|
50
48
|
return undefined;
|
|
51
49
|
const dynamicImport = node.expression.kind === ts.SyntaxKind.ImportKeyword;
|
|
52
50
|
const requireCall = ts.isIdentifier(node.expression) && node.expression.text === 'require';
|
|
@@ -55,3 +53,7 @@ function moduleSpecifier(node) {
|
|
|
55
53
|
? argument.text
|
|
56
54
|
: undefined;
|
|
57
55
|
}
|
|
56
|
+
function packageName(specifier) {
|
|
57
|
+
const parts = specifier.split('/');
|
|
58
|
+
return specifier.startsWith('@') ? `${parts[0]}/${parts[1]}` : parts[0];
|
|
59
|
+
}
|
|
@@ -6,5 +6,5 @@ export interface PackageDomainResult {
|
|
|
6
6
|
readonly replacements: number;
|
|
7
7
|
readonly packages: readonly string[];
|
|
8
8
|
}
|
|
9
|
-
/** Compile, normalize, and verify one
|
|
9
|
+
/** Compile, normalize, and verify one public Domain Schema contract package. */
|
|
10
10
|
export declare function packageDomain(input: PackageDomainInput): Promise<PackageDomainResult>;
|
|
@@ -1,92 +1,120 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { rm } from 'node:fs/promises';
|
|
2
3
|
import { resolve } from 'node:path';
|
|
3
4
|
import { admitDomainPackage } from './admission.js';
|
|
4
5
|
import { compileDomainPackage } from './compile.js';
|
|
5
6
|
import { normalizeDeclarations, verifyDeclarationPackage } from './declarations/index.js';
|
|
6
7
|
import { verifyPublishedJavaScript } from './exports.js';
|
|
7
|
-
import {
|
|
8
|
-
/** Compile, normalize, and verify one
|
|
8
|
+
import { verifyContractJavaScript } from './javascript.js';
|
|
9
|
+
/** Compile, normalize, and verify one public Domain Schema contract package. */
|
|
9
10
|
export async function packageDomain(input) {
|
|
10
11
|
const projectDir = resolve(input.projectDir);
|
|
11
12
|
const manifest = JSON.parse(await readFile(resolve(projectDir, 'package.json'), 'utf8'));
|
|
12
13
|
admitDomainPackage(projectDir, manifest);
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
function relativeOutputRoot(project, output) {
|
|
38
|
-
return output.slice(project.length + 1).replaceAll('\\', '/');
|
|
14
|
+
admitContractManifest(manifest);
|
|
15
|
+
const { root, typeClosure } = compileDomainPackage(projectDir);
|
|
16
|
+
try {
|
|
17
|
+
const entrypoints = ['schema/index.d.ts'];
|
|
18
|
+
verifyPublishedJavaScript(projectDir, manifest);
|
|
19
|
+
const javascriptPackages = verifyContractJavaScript(root);
|
|
20
|
+
const normalized = await normalizeDeclarations({
|
|
21
|
+
root,
|
|
22
|
+
imports: declarationImports(manifest, typeClosure),
|
|
23
|
+
entrypoints,
|
|
24
|
+
});
|
|
25
|
+
const verified = await verifyDeclarationPackage({ root, packageRoot: projectDir, entrypoints });
|
|
26
|
+
const packages = Object.freeze([...new Set([...javascriptPackages, ...verified.packages])].sort());
|
|
27
|
+
verifyDependencies(manifest, packages);
|
|
28
|
+
return Object.freeze({
|
|
29
|
+
declarations: verified.files.length,
|
|
30
|
+
replacements: normalized.replacements,
|
|
31
|
+
packages,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
catch (cause) {
|
|
35
|
+
await rm(root, { recursive: true, force: true });
|
|
36
|
+
throw cause;
|
|
37
|
+
}
|
|
39
38
|
}
|
|
40
|
-
function
|
|
41
|
-
const
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
return;
|
|
39
|
+
function declarationImports(manifest, closure) {
|
|
40
|
+
const imports = { ...manifest.publishConfig?.imports };
|
|
41
|
+
for (const { source, target } of closure) {
|
|
42
|
+
const specifier = packageImportForSource(manifest.imports ?? {}, source);
|
|
43
|
+
if (specifier === undefined) {
|
|
44
|
+
throw new TypeError(`Domain contract type closure has no package import: ${source}.`);
|
|
47
45
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
collect(nested, condition === 'types');
|
|
52
|
-
};
|
|
53
|
-
const published = manifest.publishConfig;
|
|
54
|
-
collect(published?.types ?? manifest.types, true);
|
|
55
|
-
collect(published?.exports ?? manifest.exports);
|
|
56
|
-
if (values.size === 0)
|
|
57
|
-
throw new TypeError('Published Domain package declares no TypeScript entrypoint.');
|
|
58
|
-
return Object.freeze([...values].sort());
|
|
46
|
+
imports[specifier] = `./dist/${target}`;
|
|
47
|
+
}
|
|
48
|
+
return Object.keys(imports).length === 0 ? undefined : imports;
|
|
59
49
|
}
|
|
60
|
-
function
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (types || /\.d\.(?:ts|mts|cts)$/u.test(value))
|
|
71
|
-
values.push(normalizePath(value));
|
|
72
|
-
return;
|
|
50
|
+
function packageImportForSource(imports, source) {
|
|
51
|
+
for (const [specifier, target] of Object.entries(imports).sort(([left], [right]) => left.localeCompare(right))) {
|
|
52
|
+
if (typeof target !== 'string')
|
|
53
|
+
continue;
|
|
54
|
+
const normalized = target.replace(/^\.\//u, '');
|
|
55
|
+
const wildcard = normalized.indexOf('*');
|
|
56
|
+
if (wildcard === -1) {
|
|
57
|
+
if (normalized === source)
|
|
58
|
+
return specifier;
|
|
59
|
+
continue;
|
|
73
60
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
61
|
+
const prefix = normalized.slice(0, wildcard);
|
|
62
|
+
const suffix = normalized.slice(wildcard + 1);
|
|
63
|
+
if (!source.startsWith(prefix) || !source.endsWith(suffix))
|
|
64
|
+
continue;
|
|
65
|
+
const value = source.slice(prefix.length, source.length - suffix.length);
|
|
66
|
+
return specifier.replace('*', value);
|
|
67
|
+
}
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
function admitContractManifest(manifest) {
|
|
71
|
+
if (manifest.files?.length !== 1 || manifest.files[0] !== 'dist') {
|
|
72
|
+
throw new TypeError('Domain contract package files must be exactly ["dist"].');
|
|
73
|
+
}
|
|
74
|
+
if (manifest.main !== './schema/index.ts' || manifest.types !== './schema/index.ts') {
|
|
75
|
+
throw new TypeError('Domain contract source entrypoint must be ./schema/index.ts.');
|
|
82
76
|
}
|
|
83
|
-
|
|
77
|
+
admitExports(manifest.exports, './schema/index.ts', './schema/index.ts', 'source');
|
|
78
|
+
const published = manifest.publishConfig;
|
|
79
|
+
if (published?.main !== './dist/schema/index.js' ||
|
|
80
|
+
published.types !== './dist/schema/index.d.ts') {
|
|
81
|
+
throw new TypeError('Published Domain contract entrypoint must be ./dist/schema/index.js.');
|
|
82
|
+
}
|
|
83
|
+
admitExports(published.exports, './dist/schema/index.d.ts', './dist/schema/index.js', 'published');
|
|
84
84
|
}
|
|
85
|
-
function
|
|
86
|
-
|
|
85
|
+
function admitExports(exports, types, imported, label) {
|
|
86
|
+
if (exports === null || typeof exports !== 'object' || Array.isArray(exports)) {
|
|
87
|
+
throw new TypeError(`Domain contract ${label} exports must be an object.`);
|
|
88
|
+
}
|
|
89
|
+
const record = exports;
|
|
90
|
+
const keys = Object.keys(record).sort();
|
|
91
|
+
if (keys.length !== 2 || keys[0] !== '.' || keys[1] !== './package.json') {
|
|
92
|
+
throw new TypeError(`Domain contract ${label} exports must contain only "." and "./package.json".`);
|
|
93
|
+
}
|
|
94
|
+
const root = record['.'];
|
|
95
|
+
if (root === undefined ||
|
|
96
|
+
typeof root !== 'object' ||
|
|
97
|
+
root.types !== types ||
|
|
98
|
+
root.import !== imported) {
|
|
99
|
+
throw new TypeError(`Domain contract ${label} root export is invalid.`);
|
|
100
|
+
}
|
|
101
|
+
if (record['./package.json'] !== './package.json') {
|
|
102
|
+
throw new TypeError(`Domain contract ${label} package metadata export is invalid.`);
|
|
103
|
+
}
|
|
87
104
|
}
|
|
88
|
-
function
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
105
|
+
function verifyDependencies(manifest, packages) {
|
|
106
|
+
const declared = new Set([
|
|
107
|
+
...Object.keys(manifest.dependencies ?? {}),
|
|
108
|
+
...Object.keys(manifest.optionalDependencies ?? {}),
|
|
109
|
+
...Object.keys(manifest.peerDependencies ?? {}),
|
|
110
|
+
]);
|
|
111
|
+
const used = new Set(packages);
|
|
112
|
+
const missing = packages.filter((name) => !declared.has(name));
|
|
113
|
+
if (missing.length > 0) {
|
|
114
|
+
throw new TypeError(`Published Domain contract references undeclared packages: ${missing.join(', ')}.`);
|
|
115
|
+
}
|
|
116
|
+
const unused = [...declared].filter((name) => !used.has(name)).sort();
|
|
117
|
+
if (unused.length > 0) {
|
|
118
|
+
throw new TypeError(`Domain contract declares unused production packages: ${unused.join(', ')}.`);
|
|
119
|
+
}
|
|
92
120
|
}
|
package/package.json
CHANGED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
/** Reject direct Kernel runtime imports from a packaged product Domain. */
|
|
2
|
-
export declare function verifyRuntimeFacades(root: string): void;
|
|
3
|
-
/** Publish only the admitted Runtime identity; handlers and providers remain package-private. */
|
|
4
|
-
export declare function writeRuntimeDeclaration(root: string, entrypoint: string): void;
|