@neocompose/cli 0.59.0 → 0.59.1

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/neo.mjs CHANGED
@@ -131154,104 +131154,381 @@ var init_concurrency = __esm({
131154
131154
  }
131155
131155
  });
131156
131156
 
131157
- // src/project-source/project-file-push.ts
131158
- import { basename as basename2, join as join9 } from "node:path";
131159
- import { readFileSync as readFileSync11 } from "node:fs";
131160
- import { randomUUID as randomUUID4 } from "node:crypto";
131161
- async function cleanupRejectedProjectFilePushesV4(args) {
131162
- if (args.stagedUploadSessionId === null) return;
131163
- try {
131164
- await args.client.post(
131165
- versionPath(args.workspace, "files/cleanup-staged-upload"),
131166
- { stagedUploadSessionId: args.stagedUploadSessionId }
131167
- );
131168
- } catch {
131169
- }
131170
- }
131171
- function ensureProjectFileBinaryChangesV4(args) {
131172
- for (const binary of args.binaryChanges) {
131173
- if (binary.action !== "upload") continue;
131174
- if (args.changes.some(
131175
- (change) => change.recordKind === "project-file" && change.recordId === binary.fileId
131176
- )) {
131177
- continue;
131157
+ // src/project-source/project-file-pull.ts
131158
+ import {
131159
+ existsSync as existsSync8,
131160
+ mkdtempSync,
131161
+ readFileSync as readFileSync11,
131162
+ rmSync as rmSync5,
131163
+ writeFileSync as writeFileSync8
131164
+ } from "node:fs";
131165
+ import { tmpdir } from "node:os";
131166
+ import { join as join9 } from "node:path";
131167
+ async function pullProjectBinariesV4(args) {
131168
+ const resolvePath = createWorkspaceSourcePathResolver(args.workspace.root);
131169
+ const plans = /* @__PURE__ */ new Map();
131170
+ const downloads = [];
131171
+ const localById = new Map(
131172
+ (args.localBinaries ?? []).map((binary) => [binary.fileId, binary])
131173
+ );
131174
+ const states = /* @__PURE__ */ new Map();
131175
+ const conflicts = [];
131176
+ let downloaded = 0;
131177
+ let conflicted = 0;
131178
+ for (const record6 of args.document.records.values()) {
131179
+ if (record6.deleted || record6.recordKind !== "project-file") continue;
131180
+ if (!isObjectRecord2(record6.data)) {
131181
+ throw new Error(`project-file:${record6.recordId} must be an object.`);
131178
131182
  }
131179
- const base = args.workspace.state.records[`project-file:${binary.fileId}`];
131180
- if (base === void 0 || !isObjectRecord2(base.data)) {
131181
- throw new Error(
131182
- `Project file ${binary.fileId} has replacement bytes but no CAS base.`
131183
- );
131183
+ if (record6.data.status !== "uploaded") continue;
131184
+ const existing = plans.get(record6.recordId);
131185
+ if (existing !== void 0) {
131186
+ if (existing.contentHash !== record6.contentHash)
131187
+ throw new Error(
131188
+ `Project file ${record6.recordId} appears with conflicting content hashes in the pulled document.`
131189
+ );
131190
+ continue;
131184
131191
  }
131185
- args.changes.push({
131186
- kind: "update",
131187
- recordKind: "project-file",
131188
- recordId: binary.fileId,
131189
- file: base.file ?? null,
131190
- nextData: { ...base.data },
131191
- baseData: base.data,
131192
- baseContentHash: base.contentHash,
131193
- casBaseHash: base.contentHash
131194
- });
131195
- }
131196
- }
131197
- function prepareProjectFilePushesV4(args) {
131198
- const prepared = [];
131199
- for (const binary of args.binaryChanges) {
131200
- if (binary.action !== "create" && binary.action !== "upload") continue;
131201
- const recordId = args.assignedIds.get(binary.fileId) ?? binary.fileId;
131202
- const change = args.changes.find(
131203
- (candidate) => candidate.recordKind === "project-file" && candidate.recordId === recordId
131192
+ const key = `project-file:${record6.recordId}`;
131193
+ const previous = args.previousRecords[key];
131194
+ const localStatus = localById.get(record6.recordId);
131195
+ const declarationPresent = args.destructive === true || args.localBinaries === void 0 || previous === void 0 || localStatus !== void 0;
131196
+ const path = localStatus?.path ?? previous?.projectBinary?.path ?? canonicalProjectBinaryPathV42(record6.data);
131197
+ const absolute = resolvePath(path);
131198
+ const localDigest = existsSync8(absolute) ? sha256File(absolute) : null;
131199
+ const baseDigest = previous?.projectBinary?.sha256 ?? readSha256(previous?.data) ?? null;
131200
+ const remoteDigest = requiredSha256(
131201
+ record6.data,
131202
+ `project-file:${record6.recordId} metadata`
131204
131203
  );
131205
- if (change === void 0) {
131206
- throw new Error(
131207
- `Project file ${recordId} has upload bytes but no source change.`
131208
- );
131204
+ let action = planBinaryMergeV4({
131205
+ baseDigest,
131206
+ localDigest,
131207
+ remoteDigest,
131208
+ declarationPresent,
131209
+ hadLocalBytes: previous?.projectBinary !== void 0
131210
+ });
131211
+ if (args.destructive === true) {
131212
+ action = localDigest === remoteDigest ? "unchanged" : "download";
131209
131213
  }
131210
- if (!isObjectRecord2(change.nextData)) {
131211
- throw new Error(
131212
- `Project file ${recordId} has upload bytes but its source change has no record data.`
131214
+ const plan = {
131215
+ key,
131216
+ fileId: record6.recordId,
131217
+ contentHash: record6.contentHash,
131218
+ data: record6.data,
131219
+ previous,
131220
+ path,
131221
+ baseDigest,
131222
+ remoteDigest,
131223
+ action
131224
+ };
131225
+ plans.set(record6.recordId, plan);
131226
+ if (action === "download" || action === "conflict") downloads.push(plan);
131227
+ }
131228
+ let stagingDirectory;
131229
+ try {
131230
+ const stagedPaths = /* @__PURE__ */ new Map();
131231
+ if (downloads.length > 0) {
131232
+ stagingDirectory = mkdtempSync(join9(tmpdir(), "neo-pull-files-"));
131233
+ const directory = stagingDirectory;
131234
+ const client = args.client ?? NeoApiClient.forBaseUrl(args.workspace.config.apiBaseUrl);
131235
+ await mapWithConcurrency(
131236
+ downloads,
131237
+ DOWNLOAD_CONCURRENCY,
131238
+ async (plan, index) => {
131239
+ const response = await download(
131240
+ plan.data,
131241
+ plan.fileId,
131242
+ client,
131243
+ args.workspace
131244
+ );
131245
+ if (response.sha256 !== plan.remoteDigest) {
131246
+ throw new Error(
131247
+ `Project file ${plan.fileId} content changed while pull was in progress (${plan.remoteDigest} -> ${response.sha256}). Pull again against a stable version head.`
131248
+ );
131249
+ }
131250
+ const stagedPath = join9(directory, String(index));
131251
+ writeFileSync8(stagedPath, response.bytes);
131252
+ stagedPaths.set(plan.fileId, stagedPath);
131253
+ }
131213
131254
  );
131214
131255
  }
131215
- const absolute = join9(args.workspace.root, binary.path);
131216
- const bytes = new Uint8Array(readFileSync11(absolute));
131217
- const digest = sha256Bytes(bytes);
131218
- if (binary.localSha256 !== null && digest !== binary.localSha256) {
131219
- throw new Error(
131220
- `Project file ${binary.path} changed after status was computed; run push again.`
131221
- );
131256
+ for (const plan of plans.values()) {
131257
+ const {
131258
+ key,
131259
+ fileId,
131260
+ data,
131261
+ previous,
131262
+ path,
131263
+ baseDigest,
131264
+ remoteDigest,
131265
+ action
131266
+ } = plan;
131267
+ const absolute = resolvePath(path);
131268
+ const stagedPath = stagedPaths.get(fileId);
131269
+ const remoteBytes = stagedPath === void 0 ? null : readFileSync11(stagedPath);
131270
+ if (action === "download") {
131271
+ if (remoteBytes === null) {
131272
+ throw new Error(
131273
+ `Project file ${fileId} download plan has no verified remote bytes.`
131274
+ );
131275
+ }
131276
+ writeVerifiedBinaryDownloadV4(absolute, remoteBytes, remoteDigest);
131277
+ downloaded += 1;
131278
+ states.set(key, binaryState(path, remoteDigest, data));
131279
+ continue;
131280
+ }
131281
+ if (action === "conflict") {
131282
+ if (remoteBytes === null) {
131283
+ throw new Error(
131284
+ `Project file ${fileId} conflict plan has no verified remote bytes.`
131285
+ );
131286
+ }
131287
+ const artifactPath = writeBinaryConflictArtifactV4(
131288
+ args.workspace.root,
131289
+ fileId,
131290
+ fileName(data),
131291
+ remoteBytes,
131292
+ remoteDigest
131293
+ );
131294
+ const state = binaryState(path, baseDigest, data);
131295
+ states.set(key, {
131296
+ ...state,
131297
+ conflict: {
131298
+ action: "conflict",
131299
+ remoteSha256: remoteDigest,
131300
+ artifactPath
131301
+ }
131302
+ });
131303
+ conflicts.push(path);
131304
+ conflicted += 1;
131305
+ continue;
131306
+ }
131307
+ if (action === "converged" || action === "unchanged") {
131308
+ states.set(key, binaryState(path, remoteDigest, data));
131309
+ removePreviousConflict(args.workspace.root, previous?.projectBinary);
131310
+ continue;
131311
+ }
131312
+ states.set(key, binaryState(path, baseDigest, data));
131222
131313
  }
131223
- const mimeType = binary.mimeType;
131224
- if (mimeType === null) {
131225
- throw new Error(`Project file ${binary.path} has no MIME type.`);
131314
+ for (const [key, previous] of Object.entries(args.previousRecords)) {
131315
+ if (previous.recordKind !== "project-file") continue;
131316
+ if (args.document.records.has(key)) continue;
131317
+ const localStatus = localById.get(previous.recordId);
131318
+ const path = localStatus?.path ?? previous.projectBinary?.path ?? canonicalProjectBinaryPathV42(previous.data);
131319
+ const absolute = resolvePath(path);
131320
+ const localDigest = existsSync8(absolute) ? sha256File(absolute) : null;
131321
+ const baseDigest = previous.projectBinary?.sha256 ?? readSha256(previous.data) ?? null;
131322
+ const action = planBinaryMergeV4({
131323
+ baseDigest,
131324
+ localDigest,
131325
+ remoteDigest: null,
131326
+ declarationPresent: localStatus !== void 0,
131327
+ hadLocalBytes: previous.projectBinary !== void 0
131328
+ });
131329
+ if (action === "delete-modify") {
131330
+ const data = isObjectRecord2(previous.data) ? previous.data : {};
131331
+ states.set(key, {
131332
+ ...binaryState(path, baseDigest, data),
131333
+ conflict: {
131334
+ action: "delete-modify",
131335
+ remoteSha256: null
131336
+ }
131337
+ });
131338
+ conflicts.push(path);
131339
+ conflicted += 1;
131340
+ continue;
131341
+ }
131342
+ rmSync5(absolute, { force: true });
131343
+ removePreviousConflict(args.workspace.root, previous.projectBinary);
131226
131344
  }
131227
- prepared.push({
131228
- uploadToken: `${recordId}-${prepared.length}`,
131229
- recordId,
131230
- replaceFileId: binary.action === "upload" ? recordId : null,
131231
- name: basename2(binary.path),
131232
- fileType: binary.kind,
131233
- mimeType,
131234
- byteLength: bytes.byteLength,
131235
- contentSha256: digest,
131236
- audioDurationSeconds: binary.kind === "audio" && typeof change.nextData.audioDurationSeconds === "number" ? change.nextData.audioDurationSeconds : null,
131237
- bytes
131238
- });
131345
+ return { states, downloaded, conflicted, conflicts };
131346
+ } finally {
131347
+ if (stagingDirectory !== void 0)
131348
+ rmSync5(stagingDirectory, { recursive: true, force: true });
131239
131349
  }
131240
- return prepared;
131241
131350
  }
131242
- async function stageProjectFilePushesV4(args) {
131243
- const prepared = args.prepared ?? prepareProjectFilePushesV4({
131244
- workspace: args.workspace,
131245
- changes: args.changes,
131246
- binaryChanges: args.binaryChanges,
131247
- assignedIds: args.assignedIds
131248
- });
131249
- const staged = [];
131250
- const stagedUploadSessionId = args.stagedUploadSessionId ?? randomUUID4();
131251
- const put = args.put ?? fetch;
131252
- const interruptController = new AbortController();
131253
- const interrupt = () => interruptController.abort(new ProjectFilePushCancelledError());
131254
- process.once("SIGINT", interrupt);
131351
+ async function download(data, fileId, client, workspace) {
131352
+ const version = typeof data.storageETag === "string" ? `?v=${encodeURIComponent(data.storageETag)}` : "";
131353
+ const path = `/api/projects/${workspace.config.projectId}/versions/${workspace.config.versionId}/files/${fileId}/content${version}`;
131354
+ const response = await client.getBytes(path);
131355
+ const computed = sha256Bytes(response.bytes);
131356
+ const metadataDigest = requiredSha256(
131357
+ data,
131358
+ `project-file:${fileId} metadata`
131359
+ );
131360
+ const headerDigest = normalizeSha256V4(
131361
+ response.contentSha256,
131362
+ `download response for project file ${fileId}`
131363
+ );
131364
+ if (headerDigest !== null && metadataDigest !== headerDigest) {
131365
+ throw new Error(
131366
+ `Project file ${fileId} metadata SHA-256 ${metadataDigest} did not match authenticated content response ${headerDigest}.`
131367
+ );
131368
+ }
131369
+ if (computed !== metadataDigest) {
131370
+ throw new Error(
131371
+ `Downloaded project file ${fileId} SHA-256 ${computed} did not match expected ${metadataDigest}.`
131372
+ );
131373
+ }
131374
+ if (typeof data.byteLength === "number" && response.bytes.byteLength !== data.byteLength) {
131375
+ throw new Error(
131376
+ `Downloaded project file ${fileId} has ${response.bytes.byteLength} bytes; metadata requires ${data.byteLength}.`
131377
+ );
131378
+ }
131379
+ if (typeof data.mimeType === "string" && response.contentType !== null && response.contentType !== data.mimeType) {
131380
+ throw new Error(
131381
+ `Downloaded project file ${fileId} content type ${response.contentType} did not match metadata ${data.mimeType}.`
131382
+ );
131383
+ }
131384
+ return { bytes: response.bytes, sha256: computed };
131385
+ }
131386
+ function committedProjectBinaryStateV4(previous, data) {
131387
+ return isObjectRecord2(data) ? binaryState(previous.path, readSha256(data), data) : previous;
131388
+ }
131389
+ function binaryState(path, sha256, data) {
131390
+ return {
131391
+ path,
131392
+ sha256,
131393
+ byteLength: typeof data.byteLength === "number" ? data.byteLength : 0,
131394
+ mimeType: typeof data.mimeType === "string" ? data.mimeType : "application/octet-stream"
131395
+ };
131396
+ }
131397
+ function readSha256(data) {
131398
+ if (!isObjectRecord2(data)) return null;
131399
+ return normalizeSha256V4(data.contentSha256, "project file metadata");
131400
+ }
131401
+ function requiredSha256(data, source) {
131402
+ if (!isObjectRecord2(data)) {
131403
+ throw new Error(`${source} must be an object.`);
131404
+ }
131405
+ const digest = normalizeSha256V4(data.contentSha256, source);
131406
+ if (digest === null) {
131407
+ throw new Error(`${source} is missing required contentSha256.`);
131408
+ }
131409
+ return digest;
131410
+ }
131411
+ function fileName(data) {
131412
+ if (typeof data.name !== "string") {
131413
+ throw new Error("Project file metadata has no filename.");
131414
+ }
131415
+ return data.name;
131416
+ }
131417
+ function removePreviousConflict(root, state) {
131418
+ if (state?.conflict?.artifactPath === void 0) return;
131419
+ rmSync5(join9(root, state.conflict.artifactPath), { force: true });
131420
+ }
131421
+ var DOWNLOAD_CONCURRENCY;
131422
+ var init_project_file_pull = __esm({
131423
+ "src/project-source/project-file-pull.ts"() {
131424
+ "use strict";
131425
+ init_workspace_source_path();
131426
+ init_concurrency();
131427
+ init_http();
131428
+ init_projection();
131429
+ init_project_files();
131430
+ DOWNLOAD_CONCURRENCY = 8;
131431
+ }
131432
+ });
131433
+
131434
+ // src/project-source/project-file-push.ts
131435
+ import { basename as basename2, join as join10 } from "node:path";
131436
+ import { readFileSync as readFileSync12 } from "node:fs";
131437
+ import { randomUUID as randomUUID4 } from "node:crypto";
131438
+ async function cleanupRejectedProjectFilePushesV4(args) {
131439
+ if (args.stagedUploadSessionId === null) return;
131440
+ try {
131441
+ await args.client.post(
131442
+ versionPath(args.workspace, "files/cleanup-staged-upload"),
131443
+ { stagedUploadSessionId: args.stagedUploadSessionId }
131444
+ );
131445
+ } catch {
131446
+ }
131447
+ }
131448
+ function ensureProjectFileBinaryChangesV4(args) {
131449
+ for (const binary of args.binaryChanges) {
131450
+ if (binary.action !== "upload") continue;
131451
+ if (args.changes.some(
131452
+ (change) => change.recordKind === "project-file" && change.recordId === binary.fileId
131453
+ )) {
131454
+ continue;
131455
+ }
131456
+ const base = args.workspace.state.records[`project-file:${binary.fileId}`];
131457
+ if (base === void 0 || !isObjectRecord2(base.data)) {
131458
+ throw new Error(
131459
+ `Project file ${binary.fileId} has replacement bytes but no CAS base.`
131460
+ );
131461
+ }
131462
+ args.changes.push({
131463
+ kind: "update",
131464
+ recordKind: "project-file",
131465
+ recordId: binary.fileId,
131466
+ file: base.file ?? null,
131467
+ nextData: { ...base.data },
131468
+ baseData: base.data,
131469
+ baseContentHash: base.contentHash,
131470
+ casBaseHash: base.contentHash
131471
+ });
131472
+ }
131473
+ }
131474
+ function prepareProjectFilePushesV4(args) {
131475
+ const prepared = [];
131476
+ for (const binary of args.binaryChanges) {
131477
+ if (binary.action !== "create" && binary.action !== "upload") continue;
131478
+ const recordId = args.assignedIds.get(binary.fileId) ?? binary.fileId;
131479
+ const change = args.changes.find(
131480
+ (candidate) => candidate.recordKind === "project-file" && candidate.recordId === recordId
131481
+ );
131482
+ if (change === void 0) {
131483
+ throw new Error(
131484
+ `Project file ${recordId} has upload bytes but no source change.`
131485
+ );
131486
+ }
131487
+ if (!isObjectRecord2(change.nextData)) {
131488
+ throw new Error(
131489
+ `Project file ${recordId} has upload bytes but its source change has no record data.`
131490
+ );
131491
+ }
131492
+ const absolute = join10(args.workspace.root, binary.path);
131493
+ const bytes = new Uint8Array(readFileSync12(absolute));
131494
+ const digest = sha256Bytes(bytes);
131495
+ if (binary.localSha256 !== null && digest !== binary.localSha256) {
131496
+ throw new Error(
131497
+ `Project file ${binary.path} changed after status was computed; run push again.`
131498
+ );
131499
+ }
131500
+ const mimeType = binary.mimeType;
131501
+ if (mimeType === null) {
131502
+ throw new Error(`Project file ${binary.path} has no MIME type.`);
131503
+ }
131504
+ prepared.push({
131505
+ uploadToken: `${recordId}-${prepared.length}`,
131506
+ recordId,
131507
+ replaceFileId: binary.action === "upload" ? recordId : null,
131508
+ name: basename2(binary.path),
131509
+ fileType: binary.kind,
131510
+ mimeType,
131511
+ byteLength: bytes.byteLength,
131512
+ contentSha256: digest,
131513
+ audioDurationSeconds: binary.kind === "audio" && typeof change.nextData.audioDurationSeconds === "number" ? change.nextData.audioDurationSeconds : null,
131514
+ bytes
131515
+ });
131516
+ }
131517
+ return prepared;
131518
+ }
131519
+ async function stageProjectFilePushesV4(args) {
131520
+ const prepared = args.prepared ?? prepareProjectFilePushesV4({
131521
+ workspace: args.workspace,
131522
+ changes: args.changes,
131523
+ binaryChanges: args.binaryChanges,
131524
+ assignedIds: args.assignedIds
131525
+ });
131526
+ const staged = [];
131527
+ const stagedUploadSessionId = args.stagedUploadSessionId ?? randomUUID4();
131528
+ const put = args.put ?? fetch;
131529
+ const interruptController = new AbortController();
131530
+ const interrupt = () => interruptController.abort(new ProjectFilePushCancelledError());
131531
+ process.once("SIGINT", interrupt);
131255
131532
  const signal = combineSignals(args.signal, interruptController.signal);
131256
131533
  const totalBytes = prepared.reduce((sum, file) => sum + file.byteLength, 0);
131257
131534
  let completedBytes = 0;
@@ -131829,8 +132106,8 @@ var init_project_version_validation = __esm({
131829
132106
  });
131830
132107
 
131831
132108
  // src/commands/push-body-diagnostics.ts
131832
- import { existsSync as existsSync8, readFileSync as readFileSync12 } from "node:fs";
131833
- import { join as join10 } from "node:path";
132109
+ import { existsSync as existsSync9, readFileSync as readFileSync13 } from "node:fs";
132110
+ import { join as join11 } from "node:path";
131834
132111
  function createNeoScriptBodySourceLocator(workspace, status) {
131835
132112
  const textByFile = /* @__PURE__ */ new Map();
131836
132113
  return {
@@ -131852,8 +132129,8 @@ function createNeoScriptBodySourceLocator(workspace, status) {
131852
132129
  textOf(file) {
131853
132130
  const cached = textByFile.get(file);
131854
132131
  if (cached !== void 0) return cached;
131855
- const path = join10(workspace.root, file);
131856
- const text = existsSync8(path) ? readFileSync12(path, "utf8") : null;
132132
+ const path = join11(workspace.root, file);
132133
+ const text = existsSync9(path) ? readFileSync13(path, "utf8") : null;
131857
132134
  textByFile.set(file, text);
131858
132135
  return text;
131859
132136
  }
@@ -132011,13 +132288,13 @@ __export(push_exports, {
132011
132288
  import { createHash as createHash10, randomUUID as randomUUID5 } from "node:crypto";
132012
132289
  import {
132013
132290
  mkdirSync as mkdirSync8,
132014
- writeFileSync as writeFileSync8,
132015
- rmSync as rmSync5,
132016
- existsSync as existsSync9,
132017
- readFileSync as readFileSync13,
132291
+ writeFileSync as writeFileSync9,
132292
+ rmSync as rmSync6,
132293
+ existsSync as existsSync10,
132294
+ readFileSync as readFileSync14,
132018
132295
  statSync
132019
132296
  } from "node:fs";
132020
- import { join as join11, relative as relative6, sep as sep6 } from "node:path";
132297
+ import { join as join12, relative as relative6, sep as sep6 } from "node:path";
132021
132298
  function assignPendingIds(changes, authoredValueSeeds, reconstructed3) {
132022
132299
  const assigned = /* @__PURE__ */ new Map();
132023
132300
  const assign = (pendingId2) => {
@@ -132984,7 +133261,7 @@ async function runPush(workspace, options, preparationOverride) {
132984
133261
  );
132985
133262
  }
132986
133263
  if (options.noVerify !== true) {
132987
- const preparedBuildDir = configuredHook === void 0 ? void 0 : join11(
133264
+ const preparedBuildDir = configuredHook === void 0 ? void 0 : join12(
132988
133265
  workspace.root,
132989
133266
  ".neo",
132990
133267
  "test-build",
@@ -133000,8 +133277,8 @@ async function runPush(workspace, options, preparationOverride) {
133000
133277
  }
133001
133278
  const documentJson = JSON.stringify(candidate.document);
133002
133279
  mkdirSync8(preparedBuildDir, { recursive: true });
133003
- writeFileSync8(
133004
- join11(preparedBuildDir, "candidate.json"),
133280
+ writeFileSync9(
133281
+ join12(preparedBuildDir, "candidate.json"),
133005
133282
  `${JSON.stringify({
133006
133283
  version: 1,
133007
133284
  projectFingerprint: `sha256:${preparedLocal.source.sourceHash}`,
@@ -133023,7 +133300,7 @@ async function runPush(workspace, options, preparationOverride) {
133023
133300
  });
133024
133301
  } finally {
133025
133302
  if (preparedBuildDir !== void 0) {
133026
- rmSync5(preparedBuildDir, { recursive: true, force: true });
133303
+ rmSync6(preparedBuildDir, { recursive: true, force: true });
133027
133304
  }
133028
133305
  }
133029
133306
  } else {
@@ -134253,7 +134530,7 @@ function statusForPushResult(workspace, submitted) {
134253
134530
  const input = snapshot.inputs[index];
134254
134531
  if (input?.uri !== relative6(workspace.root, path).split(sep6).join("/"))
134255
134532
  return computeWorkspaceStatus2(workspace);
134256
- if (input.text !== readFileSync13(path, "utf8"))
134533
+ if (input.text !== readFileSync14(path, "utf8"))
134257
134534
  return computeWorkspaceStatus2(workspace);
134258
134535
  }
134259
134536
  const binaries = inspectProjectBinaryStatusV4(
@@ -134422,13 +134699,14 @@ function foldChangedRecords(previous, changedRecords) {
134422
134699
  `Changed record "${key}" response is missing its snapshot content hash.`
134423
134700
  );
134424
134701
  }
134702
+ const projectBinary = records2[key]?.projectBinary;
134425
134703
  records2[key] = {
134426
134704
  recordKind,
134427
134705
  recordId,
134428
134706
  contentHash: snapshot.contentHash,
134429
134707
  data: changed.data,
134430
134708
  file: records2[key]?.file,
134431
- projectBinary: records2[key]?.projectBinary
134709
+ projectBinary: projectBinary && committedProjectBinaryStateV4(projectBinary, changed.data)
134432
134710
  };
134433
134711
  }
134434
134712
  return records2;
@@ -134459,8 +134737,8 @@ function authoredMemberFormsByFile(root, alternates) {
134459
134737
  for (const alternate of alternates) {
134460
134738
  let existing = contents.get(alternate.path);
134461
134739
  if (existing === void 0) {
134462
- const absolute = join11(root, alternate.path);
134463
- existing = existsSync9(absolute) ? readFileSync13(absolute, "utf8") : null;
134740
+ const absolute = join12(root, alternate.path);
134741
+ existing = existsSync10(absolute) ? readFileSync14(absolute, "utf8") : null;
134464
134742
  contents.set(alternate.path, existing);
134465
134743
  }
134466
134744
  if (existing === null || !existing.includes(alternate.block)) continue;
@@ -134569,7 +134847,7 @@ function rewriteFilesFromState(workspace, records2 = new Map(
134569
134847
  ];
134570
134848
  for (const path of previousPaths) {
134571
134849
  const absolute = resolveSourcePath(path);
134572
- if (!existsSync9(absolute) || emittedEntries.has(fileSystemEntryKey(absolute)))
134850
+ if (!existsSync10(absolute) || emittedEntries.has(fileSystemEntryKey(absolute)))
134573
134851
  continue;
134574
134852
  output.set(normalizeWorkspaceSourcePath(path), null);
134575
134853
  }
@@ -134577,7 +134855,7 @@ function rewriteFilesFromState(workspace, records2 = new Map(
134577
134855
  const absolute = resolveSourcePath(path);
134578
134856
  return {
134579
134857
  path,
134580
- before: existsSync9(absolute) ? readFileSync13(absolute, "utf8") : null,
134858
+ before: existsSync10(absolute) ? readFileSync14(absolute, "utf8") : null,
134581
134859
  after
134582
134860
  };
134583
134861
  });
@@ -134629,7 +134907,7 @@ function materializeAssignedSchemaIdsInAuthoredSource(workspace, replacements, p
134629
134907
  if (pendingIdsByUri.size === 0) return /* @__PURE__ */ new Map();
134630
134908
  const inputs = options.sourceSnapshot?.inputs ?? listProjectSourceFilesV4(workspace.root).map((absolutePath) => {
134631
134909
  const uri = relative6(workspace.root, absolutePath).split(sep6).join("/");
134632
- const text = readFileSync13(absolutePath, "utf8");
134910
+ const text = readFileSync14(absolutePath, "utf8");
134633
134911
  const kind = neoProjectSourceKind(uri, text);
134634
134912
  if (kind === null || !isNeoProjectProductionSourceKind(kind)) {
134635
134913
  throw new Error(
@@ -135433,6 +135711,7 @@ var init_push = __esm({
135433
135711
  init_project_documents();
135434
135712
  init_project_document_cache();
135435
135713
  init_project_files();
135714
+ init_project_file_pull();
135436
135715
  init_workspace_status();
135437
135716
  init_lower_support();
135438
135717
  init_init_conversion();
@@ -136087,515 +136366,241 @@ var init_conflict_markers = __esm({
136087
136366
  "src/project-sync/conflict-markers.ts"() {
136088
136367
  "use strict";
136089
136368
  }
136090
- });
136091
-
136092
- // src/project-sync/source-record-comparison.ts
136093
- function sourceAuthoredRecordsSemanticallyEqual(recordKind, left, right, mainLocale) {
136094
- if (isSchemaRecordKindV4(recordKind)) {
136095
- return schemaDocumentAuthoredSemanticallyEqual(recordKind, left, right);
136096
- }
136097
- return canonicallyEqual(
136098
- sourceComparableRecord(recordKind, left, mainLocale),
136099
- sourceComparableRecord(recordKind, right, mainLocale)
136100
- );
136101
- }
136102
- function sourceComparableObjectRecord(recordKind, value, mainLocale) {
136103
- const comparable = sourceComparableRecord(recordKind, value, mainLocale);
136104
- if (!isObjectRecord2(comparable)) {
136105
- throw new Error(
136106
- `Cannot merge ${recordKind} record because its source-comparable payload is not an object.`
136107
- );
136108
- }
136109
- return comparable;
136110
- }
136111
- function workspaceMainLocale(records2) {
136112
- const config = Object.values(records2).find(
136113
- (record6) => record6.recordKind === "localization-config"
136114
- );
136115
- const data = config?.data;
136116
- return isObjectRecord2(data) && typeof data.mainLocale === "string" ? data.mainLocale : "en-US";
136117
- }
136118
- function acceptSourceEquivalentConflictBases(workspace, mainLocale = workspaceMainLocale(workspace.state.records)) {
136119
- let accepted = 0;
136120
- for (const [key, record6] of Object.entries(workspace.state.records)) {
136121
- if (typeof record6.conflictServerHash !== "string") continue;
136122
- if (!sourceAuthoredRecordsSemanticallyEqual(
136123
- record6.recordKind,
136124
- record6.data,
136125
- record6.conflictServerData,
136126
- mainLocale
136127
- )) {
136128
- continue;
136129
- }
136130
- const next = {
136131
- ...record6,
136132
- contentHash: record6.conflictServerHash,
136133
- data: record6.conflictServerData
136134
- };
136135
- delete next.conflictServerHash;
136136
- delete next.conflictServerData;
136137
- delete next.conflictFiles;
136138
- workspace.state.records[key] = next;
136139
- accepted += 1;
136140
- }
136141
- return accepted;
136142
- }
136143
- function sourceComparableRecord(recordKind, value, mainLocale) {
136144
- return sourceComparableValue(
136145
- recordKind,
136146
- recordKind === "value" ? valueRecordComparisonBody(value) : value,
136147
- mainLocale
136148
- );
136149
- }
136150
- function sourceComparableValue(recordKind, value, mainLocale) {
136151
- if (Array.isArray(value)) {
136152
- return value.map(
136153
- (entry) => sourceComparableValue(recordKind, entry, mainLocale)
136154
- );
136155
- }
136156
- if (!isObjectRecord2(value)) return value;
136157
- const result = {};
136158
- const hasAuthoredCode = typeof value.code === "string" || typeof value.setterCode === "string";
136159
- for (const [key, entry] of Object.entries(value)) {
136160
- if (key === "projectId" || key === "createdAt" || key === "updatedAt") {
136161
- continue;
136162
- }
136163
- if (recordKind === "dialogue-node" && key === "layout") continue;
136164
- if (hasAuthoredCode && (key === "getter" || key === "setter" || key === "action")) {
136165
- continue;
136166
- }
136167
- if (recordKind === "localized-text" && key === "localeValues" && isObjectRecord2(entry)) {
136168
- result[key] = Object.hasOwn(entry, mainLocale) ? {
136169
- [mainLocale]: sourceComparableValue(
136170
- recordKind,
136171
- entry[mainLocale],
136172
- mainLocale
136173
- )
136174
- } : {};
136175
- continue;
136176
- }
136177
- result[key] = sourceComparableValue(recordKind, entry, mainLocale);
136178
- }
136179
- return result;
136180
- }
136181
- var init_source_record_comparison = __esm({
136182
- "src/project-sync/source-record-comparison.ts"() {
136183
- "use strict";
136184
- init_project_manifest();
136185
- init_project_documents();
136186
- init_projection();
136187
- init_value_record_comparison();
136188
- }
136189
- });
136190
-
136191
- // src/project-source/reset.ts
136192
- import {
136193
- existsSync as existsSync10,
136194
- mkdirSync as mkdirSync9,
136195
- readFileSync as readFileSync14,
136196
- rmSync as rmSync6,
136197
- writeFileSync as writeFileSync9
136198
- } from "node:fs";
136199
- import { dirname as dirname8, join as join12, relative as relative7, sep as sep7 } from "node:path";
136200
- function prepareWorkspaceResetV4(workspace, document, options = {}) {
136201
- const emissionRecords = options.regenerateSourceNames ? regenerateDialogueSourceNamesV4(document.records) : document.records;
136202
- const layoutWorkspace = workspaceForSourceLayoutV4(workspace, document);
136203
- const status = listProjectSourceFilesV4(workspace.root).length === 0 ? null : readWorkspaceLayoutStatusV4(layoutWorkspace);
136204
- const localValues = status?.localValues ?? workspace.state.localValues;
136205
- const emitted = emitProjectDocumentFilesV4(emissionRecords, {
136206
- localValues,
136207
- binaryPaths: workspaceBinaryPathsV4(workspace, status?.binaryFiles),
136208
- recordFiles: readWorkspaceRecordFilesV4(layoutWorkspace)
136209
- });
136210
- assertUniqueEmittedPaths2(emitted.files);
136211
- const resolveSourcePath = createWorkspaceSourcePathResolver(workspace.root);
136212
- for (const file of emitted.files) resolveSourcePath(file.path);
136213
- trackedProductionSourcesBeforeReset(workspace, resolveSourcePath);
136214
- return { emitted, localValues, status };
136215
- }
136216
- function resetWorkspaceToProjectSourcesV4(workspace, document, options = {}) {
136217
- const { emitted, localValues } = options.preparation ?? prepareWorkspaceResetV4(workspace, document, options);
136218
- const resolveSourcePath = createWorkspaceSourcePathResolver(workspace.root);
136219
- const trackedSources = trackedProductionSourcesBeforeReset(
136220
- workspace,
136221
- resolveSourcePath
136222
- );
136223
- const obsolete = new Set(trackedSources);
136224
- for (const path of listProjectSourceFilesV4(workspace.root)) {
136225
- obsolete.add(relative7(workspace.root, path).split(sep7).join("/"));
136226
- }
136227
- for (const file of emitted.files) obsolete.delete(file.path);
136228
- for (const path of obsolete) {
136229
- rmSync6(resolveSourcePath(path), { force: true });
136230
- }
136231
- for (const file of LEGACY_ROOT_FILES) {
136232
- rmSync6(join12(workspace.root, file), { force: true });
136233
- }
136234
- for (const privatePath of LEGACY_PRIVATE_PATHS) {
136235
- rmSync6(join12(workspace.root, privatePath), { recursive: true, force: true });
136236
- }
136237
- for (const file of emitted.files) {
136238
- const absolute = resolveSourcePath(file.path);
136239
- mkdirSync9(dirname8(absolute), { recursive: true });
136240
- if (existsSync10(absolute) && readFileSync14(absolute, "utf8") === file.content) {
136241
- continue;
136242
- }
136243
- writeFileSync9(absolute, file.content, "utf8");
136244
- }
136245
- writeProjectSourceAnalysisCacheV4(workspace.root, emitted.analysis);
136246
- const records2 = {};
136247
- for (const [key, record6] of document.records) {
136248
- if (record6.deleted) continue;
136249
- const file = emitted.recordFiles.get(key);
136250
- records2[key] = {
136251
- recordKind: record6.recordKind,
136252
- recordId: record6.recordId,
136253
- contentHash: record6.contentHash,
136254
- data: record6.data,
136255
- ...file ? { file } : {}
136256
- };
136257
- }
136258
- workspace.state.localValues = localValues;
136259
- workspace.state.records = records2;
136260
- writeMaterializedConstructionBuildCacheV2(workspace.root, workspace.state, {
136261
- expressions: emitted.materializedConstructors,
136262
- overrideKeys: emitted.materializedConstructorOverrideKeys
136263
- });
136264
- writeWorkspaceState(workspace.root, workspace.state);
136265
- workspace.config = { ...workspace.config, formatVersion: 4 };
136266
- writeWorkspaceConfig(workspace.root, workspace.config);
136267
- return {
136268
- written: emitted.files.length,
136269
- removed: obsolete.size,
136270
- fileCount: emitted.files.length,
136271
- staticMemberOwnershipRecoveries: emitted.staticMemberOwnershipRecoveries
136272
- };
136273
- }
136274
- function trackedProductionSourcesBeforeReset(workspace, resolveSourcePath) {
136275
- const paths = /* @__PURE__ */ new Set();
136276
- for (const record6 of Object.values(workspace.state.records)) {
136277
- if (record6.file === void 0) continue;
136278
- const path = normalizeWorkspaceSourcePath(record6.file);
136279
- const kind = neoProjectSourceKind(path);
136280
- if (kind === null || !isNeoProjectProductionSourceKind(kind)) {
136281
- continue;
136282
- }
136283
- const absolute = resolveSourcePath(path);
136284
- if (existsSync10(absolute)) paths.add(path);
136285
- }
136286
- return paths;
136287
- }
136288
- function assertUniqueEmittedPaths2(files) {
136289
- const seen = /* @__PURE__ */ new Map();
136290
- for (const file of files) {
136291
- const folded = file.path.toLowerCase();
136292
- const previous = seen.get(folded);
136293
- if (previous !== void 0) {
136294
- throw new Error(
136295
- `Reset projection emitted case-insensitive path collision ${JSON.stringify(previous)} / ${JSON.stringify(file.path)}. Rename one source declaration before resetting the workspace.`
136296
- );
136297
- }
136298
- seen.set(folded, file.path);
136299
- }
136300
- }
136301
- var LEGACY_ROOT_FILES, LEGACY_PRIVATE_PATHS;
136302
- var init_reset = __esm({
136303
- "src/project-source/reset.ts"() {
136304
- "use strict";
136305
- init_src();
136306
- init_source_layout();
136307
- init_workspace_status();
136308
- init_workspace();
136309
- init_project_documents();
136310
- init_project_document_cache();
136311
- init_materialized_construction_cache();
136312
- init_dialogue_sources();
136313
- init_workspace_source_path();
136314
- LEGACY_ROOT_FILES = [
136315
- "Localization.cs",
136316
- "Root.cs",
136317
- "Project.cs",
136318
- "NeoCompose.Schema.csproj"
136319
- ];
136320
- LEGACY_PRIVATE_PATHS = [
136321
- ".neo/tooling",
136322
- ".neo/dotnet-host.json",
136323
- ".neo/schema-manifest-v3.json"
136324
- ];
136325
- }
136326
- });
136327
-
136328
- // src/project-source/project-file-pull.ts
136329
- import {
136330
- existsSync as existsSync11,
136331
- mkdtempSync,
136332
- readFileSync as readFileSync15,
136333
- rmSync as rmSync7,
136334
- writeFileSync as writeFileSync10
136335
- } from "node:fs";
136336
- import { tmpdir } from "node:os";
136337
- import { join as join13 } from "node:path";
136338
- async function pullProjectBinariesV4(args) {
136339
- const resolvePath = createWorkspaceSourcePathResolver(args.workspace.root);
136340
- const plans = /* @__PURE__ */ new Map();
136341
- const downloads = [];
136342
- const localById = new Map(
136343
- (args.localBinaries ?? []).map((binary) => [binary.fileId, binary])
136344
- );
136345
- const states = /* @__PURE__ */ new Map();
136346
- const conflicts = [];
136347
- let downloaded = 0;
136348
- let conflicted = 0;
136349
- for (const record6 of args.document.records.values()) {
136350
- if (record6.deleted || record6.recordKind !== "project-file") continue;
136351
- if (!isObjectRecord2(record6.data)) {
136352
- throw new Error(`project-file:${record6.recordId} must be an object.`);
136353
- }
136354
- if (record6.data.status !== "uploaded") continue;
136355
- const existing = plans.get(record6.recordId);
136356
- if (existing !== void 0) {
136357
- if (existing.contentHash !== record6.contentHash)
136358
- throw new Error(
136359
- `Project file ${record6.recordId} appears with conflicting content hashes in the pulled document.`
136360
- );
136361
- continue;
136362
- }
136363
- const key = `project-file:${record6.recordId}`;
136364
- const previous = args.previousRecords[key];
136365
- const localStatus = localById.get(record6.recordId);
136366
- const declarationPresent = args.destructive === true || args.localBinaries === void 0 || previous === void 0 || localStatus !== void 0;
136367
- const path = localStatus?.path ?? previous?.projectBinary?.path ?? canonicalProjectBinaryPathV42(record6.data);
136368
- const absolute = resolvePath(path);
136369
- const localDigest = existsSync11(absolute) ? sha256File(absolute) : null;
136370
- const baseDigest = previous?.projectBinary?.sha256 ?? readSha256(previous?.data) ?? null;
136371
- const remoteDigest = requiredSha256(
136372
- record6.data,
136373
- `project-file:${record6.recordId} metadata`
136374
- );
136375
- let action = planBinaryMergeV4({
136376
- baseDigest,
136377
- localDigest,
136378
- remoteDigest,
136379
- declarationPresent,
136380
- hadLocalBytes: previous?.projectBinary !== void 0
136381
- });
136382
- if (args.destructive === true) {
136383
- action = localDigest === remoteDigest ? "unchanged" : "download";
136384
- }
136385
- const plan = {
136386
- key,
136387
- fileId: record6.recordId,
136388
- contentHash: record6.contentHash,
136389
- data: record6.data,
136390
- previous,
136391
- path,
136392
- baseDigest,
136393
- remoteDigest,
136394
- action
136395
- };
136396
- plans.set(record6.recordId, plan);
136397
- if (action === "download" || action === "conflict") downloads.push(plan);
136398
- }
136399
- let stagingDirectory;
136400
- try {
136401
- const stagedPaths = /* @__PURE__ */ new Map();
136402
- if (downloads.length > 0) {
136403
- stagingDirectory = mkdtempSync(join13(tmpdir(), "neo-pull-files-"));
136404
- const directory = stagingDirectory;
136405
- const client = args.client ?? NeoApiClient.forBaseUrl(args.workspace.config.apiBaseUrl);
136406
- await mapWithConcurrency(
136407
- downloads,
136408
- DOWNLOAD_CONCURRENCY,
136409
- async (plan, index) => {
136410
- const response = await download(
136411
- plan.data,
136412
- plan.fileId,
136413
- client,
136414
- args.workspace
136415
- );
136416
- if (response.sha256 !== plan.remoteDigest) {
136417
- throw new Error(
136418
- `Project file ${plan.fileId} content changed while pull was in progress (${plan.remoteDigest} -> ${response.sha256}). Pull again against a stable version head.`
136419
- );
136420
- }
136421
- const stagedPath = join13(directory, String(index));
136422
- writeFileSync10(stagedPath, response.bytes);
136423
- stagedPaths.set(plan.fileId, stagedPath);
136424
- }
136425
- );
136426
- }
136427
- for (const plan of plans.values()) {
136428
- const {
136429
- key,
136430
- fileId,
136431
- data,
136432
- previous,
136433
- path,
136434
- baseDigest,
136435
- remoteDigest,
136436
- action
136437
- } = plan;
136438
- const absolute = resolvePath(path);
136439
- const stagedPath = stagedPaths.get(fileId);
136440
- const remoteBytes = stagedPath === void 0 ? null : readFileSync15(stagedPath);
136441
- if (action === "download") {
136442
- if (remoteBytes === null) {
136443
- throw new Error(
136444
- `Project file ${fileId} download plan has no verified remote bytes.`
136445
- );
136446
- }
136447
- writeVerifiedBinaryDownloadV4(absolute, remoteBytes, remoteDigest);
136448
- downloaded += 1;
136449
- states.set(key, binaryState(path, remoteDigest, data));
136450
- continue;
136451
- }
136452
- if (action === "conflict") {
136453
- if (remoteBytes === null) {
136454
- throw new Error(
136455
- `Project file ${fileId} conflict plan has no verified remote bytes.`
136456
- );
136457
- }
136458
- const artifactPath = writeBinaryConflictArtifactV4(
136459
- args.workspace.root,
136460
- fileId,
136461
- fileName(data),
136462
- remoteBytes,
136463
- remoteDigest
136464
- );
136465
- const state = binaryState(path, baseDigest, data);
136466
- states.set(key, {
136467
- ...state,
136468
- conflict: {
136469
- action: "conflict",
136470
- remoteSha256: remoteDigest,
136471
- artifactPath
136472
- }
136473
- });
136474
- conflicts.push(path);
136475
- conflicted += 1;
136476
- continue;
136477
- }
136478
- if (action === "converged" || action === "unchanged") {
136479
- states.set(key, binaryState(path, remoteDigest, data));
136480
- removePreviousConflict(args.workspace.root, previous?.projectBinary);
136481
- continue;
136482
- }
136483
- states.set(key, binaryState(path, baseDigest, data));
136484
- }
136485
- for (const [key, previous] of Object.entries(args.previousRecords)) {
136486
- if (previous.recordKind !== "project-file") continue;
136487
- if (args.document.records.has(key)) continue;
136488
- const localStatus = localById.get(previous.recordId);
136489
- const path = localStatus?.path ?? previous.projectBinary?.path ?? canonicalProjectBinaryPathV42(previous.data);
136490
- const absolute = resolvePath(path);
136491
- const localDigest = existsSync11(absolute) ? sha256File(absolute) : null;
136492
- const baseDigest = previous.projectBinary?.sha256 ?? readSha256(previous.data) ?? null;
136493
- const action = planBinaryMergeV4({
136494
- baseDigest,
136495
- localDigest,
136496
- remoteDigest: null,
136497
- declarationPresent: localStatus !== void 0,
136498
- hadLocalBytes: previous.projectBinary !== void 0
136499
- });
136500
- if (action === "delete-modify") {
136501
- const data = isObjectRecord2(previous.data) ? previous.data : {};
136502
- states.set(key, {
136503
- ...binaryState(path, baseDigest, data),
136504
- conflict: {
136505
- action: "delete-modify",
136506
- remoteSha256: null
136507
- }
136508
- });
136509
- conflicts.push(path);
136510
- conflicted += 1;
136511
- continue;
136512
- }
136513
- rmSync7(absolute, { force: true });
136514
- removePreviousConflict(args.workspace.root, previous.projectBinary);
136515
- }
136516
- return { states, downloaded, conflicted, conflicts };
136517
- } finally {
136518
- if (stagingDirectory !== void 0)
136519
- rmSync7(stagingDirectory, { recursive: true, force: true });
136520
- }
136521
- }
136522
- async function download(data, fileId, client, workspace) {
136523
- const version = typeof data.storageETag === "string" ? `?v=${encodeURIComponent(data.storageETag)}` : "";
136524
- const path = `/api/projects/${workspace.config.projectId}/versions/${workspace.config.versionId}/files/${fileId}/content${version}`;
136525
- const response = await client.getBytes(path);
136526
- const computed = sha256Bytes(response.bytes);
136527
- const metadataDigest = requiredSha256(
136528
- data,
136529
- `project-file:${fileId} metadata`
136530
- );
136531
- const headerDigest = normalizeSha256V4(
136532
- response.contentSha256,
136533
- `download response for project file ${fileId}`
136369
+ });
136370
+
136371
+ // src/project-sync/source-record-comparison.ts
136372
+ function sourceAuthoredRecordsSemanticallyEqual(recordKind, left, right, mainLocale) {
136373
+ if (isSchemaRecordKindV4(recordKind)) {
136374
+ return schemaDocumentAuthoredSemanticallyEqual(recordKind, left, right);
136375
+ }
136376
+ return canonicallyEqual(
136377
+ sourceComparableRecord(recordKind, left, mainLocale),
136378
+ sourceComparableRecord(recordKind, right, mainLocale)
136534
136379
  );
136535
- if (headerDigest !== null && metadataDigest !== headerDigest) {
136380
+ }
136381
+ function sourceComparableObjectRecord(recordKind, value, mainLocale) {
136382
+ const comparable = sourceComparableRecord(recordKind, value, mainLocale);
136383
+ if (!isObjectRecord2(comparable)) {
136536
136384
  throw new Error(
136537
- `Project file ${fileId} metadata SHA-256 ${metadataDigest} did not match authenticated content response ${headerDigest}.`
136385
+ `Cannot merge ${recordKind} record because its source-comparable payload is not an object.`
136538
136386
  );
136539
136387
  }
136540
- if (computed !== metadataDigest) {
136541
- throw new Error(
136542
- `Downloaded project file ${fileId} SHA-256 ${computed} did not match expected ${metadataDigest}.`
136543
- );
136388
+ return comparable;
136389
+ }
136390
+ function workspaceMainLocale(records2) {
136391
+ const config = Object.values(records2).find(
136392
+ (record6) => record6.recordKind === "localization-config"
136393
+ );
136394
+ const data = config?.data;
136395
+ return isObjectRecord2(data) && typeof data.mainLocale === "string" ? data.mainLocale : "en-US";
136396
+ }
136397
+ function acceptSourceEquivalentConflictBases(workspace, mainLocale = workspaceMainLocale(workspace.state.records)) {
136398
+ let accepted = 0;
136399
+ for (const [key, record6] of Object.entries(workspace.state.records)) {
136400
+ if (typeof record6.conflictServerHash !== "string") continue;
136401
+ if (!sourceAuthoredRecordsSemanticallyEqual(
136402
+ record6.recordKind,
136403
+ record6.data,
136404
+ record6.conflictServerData,
136405
+ mainLocale
136406
+ )) {
136407
+ continue;
136408
+ }
136409
+ const next = {
136410
+ ...record6,
136411
+ contentHash: record6.conflictServerHash,
136412
+ data: record6.conflictServerData
136413
+ };
136414
+ delete next.conflictServerHash;
136415
+ delete next.conflictServerData;
136416
+ delete next.conflictFiles;
136417
+ workspace.state.records[key] = next;
136418
+ accepted += 1;
136544
136419
  }
136545
- if (typeof data.byteLength === "number" && response.bytes.byteLength !== data.byteLength) {
136546
- throw new Error(
136547
- `Downloaded project file ${fileId} has ${response.bytes.byteLength} bytes; metadata requires ${data.byteLength}.`
136420
+ return accepted;
136421
+ }
136422
+ function sourceComparableRecord(recordKind, value, mainLocale) {
136423
+ return sourceComparableValue(
136424
+ recordKind,
136425
+ recordKind === "value" ? valueRecordComparisonBody(value) : value,
136426
+ mainLocale
136427
+ );
136428
+ }
136429
+ function sourceComparableValue(recordKind, value, mainLocale) {
136430
+ if (Array.isArray(value)) {
136431
+ return value.map(
136432
+ (entry) => sourceComparableValue(recordKind, entry, mainLocale)
136548
136433
  );
136549
136434
  }
136550
- if (typeof data.mimeType === "string" && response.contentType !== null && response.contentType !== data.mimeType) {
136551
- throw new Error(
136552
- `Downloaded project file ${fileId} content type ${response.contentType} did not match metadata ${data.mimeType}.`
136553
- );
136435
+ if (!isObjectRecord2(value)) return value;
136436
+ const result = {};
136437
+ const hasAuthoredCode = typeof value.code === "string" || typeof value.setterCode === "string";
136438
+ for (const [key, entry] of Object.entries(value)) {
136439
+ if (key === "projectId" || key === "createdAt" || key === "updatedAt") {
136440
+ continue;
136441
+ }
136442
+ if (recordKind === "dialogue-node" && key === "layout") continue;
136443
+ if (hasAuthoredCode && (key === "getter" || key === "setter" || key === "action")) {
136444
+ continue;
136445
+ }
136446
+ if (recordKind === "localized-text" && key === "localeValues" && isObjectRecord2(entry)) {
136447
+ result[key] = Object.hasOwn(entry, mainLocale) ? {
136448
+ [mainLocale]: sourceComparableValue(
136449
+ recordKind,
136450
+ entry[mainLocale],
136451
+ mainLocale
136452
+ )
136453
+ } : {};
136454
+ continue;
136455
+ }
136456
+ result[key] = sourceComparableValue(recordKind, entry, mainLocale);
136554
136457
  }
136555
- return { bytes: response.bytes, sha256: computed };
136556
- }
136557
- function binaryState(path, sha256, data) {
136558
- return {
136559
- path,
136560
- sha256,
136561
- byteLength: typeof data.byteLength === "number" ? data.byteLength : 0,
136562
- mimeType: typeof data.mimeType === "string" ? data.mimeType : "application/octet-stream"
136563
- };
136458
+ return result;
136564
136459
  }
136565
- function readSha256(data) {
136566
- if (!isObjectRecord2(data)) return null;
136567
- return normalizeSha256V4(data.contentSha256, "project file metadata");
136460
+ var init_source_record_comparison = __esm({
136461
+ "src/project-sync/source-record-comparison.ts"() {
136462
+ "use strict";
136463
+ init_project_manifest();
136464
+ init_project_documents();
136465
+ init_projection();
136466
+ init_value_record_comparison();
136467
+ }
136468
+ });
136469
+
136470
+ // src/project-source/reset.ts
136471
+ import {
136472
+ existsSync as existsSync11,
136473
+ mkdirSync as mkdirSync9,
136474
+ readFileSync as readFileSync15,
136475
+ rmSync as rmSync7,
136476
+ writeFileSync as writeFileSync10
136477
+ } from "node:fs";
136478
+ import { dirname as dirname8, join as join13, relative as relative7, sep as sep7 } from "node:path";
136479
+ function prepareWorkspaceResetV4(workspace, document, options = {}) {
136480
+ const emissionRecords = options.regenerateSourceNames ? regenerateDialogueSourceNamesV4(document.records) : document.records;
136481
+ const layoutWorkspace = workspaceForSourceLayoutV4(workspace, document);
136482
+ const status = listProjectSourceFilesV4(workspace.root).length === 0 ? null : readWorkspaceLayoutStatusV4(layoutWorkspace);
136483
+ const localValues = status?.localValues ?? workspace.state.localValues;
136484
+ const emitted = emitProjectDocumentFilesV4(emissionRecords, {
136485
+ localValues,
136486
+ binaryPaths: workspaceBinaryPathsV4(workspace, status?.binaryFiles),
136487
+ recordFiles: readWorkspaceRecordFilesV4(layoutWorkspace)
136488
+ });
136489
+ assertUniqueEmittedPaths2(emitted.files);
136490
+ const resolveSourcePath = createWorkspaceSourcePathResolver(workspace.root);
136491
+ for (const file of emitted.files) resolveSourcePath(file.path);
136492
+ trackedProductionSourcesBeforeReset(workspace, resolveSourcePath);
136493
+ return { emitted, localValues, status };
136568
136494
  }
136569
- function requiredSha256(data, source) {
136570
- if (!isObjectRecord2(data)) {
136571
- throw new Error(`${source} must be an object.`);
136495
+ function resetWorkspaceToProjectSourcesV4(workspace, document, options = {}) {
136496
+ const { emitted, localValues } = options.preparation ?? prepareWorkspaceResetV4(workspace, document, options);
136497
+ const resolveSourcePath = createWorkspaceSourcePathResolver(workspace.root);
136498
+ const trackedSources = trackedProductionSourcesBeforeReset(
136499
+ workspace,
136500
+ resolveSourcePath
136501
+ );
136502
+ const obsolete = new Set(trackedSources);
136503
+ for (const path of listProjectSourceFilesV4(workspace.root)) {
136504
+ obsolete.add(relative7(workspace.root, path).split(sep7).join("/"));
136572
136505
  }
136573
- const digest = normalizeSha256V4(data.contentSha256, source);
136574
- if (digest === null) {
136575
- throw new Error(`${source} is missing required contentSha256.`);
136506
+ for (const file of emitted.files) obsolete.delete(file.path);
136507
+ for (const path of obsolete) {
136508
+ rmSync7(resolveSourcePath(path), { force: true });
136576
136509
  }
136577
- return digest;
136510
+ for (const file of LEGACY_ROOT_FILES) {
136511
+ rmSync7(join13(workspace.root, file), { force: true });
136512
+ }
136513
+ for (const privatePath of LEGACY_PRIVATE_PATHS) {
136514
+ rmSync7(join13(workspace.root, privatePath), { recursive: true, force: true });
136515
+ }
136516
+ for (const file of emitted.files) {
136517
+ const absolute = resolveSourcePath(file.path);
136518
+ mkdirSync9(dirname8(absolute), { recursive: true });
136519
+ if (existsSync11(absolute) && readFileSync15(absolute, "utf8") === file.content) {
136520
+ continue;
136521
+ }
136522
+ writeFileSync10(absolute, file.content, "utf8");
136523
+ }
136524
+ writeProjectSourceAnalysisCacheV4(workspace.root, emitted.analysis);
136525
+ const records2 = {};
136526
+ for (const [key, record6] of document.records) {
136527
+ if (record6.deleted) continue;
136528
+ const file = emitted.recordFiles.get(key);
136529
+ records2[key] = {
136530
+ recordKind: record6.recordKind,
136531
+ recordId: record6.recordId,
136532
+ contentHash: record6.contentHash,
136533
+ data: record6.data,
136534
+ ...file ? { file } : {}
136535
+ };
136536
+ }
136537
+ workspace.state.localValues = localValues;
136538
+ workspace.state.records = records2;
136539
+ writeMaterializedConstructionBuildCacheV2(workspace.root, workspace.state, {
136540
+ expressions: emitted.materializedConstructors,
136541
+ overrideKeys: emitted.materializedConstructorOverrideKeys
136542
+ });
136543
+ writeWorkspaceState(workspace.root, workspace.state);
136544
+ workspace.config = { ...workspace.config, formatVersion: 4 };
136545
+ writeWorkspaceConfig(workspace.root, workspace.config);
136546
+ return {
136547
+ written: emitted.files.length,
136548
+ removed: obsolete.size,
136549
+ fileCount: emitted.files.length,
136550
+ staticMemberOwnershipRecoveries: emitted.staticMemberOwnershipRecoveries
136551
+ };
136578
136552
  }
136579
- function fileName(data) {
136580
- if (typeof data.name !== "string") {
136581
- throw new Error("Project file metadata has no filename.");
136553
+ function trackedProductionSourcesBeforeReset(workspace, resolveSourcePath) {
136554
+ const paths = /* @__PURE__ */ new Set();
136555
+ for (const record6 of Object.values(workspace.state.records)) {
136556
+ if (record6.file === void 0) continue;
136557
+ const path = normalizeWorkspaceSourcePath(record6.file);
136558
+ const kind = neoProjectSourceKind(path);
136559
+ if (kind === null || !isNeoProjectProductionSourceKind(kind)) {
136560
+ continue;
136561
+ }
136562
+ const absolute = resolveSourcePath(path);
136563
+ if (existsSync11(absolute)) paths.add(path);
136582
136564
  }
136583
- return data.name;
136565
+ return paths;
136584
136566
  }
136585
- function removePreviousConflict(root, state) {
136586
- if (state?.conflict?.artifactPath === void 0) return;
136587
- rmSync7(join13(root, state.conflict.artifactPath), { force: true });
136567
+ function assertUniqueEmittedPaths2(files) {
136568
+ const seen = /* @__PURE__ */ new Map();
136569
+ for (const file of files) {
136570
+ const folded = file.path.toLowerCase();
136571
+ const previous = seen.get(folded);
136572
+ if (previous !== void 0) {
136573
+ throw new Error(
136574
+ `Reset projection emitted case-insensitive path collision ${JSON.stringify(previous)} / ${JSON.stringify(file.path)}. Rename one source declaration before resetting the workspace.`
136575
+ );
136576
+ }
136577
+ seen.set(folded, file.path);
136578
+ }
136588
136579
  }
136589
- var DOWNLOAD_CONCURRENCY;
136590
- var init_project_file_pull = __esm({
136591
- "src/project-source/project-file-pull.ts"() {
136580
+ var LEGACY_ROOT_FILES, LEGACY_PRIVATE_PATHS;
136581
+ var init_reset = __esm({
136582
+ "src/project-source/reset.ts"() {
136592
136583
  "use strict";
136584
+ init_src();
136585
+ init_source_layout();
136586
+ init_workspace_status();
136587
+ init_workspace();
136588
+ init_project_documents();
136589
+ init_project_document_cache();
136590
+ init_materialized_construction_cache();
136591
+ init_dialogue_sources();
136593
136592
  init_workspace_source_path();
136594
- init_concurrency();
136595
- init_http();
136596
- init_projection();
136597
- init_project_files();
136598
- DOWNLOAD_CONCURRENCY = 8;
136593
+ LEGACY_ROOT_FILES = [
136594
+ "Localization.cs",
136595
+ "Root.cs",
136596
+ "Project.cs",
136597
+ "NeoCompose.Schema.csproj"
136598
+ ];
136599
+ LEGACY_PRIVATE_PATHS = [
136600
+ ".neo/tooling",
136601
+ ".neo/dotnet-host.json",
136602
+ ".neo/schema-manifest-v3.json"
136603
+ ];
136599
136604
  }
136600
136605
  });
136601
136606
 
@@ -140752,7 +140757,7 @@ var init_registry2 = __esm({
140752
140757
  "schema-contract/registry.mjs"() {
140753
140758
  "use strict";
140754
140759
  PROJECT_SCHEMA_CONTRACT = Object.freeze({
140755
- cliVersion: "0.59.0",
140760
+ cliVersion: "0.59.1",
140756
140761
  projectFileUploadBatchSize: 32,
140757
140762
  documentRecords: {
140758
140763
  member: {
@@ -148573,7 +148578,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
148573
148578
  async function main() {
148574
148579
  const args = parseArgs(process.argv.slice(2));
148575
148580
  if (args.command === "--version") {
148576
- console.log("0.59.0");
148581
+ console.log("0.59.1");
148577
148582
  return;
148578
148583
  }
148579
148584
  if (args.command === null || args.command === "help" || args.command === "--help" || args.command === "-h") {