@neocompose/cli 0.50.1 → 0.50.3

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,14 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.50.3] - 2026-09-14
4
+
5
+ - Preserve packed siblings when preparing push source identity for new children in an existing value graph. Pending seeds now fill missing rows without replacing reconstructed stored rows.
6
+ - Resolve omitted fields on stored classes without construction records from their shared declaration defaults. CLI validation, NeoScript reads, and editor value cells now agree without adding per-instance overrides or inventing constructor provenance.
7
+
8
+ ## [0.50.2] - 2026-09-12
9
+
10
+ - Export Unity C# as separate files named after their generated types. Keep stable identities in the manifest to preserve Unity GUIDs through renames. Preserve unchanged files and their Unity metadata, remove obsolete owned outputs, and share the integrity manifest with editor synchronization.
11
+
3
12
  ## [0.50.1] - 2026-09-12
4
13
 
5
14
  - Reuse schema and symbol indexes while compiling a push, including migration bodies, and compile each changed member body once. Bulk scripted changes no longer rebuild the complete project for every body.
package/dist/neo.mjs CHANGED
@@ -35365,7 +35365,7 @@ var init_login = __esm({
35365
35365
  "project:localization:export",
35366
35366
  "project:localization:import",
35367
35367
  "project:release-channel:read",
35368
- // `neo export unity` writes project.json + NeoGeneratedTypes.cs headlessly
35368
+ // `neo export unity` writes project.json + generated C# files headlessly
35369
35369
  // (the escape hatch when game code references not-yet-generated members and
35370
35370
  // a broken compile blocks the in-editor sync).
35371
35371
  "unity:export",
@@ -53894,7 +53894,7 @@ function buildDefaultMemberValue(args) {
53894
53894
  `Cannot create a value of missing class "${effectiveClassId}".`
53895
53895
  );
53896
53896
  }
53897
- if (member.payload !== 1 /* Partial */ && typeof effectiveClass.requiredConstructorId === "string" && args.constructorRoot === void 0 && args.authoredRoot === void 0) {
53897
+ if (member.payload !== 1 /* Partial */ && typeof effectiveClass.requiredConstructorId === "string" && args.constructorRoot === void 0 && args.authoredRoot === void 0 && args.declarationRoot !== true) {
53898
53898
  throw new Error(
53899
53899
  `Class "${effectiveClass.name}" declares a required constructor; provide its constructor arguments.`
53900
53900
  );
@@ -53967,6 +53967,9 @@ function buildDefaultMemberValue(args) {
53967
53967
  anchorsParentClassPartition = true;
53968
53968
  }
53969
53969
  if (record4[schemaKey] !== void 0) continue;
53970
+ if (args.declarationRoot === true && !isLiteralValueContent(resolvedChildMember.defaultValue)) {
53971
+ continue;
53972
+ }
53970
53973
  if (args.authoredRoot?.suppliedSchemaKeys.has(schemaKey) === true) {
53971
53974
  continue;
53972
53975
  }
@@ -81273,10 +81276,10 @@ function materializeMemberDefaultValue(args) {
81273
81276
  args.readTracking?.recordUnattributableValueRead(
81274
81277
  `declaration-owner-resolution:${args.envelope.id}`
81275
81278
  );
81276
- const { valuesById, rootOwners } = declarationInitializerContext(
81277
- args.document
81278
- );
81279
81279
  const declarationArguments = (member, init, sourceValueId) => {
81280
+ const { valuesById, rootOwners } = declarationInitializerContext(
81281
+ args.document
81282
+ );
81280
81283
  const ownerClass = typeof sourceValueId === "string" ? initializerOwnerContext(
81281
81284
  args.document,
81282
81285
  sourceValueId,
@@ -81308,6 +81311,7 @@ function materializeMemberDefaultValue(args) {
81308
81311
  member: args.member,
81309
81312
  createdValues,
81310
81313
  storageKeyDeclarations,
81314
+ declarationRoot: args.declarationRoot,
81311
81315
  ...args.genericEnv === void 0 ? {} : { genericEnv: args.genericEnv },
81312
81316
  // A declaration default may itself be init-backed one level down (P43 §2),
81313
81317
  // and those nested initializers must evaluate against the same document.
@@ -81750,13 +81754,12 @@ function resolveVirtualInstanceGraph(args) {
81750
81754
  const effective = {
81751
81755
  ...mergeExpandedAndMaterializedRow(indexed.expandedRow, materialized),
81752
81756
  id: effectiveId,
81753
- // Virtual rows inherit partition and timestamps from the root. Real rows
81754
- // retain their own envelope through the spread above.
81757
+ // Materialization has already resolved each child's storage partition.
81758
+ // Virtual rows inherit root timestamps; real rows retain their envelope.
81755
81759
  ...materialized === null ? {
81756
81760
  projectId: args.instanceRoot.projectId,
81757
81761
  createdAt: args.instanceRoot.createdAt,
81758
- updatedAt: args.instanceRoot.updatedAt,
81759
- ...args.instanceRoot.mapKey === void 0 ? {} : { mapKey: args.instanceRoot.mapKey }
81762
+ updatedAt: args.instanceRoot.updatedAt
81760
81763
  } : {}
81761
81764
  };
81762
81765
  if (isMemberClassBase(indexed.member) && effective.value !== null) {
@@ -82036,13 +82039,26 @@ function resolvedStoredInstanceRows(args) {
82036
82039
  const rows = new Map(document.values.map((row) => [row.id, row]));
82037
82040
  const materializedIndex = buildVirtualInstanceMaterializedIndex({ document });
82038
82041
  const queue = [{ root: args.instanceRoot, member: args.rootMember }];
82042
+ const stored = indexExpansion({
82043
+ document,
82044
+ instanceRoot: args.instanceRoot,
82045
+ rootMember: args.rootMember,
82046
+ expandedRoot: rows.get(args.instanceRoot.id) ?? args.instanceRoot,
82047
+ expandedRows: document.values
82048
+ });
82049
+ for (const node of stored.nodesByPath.values()) {
82050
+ if (node.expandedRow.id === args.instanceRoot.id) continue;
82051
+ if (!isMemberClassBase(node.member)) continue;
82052
+ if (!isStoredClassResolutionRoot(node.expandedRow)) continue;
82053
+ queue.push({ root: node.expandedRow, member: node.member });
82054
+ }
82039
82055
  const resolvedRootIds = /* @__PURE__ */ new Set();
82040
82056
  const resolvedFrameByValueId = /* @__PURE__ */ new Map();
82041
82057
  while (queue.length > 0) {
82042
82058
  const next = queue.shift();
82043
82059
  if (next === void 0 || resolvedRootIds.has(next.root.id)) continue;
82044
82060
  resolvedRootIds.add(next.root.id);
82045
- const expanded = expandStoredInstance({
82061
+ const expanded = expandStoredClassValue({
82046
82062
  evaluateInitializer: args.evaluateInitializer,
82047
82063
  document,
82048
82064
  instanceRoot: next.root,
@@ -82089,6 +82105,63 @@ function resolvedStoredInstanceRows(args) {
82089
82105
  }
82090
82106
  return rows;
82091
82107
  }
82108
+ function expandStoredClassValue(args) {
82109
+ if (isLiteralValueContent(args.instanceRoot) && isVirtualInstanceRootShape(args.instanceRoot)) {
82110
+ return expandStoredInstance(args);
82111
+ }
82112
+ const root = literalRow(args.instanceRoot);
82113
+ if (root.constructorArgs !== void 0) return expandStoredInstance(args);
82114
+ const member = resolveMember2(args.rootMember, args.document.members);
82115
+ if (!isMemberClassBase(member)) {
82116
+ throw new Error(
82117
+ `Stored default projection member "${memberIdOf(args.rootMember)}" is not Class-valued.`
82118
+ );
82119
+ }
82120
+ if (member.payload === 1 /* Partial */) {
82121
+ return { root, rows: [], pinnedRootSchemaKeys: /* @__PURE__ */ new Set() };
82122
+ }
82123
+ if (root.value === null) {
82124
+ return { root, rows: [], pinnedRootSchemaKeys: /* @__PURE__ */ new Set() };
82125
+ }
82126
+ const classId = root.classId ?? member.classId;
82127
+ args.readRecorder?.recordValueRead(root.id);
82128
+ args.readRecorder?.recordGlobalRead("declaration-default");
82129
+ const materialized = materializeMemberDefaultValue({
82130
+ evaluateInitializer: args.evaluateInitializer,
82131
+ document: args.document,
82132
+ member: closeClassArgumentsFromRowStamp(
82133
+ {
82134
+ ...member,
82135
+ classId,
82136
+ defaultValue: derivesContentFromMemberDefault({
82137
+ member,
82138
+ instanceRoot: root,
82139
+ effectiveClassId: classId
82140
+ }) ? member.defaultValue : void 0
82141
+ },
82142
+ root
82143
+ ),
82144
+ envelope: root,
82145
+ declarationRoot: true
82146
+ });
82147
+ return {
82148
+ root: { ...root, classId, value: materialized.root.value },
82149
+ rows: materialized.createdValues,
82150
+ pinnedRootSchemaKeys: /* @__PURE__ */ new Set()
82151
+ };
82152
+ }
82153
+ function isStoredClassResolutionRoot(value) {
82154
+ if (!isLiteralValueContent(value)) return false;
82155
+ if (isVirtualInstanceRootShape(value)) return true;
82156
+ return typeof value.classId === "string" && value.value !== null && typeof value.value === "object" && !Array.isArray(value.value);
82157
+ }
82158
+ function storedClassResolutionMember(member, row, members) {
82159
+ const resolved = resolveMember2(member, members);
82160
+ if (isMemberClassBase(resolved)) return resolved;
82161
+ if (resolved.kind !== 21 /* Generic */ || typeof row.classId !== "string")
82162
+ return null;
82163
+ return { ...resolved, kind: 7 /* Class */, classId: row.classId };
82164
+ }
82092
82165
  function planVirtualOverrideWrite(args) {
82093
82166
  const target = args.graph.locationsById.get(args.valueId);
82094
82167
  if (target === void 0) {
@@ -83569,8 +83642,12 @@ function createHeadlessVirtualInstanceResolver(args) {
83569
83642
  failedRootIds.add(rootId);
83570
83643
  return /* @__PURE__ */ new Map();
83571
83644
  }
83572
- const member = resolveMember2(declaredMember, document.members);
83573
- if (!isMemberClassBase(member) || !isLiteralValueContent(instanceRoot)) {
83645
+ const member = storedClassResolutionMember(
83646
+ declaredMember,
83647
+ instanceRoot,
83648
+ document.members
83649
+ );
83650
+ if (member === null || !isLiteralValueContent(instanceRoot)) {
83574
83651
  failedRootIds.add(rootId);
83575
83652
  return /* @__PURE__ */ new Map();
83576
83653
  }
@@ -83580,7 +83657,7 @@ function createHeadlessVirtualInstanceResolver(args) {
83580
83657
  document: resolverDocument,
83581
83658
  materializedRows: document.values
83582
83659
  });
83583
- const expanded = expandStoredInstance({
83660
+ const expanded = expandStoredClassValue({
83584
83661
  evaluateInitializer: args.evaluateInitializer,
83585
83662
  document: resolverDocument,
83586
83663
  instanceRoot,
@@ -83622,7 +83699,7 @@ function createHeadlessVirtualInstanceResolver(args) {
83622
83699
  while (placement !== null) {
83623
83700
  args.readRecorder?.recordValueRead(placement.valueId);
83624
83701
  const raw = rawById.get(placement.valueId);
83625
- if (raw !== void 0 && isLiteralValueContent(raw) && isVirtualInstanceRootShape(raw)) {
83702
+ if (raw !== void 0 && isLiteralValueContent(raw) && isStoredClassResolutionRoot(raw)) {
83626
83703
  return [...expandRoot(placement.valueId, placement.memberId).values()];
83627
83704
  }
83628
83705
  placement = placement.parentValueId === null ? null : placements().get(placement.parentValueId) ?? null;
@@ -84032,6 +84109,12 @@ var init_materialize_values = __esm({
84032
84109
  });
84033
84110
 
84034
84111
  // ../src/runtime/virtual-instance-values.ts
84112
+ function expandStoredClassValue2(args) {
84113
+ return expandStoredClassValue({
84114
+ ...args,
84115
+ evaluateInitializer: evaluateMemberInitializer
84116
+ });
84117
+ }
84035
84118
  function expandStoredInstance2(args) {
84036
84119
  return expandStoredInstance({
84037
84120
  ...args,
@@ -101776,6 +101859,7 @@ var init_project_version_static_value_writes = __esm({
101776
101859
  dialogueIds = /* @__PURE__ */ new Set();
101777
101860
  schemaIndex;
101778
101861
  virtualValueIds;
101862
+ declarationMaterializedIndex = null;
101779
101863
  stack = /* @__PURE__ */ new Set();
101780
101864
  createdCollectionStampByValueId = /* @__PURE__ */ new Map();
101781
101865
  /** Built once and reused by every nested construction-root proof. */
@@ -101902,11 +101986,51 @@ var init_project_version_static_value_writes = __esm({
101902
101986
  args.genericEnv,
101903
101987
  args.path
101904
101988
  );
101905
- this.validateSemanticNode({ ...args, member, value });
101989
+ const resolved = this.resolveDeclaredDefaults(
101990
+ member,
101991
+ value,
101992
+ args.genericEnv
101993
+ );
101994
+ this.validateSemanticNode({ ...args, member, value: resolved });
101906
101995
  } finally {
101907
101996
  this.stack.delete(value.id);
101908
101997
  }
101909
101998
  }
101999
+ resolveDeclaredDefaults(member, value, genericEnv) {
102000
+ const closed = substituteMember(member, genericEnv, this.document.members);
102001
+ if (!isMemberClassBase(closed)) return value;
102002
+ if (!isLiteralValueContent(value)) return value;
102003
+ if (value.value === null || typeof value.value !== "object" || Array.isArray(value.value))
102004
+ return value;
102005
+ if (value.instanceConstructorId !== void 0 || value.instanceVariantId != null)
102006
+ return value;
102007
+ if (this.virtualValueIds.has(value.id)) return value;
102008
+ const expanded = expandStoredClassValue2({
102009
+ document: this.merged().document,
102010
+ instanceRoot: value,
102011
+ rootMember: closed
102012
+ });
102013
+ const graph = resolveVirtualInstanceGraph({
102014
+ document: this.merged().document,
102015
+ instanceRoot: value,
102016
+ rootMember: closed,
102017
+ expandedRoot: expanded.root,
102018
+ expandedRows: expanded.rows,
102019
+ materializedIndex: this.declarationMaterializedIndex ??= buildVirtualInstanceMaterializedIndex({
102020
+ document: this.merged().document
102021
+ })
102022
+ });
102023
+ for (const [id2, row] of graph.rowsById) {
102024
+ if (this.valueById.has(id2)) continue;
102025
+ this.valueById.set(id2, row);
102026
+ }
102027
+ const resolved = graph.rowsById.get(value.id);
102028
+ if (resolved === void 0 || !isLiteralValueContent(resolved))
102029
+ return value;
102030
+ if (resolved.value === null || typeof resolved.value !== "object" || Array.isArray(resolved.value))
102031
+ return value;
102032
+ return { ...resolved, value: { ...resolved.value, ...value.value } };
102033
+ }
101910
102034
  /**
101911
102035
  * A static graph can contain nested construction roots under an ordinary
101912
102036
  * list or Class root. Replay each reachable root that carries complete
@@ -130834,20 +130958,22 @@ function createPendingProjectSourceIdentityV4(workspace, status, authoredValueSe
130834
130958
  });
130835
130959
  }
130836
130960
  if (seed.valueId === void 0) continue;
130837
- records2.set(`value:${seed.valueId}`, {
130838
- recordKind: "value",
130839
- recordId: seed.valueId,
130840
- contentHash: "",
130841
- deleted: false,
130842
- data: {
130843
- id: seed.valueId,
130844
- projectId: workspace.config.projectId,
130845
- ...encodeStoredValueNode(seed),
130846
- createdAt: now,
130847
- updatedAt: now
130848
- }
130849
- });
130961
+ if (!records2.has(`value:${seed.valueId}`))
130962
+ records2.set(`value:${seed.valueId}`, {
130963
+ recordKind: "value",
130964
+ recordId: seed.valueId,
130965
+ contentHash: "",
130966
+ deleted: false,
130967
+ data: {
130968
+ id: seed.valueId,
130969
+ projectId: workspace.config.projectId,
130970
+ ...encodeStoredValueNode(seed),
130971
+ createdAt: now,
130972
+ updatedAt: now
130973
+ }
130974
+ });
130850
130975
  for (const value of seed.values ?? []) {
130976
+ if (records2.has(`value:${value.id}`)) continue;
130851
130977
  records2.set(`value:${value.id}`, {
130852
130978
  recordKind: "value",
130853
130979
  recordId: value.id,
@@ -132352,7 +132478,7 @@ var init_registry2 = __esm({
132352
132478
  "schema-contract/registry.mjs"() {
132353
132479
  "use strict";
132354
132480
  PROJECT_SCHEMA_CONTRACT = Object.freeze({
132355
- cliVersion: "0.50.1",
132481
+ cliVersion: "0.50.3",
132356
132482
  projectFileUploadBatchSize: 32,
132357
132483
  documentRecords: {
132358
132484
  member: {
@@ -138828,13 +138954,177 @@ var init_dialogue_dryrun = __esm({
138828
138954
  }
138829
138955
  });
138830
138956
 
138957
+ // src/unity-generated-files.ts
138958
+ import { createHash as createHash12 } from "node:crypto";
138959
+ import {
138960
+ existsSync as existsSync16,
138961
+ mkdirSync as mkdirSync13,
138962
+ readFileSync as readFileSync21,
138963
+ unlinkSync,
138964
+ writeFileSync as writeFileSync13
138965
+ } from "node:fs";
138966
+ import { dirname as dirname11, join as join18 } from "node:path";
138967
+ function validatePath(path) {
138968
+ if (!/^Generated\/[a-zA-Z0-9_%./-]+\.g\.cs$/.test(path))
138969
+ throw new Error(`Invalid generated file path: ${path}`);
138970
+ if (path.split("/").some((part) => part === "." || part === ".." || part === ""))
138971
+ throw new Error(`Invalid generated file path segment: ${path}`);
138972
+ }
138973
+ function readManifest(path) {
138974
+ if (!existsSync16(path)) return null;
138975
+ const data = JSON.parse(readFileSync21(path, "utf8"));
138976
+ if (data === null || typeof data !== "object" || !("schemaVersion" in data) || data.schemaVersion !== 1)
138977
+ throw new Error(`Invalid generated-file manifest version: ${path}`);
138978
+ if (!("files" in data) || !Array.isArray(data.files) || data.files.length === 0)
138979
+ throw new Error(`Invalid generated-file manifest entries: ${path}`);
138980
+ if (!("projectId" in data) || typeof data.projectId !== "string")
138981
+ throw new Error(`Invalid generated-file manifest project: ${path}`);
138982
+ const ids = /* @__PURE__ */ new Set();
138983
+ const paths = /* @__PURE__ */ new Set();
138984
+ const files = data.files.map((entry) => {
138985
+ if (entry === null || typeof entry !== "object" || !("path" in entry) || typeof entry.path !== "string")
138986
+ throw new Error(`Invalid generated-file manifest entry: ${path}`);
138987
+ if (!("id" in entry) || typeof entry.id !== "string" || entry.id.length === 0)
138988
+ throw new Error(`Invalid generated-file manifest identity: ${path}`);
138989
+ if (ids.has(entry.id))
138990
+ throw new Error(
138991
+ `Duplicate generated-file manifest identity: ${entry.id}`
138992
+ );
138993
+ ids.add(entry.id);
138994
+ validatePath(entry.path);
138995
+ if (paths.has(entry.path.toLowerCase()))
138996
+ throw new Error(`Duplicate generated-file manifest path: ${entry.path}`);
138997
+ paths.add(entry.path.toLowerCase());
138998
+ return { id: entry.id, path: entry.path };
138999
+ });
139000
+ return { projectId: data.projectId, files };
139001
+ }
139002
+ function writeUnityGeneratedFiles(directory, projectId, files) {
139003
+ if (!Array.isArray(files) || files.length === 0)
139004
+ throw new Error(
139005
+ "The export returned no generated files. Update the server before exporting Unity code."
139006
+ );
139007
+ const expected = /* @__PURE__ */ new Set();
139008
+ const ids = /* @__PURE__ */ new Set();
139009
+ for (const file of files) {
139010
+ validatePath(file.path);
139011
+ if (typeof file.id !== "string" || file.id.length === 0)
139012
+ throw new Error(`Missing generated file identity: ${file.path}`);
139013
+ if (ids.has(file.id))
139014
+ throw new Error(`Duplicate generated file identity: ${file.id}`);
139015
+ ids.add(file.id);
139016
+ const key = file.path.toLowerCase();
139017
+ if (expected.has(key))
139018
+ throw new Error(`Duplicate generated file path: ${file.path}`);
139019
+ expected.add(key);
139020
+ if (typeof file.content !== "string" || file.content.trim().length === 0)
139021
+ throw new Error(`Empty generated C# file: ${file.path}`);
139022
+ }
139023
+ const manifestPath = join18(directory, "NeoGeneratedFiles.json");
139024
+ const manifest = readManifest(manifestPath);
139025
+ const previousFiles = manifest?.files ?? [];
139026
+ const previousById = new Map(previousFiles.map((file) => [file.id, file]));
139027
+ const previousPaths = new Set(
139028
+ previousFiles.map((file) => file.path.toLowerCase())
139029
+ );
139030
+ const incomingById = new Map(files.map((file) => [file.id, file]));
139031
+ const operations = [];
139032
+ for (const file of previousFiles) {
139033
+ if (incomingById.get(file.id)?.path === file.path && manifest?.projectId === projectId)
139034
+ continue;
139035
+ operations.push(
139036
+ [join18(directory, file.path), null],
139037
+ [join18(directory, file.path + ".meta"), null]
139038
+ );
139039
+ }
139040
+ operations.push(
139041
+ [join18(directory, "NeoGeneratedTypes.cs"), null],
139042
+ [join18(directory, "NeoGeneratedTypes.cs.meta"), null]
139043
+ );
139044
+ for (const file of files) {
139045
+ const old = manifest?.projectId === projectId ? previousById.get(file.id) : void 0;
139046
+ const path = join18(directory, file.path);
139047
+ if (old !== void 0 && old.path !== file.path) {
139048
+ if (!previousPaths.has(file.path.toLowerCase()) && (existsSync16(path) || existsSync16(path + ".meta")))
139049
+ throw new Error(
139050
+ `Generated rename destination is not owned by the manifest: ${file.path}`
139051
+ );
139052
+ const meta = join18(directory, old.path + ".meta");
139053
+ operations.push([
139054
+ path + ".meta",
139055
+ existsSync16(meta) ? readFileSync21(meta, "utf8") : null
139056
+ ]);
139057
+ }
139058
+ operations.push([path, file.content]);
139059
+ }
139060
+ operations.push([
139061
+ manifestPath,
139062
+ JSON.stringify(
139063
+ {
139064
+ schemaVersion: 1,
139065
+ projectId,
139066
+ files: [...files].sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0).map((file) => ({
139067
+ id: file.id,
139068
+ path: file.path,
139069
+ hash: createHash12("sha256").update(file.content).digest("hex")
139070
+ }))
139071
+ },
139072
+ null,
139073
+ 2
139074
+ ) + "\n"
139075
+ ]);
139076
+ const previous = /* @__PURE__ */ new Map();
139077
+ for (const [path] of operations) {
139078
+ const key = path.toLowerCase();
139079
+ if (!previous.has(key))
139080
+ previous.set(key, {
139081
+ path,
139082
+ content: existsSync16(path) ? readFileSync21(path, "utf8") : null
139083
+ });
139084
+ }
139085
+ const write = (path, content) => {
139086
+ if (content === null) {
139087
+ if (existsSync16(path)) unlinkSync(path);
139088
+ } else {
139089
+ mkdirSync13(dirname11(path), { recursive: true });
139090
+ writeFileSync13(path, content);
139091
+ }
139092
+ };
139093
+ const changed = [];
139094
+ try {
139095
+ for (const [path, content] of operations) {
139096
+ const current = existsSync16(path) ? readFileSync21(path, "utf8") : null;
139097
+ if (current === content) continue;
139098
+ changed.push(path);
139099
+ write(path, content);
139100
+ }
139101
+ } catch (error) {
139102
+ const restored = /* @__PURE__ */ new Set();
139103
+ for (const path of changed.reverse()) {
139104
+ const key = path.toLowerCase();
139105
+ if (restored.has(key)) continue;
139106
+ restored.add(key);
139107
+ const original = previous.get(key);
139108
+ if (original.path !== path && existsSync16(path)) unlinkSync(path);
139109
+ const current = existsSync16(original.path) ? readFileSync21(original.path, "utf8") : null;
139110
+ if (current !== original.content) write(original.path, original.content);
139111
+ }
139112
+ throw error;
139113
+ }
139114
+ }
139115
+ var init_unity_generated_files = __esm({
139116
+ "src/unity-generated-files.ts"() {
139117
+ "use strict";
139118
+ }
139119
+ });
139120
+
138831
139121
  // src/commands/export.ts
138832
139122
  var export_exports = {};
138833
139123
  __export(export_exports, {
138834
139124
  runExportUnity: () => runExportUnity
138835
139125
  });
138836
- import { mkdirSync as mkdirSync13, writeFileSync as writeFileSync13 } from "node:fs";
138837
- import { join as join18 } from "node:path";
139126
+ import { mkdirSync as mkdirSync14, writeFileSync as writeFileSync14 } from "node:fs";
139127
+ import { join as join19 } from "node:path";
138838
139128
  async function runExportUnity(workspace, outDir) {
138839
139129
  if (outDir === null) {
138840
139130
  throw new Error(
@@ -138846,23 +139136,24 @@ async function runExportUnity(workspace, outDir) {
138846
139136
  `/api/projects/${workspace.config.projectId}/export`,
138847
139137
  { versionId: workspace.config.versionId }
138848
139138
  );
138849
- const resourcesDir = join18(outDir, "Resources", "Neo");
138850
- const localizationDir = join18(resourcesDir, "Localization");
138851
- const scriptsDir = join18(outDir, "Scripts", "Neo");
138852
- mkdirSync13(localizationDir, { recursive: true });
138853
- mkdirSync13(scriptsDir, { recursive: true });
138854
- writeFileSync13(join18(resourcesDir, "project.json"), response.projectJson);
138855
- writeFileSync13(
138856
- join18(scriptsDir, "NeoGeneratedTypes.cs"),
138857
- response.generatedTypes
138858
- );
139139
+ const resourcesDir = join19(outDir, "Resources", "Neo");
139140
+ const localizationDir = join19(resourcesDir, "Localization");
139141
+ const scriptsDir = join19(outDir, "Scripts", "Neo");
139142
+ mkdirSync14(localizationDir, { recursive: true });
139143
+ mkdirSync14(scriptsDir, { recursive: true });
139144
+ writeUnityGeneratedFiles(
139145
+ scriptsDir,
139146
+ response.projectId,
139147
+ response.generatedFiles
139148
+ );
139149
+ writeFileSync14(join19(resourcesDir, "project.json"), response.projectJson);
138859
139150
  for (const file of response.localizationFiles ?? []) {
138860
- writeFileSync13(join18(localizationDir, file.fileName), file.content);
139151
+ writeFileSync14(join19(localizationDir, file.fileName), file.content);
138861
139152
  }
138862
- console.log(`wrote ${join18(resourcesDir, "project.json")}`);
138863
- console.log(`wrote ${join18(scriptsDir, "NeoGeneratedTypes.cs")}`);
139153
+ console.log(`wrote ${join19(resourcesDir, "project.json")}`);
139154
+ console.log(`synchronized generated files in ${scriptsDir}`);
138864
139155
  for (const file of response.localizationFiles ?? []) {
138865
- console.log(`wrote ${join18(localizationDir, file.fileName)}`);
139156
+ console.log(`wrote ${join19(localizationDir, file.fileName)}`);
138866
139157
  }
138867
139158
  const diagnostics = response.diagnostics ?? [];
138868
139159
  for (const diagnostic of diagnostics) {
@@ -138875,6 +139166,7 @@ async function runExportUnity(workspace, outDir) {
138875
139166
  var init_export = __esm({
138876
139167
  "src/commands/export.ts"() {
138877
139168
  "use strict";
139169
+ init_unity_generated_files();
138878
139170
  init_http();
138879
139171
  }
138880
139172
  });
@@ -138885,7 +139177,7 @@ __export(dev_exports, {
138885
139177
  runDev: () => runDev
138886
139178
  });
138887
139179
  import { watch } from "node:fs";
138888
- import { join as join19 } from "node:path";
139180
+ import { join as join20 } from "node:path";
138889
139181
  import { emitKeypressEvents } from "node:readline";
138890
139182
  import { ConvexClient } from "convex/browser";
138891
139183
  function isSchemaSignal(value) {
@@ -138995,7 +139287,7 @@ async function runDev(workspace, options) {
138995
139287
  };
138996
139288
  for (const dir of ["Classes", "Enums"]) {
138997
139289
  try {
138998
- watch(join19(workspace.root, dir), { persistent: true }, onFileChange);
139290
+ watch(join20(workspace.root, dir), { persistent: true }, onFileChange);
138999
139291
  } catch {
139000
139292
  }
139001
139293
  }
@@ -139049,8 +139341,8 @@ __export(resolve_exports, {
139049
139341
  resolveMarkers: () => resolveMarkers,
139050
139342
  runResolve: () => runResolve
139051
139343
  });
139052
- import { readFileSync as readFileSync21, rmSync as rmSync8, writeFileSync as writeFileSync14 } from "node:fs";
139053
- import { join as join20, relative as relative11 } from "node:path";
139344
+ import { readFileSync as readFileSync22, rmSync as rmSync8, writeFileSync as writeFileSync15 } from "node:fs";
139345
+ import { join as join21, relative as relative11 } from "node:path";
139054
139346
  function runResolve(workspace, side) {
139055
139347
  const marked = readMarkedProjectSourcesV4(workspace.root);
139056
139348
  assertRecordConflictsResolveTogetherV4(
@@ -139060,7 +139352,7 @@ function runResolve(workspace, side) {
139060
139352
  const resolvedRecords = adoptServerConflictBases(workspace);
139061
139353
  let resolvedFiles = 0;
139062
139354
  for (const file of marked) {
139063
- writeFileSync14(file.absolutePath, resolveMarkers(file.source, side), "utf8");
139355
+ writeFileSync15(file.absolutePath, resolveMarkers(file.source, side), "utf8");
139064
139356
  resolvedFiles += 1;
139065
139357
  }
139066
139358
  let resolvedBinaries = 0;
@@ -139068,12 +139360,12 @@ function runResolve(workspace, side) {
139068
139360
  const binary = state.projectBinary;
139069
139361
  const conflict2 = binary?.conflict;
139070
139362
  if (binary === void 0 || conflict2 === void 0) continue;
139071
- const destination = join20(workspace.root, binary.path);
139363
+ const destination = join21(workspace.root, binary.path);
139072
139364
  if (side === "theirs") {
139073
139365
  if (conflict2.remoteSha256 !== null && conflict2.artifactPath !== void 0) {
139074
139366
  writeVerifiedBinaryDownloadV4(
139075
139367
  destination,
139076
- readFileSync21(join20(workspace.root, conflict2.artifactPath)),
139368
+ readFileSync22(join21(workspace.root, conflict2.artifactPath)),
139077
139369
  conflict2.remoteSha256
139078
139370
  );
139079
139371
  binary.sha256 = conflict2.remoteSha256;
@@ -139083,7 +139375,7 @@ function runResolve(workspace, side) {
139083
139375
  }
139084
139376
  }
139085
139377
  if (conflict2.artifactPath !== void 0) {
139086
- rmSync8(join20(workspace.root, conflict2.artifactPath), { force: true });
139378
+ rmSync8(join21(workspace.root, conflict2.artifactPath), { force: true });
139087
139379
  }
139088
139380
  delete binary.conflict;
139089
139381
  resolvedBinaries += 1;
@@ -139102,7 +139394,7 @@ function runResolve(workspace, side) {
139102
139394
  function readMarkedProjectSourcesV4(root) {
139103
139395
  const files = [];
139104
139396
  for (const absolutePath of listProjectSourceFilesV4(root)) {
139105
- const source = readFileSync21(absolutePath, "utf8");
139397
+ const source = readFileSync22(absolutePath, "utf8");
139106
139398
  if (detectConflictMarkers(source) === null) continue;
139107
139399
  files.push({
139108
139400
  path: normalizeWorkspaceSourcePath(relative11(root, absolutePath)),
@@ -139402,7 +139694,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
139402
139694
  async function main() {
139403
139695
  const args = parseArgs(process.argv.slice(2));
139404
139696
  if (args.command === "--version") {
139405
- console.log("0.50.1");
139697
+ console.log("0.50.3");
139406
139698
  return;
139407
139699
  }
139408
139700
  if (args.command === null || args.command === "help" || args.command === "--help" || args.command === "-h") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neocompose/cli",
3
- "version": "0.50.1",
3
+ "version": "0.50.3",
4
4
  "description": "Neo Compose native project-source CLI with bidirectional sync.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -9,7 +9,7 @@ description: >-
9
9
  `@neocompose/cli` or `node cli/bin/neo.mjs` in the neo-compose repository.
10
10
  ---
11
11
 
12
- <!-- reviewed-through-cli: 0.50.1 -->
12
+ <!-- reviewed-through-cli: 0.50.3 -->
13
13
 
14
14
  # Neo Compose CLI
15
15
 
@@ -88,7 +88,7 @@ wrappers.
88
88
  The marker near the top of `SKILL.md` must exactly match the package version:
89
89
 
90
90
  ```html
91
- <!-- reviewed-through-cli: 0.50.1 -->
91
+ <!-- reviewed-through-cli: 0.50.3 -->
92
92
  ```
93
93
 
94
94
  The quoted version above is checked too, so this instruction cannot go stale
@@ -246,3 +246,11 @@ honor both variables.
246
246
 
247
247
  Pass explicit project/version IDs and flags in automation. Prefer `--json` for
248
248
  machine-readable output and do not depend on interactive pickers or confirms.
249
+
250
+ ## Unity code export
251
+
252
+ `neo export unity --out <UnityAssetsDir>` writes separate C# files under
253
+ `Scripts/Neo/Generated/`, named after the generated C# types, plus project and localization JSON. It compares
254
+ contents before writing C#, preserves unchanged `.meta` files, removes obsolete
255
+ outputs listed in `NeoGeneratedFiles.json`, and removes the former
256
+ `NeoGeneratedTypes.cs`. Stable identities in the manifest preserve `.meta` GUIDs when types are renamed. Keep the manifest with the generated files.
@@ -136,6 +136,13 @@ member creates a sparse override with its deterministic projected ID; removing
136
136
  that member from an existing initializer resets the override and resumes source
137
137
  tracking. Require either transition to appear in `neo diff`.
138
138
 
139
+ A stored class with no construction record still inherits literal declaration
140
+ defaults for omitted fields. For example, `FoodInfo.Quality = .OneStar` supplies
141
+ the quality of an existing food row that stores only its price. Do not add an
142
+ instance override to repair that omission. Reads do not invent a historical
143
+ constructor, and required fields with no applicable default remain errors.
144
+ Explicit stored overrides and partial payloads keep their existing meaning.
145
+
139
146
  Generic arguments specify types, not default values. A required generic member
140
147
  must be supplied at construction unless its member declaration or an inherited
141
148
  member declaration supplies a default. Missing required members are compile