genesis-compiler 1.10.3 → 1.10.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/docs/templates.md +2 -2
- package/package.json +1 -1
- package/plugins/genesis/.codex-plugin/plugin.json +1 -1
- package/src/index/city-documents.js +148 -0
- package/src/index/code-index.js +7 -6
- package/src/index.js +1 -0
package/README.md
CHANGED
|
@@ -941,6 +941,7 @@ import {
|
|
|
941
941
|
syncSkills,
|
|
942
942
|
validateArchitectureFacts,
|
|
943
943
|
validateArchitectureObservations,
|
|
944
|
+
validateCityDocument,
|
|
944
945
|
verify,
|
|
945
946
|
withTrustedGitRepository,
|
|
946
947
|
} from 'genesis-compiler';
|
|
@@ -991,6 +992,12 @@ and packages; `connectArchitectureObservations()` joins exact owners and
|
|
|
991
992
|
controller operations. `mergeArchitectureObservations()` combines distinct
|
|
992
993
|
inspectors while rejecting conflicting facts or package snapshots.
|
|
993
994
|
`validateArchitectureFacts()` validates the combined architecture snapshot.
|
|
995
|
+
`validateCityDocument(city, kind)` validates a saved or freshly built Machine
|
|
996
|
+
(`'machine'`) or Program (`'program'`) City document against the format Genesis
|
|
997
|
+
writes, including embedded architecture facts, and returns it unchanged; hosts
|
|
998
|
+
reading `.genesis/*-city.json` call it rather than keeping their own copy.
|
|
999
|
+
`MACHINE_CITY_SCHEMA`, `PROGRAM_CITY_SCHEMA` and `CITY_SCHEMA_VERSION` name
|
|
1000
|
+
those formats.
|
|
994
1001
|
These operations neither run a framework inspector nor claim complete runtime
|
|
995
1002
|
behavior.
|
|
996
1003
|
`sourceExplanationGuidance()` and `sourceExplanationReviewGuidance()` supply
|
package/docs/templates.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Project opening and templates
|
|
2
2
|
|
|
3
|
-
The official JSKIT choices are **Basic app**, **
|
|
4
|
-
|
|
3
|
+
The official JSKIT choices are **Basic app**, **App with no users, but a database**,
|
|
4
|
+
**App with users** and **Accounts hub**. Basic keeps the existing `jskit/public` ID and
|
|
5
5
|
`public` branch. Database uses `jskit/database` and the `database` branch,
|
|
6
6
|
adding MySQL without login. Accounts keeps `jskit/accounts` and the `accounts`
|
|
7
7
|
branch. Accounts hub uses `jskit/accounts-hub` and the `accounts-hub` branch:
|
package/package.json
CHANGED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { GenesisError } from './errors.js';
|
|
2
|
+
import { validateArchitectureFacts } from './architecture.js';
|
|
3
|
+
|
|
4
|
+
// The Machine and Program City document formats are produced here
|
|
5
|
+
// (code-index.js), so their shape and validation are owned here too. A host
|
|
6
|
+
// reading a saved City calls validateCityDocument instead of keeping a copy.
|
|
7
|
+
export const MACHINE_CITY_SCHEMA = 'genesis.machine-city.v1';
|
|
8
|
+
export const PROGRAM_CITY_SCHEMA = 'genesis.program-city.v1';
|
|
9
|
+
export const CITY_SCHEMA_VERSION = 1;
|
|
10
|
+
|
|
11
|
+
const CITY_KINDS = Object.freeze({
|
|
12
|
+
machine: Object.freeze({ schema: MACHINE_CITY_SCHEMA, title: 'Machine City' }),
|
|
13
|
+
program: Object.freeze({ schema: PROGRAM_CITY_SCHEMA, title: 'Program City' }),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
function checker(kind) {
|
|
17
|
+
const title = CITY_KINDS[kind].title;
|
|
18
|
+
const check = (condition, message) => {
|
|
19
|
+
if (!condition) throw new GenesisError('CITY_INVALID', `${title} ${message}`, { kind });
|
|
20
|
+
};
|
|
21
|
+
const record = (value, label) => check(value !== null && typeof value === 'object' && !Array.isArray(value), `${label} must be an object.`);
|
|
22
|
+
const array = (value, label) => check(Array.isArray(value), `${label} must be an array.`);
|
|
23
|
+
const string = (value, label) => check(typeof value === 'string', `${label} must be a string.`);
|
|
24
|
+
const optionalString = (value, label) => check(value === null || typeof value === 'string', `${label} must be a string or null.`);
|
|
25
|
+
const boolean = (value, label) => check(typeof value === 'boolean', `${label} must be a boolean.`);
|
|
26
|
+
const count = (value, label) => check(Number.isInteger(value) && value >= 0, `${label} must be a non-negative integer.`);
|
|
27
|
+
const strings = (value, label) => {
|
|
28
|
+
array(value, label);
|
|
29
|
+
value.forEach((entry, index) => string(entry, `${label}[${index}]`));
|
|
30
|
+
};
|
|
31
|
+
const diagnostics = (value) => {
|
|
32
|
+
array(value, 'diagnostics');
|
|
33
|
+
value.forEach((entry, index) => record(entry, `diagnostics[${index}]`));
|
|
34
|
+
};
|
|
35
|
+
const uniqueIds = (records, label) => {
|
|
36
|
+
const seen = new Set();
|
|
37
|
+
records.forEach((entry, index) => {
|
|
38
|
+
record(entry, `${label}[${index}]`);
|
|
39
|
+
string(entry.id, `${label}[${index}].id`);
|
|
40
|
+
check(!seen.has(entry.id), `${label} contains duplicate id ${entry.id}.`);
|
|
41
|
+
seen.add(entry.id);
|
|
42
|
+
});
|
|
43
|
+
return seen;
|
|
44
|
+
};
|
|
45
|
+
const districts = (value) => {
|
|
46
|
+
array(value, 'districts');
|
|
47
|
+
const ids = uniqueIds(value, 'districts');
|
|
48
|
+
value.forEach((district, index) => {
|
|
49
|
+
const label = `districts[${index}]`;
|
|
50
|
+
string(district.path, `${label}.path`);
|
|
51
|
+
string(district.title, `${label}.title`);
|
|
52
|
+
optionalString(district.parentId, `${label}.parentId`);
|
|
53
|
+
if (district.parentId !== null) check(ids.has(district.parentId), `${label}.parentId does not identify a district.`);
|
|
54
|
+
});
|
|
55
|
+
return ids;
|
|
56
|
+
};
|
|
57
|
+
return { array, boolean, check, count, diagnostics, districts, optionalString, record, string, strings, uniqueIds };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function validateMachineCity(city) {
|
|
61
|
+
const v = checker('machine');
|
|
62
|
+
v.string(city.codeHash, 'codeHash');
|
|
63
|
+
v.strings(city.stackComponents, 'stackComponents');
|
|
64
|
+
v.strings(city.indexers, 'indexers');
|
|
65
|
+
v.diagnostics(city.diagnostics);
|
|
66
|
+
const districtIds = v.districts(city.districts);
|
|
67
|
+
|
|
68
|
+
v.array(city.buildings, 'buildings');
|
|
69
|
+
const buildingIds = v.uniqueIds(city.buildings, 'buildings');
|
|
70
|
+
city.buildings.forEach((building, index) => {
|
|
71
|
+
const label = `buildings[${index}]`;
|
|
72
|
+
for (const field of ['path', 'title', 'districtId', 'language', 'role', 'hash']) v.string(building[field], `${label}.${field}`);
|
|
73
|
+
v.check(districtIds.has(building.districtId), `${label}.districtId does not identify a district.`);
|
|
74
|
+
for (const field of ['mode', 'bytes', 'lines']) v.count(building[field], `${label}.${field}`);
|
|
75
|
+
v.strings(building.extractors, `${label}.extractors`);
|
|
76
|
+
v.strings(building.functionIds, `${label}.functionIds`);
|
|
77
|
+
v.count(building.publicFunctionCount, `${label}.publicFunctionCount`);
|
|
78
|
+
v.count(building.internalFunctionCount, `${label}.internalFunctionCount`);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
v.array(city.functions, 'functions');
|
|
82
|
+
const functionIds = v.uniqueIds(city.functions, 'functions');
|
|
83
|
+
city.functions.forEach((entry, index) => {
|
|
84
|
+
const label = `functions[${index}]`;
|
|
85
|
+
for (const field of ['name', 'qualifiedName', 'kind', 'visibility', 'path', 'fileId', 'role', 'language', 'extractor']) {
|
|
86
|
+
v.string(entry[field], `${label}.${field}`);
|
|
87
|
+
}
|
|
88
|
+
v.optionalString(entry.container, `${label}.container`);
|
|
89
|
+
v.check(buildingIds.has(entry.fileId), `${label}.fileId does not identify a building.`);
|
|
90
|
+
v.count(entry.line, `${label}.line`);
|
|
91
|
+
v.count(entry.column, `${label}.column`);
|
|
92
|
+
v.strings(entry.parameters, `${label}.parameters`);
|
|
93
|
+
for (const field of ['async', 'generator', 'static']) v.boolean(entry[field], `${label}.${field}`);
|
|
94
|
+
});
|
|
95
|
+
city.buildings.forEach((building, index) => {
|
|
96
|
+
for (const functionId of building.functionIds) {
|
|
97
|
+
v.check(functionIds.has(functionId), `buildings[${index}].functionIds contains an unknown function id.`);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
if (city.architecture) validateArchitectureFacts(city.architecture);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function validateProgramCity(city) {
|
|
104
|
+
const v = checker('program');
|
|
105
|
+
v.string(city.programHash, 'programHash');
|
|
106
|
+
v.diagnostics(city.diagnostics);
|
|
107
|
+
const districtIds = v.districts(city.districts);
|
|
108
|
+
|
|
109
|
+
v.array(city.buildings, 'buildings');
|
|
110
|
+
const buildingIds = v.uniqueIds(city.buildings, 'buildings');
|
|
111
|
+
city.buildings.forEach((building, index) => {
|
|
112
|
+
const label = `buildings[${index}]`;
|
|
113
|
+
for (const field of ['name', 'title', 'description', 'publicContract', 'implementationMap', 'path']) v.string(building[field], `${label}.${field}`);
|
|
114
|
+
v.optionalString(building.subsystem, `${label}.subsystem`);
|
|
115
|
+
v.optionalString(building.districtId, `${label}.districtId`);
|
|
116
|
+
v.check(building.districtId === null || districtIds.has(building.districtId), `${label}.districtId does not identify a district.`);
|
|
117
|
+
v.strings(building.sources, `${label}.sources`);
|
|
118
|
+
if (building.mainSource !== undefined) {
|
|
119
|
+
v.string(building.mainSource, `${label}.mainSource`);
|
|
120
|
+
v.check(building.sources.includes(building.mainSource), `${label}.mainSource must be a cited source.`);
|
|
121
|
+
}
|
|
122
|
+
v.strings(building.sourceFileIds, `${label}.sourceFileIds`);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
v.array(city.links, 'links');
|
|
126
|
+
city.links.forEach((link, index) => {
|
|
127
|
+
const label = `links[${index}]`;
|
|
128
|
+
v.record(link, label);
|
|
129
|
+
v.check(link.kind === 'implemented-by', `${label}.kind must be implemented-by.`);
|
|
130
|
+
v.string(link.fromId, `${label}.fromId`);
|
|
131
|
+
v.string(link.toId, `${label}.toId`);
|
|
132
|
+
v.check(buildingIds.has(link.fromId), `${label}.fromId does not identify a Program building.`);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Validates a saved or freshly built City document of the given kind
|
|
137
|
+
// ('machine' or 'program') and returns it unchanged.
|
|
138
|
+
export function validateCityDocument(city, kind) {
|
|
139
|
+
if (!Object.hasOwn(CITY_KINDS, kind)) throw new GenesisError('CITY_KIND_UNSUPPORTED', `City kind is not supported: ${String(kind || '')}`);
|
|
140
|
+
const v = checker(kind);
|
|
141
|
+
v.record(city, 'document');
|
|
142
|
+
v.check(city.schema === CITY_KINDS[kind].schema, `schema must be ${CITY_KINDS[kind].schema}.`);
|
|
143
|
+
v.check(city.schemaVersion === CITY_SCHEMA_VERSION, `schemaVersion must be ${CITY_SCHEMA_VERSION}.`);
|
|
144
|
+
v.string(city.status, 'status');
|
|
145
|
+
if (kind === 'machine') validateMachineCity(city);
|
|
146
|
+
else validateProgramCity(city);
|
|
147
|
+
return city;
|
|
148
|
+
}
|
package/src/index/code-index.js
CHANGED
|
@@ -11,6 +11,7 @@ import { isProjectContentPath } from './paths.js';
|
|
|
11
11
|
import { inspectProgram } from './program.js';
|
|
12
12
|
import { inspectSubsystems } from './subsystems.js';
|
|
13
13
|
import { buildArchitectureFacts } from './architecture.js';
|
|
14
|
+
import { CITY_SCHEMA_VERSION, MACHINE_CITY_SCHEMA, PROGRAM_CITY_SCHEMA } from './city-documents.js';
|
|
14
15
|
import { gitVisibleFileStates } from './project-files.js';
|
|
15
16
|
import { readStack } from './stack.js';
|
|
16
17
|
import { sha256, stableJson, writeFileAtomic } from './utils.js';
|
|
@@ -162,8 +163,8 @@ function machineCity({ cityRegions, components, contributions, diagnostics, inde
|
|
|
162
163
|
mode,
|
|
163
164
|
}))));
|
|
164
165
|
return {
|
|
165
|
-
schema:
|
|
166
|
-
schemaVersion:
|
|
166
|
+
schema: MACHINE_CITY_SCHEMA,
|
|
167
|
+
schemaVersion: CITY_SCHEMA_VERSION,
|
|
167
168
|
status: indexers.length === 0 ? 'unconfigured' : diagnostics.length > 0 ? 'completed-with-warning' : 'current',
|
|
168
169
|
codeHash,
|
|
169
170
|
presentationRegions: presentation.regions,
|
|
@@ -206,8 +207,8 @@ async function programCity(projectRoot) {
|
|
|
206
207
|
sourceFileIds: module.sources.map(fileId),
|
|
207
208
|
}));
|
|
208
209
|
return {
|
|
209
|
-
schema:
|
|
210
|
-
schemaVersion:
|
|
210
|
+
schema: PROGRAM_CITY_SCHEMA,
|
|
211
|
+
schemaVersion: CITY_SCHEMA_VERSION,
|
|
211
212
|
status: program.status,
|
|
212
213
|
programHash: sha256(stableJson({ operations, subsystems: program.subsystemMap })),
|
|
213
214
|
diagnostics: [],
|
|
@@ -221,8 +222,8 @@ async function programCity(projectRoot) {
|
|
|
221
222
|
};
|
|
222
223
|
} catch (error) {
|
|
223
224
|
return {
|
|
224
|
-
schema:
|
|
225
|
-
schemaVersion:
|
|
225
|
+
schema: PROGRAM_CITY_SCHEMA,
|
|
226
|
+
schemaVersion: CITY_SCHEMA_VERSION,
|
|
226
227
|
status: 'invalid',
|
|
227
228
|
programHash: sha256('invalid'),
|
|
228
229
|
diagnostics: [asDiagnostic(error)],
|
package/src/index.js
CHANGED
|
@@ -248,3 +248,4 @@ export { inspectOpenCodePlugin, installOpenCodePlugin as syncOpenCodePlugin } fr
|
|
|
248
248
|
export { ARCHITECTURE_OBSERVATIONS_SCHEMA, validateArchitectureObservations, connectArchitectureObservations, mergeArchitectureObservations } from './index/architecture-observations.js';
|
|
249
249
|
|
|
250
250
|
export { validateArchitectureFacts } from './index/architecture.js';
|
|
251
|
+
export { CITY_SCHEMA_VERSION, MACHINE_CITY_SCHEMA, PROGRAM_CITY_SCHEMA, validateCityDocument } from './index/city-documents.js';
|