@neocompose/cli 0.50.1 → 0.50.2
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,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.50.2] - 2026-09-12
|
|
4
|
+
|
|
5
|
+
- 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.
|
|
6
|
+
|
|
3
7
|
## [0.50.1] - 2026-09-12
|
|
4
8
|
|
|
5
9
|
- 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 +
|
|
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",
|
|
@@ -132352,7 +132352,7 @@ var init_registry2 = __esm({
|
|
|
132352
132352
|
"schema-contract/registry.mjs"() {
|
|
132353
132353
|
"use strict";
|
|
132354
132354
|
PROJECT_SCHEMA_CONTRACT = Object.freeze({
|
|
132355
|
-
cliVersion: "0.50.
|
|
132355
|
+
cliVersion: "0.50.2",
|
|
132356
132356
|
projectFileUploadBatchSize: 32,
|
|
132357
132357
|
documentRecords: {
|
|
132358
132358
|
member: {
|
|
@@ -138828,13 +138828,177 @@ var init_dialogue_dryrun = __esm({
|
|
|
138828
138828
|
}
|
|
138829
138829
|
});
|
|
138830
138830
|
|
|
138831
|
+
// src/unity-generated-files.ts
|
|
138832
|
+
import { createHash as createHash12 } from "node:crypto";
|
|
138833
|
+
import {
|
|
138834
|
+
existsSync as existsSync16,
|
|
138835
|
+
mkdirSync as mkdirSync13,
|
|
138836
|
+
readFileSync as readFileSync21,
|
|
138837
|
+
unlinkSync,
|
|
138838
|
+
writeFileSync as writeFileSync13
|
|
138839
|
+
} from "node:fs";
|
|
138840
|
+
import { dirname as dirname11, join as join18 } from "node:path";
|
|
138841
|
+
function validatePath(path) {
|
|
138842
|
+
if (!/^Generated\/[a-zA-Z0-9_%./-]+\.g\.cs$/.test(path))
|
|
138843
|
+
throw new Error(`Invalid generated file path: ${path}`);
|
|
138844
|
+
if (path.split("/").some((part) => part === "." || part === ".." || part === ""))
|
|
138845
|
+
throw new Error(`Invalid generated file path segment: ${path}`);
|
|
138846
|
+
}
|
|
138847
|
+
function readManifest(path) {
|
|
138848
|
+
if (!existsSync16(path)) return null;
|
|
138849
|
+
const data = JSON.parse(readFileSync21(path, "utf8"));
|
|
138850
|
+
if (data === null || typeof data !== "object" || !("schemaVersion" in data) || data.schemaVersion !== 1)
|
|
138851
|
+
throw new Error(`Invalid generated-file manifest version: ${path}`);
|
|
138852
|
+
if (!("files" in data) || !Array.isArray(data.files) || data.files.length === 0)
|
|
138853
|
+
throw new Error(`Invalid generated-file manifest entries: ${path}`);
|
|
138854
|
+
if (!("projectId" in data) || typeof data.projectId !== "string")
|
|
138855
|
+
throw new Error(`Invalid generated-file manifest project: ${path}`);
|
|
138856
|
+
const ids = /* @__PURE__ */ new Set();
|
|
138857
|
+
const paths = /* @__PURE__ */ new Set();
|
|
138858
|
+
const files = data.files.map((entry) => {
|
|
138859
|
+
if (entry === null || typeof entry !== "object" || !("path" in entry) || typeof entry.path !== "string")
|
|
138860
|
+
throw new Error(`Invalid generated-file manifest entry: ${path}`);
|
|
138861
|
+
if (!("id" in entry) || typeof entry.id !== "string" || entry.id.length === 0)
|
|
138862
|
+
throw new Error(`Invalid generated-file manifest identity: ${path}`);
|
|
138863
|
+
if (ids.has(entry.id))
|
|
138864
|
+
throw new Error(
|
|
138865
|
+
`Duplicate generated-file manifest identity: ${entry.id}`
|
|
138866
|
+
);
|
|
138867
|
+
ids.add(entry.id);
|
|
138868
|
+
validatePath(entry.path);
|
|
138869
|
+
if (paths.has(entry.path.toLowerCase()))
|
|
138870
|
+
throw new Error(`Duplicate generated-file manifest path: ${entry.path}`);
|
|
138871
|
+
paths.add(entry.path.toLowerCase());
|
|
138872
|
+
return { id: entry.id, path: entry.path };
|
|
138873
|
+
});
|
|
138874
|
+
return { projectId: data.projectId, files };
|
|
138875
|
+
}
|
|
138876
|
+
function writeUnityGeneratedFiles(directory, projectId, files) {
|
|
138877
|
+
if (!Array.isArray(files) || files.length === 0)
|
|
138878
|
+
throw new Error(
|
|
138879
|
+
"The export returned no generated files. Update the server before exporting Unity code."
|
|
138880
|
+
);
|
|
138881
|
+
const expected = /* @__PURE__ */ new Set();
|
|
138882
|
+
const ids = /* @__PURE__ */ new Set();
|
|
138883
|
+
for (const file of files) {
|
|
138884
|
+
validatePath(file.path);
|
|
138885
|
+
if (typeof file.id !== "string" || file.id.length === 0)
|
|
138886
|
+
throw new Error(`Missing generated file identity: ${file.path}`);
|
|
138887
|
+
if (ids.has(file.id))
|
|
138888
|
+
throw new Error(`Duplicate generated file identity: ${file.id}`);
|
|
138889
|
+
ids.add(file.id);
|
|
138890
|
+
const key = file.path.toLowerCase();
|
|
138891
|
+
if (expected.has(key))
|
|
138892
|
+
throw new Error(`Duplicate generated file path: ${file.path}`);
|
|
138893
|
+
expected.add(key);
|
|
138894
|
+
if (typeof file.content !== "string" || file.content.trim().length === 0)
|
|
138895
|
+
throw new Error(`Empty generated C# file: ${file.path}`);
|
|
138896
|
+
}
|
|
138897
|
+
const manifestPath = join18(directory, "NeoGeneratedFiles.json");
|
|
138898
|
+
const manifest = readManifest(manifestPath);
|
|
138899
|
+
const previousFiles = manifest?.files ?? [];
|
|
138900
|
+
const previousById = new Map(previousFiles.map((file) => [file.id, file]));
|
|
138901
|
+
const previousPaths = new Set(
|
|
138902
|
+
previousFiles.map((file) => file.path.toLowerCase())
|
|
138903
|
+
);
|
|
138904
|
+
const incomingById = new Map(files.map((file) => [file.id, file]));
|
|
138905
|
+
const operations = [];
|
|
138906
|
+
for (const file of previousFiles) {
|
|
138907
|
+
if (incomingById.get(file.id)?.path === file.path && manifest?.projectId === projectId)
|
|
138908
|
+
continue;
|
|
138909
|
+
operations.push(
|
|
138910
|
+
[join18(directory, file.path), null],
|
|
138911
|
+
[join18(directory, file.path + ".meta"), null]
|
|
138912
|
+
);
|
|
138913
|
+
}
|
|
138914
|
+
operations.push(
|
|
138915
|
+
[join18(directory, "NeoGeneratedTypes.cs"), null],
|
|
138916
|
+
[join18(directory, "NeoGeneratedTypes.cs.meta"), null]
|
|
138917
|
+
);
|
|
138918
|
+
for (const file of files) {
|
|
138919
|
+
const old = manifest?.projectId === projectId ? previousById.get(file.id) : void 0;
|
|
138920
|
+
const path = join18(directory, file.path);
|
|
138921
|
+
if (old !== void 0 && old.path !== file.path) {
|
|
138922
|
+
if (!previousPaths.has(file.path.toLowerCase()) && (existsSync16(path) || existsSync16(path + ".meta")))
|
|
138923
|
+
throw new Error(
|
|
138924
|
+
`Generated rename destination is not owned by the manifest: ${file.path}`
|
|
138925
|
+
);
|
|
138926
|
+
const meta = join18(directory, old.path + ".meta");
|
|
138927
|
+
operations.push([
|
|
138928
|
+
path + ".meta",
|
|
138929
|
+
existsSync16(meta) ? readFileSync21(meta, "utf8") : null
|
|
138930
|
+
]);
|
|
138931
|
+
}
|
|
138932
|
+
operations.push([path, file.content]);
|
|
138933
|
+
}
|
|
138934
|
+
operations.push([
|
|
138935
|
+
manifestPath,
|
|
138936
|
+
JSON.stringify(
|
|
138937
|
+
{
|
|
138938
|
+
schemaVersion: 1,
|
|
138939
|
+
projectId,
|
|
138940
|
+
files: [...files].sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0).map((file) => ({
|
|
138941
|
+
id: file.id,
|
|
138942
|
+
path: file.path,
|
|
138943
|
+
hash: createHash12("sha256").update(file.content).digest("hex")
|
|
138944
|
+
}))
|
|
138945
|
+
},
|
|
138946
|
+
null,
|
|
138947
|
+
2
|
|
138948
|
+
) + "\n"
|
|
138949
|
+
]);
|
|
138950
|
+
const previous = /* @__PURE__ */ new Map();
|
|
138951
|
+
for (const [path] of operations) {
|
|
138952
|
+
const key = path.toLowerCase();
|
|
138953
|
+
if (!previous.has(key))
|
|
138954
|
+
previous.set(key, {
|
|
138955
|
+
path,
|
|
138956
|
+
content: existsSync16(path) ? readFileSync21(path, "utf8") : null
|
|
138957
|
+
});
|
|
138958
|
+
}
|
|
138959
|
+
const write = (path, content) => {
|
|
138960
|
+
if (content === null) {
|
|
138961
|
+
if (existsSync16(path)) unlinkSync(path);
|
|
138962
|
+
} else {
|
|
138963
|
+
mkdirSync13(dirname11(path), { recursive: true });
|
|
138964
|
+
writeFileSync13(path, content);
|
|
138965
|
+
}
|
|
138966
|
+
};
|
|
138967
|
+
const changed = [];
|
|
138968
|
+
try {
|
|
138969
|
+
for (const [path, content] of operations) {
|
|
138970
|
+
const current = existsSync16(path) ? readFileSync21(path, "utf8") : null;
|
|
138971
|
+
if (current === content) continue;
|
|
138972
|
+
changed.push(path);
|
|
138973
|
+
write(path, content);
|
|
138974
|
+
}
|
|
138975
|
+
} catch (error) {
|
|
138976
|
+
const restored = /* @__PURE__ */ new Set();
|
|
138977
|
+
for (const path of changed.reverse()) {
|
|
138978
|
+
const key = path.toLowerCase();
|
|
138979
|
+
if (restored.has(key)) continue;
|
|
138980
|
+
restored.add(key);
|
|
138981
|
+
const original = previous.get(key);
|
|
138982
|
+
if (original.path !== path && existsSync16(path)) unlinkSync(path);
|
|
138983
|
+
const current = existsSync16(original.path) ? readFileSync21(original.path, "utf8") : null;
|
|
138984
|
+
if (current !== original.content) write(original.path, original.content);
|
|
138985
|
+
}
|
|
138986
|
+
throw error;
|
|
138987
|
+
}
|
|
138988
|
+
}
|
|
138989
|
+
var init_unity_generated_files = __esm({
|
|
138990
|
+
"src/unity-generated-files.ts"() {
|
|
138991
|
+
"use strict";
|
|
138992
|
+
}
|
|
138993
|
+
});
|
|
138994
|
+
|
|
138831
138995
|
// src/commands/export.ts
|
|
138832
138996
|
var export_exports = {};
|
|
138833
138997
|
__export(export_exports, {
|
|
138834
138998
|
runExportUnity: () => runExportUnity
|
|
138835
138999
|
});
|
|
138836
|
-
import { mkdirSync as
|
|
138837
|
-
import { join as
|
|
139000
|
+
import { mkdirSync as mkdirSync14, writeFileSync as writeFileSync14 } from "node:fs";
|
|
139001
|
+
import { join as join19 } from "node:path";
|
|
138838
139002
|
async function runExportUnity(workspace, outDir) {
|
|
138839
139003
|
if (outDir === null) {
|
|
138840
139004
|
throw new Error(
|
|
@@ -138846,23 +139010,24 @@ async function runExportUnity(workspace, outDir) {
|
|
|
138846
139010
|
`/api/projects/${workspace.config.projectId}/export`,
|
|
138847
139011
|
{ versionId: workspace.config.versionId }
|
|
138848
139012
|
);
|
|
138849
|
-
const resourcesDir =
|
|
138850
|
-
const localizationDir =
|
|
138851
|
-
const scriptsDir =
|
|
138852
|
-
|
|
138853
|
-
|
|
138854
|
-
|
|
138855
|
-
|
|
138856
|
-
|
|
138857
|
-
response.
|
|
138858
|
-
);
|
|
139013
|
+
const resourcesDir = join19(outDir, "Resources", "Neo");
|
|
139014
|
+
const localizationDir = join19(resourcesDir, "Localization");
|
|
139015
|
+
const scriptsDir = join19(outDir, "Scripts", "Neo");
|
|
139016
|
+
mkdirSync14(localizationDir, { recursive: true });
|
|
139017
|
+
mkdirSync14(scriptsDir, { recursive: true });
|
|
139018
|
+
writeUnityGeneratedFiles(
|
|
139019
|
+
scriptsDir,
|
|
139020
|
+
response.projectId,
|
|
139021
|
+
response.generatedFiles
|
|
139022
|
+
);
|
|
139023
|
+
writeFileSync14(join19(resourcesDir, "project.json"), response.projectJson);
|
|
138859
139024
|
for (const file of response.localizationFiles ?? []) {
|
|
138860
|
-
|
|
139025
|
+
writeFileSync14(join19(localizationDir, file.fileName), file.content);
|
|
138861
139026
|
}
|
|
138862
|
-
console.log(`wrote ${
|
|
138863
|
-
console.log(`
|
|
139027
|
+
console.log(`wrote ${join19(resourcesDir, "project.json")}`);
|
|
139028
|
+
console.log(`synchronized generated files in ${scriptsDir}`);
|
|
138864
139029
|
for (const file of response.localizationFiles ?? []) {
|
|
138865
|
-
console.log(`wrote ${
|
|
139030
|
+
console.log(`wrote ${join19(localizationDir, file.fileName)}`);
|
|
138866
139031
|
}
|
|
138867
139032
|
const diagnostics = response.diagnostics ?? [];
|
|
138868
139033
|
for (const diagnostic of diagnostics) {
|
|
@@ -138875,6 +139040,7 @@ async function runExportUnity(workspace, outDir) {
|
|
|
138875
139040
|
var init_export = __esm({
|
|
138876
139041
|
"src/commands/export.ts"() {
|
|
138877
139042
|
"use strict";
|
|
139043
|
+
init_unity_generated_files();
|
|
138878
139044
|
init_http();
|
|
138879
139045
|
}
|
|
138880
139046
|
});
|
|
@@ -138885,7 +139051,7 @@ __export(dev_exports, {
|
|
|
138885
139051
|
runDev: () => runDev
|
|
138886
139052
|
});
|
|
138887
139053
|
import { watch } from "node:fs";
|
|
138888
|
-
import { join as
|
|
139054
|
+
import { join as join20 } from "node:path";
|
|
138889
139055
|
import { emitKeypressEvents } from "node:readline";
|
|
138890
139056
|
import { ConvexClient } from "convex/browser";
|
|
138891
139057
|
function isSchemaSignal(value) {
|
|
@@ -138995,7 +139161,7 @@ async function runDev(workspace, options) {
|
|
|
138995
139161
|
};
|
|
138996
139162
|
for (const dir of ["Classes", "Enums"]) {
|
|
138997
139163
|
try {
|
|
138998
|
-
watch(
|
|
139164
|
+
watch(join20(workspace.root, dir), { persistent: true }, onFileChange);
|
|
138999
139165
|
} catch {
|
|
139000
139166
|
}
|
|
139001
139167
|
}
|
|
@@ -139049,8 +139215,8 @@ __export(resolve_exports, {
|
|
|
139049
139215
|
resolveMarkers: () => resolveMarkers,
|
|
139050
139216
|
runResolve: () => runResolve
|
|
139051
139217
|
});
|
|
139052
|
-
import { readFileSync as
|
|
139053
|
-
import { join as
|
|
139218
|
+
import { readFileSync as readFileSync22, rmSync as rmSync8, writeFileSync as writeFileSync15 } from "node:fs";
|
|
139219
|
+
import { join as join21, relative as relative11 } from "node:path";
|
|
139054
139220
|
function runResolve(workspace, side) {
|
|
139055
139221
|
const marked = readMarkedProjectSourcesV4(workspace.root);
|
|
139056
139222
|
assertRecordConflictsResolveTogetherV4(
|
|
@@ -139060,7 +139226,7 @@ function runResolve(workspace, side) {
|
|
|
139060
139226
|
const resolvedRecords = adoptServerConflictBases(workspace);
|
|
139061
139227
|
let resolvedFiles = 0;
|
|
139062
139228
|
for (const file of marked) {
|
|
139063
|
-
|
|
139229
|
+
writeFileSync15(file.absolutePath, resolveMarkers(file.source, side), "utf8");
|
|
139064
139230
|
resolvedFiles += 1;
|
|
139065
139231
|
}
|
|
139066
139232
|
let resolvedBinaries = 0;
|
|
@@ -139068,12 +139234,12 @@ function runResolve(workspace, side) {
|
|
|
139068
139234
|
const binary = state.projectBinary;
|
|
139069
139235
|
const conflict2 = binary?.conflict;
|
|
139070
139236
|
if (binary === void 0 || conflict2 === void 0) continue;
|
|
139071
|
-
const destination =
|
|
139237
|
+
const destination = join21(workspace.root, binary.path);
|
|
139072
139238
|
if (side === "theirs") {
|
|
139073
139239
|
if (conflict2.remoteSha256 !== null && conflict2.artifactPath !== void 0) {
|
|
139074
139240
|
writeVerifiedBinaryDownloadV4(
|
|
139075
139241
|
destination,
|
|
139076
|
-
|
|
139242
|
+
readFileSync22(join21(workspace.root, conflict2.artifactPath)),
|
|
139077
139243
|
conflict2.remoteSha256
|
|
139078
139244
|
);
|
|
139079
139245
|
binary.sha256 = conflict2.remoteSha256;
|
|
@@ -139083,7 +139249,7 @@ function runResolve(workspace, side) {
|
|
|
139083
139249
|
}
|
|
139084
139250
|
}
|
|
139085
139251
|
if (conflict2.artifactPath !== void 0) {
|
|
139086
|
-
rmSync8(
|
|
139252
|
+
rmSync8(join21(workspace.root, conflict2.artifactPath), { force: true });
|
|
139087
139253
|
}
|
|
139088
139254
|
delete binary.conflict;
|
|
139089
139255
|
resolvedBinaries += 1;
|
|
@@ -139102,7 +139268,7 @@ function runResolve(workspace, side) {
|
|
|
139102
139268
|
function readMarkedProjectSourcesV4(root) {
|
|
139103
139269
|
const files = [];
|
|
139104
139270
|
for (const absolutePath of listProjectSourceFilesV4(root)) {
|
|
139105
|
-
const source =
|
|
139271
|
+
const source = readFileSync22(absolutePath, "utf8");
|
|
139106
139272
|
if (detectConflictMarkers(source) === null) continue;
|
|
139107
139273
|
files.push({
|
|
139108
139274
|
path: normalizeWorkspaceSourcePath(relative11(root, absolutePath)),
|
|
@@ -139402,7 +139568,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
|
|
|
139402
139568
|
async function main() {
|
|
139403
139569
|
const args = parseArgs(process.argv.slice(2));
|
|
139404
139570
|
if (args.command === "--version") {
|
|
139405
|
-
console.log("0.50.
|
|
139571
|
+
console.log("0.50.2");
|
|
139406
139572
|
return;
|
|
139407
139573
|
}
|
|
139408
139574
|
if (args.command === null || args.command === "help" || args.command === "--help" || args.command === "-h") {
|
package/package.json
CHANGED
|
@@ -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.
|
|
91
|
+
<!-- reviewed-through-cli: 0.50.2 -->
|
|
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.
|