@lotics/cli 0.86.0 → 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/dev/file_relay.d.ts +39 -0
- package/dist/dev/file_relay.js +87 -0
- package/dist/dev/file_relay.test.d.ts +1 -0
- package/dist/dev/file_relay.test.js +87 -0
- package/dist/dev/server.d.ts +21 -3
- package/dist/dev/server.js +175 -5
- package/dist/dev/upload_relay.d.ts +36 -0
- package/dist/dev/upload_relay.js +61 -0
- package/dist/dev/upload_relay.test.d.ts +1 -0
- package/dist/dev/upload_relay.test.js +76 -0
- package/dist/dev/wrapper_page.js +7 -1
- 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 +304 -12
- package/package.json +1 -1
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
|
}
|
|
@@ -31598,7 +31604,13 @@ function buildWrapperPage(args) {
|
|
|
31598
31604
|
|
|
31599
31605
|
// The iframe SDK sends one "upload" op carrying a File. A File can't
|
|
31600
31606
|
// cross the JSON /_rpc hop, so the upload runs here in the browser:
|
|
31601
|
-
// mint
|
|
31607
|
+
// mint an upload URL, PUT the bytes, then finalize.
|
|
31608
|
+
//
|
|
31609
|
+
// In dev the minted URL is same-origin (/_upload/<file_id>) \u2014 the dev
|
|
31610
|
+
// server relays the bytes to storage on our behalf, because the prod
|
|
31611
|
+
// bucket's CORS does not admit a localhost origin. Production PUTs the
|
|
31612
|
+
// presigned storage URL directly. Same three lines either way: the page
|
|
31613
|
+
// PUTs wherever the mint points.
|
|
31602
31614
|
//
|
|
31603
31615
|
// Retry policy parity with the production SDK
|
|
31604
31616
|
// (packages/app-sdk/src/upload/transport.ts): 3 PUT attempts with
|
|
@@ -31803,6 +31815,85 @@ function escapeHtml2(s) {
|
|
|
31803
31815
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
31804
31816
|
}
|
|
31805
31817
|
|
|
31818
|
+
// src/dev/upload_relay.ts
|
|
31819
|
+
var PRESIGN_TTL_MS = 15 * 60 * 1e3;
|
|
31820
|
+
function createUploadRelay(now = Date.now) {
|
|
31821
|
+
const minted = /* @__PURE__ */ new Map();
|
|
31822
|
+
const prune = (at) => {
|
|
31823
|
+
for (const [id, entry] of minted) {
|
|
31824
|
+
if (at - entry.mintedAt > PRESIGN_TTL_MS) minted.delete(id);
|
|
31825
|
+
}
|
|
31826
|
+
};
|
|
31827
|
+
return {
|
|
31828
|
+
rewriteMint(result) {
|
|
31829
|
+
const mint = result;
|
|
31830
|
+
if (typeof mint?.file_id !== "string" || typeof mint?.upload_url !== "string") return result;
|
|
31831
|
+
const at = now();
|
|
31832
|
+
prune(at);
|
|
31833
|
+
minted.set(mint.file_id, { url: mint.upload_url, mintedAt: at });
|
|
31834
|
+
return { ...mint, upload_url: `/_upload/${encodeURIComponent(mint.file_id)}` };
|
|
31835
|
+
},
|
|
31836
|
+
destinationFor(fileId) {
|
|
31837
|
+
const entry = minted.get(fileId);
|
|
31838
|
+
if (!entry) return null;
|
|
31839
|
+
if (now() - entry.mintedAt > PRESIGN_TTL_MS) {
|
|
31840
|
+
minted.delete(fileId);
|
|
31841
|
+
return null;
|
|
31842
|
+
}
|
|
31843
|
+
return entry.url;
|
|
31844
|
+
},
|
|
31845
|
+
settle(fileId) {
|
|
31846
|
+
minted.delete(fileId);
|
|
31847
|
+
},
|
|
31848
|
+
size: () => minted.size
|
|
31849
|
+
};
|
|
31850
|
+
}
|
|
31851
|
+
|
|
31852
|
+
// src/dev/file_relay.ts
|
|
31853
|
+
import { createHash } from "node:crypto";
|
|
31854
|
+
var TTL_MS = 24 * 60 * 60 * 1e3;
|
|
31855
|
+
var MAX_ENTRIES = 5e3;
|
|
31856
|
+
var PRESIGNED_KEYS = /* @__PURE__ */ new Set(["url", "thumbnail_url", "preview_url"]);
|
|
31857
|
+
var isFileObject = (o) => typeof o.filename === "string" && typeof o.mime_type === "string";
|
|
31858
|
+
var isRemoteUrl = (v) => typeof v === "string" && /^https?:\/\//i.test(v);
|
|
31859
|
+
function createFileRelay(wrapperOrigin, now = Date.now) {
|
|
31860
|
+
const seen = /* @__PURE__ */ new Map();
|
|
31861
|
+
const tokenFor = (url2) => createHash("sha256").update(url2).digest("hex").slice(0, 24);
|
|
31862
|
+
const remember = (url2) => {
|
|
31863
|
+
const token = tokenFor(url2);
|
|
31864
|
+
seen.set(token, { url: url2, at: now() });
|
|
31865
|
+
if (seen.size > MAX_ENTRIES) {
|
|
31866
|
+
const oldest = seen.keys().next();
|
|
31867
|
+
if (!oldest.done) seen.delete(oldest.value);
|
|
31868
|
+
}
|
|
31869
|
+
return token;
|
|
31870
|
+
};
|
|
31871
|
+
const walk2 = (node) => {
|
|
31872
|
+
if (Array.isArray(node)) return node.map(walk2);
|
|
31873
|
+
if (!node || typeof node !== "object") return node;
|
|
31874
|
+
const obj = node;
|
|
31875
|
+
const file2 = isFileObject(obj);
|
|
31876
|
+
const out = {};
|
|
31877
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
31878
|
+
out[key] = file2 && PRESIGNED_KEYS.has(key) && isRemoteUrl(value) ? `${wrapperOrigin}/_file/${remember(value)}` : walk2(value);
|
|
31879
|
+
}
|
|
31880
|
+
return out;
|
|
31881
|
+
};
|
|
31882
|
+
return {
|
|
31883
|
+
rewrite: (result) => walk2(result),
|
|
31884
|
+
destinationFor(token) {
|
|
31885
|
+
const entry = seen.get(token);
|
|
31886
|
+
if (!entry) return null;
|
|
31887
|
+
if (now() - entry.at > TTL_MS) {
|
|
31888
|
+
seen.delete(token);
|
|
31889
|
+
return null;
|
|
31890
|
+
}
|
|
31891
|
+
return entry.url;
|
|
31892
|
+
},
|
|
31893
|
+
size: () => seen.size
|
|
31894
|
+
};
|
|
31895
|
+
}
|
|
31896
|
+
|
|
31806
31897
|
// src/dev/server.ts
|
|
31807
31898
|
var DEFAULT_PORT = 5174;
|
|
31808
31899
|
var DEFAULT_VITE_PORT = 5173;
|
|
@@ -31849,6 +31940,24 @@ async function startDevServer(args) {
|
|
|
31849
31940
|
}
|
|
31850
31941
|
};
|
|
31851
31942
|
process.once("exit", killViteOnExit);
|
|
31943
|
+
const uploads = createUploadRelay();
|
|
31944
|
+
const wrapperOrigin = `http://localhost:${wrapperPort}`;
|
|
31945
|
+
const viteOrigin = `http://localhost:${vitePort}`;
|
|
31946
|
+
const files = createFileRelay(wrapperOrigin);
|
|
31947
|
+
const fileCors = {
|
|
31948
|
+
"Access-Control-Allow-Origin": viteOrigin,
|
|
31949
|
+
"Access-Control-Allow-Headers": "range, content-type",
|
|
31950
|
+
"Access-Control-Expose-Headers": "content-length, content-range, accept-ranges, content-type, content-disposition, etag"
|
|
31951
|
+
};
|
|
31952
|
+
const PASS_THROUGH = [
|
|
31953
|
+
"content-type",
|
|
31954
|
+
"content-length",
|
|
31955
|
+
"content-range",
|
|
31956
|
+
"accept-ranges",
|
|
31957
|
+
"etag",
|
|
31958
|
+
"last-modified",
|
|
31959
|
+
"content-disposition"
|
|
31960
|
+
];
|
|
31852
31961
|
const server = http.createServer(async (req, res) => {
|
|
31853
31962
|
const url2 = req.url ?? "/";
|
|
31854
31963
|
const pathname = url2.split("?")[0];
|
|
@@ -31877,7 +31986,11 @@ async function startDevServer(args) {
|
|
|
31877
31986
|
process.stderr.write(`[rpc] ${body.op} ${ms}ms
|
|
31878
31987
|
`);
|
|
31879
31988
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
31880
|
-
res.end(
|
|
31989
|
+
res.end(
|
|
31990
|
+
serializeRpcResult(
|
|
31991
|
+
body.op === "upload_url" ? uploads.rewriteMint(result) : files.rewrite(result)
|
|
31992
|
+
)
|
|
31993
|
+
);
|
|
31881
31994
|
} catch (err2) {
|
|
31882
31995
|
const message = err2 instanceof Error ? err2.message : String(err2);
|
|
31883
31996
|
process.stderr.write(`[rpc] ERROR ${message}
|
|
@@ -31930,12 +32043,115 @@ async function startDevServer(args) {
|
|
|
31930
32043
|
}
|
|
31931
32044
|
return;
|
|
31932
32045
|
}
|
|
32046
|
+
if (pathname.startsWith("/_file/")) {
|
|
32047
|
+
if (req.method === "OPTIONS") {
|
|
32048
|
+
res.writeHead(204, fileCors);
|
|
32049
|
+
res.end();
|
|
32050
|
+
return;
|
|
32051
|
+
}
|
|
32052
|
+
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
32053
|
+
res.writeHead(405, { ...fileCors, Allow: "GET, HEAD, OPTIONS" });
|
|
32054
|
+
res.end();
|
|
32055
|
+
return;
|
|
32056
|
+
}
|
|
32057
|
+
const token = decodeURIComponent(pathname.slice("/_file/".length));
|
|
32058
|
+
const destination = files.destinationFor(token);
|
|
32059
|
+
if (!destination) {
|
|
32060
|
+
process.stderr.write(`[file] ERROR unknown or expired token ${token}
|
|
32061
|
+
`);
|
|
32062
|
+
res.writeHead(404, { ...fileCors, "Content-Type": "application/json" });
|
|
32063
|
+
res.end(JSON.stringify({ message: "No file for this token" }));
|
|
32064
|
+
return;
|
|
32065
|
+
}
|
|
32066
|
+
try {
|
|
32067
|
+
const range = req.headers.range;
|
|
32068
|
+
const upstream = await fetch(destination, {
|
|
32069
|
+
method: req.method,
|
|
32070
|
+
headers: range ? { Range: range } : void 0
|
|
32071
|
+
});
|
|
32072
|
+
const headers = { ...fileCors };
|
|
32073
|
+
for (const name of PASS_THROUGH) {
|
|
32074
|
+
const value = upstream.headers.get(name);
|
|
32075
|
+
if (value) headers[name] = value;
|
|
32076
|
+
}
|
|
32077
|
+
const size = headers["content-length"] ?? "?";
|
|
32078
|
+
process.stderr.write(`[file] ${token} ${upstream.status} ${size}B
|
|
32079
|
+
`);
|
|
32080
|
+
res.writeHead(upstream.status, headers);
|
|
32081
|
+
if (req.method === "HEAD" || !upstream.body) {
|
|
32082
|
+
res.end();
|
|
32083
|
+
return;
|
|
32084
|
+
}
|
|
32085
|
+
const reader = upstream.body.getReader();
|
|
32086
|
+
for (; ; ) {
|
|
32087
|
+
const { value, done } = await reader.read();
|
|
32088
|
+
if (done) break;
|
|
32089
|
+
res.write(Buffer.from(value));
|
|
32090
|
+
}
|
|
32091
|
+
res.end();
|
|
32092
|
+
} catch (err2) {
|
|
32093
|
+
const message = err2 instanceof Error ? err2.message : String(err2);
|
|
32094
|
+
process.stderr.write(`[file] ERROR ${token} ${message}
|
|
32095
|
+
`);
|
|
32096
|
+
if (!res.headersSent) {
|
|
32097
|
+
res.writeHead(502, { ...fileCors, "Content-Type": "application/json" });
|
|
32098
|
+
res.end(JSON.stringify({ message }));
|
|
32099
|
+
} else {
|
|
32100
|
+
res.end();
|
|
32101
|
+
}
|
|
32102
|
+
}
|
|
32103
|
+
return;
|
|
32104
|
+
}
|
|
32105
|
+
if (req.method === "PUT" && pathname.startsWith("/_upload/")) {
|
|
32106
|
+
const fileId = decodeURIComponent(pathname.slice("/_upload/".length));
|
|
32107
|
+
const destination = uploads.destinationFor(fileId);
|
|
32108
|
+
if (!destination) {
|
|
32109
|
+
process.stderr.write(`[upload] ERROR unknown or expired file_id ${fileId}
|
|
32110
|
+
`);
|
|
32111
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
32112
|
+
res.end(JSON.stringify({ message: `No presigned upload pending for ${fileId}` }));
|
|
32113
|
+
return;
|
|
32114
|
+
}
|
|
32115
|
+
try {
|
|
32116
|
+
const chunks = [];
|
|
32117
|
+
for await (const chunk of req) chunks.push(chunk);
|
|
32118
|
+
const bytes = Buffer.concat(chunks);
|
|
32119
|
+
const startedAt = Date.now();
|
|
32120
|
+
const upstream = await fetch(destination, {
|
|
32121
|
+
method: "PUT",
|
|
32122
|
+
body: bytes,
|
|
32123
|
+
headers: { "Content-Type": req.headers["content-type"] ?? "application/octet-stream" }
|
|
32124
|
+
});
|
|
32125
|
+
const ms = Date.now() - startedAt;
|
|
32126
|
+
if (upstream.ok) {
|
|
32127
|
+
uploads.settle(fileId);
|
|
32128
|
+
process.stderr.write(`[upload] ${fileId} ${bytes.length}B ${ms}ms
|
|
32129
|
+
`);
|
|
32130
|
+
} else {
|
|
32131
|
+
process.stderr.write(`[upload] ERROR ${fileId} storage returned ${upstream.status}
|
|
32132
|
+
`);
|
|
32133
|
+
}
|
|
32134
|
+
res.writeHead(upstream.status);
|
|
32135
|
+
res.end();
|
|
32136
|
+
} catch (err2) {
|
|
32137
|
+
const message = err2 instanceof Error ? err2.message : String(err2);
|
|
32138
|
+
process.stderr.write(`[upload] ERROR ${fileId} ${message}
|
|
32139
|
+
`);
|
|
32140
|
+
if (!res.headersSent) {
|
|
32141
|
+
res.writeHead(502, { "Content-Type": "application/json" });
|
|
32142
|
+
res.end(JSON.stringify({ message }));
|
|
32143
|
+
} else {
|
|
32144
|
+
res.end();
|
|
32145
|
+
}
|
|
32146
|
+
}
|
|
32147
|
+
return;
|
|
32148
|
+
}
|
|
31933
32149
|
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
31934
32150
|
res.end("Not Found");
|
|
31935
32151
|
});
|
|
31936
32152
|
await new Promise((resolve2, reject2) => {
|
|
31937
32153
|
server.once("error", reject2);
|
|
31938
|
-
server.listen(wrapperPort, () => {
|
|
32154
|
+
server.listen(wrapperPort, "127.0.0.1", () => {
|
|
31939
32155
|
server.off("error", reject2);
|
|
31940
32156
|
resolve2();
|
|
31941
32157
|
});
|
|
@@ -50167,7 +50383,20 @@ function readLocalAppManifest(projectDir) {
|
|
|
50167
50383
|
}
|
|
50168
50384
|
}
|
|
50169
50385
|
}
|
|
50170
|
-
|
|
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 };
|
|
50171
50400
|
}
|
|
50172
50401
|
function parseRenameFlags(renames) {
|
|
50173
50402
|
return renames.map((entry) => {
|
|
@@ -50206,6 +50435,10 @@ function parseResolveFlags(resolve2) {
|
|
|
50206
50435
|
return resolutions;
|
|
50207
50436
|
}
|
|
50208
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
|
+
}
|
|
50209
50442
|
const app_id = resolveInstallationAppId(args.app_id);
|
|
50210
50443
|
const health = await client.getPackageHealth(app_id);
|
|
50211
50444
|
console.error(
|
|
@@ -50425,6 +50658,38 @@ function formatTemplateResolveHint(entry) {
|
|
|
50425
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)";
|
|
50426
50659
|
return ` --resolve template.${entry.alias}=revert|keep${note}`;
|
|
50427
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
|
+
}
|
|
50428
50693
|
async function resolveWorkspaceContentInstallation(client, package_id) {
|
|
50429
50694
|
const workspaceId = client.getWorkspaceId();
|
|
50430
50695
|
if (!workspaceId) {
|
|
@@ -50782,8 +51047,9 @@ async function appPublish(client, args) {
|
|
|
50782
51047
|
);
|
|
50783
51048
|
}
|
|
50784
51049
|
const knowledge = local && local.app_id === appId ? local.knowledge : [];
|
|
51050
|
+
const config2 = local && local.app_id === appId ? local.config : [];
|
|
50785
51051
|
const renames = parseRenameFlags(args.renames);
|
|
50786
|
-
const preview = await client.previewPublishAppPackage(appId, { renames, knowledge });
|
|
51052
|
+
const preview = await client.previewPublishAppPackage(appId, { renames, knowledge, config: config2 });
|
|
50787
51053
|
const { lines, hasError } = formatExtractReport(preview.findings);
|
|
50788
51054
|
console.error(`Publish preview \u2014 ${appId} as new package "${preview.package_name}" (v1):`);
|
|
50789
51055
|
const groups = [
|
|
@@ -50805,6 +51071,11 @@ async function appPublish(client, args) {
|
|
|
50805
51071
|
} else {
|
|
50806
51072
|
console.error(" No renamable aliases.");
|
|
50807
51073
|
}
|
|
51074
|
+
if (config2.length > 0) {
|
|
51075
|
+
console.error(
|
|
51076
|
+
` Config knobs (${config2.length}): ${config2.map((c) => `${c.alias} (${c.type})`).join(", ")}`
|
|
51077
|
+
);
|
|
51078
|
+
}
|
|
50808
51079
|
if (lines.length > 0) {
|
|
50809
51080
|
console.error(` Findings (${preview.findings.length}):`);
|
|
50810
51081
|
for (const line of lines) console.error(line);
|
|
@@ -50829,7 +51100,8 @@ Re-run with --yes to publish v1:`);
|
|
|
50829
51100
|
const result = await client.publishAppAsPackage(appId, {
|
|
50830
51101
|
renames,
|
|
50831
51102
|
changelog: args.changelog ?? null,
|
|
50832
|
-
knowledge
|
|
51103
|
+
knowledge,
|
|
51104
|
+
config: config2
|
|
50833
51105
|
});
|
|
50834
51106
|
console.error(`Published ${result.package_id} v${result.version} from app ${appId}.`);
|
|
50835
51107
|
console.error(` The app is now installation #1 \u2014 develop it in place, then release the next version:`);
|
|
@@ -50848,7 +51120,8 @@ async function appRelease(client, args) {
|
|
|
50848
51120
|
);
|
|
50849
51121
|
}
|
|
50850
51122
|
const knowledge = local && local.app_id === appId && local.knowledge.length > 0 ? local.knowledge : void 0;
|
|
50851
|
-
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 });
|
|
50852
51125
|
const { lines, hasError } = formatExtractReport(preview.findings);
|
|
50853
51126
|
console.error(`Release preview \u2014 ${appId} \u2192 ${preview.package_id} v${preview.version}:`);
|
|
50854
51127
|
if (preview.added_aliases.length > 0) {
|
|
@@ -50857,6 +51130,16 @@ async function appRelease(client, args) {
|
|
|
50857
51130
|
if (preview.changed_artifacts.length > 0) {
|
|
50858
51131
|
console.error(` Changed (${preview.changed_artifacts.length}): ${preview.changed_artifacts.join(", ")}`);
|
|
50859
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
|
+
}
|
|
50860
51143
|
const k = preview.knowledge ?? { added: [], removed: [], changed: [] };
|
|
50861
51144
|
if (k.added.length > 0 || k.removed.length > 0 || k.changed.length > 0) {
|
|
50862
51145
|
const parts = [
|
|
@@ -50866,7 +51149,16 @@ async function appRelease(client, args) {
|
|
|
50866
51149
|
].filter((p) => p !== null);
|
|
50867
51150
|
console.error(` Knowledge: ${parts.join("; ")}`);
|
|
50868
51151
|
}
|
|
50869
|
-
|
|
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) {
|
|
50870
51162
|
console.error(" No contract changes since the current version (a fresh code/dist snapshot still ships).");
|
|
50871
51163
|
}
|
|
50872
51164
|
if (lines.length > 0) {
|
|
@@ -50885,7 +51177,7 @@ Re-run with --yes to publish v${preview.version}:`);
|
|
|
50885
51177
|
process.exitCode = 1;
|
|
50886
51178
|
return;
|
|
50887
51179
|
}
|
|
50888
|
-
const result = await client.releasePackage(appId, { changelog: args.changelog, knowledge });
|
|
51180
|
+
const result = await client.releasePackage(appId, { changelog: args.changelog, knowledge, config: config2 });
|
|
50889
51181
|
console.error(`Released ${result.package_id} v${result.version}.`);
|
|
50890
51182
|
console.error(` The origin was re-pinned to v${result.version} \u2014 verify: lotics package doctor ${appId}`);
|
|
50891
51183
|
}
|
|
@@ -50916,7 +51208,7 @@ async function packageFleetUpgrade(client, args) {
|
|
|
50916
51208
|
const line = ` [${inst.outcome}] ${inst.workspace_name} \u2014 ${inst.app_name} (${from} \u2192 v${result.target_version})`;
|
|
50917
51209
|
if (inst.outcome === "skipped" && inst.blockers) {
|
|
50918
51210
|
console.error(
|
|
50919
|
-
`${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}`
|
|
50920
51212
|
);
|
|
50921
51213
|
console.error(
|
|
50922
51214
|
` resolve via: lotics upgrade ${inst.app_id} --version ${result.target_version} ...`
|
|
@@ -67806,7 +68098,7 @@ COMMANDS
|
|
|
67806
68098
|
<alias>=apply|keep|archive|recreate|unbind); an APP package
|
|
67807
68099
|
FLEET-upgrades every installation across your org (clean
|
|
67808
68100
|
apply, findings skip). --apply-all accepts the package's version
|
|
67809
|
-
lotics package doctor [app_id]
|
|
68101
|
+
lotics package doctor [app_id|package_id] Installation health: version pin vs latest, binding
|
|
67810
68102
|
drift, locally modified core, knowledge drift/edits
|
|
67811
68103
|
(exit 1 on findings)
|
|
67812
68104
|
lotics package config <app_id> [--set key=value ...]
|
|
@@ -68335,7 +68627,7 @@ async function main() {
|
|
|
68335
68627
|
console.error(" lotics install <package_id> [--version N] [--bind-to <alias>=<kdc_id>] [--config key=value ...] Install a package (app or content)");
|
|
68336
68628
|
console.error(" lotics uninstall <app_id|package_id> [--archive-tables] [--keep-content] Uninstall \u2014 dispatched by id (app vs content)");
|
|
68337
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_)");
|
|
68338
|
-
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)");
|
|
68339
68631
|
console.error(" lotics package config <app_id> [--set key=value ...] Show or edit an installation's config knobs");
|
|
68340
68632
|
console.error(" lotics package eject <app_id> Sever an installation's package link");
|
|
68341
68633
|
console.error(" lotics package show <package_id> Registry metadata + version history (channel, yank, changelog)");
|