@lotics/cli 0.86.1 → 0.86.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +2 -2
- package/dist/client.d.ts +22 -1
- package/dist/client.js +6 -0
- package/dist/package_commands.d.ts +15 -8
- package/dist/package_commands.js +111 -22
- package/dist/package_commands.test.js +131 -25
- package/dist/src/cli.js +91 -9
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -127,7 +127,7 @@ COMMANDS
|
|
|
127
127
|
<alias>=apply|keep|archive|recreate|unbind); an APP package
|
|
128
128
|
FLEET-upgrades every installation across your org (clean
|
|
129
129
|
apply, findings skip). --apply-all accepts the package's version
|
|
130
|
-
lotics package doctor [app_id]
|
|
130
|
+
lotics package doctor [app_id|package_id] Installation health: version pin vs latest, binding
|
|
131
131
|
drift, locally modified core, knowledge drift/edits
|
|
132
132
|
(exit 1 on findings)
|
|
133
133
|
lotics package config <app_id> [--set key=value ...]
|
|
@@ -717,7 +717,7 @@ async function main() {
|
|
|
717
717
|
console.error(" lotics install <package_id> [--version N] [--bind-to <alias>=<kdc_id>] [--config key=value ...] Install a package (app or content)");
|
|
718
718
|
console.error(" lotics uninstall <app_id|package_id> [--archive-tables] [--keep-content] Uninstall — dispatched by id (app vs content)");
|
|
719
719
|
console.error(" lotics upgrade <app_id|package_id> [--version N] [--resolve <key>=... ] [--bind-to ...] [--apply-all] Upgrade — app install / content install / whole app fleet (apg_)");
|
|
720
|
-
console.error(" lotics package doctor [app_id]
|
|
720
|
+
console.error(" lotics package doctor [app_id|package_id] Health: version pin vs latest + binding/knowledge drift (apg_ = this workspace's content install)");
|
|
721
721
|
console.error(" lotics package config <app_id> [--set key=value ...] Show or edit an installation's config knobs");
|
|
722
722
|
console.error(" lotics package eject <app_id> Sever an installation's package link");
|
|
723
723
|
console.error(" lotics package show <package_id> Registry metadata + version history (channel, yank, changelog)");
|
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { KnowledgeUpgradeEntry, ModifiedArtifact, PackageContentBinding, UpgradeResolutions } from "@lotics/shared/schemas/packages";
|
|
1
|
+
import type { ContractConfigEntry, KnowledgeUpgradeEntry, ModifiedArtifact, PackageContentBinding, UpgradeResolutions } from "@lotics/shared/schemas/packages";
|
|
2
2
|
/**
|
|
3
3
|
* The error message for a non-ok response. A genuine JSON error (a 4xx carrying
|
|
4
4
|
* a `message`) surfaces verbatim; a non-JSON body (a gateway HTML page), any
|
|
@@ -385,6 +385,7 @@ export declare class LoticsClient {
|
|
|
385
385
|
breaking: number;
|
|
386
386
|
drift: number;
|
|
387
387
|
modified: number;
|
|
388
|
+
knowledge: number;
|
|
388
389
|
};
|
|
389
390
|
message?: string;
|
|
390
391
|
}>;
|
|
@@ -555,6 +556,8 @@ export declare class LoticsClient {
|
|
|
555
556
|
alias: string;
|
|
556
557
|
doc_id: string;
|
|
557
558
|
}>;
|
|
559
|
+
/** Config knob declarations re-declaring the config (from the manifest). */
|
|
560
|
+
config?: ContractConfigEntry[];
|
|
558
561
|
}): Promise<{
|
|
559
562
|
package_id: string;
|
|
560
563
|
version: number;
|
|
@@ -566,6 +569,12 @@ export declare class LoticsClient {
|
|
|
566
569
|
removed: string[];
|
|
567
570
|
changed: string[];
|
|
568
571
|
};
|
|
572
|
+
/** Absent from a pre-declaration server (deploy skew) — treat as empty delta. */
|
|
573
|
+
config?: {
|
|
574
|
+
added: string[];
|
|
575
|
+
removed: string[];
|
|
576
|
+
changed: string[];
|
|
577
|
+
};
|
|
569
578
|
findings: ExtractFinding[];
|
|
570
579
|
}>;
|
|
571
580
|
/**
|
|
@@ -585,6 +594,8 @@ export declare class LoticsClient {
|
|
|
585
594
|
alias: string;
|
|
586
595
|
doc_id: string;
|
|
587
596
|
}>;
|
|
597
|
+
/** Config knob declarations re-declaring the config (from the manifest). */
|
|
598
|
+
config?: ContractConfigEntry[];
|
|
588
599
|
}): Promise<{
|
|
589
600
|
package_id: string;
|
|
590
601
|
version: number;
|
|
@@ -596,6 +607,12 @@ export declare class LoticsClient {
|
|
|
596
607
|
removed: string[];
|
|
597
608
|
changed: string[];
|
|
598
609
|
};
|
|
610
|
+
/** Absent from a pre-declaration server (deploy skew) — treat as empty delta. */
|
|
611
|
+
config?: {
|
|
612
|
+
added: string[];
|
|
613
|
+
removed: string[];
|
|
614
|
+
changed: string[];
|
|
615
|
+
};
|
|
599
616
|
}>;
|
|
600
617
|
/**
|
|
601
618
|
* Dry-run preview of a first-release — the `GET` behind `lotics app publish`
|
|
@@ -615,6 +632,8 @@ export declare class LoticsClient {
|
|
|
615
632
|
alias: string;
|
|
616
633
|
doc_id: string;
|
|
617
634
|
}>;
|
|
635
|
+
/** Config knob declarations v1 would freeze (from the manifest's `lotics.config`). */
|
|
636
|
+
config?: ContractConfigEntry[];
|
|
618
637
|
}): Promise<{
|
|
619
638
|
app_id: string;
|
|
620
639
|
package_name: string;
|
|
@@ -649,6 +668,8 @@ export declare class LoticsClient {
|
|
|
649
668
|
alias: string;
|
|
650
669
|
doc_id: string;
|
|
651
670
|
}>;
|
|
671
|
+
/** Config knob declarations v1 freezes (from the manifest's `lotics.config`). */
|
|
672
|
+
config?: ContractConfigEntry[];
|
|
652
673
|
}): Promise<{
|
|
653
674
|
package_id: string;
|
|
654
675
|
version: number;
|
package/dist/client.js
CHANGED
|
@@ -365,6 +365,9 @@ export class LoticsClient {
|
|
|
365
365
|
if (opts.knowledge !== undefined && opts.knowledge.length > 0) {
|
|
366
366
|
params.set("knowledge", JSON.stringify(opts.knowledge));
|
|
367
367
|
}
|
|
368
|
+
if (opts.config !== undefined) {
|
|
369
|
+
params.set("config", JSON.stringify(opts.config));
|
|
370
|
+
}
|
|
368
371
|
const query = params.toString();
|
|
369
372
|
return this.request("GET", `/v1/apps/${encodeURIComponent(app_id)}/package-release${query ? `?${query}` : ""}`);
|
|
370
373
|
}
|
|
@@ -394,6 +397,9 @@ export class LoticsClient {
|
|
|
394
397
|
if (opts.knowledge !== undefined && opts.knowledge.length > 0) {
|
|
395
398
|
params.set("knowledge", JSON.stringify(opts.knowledge));
|
|
396
399
|
}
|
|
400
|
+
if (opts.config !== undefined && opts.config.length > 0) {
|
|
401
|
+
params.set("config", JSON.stringify(opts.config));
|
|
402
|
+
}
|
|
397
403
|
if (opts.renames !== undefined && opts.renames.length > 0) {
|
|
398
404
|
params.set("renames", JSON.stringify(opts.renames));
|
|
399
405
|
}
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import { LoticsClient, type ExtractFinding } from "./client.js";
|
|
2
|
-
import { type UpgradeResolutions } from "@lotics/shared/schemas/packages";
|
|
2
|
+
import { type ContractConfigEntry, type UpgradeResolutions } from "@lotics/shared/schemas/packages";
|
|
3
3
|
/**
|
|
4
4
|
* Read the local APP project's manifest (`package.json#lotics.app_id` +
|
|
5
|
-
* `lotics.knowledge`) — what `lotics app pull` writes.
|
|
6
|
-
* `release` run from a pulled app project resolve the app
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
5
|
+
* `lotics.knowledge` + `lotics.config`) — what `lotics app pull` writes.
|
|
6
|
+
* `lotics app publish` / `release` run from a pulled app project resolve the app
|
|
7
|
+
* id from it and forward the package-managed knowledge declaration AND the config
|
|
8
|
+
* knob declarations to the server — both are declarations extract cannot invert
|
|
9
|
+
* from artifacts (agents reference docs by free text; a config knob has no concrete
|
|
10
|
+
* artifact), so the author declares them. Each `lotics.config` entry is validated
|
|
11
|
+
* against the shared `contractConfigEntrySchema` so a malformed knob fails LOUDLY
|
|
12
|
+
* client-side naming the alias, never silently dropped. Returns null when the dir
|
|
13
|
+
* has no package.json. `app_id` is null when the manifest is a package/non-app
|
|
14
|
+
* project.
|
|
11
15
|
*/
|
|
12
16
|
export declare function readLocalAppManifest(projectDir: string): {
|
|
13
17
|
app_id: string | null;
|
|
@@ -15,6 +19,7 @@ export declare function readLocalAppManifest(projectDir: string): {
|
|
|
15
19
|
alias: string;
|
|
16
20
|
doc_id: string;
|
|
17
21
|
}>;
|
|
22
|
+
config: ContractConfigEntry[];
|
|
18
23
|
} | null;
|
|
19
24
|
/** Parse repeated `--rename old=new` flags into `{ from, to }[]` (first-publish alias fixes). */
|
|
20
25
|
export declare function parseRenameFlags(renames: string[]): Array<{
|
|
@@ -43,7 +48,9 @@ export declare function formatExtractReport(report: ExtractFinding[]): {
|
|
|
43
48
|
export declare function parseResolveFlags(resolve: string[]): UpgradeResolutions;
|
|
44
49
|
/**
|
|
45
50
|
* Health check: version pin vs. registry latest + binding drift. Exits
|
|
46
|
-
* non-zero when drift is found so scripts can gate on it.
|
|
51
|
+
* non-zero when drift is found so scripts can gate on it. An `apg_` PACKAGE id
|
|
52
|
+
* resolves THIS workspace's standalone content installation — the same
|
|
53
|
+
* package-id addressing `upgrade`/`uninstall` speak.
|
|
47
54
|
*/
|
|
48
55
|
export declare function packageDoctor(client: LoticsClient, args: {
|
|
49
56
|
app_id?: string;
|
package/dist/package_commands.js
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
import fs from "node:fs";
|
|
22
22
|
import path from "node:path";
|
|
23
23
|
import "./client.js";
|
|
24
|
-
import { knowledgeEntryNeedsConsent, validKnowledgeResolutions, } from "@lotics/shared/schemas/packages";
|
|
24
|
+
import { contractConfigEntrySchema, knowledgeEntryNeedsConsent, validKnowledgeResolutions, } from "@lotics/shared/schemas/packages";
|
|
25
25
|
function packageJsonPath(projectDir) {
|
|
26
26
|
return path.join(projectDir, "package.json");
|
|
27
27
|
}
|
|
@@ -30,12 +30,16 @@ function isPlainObject(value) {
|
|
|
30
30
|
}
|
|
31
31
|
/**
|
|
32
32
|
* Read the local APP project's manifest (`package.json#lotics.app_id` +
|
|
33
|
-
* `lotics.knowledge`) — what `lotics app pull` writes.
|
|
34
|
-
* `release` run from a pulled app project resolve the app
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
33
|
+
* `lotics.knowledge` + `lotics.config`) — what `lotics app pull` writes.
|
|
34
|
+
* `lotics app publish` / `release` run from a pulled app project resolve the app
|
|
35
|
+
* id from it and forward the package-managed knowledge declaration AND the config
|
|
36
|
+
* knob declarations to the server — both are declarations extract cannot invert
|
|
37
|
+
* from artifacts (agents reference docs by free text; a config knob has no concrete
|
|
38
|
+
* artifact), so the author declares them. Each `lotics.config` entry is validated
|
|
39
|
+
* against the shared `contractConfigEntrySchema` so a malformed knob fails LOUDLY
|
|
40
|
+
* client-side naming the alias, never silently dropped. Returns null when the dir
|
|
41
|
+
* has no package.json. `app_id` is null when the manifest is a package/non-app
|
|
42
|
+
* project.
|
|
39
43
|
*/
|
|
40
44
|
export function readLocalAppManifest(projectDir) {
|
|
41
45
|
const pkgPath = packageJsonPath(projectDir);
|
|
@@ -54,7 +58,19 @@ export function readLocalAppManifest(projectDir) {
|
|
|
54
58
|
}
|
|
55
59
|
}
|
|
56
60
|
}
|
|
57
|
-
|
|
61
|
+
const config = [];
|
|
62
|
+
if (Array.isArray(lotics.config)) {
|
|
63
|
+
for (const entry of lotics.config) {
|
|
64
|
+
const result = contractConfigEntrySchema.safeParse(entry);
|
|
65
|
+
if (!result.success) {
|
|
66
|
+
const alias = isPlainObject(entry) && typeof entry.alias === "string" ? entry.alias : "?";
|
|
67
|
+
throw new Error(`Invalid lotics.config knob "${alias}" in package.json: ` +
|
|
68
|
+
result.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; "));
|
|
69
|
+
}
|
|
70
|
+
config.push(result.data);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return { app_id, knowledge, config };
|
|
58
74
|
}
|
|
59
75
|
/** Parse repeated `--rename old=new` flags into `{ from, to }[]` (first-publish alias fixes). */
|
|
60
76
|
export function parseRenameFlags(renames) {
|
|
@@ -115,9 +131,15 @@ export function parseResolveFlags(resolve) {
|
|
|
115
131
|
}
|
|
116
132
|
/**
|
|
117
133
|
* Health check: version pin vs. registry latest + binding drift. Exits
|
|
118
|
-
* non-zero when drift is found so scripts can gate on it.
|
|
134
|
+
* non-zero when drift is found so scripts can gate on it. An `apg_` PACKAGE id
|
|
135
|
+
* resolves THIS workspace's standalone content installation — the same
|
|
136
|
+
* package-id addressing `upgrade`/`uninstall` speak.
|
|
119
137
|
*/
|
|
120
138
|
export async function packageDoctor(client, args) {
|
|
139
|
+
if (args.app_id !== undefined && args.app_id.startsWith("apg_")) {
|
|
140
|
+
await contentDoctor(client, args.app_id);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
121
143
|
const app_id = resolveInstallationAppId(args.app_id);
|
|
122
144
|
const health = await client.getPackageHealth(app_id);
|
|
123
145
|
console.error(`${health.package_name} — installation ${health.app_id}` +
|
|
@@ -376,6 +398,47 @@ function formatTemplateResolveHint(entry) {
|
|
|
376
398
|
: " (a local edit — revert overwrites it with the package's version; keep retains it)";
|
|
377
399
|
return ` --resolve template.${entry.alias}=revert|keep${note}`;
|
|
378
400
|
}
|
|
401
|
+
/**
|
|
402
|
+
* Health report for a STANDALONE content installation, addressed by PACKAGE id.
|
|
403
|
+
* Composed client-side from the surfaces the consumer verbs already speak: the
|
|
404
|
+
* list row (pin vs registry latest) and the upgrade preview against latest
|
|
405
|
+
* (which carries per-alias drift + local-edit detection even when the pin is
|
|
406
|
+
* current — the repair view). Exits non-zero on drift, mirroring the app
|
|
407
|
+
* doctor's gate semantics.
|
|
408
|
+
*/
|
|
409
|
+
async function contentDoctor(client, package_id) {
|
|
410
|
+
const installation = await resolveWorkspaceContentInstallation(client, package_id);
|
|
411
|
+
const preview = await client.previewContentInstallationUpgrade(installation.id, {});
|
|
412
|
+
const name = installation.package_registry?.name ?? package_id;
|
|
413
|
+
console.error(`${name} — content installation of ${package_id} (this workspace)`);
|
|
414
|
+
console.error(` Installed: v${preview.from_version} Latest: v${preview.to_version}` +
|
|
415
|
+
(preview.to_version > preview.from_version ? " → update available" : ""));
|
|
416
|
+
const drifted = preview.entries.filter((e) => e.change === "drifted");
|
|
417
|
+
if (drifted.length === 0) {
|
|
418
|
+
console.error(" Binding: healthy — every bound doc resolves.");
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
console.error(` Binding drift (${drifted.length}):`);
|
|
422
|
+
for (const entry of drifted) {
|
|
423
|
+
console.error(` - knowledge.${entry.alias} "${entry.name}" (bound doc is gone)`);
|
|
424
|
+
}
|
|
425
|
+
console.error(` Resolve while upgrading:\n lotics upgrade ${package_id}` +
|
|
426
|
+
` --resolve knowledge.<alias>=recreate|unbind (or --bind-to <alias>=<kdc_id> to re-point)`);
|
|
427
|
+
process.exitCode = 1;
|
|
428
|
+
}
|
|
429
|
+
const editedDocs = preview.entries.filter((e) => e.modified);
|
|
430
|
+
const editedTemplates = preview.templates;
|
|
431
|
+
if (editedDocs.length === 0 && editedTemplates.length === 0) {
|
|
432
|
+
console.error(" Content: pristine — no local edits an upgrade would ask about.");
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
console.error(` Local edits (${editedDocs.length + editedTemplates.length}) — each takes a keep/overwrite consent at upgrade:`);
|
|
436
|
+
for (const entry of editedDocs)
|
|
437
|
+
console.error(` - knowledge.${entry.alias} "${entry.name}"`);
|
|
438
|
+
for (const entry of editedTemplates)
|
|
439
|
+
console.error(` - template.${entry.alias}`);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
379
442
|
/**
|
|
380
443
|
* Resolve a STANDALONE content package's installation in the CURRENT workspace
|
|
381
444
|
* from its PACKAGE id. `UNIQUE (workspace_id, package_id)` means the package id
|
|
@@ -816,16 +879,17 @@ export async function appPublish(client, args) {
|
|
|
816
879
|
throw new Error("No app id. Run `lotics app publish` from a pulled app project (lotics app pull <app_id>), " +
|
|
817
880
|
"or pass one: lotics app publish <app_id>.");
|
|
818
881
|
}
|
|
819
|
-
// Forward the app's package-managed knowledge
|
|
820
|
-
// manifest is this app's own project (
|
|
821
|
-
//
|
|
822
|
-
//
|
|
882
|
+
// Forward the app's package-managed knowledge + config declarations ONLY when the
|
|
883
|
+
// local manifest is this app's own project (both are declarations extract can't
|
|
884
|
+
// invert, so the author declares them). A bare id published from elsewhere ships
|
|
885
|
+
// without them — publish from the app dir to include them.
|
|
823
886
|
const knowledge = local && local.app_id === appId ? local.knowledge : [];
|
|
887
|
+
const config = local && local.app_id === appId ? local.config : [];
|
|
824
888
|
const renames = parseRenameFlags(args.renames);
|
|
825
889
|
// Preview first (GET, no writes) — the same preview→--yes flow as `app release`,
|
|
826
890
|
// so the aliases v1 freezes forever are never a blind publish and `--rename`
|
|
827
891
|
// targets are inspectable before committing.
|
|
828
|
-
const preview = await client.previewPublishAppPackage(appId, { renames, knowledge });
|
|
892
|
+
const preview = await client.previewPublishAppPackage(appId, { renames, knowledge, config });
|
|
829
893
|
const { lines, hasError } = formatExtractReport(preview.findings);
|
|
830
894
|
console.error(`Publish preview — ${appId} as new package "${preview.package_name}" (v1):`);
|
|
831
895
|
const groups = [
|
|
@@ -847,6 +911,9 @@ export async function appPublish(client, args) {
|
|
|
847
911
|
else {
|
|
848
912
|
console.error(" No renamable aliases.");
|
|
849
913
|
}
|
|
914
|
+
if (config.length > 0) {
|
|
915
|
+
console.error(` Config knobs (${config.length}): ${config.map((c) => `${c.alias} (${c.type})`).join(", ")}`);
|
|
916
|
+
}
|
|
850
917
|
if (lines.length > 0) {
|
|
851
918
|
console.error(` Findings (${preview.findings.length}):`);
|
|
852
919
|
for (const line of lines)
|
|
@@ -870,6 +937,7 @@ export async function appPublish(client, args) {
|
|
|
870
937
|
renames,
|
|
871
938
|
changelog: args.changelog ?? null,
|
|
872
939
|
knowledge,
|
|
940
|
+
config,
|
|
873
941
|
});
|
|
874
942
|
console.error(`Published ${result.package_id} v${result.version} from app ${appId}.`);
|
|
875
943
|
console.error(` The app is now installation #1 — develop it in place, then release the next version:`);
|
|
@@ -901,11 +969,12 @@ export async function appRelease(client, args) {
|
|
|
901
969
|
if (appId === null) {
|
|
902
970
|
throw new Error("No app id. Run this from a pulled app project (lotics app pull <app_id>), or pass an app id explicitly.");
|
|
903
971
|
}
|
|
904
|
-
// Forward the manifest's knowledge
|
|
905
|
-
// AND only when
|
|
906
|
-
// (reconstruct from the pin), never "drop
|
|
972
|
+
// Forward the manifest's knowledge + config DECLARATIONS only from this app's own
|
|
973
|
+
// project AND only when non-empty — an empty/absent declaration is "no change"
|
|
974
|
+
// (reconstruct knowledge from the pin / carry config forward), never "drop them".
|
|
907
975
|
const knowledge = local && local.app_id === appId && local.knowledge.length > 0 ? local.knowledge : undefined;
|
|
908
|
-
const
|
|
976
|
+
const config = local && local.app_id === appId && local.config.length > 0 ? local.config : undefined;
|
|
977
|
+
const preview = await client.previewPackageRelease(appId, { knowledge, config });
|
|
909
978
|
const { lines, hasError } = formatExtractReport(preview.findings);
|
|
910
979
|
console.error(`Release preview — ${appId} → ${preview.package_id} v${preview.version}:`);
|
|
911
980
|
if (preview.added_aliases.length > 0) {
|
|
@@ -914,7 +983,15 @@ export async function appRelease(client, args) {
|
|
|
914
983
|
if (preview.changed_artifacts.length > 0) {
|
|
915
984
|
console.error(` Changed (${preview.changed_artifacts.length}): ${preview.changed_artifacts.join(", ")}`);
|
|
916
985
|
}
|
|
917
|
-
// Deploy-skew boundary: a pre-
|
|
986
|
+
// Deploy-skew boundary: a pre-declaration server omits the echo field. Silent
|
|
987
|
+
// skew is dangerous exactly when a declaration was SENT — the server ignored it
|
|
988
|
+
// and the preview would misread as "no changes"; say so loudly, per surface.
|
|
989
|
+
if (preview.knowledge === undefined && knowledge !== undefined) {
|
|
990
|
+
console.error(" WARNING: the server ignored the knowledge declaration (it predates bundled-knowledge releases) — the bundle will NOT change. Retry after the platform deploy completes.");
|
|
991
|
+
}
|
|
992
|
+
if (preview.config === undefined && config !== undefined) {
|
|
993
|
+
console.error(" WARNING: the server ignored the config declaration (it predates manifest config declarations) — the knobs will NOT change. Retry after the platform deploy completes.");
|
|
994
|
+
}
|
|
918
995
|
const k = preview.knowledge ?? { added: [], removed: [], changed: [] };
|
|
919
996
|
if (k.added.length > 0 || k.removed.length > 0 || k.changed.length > 0) {
|
|
920
997
|
const parts = [
|
|
@@ -924,11 +1001,23 @@ export async function appRelease(client, args) {
|
|
|
924
1001
|
].filter((p) => p !== null);
|
|
925
1002
|
console.error(` Knowledge: ${parts.join("; ")}`);
|
|
926
1003
|
}
|
|
1004
|
+
const cfg = preview.config ?? { added: [], removed: [], changed: [] };
|
|
1005
|
+
if (cfg.added.length > 0 || cfg.removed.length > 0 || cfg.changed.length > 0) {
|
|
1006
|
+
const parts = [
|
|
1007
|
+
cfg.added.length > 0 ? `+${cfg.added.join(", ")}` : null,
|
|
1008
|
+
cfg.removed.length > 0 ? `dropped ${cfg.removed.join(", ")}` : null,
|
|
1009
|
+
cfg.changed.length > 0 ? `changed ${cfg.changed.join(", ")}` : null,
|
|
1010
|
+
].filter((p) => p !== null);
|
|
1011
|
+
console.error(` Config: ${parts.join("; ")}`);
|
|
1012
|
+
}
|
|
927
1013
|
if (preview.added_aliases.length === 0 &&
|
|
928
1014
|
preview.changed_artifacts.length === 0 &&
|
|
929
1015
|
k.added.length === 0 &&
|
|
930
1016
|
k.removed.length === 0 &&
|
|
931
|
-
k.changed.length === 0
|
|
1017
|
+
k.changed.length === 0 &&
|
|
1018
|
+
cfg.added.length === 0 &&
|
|
1019
|
+
cfg.removed.length === 0 &&
|
|
1020
|
+
cfg.changed.length === 0) {
|
|
932
1021
|
console.error(" No contract changes since the current version (a fresh code/dist snapshot still ships).");
|
|
933
1022
|
}
|
|
934
1023
|
if (lines.length > 0) {
|
|
@@ -947,7 +1036,7 @@ export async function appRelease(client, args) {
|
|
|
947
1036
|
process.exitCode = 1;
|
|
948
1037
|
return;
|
|
949
1038
|
}
|
|
950
|
-
const result = await client.releasePackage(appId, { changelog: args.changelog, knowledge });
|
|
1039
|
+
const result = await client.releasePackage(appId, { changelog: args.changelog, knowledge, config });
|
|
951
1040
|
console.error(`Released ${result.package_id} v${result.version}.`);
|
|
952
1041
|
console.error(` The origin was re-pinned to v${result.version} — verify: lotics package doctor ${appId}`);
|
|
953
1042
|
}
|
|
@@ -990,7 +1079,7 @@ async function packageFleetUpgrade(client, args) {
|
|
|
990
1079
|
const from = inst.from_version === null ? "?" : `v${inst.from_version}`;
|
|
991
1080
|
const line = ` [${inst.outcome}] ${inst.workspace_name} — ${inst.app_name} (${from} → v${result.target_version})`;
|
|
992
1081
|
if (inst.outcome === "skipped" && inst.blockers) {
|
|
993
|
-
console.error(`${line}: breaking=${inst.blockers.breaking} drift=${inst.blockers.drift} modified=${inst.blockers.modified}`);
|
|
1082
|
+
console.error(`${line}: breaking=${inst.blockers.breaking} drift=${inst.blockers.drift} modified=${inst.blockers.modified} knowledge=${inst.blockers.knowledge}`);
|
|
994
1083
|
console.error(` resolve via: lotics upgrade ${inst.app_id} --version ${result.target_version} ...`);
|
|
995
1084
|
}
|
|
996
1085
|
else if (inst.message) {
|
|
@@ -2,7 +2,7 @@ import { describe, it, expect, vi } from "vitest";
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
|
-
import { parseResolveFlags, parseBindToFlags, parseRenameFlags, readLocalAppManifest, formatExtractReport, appRelease, packageUpgradeByPackageId, } from "./package_commands.js";
|
|
5
|
+
import { parseResolveFlags, parseBindToFlags, parseRenameFlags, readLocalAppManifest, formatExtractReport, appRelease, packageUpgradeByPackageId, packageDoctor, } from "./package_commands.js";
|
|
6
6
|
import { parseArgs } from "./args.js";
|
|
7
7
|
describe("parseRenameFlags", () => {
|
|
8
8
|
it("parses old=new pairs", () => {
|
|
@@ -20,21 +20,42 @@ describe("parseRenameFlags", () => {
|
|
|
20
20
|
});
|
|
21
21
|
});
|
|
22
22
|
describe("readLocalAppManifest", () => {
|
|
23
|
-
|
|
23
|
+
function withManifest(lotics, fn) {
|
|
24
24
|
const dir = fs.mkdtempSync(path.join(tmpdir(), "lotics-app-test-"));
|
|
25
25
|
try {
|
|
26
|
-
fs.writeFileSync(path.join(dir, "package.json"), JSON.stringify({
|
|
27
|
-
|
|
28
|
-
lotics: { app_id: "app_x", knowledge: [{ alias: "sop", doc_id: "kdc_1" }] },
|
|
29
|
-
}));
|
|
30
|
-
expect(readLocalAppManifest(dir)).toEqual({
|
|
31
|
-
app_id: "app_x",
|
|
32
|
-
knowledge: [{ alias: "sop", doc_id: "kdc_1" }],
|
|
33
|
-
});
|
|
26
|
+
fs.writeFileSync(path.join(dir, "package.json"), JSON.stringify({ name: "crm", lotics }));
|
|
27
|
+
return fn(dir);
|
|
34
28
|
}
|
|
35
29
|
finally {
|
|
36
30
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
37
31
|
}
|
|
32
|
+
}
|
|
33
|
+
it("reads app_id + knowledge + config from a pulled app project", () => {
|
|
34
|
+
withManifest({
|
|
35
|
+
app_id: "app_x",
|
|
36
|
+
knowledge: [{ alias: "sop", doc_id: "kdc_1" }],
|
|
37
|
+
config: [{ alias: "page_size", label: "Page size", type: "number", default: 25 }],
|
|
38
|
+
}, (dir) => {
|
|
39
|
+
expect(readLocalAppManifest(dir)).toEqual({
|
|
40
|
+
app_id: "app_x",
|
|
41
|
+
knowledge: [{ alias: "sop", doc_id: "kdc_1" }],
|
|
42
|
+
config: [{ alias: "page_size", label: "Page size", type: "number", default: 25 }],
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
it("defaults knowledge + config to empty arrays when the manifest omits them", () => {
|
|
47
|
+
withManifest({ app_id: "app_x" }, (dir) => {
|
|
48
|
+
expect(readLocalAppManifest(dir)).toEqual({ app_id: "app_x", knowledge: [], config: [] });
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
it("validates each config knob against the shared schema, throwing NAMING the bad alias", () => {
|
|
52
|
+
withManifest({
|
|
53
|
+
app_id: "app_x",
|
|
54
|
+
// A select knob with no `options` — the shared schema rejects it.
|
|
55
|
+
config: [{ alias: "theme", label: "Theme", type: "select", default: "light" }],
|
|
56
|
+
}, (dir) => {
|
|
57
|
+
expect(() => readLocalAppManifest(dir)).toThrow(/lotics\.config knob "theme"/);
|
|
58
|
+
});
|
|
38
59
|
});
|
|
39
60
|
it("returns null when the dir has no package.json", () => {
|
|
40
61
|
const dir = fs.mkdtempSync(path.join(tmpdir(), "lotics-empty-"));
|
|
@@ -146,27 +167,30 @@ describe("parseArgs — package list-content", () => {
|
|
|
146
167
|
expect(flags.workspace).toBe("wsp_dev");
|
|
147
168
|
});
|
|
148
169
|
});
|
|
149
|
-
describe("appRelease — knowledge declaration forwarding", () => {
|
|
170
|
+
describe("appRelease — knowledge + config declaration forwarding", () => {
|
|
171
|
+
const pageSizeKnob = { alias: "page_size", label: "Page size", type: "number", default: 25 };
|
|
150
172
|
const previewResult = {
|
|
151
173
|
package_id: "apg_1",
|
|
152
174
|
version: 2,
|
|
153
175
|
added_aliases: [],
|
|
154
176
|
changed_artifacts: [],
|
|
155
177
|
knowledge: { added: ["guide"], removed: [], changed: [] },
|
|
178
|
+
config: { added: ["page_size"], removed: [], changed: [] },
|
|
156
179
|
findings: [],
|
|
157
180
|
};
|
|
158
|
-
/** A mock client capturing the knowledge forwarded to preview + release. */
|
|
159
|
-
function mockClient() {
|
|
181
|
+
/** A mock client capturing the knowledge + config forwarded to preview + release. */
|
|
182
|
+
function mockClient(overridePreview) {
|
|
160
183
|
const preview = [];
|
|
161
184
|
const release = [];
|
|
185
|
+
const result = { ...previewResult, ...overridePreview };
|
|
162
186
|
const client = {
|
|
163
187
|
previewPackageRelease: async (app_id, opts = {}) => {
|
|
164
188
|
preview.push({ app_id, opts });
|
|
165
|
-
return
|
|
189
|
+
return result;
|
|
166
190
|
},
|
|
167
191
|
releasePackage: async (app_id, body) => {
|
|
168
192
|
release.push({ app_id, body });
|
|
169
|
-
return { ...
|
|
193
|
+
return { ...result, findings: undefined };
|
|
170
194
|
},
|
|
171
195
|
};
|
|
172
196
|
return { client, preview, release };
|
|
@@ -176,33 +200,53 @@ describe("appRelease — knowledge declaration forwarding", () => {
|
|
|
176
200
|
fs.writeFileSync(path.join(dir, "package.json"), JSON.stringify({ name: "app", lotics }));
|
|
177
201
|
return fn(dir).finally(() => fs.rmSync(dir, { recursive: true, force: true }));
|
|
178
202
|
}
|
|
179
|
-
it("forwards the manifest's knowledge
|
|
203
|
+
it("forwards the manifest's knowledge + config declarations from the app's own project", async () => {
|
|
180
204
|
vi.spyOn(console, "error").mockImplementation(() => undefined);
|
|
181
205
|
const { client, preview, release } = mockClient();
|
|
182
|
-
await withManifestDir({ app_id: "app_x", knowledge: [{ alias: "guide", doc_id: "kdc_1" }] }, async (dir) => {
|
|
206
|
+
await withManifestDir({ app_id: "app_x", knowledge: [{ alias: "guide", doc_id: "kdc_1" }], config: [pageSizeKnob] }, async (dir) => {
|
|
183
207
|
await appRelease(client, { app_id: ".", changelog: "ship", yes: true, projectDir: dir });
|
|
184
208
|
});
|
|
185
|
-
expect(preview[0]).toEqual({
|
|
186
|
-
|
|
209
|
+
expect(preview[0]).toEqual({
|
|
210
|
+
app_id: "app_x",
|
|
211
|
+
opts: { knowledge: [{ alias: "guide", doc_id: "kdc_1" }], config: [pageSizeKnob] },
|
|
212
|
+
});
|
|
213
|
+
expect(release[0].body).toEqual({
|
|
214
|
+
changelog: "ship",
|
|
215
|
+
knowledge: [{ alias: "guide", doc_id: "kdc_1" }],
|
|
216
|
+
config: [pageSizeKnob],
|
|
217
|
+
});
|
|
187
218
|
vi.restoreAllMocks();
|
|
188
219
|
});
|
|
189
|
-
it("omits knowledge (reconstruct
|
|
220
|
+
it("omits knowledge + config (reconstruct/carry-forward) when the manifest declares neither", async () => {
|
|
190
221
|
vi.spyOn(console, "error").mockImplementation(() => undefined);
|
|
191
222
|
const { client, preview, release } = mockClient();
|
|
192
223
|
await withManifestDir({ app_id: "app_x" }, async (dir) => {
|
|
193
224
|
await appRelease(client, { app_id: ".", changelog: "ship", yes: true, projectDir: dir });
|
|
194
225
|
});
|
|
195
|
-
expect(preview[0].opts).toEqual({ knowledge: undefined });
|
|
196
|
-
expect(release[0].body).toEqual({ changelog: "ship", knowledge: undefined });
|
|
226
|
+
expect(preview[0].opts).toEqual({ knowledge: undefined, config: undefined });
|
|
227
|
+
expect(release[0].body).toEqual({ changelog: "ship", knowledge: undefined, config: undefined });
|
|
197
228
|
vi.restoreAllMocks();
|
|
198
229
|
});
|
|
199
|
-
it("omits knowledge for a bare app id whose manifest is a DIFFERENT app", async () => {
|
|
230
|
+
it("omits knowledge + config for a bare app id whose manifest is a DIFFERENT app", async () => {
|
|
200
231
|
vi.spyOn(console, "error").mockImplementation(() => undefined);
|
|
201
232
|
const { client, preview } = mockClient();
|
|
202
|
-
await withManifestDir({ app_id: "app_other", knowledge: [{ alias: "guide", doc_id: "kdc_1" }] }, async (dir) => {
|
|
233
|
+
await withManifestDir({ app_id: "app_other", knowledge: [{ alias: "guide", doc_id: "kdc_1" }], config: [pageSizeKnob] }, async (dir) => {
|
|
203
234
|
await appRelease(client, { app_id: "app_x", changelog: "ship", yes: true, projectDir: dir });
|
|
204
235
|
});
|
|
205
|
-
expect(preview[0]).toEqual({ app_id: "app_x", opts: { knowledge: undefined } });
|
|
236
|
+
expect(preview[0]).toEqual({ app_id: "app_x", opts: { knowledge: undefined, config: undefined } });
|
|
237
|
+
vi.restoreAllMocks();
|
|
238
|
+
});
|
|
239
|
+
it("warns LOUDLY when a SENT config declaration is ignored by a pre-declaration server (deploy skew)", async () => {
|
|
240
|
+
const errs = [];
|
|
241
|
+
vi.spyOn(console, "error").mockImplementation((msg) => {
|
|
242
|
+
errs.push(String(msg));
|
|
243
|
+
});
|
|
244
|
+
// A pre-declaration server omits `config` from the preview echo.
|
|
245
|
+
const { client } = mockClient({ config: undefined });
|
|
246
|
+
await withManifestDir({ app_id: "app_x", config: [pageSizeKnob] }, async (dir) => {
|
|
247
|
+
await appRelease(client, { app_id: ".", changelog: "ship", yes: true, projectDir: dir });
|
|
248
|
+
});
|
|
249
|
+
expect(errs.some((e) => /server ignored the config declaration/.test(e))).toBe(true);
|
|
206
250
|
vi.restoreAllMocks();
|
|
207
251
|
});
|
|
208
252
|
});
|
|
@@ -307,3 +351,65 @@ describe("formatExtractReport", () => {
|
|
|
307
351
|
expect(formatExtractReport([])).toEqual({ lines: [], hasError: false });
|
|
308
352
|
});
|
|
309
353
|
});
|
|
354
|
+
describe("packageDoctor — content package-id form", () => {
|
|
355
|
+
function mockClient(preview) {
|
|
356
|
+
return {
|
|
357
|
+
getWorkspaceId: () => "wsp_1",
|
|
358
|
+
listContentInstallations: async () => [
|
|
359
|
+
{
|
|
360
|
+
id: "pci_1",
|
|
361
|
+
package_id: "apg_corpus",
|
|
362
|
+
workspace_id: "wsp_1",
|
|
363
|
+
package_version: preview.from_version,
|
|
364
|
+
app_id: null,
|
|
365
|
+
binding: { knowledge: {}, templates: {} },
|
|
366
|
+
installed_by: null,
|
|
367
|
+
created_at: "",
|
|
368
|
+
updated_at: "",
|
|
369
|
+
package_registry: {
|
|
370
|
+
name: "Ops corpus",
|
|
371
|
+
kind: "content",
|
|
372
|
+
is_official: false,
|
|
373
|
+
latest_version: preview.to_version,
|
|
374
|
+
update_available: preview.to_version > preview.from_version,
|
|
375
|
+
},
|
|
376
|
+
},
|
|
377
|
+
],
|
|
378
|
+
previewContentInstallationUpgrade: async () => preview,
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
it("reports a healthy, pristine install without failing the check", async () => {
|
|
382
|
+
const lines = [];
|
|
383
|
+
vi.spyOn(console, "error").mockImplementation((msg) => void lines.push(msg));
|
|
384
|
+
const before = process.exitCode;
|
|
385
|
+
await packageDoctor(mockClient({ from_version: 3, to_version: 3, entries: [], templates: [] }), { app_id: "apg_corpus" });
|
|
386
|
+
expect(lines.join("\n")).toContain("Ops corpus — content installation of apg_corpus");
|
|
387
|
+
expect(lines.join("\n")).toContain("Installed: v3 Latest: v3");
|
|
388
|
+
expect(lines.join("\n")).toContain("Binding: healthy");
|
|
389
|
+
expect(lines.join("\n")).toContain("Content: pristine");
|
|
390
|
+
expect(process.exitCode).toBe(before);
|
|
391
|
+
vi.restoreAllMocks();
|
|
392
|
+
});
|
|
393
|
+
it("reports drift (non-zero exit) and local edits with their consent framing", async () => {
|
|
394
|
+
const lines = [];
|
|
395
|
+
vi.spyOn(console, "error").mockImplementation((msg) => void lines.push(msg));
|
|
396
|
+
await packageDoctor(mockClient({
|
|
397
|
+
from_version: 2,
|
|
398
|
+
to_version: 3,
|
|
399
|
+
entries: [
|
|
400
|
+
{ alias: "sop", name: "SOP", change: "drifted", modified: false, doc_id: null },
|
|
401
|
+
{ alias: "pricing", name: "Pricing", change: "changed", modified: true, doc_id: "kdc_2" },
|
|
402
|
+
],
|
|
403
|
+
templates: [{ kind: "template", alias: "bien_ban" }],
|
|
404
|
+
}), { app_id: "apg_corpus" });
|
|
405
|
+
const out = lines.join("\n");
|
|
406
|
+
expect(out).toContain("→ update available");
|
|
407
|
+
expect(out).toContain('knowledge.sop "SOP" (bound doc is gone)');
|
|
408
|
+
expect(out).toContain("--resolve knowledge.<alias>=recreate|unbind");
|
|
409
|
+
expect(out).toContain('knowledge.pricing "Pricing"');
|
|
410
|
+
expect(out).toContain("template.bien_ban");
|
|
411
|
+
expect(process.exitCode).toBe(1);
|
|
412
|
+
process.exitCode = 0;
|
|
413
|
+
vi.restoreAllMocks();
|
|
414
|
+
});
|
|
415
|
+
});
|
package/dist/src/cli.js
CHANGED
|
@@ -29960,6 +29960,9 @@ var LoticsClient = class {
|
|
|
29960
29960
|
if (opts.knowledge !== void 0 && opts.knowledge.length > 0) {
|
|
29961
29961
|
params.set("knowledge", JSON.stringify(opts.knowledge));
|
|
29962
29962
|
}
|
|
29963
|
+
if (opts.config !== void 0) {
|
|
29964
|
+
params.set("config", JSON.stringify(opts.config));
|
|
29965
|
+
}
|
|
29963
29966
|
const query = params.toString();
|
|
29964
29967
|
return this.request(
|
|
29965
29968
|
"GET",
|
|
@@ -29992,6 +29995,9 @@ var LoticsClient = class {
|
|
|
29992
29995
|
if (opts.knowledge !== void 0 && opts.knowledge.length > 0) {
|
|
29993
29996
|
params.set("knowledge", JSON.stringify(opts.knowledge));
|
|
29994
29997
|
}
|
|
29998
|
+
if (opts.config !== void 0 && opts.config.length > 0) {
|
|
29999
|
+
params.set("config", JSON.stringify(opts.config));
|
|
30000
|
+
}
|
|
29995
30001
|
if (opts.renames !== void 0 && opts.renames.length > 0) {
|
|
29996
30002
|
params.set("renames", JSON.stringify(opts.renames));
|
|
29997
30003
|
}
|
|
@@ -50377,7 +50383,20 @@ function readLocalAppManifest(projectDir) {
|
|
|
50377
50383
|
}
|
|
50378
50384
|
}
|
|
50379
50385
|
}
|
|
50380
|
-
|
|
50386
|
+
const config2 = [];
|
|
50387
|
+
if (Array.isArray(lotics.config)) {
|
|
50388
|
+
for (const entry of lotics.config) {
|
|
50389
|
+
const result = contractConfigEntrySchema.safeParse(entry);
|
|
50390
|
+
if (!result.success) {
|
|
50391
|
+
const alias = isPlainObject2(entry) && typeof entry.alias === "string" ? entry.alias : "?";
|
|
50392
|
+
throw new Error(
|
|
50393
|
+
`Invalid lotics.config knob "${alias}" in package.json: ` + result.error.issues.map((i2) => `${i2.path.join(".") || "(root)"}: ${i2.message}`).join("; ")
|
|
50394
|
+
);
|
|
50395
|
+
}
|
|
50396
|
+
config2.push(result.data);
|
|
50397
|
+
}
|
|
50398
|
+
}
|
|
50399
|
+
return { app_id, knowledge, config: config2 };
|
|
50381
50400
|
}
|
|
50382
50401
|
function parseRenameFlags(renames) {
|
|
50383
50402
|
return renames.map((entry) => {
|
|
@@ -50416,6 +50435,10 @@ function parseResolveFlags(resolve2) {
|
|
|
50416
50435
|
return resolutions;
|
|
50417
50436
|
}
|
|
50418
50437
|
async function packageDoctor(client, args) {
|
|
50438
|
+
if (args.app_id !== void 0 && args.app_id.startsWith("apg_")) {
|
|
50439
|
+
await contentDoctor(client, args.app_id);
|
|
50440
|
+
return;
|
|
50441
|
+
}
|
|
50419
50442
|
const app_id = resolveInstallationAppId(args.app_id);
|
|
50420
50443
|
const health = await client.getPackageHealth(app_id);
|
|
50421
50444
|
console.error(
|
|
@@ -50635,6 +50658,38 @@ function formatTemplateResolveHint(entry) {
|
|
|
50635
50658
|
const note = entry.baseline_unknown ? " (can't verify the local edit \u2014 older package version; revert overwrites, keep retains)" : " (a local edit \u2014 revert overwrites it with the package's version; keep retains it)";
|
|
50636
50659
|
return ` --resolve template.${entry.alias}=revert|keep${note}`;
|
|
50637
50660
|
}
|
|
50661
|
+
async function contentDoctor(client, package_id) {
|
|
50662
|
+
const installation = await resolveWorkspaceContentInstallation(client, package_id);
|
|
50663
|
+
const preview = await client.previewContentInstallationUpgrade(installation.id, {});
|
|
50664
|
+
const name = installation.package_registry?.name ?? package_id;
|
|
50665
|
+
console.error(`${name} \u2014 content installation of ${package_id} (this workspace)`);
|
|
50666
|
+
console.error(
|
|
50667
|
+
` Installed: v${preview.from_version} Latest: v${preview.to_version}` + (preview.to_version > preview.from_version ? " \u2192 update available" : "")
|
|
50668
|
+
);
|
|
50669
|
+
const drifted = preview.entries.filter((e) => e.change === "drifted");
|
|
50670
|
+
if (drifted.length === 0) {
|
|
50671
|
+
console.error(" Binding: healthy \u2014 every bound doc resolves.");
|
|
50672
|
+
} else {
|
|
50673
|
+
console.error(` Binding drift (${drifted.length}):`);
|
|
50674
|
+
for (const entry of drifted) {
|
|
50675
|
+
console.error(` - knowledge.${entry.alias} "${entry.name}" (bound doc is gone)`);
|
|
50676
|
+
}
|
|
50677
|
+
console.error(
|
|
50678
|
+
` Resolve while upgrading:
|
|
50679
|
+
lotics upgrade ${package_id} --resolve knowledge.<alias>=recreate|unbind (or --bind-to <alias>=<kdc_id> to re-point)`
|
|
50680
|
+
);
|
|
50681
|
+
process.exitCode = 1;
|
|
50682
|
+
}
|
|
50683
|
+
const editedDocs = preview.entries.filter((e) => e.modified);
|
|
50684
|
+
const editedTemplates = preview.templates;
|
|
50685
|
+
if (editedDocs.length === 0 && editedTemplates.length === 0) {
|
|
50686
|
+
console.error(" Content: pristine \u2014 no local edits an upgrade would ask about.");
|
|
50687
|
+
} else {
|
|
50688
|
+
console.error(` Local edits (${editedDocs.length + editedTemplates.length}) \u2014 each takes a keep/overwrite consent at upgrade:`);
|
|
50689
|
+
for (const entry of editedDocs) console.error(` - knowledge.${entry.alias} "${entry.name}"`);
|
|
50690
|
+
for (const entry of editedTemplates) console.error(` - template.${entry.alias}`);
|
|
50691
|
+
}
|
|
50692
|
+
}
|
|
50638
50693
|
async function resolveWorkspaceContentInstallation(client, package_id) {
|
|
50639
50694
|
const workspaceId = client.getWorkspaceId();
|
|
50640
50695
|
if (!workspaceId) {
|
|
@@ -50992,8 +51047,9 @@ async function appPublish(client, args) {
|
|
|
50992
51047
|
);
|
|
50993
51048
|
}
|
|
50994
51049
|
const knowledge = local && local.app_id === appId ? local.knowledge : [];
|
|
51050
|
+
const config2 = local && local.app_id === appId ? local.config : [];
|
|
50995
51051
|
const renames = parseRenameFlags(args.renames);
|
|
50996
|
-
const preview = await client.previewPublishAppPackage(appId, { renames, knowledge });
|
|
51052
|
+
const preview = await client.previewPublishAppPackage(appId, { renames, knowledge, config: config2 });
|
|
50997
51053
|
const { lines, hasError } = formatExtractReport(preview.findings);
|
|
50998
51054
|
console.error(`Publish preview \u2014 ${appId} as new package "${preview.package_name}" (v1):`);
|
|
50999
51055
|
const groups = [
|
|
@@ -51015,6 +51071,11 @@ async function appPublish(client, args) {
|
|
|
51015
51071
|
} else {
|
|
51016
51072
|
console.error(" No renamable aliases.");
|
|
51017
51073
|
}
|
|
51074
|
+
if (config2.length > 0) {
|
|
51075
|
+
console.error(
|
|
51076
|
+
` Config knobs (${config2.length}): ${config2.map((c) => `${c.alias} (${c.type})`).join(", ")}`
|
|
51077
|
+
);
|
|
51078
|
+
}
|
|
51018
51079
|
if (lines.length > 0) {
|
|
51019
51080
|
console.error(` Findings (${preview.findings.length}):`);
|
|
51020
51081
|
for (const line of lines) console.error(line);
|
|
@@ -51039,7 +51100,8 @@ Re-run with --yes to publish v1:`);
|
|
|
51039
51100
|
const result = await client.publishAppAsPackage(appId, {
|
|
51040
51101
|
renames,
|
|
51041
51102
|
changelog: args.changelog ?? null,
|
|
51042
|
-
knowledge
|
|
51103
|
+
knowledge,
|
|
51104
|
+
config: config2
|
|
51043
51105
|
});
|
|
51044
51106
|
console.error(`Published ${result.package_id} v${result.version} from app ${appId}.`);
|
|
51045
51107
|
console.error(` The app is now installation #1 \u2014 develop it in place, then release the next version:`);
|
|
@@ -51058,7 +51120,8 @@ async function appRelease(client, args) {
|
|
|
51058
51120
|
);
|
|
51059
51121
|
}
|
|
51060
51122
|
const knowledge = local && local.app_id === appId && local.knowledge.length > 0 ? local.knowledge : void 0;
|
|
51061
|
-
const
|
|
51123
|
+
const config2 = local && local.app_id === appId && local.config.length > 0 ? local.config : void 0;
|
|
51124
|
+
const preview = await client.previewPackageRelease(appId, { knowledge, config: config2 });
|
|
51062
51125
|
const { lines, hasError } = formatExtractReport(preview.findings);
|
|
51063
51126
|
console.error(`Release preview \u2014 ${appId} \u2192 ${preview.package_id} v${preview.version}:`);
|
|
51064
51127
|
if (preview.added_aliases.length > 0) {
|
|
@@ -51067,6 +51130,16 @@ async function appRelease(client, args) {
|
|
|
51067
51130
|
if (preview.changed_artifacts.length > 0) {
|
|
51068
51131
|
console.error(` Changed (${preview.changed_artifacts.length}): ${preview.changed_artifacts.join(", ")}`);
|
|
51069
51132
|
}
|
|
51133
|
+
if (preview.knowledge === void 0 && knowledge !== void 0) {
|
|
51134
|
+
console.error(
|
|
51135
|
+
" WARNING: the server ignored the knowledge declaration (it predates bundled-knowledge releases) \u2014 the bundle will NOT change. Retry after the platform deploy completes."
|
|
51136
|
+
);
|
|
51137
|
+
}
|
|
51138
|
+
if (preview.config === void 0 && config2 !== void 0) {
|
|
51139
|
+
console.error(
|
|
51140
|
+
" WARNING: the server ignored the config declaration (it predates manifest config declarations) \u2014 the knobs will NOT change. Retry after the platform deploy completes."
|
|
51141
|
+
);
|
|
51142
|
+
}
|
|
51070
51143
|
const k = preview.knowledge ?? { added: [], removed: [], changed: [] };
|
|
51071
51144
|
if (k.added.length > 0 || k.removed.length > 0 || k.changed.length > 0) {
|
|
51072
51145
|
const parts = [
|
|
@@ -51076,7 +51149,16 @@ async function appRelease(client, args) {
|
|
|
51076
51149
|
].filter((p) => p !== null);
|
|
51077
51150
|
console.error(` Knowledge: ${parts.join("; ")}`);
|
|
51078
51151
|
}
|
|
51079
|
-
|
|
51152
|
+
const cfg = preview.config ?? { added: [], removed: [], changed: [] };
|
|
51153
|
+
if (cfg.added.length > 0 || cfg.removed.length > 0 || cfg.changed.length > 0) {
|
|
51154
|
+
const parts = [
|
|
51155
|
+
cfg.added.length > 0 ? `+${cfg.added.join(", ")}` : null,
|
|
51156
|
+
cfg.removed.length > 0 ? `dropped ${cfg.removed.join(", ")}` : null,
|
|
51157
|
+
cfg.changed.length > 0 ? `changed ${cfg.changed.join(", ")}` : null
|
|
51158
|
+
].filter((p) => p !== null);
|
|
51159
|
+
console.error(` Config: ${parts.join("; ")}`);
|
|
51160
|
+
}
|
|
51161
|
+
if (preview.added_aliases.length === 0 && preview.changed_artifacts.length === 0 && k.added.length === 0 && k.removed.length === 0 && k.changed.length === 0 && cfg.added.length === 0 && cfg.removed.length === 0 && cfg.changed.length === 0) {
|
|
51080
51162
|
console.error(" No contract changes since the current version (a fresh code/dist snapshot still ships).");
|
|
51081
51163
|
}
|
|
51082
51164
|
if (lines.length > 0) {
|
|
@@ -51095,7 +51177,7 @@ Re-run with --yes to publish v${preview.version}:`);
|
|
|
51095
51177
|
process.exitCode = 1;
|
|
51096
51178
|
return;
|
|
51097
51179
|
}
|
|
51098
|
-
const result = await client.releasePackage(appId, { changelog: args.changelog, knowledge });
|
|
51180
|
+
const result = await client.releasePackage(appId, { changelog: args.changelog, knowledge, config: config2 });
|
|
51099
51181
|
console.error(`Released ${result.package_id} v${result.version}.`);
|
|
51100
51182
|
console.error(` The origin was re-pinned to v${result.version} \u2014 verify: lotics package doctor ${appId}`);
|
|
51101
51183
|
}
|
|
@@ -51126,7 +51208,7 @@ async function packageFleetUpgrade(client, args) {
|
|
|
51126
51208
|
const line = ` [${inst.outcome}] ${inst.workspace_name} \u2014 ${inst.app_name} (${from} \u2192 v${result.target_version})`;
|
|
51127
51209
|
if (inst.outcome === "skipped" && inst.blockers) {
|
|
51128
51210
|
console.error(
|
|
51129
|
-
`${line}: breaking=${inst.blockers.breaking} drift=${inst.blockers.drift} modified=${inst.blockers.modified}`
|
|
51211
|
+
`${line}: breaking=${inst.blockers.breaking} drift=${inst.blockers.drift} modified=${inst.blockers.modified} knowledge=${inst.blockers.knowledge}`
|
|
51130
51212
|
);
|
|
51131
51213
|
console.error(
|
|
51132
51214
|
` resolve via: lotics upgrade ${inst.app_id} --version ${result.target_version} ...`
|
|
@@ -68016,7 +68098,7 @@ COMMANDS
|
|
|
68016
68098
|
<alias>=apply|keep|archive|recreate|unbind); an APP package
|
|
68017
68099
|
FLEET-upgrades every installation across your org (clean
|
|
68018
68100
|
apply, findings skip). --apply-all accepts the package's version
|
|
68019
|
-
lotics package doctor [app_id]
|
|
68101
|
+
lotics package doctor [app_id|package_id] Installation health: version pin vs latest, binding
|
|
68020
68102
|
drift, locally modified core, knowledge drift/edits
|
|
68021
68103
|
(exit 1 on findings)
|
|
68022
68104
|
lotics package config <app_id> [--set key=value ...]
|
|
@@ -68545,7 +68627,7 @@ async function main() {
|
|
|
68545
68627
|
console.error(" lotics install <package_id> [--version N] [--bind-to <alias>=<kdc_id>] [--config key=value ...] Install a package (app or content)");
|
|
68546
68628
|
console.error(" lotics uninstall <app_id|package_id> [--archive-tables] [--keep-content] Uninstall \u2014 dispatched by id (app vs content)");
|
|
68547
68629
|
console.error(" lotics upgrade <app_id|package_id> [--version N] [--resolve <key>=... ] [--bind-to ...] [--apply-all] Upgrade \u2014 app install / content install / whole app fleet (apg_)");
|
|
68548
|
-
console.error(" lotics package doctor [app_id]
|
|
68630
|
+
console.error(" lotics package doctor [app_id|package_id] Health: version pin vs latest + binding/knowledge drift (apg_ = this workspace's content install)");
|
|
68549
68631
|
console.error(" lotics package config <app_id> [--set key=value ...] Show or edit an installation's config knobs");
|
|
68550
68632
|
console.error(" lotics package eject <app_id> Sever an installation's package link");
|
|
68551
68633
|
console.error(" lotics package show <package_id> Registry metadata + version history (channel, yank, changelog)");
|