@neocompose/cli 0.3.0 → 0.3.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/CHANGELOG.md +14 -0
- package/dist/neo.mjs +643 -7
- package/dist/tooling/NeoCompose.Schema.Tooling.dll +0 -0
- package/dist/tooling/NeoCompose.Schema.Tooling.pdb +0 -0
- package/dist/tooling/NeoCompose.Schema.dll +0 -0
- package/dist/tooling/NeoCompose.Schema.pdb +0 -0
- package/dist/tooling/NeoCompose.SchemaCompiler.dll +0 -0
- package/dist/tooling/NeoCompose.SchemaCompiler.pdb +0 -0
- package/dist/tooling/schema-contract.json +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.1] - 2026-07-16
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Added `neo history inspect`, `undo`, `revert`, and `flatten` workflows with
|
|
8
|
+
interactive project and version selection.
|
|
9
|
+
- Added deterministic `@current` and `@latest` version resolution for history
|
|
10
|
+
commands.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Reduced project synchronization I/O with incremental export manifests and
|
|
15
|
+
bounded snapshot retrieval.
|
|
16
|
+
|
|
3
17
|
## [0.3.0] - 2026-07-15
|
|
4
18
|
|
|
5
19
|
### Breaking
|
package/dist/neo.mjs
CHANGED
|
@@ -1390,7 +1390,7 @@ var init_source_protocol = __esm({
|
|
|
1390
1390
|
SCHEMA_SOURCE_MANIFEST_NAME = "neo-schema-manifest-v3";
|
|
1391
1391
|
SCHEMA_COMPILER_PROTOCOL_VERSION = 4;
|
|
1392
1392
|
SCHEMA_SDK_VERSION = "3.0.0";
|
|
1393
|
-
SCHEMA_CLI_VERSION = "0.3.
|
|
1393
|
+
SCHEMA_CLI_VERSION = "0.3.1";
|
|
1394
1394
|
SCHEMA_API_TARGET = "netstandard2.1";
|
|
1395
1395
|
SCHEMA_REFERENCE_ASSEMBLY_SET = "NETStandard.Library.Ref/2.1.0";
|
|
1396
1396
|
SCHEMA_REQUIRED_TOOLING_FILES = [
|
|
@@ -31121,10 +31121,15 @@ function appendBucketRecord(buckets, recordKind, data) {
|
|
|
31121
31121
|
}
|
|
31122
31122
|
buckets[bucket].push(data);
|
|
31123
31123
|
}
|
|
31124
|
-
function readProjectDocument(value) {
|
|
31124
|
+
function readProjectDocument(value, options = {}) {
|
|
31125
31125
|
if (!isObject2(value)) {
|
|
31126
31126
|
throw new Error("Convex project document query returned a non-object.");
|
|
31127
31127
|
}
|
|
31128
|
+
const localizationConfig = options.localizationConfig === "nullable" ? readOptionalNullableField(
|
|
31129
|
+
value,
|
|
31130
|
+
"localizationConfig",
|
|
31131
|
+
isProjectLocalizationConfig
|
|
31132
|
+
) : readField(value, "localizationConfig", isProjectLocalizationConfig);
|
|
31128
31133
|
return {
|
|
31129
31134
|
version: readField(value, "version", isProjectVersion),
|
|
31130
31135
|
versions: readArrayField(value, "versions", isProjectVersion),
|
|
@@ -31176,11 +31181,7 @@ function readProjectDocument(value) {
|
|
|
31176
31181
|
"audioClipTemplates",
|
|
31177
31182
|
isUnityAudioClipImportSettingsTemplate
|
|
31178
31183
|
),
|
|
31179
|
-
localizationConfig
|
|
31180
|
-
value,
|
|
31181
|
-
"localizationConfig",
|
|
31182
|
-
isProjectLocalizationConfig
|
|
31183
|
-
),
|
|
31184
|
+
localizationConfig,
|
|
31184
31185
|
localizationStatuses: readArrayField(
|
|
31185
31186
|
value,
|
|
31186
31187
|
"localizationStatuses",
|
|
@@ -31221,6 +31222,14 @@ function readField(source, key, isValid) {
|
|
|
31221
31222
|
`Convex project document field "${key}" has an invalid shape.`
|
|
31222
31223
|
);
|
|
31223
31224
|
}
|
|
31225
|
+
function readOptionalNullableField(source, key, isValid) {
|
|
31226
|
+
const value = source[key];
|
|
31227
|
+
if (value === void 0 || value === null) return null;
|
|
31228
|
+
if (isValid(value)) return value;
|
|
31229
|
+
throw new Error(
|
|
31230
|
+
`Convex project document field "${key}" has an invalid shape.`
|
|
31231
|
+
);
|
|
31232
|
+
}
|
|
31224
31233
|
function readArrayField(source, key, isValid) {
|
|
31225
31234
|
const value = source[key];
|
|
31226
31235
|
if (!Array.isArray(value)) {
|
|
@@ -32162,6 +32171,565 @@ var init_init = __esm({
|
|
|
32162
32171
|
}
|
|
32163
32172
|
});
|
|
32164
32173
|
|
|
32174
|
+
// src/commands/history-selection.ts
|
|
32175
|
+
async function resolveHistoryScope(options) {
|
|
32176
|
+
const projectSelector = await readHistorySelector({
|
|
32177
|
+
command: options.command,
|
|
32178
|
+
kind: "project",
|
|
32179
|
+
provided: options.projectSelector
|
|
32180
|
+
});
|
|
32181
|
+
const project = await options.resolveProject(projectSelector);
|
|
32182
|
+
console.log(`${sym.info} Project: ${project.name} (${project.id})`);
|
|
32183
|
+
if (!options.requiresVersion) {
|
|
32184
|
+
return { project, version: null };
|
|
32185
|
+
}
|
|
32186
|
+
const versionSelector = await readHistorySelector({
|
|
32187
|
+
command: options.command,
|
|
32188
|
+
kind: "version",
|
|
32189
|
+
provided: options.versionSelector
|
|
32190
|
+
});
|
|
32191
|
+
const version = await options.resolveVersion(project.id, versionSelector);
|
|
32192
|
+
console.log(`${sym.info} Version: ${version.name} (${version.id})`);
|
|
32193
|
+
return { project, version };
|
|
32194
|
+
}
|
|
32195
|
+
async function readHistorySelector(args) {
|
|
32196
|
+
if (args.provided !== null) return normalizeHistorySelector(args.provided);
|
|
32197
|
+
const flag = args.kind === "project" ? "--project" : "--version";
|
|
32198
|
+
if (!isInteractive()) {
|
|
32199
|
+
throw new Error(
|
|
32200
|
+
`neo history ${args.command} requires ${flag} <id|@latest|@current> outside an interactive terminal.`
|
|
32201
|
+
);
|
|
32202
|
+
}
|
|
32203
|
+
const label = args.kind === "project" ? "Project" : "Version";
|
|
32204
|
+
const value = await promptInput({
|
|
32205
|
+
message: `${label} ID (@latest or @current; Enter for latest)`,
|
|
32206
|
+
nonInteractiveHint: `Pass ${flag} <id|@latest|@current>.`
|
|
32207
|
+
});
|
|
32208
|
+
return normalizeHistorySelector(value);
|
|
32209
|
+
}
|
|
32210
|
+
function normalizeHistorySelector(value) {
|
|
32211
|
+
const trimmed = value.trim();
|
|
32212
|
+
if (trimmed.length === 0 || trimmed === "@latest" || trimmed === "@current") {
|
|
32213
|
+
return "@latest";
|
|
32214
|
+
}
|
|
32215
|
+
if (trimmed.startsWith("@")) {
|
|
32216
|
+
throw new Error(
|
|
32217
|
+
`Unknown history selector "${trimmed}". Use a stable ID, @latest, or @current.`
|
|
32218
|
+
);
|
|
32219
|
+
}
|
|
32220
|
+
return trimmed;
|
|
32221
|
+
}
|
|
32222
|
+
var init_history_selection = __esm({
|
|
32223
|
+
"src/commands/history-selection.ts"() {
|
|
32224
|
+
"use strict";
|
|
32225
|
+
init_ui();
|
|
32226
|
+
}
|
|
32227
|
+
});
|
|
32228
|
+
|
|
32229
|
+
// src/commands/history-inspect.ts
|
|
32230
|
+
function inspectHistoryDataset(dataset) {
|
|
32231
|
+
const brokenReferences = [];
|
|
32232
|
+
const projectSnapshots = new Map(
|
|
32233
|
+
dataset["project-snapshots"].map((snapshot) => [snapshot.id, snapshot])
|
|
32234
|
+
);
|
|
32235
|
+
const projectTransactions = new Set(
|
|
32236
|
+
dataset["project-transactions"].map((transaction) => transaction.id)
|
|
32237
|
+
);
|
|
32238
|
+
const reachableProjectSnapshots = walkAncestors({
|
|
32239
|
+
roots: dataset["project-heads"].map((head) => ({
|
|
32240
|
+
id: head.snapshotId,
|
|
32241
|
+
source: `project head "${head.id}"`
|
|
32242
|
+
})),
|
|
32243
|
+
rows: projectSnapshots,
|
|
32244
|
+
baseId: (snapshot) => snapshot.baseSnapshotId,
|
|
32245
|
+
kind: "project snapshot",
|
|
32246
|
+
brokenReferences
|
|
32247
|
+
});
|
|
32248
|
+
for (const snapshot of projectSnapshots.values()) {
|
|
32249
|
+
if (!projectTransactions.has(snapshot.transactionId)) {
|
|
32250
|
+
brokenReferences.push(
|
|
32251
|
+
`Project snapshot "${snapshot.id}" references missing transaction "${snapshot.transactionId}".`
|
|
32252
|
+
);
|
|
32253
|
+
}
|
|
32254
|
+
}
|
|
32255
|
+
const reachableProjectTransactions = new Set(
|
|
32256
|
+
[...reachableProjectSnapshots].map((id) => projectSnapshots.get(id)?.transactionId).filter(
|
|
32257
|
+
(id) => id !== void 0 && projectTransactions.has(id)
|
|
32258
|
+
)
|
|
32259
|
+
);
|
|
32260
|
+
const wikiSnapshots = new Map(
|
|
32261
|
+
dataset["wiki-snapshots"].map((snapshot) => [snapshot.id, snapshot])
|
|
32262
|
+
);
|
|
32263
|
+
const wikiTransactions = new Set(
|
|
32264
|
+
dataset["wiki-transactions"].map((transaction) => transaction.id)
|
|
32265
|
+
);
|
|
32266
|
+
const wikiDocuments = new Map(
|
|
32267
|
+
dataset["wiki-documents"].map((document) => [document.id, document])
|
|
32268
|
+
);
|
|
32269
|
+
const reachableWikiSnapshots = walkAncestors({
|
|
32270
|
+
roots: dataset["wiki-heads"].map((head) => ({
|
|
32271
|
+
id: head.snapshotId,
|
|
32272
|
+
source: `wiki head "${head.id}"`
|
|
32273
|
+
})),
|
|
32274
|
+
rows: wikiSnapshots,
|
|
32275
|
+
baseId: (snapshot) => snapshot.baseSnapshotId,
|
|
32276
|
+
kind: "wiki snapshot",
|
|
32277
|
+
brokenReferences
|
|
32278
|
+
});
|
|
32279
|
+
const reachableWikiDocuments = walkAncestors({
|
|
32280
|
+
roots: [...reachableWikiSnapshots].flatMap((snapshotId) => {
|
|
32281
|
+
const snapshot = wikiSnapshots.get(snapshotId);
|
|
32282
|
+
return snapshot === void 0 ? [] : [
|
|
32283
|
+
{
|
|
32284
|
+
id: snapshot.yjsDocumentId,
|
|
32285
|
+
source: `wiki snapshot "${snapshot.id}"`
|
|
32286
|
+
}
|
|
32287
|
+
];
|
|
32288
|
+
}),
|
|
32289
|
+
rows: wikiDocuments,
|
|
32290
|
+
baseId: (document) => document.forkedFromDocumentId,
|
|
32291
|
+
kind: "wiki document",
|
|
32292
|
+
brokenReferences
|
|
32293
|
+
});
|
|
32294
|
+
const reachableWikiTransactions = /* @__PURE__ */ new Set();
|
|
32295
|
+
for (const snapshotId of reachableWikiSnapshots) {
|
|
32296
|
+
const snapshot = wikiSnapshots.get(snapshotId);
|
|
32297
|
+
if (snapshot !== void 0 && wikiTransactions.has(snapshot.transactionId)) {
|
|
32298
|
+
reachableWikiTransactions.add(snapshot.transactionId);
|
|
32299
|
+
}
|
|
32300
|
+
}
|
|
32301
|
+
for (const snapshot of wikiSnapshots.values()) {
|
|
32302
|
+
if (!wikiTransactions.has(snapshot.transactionId)) {
|
|
32303
|
+
brokenReferences.push(
|
|
32304
|
+
`Wiki snapshot "${snapshot.id}" references missing transaction "${snapshot.transactionId}".`
|
|
32305
|
+
);
|
|
32306
|
+
}
|
|
32307
|
+
if (!wikiDocuments.has(snapshot.yjsDocumentId)) {
|
|
32308
|
+
brokenReferences.push(
|
|
32309
|
+
`Wiki snapshot "${snapshot.id}" references missing document "${snapshot.yjsDocumentId}".`
|
|
32310
|
+
);
|
|
32311
|
+
}
|
|
32312
|
+
}
|
|
32313
|
+
let unreachableWikiRevisions = 0;
|
|
32314
|
+
for (const revision of dataset["wiki-revisions"]) {
|
|
32315
|
+
const documentExists = wikiDocuments.has(revision.yjsDocumentId);
|
|
32316
|
+
const transactionExists = wikiTransactions.has(revision.transactionId);
|
|
32317
|
+
if (!documentExists) {
|
|
32318
|
+
brokenReferences.push(
|
|
32319
|
+
`Wiki revision "${revision.id}" references missing document "${revision.yjsDocumentId}".`
|
|
32320
|
+
);
|
|
32321
|
+
}
|
|
32322
|
+
if (!transactionExists) {
|
|
32323
|
+
brokenReferences.push(
|
|
32324
|
+
`Wiki revision "${revision.id}" references missing transaction "${revision.transactionId}".`
|
|
32325
|
+
);
|
|
32326
|
+
}
|
|
32327
|
+
if (transactionExists && reachableWikiDocuments.has(revision.yjsDocumentId)) {
|
|
32328
|
+
reachableWikiTransactions.add(revision.transactionId);
|
|
32329
|
+
} else {
|
|
32330
|
+
unreachableWikiRevisions += 1;
|
|
32331
|
+
}
|
|
32332
|
+
}
|
|
32333
|
+
return {
|
|
32334
|
+
versions: {
|
|
32335
|
+
total: dataset.versions.length,
|
|
32336
|
+
archived: dataset.versions.filter((version) => version.archived).length
|
|
32337
|
+
},
|
|
32338
|
+
projectRecords: {
|
|
32339
|
+
heads: dataset["project-heads"].length,
|
|
32340
|
+
snapshots: projectSnapshots.size,
|
|
32341
|
+
transactions: projectTransactions.size,
|
|
32342
|
+
unreachableSnapshots: projectSnapshots.size - reachableProjectSnapshots.size,
|
|
32343
|
+
unreachableTransactions: projectTransactions.size - reachableProjectTransactions.size,
|
|
32344
|
+
duplicateSemanticSnapshots: duplicateCount(
|
|
32345
|
+
dataset["project-snapshots"],
|
|
32346
|
+
(snapshot) => `${snapshot.recordKind}:${snapshot.recordId}:${snapshot.contentHash}`
|
|
32347
|
+
)
|
|
32348
|
+
},
|
|
32349
|
+
wiki: {
|
|
32350
|
+
heads: dataset["wiki-heads"].length,
|
|
32351
|
+
snapshots: wikiSnapshots.size,
|
|
32352
|
+
transactions: wikiTransactions.size,
|
|
32353
|
+
documents: wikiDocuments.size,
|
|
32354
|
+
revisions: dataset["wiki-revisions"].length,
|
|
32355
|
+
unreachableSnapshots: wikiSnapshots.size - reachableWikiSnapshots.size,
|
|
32356
|
+
unreachableTransactions: wikiTransactions.size - reachableWikiTransactions.size,
|
|
32357
|
+
unreachableDocuments: wikiDocuments.size - reachableWikiDocuments.size,
|
|
32358
|
+
unreachableRevisions: unreachableWikiRevisions,
|
|
32359
|
+
duplicateSemanticSnapshots: duplicateCount(
|
|
32360
|
+
dataset["wiki-snapshots"],
|
|
32361
|
+
(snapshot) => `${snapshot.pageId}:${snapshot.contentHash}`
|
|
32362
|
+
),
|
|
32363
|
+
duplicateSemanticDocuments: duplicateCount(
|
|
32364
|
+
dataset["wiki-documents"],
|
|
32365
|
+
(document) => `${document.pageId}:${document.contentHash}`
|
|
32366
|
+
)
|
|
32367
|
+
},
|
|
32368
|
+
brokenReferences
|
|
32369
|
+
};
|
|
32370
|
+
}
|
|
32371
|
+
function walkAncestors(args) {
|
|
32372
|
+
const reachable = /* @__PURE__ */ new Set();
|
|
32373
|
+
const stack = [...args.roots];
|
|
32374
|
+
while (stack.length > 0) {
|
|
32375
|
+
const current = stack.pop();
|
|
32376
|
+
if (reachable.has(current.id)) continue;
|
|
32377
|
+
const row = args.rows.get(current.id);
|
|
32378
|
+
if (row === void 0) {
|
|
32379
|
+
args.brokenReferences.push(
|
|
32380
|
+
`${current.source} references missing ${args.kind} "${current.id}".`
|
|
32381
|
+
);
|
|
32382
|
+
continue;
|
|
32383
|
+
}
|
|
32384
|
+
reachable.add(current.id);
|
|
32385
|
+
const baseId = args.baseId(row);
|
|
32386
|
+
if (baseId !== null) {
|
|
32387
|
+
stack.push({
|
|
32388
|
+
id: baseId,
|
|
32389
|
+
source: `${args.kind} "${current.id}"`
|
|
32390
|
+
});
|
|
32391
|
+
}
|
|
32392
|
+
}
|
|
32393
|
+
return reachable;
|
|
32394
|
+
}
|
|
32395
|
+
function duplicateCount(rows, key) {
|
|
32396
|
+
const counts = /* @__PURE__ */ new Map();
|
|
32397
|
+
for (const row of rows) {
|
|
32398
|
+
const value = key(row);
|
|
32399
|
+
counts.set(value, (counts.get(value) ?? 0) + 1);
|
|
32400
|
+
}
|
|
32401
|
+
let duplicates = 0;
|
|
32402
|
+
for (const count of counts.values()) {
|
|
32403
|
+
if (count > 1) duplicates += count - 1;
|
|
32404
|
+
}
|
|
32405
|
+
return duplicates;
|
|
32406
|
+
}
|
|
32407
|
+
var HISTORY_INSPECT_PHASES;
|
|
32408
|
+
var init_history_inspect = __esm({
|
|
32409
|
+
"src/commands/history-inspect.ts"() {
|
|
32410
|
+
"use strict";
|
|
32411
|
+
HISTORY_INSPECT_PHASES = [
|
|
32412
|
+
"versions",
|
|
32413
|
+
"project-heads",
|
|
32414
|
+
"project-snapshots",
|
|
32415
|
+
"project-transactions",
|
|
32416
|
+
"wiki-heads",
|
|
32417
|
+
"wiki-snapshots",
|
|
32418
|
+
"wiki-transactions",
|
|
32419
|
+
"wiki-documents",
|
|
32420
|
+
"wiki-revisions"
|
|
32421
|
+
];
|
|
32422
|
+
}
|
|
32423
|
+
});
|
|
32424
|
+
|
|
32425
|
+
// src/commands/history.ts
|
|
32426
|
+
var history_exports = {};
|
|
32427
|
+
__export(history_exports, {
|
|
32428
|
+
resolveHistoryScopeFromServer: () => resolveHistoryScopeFromServer,
|
|
32429
|
+
runHistory: () => runHistory
|
|
32430
|
+
});
|
|
32431
|
+
async function resolveHistoryScopeFromServer(options) {
|
|
32432
|
+
const convex = await createConvexClientForApi(options.apiBaseUrl);
|
|
32433
|
+
return await resolveHistoryScope({
|
|
32434
|
+
command: options.command,
|
|
32435
|
+
projectSelector: options.projectSelector,
|
|
32436
|
+
versionSelector: options.versionSelector,
|
|
32437
|
+
requiresVersion: options.command === "log" || options.command === "flatten",
|
|
32438
|
+
resolveProject: async (selector) => await convex.query(api2.projectHistory.resolveProjectSelector, {
|
|
32439
|
+
selector
|
|
32440
|
+
}),
|
|
32441
|
+
resolveVersion: async (projectId, selector) => await convex.query(api2.projectHistory.resolveVersionSelector, {
|
|
32442
|
+
projectId,
|
|
32443
|
+
selector
|
|
32444
|
+
})
|
|
32445
|
+
});
|
|
32446
|
+
}
|
|
32447
|
+
async function runHistory(options) {
|
|
32448
|
+
assertHistoryOptions(options);
|
|
32449
|
+
const convex = await createConvexClientForApi(options.apiBaseUrl);
|
|
32450
|
+
if (options.command === "inspect") {
|
|
32451
|
+
const scope = await resolveScopeWithClient(convex, options);
|
|
32452
|
+
await printHistoryInspection(convex, scope.project.id);
|
|
32453
|
+
return;
|
|
32454
|
+
}
|
|
32455
|
+
if (options.command === "log") {
|
|
32456
|
+
const scope = await resolveScopeWithClient(convex, options);
|
|
32457
|
+
await printVersionLog(convex, scope);
|
|
32458
|
+
return;
|
|
32459
|
+
}
|
|
32460
|
+
if (options.command === "flatten") {
|
|
32461
|
+
const scope = await resolveScopeWithClient(convex, options);
|
|
32462
|
+
if (scope.version === null) {
|
|
32463
|
+
throw new Error("Flatten requires a resolved version.");
|
|
32464
|
+
}
|
|
32465
|
+
const started = await convex.mutation(api2.projectHistory.planFlatten, {
|
|
32466
|
+
projectId: scope.project.id,
|
|
32467
|
+
versionId: scope.version.id
|
|
32468
|
+
});
|
|
32469
|
+
console.log(`${sym.info} Plan: ${started.planId}`);
|
|
32470
|
+
console.log(`${sym.info} Planning run: ${started.runId}`);
|
|
32471
|
+
const plan = await waitForPlan(convex, started.planId);
|
|
32472
|
+
printPlan(plan);
|
|
32473
|
+
return;
|
|
32474
|
+
}
|
|
32475
|
+
if (options.command === "prune") {
|
|
32476
|
+
const scope = await resolveScopeWithClient(convex, options);
|
|
32477
|
+
const started = await convex.mutation(api2.projectHistory.planPrune, {
|
|
32478
|
+
projectId: scope.project.id
|
|
32479
|
+
});
|
|
32480
|
+
console.log(`${sym.info} Plan: ${started.planId}`);
|
|
32481
|
+
console.log(`${sym.info} Planning run: ${started.runId}`);
|
|
32482
|
+
const plan = await waitForPlan(convex, started.planId);
|
|
32483
|
+
printPlan(plan);
|
|
32484
|
+
return;
|
|
32485
|
+
}
|
|
32486
|
+
if (options.command === "apply") {
|
|
32487
|
+
const plan = await convex.query(api2.projectHistory.getPlan, {
|
|
32488
|
+
planId: options.planId
|
|
32489
|
+
});
|
|
32490
|
+
const expectedScope = plan.versionId === null ? plan.projectId : `${plan.projectId}/${plan.versionId}`;
|
|
32491
|
+
let confirmScope = options.confirmScope;
|
|
32492
|
+
if (plan.productionConfirmationRequired && confirmScope === null) {
|
|
32493
|
+
if (!isInteractive()) {
|
|
32494
|
+
throw new Error(
|
|
32495
|
+
`Production history apply requires --confirm-scope ${expectedScope}.`
|
|
32496
|
+
);
|
|
32497
|
+
}
|
|
32498
|
+
confirmScope = await promptInput({
|
|
32499
|
+
message: `Type ${expectedScope} to confirm production history rewrite`,
|
|
32500
|
+
nonInteractiveHint: `Pass --confirm-scope ${expectedScope}.`,
|
|
32501
|
+
validate: (value) => value === expectedScope ? true : `Confirmation must exactly match ${expectedScope}.`
|
|
32502
|
+
});
|
|
32503
|
+
}
|
|
32504
|
+
const started = await convex.mutation(api2.projectHistory.applyPlan, {
|
|
32505
|
+
planId: plan.id,
|
|
32506
|
+
confirmScope
|
|
32507
|
+
});
|
|
32508
|
+
console.log(`${sym.info} Run: ${started.runId}`);
|
|
32509
|
+
console.log(`${sym.info} State: ${started.state}`);
|
|
32510
|
+
return;
|
|
32511
|
+
}
|
|
32512
|
+
if (options.command === "status") {
|
|
32513
|
+
const status = await convex.query(api2.projectHistory.getRunStatus, {
|
|
32514
|
+
runId: options.runId
|
|
32515
|
+
});
|
|
32516
|
+
printRunStatus(status);
|
|
32517
|
+
return;
|
|
32518
|
+
}
|
|
32519
|
+
if (options.command === "resume") {
|
|
32520
|
+
const resumed = await convex.mutation(api2.projectHistory.resumeRun, {
|
|
32521
|
+
runId: options.runId
|
|
32522
|
+
});
|
|
32523
|
+
console.log(`${sym.info} Run: ${resumed.runId}`);
|
|
32524
|
+
console.log(`${sym.info} State: ${resumed.state}`);
|
|
32525
|
+
return;
|
|
32526
|
+
}
|
|
32527
|
+
}
|
|
32528
|
+
async function resolveScopeWithClient(convex, options) {
|
|
32529
|
+
if (options.command !== "inspect" && options.command !== "log" && options.command !== "prune" && options.command !== "flatten") {
|
|
32530
|
+
throw new Error(`neo history ${options.command} has immutable scope.`);
|
|
32531
|
+
}
|
|
32532
|
+
return await resolveHistoryScope({
|
|
32533
|
+
command: options.command,
|
|
32534
|
+
projectSelector: options.projectSelector,
|
|
32535
|
+
versionSelector: options.versionSelector,
|
|
32536
|
+
requiresVersion: options.command === "log" || options.command === "flatten",
|
|
32537
|
+
resolveProject: async (selector) => await convex.query(api2.projectHistory.resolveProjectSelector, {
|
|
32538
|
+
selector
|
|
32539
|
+
}),
|
|
32540
|
+
resolveVersion: async (projectId, selector) => await convex.query(api2.projectHistory.resolveVersionSelector, {
|
|
32541
|
+
projectId,
|
|
32542
|
+
selector
|
|
32543
|
+
})
|
|
32544
|
+
});
|
|
32545
|
+
}
|
|
32546
|
+
async function waitForPlan(convex, planId) {
|
|
32547
|
+
let lastState = null;
|
|
32548
|
+
while (true) {
|
|
32549
|
+
const plan = await convex.query(api2.projectHistory.getPlan, { planId });
|
|
32550
|
+
if (plan.state !== lastState) {
|
|
32551
|
+
console.log(`${sym.info} Plan state: ${plan.state}`);
|
|
32552
|
+
lastState = plan.state;
|
|
32553
|
+
}
|
|
32554
|
+
if (plan.state === "sealed") return plan;
|
|
32555
|
+
if (plan.state === "failed") {
|
|
32556
|
+
throw new Error(
|
|
32557
|
+
`History plan "${plan.id}" failed: ${plan.error ?? "unknown error"}`
|
|
32558
|
+
);
|
|
32559
|
+
}
|
|
32560
|
+
await new Promise((resolve6) => setTimeout(resolve6, 500));
|
|
32561
|
+
}
|
|
32562
|
+
}
|
|
32563
|
+
async function printVersionLog(convex, scope) {
|
|
32564
|
+
if (scope.version === null) {
|
|
32565
|
+
throw new Error("History log requires a resolved version.");
|
|
32566
|
+
}
|
|
32567
|
+
let cursor = null;
|
|
32568
|
+
let count = 0;
|
|
32569
|
+
while (true) {
|
|
32570
|
+
const page = await convex.query(
|
|
32571
|
+
api2.projectVersionHistory.getChangelog,
|
|
32572
|
+
{
|
|
32573
|
+
projectId: scope.project.id,
|
|
32574
|
+
versionId: scope.version.id,
|
|
32575
|
+
paginationOpts: { cursor, numItems: 50 }
|
|
32576
|
+
}
|
|
32577
|
+
);
|
|
32578
|
+
if (page === null) {
|
|
32579
|
+
throw new Error(
|
|
32580
|
+
`Version "${scope.version.id}" no longer exists in project "${scope.project.id}".`
|
|
32581
|
+
);
|
|
32582
|
+
}
|
|
32583
|
+
for (const transaction of page.transactions) {
|
|
32584
|
+
count += 1;
|
|
32585
|
+
console.log(
|
|
32586
|
+
`${transaction.id} ${new Date(transaction.createdAt).toISOString()} ${transaction.operation} ${transaction.summary ?? ""}`.trimEnd()
|
|
32587
|
+
);
|
|
32588
|
+
}
|
|
32589
|
+
if (page.isDone) break;
|
|
32590
|
+
cursor = page.continueCursor;
|
|
32591
|
+
}
|
|
32592
|
+
if (count === 0) console.log("No retained history entries.");
|
|
32593
|
+
}
|
|
32594
|
+
async function printHistoryInspection(convex, projectId) {
|
|
32595
|
+
const dataset = {
|
|
32596
|
+
versions: [],
|
|
32597
|
+
"project-heads": [],
|
|
32598
|
+
"project-snapshots": [],
|
|
32599
|
+
"project-transactions": [],
|
|
32600
|
+
"wiki-heads": [],
|
|
32601
|
+
"wiki-snapshots": [],
|
|
32602
|
+
"wiki-transactions": [],
|
|
32603
|
+
"wiki-documents": [],
|
|
32604
|
+
"wiki-revisions": []
|
|
32605
|
+
};
|
|
32606
|
+
for (const phase of HISTORY_INSPECT_PHASES) {
|
|
32607
|
+
let cursor = null;
|
|
32608
|
+
while (true) {
|
|
32609
|
+
const page = await convex.query(api2.projectHistory.inspectPage, {
|
|
32610
|
+
projectId,
|
|
32611
|
+
phase,
|
|
32612
|
+
paginationOpts: { cursor, numItems: 100 }
|
|
32613
|
+
});
|
|
32614
|
+
if (page.phase !== phase) {
|
|
32615
|
+
throw new Error(
|
|
32616
|
+
`History inspection requested phase "${phase}" but received "${page.phase}".`
|
|
32617
|
+
);
|
|
32618
|
+
}
|
|
32619
|
+
dataset[phase].push(...page.rows);
|
|
32620
|
+
if (page.isDone) break;
|
|
32621
|
+
cursor = page.continueCursor;
|
|
32622
|
+
}
|
|
32623
|
+
}
|
|
32624
|
+
const report = inspectHistoryDataset(dataset);
|
|
32625
|
+
console.log(
|
|
32626
|
+
`${sym.info} Versions: ${report.versions.total} total, ${report.versions.archived} archived`
|
|
32627
|
+
);
|
|
32628
|
+
console.log(
|
|
32629
|
+
`${sym.info} Project records: ${report.projectRecords.heads} heads, ${report.projectRecords.snapshots} snapshots, ${report.projectRecords.transactions} transactions`
|
|
32630
|
+
);
|
|
32631
|
+
console.log(
|
|
32632
|
+
`${sym.info} Project cleanup candidates: ${report.projectRecords.unreachableSnapshots} unreachable snapshots, ${report.projectRecords.unreachableTransactions} unreachable transactions, ${report.projectRecords.duplicateSemanticSnapshots} duplicate semantic snapshots`
|
|
32633
|
+
);
|
|
32634
|
+
console.log(
|
|
32635
|
+
`${sym.info} Wiki: ${report.wiki.heads} heads, ${report.wiki.snapshots} snapshots, ${report.wiki.transactions} transactions, ${report.wiki.documents} documents, ${report.wiki.revisions} revisions`
|
|
32636
|
+
);
|
|
32637
|
+
console.log(
|
|
32638
|
+
`${sym.info} Wiki cleanup candidates: ${report.wiki.unreachableSnapshots} unreachable snapshots, ${report.wiki.unreachableTransactions} unreachable transactions, ${report.wiki.unreachableDocuments} unreachable documents, ${report.wiki.unreachableRevisions} unreachable revisions`
|
|
32639
|
+
);
|
|
32640
|
+
console.log(
|
|
32641
|
+
`${sym.info} Wiki duplicates: ${report.wiki.duplicateSemanticSnapshots} semantic snapshots, ${report.wiki.duplicateSemanticDocuments} semantic documents`
|
|
32642
|
+
);
|
|
32643
|
+
console.log(
|
|
32644
|
+
`${sym.info} Broken references: ${report.brokenReferences.length}`
|
|
32645
|
+
);
|
|
32646
|
+
for (const broken of report.brokenReferences.slice(0, 20)) {
|
|
32647
|
+
console.log(` ${broken}`);
|
|
32648
|
+
}
|
|
32649
|
+
if (report.brokenReferences.length > 20) {
|
|
32650
|
+
console.log(
|
|
32651
|
+
` ... ${report.brokenReferences.length - 20} additional broken references`
|
|
32652
|
+
);
|
|
32653
|
+
}
|
|
32654
|
+
}
|
|
32655
|
+
function printPlan(plan) {
|
|
32656
|
+
const scope = plan.versionId === null ? plan.projectId : `${plan.projectId}/${plan.versionId}`;
|
|
32657
|
+
console.log(`${sym.info} Scope: ${scope}`);
|
|
32658
|
+
console.log(`${sym.info} Mode: ${plan.mode}`);
|
|
32659
|
+
console.log(
|
|
32660
|
+
`${sym.info} Project records: ${plan.counts.removableSnapshots} snapshots and ${plan.counts.removableTransactions} transactions removable`
|
|
32661
|
+
);
|
|
32662
|
+
if (plan.wikiCounts !== null) {
|
|
32663
|
+
console.log(
|
|
32664
|
+
`${sym.info} Wiki: ${plan.wikiCounts.removableSnapshots} snapshots, ${plan.wikiCounts.removableTransactions} transactions, and ${plan.wikiCounts.removableDocuments} documents removable`
|
|
32665
|
+
);
|
|
32666
|
+
}
|
|
32667
|
+
console.log(
|
|
32668
|
+
`${sym.info} Current baseline: project=${String(plan.compactionCurrent)} wiki=${String(plan.wikiCompactionCurrent)}`
|
|
32669
|
+
);
|
|
32670
|
+
console.log(`Apply with: neo history apply --plan ${plan.id}`);
|
|
32671
|
+
}
|
|
32672
|
+
function printRunStatus(status) {
|
|
32673
|
+
const scope = status.versionId === null ? status.projectId : `${status.projectId}/${status.versionId}`;
|
|
32674
|
+
console.log(`${sym.info} Run: ${status.runId}`);
|
|
32675
|
+
console.log(`${sym.info} Plan: ${status.planId}`);
|
|
32676
|
+
console.log(`${sym.info} Scope: ${scope}`);
|
|
32677
|
+
console.log(`${sym.info} State: ${status.state} (${status.phase})`);
|
|
32678
|
+
console.log(
|
|
32679
|
+
`${sym.info} Correctness verification: ${status.verified ? "verified" : "pending"}`
|
|
32680
|
+
);
|
|
32681
|
+
if (status.lastError !== null) {
|
|
32682
|
+
console.log(`${sym.info} Error: ${status.lastError}`);
|
|
32683
|
+
}
|
|
32684
|
+
}
|
|
32685
|
+
function assertHistoryOptions(options) {
|
|
32686
|
+
const planningOrRead = options.command === "inspect" || options.command === "log" || options.command === "prune" || options.command === "flatten";
|
|
32687
|
+
if (planningOrRead) {
|
|
32688
|
+
if (options.planId !== null || options.runId !== null) {
|
|
32689
|
+
throw new Error(
|
|
32690
|
+
`neo history ${options.command} does not accept --plan or --run.`
|
|
32691
|
+
);
|
|
32692
|
+
}
|
|
32693
|
+
if (options.confirmScope !== null) {
|
|
32694
|
+
throw new Error(
|
|
32695
|
+
`neo history ${options.command} does not accept --confirm-scope.`
|
|
32696
|
+
);
|
|
32697
|
+
}
|
|
32698
|
+
if ((options.command === "inspect" || options.command === "prune") && options.versionSelector !== null) {
|
|
32699
|
+
throw new Error(
|
|
32700
|
+
`neo history ${options.command} is project-scoped and does not accept --version.`
|
|
32701
|
+
);
|
|
32702
|
+
}
|
|
32703
|
+
return;
|
|
32704
|
+
}
|
|
32705
|
+
if (options.command === "apply") {
|
|
32706
|
+
if (options.planId === null) {
|
|
32707
|
+
throw new Error("neo history apply requires --plan <plan-id>.");
|
|
32708
|
+
}
|
|
32709
|
+
if (options.runId !== null) {
|
|
32710
|
+
throw new Error("neo history apply does not accept --run.");
|
|
32711
|
+
}
|
|
32712
|
+
return;
|
|
32713
|
+
}
|
|
32714
|
+
if (options.runId === null) {
|
|
32715
|
+
throw new Error(`neo history ${options.command} requires --run <run-id>.`);
|
|
32716
|
+
}
|
|
32717
|
+
if (options.planId !== null || options.confirmScope !== null) {
|
|
32718
|
+
throw new Error(
|
|
32719
|
+
`neo history ${options.command} does not accept --plan or --confirm-scope.`
|
|
32720
|
+
);
|
|
32721
|
+
}
|
|
32722
|
+
}
|
|
32723
|
+
var init_history = __esm({
|
|
32724
|
+
"src/commands/history.ts"() {
|
|
32725
|
+
"use strict";
|
|
32726
|
+
init_convex();
|
|
32727
|
+
init_ui();
|
|
32728
|
+
init_history_selection();
|
|
32729
|
+
init_history_inspect();
|
|
32730
|
+
}
|
|
32731
|
+
});
|
|
32732
|
+
|
|
32165
32733
|
// src/commands/doctor.ts
|
|
32166
32734
|
var doctor_exports = {};
|
|
32167
32735
|
__export(doctor_exports, {
|
|
@@ -44574,16 +45142,19 @@ var KNOWN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
44574
45142
|
"out",
|
|
44575
45143
|
"profile",
|
|
44576
45144
|
"project",
|
|
45145
|
+
"plan",
|
|
44577
45146
|
"push",
|
|
44578
45147
|
"replace",
|
|
44579
45148
|
"reset",
|
|
44580
45149
|
"returns",
|
|
45150
|
+
"run",
|
|
44581
45151
|
"save",
|
|
44582
45152
|
"save-project",
|
|
44583
45153
|
"server",
|
|
44584
45154
|
"skip-invalid",
|
|
44585
45155
|
"status",
|
|
44586
45156
|
"summary",
|
|
45157
|
+
"confirm-scope",
|
|
44587
45158
|
"target",
|
|
44588
45159
|
"template",
|
|
44589
45160
|
"theirs",
|
|
@@ -44681,6 +45252,7 @@ ${h("Branches & releases")}
|
|
|
44681
45252
|
merge ${d("[source] [--dry-run] [--migrate] [--mine|--theirs <kind>:<id>,...]")}
|
|
44682
45253
|
release ${d("cut [--bump ...] | publish <ref> | archive <ref> | restore <ref>")}
|
|
44683
45254
|
channel ${d("list | create | edit | delete")}
|
|
45255
|
+
history ${d("inspect | log | prune | flatten | apply | status | resume")}
|
|
44684
45256
|
|
|
44685
45257
|
${h("Content & scripts")}
|
|
44686
45258
|
values ${d("list | get | set | create | delete | ...")} loc ${d("locales | list | set | archive | restore | ...")}
|
|
@@ -44748,6 +45320,46 @@ ${h("Usage")}
|
|
|
44748
45320
|
Neo checks Unity 6000's existing runtime first, then a compatible system
|
|
44749
45321
|
dotnet. It never downloads or installs a runtime. Set NEO_DOTNET_HOST to use
|
|
44750
45322
|
an explicit host.
|
|
45323
|
+
`;
|
|
45324
|
+
}
|
|
45325
|
+
if (command === "history") {
|
|
45326
|
+
return `${h("neo history")} \u2014 inspect and deliberately rewrite project history
|
|
45327
|
+
|
|
45328
|
+
${h("Usage")}
|
|
45329
|
+
neo history inspect ${d("[--project <id|@latest|@current>]")}
|
|
45330
|
+
neo history log ${d("[--project <id|@latest|@current>] [--version <id|@latest|@current>]")}
|
|
45331
|
+
neo history prune ${d("[--project <id|@latest|@current>]")}
|
|
45332
|
+
neo history flatten ${d("[--project <id|@latest|@current>] [--version <id|@latest|@current>]")}
|
|
45333
|
+
neo history apply --plan <plan-id> ${d("[--confirm-scope <project-id>[/<version-id>]]")}
|
|
45334
|
+
neo history status --run <run-id>
|
|
45335
|
+
neo history resume --run <run-id>
|
|
45336
|
+
|
|
45337
|
+
${h("Commands")}
|
|
45338
|
+
inspect ${d("Read-only project history audit; creates no plan.")}
|
|
45339
|
+
log ${d("Show retained history for exactly one version.")}
|
|
45340
|
+
prune ${d("Create an immutable plan for rows proven unreachable; writes nothing.")}
|
|
45341
|
+
flatten ${d("Create an immutable one-version baseline plan; writes nothing.")}
|
|
45342
|
+
apply ${d("Begin the destructive execution of an immutable plan.")}
|
|
45343
|
+
status ${d("Report execution phase, result, and correctness verification.")}
|
|
45344
|
+
resume ${d("Restart a stopped retryable execution.")}
|
|
45345
|
+
|
|
45346
|
+
${h("Arguments")}
|
|
45347
|
+
--project <id|@latest|@current>
|
|
45348
|
+
${d("Stable project ID, or latest authorized project. Blank interactive input uses latest.")}
|
|
45349
|
+
--version <id|@latest|@current>
|
|
45350
|
+
${d("Stable version ID in the resolved project, or latest unarchived version. Blank interactive input uses latest.")}
|
|
45351
|
+
--plan <id>
|
|
45352
|
+
${d("Immutable plan returned by prune or flatten.")}
|
|
45353
|
+
--run <id>
|
|
45354
|
+
${d("Execution returned by apply.")}
|
|
45355
|
+
--confirm-scope <project>[/<version>]
|
|
45356
|
+
${d("Exact resolved production scope acknowledgement.")}
|
|
45357
|
+
|
|
45358
|
+
${h("Selection")}
|
|
45359
|
+
${d("Project resolution always happens before version resolution. @latest and @current are aliases. apply, status, and resume use immutable plan/run scope and do not accept project or version selectors.")}
|
|
45360
|
+
|
|
45361
|
+
There is no --dry-run: prune and flatten are always non-mutating until apply.
|
|
45362
|
+
There is no --keep-current: preserving current semantic state defines flatten.
|
|
44751
45363
|
`;
|
|
44752
45364
|
}
|
|
44753
45365
|
return usage();
|
|
@@ -44788,6 +45400,30 @@ async function main() {
|
|
|
44788
45400
|
});
|
|
44789
45401
|
}
|
|
44790
45402
|
return;
|
|
45403
|
+
case "history": {
|
|
45404
|
+
const sub = args.positional[0];
|
|
45405
|
+
if (sub !== "inspect" && sub !== "log" && sub !== "prune" && sub !== "flatten" && sub !== "apply" && sub !== "status" && sub !== "resume") {
|
|
45406
|
+
throw new Error(
|
|
45407
|
+
"neo history requires a subcommand: inspect | log | prune | flatten | apply | status | resume."
|
|
45408
|
+
);
|
|
45409
|
+
}
|
|
45410
|
+
if ((sub === "apply" || sub === "status" || sub === "resume") && (stringFlag(args, "project") !== null || stringFlag(args, "version") !== null)) {
|
|
45411
|
+
throw new Error(
|
|
45412
|
+
`neo history ${sub} derives its immutable scope from the plan or run and does not accept --project or --version.`
|
|
45413
|
+
);
|
|
45414
|
+
}
|
|
45415
|
+
const { runHistory: runHistory2 } = await Promise.resolve().then(() => (init_history(), history_exports));
|
|
45416
|
+
await runHistory2({
|
|
45417
|
+
apiBaseUrl,
|
|
45418
|
+
command: sub,
|
|
45419
|
+
projectSelector: stringFlag(args, "project"),
|
|
45420
|
+
versionSelector: stringFlag(args, "version"),
|
|
45421
|
+
planId: stringFlag(args, "plan"),
|
|
45422
|
+
runId: stringFlag(args, "run"),
|
|
45423
|
+
confirmScope: stringFlag(args, "confirm-scope")
|
|
45424
|
+
});
|
|
45425
|
+
return;
|
|
45426
|
+
}
|
|
44791
45427
|
case "pull": {
|
|
44792
45428
|
const workspace = loadWorkspaceForCommand(args);
|
|
44793
45429
|
const { runPull: runPull2 } = await Promise.resolve().then(() => (init_pull(), pull_exports));
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|