@neocompose/cli 0.29.0 → 0.30.0
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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.30.0] - 2026-08-13
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- P66 sealed classes: author a leaf type as `sealed class` in project source.
|
|
8
|
+
The modifier round-trips through pull and push, and the CLI rejects both
|
|
9
|
+
`sealed abstract` declarations and attempts to extend a sealed class.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Sealed classes are omitted from extends-position language completions, and
|
|
14
|
+
the project contract carries optional `isSealed` metadata. Absence and
|
|
15
|
+
explicit `false` are equivalent, so existing projects need no migration.
|
|
16
|
+
- Unity export schema version is 20. Matching SDKs preserve the sealed
|
|
17
|
+
inheritance contract in generated C#.
|
|
18
|
+
|
|
3
19
|
## [0.29.0] - 2026-08-12
|
|
4
20
|
|
|
5
21
|
### Added
|
package/dist/neo.mjs
CHANGED
|
@@ -17376,7 +17376,7 @@ var init_project_schema_contract_generated = __esm({
|
|
|
17376
17376
|
"../packages/neoscript-language/src/project-schema-contract.generated.ts"() {
|
|
17377
17377
|
"use strict";
|
|
17378
17378
|
PROJECT_SCHEMA_MANIFEST_FORMAT_VERSION = 3;
|
|
17379
|
-
PROJECT_SCHEMA_MANIFEST_CONTRACT_VERSION = "3.
|
|
17379
|
+
PROJECT_SCHEMA_MANIFEST_CONTRACT_VERSION = "3.11";
|
|
17380
17380
|
PROJECT_FILE_UPLOAD_BATCH_SIZE = 32;
|
|
17381
17381
|
NEO_PROJECT_SOURCE_RECORD_CONTRACT = {
|
|
17382
17382
|
"recordFields": {
|
|
@@ -17437,6 +17437,7 @@ var init_project_schema_contract_generated = __esm({
|
|
|
17437
17437
|
"extendsClassId",
|
|
17438
17438
|
"implementsInterfaceIds",
|
|
17439
17439
|
"hiddenInMemberSelector",
|
|
17440
|
+
"isSealed",
|
|
17440
17441
|
"isAbstract",
|
|
17441
17442
|
"system",
|
|
17442
17443
|
"allowedStorage",
|
|
@@ -26020,6 +26021,7 @@ function projectFallbackCompletions(analysis, document, position) {
|
|
|
26020
26021
|
const expectedTypeName = construction?.expectedTypeName ?? recoveredInitializerType;
|
|
26021
26022
|
const afterNew = expression && projectAfterNewAt(document, position);
|
|
26022
26023
|
const typePosition = projectTypePositionAt(analysis, document, position);
|
|
26024
|
+
const classBasePosition = projectClassBasePositionAt(document, position);
|
|
26023
26025
|
const keywords = expression ? afterNew ? [] : ["new"] : typePosition ? [] : [
|
|
26024
26026
|
"class",
|
|
26025
26027
|
"interface",
|
|
@@ -26058,6 +26060,11 @@ function projectFallbackCompletions(analysis, document, position) {
|
|
|
26058
26060
|
continue;
|
|
26059
26061
|
}
|
|
26060
26062
|
const isType = symbol.kind === "class" || symbol.kind === "interface" || symbol.kind === "enum" || symbol.kind === "genericParameter";
|
|
26063
|
+
if (classBasePosition && symbol.kind === "class") {
|
|
26064
|
+
const declaration = findTypeDeclaration(analysis, symbol.name);
|
|
26065
|
+
if (declaration?.kind === "class" && declaration.modifiers.includes("sealed"))
|
|
26066
|
+
continue;
|
|
26067
|
+
}
|
|
26061
26068
|
if (typePosition && !isType) continue;
|
|
26062
26069
|
if (expression) {
|
|
26063
26070
|
if (afterNew) {
|
|
@@ -26199,6 +26206,49 @@ function projectTypePositionAt(analysis, document, position) {
|
|
|
26199
26206
|
line
|
|
26200
26207
|
);
|
|
26201
26208
|
}
|
|
26209
|
+
function projectClassBasePositionAt(document, position) {
|
|
26210
|
+
const tokens = tokensBeforePosition(document.text, position);
|
|
26211
|
+
let classIndex = -1;
|
|
26212
|
+
for (let index = tokens.length - 1; index >= 0; index--) {
|
|
26213
|
+
const text = tokens[index]?.text;
|
|
26214
|
+
if (text === "class") {
|
|
26215
|
+
classIndex = index;
|
|
26216
|
+
break;
|
|
26217
|
+
}
|
|
26218
|
+
if (text === "{" || text === "}" || text === ";") return false;
|
|
26219
|
+
}
|
|
26220
|
+
if (classIndex < 0) return false;
|
|
26221
|
+
let angleDepth = 0;
|
|
26222
|
+
let parenthesisDepth = 0;
|
|
26223
|
+
let bracketDepth = 0;
|
|
26224
|
+
let colonIndex = -1;
|
|
26225
|
+
for (let index = classIndex + 1; index < tokens.length; index++) {
|
|
26226
|
+
const text = tokens[index]?.text;
|
|
26227
|
+
if (text === "<") angleDepth++;
|
|
26228
|
+
else if (text === ">") angleDepth = Math.max(0, angleDepth - 1);
|
|
26229
|
+
else if (text === "(") parenthesisDepth++;
|
|
26230
|
+
else if (text === ")") parenthesisDepth = Math.max(0, parenthesisDepth - 1);
|
|
26231
|
+
else if (text === "[") bracketDepth++;
|
|
26232
|
+
else if (text === "]") bracketDepth = Math.max(0, bracketDepth - 1);
|
|
26233
|
+
else if (text === ":" && angleDepth === 0 && parenthesisDepth === 0 && bracketDepth === 0) {
|
|
26234
|
+
colonIndex = index;
|
|
26235
|
+
break;
|
|
26236
|
+
} else if (text === "{" || text === "}" || text === ";") {
|
|
26237
|
+
return false;
|
|
26238
|
+
}
|
|
26239
|
+
}
|
|
26240
|
+
if (colonIndex < 0) return false;
|
|
26241
|
+
angleDepth = 0;
|
|
26242
|
+
for (let index = colonIndex + 1; index < tokens.length; index++) {
|
|
26243
|
+
const text = tokens[index]?.text;
|
|
26244
|
+
if (text === "<") angleDepth++;
|
|
26245
|
+
else if (text === ">") angleDepth = Math.max(0, angleDepth - 1);
|
|
26246
|
+
else if (angleDepth === 0 && (text === "(" || text === "{" || text === "}" || text === ";")) {
|
|
26247
|
+
return false;
|
|
26248
|
+
}
|
|
26249
|
+
}
|
|
26250
|
+
return true;
|
|
26251
|
+
}
|
|
26202
26252
|
function sourceTypeAt(document, position) {
|
|
26203
26253
|
const contains2 = (type) => {
|
|
26204
26254
|
for (const argument2 of type.typeArguments) {
|
|
@@ -31520,6 +31570,7 @@ var init_document_contracts = __esm({
|
|
|
31520
31570
|
"extendsClassId",
|
|
31521
31571
|
"implementsInterfaceIds",
|
|
31522
31572
|
"hiddenInMemberSelector",
|
|
31573
|
+
"isSealed",
|
|
31523
31574
|
"isAbstract",
|
|
31524
31575
|
"system",
|
|
31525
31576
|
"allowedStorage",
|
|
@@ -31534,6 +31585,7 @@ var init_document_contracts = __esm({
|
|
|
31534
31585
|
derived: [],
|
|
31535
31586
|
volatile: ["projectId", "createdAt", "updatedAt"],
|
|
31536
31587
|
normalization: {
|
|
31588
|
+
isSealed: "falseIsAbsent",
|
|
31537
31589
|
targetMemberId: "nullIsAbsent",
|
|
31538
31590
|
schemaKeyOrder: "nullIsAbsent",
|
|
31539
31591
|
extendsClassId: "nullIsAbsent",
|
|
@@ -31802,6 +31854,10 @@ function normalizeDocumentFields(recordKind, value) {
|
|
|
31802
31854
|
}
|
|
31803
31855
|
continue;
|
|
31804
31856
|
}
|
|
31857
|
+
if (rule === "falseIsAbsent") {
|
|
31858
|
+
if (current === false) delete normalized[field];
|
|
31859
|
+
continue;
|
|
31860
|
+
}
|
|
31805
31861
|
const isFullMember = recordKind !== "member" || typeof normalized.extendsMemberId !== "string";
|
|
31806
31862
|
if (recordKind === "member" && (current === void 0 || current === null) && rule.startsWith("missingIs") && !memberDefaultApplies(field, normalized.kind)) {
|
|
31807
31863
|
if (current === null) delete normalized[field];
|
|
@@ -34399,6 +34455,7 @@ function schemaClassFromDocument(record3, context) {
|
|
|
34399
34455
|
name: requiredString(data, "name", record3),
|
|
34400
34456
|
...optionalDocsTextProperty(data.docsText, record3),
|
|
34401
34457
|
declarationModifier: data.isAbstract === true ? "abstract" : "concrete",
|
|
34458
|
+
...data.isSealed === void 0 ? {} : { isSealed: booleanValue(data.isSealed, record3, "isSealed") },
|
|
34402
34459
|
schema: requiredRecord(data, "schema", record3),
|
|
34403
34460
|
schemaKeyOrder: optionalStringArrayOrNull(data.schemaKeyOrder),
|
|
34404
34461
|
extendsClassId: optionalStringOrNull(data.extendsClassId),
|
|
@@ -34434,6 +34491,7 @@ function schemaClassToDocument(schemaClass2) {
|
|
|
34434
34491
|
implementsInterfaceIds: schemaClass2.implementsInterfaceIds,
|
|
34435
34492
|
hiddenInMemberSelector: schemaClass2.hiddenInMemberSelector,
|
|
34436
34493
|
isAbstract: schemaClass2.declarationModifier === "abstract",
|
|
34494
|
+
isSealed: schemaClass2.isSealed,
|
|
34437
34495
|
system: systemToDocument(schemaClass2.system),
|
|
34438
34496
|
allowedStorage: schemaClass2.allowedStorage,
|
|
34439
34497
|
allowedStorageKeys: schemaClass2.allowedStorageKeys,
|
|
@@ -35708,6 +35766,7 @@ function assertNeoSchemaClass(value, path) {
|
|
|
35708
35766
|
],
|
|
35709
35767
|
[
|
|
35710
35768
|
"docsText",
|
|
35769
|
+
"isSealed",
|
|
35711
35770
|
"constructorProjections",
|
|
35712
35771
|
"constructorIds",
|
|
35713
35772
|
"requiredConstructorId"
|
|
@@ -35723,6 +35782,9 @@ function assertNeoSchemaClass(value, path) {
|
|
|
35723
35782
|
);
|
|
35724
35783
|
stringRecord(type.schema, `${path}.schema`);
|
|
35725
35784
|
nullableStringArray(type.schemaKeyOrder, `${path}.schemaKeyOrder`);
|
|
35785
|
+
if (type.isSealed !== void 0) {
|
|
35786
|
+
booleanAt(type.isSealed, `${path}.isSealed`);
|
|
35787
|
+
}
|
|
35726
35788
|
nullableNonEmptyString(type.extendsClassId, `${path}.extendsClassId`);
|
|
35727
35789
|
stringArray(type.implementsInterfaceIds, `${path}.implementsInterfaceIds`);
|
|
35728
35790
|
booleanAt(type.hiddenInMemberSelector, `${path}.hiddenInMemberSelector`);
|
|
@@ -39482,7 +39544,7 @@ function isNeoSchemaClassBase(value) {
|
|
|
39482
39544
|
const v = value;
|
|
39483
39545
|
return typeof v?.name === "string" && isValidDocsText(v.docsText) && isClassSchema(v?.schema) && isOptionalSchemaKeyOrder(v?.schemaKeyOrder) && (v.implementsInterfaceIds === void 0 || v.implementsInterfaceIds === null || Array.isArray(v.implementsInterfaceIds) && v.implementsInterfaceIds.every(
|
|
39484
39546
|
(interfaceId) => typeof interfaceId === "string" && interfaceId.length > 0
|
|
39485
|
-
)) && typeof v?.hiddenInMemberSelector === "boolean" && typeof v?.isAbstract === "boolean" && (v.allowedStorage === void 0 || v.allowedStorage === null || isEffectiveMemberStorage(v.allowedStorage)) && (v.allowedStorageKeys === void 0 || v.allowedStorageKeys === null || Array.isArray(v.allowedStorageKeys) && v.allowedStorageKeys.every((key) => typeof key === "string")) && (v.genericParams === void 0 || v.genericParams === null || Array.isArray(v.genericParams) && v.genericParams.every(isGenericParamDeclaration)) && (v.extendsGenericBindings === void 0 || v.extendsGenericBindings === null || isGenericBindingsRecord(v.extendsGenericBindings)) && (v.constructorProjections === void 0 || v.constructorProjections === null || Array.isArray(v.constructorProjections) && v.constructorProjections.every(isNeoClassConstructorProjection) && v.system?.kind === SystemProtectionKind.WorldAuthoring) && (v.constructorIds === void 0 || v.constructorIds === null || Array.isArray(v.constructorIds) && v.constructorIds.every(
|
|
39547
|
+
)) && typeof v?.hiddenInMemberSelector === "boolean" && typeof v?.isAbstract === "boolean" && (v.isSealed === void 0 || typeof v.isSealed === "boolean") && (v.allowedStorage === void 0 || v.allowedStorage === null || isEffectiveMemberStorage(v.allowedStorage)) && (v.allowedStorageKeys === void 0 || v.allowedStorageKeys === null || Array.isArray(v.allowedStorageKeys) && v.allowedStorageKeys.every((key) => typeof key === "string")) && (v.genericParams === void 0 || v.genericParams === null || Array.isArray(v.genericParams) && v.genericParams.every(isGenericParamDeclaration)) && (v.extendsGenericBindings === void 0 || v.extendsGenericBindings === null || isGenericBindingsRecord(v.extendsGenericBindings)) && (v.constructorProjections === void 0 || v.constructorProjections === null || Array.isArray(v.constructorProjections) && v.constructorProjections.every(isNeoClassConstructorProjection) && v.system?.kind === SystemProtectionKind.WorldAuthoring) && (v.constructorIds === void 0 || v.constructorIds === null || Array.isArray(v.constructorIds) && v.constructorIds.every(
|
|
39486
39548
|
(constructorId) => typeof constructorId === "string" && constructorId.length > 0
|
|
39487
39549
|
) && new Set(v.constructorIds).size === v.constructorIds.length) && (v.requiredConstructorId === void 0 || v.requiredConstructorId === null || typeof v.requiredConstructorId === "string" && v.requiredConstructorId.length > 0 && (v.constructorIds ?? []).length === 0) && (v.targetMemberId === void 0 || v.targetMemberId === null || typeof v.targetMemberId === "string" && v.targetMemberId.length > 0) && (v.system === void 0 || v.system === null || isSystemMetadata(v.system));
|
|
39488
39550
|
}
|
|
@@ -46682,7 +46744,7 @@ function emitClass(context, schemaClass2, memberIds) {
|
|
|
46682
46744
|
...schemaClass2.system ? [systemAnnotation2(schemaClass2.system)] : [],
|
|
46683
46745
|
...relationsAnnotations(context, schemaClass2)
|
|
46684
46746
|
];
|
|
46685
|
-
const modifier = schemaClass2.declarationModifier === "abstract" ? "abstract " : "";
|
|
46747
|
+
const modifier = schemaClass2.isSealed ? "sealed " : schemaClass2.declarationModifier === "abstract" ? "abstract " : "";
|
|
46686
46748
|
const generics = schemaClass2.genericParameters.length ? `<
|
|
46687
46749
|
${schemaClass2.genericParameters.map((parameter4) => {
|
|
46688
46750
|
const constraint = parameter4.constraint ? ` extends ${parameter4.constraint.kind === "class" ? required(
|
|
@@ -53034,6 +53096,7 @@ function lowerClass(context, declaration) {
|
|
|
53034
53096
|
name: declaration.name,
|
|
53035
53097
|
...declaration.docsText === void 0 ? {} : { docsText: declaration.docsText },
|
|
53036
53098
|
declarationModifier: declaration.modifiers.includes("abstract") ? "abstract" : "concrete",
|
|
53099
|
+
...declaration.modifiers.includes("sealed") ? { isSealed: true } : {},
|
|
53037
53100
|
schema,
|
|
53038
53101
|
schemaKeyOrder: persistedSchemaOrder,
|
|
53039
53102
|
extendsClassId,
|
|
@@ -77015,6 +77078,19 @@ function readClass(value) {
|
|
|
77015
77078
|
throw new Error(`Class "${value.id}" schema value is invalid.`);
|
|
77016
77079
|
}
|
|
77017
77080
|
}
|
|
77081
|
+
if (value.isSealed !== void 0 && typeof value.isSealed !== "boolean") {
|
|
77082
|
+
throw new Error(`Class "${value.name}" (${value.id}) isSealed is invalid.`);
|
|
77083
|
+
}
|
|
77084
|
+
if (value.isAbstract !== void 0 && typeof value.isAbstract !== "boolean") {
|
|
77085
|
+
throw new Error(
|
|
77086
|
+
`Class "${value.name}" (${value.id}) isAbstract is invalid.`
|
|
77087
|
+
);
|
|
77088
|
+
}
|
|
77089
|
+
if (value.isAbstract === true && value.isSealed === true) {
|
|
77090
|
+
throw new Error(
|
|
77091
|
+
`Class "${value.name}" (${value.id}) cannot be both abstract and sealed.`
|
|
77092
|
+
);
|
|
77093
|
+
}
|
|
77018
77094
|
if (value.implementsInterfaceIds != null && (!Array.isArray(value.implementsInterfaceIds) || !value.implementsInterfaceIds.every((id2) => typeof id2 === "string"))) {
|
|
77019
77095
|
throw new Error(`Class "${value.id}" implementsInterfaceIds is invalid.`);
|
|
77020
77096
|
}
|
|
@@ -77287,6 +77363,14 @@ function assertProjectInterfaceDocumentValid(view, options = {}) {
|
|
|
77287
77363
|
const classesById = new Map(
|
|
77288
77364
|
classes.map((schemaClass2) => [schemaClass2.id, schemaClass2])
|
|
77289
77365
|
);
|
|
77366
|
+
for (const schemaClass2 of classes) {
|
|
77367
|
+
if (schemaClass2.extendsClassId === void 0) continue;
|
|
77368
|
+
const baseClass = classesById.get(schemaClass2.extendsClassId);
|
|
77369
|
+
if (baseClass?.isSealed !== true) continue;
|
|
77370
|
+
throw new Error(
|
|
77371
|
+
`Class "${schemaClass2.name}" (${schemaClass2.id}) cannot extend sealed class "${baseClass.name}" (${baseClass.id}).`
|
|
77372
|
+
);
|
|
77373
|
+
}
|
|
77290
77374
|
const genericParamConstraintsById = new Map(
|
|
77291
77375
|
classes.flatMap(
|
|
77292
77376
|
(schemaClass2) => (schemaClass2.genericParams ?? []).map(
|
|
@@ -104529,8 +104613,8 @@ var init_registry2 = __esm({
|
|
|
104529
104613
|
"use strict";
|
|
104530
104614
|
PROJECT_SCHEMA_CONTRACT = Object.freeze({
|
|
104531
104615
|
formatVersion: 3,
|
|
104532
|
-
contractVersion: "3.
|
|
104533
|
-
cliVersion: "0.
|
|
104616
|
+
contractVersion: "3.11",
|
|
104617
|
+
cliVersion: "0.30.0",
|
|
104534
104618
|
projectFileUploadBatchSize: 32,
|
|
104535
104619
|
documentRecords: {
|
|
104536
104620
|
member: {
|
|
@@ -104625,6 +104709,7 @@ var init_registry2 = __esm({
|
|
|
104625
104709
|
"extendsClassId",
|
|
104626
104710
|
"implementsInterfaceIds",
|
|
104627
104711
|
"hiddenInMemberSelector",
|
|
104712
|
+
"isSealed",
|
|
104628
104713
|
"isAbstract",
|
|
104629
104714
|
"system",
|
|
104630
104715
|
"allowedStorage",
|
|
@@ -104639,6 +104724,7 @@ var init_registry2 = __esm({
|
|
|
104639
104724
|
derived: [],
|
|
104640
104725
|
volatile: ["projectId", "createdAt", "updatedAt"],
|
|
104641
104726
|
normalization: {
|
|
104727
|
+
isSealed: "falseIsAbsent",
|
|
104642
104728
|
targetMemberId: "nullIsAbsent",
|
|
104643
104729
|
schemaKeyOrder: "nullIsAbsent",
|
|
104644
104730
|
extendsClassId: "nullIsAbsent",
|
|
@@ -111096,7 +111182,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
|
|
|
111096
111182
|
async function main() {
|
|
111097
111183
|
const args = parseArgs(process.argv.slice(2));
|
|
111098
111184
|
if (args.command === "--version") {
|
|
111099
|
-
console.log("0.
|
|
111185
|
+
console.log("0.30.0");
|
|
111100
111186
|
return;
|
|
111101
111187
|
}
|
|
111102
111188
|
if (args.command === null || args.command === "help" || args.command === "--help" || args.command === "-h") {
|
package/package.json
CHANGED
|
@@ -83,7 +83,7 @@ wrappers.
|
|
|
83
83
|
The marker near the top of `SKILL.md` must exactly match the package version:
|
|
84
84
|
|
|
85
85
|
```html
|
|
86
|
-
<!-- reviewed-through-cli: 0.
|
|
86
|
+
<!-- reviewed-through-cli: 0.30.0 -->
|
|
87
87
|
```
|
|
88
88
|
|
|
89
89
|
The quoted version above is checked too, so this instruction cannot go stale
|
|
@@ -47,8 +47,17 @@ abstract class Item<TContext extends SomeClass> : INamed {
|
|
|
47
47
|
```
|
|
48
48
|
|
|
49
49
|
Use ordinary `public`, `protected`, `private`, `static`, `virtual`, `abstract`,
|
|
50
|
-
and `override` semantics.
|
|
51
|
-
|
|
50
|
+
`sealed`, and `override` semantics. A sealed class is an instantiable leaf:
|
|
51
|
+
it may extend another class but cannot be extended, cannot also be abstract,
|
|
52
|
+
and can only be sealed while it has no subclasses. Interfaces are non-generic.
|
|
53
|
+
Do not put `@id` on inferred list/dictionary type entries or on derived
|
|
54
|
+
structural descriptors.
|
|
55
|
+
|
|
56
|
+
```neo
|
|
57
|
+
sealed class DirtTile : PlaceableTile {
|
|
58
|
+
public bool IsWet = false;
|
|
59
|
+
}
|
|
60
|
+
```
|
|
52
61
|
|
|
53
62
|
A bodyless function must be `abstract`, `native`, or an interface contract.
|
|
54
63
|
`async` alone does not make it valid.
|