@lotics/cli 0.76.1 → 0.83.0
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/README.md +52 -48
- package/dist/app_commands.d.ts +9 -4
- package/dist/app_commands.js +44 -10
- package/dist/app_commands.test.js +69 -0
- package/dist/args.d.ts +6 -5
- package/dist/args.js +18 -17
- package/dist/args.test.js +25 -17
- package/dist/cli.js +201 -253
- package/dist/cli_dispatch.test.js +77 -25
- package/dist/client.d.ts +105 -176
- package/dist/client.js +61 -95
- package/dist/generate_package_fields.d.ts +39 -38
- package/dist/generate_package_fields.js +113 -60
- package/dist/generate_package_fields.test.js +30 -22
- package/dist/package_commands.d.ts +100 -318
- package/dist/package_commands.js +303 -1298
- package/dist/package_commands.test.js +66 -542
- package/dist/src/cli.js +624 -1834
- package/dist/starter_template.d.ts +0 -19
- package/dist/starter_template.js +0 -389
- package/dist/starter_template.test.js +1 -69
- package/package.json +1 -1
package/dist/src/cli.js
CHANGED
|
@@ -29709,15 +29709,6 @@ var LoticsClient = class {
|
|
|
29709
29709
|
async listWorkspaces() {
|
|
29710
29710
|
return this.request("GET", "/v1/workspaces");
|
|
29711
29711
|
}
|
|
29712
|
-
/**
|
|
29713
|
-
* Resolve one workspace's info by id from the org's workspace list (the only
|
|
29714
|
-
* API-key-accessible source carrying `is_dev`). Returns null when the
|
|
29715
|
-
* workspace isn't visible to these credentials.
|
|
29716
|
-
*/
|
|
29717
|
-
async getWorkspaceInfo(id) {
|
|
29718
|
-
const workspaces = await this.listWorkspaces();
|
|
29719
|
-
return workspaces.find((w) => w.id === id) ?? null;
|
|
29720
|
-
}
|
|
29721
29712
|
async createWorkspace(body) {
|
|
29722
29713
|
return this.request("POST", "/v1/workspaces", body);
|
|
29723
29714
|
}
|
|
@@ -29785,7 +29776,7 @@ var LoticsClient = class {
|
|
|
29785
29776
|
/**
|
|
29786
29777
|
* Uninstall a standalone content package — delete the installation row. By
|
|
29787
29778
|
* default the package-bound docs are ARCHIVED; `keep_content` retains them as
|
|
29788
|
-
* ordinary workspace docs. Admin-only. Backs `lotics
|
|
29779
|
+
* ordinary workspace docs. Admin-only. Backs `lotics uninstall`.
|
|
29789
29780
|
*/
|
|
29790
29781
|
async uninstallContentPackage(installation_id, opts = {}) {
|
|
29791
29782
|
const qs = opts.keep_content ? "?keep_content=true" : "";
|
|
@@ -29833,7 +29824,7 @@ var LoticsClient = class {
|
|
|
29833
29824
|
);
|
|
29834
29825
|
}
|
|
29835
29826
|
/**
|
|
29836
|
-
* Uninstall a package installation (backs `lotics
|
|
29827
|
+
* Uninstall a package installation (backs `lotics uninstall`). Does
|
|
29837
29828
|
* everything DELETE does plus archives the installation's lifecycle
|
|
29838
29829
|
* artifacts; with `archive_tables` it also archives the scaffolded entity
|
|
29839
29830
|
* tables — refused server-side unless this installation created them
|
|
@@ -29851,10 +29842,11 @@ var LoticsClient = class {
|
|
|
29851
29842
|
return this.request("PATCH", `/v1/apps/${encodeURIComponent(app_id)}/package-config`, body);
|
|
29852
29843
|
}
|
|
29853
29844
|
/**
|
|
29854
|
-
* Retire (or `undo` un-retire) a registry package (backs `lotics
|
|
29855
|
-
*
|
|
29856
|
-
*
|
|
29857
|
-
* Owner-org
|
|
29845
|
+
* Retire (or `undo` un-retire) a registry package (backs `lotics app
|
|
29846
|
+
* unpublish` — the endpoint/audit action keep the `retire` name to avoid API
|
|
29847
|
+
* churn). Retiring refuses NEW installs and hides the package from non-owning
|
|
29848
|
+
* orgs; existing installations keep working and may still upgrade. Owner-org
|
|
29849
|
+
* admin-only.
|
|
29858
29850
|
*/
|
|
29859
29851
|
async retirePackage(package_id, body) {
|
|
29860
29852
|
return this.request("POST", `/v1/packages/${encodeURIComponent(package_id)}/retire`, body);
|
|
@@ -29869,38 +29861,12 @@ var LoticsClient = class {
|
|
|
29869
29861
|
async ejectPackage(app_id) {
|
|
29870
29862
|
return this.request("POST", `/v1/apps/${encodeURIComponent(app_id)}/eject`);
|
|
29871
29863
|
}
|
|
29872
|
-
/**
|
|
29873
|
-
* Extract a DRAFT app package from an existing bespoke app — the promotion
|
|
29874
|
-
* read (docs/packages.md § Promotion). Pure: nothing is written. Returns
|
|
29875
|
-
* the alias-keyed draft `contract` (opaque to the CLI — the server is the
|
|
29876
|
-
* validating authority), the origin workspace's `binding` (which doubles as
|
|
29877
|
-
* the adopt binding), a findings `report` (any `error` ⇒ not publishable
|
|
29878
|
-
* as-is), and the file-backed `template_files` the CLI must stage into the
|
|
29879
|
-
* project at their `bytes_ref` paths. Backs `lotics package extract`.
|
|
29880
|
-
* Admin-only.
|
|
29881
|
-
*/
|
|
29882
|
-
async extractPackage(app_id, opts = {}) {
|
|
29883
|
-
const qs = opts.knowledge && opts.knowledge.length > 0 ? `?knowledge=${encodeURIComponent(JSON.stringify(opts.knowledge))}` : "";
|
|
29884
|
-
return this.request("GET", `/v1/apps/${encodeURIComponent(app_id)}/package-extract${qs}`);
|
|
29885
|
-
}
|
|
29886
|
-
/**
|
|
29887
|
-
* Adopt a published package onto an EXISTING (bespoke or ejected) app — the
|
|
29888
|
-
* final promotion step. The workspace already holds the concrete objects, so
|
|
29889
|
-
* nothing is scaffolded or rewritten: the server verifies the `binding` is
|
|
29890
|
-
* complete, live, and FAITHFUL to the version's contract, then writes only the
|
|
29891
|
-
* installation pin (the app becomes installation #1, upgradeable again). A
|
|
29892
|
-
* ConflictError names the aliases that diverge. `version` omitted adopts the
|
|
29893
|
-
* latest. Backs `lotics package adopt`. Admin-only.
|
|
29894
|
-
*/
|
|
29895
|
-
async adoptPackage(app_id, body) {
|
|
29896
|
-
return this.request("POST", `/v1/apps/${encodeURIComponent(app_id)}/package-adopt`, body);
|
|
29897
|
-
}
|
|
29898
29864
|
/**
|
|
29899
29865
|
* Fleet upgrade — bring every installation of a package across the CALLER'S
|
|
29900
29866
|
* org to the target version (latest when omitted) in one call. Hands-off
|
|
29901
29867
|
* applies only where the preview is clean; installations with breaking/
|
|
29902
29868
|
* drift/modified-core findings are skipped and reported for the normal
|
|
29903
|
-
* per-installation consent flow. Backs `lotics
|
|
29869
|
+
* per-installation consent flow. Backs `lotics upgrade <package_id>` (fleet).
|
|
29904
29870
|
* Admin-only; org-scoped (no workspace header needed).
|
|
29905
29871
|
*/
|
|
29906
29872
|
/**
|
|
@@ -29922,16 +29888,10 @@ var LoticsClient = class {
|
|
|
29922
29888
|
body
|
|
29923
29889
|
);
|
|
29924
29890
|
}
|
|
29925
|
-
// ---
|
|
29926
|
-
|
|
29927
|
-
|
|
29928
|
-
|
|
29929
|
-
* local manifest has no `package_id`), then publishes version 1 against the
|
|
29930
|
-
* returned id. Admin-only.
|
|
29931
|
-
*/
|
|
29932
|
-
async createPackage(body) {
|
|
29933
|
-
return this.request("POST", "/v1/packages", body);
|
|
29934
|
-
}
|
|
29891
|
+
// --- Packages (registry reads + installations) ---
|
|
29892
|
+
// Authoring is server-side: apps via `POST /v1/apps/{id}/package-publish|release`,
|
|
29893
|
+
// content packages via the `publish_content`/`release_content` tools. There is no
|
|
29894
|
+
// client-side create-package / upload-bundle path.
|
|
29935
29895
|
/**
|
|
29936
29896
|
* Fetch a registry app package's metadata (incl. `kind`, `latest_version` and
|
|
29937
29897
|
* the Lotics-backed `is_official` trust badge). Admin-only; cross-tenant by id.
|
|
@@ -29943,33 +29903,6 @@ var LoticsClient = class {
|
|
|
29943
29903
|
async listPackageVersions(package_id) {
|
|
29944
29904
|
return this.request("GET", `/v1/packages/${encodeURIComponent(package_id)}/versions`);
|
|
29945
29905
|
}
|
|
29946
|
-
/**
|
|
29947
|
-
* Publish a new immutable package version — multipart upload of the alias-keyed
|
|
29948
|
-
* contract (JSON) + the prebuilt code bundle (a gzipped tarball carrying
|
|
29949
|
-
* `source.tar.gz` + `dist.tar.gz` members). The server validates the contract +
|
|
29950
|
-
* bundle, then allocates the next monotonic version. The `contract` is opaque
|
|
29951
|
-
* JSON to the transport (the server is the validating authority). Admin-only.
|
|
29952
|
-
*/
|
|
29953
|
-
async publishPackageVersion(package_id, args) {
|
|
29954
|
-
const formData = new FormData();
|
|
29955
|
-
formData.append("contract", JSON.stringify(args.contract));
|
|
29956
|
-
formData.append(
|
|
29957
|
-
"bundle",
|
|
29958
|
-
new Blob([new Uint8Array(args.bundle)], { type: "application/gzip" }),
|
|
29959
|
-
"bundle.tar.gz"
|
|
29960
|
-
);
|
|
29961
|
-
if (args.changelog) formData.append("changelog", args.changelog);
|
|
29962
|
-
if (args.channel) formData.append("channel", args.channel);
|
|
29963
|
-
const url2 = `${this.baseUrl}/v1/packages/${encodeURIComponent(package_id)}/versions`;
|
|
29964
|
-
const response = await fetch(url2, {
|
|
29965
|
-
method: "POST",
|
|
29966
|
-
headers: this.buildHeaders(),
|
|
29967
|
-
// no Content-Type — fetch sets the multipart boundary
|
|
29968
|
-
body: formData
|
|
29969
|
-
});
|
|
29970
|
-
if (!response.ok) await this.throwResponseError(response);
|
|
29971
|
-
return response.json();
|
|
29972
|
-
}
|
|
29973
29906
|
/**
|
|
29974
29907
|
* Upgrade a package installation to a newer published version — extends the
|
|
29975
29908
|
* binding additively, re-materializes the target version's
|
|
@@ -29996,18 +29929,6 @@ var LoticsClient = class {
|
|
|
29996
29929
|
`/v1/apps/${encodeURIComponent(app_id)}/package-upgrade${query}`
|
|
29997
29930
|
);
|
|
29998
29931
|
}
|
|
29999
|
-
/**
|
|
30000
|
-
* Re-bind a package role to a different workspace group (the current group
|
|
30001
|
-
* still exists). Re-materializes at the pinned version; refuses over
|
|
30002
|
-
* modified-core findings. Admin-only.
|
|
30003
|
-
*/
|
|
30004
|
-
async rebindPackageRole(app_id, body) {
|
|
30005
|
-
return this.request(
|
|
30006
|
-
"POST",
|
|
30007
|
-
`/v1/apps/${encodeURIComponent(app_id)}/package-rebind-role`,
|
|
30008
|
-
body
|
|
30009
|
-
);
|
|
30010
|
-
}
|
|
30011
29932
|
/**
|
|
30012
29933
|
* Workspace-wide dangling-reference sweep — active app/workflow artifacts
|
|
30013
29934
|
* whose prefixed schema ids no longer resolve. Backs
|
|
@@ -30025,13 +29946,59 @@ var LoticsClient = class {
|
|
|
30025
29946
|
return this.request("GET", `/v1/apps/${encodeURIComponent(app_id)}/package-health`);
|
|
30026
29947
|
}
|
|
30027
29948
|
/**
|
|
30028
|
-
*
|
|
30029
|
-
*
|
|
30030
|
-
*
|
|
30031
|
-
* Admin
|
|
29949
|
+
* Preview a release — the dry run behind `lotics app release`. Runs the
|
|
29950
|
+
* binding-aware extract of the origin (aliases stable through the app's current
|
|
29951
|
+
* binding) and reports the next version number, the new + changed aliases, and
|
|
29952
|
+
* any extract findings (an `error` blocks the apply). No writes. Admin,
|
|
29953
|
+
* owning-org only.
|
|
30032
29954
|
*/
|
|
30033
|
-
async
|
|
30034
|
-
return this.request("
|
|
29955
|
+
async previewPackageRelease(app_id) {
|
|
29956
|
+
return this.request("GET", `/v1/apps/${encodeURIComponent(app_id)}/package-release`);
|
|
29957
|
+
}
|
|
29958
|
+
/**
|
|
29959
|
+
* Release — snapshot the origin app into the next registry version. The server
|
|
29960
|
+
* binding-aware-extracts it, repackages its deployed source + dist as the
|
|
29961
|
+
* bundle, publishes the next `release`-channel version with the changelog, and
|
|
29962
|
+
* re-pins the origin. Error findings from extract surface as a 409. Admin,
|
|
29963
|
+
* owning-org only. Backs `lotics app release --yes`.
|
|
29964
|
+
*/
|
|
29965
|
+
async releasePackage(app_id, body) {
|
|
29966
|
+
return this.request("POST", `/v1/apps/${encodeURIComponent(app_id)}/package-release`, body);
|
|
29967
|
+
}
|
|
29968
|
+
/**
|
|
29969
|
+
* Dry-run preview of a first-release — the `GET` behind `lotics app publish`
|
|
29970
|
+
* (no `--yes`), the publish-side analogue of `previewPackageRelease`. The
|
|
29971
|
+
* server runs the same fresh-alias extract + `src/` scan the apply runs
|
|
29972
|
+
* (through any `renames`) and returns the package name it would mint, the
|
|
29973
|
+
* auto-minted RENAMABLE aliases (the exact `--rename` keys), and the extract
|
|
29974
|
+
* findings (an `error` blocks the apply). No writes. Admin-only.
|
|
29975
|
+
*/
|
|
29976
|
+
async previewPublishAppPackage(app_id, opts = {}) {
|
|
29977
|
+
const params = new URLSearchParams();
|
|
29978
|
+
if (opts.knowledge !== void 0 && opts.knowledge.length > 0) {
|
|
29979
|
+
params.set("knowledge", JSON.stringify(opts.knowledge));
|
|
29980
|
+
}
|
|
29981
|
+
if (opts.renames !== void 0 && opts.renames.length > 0) {
|
|
29982
|
+
params.set("renames", JSON.stringify(opts.renames));
|
|
29983
|
+
}
|
|
29984
|
+
const query = params.toString();
|
|
29985
|
+
return this.request(
|
|
29986
|
+
"GET",
|
|
29987
|
+
`/v1/apps/${encodeURIComponent(app_id)}/package-publish${query ? `?${query}` : ""}`
|
|
29988
|
+
);
|
|
29989
|
+
}
|
|
29990
|
+
/**
|
|
29991
|
+
* First-release apply — mint a package from a BESPOKE app and publish v1 in one
|
|
29992
|
+
* call (the `POST` behind `lotics app publish --yes`). The server extracts an
|
|
29993
|
+
* alias-keyed contract from the app (fresh aliases; `renames` fixes them before
|
|
29994
|
+
* v1 freezes), creates the registry package (name/description from the app),
|
|
29995
|
+
* publishes v1 from the app's deployed source + dist, and pins the origin as
|
|
29996
|
+
* installation #1. Error findings from extract surface as a 409. An
|
|
29997
|
+
* already-linked app must use `releasePackage` instead. Admin-only. Backs
|
|
29998
|
+
* `lotics app publish <app_id>`.
|
|
29999
|
+
*/
|
|
30000
|
+
async publishAppAsPackage(app_id, body) {
|
|
30001
|
+
return this.request("POST", `/v1/apps/${encodeURIComponent(app_id)}/package-publish`, body);
|
|
30035
30002
|
}
|
|
30036
30003
|
/**
|
|
30037
30004
|
* Resolve the display name + fields (incl. select options) of the given tables
|
|
@@ -31360,379 +31327,6 @@ See https://lotics.ai/docs/app-sdk for the SDK reference.
|
|
|
31360
31327
|
}
|
|
31361
31328
|
];
|
|
31362
31329
|
}
|
|
31363
|
-
function buildPackageStarterOverrides(args) {
|
|
31364
|
-
const nameLit = JSON.stringify(args.app_name);
|
|
31365
|
-
return [
|
|
31366
|
-
{
|
|
31367
|
-
// The package App.tsx imports `../.lotics/app_fields` — a DETERMINISTIC
|
|
31368
|
-
// pure function of contract.json (generate_package_fields.ts) — so unlike
|
|
31369
|
-
// the app starter's fully-ignored `.lotics`, it must be committed or the
|
|
31370
|
-
// scaffold's own shipped CI (npm ci → typecheck/test/build on a clean
|
|
31371
|
-
// clone) fails on the missing module. The sync-written `.d.ts` companions
|
|
31372
|
-
// stay ignored: they need a live workspace and their absence only degrades
|
|
31373
|
-
// hooks to untyped overloads, never breaks the build.
|
|
31374
|
-
path: ".gitignore",
|
|
31375
|
-
content: `node_modules
|
|
31376
|
-
dist
|
|
31377
|
-
*.tsbuildinfo
|
|
31378
|
-
.DS_Store
|
|
31379
|
-
.lotics/*
|
|
31380
|
-
!.lotics/app_fields.ts
|
|
31381
|
-
coverage
|
|
31382
|
-
`
|
|
31383
|
-
},
|
|
31384
|
-
{
|
|
31385
|
-
path: "src/App.tsx",
|
|
31386
|
-
content: `import { useMemo, type ReactNode } from "react";
|
|
31387
|
-
import { ScrollView, View } from "react-native";
|
|
31388
|
-
import { useConfig, useQuery, readSelect, row } from "@lotics/app-sdk";
|
|
31389
|
-
import { AppRouter } from "@lotics/app-sdk/router";
|
|
31390
|
-
import { Text } from "@lotics/ui/text";
|
|
31391
|
-
import { Card } from "@lotics/ui/card";
|
|
31392
|
-
import { Button } from "@lotics/ui/button";
|
|
31393
|
-
import { OptionBadge } from "@lotics/ui/option_badge";
|
|
31394
|
-
import { Skeleton } from "@lotics/ui/skeleton";
|
|
31395
|
-
import { EmptyState } from "@lotics/ui/empty_state";
|
|
31396
|
-
import { Callout, CalloutTitle, CalloutText, CalloutActions } from "@lotics/ui/callout";
|
|
31397
|
-
import { F, OPT } from "../.lotics/app_fields";
|
|
31398
|
-
|
|
31399
|
-
// This is a PACKAGE starter \u2014 a workspace-agnostic blueprint installed into many
|
|
31400
|
-
// workspaces, each binding the contract's aliases to DIFFERENT concrete ids. So a
|
|
31401
|
-
// screen never hardcodes a \`fld_\u2026\`/\`opt_\u2026\` id: it addresses a field by contract
|
|
31402
|
-
// alias through \`F\` and a select option through \`OPT\`, both from the generated
|
|
31403
|
-
// \`.lotics/app_fields.ts\` (which resolves every alias to THIS installation's id at
|
|
31404
|
-
// module load from the binding). The aliases come from contract.json \u2014 entity
|
|
31405
|
-
// \`item\`, fields name/notes/status, options open/done, query \`items\`, config knob
|
|
31406
|
-
// \`heading\`. Edit contract.json, then \`lotics package sync\` to regenerate F/OPT.
|
|
31407
|
-
|
|
31408
|
-
interface Item {
|
|
31409
|
-
id: string;
|
|
31410
|
-
name: string;
|
|
31411
|
-
notes: string;
|
|
31412
|
-
status: ReturnType<typeof readSelect>[number] | null;
|
|
31413
|
-
done: boolean;
|
|
31414
|
-
}
|
|
31415
|
-
|
|
31416
|
-
// The \`items\` query is a bare from_entity, so a row is keyed by FIELD id: read a
|
|
31417
|
-
// cell as \`r[F.ITEM.<field>]\` and decode it with the SDK's pure readers (\`row.text\`,
|
|
31418
|
-
// \`readSelect\`). \`OPT.ITEM.status.done\` is this install's \`opt_\u2026\` id, so comparing
|
|
31419
|
-
// the cell's stored option key to it marks a done row.
|
|
31420
|
-
function decode(r: Record<string, unknown>): Item {
|
|
31421
|
-
const status = readSelect(r[F.ITEM.status])[0] ?? null;
|
|
31422
|
-
return {
|
|
31423
|
-
id: row.text(r.__source_record_id),
|
|
31424
|
-
name: row.text(r[F.ITEM.name]),
|
|
31425
|
-
notes: row.text(r[F.ITEM.notes]),
|
|
31426
|
-
status,
|
|
31427
|
-
done: status?.key === OPT.ITEM.status.done,
|
|
31428
|
-
};
|
|
31429
|
-
}
|
|
31430
|
-
|
|
31431
|
-
// Outer <View flex:1> claims the iframe height (index.html sets html/body/#root to
|
|
31432
|
-
// 100% + #root is a flex column); the list scrolls beneath the heading. Keep this
|
|
31433
|
-
// flex chain plain (not @lotics/ui/stack) so a fill-remaining-space child gets height.
|
|
31434
|
-
function Screen({ children }: { children: ReactNode }) {
|
|
31435
|
-
return (
|
|
31436
|
-
<View style={{ flex: 1 }}>
|
|
31437
|
-
<ScrollView contentContainerStyle={{ padding: 24, alignItems: "center" }}>
|
|
31438
|
-
<View style={{ maxWidth: 640, width: "100%", gap: 16 }}>{children}</View>
|
|
31439
|
-
</ScrollView>
|
|
31440
|
-
</View>
|
|
31441
|
-
);
|
|
31442
|
-
}
|
|
31443
|
-
|
|
31444
|
-
function ItemsScreen() {
|
|
31445
|
-
// \`heading\` is a contract config knob \u2014 an installation overrides it and
|
|
31446
|
-
// useConfig() renders the customized value; the literal here is only the fallback.
|
|
31447
|
-
const { config } = useConfig({ heading: ${nameLit} });
|
|
31448
|
-
const itemsQ = useQuery("items");
|
|
31449
|
-
const items = useMemo(() => itemsQ.rows.map(decode), [itemsQ.rows]);
|
|
31450
|
-
// First-load only: a skeleton while the very first fetch is in flight with no rows
|
|
31451
|
-
// yet \u2014 never blank already-loaded rows to a spinner on a background refetch.
|
|
31452
|
-
const firstLoad = itemsQ.loading && items.length === 0;
|
|
31453
|
-
|
|
31454
|
-
return (
|
|
31455
|
-
<Screen>
|
|
31456
|
-
<Text size="xxl" weight="semibold" level={1}>
|
|
31457
|
-
{config.heading}
|
|
31458
|
-
</Text>
|
|
31459
|
-
|
|
31460
|
-
{itemsQ.error ? (
|
|
31461
|
-
<Callout tone="error">
|
|
31462
|
-
<CalloutTitle>Couldn't load items</CalloutTitle>
|
|
31463
|
-
<CalloutText>{itemsQ.error}</CalloutText>
|
|
31464
|
-
<CalloutActions>
|
|
31465
|
-
<Button title="Try again" onPress={() => itemsQ.refetch()} />
|
|
31466
|
-
</CalloutActions>
|
|
31467
|
-
</Callout>
|
|
31468
|
-
) : firstLoad ? (
|
|
31469
|
-
<View style={{ gap: 10 }}>
|
|
31470
|
-
{[0, 1, 2].map((i) => (
|
|
31471
|
-
<Skeleton key={i} height={64} radius={12} />
|
|
31472
|
-
))}
|
|
31473
|
-
</View>
|
|
31474
|
-
) : items.length > 0 ? (
|
|
31475
|
-
<View style={{ gap: 10 }}>
|
|
31476
|
-
{items.map((item) => (
|
|
31477
|
-
<Card key={item.id}>
|
|
31478
|
-
<View style={{ flexDirection: "row", alignItems: "center", gap: 12 }}>
|
|
31479
|
-
<View style={{ flex: 1, gap: 2 }}>
|
|
31480
|
-
<Text weight="medium" color={item.done ? "muted" : undefined}>
|
|
31481
|
-
{item.name}
|
|
31482
|
-
</Text>
|
|
31483
|
-
{item.notes.length > 0 ? (
|
|
31484
|
-
<Text size="sm" color="muted" numberOfLines={1}>
|
|
31485
|
-
{item.notes}
|
|
31486
|
-
</Text>
|
|
31487
|
-
) : null}
|
|
31488
|
-
</View>
|
|
31489
|
-
<OptionBadge value={item.status} />
|
|
31490
|
-
</View>
|
|
31491
|
-
</Card>
|
|
31492
|
-
))}
|
|
31493
|
-
</View>
|
|
31494
|
-
) : (
|
|
31495
|
-
<EmptyState
|
|
31496
|
-
icon="list-checks"
|
|
31497
|
-
message="No items yet"
|
|
31498
|
-
hint="Rows in this package's table appear here."
|
|
31499
|
-
/>
|
|
31500
|
-
)}
|
|
31501
|
-
</Screen>
|
|
31502
|
-
);
|
|
31503
|
-
}
|
|
31504
|
-
|
|
31505
|
-
const routes = [{ path: "/", element: <ItemsScreen /> }];
|
|
31506
|
-
|
|
31507
|
-
export default function App() {
|
|
31508
|
-
return <AppRouter routes={routes} />;
|
|
31509
|
-
}
|
|
31510
|
-
`
|
|
31511
|
-
},
|
|
31512
|
-
{
|
|
31513
|
-
path: "src/App.test.tsx",
|
|
31514
|
-
content: `import { describe, test, expect, beforeEach, afterEach, vi } from "vitest";
|
|
31515
|
-
import { render, screen, cleanup } from "@testing-library/react";
|
|
31516
|
-
import App from "./App";
|
|
31517
|
-
|
|
31518
|
-
// Mock @lotics/app-sdk \u2014 the app's one external boundary (data + RPC + the package
|
|
31519
|
-
// binding). \`importOriginal\` keeps the PURE cell readers (\`row\`, \`readSelect\`) real,
|
|
31520
|
-
// so mock rows decode exactly like wire rows; only the hooks and getAppBinding are
|
|
31521
|
-
// stubbed. The binding ids below are what F.ITEM.* / OPT.ITEM.status.* resolve to;
|
|
31522
|
-
// the query rows are keyed by the SAME ids.
|
|
31523
|
-
const h = vi.hoisted(() => ({
|
|
31524
|
-
state: {
|
|
31525
|
-
rows: [] as Array<Record<string, unknown>>,
|
|
31526
|
-
loading: false,
|
|
31527
|
-
error: null as string | null,
|
|
31528
|
-
},
|
|
31529
|
-
binding: {
|
|
31530
|
-
fields: { "item.name": "fld_name", "item.notes": "fld_notes", "item.status": "fld_status" },
|
|
31531
|
-
options: { "item.status:open": "opt_open", "item.status:done": "opt_done" },
|
|
31532
|
-
roles: {},
|
|
31533
|
-
},
|
|
31534
|
-
}));
|
|
31535
|
-
|
|
31536
|
-
vi.mock("@lotics/app-sdk", async (importOriginal) => {
|
|
31537
|
-
const actual = await importOriginal<typeof import("@lotics/app-sdk")>();
|
|
31538
|
-
return {
|
|
31539
|
-
...actual,
|
|
31540
|
-
getAppBinding: async () => h.binding,
|
|
31541
|
-
useConfig: (defaults: Record<string, unknown>) => ({ config: defaults, loading: false }),
|
|
31542
|
-
useQuery: () => ({
|
|
31543
|
-
rows: h.state.rows,
|
|
31544
|
-
loading: h.state.loading,
|
|
31545
|
-
isValidating: false,
|
|
31546
|
-
error: h.state.error,
|
|
31547
|
-
refetch: () => {},
|
|
31548
|
-
}),
|
|
31549
|
-
};
|
|
31550
|
-
});
|
|
31551
|
-
|
|
31552
|
-
const rowOf = (id: string, name: string, notes: string, statusKey: string) => ({
|
|
31553
|
-
__source_record_id: id,
|
|
31554
|
-
fld_name: name,
|
|
31555
|
-
fld_notes: notes,
|
|
31556
|
-
fld_status: [{ key: statusKey, label: statusKey === "opt_done" ? "Done" : "Open" }],
|
|
31557
|
-
});
|
|
31558
|
-
|
|
31559
|
-
beforeEach(() => {
|
|
31560
|
-
h.state.rows = [];
|
|
31561
|
-
h.state.loading = false;
|
|
31562
|
-
h.state.error = null;
|
|
31563
|
-
});
|
|
31564
|
-
|
|
31565
|
-
// vitest globals are off, so @testing-library/react's automatic afterEach cleanup is
|
|
31566
|
-
// never registered \u2014 clean up explicitly or renders accumulate across tests and a
|
|
31567
|
-
// second render's duplicate matches fail getByText.
|
|
31568
|
-
afterEach(cleanup);
|
|
31569
|
-
|
|
31570
|
-
describe("App", () => {
|
|
31571
|
-
test("renders the heading and the loaded items with status badges", () => {
|
|
31572
|
-
h.state.rows = [
|
|
31573
|
-
rowOf("1", "First item", "with a note", "opt_open"),
|
|
31574
|
-
rowOf("2", "Second item", "", "opt_done"),
|
|
31575
|
-
];
|
|
31576
|
-
render(<App />);
|
|
31577
|
-
|
|
31578
|
-
expect(screen.getByText(${nameLit})).toBeTruthy();
|
|
31579
|
-
expect(screen.getByText("First item")).toBeTruthy();
|
|
31580
|
-
expect(screen.getByText("Second item")).toBeTruthy();
|
|
31581
|
-
expect(screen.getByText("with a note")).toBeTruthy();
|
|
31582
|
-
expect(screen.getByText("Open")).toBeTruthy();
|
|
31583
|
-
expect(screen.getByText("Done")).toBeTruthy();
|
|
31584
|
-
expect(screen.queryByText("No items yet")).toBeNull();
|
|
31585
|
-
});
|
|
31586
|
-
|
|
31587
|
-
test("shows the empty state when there are no items", () => {
|
|
31588
|
-
render(<App />);
|
|
31589
|
-
expect(screen.getByText("No items yet")).toBeTruthy();
|
|
31590
|
-
});
|
|
31591
|
-
|
|
31592
|
-
test("does not flash the list or empty state on first load", () => {
|
|
31593
|
-
h.state.loading = true;
|
|
31594
|
-
render(<App />);
|
|
31595
|
-
expect(screen.queryByText("No items yet")).toBeNull();
|
|
31596
|
-
});
|
|
31597
|
-
|
|
31598
|
-
test("surfaces a load error loudly", () => {
|
|
31599
|
-
h.state.error = "Network unreachable";
|
|
31600
|
-
render(<App />);
|
|
31601
|
-
expect(screen.getByText("Couldn't load items")).toBeTruthy();
|
|
31602
|
-
expect(screen.getByText("Network unreachable")).toBeTruthy();
|
|
31603
|
-
});
|
|
31604
|
-
});
|
|
31605
|
-
`
|
|
31606
|
-
},
|
|
31607
|
-
{
|
|
31608
|
-
path: "README.md",
|
|
31609
|
-
content: `# ${escapeHtml(args.app_name)}
|
|
31610
|
-
|
|
31611
|
-
A Lotics **app package** \u2014 a versioned, installable blueprint (a \`contract.json\`
|
|
31612
|
-
data model + app source) that installs into many workspaces. Authored locally,
|
|
31613
|
-
published and run through the \`lotics package\` CLI. See \`docs/app_packages.md\`.
|
|
31614
|
-
|
|
31615
|
-
## Dev loop
|
|
31616
|
-
|
|
31617
|
-
\`\`\`bash
|
|
31618
|
-
lotics workspace create "${escapeHtml(args.app_name)} dev" --dev # a throwaway dev workspace
|
|
31619
|
-
lotics package dev --workspace <dev_ws> # sync into it + run the dev server
|
|
31620
|
-
# edit contract.json \u2192 re-run \`lotics package sync\` to migrate + re-materialize
|
|
31621
|
-
# edit src/* \u2192 hot reload
|
|
31622
|
-
\`\`\`
|
|
31623
|
-
|
|
31624
|
-
\`sync\` / \`dev\` regenerate \`.lotics/app_fields.ts\` (the runtime \`F\` / \`OPT\` / \`ROLE\`
|
|
31625
|
-
surface) from \`contract.json\`, plus the typed \`.lotics/app_{queries,workflows,agents}.d.ts\`
|
|
31626
|
-
companions from the live installation \u2014 so \`useQuery\` / \`useWorkflow\` stay typed in the
|
|
31627
|
-
dev loop.
|
|
31628
|
-
|
|
31629
|
-
## Quality
|
|
31630
|
-
|
|
31631
|
-
\`\`\`bash
|
|
31632
|
-
npm run typecheck
|
|
31633
|
-
npm run lint
|
|
31634
|
-
npm test
|
|
31635
|
-
\`\`\`
|
|
31636
|
-
|
|
31637
|
-
## Publish
|
|
31638
|
-
|
|
31639
|
-
\`\`\`bash
|
|
31640
|
-
lotics package publish -m "v1" # build + publish an immutable version
|
|
31641
|
-
lotics package install <package_id> # install into any workspace
|
|
31642
|
-
\`\`\`
|
|
31643
|
-
|
|
31644
|
-
## The app screen
|
|
31645
|
-
|
|
31646
|
-
\`src/App.tsx\` reads the contract BY ALIAS through \`F\` / \`OPT\` (from the generated
|
|
31647
|
-
\`.lotics/app_fields.ts\`) \u2014 never a raw \`fld_\u2026\` / \`opt_\u2026\` id, since every install binds
|
|
31648
|
-
different concrete ids. It lists the \`items\` query and renders the \`heading\` config knob.
|
|
31649
|
-
\`@lotics/ui\` primitives render via react-native-web (aliased in \`vite.config.ts\`).
|
|
31650
|
-
|
|
31651
|
-
## Contract reference
|
|
31652
|
-
|
|
31653
|
-
\`contract.json\` is the package's alias-keyed data model
|
|
31654
|
-
(schema: \`@lotics/shared/schemas/app_packages\`). Every cross-reference is by **alias**;
|
|
31655
|
-
the materializer resolves alias \u2192 this workspace's concrete id at install.
|
|
31656
|
-
|
|
31657
|
-
### Aliases
|
|
31658
|
-
|
|
31659
|
-
- Entity / field / option / role / template / config aliases are lowercase slugs
|
|
31660
|
-
matching \`^[a-z][a-z0-9_]*$\`. A field's fully-qualified key is \`<entity>.<field>\`
|
|
31661
|
-
(e.g. \`item.status\`); a select option's is \`<entity>.<field>:<option>\`
|
|
31662
|
-
(e.g. \`item.status:done\`).
|
|
31663
|
-
- Query / workflow / agent aliases are runtime lookup keys (1\u2013200 chars) \u2014 the app
|
|
31664
|
-
source invokes them verbatim (\`useQuery("items")\`), so renaming one severs the call.
|
|
31665
|
-
|
|
31666
|
-
### Entities & fields
|
|
31667
|
-
|
|
31668
|
-
\`\`\`jsonc
|
|
31669
|
-
{ "alias": "item", "label": "Item", "fields": [
|
|
31670
|
-
{ "alias": "name", "label": "Name", "type": "text", "required": true },
|
|
31671
|
-
{ "alias": "status", "label": "Status", "type": "select",
|
|
31672
|
-
"options": [ { "alias": "open", "label": "Open", "color": "blue" },
|
|
31673
|
-
{ "alias": "done", "label": "Done", "color": "green" } ] } ] }
|
|
31674
|
-
\`\`\`
|
|
31675
|
-
|
|
31676
|
-
Field \`type\`: \`text\`, \`number\`, \`date\`, \`boolean\`, \`select\` (+ \`options\`),
|
|
31677
|
-
\`select_member\`, \`select_record_link\` (\`target_entity\` = an entity alias), \`files\`,
|
|
31678
|
-
\`formula\` (expression references same-entity fields as \`{alias}\`), \`rollup\`, \`lookup\`,
|
|
31679
|
-
\`autonumber\`. \`required\` is advisory (app / workflow-layer UX only \u2014 the table model
|
|
31680
|
-
has no required constraint). Select \`options\` are \`{ alias, label, color }\`.
|
|
31681
|
-
|
|
31682
|
-
### Queries
|
|
31683
|
-
|
|
31684
|
-
Alias-form AST \u2014 the same node kinds as the runtime query engine, except a
|
|
31685
|
-
\`from_table\` node carries \`from_entity\` (an entity alias) instead of a \`table_id\`:
|
|
31686
|
-
|
|
31687
|
-
\`\`\`jsonc
|
|
31688
|
-
{ "alias": "items", "ast": {
|
|
31689
|
-
"kind": "from_table", "from_entity": "item",
|
|
31690
|
-
"sort": [ { "field_key": "name", "order": "asc" } ] } }
|
|
31691
|
-
\`\`\`
|
|
31692
|
-
|
|
31693
|
-
### Workflows
|
|
31694
|
-
|
|
31695
|
-
The sole app-side mutation path. Each workflow has a \`trigger\`, typed \`inputs\` /
|
|
31696
|
-
\`outputs\`, and a JS-subset \`body\`.
|
|
31697
|
-
|
|
31698
|
-
- Trigger \u2014 app-invoked or table-lifecycle:
|
|
31699
|
-
- \`{ "type": "app" }\` \u2014 called by alias from the app (\`useWorkflow("<alias>")\`).
|
|
31700
|
-
- \`{ "type": "entity_lifecycle", "entity": "<entity>", "event": "<event>" }\` \u2014 fires
|
|
31701
|
-
on the bound table. \`event\` \u2208 \`before_create\`, \`after_create\`, \`before_update\`,
|
|
31702
|
-
\`after_update\`, \`before_delete\`, \`after_delete\`.
|
|
31703
|
-
- Typed \`inputs\` (\`text\` / \`number\` / \`date\` / \`member\` / \`select\` / \`record_link\`,
|
|
31704
|
-
plus \`object\` / \`array\`):
|
|
31705
|
-
- \`record_link\` \u2014 its \`table_id\` names an **entity alias**.
|
|
31706
|
-
- \`member\` \u2014 its \`group\` names a **role alias**.
|
|
31707
|
-
- \`select\` \u2014 an option \`value\` in \`entity.field:option\` form binds to that option's
|
|
31708
|
-
id (a plain value is a literal, left as-is).
|
|
31709
|
-
- Body **sentinel tokens** \u2014 a body addresses package objects by reserved tokens the
|
|
31710
|
-
materializer rewrites to concrete ids at install. Always inside a **string literal**:
|
|
31711
|
-
- \`@@entity:<entity>@@\`
|
|
31712
|
-
- \`@@field:<entity>.<field>@@\`
|
|
31713
|
-
- \`@@option:<entity>.<field>:<option>@@\`
|
|
31714
|
-
- \`@@role:<role>@@\`
|
|
31715
|
-
- \`@@template:<template>@@\`
|
|
31716
|
-
|
|
31717
|
-
### Config knobs
|
|
31718
|
-
|
|
31719
|
-
Typed customization knobs the app reads via \`useConfig()\`. Each has an \`alias\`,
|
|
31720
|
-
\`label\`, \`type\`, and \`default\`:
|
|
31721
|
-
|
|
31722
|
-
- \`text\` \u2192 a string default; \`boolean\` \u2192 a boolean; \`number\` \u2192 a number;
|
|
31723
|
-
\`color\` \u2192 a palette color token; \`select\` \u2192 \`options: [{ value, label }]\` with the
|
|
31724
|
-
\`default\` being one of those \`value\`s.
|
|
31725
|
-
|
|
31726
|
-
\`\`\`jsonc
|
|
31727
|
-
{ "alias": "heading", "label": "List heading", "type": "text", "default": "Items" }
|
|
31728
|
-
\`\`\`
|
|
31729
|
-
|
|
31730
|
-
Publish validates the whole contract (alias uniqueness, every cross-reference
|
|
31731
|
-
resolves, sentinel tokens name declared objects) before storing the version.
|
|
31732
|
-
`
|
|
31733
|
-
}
|
|
31734
|
-
];
|
|
31735
|
-
}
|
|
31736
31330
|
function escapeHtml(s) {
|
|
31737
31331
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
31738
31332
|
}
|
|
@@ -32802,6 +32396,123 @@ export type AppOptions = typeof OPT;
|
|
|
32802
32396
|
`;
|
|
32803
32397
|
}
|
|
32804
32398
|
|
|
32399
|
+
// src/generate_package_fields.ts
|
|
32400
|
+
var HEADER5 = `// Auto-generated by 'lotics app codegen' (linked/published app; also app pull/dev/deploy).
|
|
32401
|
+
// DO NOT EDIT \u2014 regenerated from the installation's live binding.
|
|
32402
|
+
//
|
|
32403
|
+
// A package installation resolves F/OPT/ROLE at MODULE LOAD from its binding
|
|
32404
|
+
// (contract alias \u2192 THIS workspace's concrete id) via the SDK's \`binding\` RPC.
|
|
32405
|
+
// Top-level await: the module graph waits for the binding before any importer
|
|
32406
|
+
// evaluates, so every entry is a plain string.
|
|
32407
|
+
import { getAppBinding } from "@lotics/app-sdk";
|
|
32408
|
+
|
|
32409
|
+
const binding = await getAppBinding();
|
|
32410
|
+
|
|
32411
|
+
function bound(map: Record<string, string>, key: string, kind: string): string {
|
|
32412
|
+
const id = map[key];
|
|
32413
|
+
if (id === undefined) {
|
|
32414
|
+
throw new Error(
|
|
32415
|
+
\`app_fields: \${kind} "\${key}" is not in this installation's binding \u2014 \` +
|
|
32416
|
+
\`the generated app_fields.ts is stale relative to the installed contract version.\`,
|
|
32417
|
+
);
|
|
32418
|
+
}
|
|
32419
|
+
return id;
|
|
32420
|
+
}
|
|
32421
|
+
`;
|
|
32422
|
+
function parseBinding(binding) {
|
|
32423
|
+
const entities = [];
|
|
32424
|
+
const entityByAlias = /* @__PURE__ */ new Map();
|
|
32425
|
+
const fieldByKey = /* @__PURE__ */ new Map();
|
|
32426
|
+
for (const key of Object.keys(binding.fields)) {
|
|
32427
|
+
const dot = key.indexOf(".");
|
|
32428
|
+
if (dot <= 0 || dot >= key.length - 1) continue;
|
|
32429
|
+
const entityAlias = key.slice(0, dot);
|
|
32430
|
+
const fieldAlias = key.slice(dot + 1);
|
|
32431
|
+
let entity = entityByAlias.get(entityAlias);
|
|
32432
|
+
if (entity === void 0) {
|
|
32433
|
+
entity = { alias: entityAlias, fields: [] };
|
|
32434
|
+
entityByAlias.set(entityAlias, entity);
|
|
32435
|
+
entities.push(entity);
|
|
32436
|
+
}
|
|
32437
|
+
const field = { alias: fieldAlias, key, options: [] };
|
|
32438
|
+
entity.fields.push(field);
|
|
32439
|
+
fieldByKey.set(key, field);
|
|
32440
|
+
}
|
|
32441
|
+
for (const key of Object.keys(binding.options)) {
|
|
32442
|
+
const colon = key.indexOf(":");
|
|
32443
|
+
if (colon <= 0 || colon >= key.length - 1) continue;
|
|
32444
|
+
const fieldKey = key.slice(0, colon);
|
|
32445
|
+
const optionAlias = key.slice(colon + 1);
|
|
32446
|
+
const field = fieldByKey.get(fieldKey);
|
|
32447
|
+
if (field === void 0) continue;
|
|
32448
|
+
field.options.push({ alias: optionAlias, key });
|
|
32449
|
+
}
|
|
32450
|
+
return { entities, roles: Object.keys(binding.roles) };
|
|
32451
|
+
}
|
|
32452
|
+
function emitFieldMap2(entities) {
|
|
32453
|
+
if (entities.length === 0) return `export const F = {} as const;`;
|
|
32454
|
+
const blocks = entities.map((entity) => {
|
|
32455
|
+
const lines = entity.fields.map(
|
|
32456
|
+
(field) => ` ${propKey(field.alias)}: bound(binding.fields, ${JSON.stringify(field.key)}, "field"),`
|
|
32457
|
+
);
|
|
32458
|
+
return ` ${propKey(entity.alias.toUpperCase())}: {
|
|
32459
|
+
${lines.join("\n")}
|
|
32460
|
+
},`;
|
|
32461
|
+
});
|
|
32462
|
+
return `export const F = {
|
|
32463
|
+
${blocks.join("\n")}
|
|
32464
|
+
} as const;`;
|
|
32465
|
+
}
|
|
32466
|
+
function emitOptionMap2(entities) {
|
|
32467
|
+
const entityBlocks = [];
|
|
32468
|
+
for (const entity of entities) {
|
|
32469
|
+
const fieldBlocks = [];
|
|
32470
|
+
for (const field of entity.fields) {
|
|
32471
|
+
if (field.options.length === 0) continue;
|
|
32472
|
+
const lines = field.options.map(
|
|
32473
|
+
(option) => ` ${propKey(option.alias)}: bound(binding.options, ${JSON.stringify(option.key)}, "option"),`
|
|
32474
|
+
);
|
|
32475
|
+
fieldBlocks.push(` ${propKey(field.alias)}: {
|
|
32476
|
+
${lines.join("\n")}
|
|
32477
|
+
},`);
|
|
32478
|
+
}
|
|
32479
|
+
if (fieldBlocks.length === 0) continue;
|
|
32480
|
+
entityBlocks.push(` ${propKey(entity.alias.toUpperCase())}: {
|
|
32481
|
+
${fieldBlocks.join("\n")}
|
|
32482
|
+
},`);
|
|
32483
|
+
}
|
|
32484
|
+
if (entityBlocks.length === 0) return `export const OPT = {} as const;`;
|
|
32485
|
+
return `export const OPT = {
|
|
32486
|
+
${entityBlocks.join("\n")}
|
|
32487
|
+
} as const;`;
|
|
32488
|
+
}
|
|
32489
|
+
function emitRoleMap(roles) {
|
|
32490
|
+
if (roles.length === 0) return `export const ROLE = {} as const;`;
|
|
32491
|
+
const lines = roles.map(
|
|
32492
|
+
(role) => ` ${propKey(role)}: bound(binding.roles, ${JSON.stringify(role)}, "role"),`
|
|
32493
|
+
);
|
|
32494
|
+
return `export const ROLE = {
|
|
32495
|
+
${lines.join("\n")}
|
|
32496
|
+
} as const;`;
|
|
32497
|
+
}
|
|
32498
|
+
function generatePackageAppFields(binding) {
|
|
32499
|
+
const { entities, roles } = parseBinding(binding);
|
|
32500
|
+
return `${HEADER5}
|
|
32501
|
+
${emitFieldMap2(entities)}
|
|
32502
|
+
|
|
32503
|
+
${emitOptionMap2(entities)}
|
|
32504
|
+
|
|
32505
|
+
${emitRoleMap(roles)}
|
|
32506
|
+
|
|
32507
|
+
/** Field-id alias map: \`F[<ENTITY>][<field>]\` is this installation's \`fld_\u2026\` id. */
|
|
32508
|
+
export type AppFields = typeof F;
|
|
32509
|
+
/** Select-option alias map: \`OPT[<ENTITY>][<field>][<option>]\` is this installation's \`opt_\u2026\` id. */
|
|
32510
|
+
export type AppOptions = typeof OPT;
|
|
32511
|
+
/** Role alias map: \`ROLE[<role>]\` is this installation's \`grp_\u2026\` id. */
|
|
32512
|
+
export type AppRoles = typeof ROLE;
|
|
32513
|
+
`;
|
|
32514
|
+
}
|
|
32515
|
+
|
|
32805
32516
|
// src/app_workflow_check.ts
|
|
32806
32517
|
import fs3 from "node:fs";
|
|
32807
32518
|
import path4 from "node:path";
|
|
@@ -33128,6 +32839,13 @@ function writeAppFields(projectDir, tables) {
|
|
|
33128
32839
|
fs4.writeFileSync(file2, generateAppFields(tables));
|
|
33129
32840
|
return file2;
|
|
33130
32841
|
}
|
|
32842
|
+
function writeBindingAppFields(projectDir, binding) {
|
|
32843
|
+
const dotLotics = path5.join(projectDir, ".lotics");
|
|
32844
|
+
fs4.mkdirSync(dotLotics, { recursive: true });
|
|
32845
|
+
const file2 = path5.join(dotLotics, "app_fields.ts");
|
|
32846
|
+
fs4.writeFileSync(file2, generatePackageAppFields(binding));
|
|
32847
|
+
return file2;
|
|
32848
|
+
}
|
|
33131
32849
|
async function appCodegen(args) {
|
|
33132
32850
|
const projectDir = path5.resolve(args.projectDir ?? process.cwd());
|
|
33133
32851
|
const meta3 = readAppMeta(projectDir);
|
|
@@ -33143,14 +32861,24 @@ async function appCodegen(args) {
|
|
|
33143
32861
|
);
|
|
33144
32862
|
return;
|
|
33145
32863
|
}
|
|
33146
|
-
const tableIds = resolveCodegenTableIds(projectDir, meta3.queries ?? {});
|
|
33147
32864
|
try {
|
|
33148
|
-
const
|
|
33149
|
-
|
|
33150
|
-
|
|
32865
|
+
const app = await args.client.getApp(meta3.app_id);
|
|
32866
|
+
if (app.package_id) {
|
|
32867
|
+
const binding = await args.client.appBinding(meta3.app_id);
|
|
32868
|
+
const fieldsPath = writeBindingAppFields(projectDir, binding);
|
|
32869
|
+
const count = Object.keys(binding.fields).length;
|
|
32870
|
+
console.error(
|
|
32871
|
+
`Regenerated ${fieldsPath} (binding form \u2014 ${count} field alias${count === 1 ? "" : "es"} resolved per-install)`
|
|
32872
|
+
);
|
|
32873
|
+
} else {
|
|
32874
|
+
const tableIds = resolveCodegenTableIds(projectDir, meta3.queries ?? {});
|
|
32875
|
+
const tables = await args.client.getWorkspaceSchema(tableIds);
|
|
32876
|
+
const fieldsPath = writeAppFields(projectDir, tables);
|
|
32877
|
+
console.error(`Regenerated ${fieldsPath} (${tables.length} table${tables.length === 1 ? "" : "s"})`);
|
|
32878
|
+
}
|
|
33151
32879
|
} catch (err2) {
|
|
33152
32880
|
console.error(
|
|
33153
|
-
`\u26A0 Could not
|
|
32881
|
+
`\u26A0 Could not regenerate .lotics/app_fields.ts (${err2 instanceof Error ? err2.message : String(err2)}). Kept the existing file.`
|
|
33154
32882
|
);
|
|
33155
32883
|
}
|
|
33156
32884
|
await refreshWorkflowGlobals(args.client, projectDir, meta3.app_id, Object.keys(meta3.workflows ?? {}));
|
|
@@ -33834,10 +33562,8 @@ function escapeRegExp(s) {
|
|
|
33834
33562
|
}
|
|
33835
33563
|
|
|
33836
33564
|
// src/package_commands.ts
|
|
33837
|
-
import
|
|
33838
|
-
import
|
|
33839
|
-
import { tmpdir as tmpdir2 } from "node:os";
|
|
33840
|
-
import { createHash } from "node:crypto";
|
|
33565
|
+
import fs5 from "node:fs";
|
|
33566
|
+
import path6 from "node:path";
|
|
33841
33567
|
|
|
33842
33568
|
// ../../node_modules/zod/v4/classic/external.js
|
|
33843
33569
|
var external_exports = {};
|
|
@@ -49725,7 +49451,7 @@ var appSchema = zod_default.object({
|
|
|
49725
49451
|
// avoid a circular import; the shapes mirror Binding / AppInstallationConfig.
|
|
49726
49452
|
package_id: zod_default.string().nullable().optional().describe("App-package registry id this app was installed from; null for a bespoke app or after eject."),
|
|
49727
49453
|
package_version: zod_default.number().int().nullable().optional().describe("Installed package version (the upgrade pin); null for a bespoke app or after eject."),
|
|
49728
|
-
binding: zod_default.record(zod_default.string(), zod_default.record(zod_default.string(), zod_default.string())).nullable().optional().describe("App-package install join: per-namespace (entities/fields/options/templates/roles) alias \u2192 concrete-id maps. Null for a bespoke app."),
|
|
49454
|
+
binding: zod_default.record(zod_default.string(), zod_default.record(zod_default.string(), zod_default.string())).nullable().optional().describe("App-package install join: per-namespace (entities/fields/options/templates/roles/workflows/knowledge) alias \u2192 concrete-id maps. Null for a bespoke app."),
|
|
49729
49455
|
config: zod_default.record(zod_default.string(), zod_default.union([zod_default.string(), zod_default.number(), zod_default.boolean()])).nullable().optional().describe("Installation-level customization config values (the customization-ladder first rung). Null for a bespoke app."),
|
|
49730
49456
|
ejected_at: zod_default.string().nullable().optional().describe("When the app was ejected from its package (severing upgrades); null if never ejected."),
|
|
49731
49457
|
created_at: zod_default.string().describe("Timestamp when app was created"),
|
|
@@ -49768,7 +49494,6 @@ var workspaceSchema = zod_default.object({
|
|
|
49768
49494
|
default_currency: zod_default.string().describe("Default currency code for the workspace"),
|
|
49769
49495
|
timezone: zod_default.string().describe("IANA timezone name (e.g., 'America/New_York', 'Asia/Tokyo')"),
|
|
49770
49496
|
organization_id: zod_default.string().describe("Organization ID this workspace belongs to"),
|
|
49771
|
-
is_dev: zod_default.boolean().optional().describe("Marks a throwaway app-package dev workspace \u2014 gates `lotics package dev/sync/reset`."),
|
|
49772
49497
|
created_at: zod_default.string().describe("Timestamp when workspace was created")
|
|
49773
49498
|
});
|
|
49774
49499
|
var memberGroupSchema = zod_default.object({
|
|
@@ -50315,7 +50040,14 @@ var bindingSchema = zod_default.object({
|
|
|
50315
50040
|
// it, and hashes look rows up through it. Excluded from BINDING_NAMESPACES:
|
|
50316
50041
|
// a deleted row is a modified-core finding (fingerprints), never binding
|
|
50317
50042
|
// drift, so validateBinding/applyDriftResolutions ignore it.
|
|
50318
|
-
workflows: bindingMapSchema.default({})
|
|
50043
|
+
workflows: bindingMapSchema.default({}),
|
|
50044
|
+
// The app's BUNDLED knowledge corpus: contract knowledge alias → the
|
|
50045
|
+
// workspace's concrete `kdc_` id. One row, one pin — the app installation IS
|
|
50046
|
+
// the anchor for its bundled docs (`package_content_installations` rows are
|
|
50047
|
+
// standalone content packages only). Excluded from BINDING_NAMESPACES: a
|
|
50048
|
+
// gone/edited doc is detected by the knowledge upgrade preview (live doc +
|
|
50049
|
+
// content-sha reads), never by validateBinding's live-id sweep.
|
|
50050
|
+
knowledge: bindingMapSchema.default({})
|
|
50319
50051
|
});
|
|
50320
50052
|
var BINDING_NAMESPACES = ["entities", "fields", "options", "templates", "roles"];
|
|
50321
50053
|
var contentBindingMapSchema = zod_default.record(zod_default.string().min(1), zod_default.string().min(1));
|
|
@@ -50349,10 +50081,6 @@ var packageArtifactHashesSchema = zod_default.object({
|
|
|
50349
50081
|
agents: zod_default.record(zod_default.string(), zod_default.string())
|
|
50350
50082
|
});
|
|
50351
50083
|
var modificationResolutionSchema = zod_default.union([zod_default.literal("revert"), zod_default.literal("keep")]);
|
|
50352
|
-
var upgradeResolutionsSchema = zod_default.record(
|
|
50353
|
-
zod_default.string(),
|
|
50354
|
-
zod_default.union([driftResolutionSchema, modificationResolutionSchema])
|
|
50355
|
-
);
|
|
50356
50084
|
var KNOWLEDGE_UPGRADE_CHANGES = ["added", "changed", "removed", "drifted"];
|
|
50357
50085
|
var knowledgeUpgradeEntrySchema = zod_default.object({
|
|
50358
50086
|
alias: zod_default.string(),
|
|
@@ -50381,15 +50109,15 @@ function validKnowledgeResolutions(change) {
|
|
|
50381
50109
|
return [];
|
|
50382
50110
|
}
|
|
50383
50111
|
}
|
|
50112
|
+
var upgradeResolutionsSchema = zod_default.record(
|
|
50113
|
+
zod_default.string(),
|
|
50114
|
+
zod_default.union([driftResolutionSchema, modificationResolutionSchema, knowledgeResolutionSchema])
|
|
50115
|
+
);
|
|
50384
50116
|
var knowledgeBindConsentSchema = zod_default.record(zod_default.string(), zod_default.string());
|
|
50385
50117
|
var knowledgeUpgradeResolutionsSchema = zod_default.object({
|
|
50386
50118
|
resolutions: zod_default.record(zod_default.string(), knowledgeResolutionSchema).default({}),
|
|
50387
50119
|
bind_to: knowledgeBindConsentSchema.default({})
|
|
50388
50120
|
});
|
|
50389
|
-
var contentUpgradeResolutionsSchema = zod_default.object({
|
|
50390
|
-
knowledge: knowledgeUpgradeResolutionsSchema.default({ resolutions: {}, bind_to: {} }),
|
|
50391
|
-
templates: zod_default.record(zod_default.string(), modificationResolutionSchema).default({})
|
|
50392
|
-
});
|
|
50393
50121
|
var knowledgeBoundRefSchema = zod_default.object({
|
|
50394
50122
|
alias: zod_default.string(),
|
|
50395
50123
|
name: zod_default.string(),
|
|
@@ -50403,261 +50131,38 @@ var appInstallationConfigSchema = zod_default.record(
|
|
|
50403
50131
|
);
|
|
50404
50132
|
var appInstallationProvenanceSchema = zod_default.enum(["install", "adopt"]);
|
|
50405
50133
|
|
|
50406
|
-
// src/generate_package_fields.ts
|
|
50407
|
-
var HEADER5 = `// Auto-generated by 'lotics package new/extract/dev/sync' from contract.json.
|
|
50408
|
-
// DO NOT EDIT \u2014 regenerated whenever the contract changes.
|
|
50409
|
-
//
|
|
50410
|
-
// Package apps resolve F/OPT/ROLE at MODULE LOAD from the installation's
|
|
50411
|
-
// binding (contract alias \u2192 THIS workspace's concrete id) via the SDK's
|
|
50412
|
-
// \`binding\` RPC. Top-level await: the module graph waits for the binding
|
|
50413
|
-
// before any importer evaluates, so every entry is a plain string.
|
|
50414
|
-
import { getAppBinding } from "@lotics/app-sdk";
|
|
50415
|
-
|
|
50416
|
-
const binding = await getAppBinding();
|
|
50417
|
-
|
|
50418
|
-
function bound(map: Record<string, string>, key: string, kind: string): string {
|
|
50419
|
-
const id = map[key];
|
|
50420
|
-
if (id === undefined) {
|
|
50421
|
-
throw new Error(
|
|
50422
|
-
\`app_fields: \${kind} "\${key}" is not in this installation's binding \u2014 \` +
|
|
50423
|
-
\`the generated app_fields.ts is stale relative to the installed contract version.\`,
|
|
50424
|
-
);
|
|
50425
|
-
}
|
|
50426
|
-
return id;
|
|
50427
|
-
}
|
|
50428
|
-
`;
|
|
50429
|
-
function generatePackageAppFields(contract) {
|
|
50430
|
-
const entities = contract.entities ?? [];
|
|
50431
|
-
const roles = contract.roles ?? [];
|
|
50432
|
-
const fieldBlocks = [];
|
|
50433
|
-
for (const entity of entities) {
|
|
50434
|
-
const lines = (entity.fields ?? []).map(
|
|
50435
|
-
(field) => ` ${propKey(field.alias)}: bound(binding.fields, ${JSON.stringify(`${entity.alias}.${field.alias}`)}, "field"),`
|
|
50436
|
-
);
|
|
50437
|
-
if (lines.length === 0) continue;
|
|
50438
|
-
fieldBlocks.push(` ${propKey(entity.alias.toUpperCase())}: {
|
|
50439
|
-
${lines.join("\n")}
|
|
50440
|
-
},`);
|
|
50441
|
-
}
|
|
50442
|
-
const optionBlocks = [];
|
|
50443
|
-
for (const entity of entities) {
|
|
50444
|
-
const perField = [];
|
|
50445
|
-
for (const field of entity.fields ?? []) {
|
|
50446
|
-
const options = field.options ?? [];
|
|
50447
|
-
if (options.length === 0) continue;
|
|
50448
|
-
const lines = options.map(
|
|
50449
|
-
(option) => ` ${propKey(option.alias)}: bound(binding.options, ${JSON.stringify(`${entity.alias}.${field.alias}:${option.alias}`)}, "option"),`
|
|
50450
|
-
);
|
|
50451
|
-
perField.push(` ${propKey(field.alias)}: {
|
|
50452
|
-
${lines.join("\n")}
|
|
50453
|
-
},`);
|
|
50454
|
-
}
|
|
50455
|
-
if (perField.length > 0) {
|
|
50456
|
-
optionBlocks.push(` ${propKey(entity.alias.toUpperCase())}: {
|
|
50457
|
-
${perField.join("\n")}
|
|
50458
|
-
},`);
|
|
50459
|
-
}
|
|
50460
|
-
}
|
|
50461
|
-
const fMap = fieldBlocks.length > 0 ? `export const F = {
|
|
50462
|
-
${fieldBlocks.join("\n")}
|
|
50463
|
-
} as const;` : `export const F = {} as const;`;
|
|
50464
|
-
const optMap = optionBlocks.length > 0 ? `export const OPT = {
|
|
50465
|
-
${optionBlocks.join("\n")}
|
|
50466
|
-
} as const;` : `export const OPT = {} as const;`;
|
|
50467
|
-
const roleMap = roles.length > 0 ? `export const ROLE = {
|
|
50468
|
-
${roles.map((role) => ` ${propKey(role.alias)}: bound(binding.roles, ${JSON.stringify(role.alias)}, "role"),`).join("\n")}
|
|
50469
|
-
} as const;` : `export const ROLE = {} as const;`;
|
|
50470
|
-
return `${HEADER5}
|
|
50471
|
-
${fMap}
|
|
50472
|
-
|
|
50473
|
-
${optMap}
|
|
50474
|
-
|
|
50475
|
-
${roleMap}
|
|
50476
|
-
|
|
50477
|
-
/** Field-id alias map: \`F[<ENTITY>][<field>]\` is this installation's \`fld_\u2026\` id. */
|
|
50478
|
-
export type AppFields = typeof F;
|
|
50479
|
-
/** Select-option alias map: \`OPT[<ENTITY>][<field>][<option>]\` is this installation's \`opt_\u2026\` id. */
|
|
50480
|
-
export type AppOptions = typeof OPT;
|
|
50481
|
-
/** Role alias map: \`ROLE[<role>]\` is this installation's \`grp_\u2026\` id. */
|
|
50482
|
-
export type AppRoles = typeof ROLE;
|
|
50483
|
-
`;
|
|
50484
|
-
}
|
|
50485
|
-
|
|
50486
|
-
// src/file_command_io.ts
|
|
50487
|
-
import fs5 from "node:fs";
|
|
50488
|
-
import path6 from "node:path";
|
|
50489
|
-
var CliError = class extends Error {
|
|
50490
|
-
constructor(message) {
|
|
50491
|
-
super(message);
|
|
50492
|
-
this.name = "CliError";
|
|
50493
|
-
}
|
|
50494
|
-
};
|
|
50495
|
-
function fail(message) {
|
|
50496
|
-
throw new CliError(message);
|
|
50497
|
-
}
|
|
50498
|
-
function writeFileAtomic(filePath, bytes) {
|
|
50499
|
-
const dir = path6.dirname(path6.resolve(filePath));
|
|
50500
|
-
const tmp = path6.join(dir, `.${path6.basename(filePath)}.${process.pid}.${Date.now()}.tmp`);
|
|
50501
|
-
fs5.writeFileSync(tmp, bytes);
|
|
50502
|
-
try {
|
|
50503
|
-
fs5.renameSync(tmp, filePath);
|
|
50504
|
-
} catch (error51) {
|
|
50505
|
-
try {
|
|
50506
|
-
fs5.unlinkSync(tmp);
|
|
50507
|
-
} catch {
|
|
50508
|
-
}
|
|
50509
|
-
throw error51;
|
|
50510
|
-
}
|
|
50511
|
-
console.error(`Wrote ${filePath}`);
|
|
50512
|
-
}
|
|
50513
|
-
|
|
50514
50134
|
// src/package_commands.ts
|
|
50515
|
-
var CONTRACT_FILE = "contract.json";
|
|
50516
|
-
var ADOPT_BINDING_FILE = "adopt_binding.json";
|
|
50517
|
-
var PACKAGE_TEMPLATE_KINDS = /* @__PURE__ */ new Set([
|
|
50518
|
-
"html",
|
|
50519
|
-
"email",
|
|
50520
|
-
"excel",
|
|
50521
|
-
"word",
|
|
50522
|
-
"pdf-form"
|
|
50523
|
-
]);
|
|
50524
50135
|
function packageJsonPath(projectDir) {
|
|
50525
|
-
return
|
|
50136
|
+
return path6.join(projectDir, "package.json");
|
|
50526
50137
|
}
|
|
50527
50138
|
function isPlainObject2(value) {
|
|
50528
50139
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
50529
50140
|
}
|
|
50530
|
-
function
|
|
50141
|
+
function readLocalAppManifest(projectDir) {
|
|
50531
50142
|
const pkgPath2 = packageJsonPath(projectDir);
|
|
50532
|
-
if (!
|
|
50533
|
-
|
|
50534
|
-
|
|
50535
|
-
|
|
50536
|
-
|
|
50537
|
-
const
|
|
50538
|
-
if (
|
|
50539
|
-
|
|
50540
|
-
|
|
50541
|
-
|
|
50542
|
-
throw new Error(
|
|
50543
|
-
`${pkgPath2} has no lotics.package manifest \u2014 not a package project. Use "lotics package new <name>".`
|
|
50544
|
-
);
|
|
50545
|
-
}
|
|
50546
|
-
const dev = {};
|
|
50547
|
-
if (isPlainObject2(pkg2.dev)) {
|
|
50548
|
-
for (const [ws, entry] of Object.entries(pkg2.dev)) {
|
|
50549
|
-
if (isPlainObject2(entry) && typeof entry.app_id === "string" && typeof entry.version === "number") {
|
|
50550
|
-
dev[ws] = { app_id: entry.app_id, version: entry.version };
|
|
50551
|
-
}
|
|
50552
|
-
}
|
|
50553
|
-
}
|
|
50554
|
-
const knowledge = {};
|
|
50555
|
-
if (isPlainObject2(pkg2.knowledge)) {
|
|
50556
|
-
for (const [alias, entry] of Object.entries(pkg2.knowledge)) {
|
|
50557
|
-
if (!isPlainObject2(entry) || typeof entry.name !== "string" || entry.name.length === 0) {
|
|
50558
|
-
throw new Error(
|
|
50559
|
-
`${pkgPath2}: lotics.package.knowledge.${alias} must be an object with a non-empty "name".`
|
|
50560
|
-
);
|
|
50561
|
-
}
|
|
50562
|
-
knowledge[alias] = {
|
|
50563
|
-
name: entry.name,
|
|
50564
|
-
description: typeof entry.description === "string" ? entry.description : null,
|
|
50565
|
-
active_by_default: typeof entry.active_by_default === "boolean" ? entry.active_by_default : true
|
|
50566
|
-
};
|
|
50567
|
-
}
|
|
50568
|
-
}
|
|
50569
|
-
const templates = {};
|
|
50570
|
-
if (isPlainObject2(pkg2.templates)) {
|
|
50571
|
-
for (const [alias, entry] of Object.entries(pkg2.templates)) {
|
|
50572
|
-
if (!isPlainObject2(entry) || typeof entry.name !== "string" || entry.name.length === 0 || typeof entry.type !== "string" || !PACKAGE_TEMPLATE_KINDS.has(entry.type) || typeof entry.file !== "string" || entry.file.length === 0) {
|
|
50573
|
-
throw new Error(
|
|
50574
|
-
`${pkgPath2}: lotics.package.templates.${alias} must be an object with a non-empty "name", a "type" of html|email|excel|word|pdf-form, and a non-empty "file" (a name in templates/).`
|
|
50575
|
-
);
|
|
50143
|
+
if (!fs5.existsSync(pkgPath2)) return null;
|
|
50144
|
+
const parsed = JSON.parse(fs5.readFileSync(pkgPath2, "utf-8"));
|
|
50145
|
+
if (!isPlainObject2(parsed)) return null;
|
|
50146
|
+
const lotics = isPlainObject2(parsed.lotics) ? parsed.lotics : {};
|
|
50147
|
+
const app_id = typeof lotics.app_id === "string" ? lotics.app_id : null;
|
|
50148
|
+
const knowledge = [];
|
|
50149
|
+
if (Array.isArray(lotics.knowledge)) {
|
|
50150
|
+
for (const entry of lotics.knowledge) {
|
|
50151
|
+
if (isPlainObject2(entry) && typeof entry.alias === "string" && typeof entry.doc_id === "string") {
|
|
50152
|
+
knowledge.push({ alias: entry.alias, doc_id: entry.doc_id });
|
|
50576
50153
|
}
|
|
50577
|
-
templates[alias] = {
|
|
50578
|
-
name: entry.name,
|
|
50579
|
-
type: entry.type,
|
|
50580
|
-
file: entry.file
|
|
50581
|
-
};
|
|
50582
50154
|
}
|
|
50583
50155
|
}
|
|
50584
|
-
|
|
50585
|
-
const manifest = {
|
|
50586
|
-
id: typeof pkg2.id === "string" ? pkg2.id : null,
|
|
50587
|
-
name: pkg2.name,
|
|
50588
|
-
description: typeof pkg2.description === "string" ? pkg2.description : null,
|
|
50589
|
-
kind: pkg2.kind === "content" ? "content" : "app",
|
|
50590
|
-
version: typeof pkg2.version === "number" ? pkg2.version : null,
|
|
50591
|
-
knowledge,
|
|
50592
|
-
templates,
|
|
50593
|
-
knowledge_expects,
|
|
50594
|
-
dev
|
|
50595
|
-
};
|
|
50596
|
-
return { pkgJson: parsed, manifest };
|
|
50597
|
-
}
|
|
50598
|
-
function writePackageManifest(projectDir, project) {
|
|
50599
|
-
const next = {
|
|
50600
|
-
...project.pkgJson,
|
|
50601
|
-
lotics: {
|
|
50602
|
-
...isPlainObject2(project.pkgJson.lotics) ? project.pkgJson.lotics : {},
|
|
50603
|
-
package: project.manifest
|
|
50604
|
-
}
|
|
50605
|
-
};
|
|
50606
|
-
writeFileAtomic(
|
|
50607
|
-
packageJsonPath(projectDir),
|
|
50608
|
-
new TextEncoder().encode(JSON.stringify(next, null, 2) + "\n")
|
|
50609
|
-
);
|
|
50156
|
+
return { app_id, knowledge };
|
|
50610
50157
|
}
|
|
50611
|
-
function
|
|
50612
|
-
|
|
50613
|
-
|
|
50614
|
-
|
|
50615
|
-
|
|
50616
|
-
...pkgJson,
|
|
50617
|
-
lotics: { ...lotics, package: pkgWithoutDev }
|
|
50618
|
-
};
|
|
50619
|
-
}
|
|
50620
|
-
function draftPackageProjectFromApp(appPkgJson, args) {
|
|
50621
|
-
const { lotics: _appManifest, ...rest } = appPkgJson;
|
|
50622
|
-
return {
|
|
50623
|
-
pkgJson: rest,
|
|
50624
|
-
manifest: {
|
|
50625
|
-
id: null,
|
|
50626
|
-
name: args.name,
|
|
50627
|
-
description: args.description,
|
|
50628
|
-
// Extraction promotes a bespoke APP to a package — always app kind.
|
|
50629
|
-
kind: "app",
|
|
50630
|
-
version: null,
|
|
50631
|
-
knowledge: {},
|
|
50632
|
-
templates: {},
|
|
50633
|
-
knowledge_expects: [],
|
|
50634
|
-
dev: {}
|
|
50635
|
-
}
|
|
50636
|
-
};
|
|
50637
|
-
}
|
|
50638
|
-
function parseAdoptBindingFile(raw, expectedAppId) {
|
|
50639
|
-
if (!isPlainObject2(raw) || typeof raw.app_id !== "string" || typeof raw.workspace_id !== "string" || !isPlainObject2(raw.binding)) {
|
|
50640
|
-
throw new Error(
|
|
50641
|
-
"Malformed .lotics/adopt_binding.json \u2014 expected { app_id, workspace_id, binding }."
|
|
50642
|
-
);
|
|
50643
|
-
}
|
|
50644
|
-
if (raw.app_id !== expectedAppId) {
|
|
50645
|
-
throw new Error(
|
|
50646
|
-
`.lotics/adopt_binding.json records app ${raw.app_id}, but you are adopting ${expectedAppId}. A binding maps one app's concrete ids and must never be applied to another \u2014 run "lotics package extract ${expectedAppId}" to produce the right pin.`
|
|
50647
|
-
);
|
|
50648
|
-
}
|
|
50649
|
-
const knowledge_binding = {};
|
|
50650
|
-
if (isPlainObject2(raw.knowledge_binding)) {
|
|
50651
|
-
for (const [alias, id] of Object.entries(raw.knowledge_binding)) {
|
|
50652
|
-
if (typeof id === "string") knowledge_binding[alias] = id;
|
|
50158
|
+
function parseRenameFlags(renames) {
|
|
50159
|
+
return renames.map((entry) => {
|
|
50160
|
+
const eq = entry.indexOf("=");
|
|
50161
|
+
if (eq <= 0 || eq === entry.length - 1) {
|
|
50162
|
+
throw new Error(`Invalid --rename "${entry}" \u2014 expected old=new (an alias to rename before v1 freezes it).`);
|
|
50653
50163
|
}
|
|
50654
|
-
|
|
50655
|
-
|
|
50656
|
-
app_id: raw.app_id,
|
|
50657
|
-
workspace_id: raw.workspace_id,
|
|
50658
|
-
binding: raw.binding,
|
|
50659
|
-
knowledge_binding
|
|
50660
|
-
};
|
|
50164
|
+
return { from: entry.slice(0, eq), to: entry.slice(eq + 1) };
|
|
50165
|
+
});
|
|
50661
50166
|
}
|
|
50662
50167
|
function formatExtractReport(report) {
|
|
50663
50168
|
const order = ["error", "warning", "info"];
|
|
@@ -50666,573 +50171,32 @@ function formatExtractReport(report) {
|
|
|
50666
50171
|
);
|
|
50667
50172
|
return { lines, hasError: report.some((f) => f.severity === "error") };
|
|
50668
50173
|
}
|
|
50669
|
-
function
|
|
50670
|
-
const dir = path7.join(projectDir, ".lotics");
|
|
50671
|
-
fs6.mkdirSync(dir, { recursive: true });
|
|
50672
|
-
return dir;
|
|
50673
|
-
}
|
|
50674
|
-
function cmpVersions(a, b) {
|
|
50675
|
-
const pa = a.split(".").map(Number);
|
|
50676
|
-
const pb = b.split(".").map(Number);
|
|
50677
|
-
for (let i2 = 0; i2 < 3; i2++) {
|
|
50678
|
-
const d = (pa[i2] || 0) - (pb[i2] || 0);
|
|
50679
|
-
if (d !== 0) return d;
|
|
50680
|
-
}
|
|
50681
|
-
return 0;
|
|
50682
|
-
}
|
|
50683
|
-
function packageSdkRange(sdkLatest) {
|
|
50684
|
-
const version2 = sdkLatest !== null && cmpVersions(sdkLatest, STARTER_FALLBACK_SDK_VERSION) > 0 ? sdkLatest : STARTER_FALLBACK_SDK_VERSION;
|
|
50685
|
-
return `^${version2}`;
|
|
50686
|
-
}
|
|
50687
|
-
function readContract(projectDir) {
|
|
50688
|
-
const contractPath = path7.join(projectDir, CONTRACT_FILE);
|
|
50689
|
-
if (!fs6.existsSync(contractPath)) {
|
|
50690
|
-
throw new Error(
|
|
50691
|
-
`No ${CONTRACT_FILE} in ${projectDir}. A package project declares its data model there.`
|
|
50692
|
-
);
|
|
50693
|
-
}
|
|
50694
|
-
return JSON.parse(fs6.readFileSync(contractPath, "utf-8"));
|
|
50695
|
-
}
|
|
50696
|
-
function foldKnowledgeIntoContract(projectDir, project, contract) {
|
|
50697
|
-
const aliases = Object.keys(project.manifest.knowledge);
|
|
50698
|
-
const expects = project.manifest.knowledge_expects;
|
|
50699
|
-
if (aliases.length === 0 && expects.length === 0) return contract;
|
|
50700
|
-
const knowledge = {};
|
|
50701
|
-
for (const alias of aliases) {
|
|
50702
|
-
const entry = project.manifest.knowledge[alias];
|
|
50703
|
-
const contentRef = `knowledge/${alias}.md`;
|
|
50704
|
-
const filePath = path7.join(projectDir, "knowledge", `${alias}.md`);
|
|
50705
|
-
if (!fs6.existsSync(filePath)) {
|
|
50706
|
-
throw new Error(
|
|
50707
|
-
`Declared knowledge doc "${alias}" (lotics.package.knowledge) has no ${contentRef} in the project. Create the file, or remove the manifest entry.`
|
|
50708
|
-
);
|
|
50709
|
-
}
|
|
50710
|
-
const bytes = fs6.readFileSync(filePath);
|
|
50711
|
-
knowledge[alias] = {
|
|
50712
|
-
name: entry.name,
|
|
50713
|
-
description: entry.description ?? "",
|
|
50714
|
-
content_ref: contentRef,
|
|
50715
|
-
content_sha256: createHash("sha256").update(bytes).digest("hex"),
|
|
50716
|
-
active_by_default: entry.active_by_default
|
|
50717
|
-
};
|
|
50718
|
-
}
|
|
50719
|
-
return { ...contract, knowledge, knowledge_expects: expects };
|
|
50720
|
-
}
|
|
50721
|
-
function foldTemplatesIntoContract(projectDir, project, contract) {
|
|
50722
|
-
if (!isPlainObject2(contract)) {
|
|
50723
|
-
throw new Error(`${CONTRACT_FILE} in ${projectDir} must be a JSON object.`);
|
|
50724
|
-
}
|
|
50725
|
-
const aliases = Object.keys(project.manifest.templates);
|
|
50726
|
-
if (aliases.length === 0) return contract;
|
|
50727
|
-
const existing = Array.isArray(contract.templates) ? contract.templates : [];
|
|
50728
|
-
const existingAliases = new Set(
|
|
50729
|
-
existing.filter((t) => isPlainObject2(t)).map((t) => t.alias).filter((a) => typeof a === "string")
|
|
50730
|
-
);
|
|
50731
|
-
const folded = aliases.map((alias) => {
|
|
50732
|
-
if (existingAliases.has(alias)) {
|
|
50733
|
-
throw new Error(
|
|
50734
|
-
`Template alias "${alias}" is declared in both lotics.package.templates and ${CONTRACT_FILE} \u2014 declare each template once.`
|
|
50735
|
-
);
|
|
50736
|
-
}
|
|
50737
|
-
const entry = project.manifest.templates[alias];
|
|
50738
|
-
const filePath = path7.join(projectDir, "templates", entry.file);
|
|
50739
|
-
if (!fs6.existsSync(filePath)) {
|
|
50740
|
-
throw new Error(
|
|
50741
|
-
`Declared template "${alias}" (lotics.package.templates) has no templates/${entry.file} in the project. Stage the file, then rebuild.`
|
|
50742
|
-
);
|
|
50743
|
-
}
|
|
50744
|
-
if (entry.type === "html" || entry.type === "email") {
|
|
50745
|
-
return { alias, label: entry.name, type: entry.type, content: fs6.readFileSync(filePath, "utf-8") };
|
|
50746
|
-
}
|
|
50747
|
-
return { alias, label: entry.name, type: entry.type, bytes_ref: `templates/${entry.file}` };
|
|
50748
|
-
});
|
|
50749
|
-
return { ...contract, templates: [...existing, ...folded] };
|
|
50750
|
-
}
|
|
50751
|
-
function foldTemplateShasIntoContract(projectDir, contract) {
|
|
50752
|
-
const templates = contract.templates;
|
|
50753
|
-
if (!Array.isArray(templates) || templates.length === 0) return contract;
|
|
50754
|
-
const withShas = templates.map((template) => {
|
|
50755
|
-
if (!isPlainObject2(template) || typeof template.type !== "string" || typeof template.alias !== "string") {
|
|
50756
|
-
return template;
|
|
50757
|
-
}
|
|
50758
|
-
if (template.type === "html" || template.type === "email") {
|
|
50759
|
-
if (typeof template.content !== "string") {
|
|
50760
|
-
throw new Error(`Inline template "${template.alias}" must carry a string "content".`);
|
|
50761
|
-
}
|
|
50762
|
-
return {
|
|
50763
|
-
...template,
|
|
50764
|
-
content_sha256: createHash("sha256").update(Buffer.from(template.content, "utf-8")).digest("hex")
|
|
50765
|
-
};
|
|
50766
|
-
}
|
|
50767
|
-
if (typeof template.bytes_ref !== "string") {
|
|
50768
|
-
throw new Error(`File-backed template "${template.alias}" must carry a string "bytes_ref".`);
|
|
50769
|
-
}
|
|
50770
|
-
const filePath = path7.join(projectDir, template.bytes_ref);
|
|
50771
|
-
if (!fs6.existsSync(filePath)) {
|
|
50772
|
-
throw new Error(
|
|
50773
|
-
`Template "${template.alias}" references "${template.bytes_ref}", which is not in the project. Stage the file, then rebuild.`
|
|
50774
|
-
);
|
|
50775
|
-
}
|
|
50776
|
-
const bytes = fs6.readFileSync(filePath);
|
|
50777
|
-
return { ...template, content_sha256: createHash("sha256").update(bytes).digest("hex") };
|
|
50778
|
-
});
|
|
50779
|
-
return { ...contract, templates: withShas };
|
|
50780
|
-
}
|
|
50781
|
-
function writePackageAppFields(projectDir) {
|
|
50782
|
-
const contract = readContract(projectDir);
|
|
50783
|
-
const dotLotics = path7.join(projectDir, ".lotics");
|
|
50784
|
-
fs6.mkdirSync(dotLotics, { recursive: true });
|
|
50785
|
-
fs6.writeFileSync(path7.join(dotLotics, "app_fields.ts"), generatePackageAppFields(contract));
|
|
50786
|
-
console.error("Wrote .lotics/app_fields.ts (contract-derived, runtime-resolved)");
|
|
50787
|
-
}
|
|
50788
|
-
var SOURCE_STAGE_EXCLUDES = /* @__PURE__ */ new Set([
|
|
50789
|
-
"node_modules",
|
|
50790
|
-
"dist",
|
|
50791
|
-
".lotics",
|
|
50792
|
-
".git",
|
|
50793
|
-
"bundle.tar.gz",
|
|
50794
|
-
"package.json"
|
|
50795
|
-
]);
|
|
50796
|
-
function stagePackageSource(projectDir, sourceStage) {
|
|
50797
|
-
const project = readPackageProject(projectDir);
|
|
50798
|
-
fs6.mkdirSync(sourceStage, { recursive: true });
|
|
50799
|
-
for (const entry of fs6.readdirSync(projectDir)) {
|
|
50800
|
-
if (SOURCE_STAGE_EXCLUDES.has(entry) || entry.endsWith(".tsbuildinfo")) continue;
|
|
50801
|
-
fs6.cpSync(path7.join(projectDir, entry), path7.join(sourceStage, entry), { recursive: true });
|
|
50802
|
-
}
|
|
50803
|
-
fs6.writeFileSync(
|
|
50804
|
-
path7.join(sourceStage, "package.json"),
|
|
50805
|
-
JSON.stringify(sanitizePackageJsonForSource(project.pkgJson), null, 2) + "\n"
|
|
50806
|
-
);
|
|
50807
|
-
}
|
|
50808
|
-
async function buildPackageBundle(projectDir) {
|
|
50809
|
-
const { manifest } = readPackageProject(projectDir);
|
|
50810
|
-
const stage = fs6.mkdtempSync(path7.join(tmpdir2(), "lotics-pkg-"));
|
|
50811
|
-
const bundlePath = path7.join(tmpdir2(), `lotics-bundle-${Date.now()}.tar.gz`);
|
|
50812
|
-
try {
|
|
50813
|
-
let distDir;
|
|
50814
|
-
if (manifest.kind === "content") {
|
|
50815
|
-
console.error("Content package \u2014 skipping app build.");
|
|
50816
|
-
distDir = path7.join(stage, "empty-dist");
|
|
50817
|
-
fs6.mkdirSync(distDir, { recursive: true });
|
|
50818
|
-
fs6.writeFileSync(
|
|
50819
|
-
path7.join(distDir, ".content-package"),
|
|
50820
|
-
"Content package \u2014 no frontend bundle. Content ships in source.tar.gz under knowledge/.\n"
|
|
50821
|
-
);
|
|
50822
|
-
} else {
|
|
50823
|
-
console.error("Building...");
|
|
50824
|
-
await runNpm(["run", "build"], projectDir);
|
|
50825
|
-
distDir = path7.join(projectDir, "dist");
|
|
50826
|
-
if (!fs6.existsSync(distDir)) {
|
|
50827
|
-
throw new Error(
|
|
50828
|
-
`Build did not produce a dist/ directory in ${projectDir}. Check that 'npm run build' is configured correctly.`
|
|
50829
|
-
);
|
|
50830
|
-
}
|
|
50831
|
-
}
|
|
50832
|
-
console.error("Packaging source...");
|
|
50833
|
-
const sourceStage = path7.join(stage, "source");
|
|
50834
|
-
stagePackageSource(projectDir, sourceStage);
|
|
50835
|
-
await runTar(["-czf", path7.join(stage, "source.tar.gz"), "-C", sourceStage, "."], projectDir);
|
|
50836
|
-
console.error("Packaging dist...");
|
|
50837
|
-
await runTar(["-czf", path7.join(stage, "dist.tar.gz"), "-C", distDir, "."], projectDir);
|
|
50838
|
-
console.error("Bundling...");
|
|
50839
|
-
await runTar(["-czf", bundlePath, "source.tar.gz", "dist.tar.gz"], stage);
|
|
50840
|
-
return fs6.readFileSync(bundlePath);
|
|
50841
|
-
} finally {
|
|
50842
|
-
fs6.rmSync(stage, { recursive: true, force: true });
|
|
50843
|
-
if (fs6.existsSync(bundlePath)) fs6.unlinkSync(bundlePath);
|
|
50844
|
-
}
|
|
50845
|
-
}
|
|
50846
|
-
function starterContract(name) {
|
|
50847
|
-
return {
|
|
50848
|
-
entities: [
|
|
50849
|
-
{
|
|
50850
|
-
alias: "item",
|
|
50851
|
-
label: name,
|
|
50852
|
-
description: `Records managed by the ${name} package.`,
|
|
50853
|
-
fields: [
|
|
50854
|
-
{ alias: "name", label: "Name", type: "text", required: true },
|
|
50855
|
-
{ alias: "notes", label: "Notes", type: "text" },
|
|
50856
|
-
{
|
|
50857
|
-
alias: "status",
|
|
50858
|
-
label: "Status",
|
|
50859
|
-
type: "select",
|
|
50860
|
-
options: [
|
|
50861
|
-
{ alias: "open", label: "Open", color: "blue" },
|
|
50862
|
-
{ alias: "done", label: "Done", color: "green" }
|
|
50863
|
-
]
|
|
50864
|
-
}
|
|
50865
|
-
]
|
|
50866
|
-
}
|
|
50867
|
-
],
|
|
50868
|
-
roles: [],
|
|
50869
|
-
templates: [],
|
|
50870
|
-
queries: [
|
|
50871
|
-
{
|
|
50872
|
-
alias: "items",
|
|
50873
|
-
ast: {
|
|
50874
|
-
kind: "from_table",
|
|
50875
|
-
from_entity: "item",
|
|
50876
|
-
sort: [{ field_key: "name", order: "asc" }]
|
|
50877
|
-
}
|
|
50878
|
-
}
|
|
50879
|
-
],
|
|
50880
|
-
workflows: [],
|
|
50881
|
-
agents: [],
|
|
50882
|
-
config: [
|
|
50883
|
-
{ alias: "heading", label: "List heading", type: "text", default: name }
|
|
50884
|
-
]
|
|
50885
|
-
};
|
|
50886
|
-
}
|
|
50887
|
-
function emptyContract() {
|
|
50888
|
-
return {
|
|
50889
|
-
entities: [],
|
|
50890
|
-
roles: [],
|
|
50891
|
-
templates: [],
|
|
50892
|
-
queries: [],
|
|
50893
|
-
workflows: [],
|
|
50894
|
-
agents: [],
|
|
50895
|
-
config: []
|
|
50896
|
-
};
|
|
50897
|
-
}
|
|
50898
|
-
function scaffoldContentPackage(name, targetPath) {
|
|
50899
|
-
const exampleAlias = "overview";
|
|
50900
|
-
const manifest = {
|
|
50901
|
-
id: null,
|
|
50902
|
-
name,
|
|
50903
|
-
description: null,
|
|
50904
|
-
kind: "content",
|
|
50905
|
-
version: null,
|
|
50906
|
-
knowledge: {
|
|
50907
|
-
[exampleAlias]: {
|
|
50908
|
-
name: `${name} Overview`,
|
|
50909
|
-
description: "What this corpus covers \u2014 replace with your doc's summary.",
|
|
50910
|
-
active_by_default: true
|
|
50911
|
-
}
|
|
50912
|
-
},
|
|
50913
|
-
// A content package can also ship standalone document templates — declare them
|
|
50914
|
-
// under `lotics.package.templates` with the files in a `templates/` dir. See
|
|
50915
|
-
// the commented example printed by the scaffold + docs/packages.md § Content.
|
|
50916
|
-
templates: {},
|
|
50917
|
-
knowledge_expects: [],
|
|
50918
|
-
dev: {}
|
|
50919
|
-
};
|
|
50920
|
-
const pkgJson = {
|
|
50921
|
-
name: appDirName(name),
|
|
50922
|
-
private: true,
|
|
50923
|
-
version: "0.0.0",
|
|
50924
|
-
lotics: { package: manifest }
|
|
50925
|
-
};
|
|
50926
|
-
fs6.writeFileSync(
|
|
50927
|
-
packageJsonPath(targetPath),
|
|
50928
|
-
JSON.stringify(pkgJson, null, 2) + "\n"
|
|
50929
|
-
);
|
|
50930
|
-
fs6.writeFileSync(
|
|
50931
|
-
path7.join(targetPath, CONTRACT_FILE),
|
|
50932
|
-
JSON.stringify(emptyContract(), null, 2) + "\n"
|
|
50933
|
-
);
|
|
50934
|
-
const knowledgeDir = path7.join(targetPath, "knowledge");
|
|
50935
|
-
fs6.mkdirSync(knowledgeDir, { recursive: true });
|
|
50936
|
-
fs6.writeFileSync(
|
|
50937
|
-
path7.join(knowledgeDir, `${exampleAlias}.md`),
|
|
50938
|
-
`# ${name}
|
|
50939
|
-
|
|
50940
|
-
Replace this file with the corpus content. Structure it under clear
|
|
50941
|
-
Markdown headers so an agent can outline and read it by section.
|
|
50942
|
-
`
|
|
50943
|
-
);
|
|
50944
|
-
console.error(`Scaffolded a content package into ${targetPath}`);
|
|
50945
|
-
console.error(`
|
|
50946
|
-
Ready. Next steps:`);
|
|
50947
|
-
console.error(` cd ${path7.relative(process.cwd(), targetPath) || "."}`);
|
|
50948
|
-
console.error(` # add docs: create knowledge/<alias>.md + a lotics.package.knowledge.<alias> entry`);
|
|
50949
|
-
console.error(` # (name/description/active_by_default), then publish:`);
|
|
50950
|
-
console.error(` # add templates: create templates/<file> + a lotics.package.templates.<alias> entry:`);
|
|
50951
|
-
console.error(` # "templates": { "quote": { "name": "Quote", "type": "html", "file": "quote.html" } }`);
|
|
50952
|
-
console.error(` # inline kinds html|email read templates/<file> as content; file-backed`);
|
|
50953
|
-
console.error(` # excel|word|pdf-form pack the bytes into the bundle (bytes_ref = templates/<file>).`);
|
|
50954
|
-
console.error(` lotics package publish -m "v1"`);
|
|
50955
|
-
console.error(` lotics package install <package_id>`);
|
|
50956
|
-
}
|
|
50957
|
-
async function packageNew(args) {
|
|
50958
|
-
const targetPath = path7.resolve(args.targetPath ?? appDirName(args.name));
|
|
50959
|
-
if (fs6.existsSync(targetPath) && fs6.readdirSync(targetPath).length > 0) {
|
|
50960
|
-
throw new Error(`Target directory ${targetPath} is not empty.`);
|
|
50961
|
-
}
|
|
50962
|
-
fs6.mkdirSync(targetPath, { recursive: true });
|
|
50963
|
-
if ((args.kind ?? "app") === "content") {
|
|
50964
|
-
scaffoldContentPackage(args.name, targetPath);
|
|
50965
|
-
return;
|
|
50966
|
-
}
|
|
50967
|
-
const [uiLatest, sdkLatest] = await Promise.all([
|
|
50968
|
-
fetchLatestNpmVersion("@lotics/ui"),
|
|
50969
|
-
fetchLatestNpmVersion("@lotics/app-sdk")
|
|
50970
|
-
]);
|
|
50971
|
-
const files = buildStarterTemplate({
|
|
50972
|
-
app_name: args.name,
|
|
50973
|
-
app_id: "",
|
|
50974
|
-
workspace_id: "",
|
|
50975
|
-
ui_version: uiLatest ? `^${uiLatest}` : void 0,
|
|
50976
|
-
sdk_version: packageSdkRange(sdkLatest)
|
|
50977
|
-
});
|
|
50978
|
-
const overrides = new Map(
|
|
50979
|
-
buildPackageStarterOverrides({ app_name: args.name }).map((f) => [f.path, f.content])
|
|
50980
|
-
);
|
|
50981
|
-
for (const file2 of files) {
|
|
50982
|
-
const fullPath = path7.join(targetPath, file2.path);
|
|
50983
|
-
if (file2.path === "package.json") {
|
|
50984
|
-
const pkg2 = JSON.parse(file2.content);
|
|
50985
|
-
const manifest = {
|
|
50986
|
-
id: null,
|
|
50987
|
-
name: args.name,
|
|
50988
|
-
description: null,
|
|
50989
|
-
kind: "app",
|
|
50990
|
-
version: null,
|
|
50991
|
-
knowledge: {},
|
|
50992
|
-
templates: {},
|
|
50993
|
-
knowledge_expects: [],
|
|
50994
|
-
dev: {}
|
|
50995
|
-
};
|
|
50996
|
-
pkg2.lotics = { package: manifest };
|
|
50997
|
-
fs6.mkdirSync(path7.dirname(fullPath), { recursive: true });
|
|
50998
|
-
fs6.writeFileSync(fullPath, JSON.stringify(pkg2, null, 2) + "\n");
|
|
50999
|
-
continue;
|
|
51000
|
-
}
|
|
51001
|
-
fs6.mkdirSync(path7.dirname(fullPath), { recursive: true });
|
|
51002
|
-
fs6.writeFileSync(fullPath, overrides.get(file2.path) ?? file2.content);
|
|
51003
|
-
}
|
|
51004
|
-
fs6.writeFileSync(
|
|
51005
|
-
path7.join(targetPath, CONTRACT_FILE),
|
|
51006
|
-
JSON.stringify(starterContract(args.name), null, 2) + "\n"
|
|
51007
|
-
);
|
|
51008
|
-
writePackageAppFields(targetPath);
|
|
51009
|
-
console.error(`Scaffolded ${files.length + 1} files into ${targetPath}`);
|
|
51010
|
-
console.error("Installing npm dependencies...");
|
|
51011
|
-
await runNpm(["install"], targetPath);
|
|
51012
|
-
console.error(`
|
|
51013
|
-
Ready. Next steps:`);
|
|
51014
|
-
console.error(` cd ${path7.relative(process.cwd(), targetPath) || "."}`);
|
|
51015
|
-
console.error(` # edit contract.json + src/App.tsx, then run it against a dev workspace:`);
|
|
51016
|
-
console.error(` lotics workspace create "<name> dev" --dev`);
|
|
51017
|
-
console.error(` lotics package dev --workspace <dev_ws>`);
|
|
51018
|
-
}
|
|
51019
|
-
async function packageBuild(args) {
|
|
51020
|
-
const projectDir = path7.resolve(args.projectDir ?? process.cwd());
|
|
51021
|
-
readPackageProject(projectDir);
|
|
51022
|
-
const bundle = await buildPackageBundle(projectDir);
|
|
51023
|
-
const out = path7.join(projectDir, "bundle.tar.gz");
|
|
51024
|
-
fs6.writeFileSync(out, bundle);
|
|
51025
|
-
console.error(`Built ${out} (${(bundle.byteLength / 1024).toFixed(1)} KB)`);
|
|
51026
|
-
}
|
|
51027
|
-
async function publishVersion(client, projectDir, opts) {
|
|
51028
|
-
const project = readPackageProject(projectDir);
|
|
51029
|
-
const rawContract = readContract(projectDir);
|
|
51030
|
-
if (!isPlainObject2(rawContract)) {
|
|
51031
|
-
throw new Error(`${CONTRACT_FILE} in ${projectDir} must be a JSON object.`);
|
|
51032
|
-
}
|
|
51033
|
-
const contract = foldTemplateShasIntoContract(
|
|
51034
|
-
projectDir,
|
|
51035
|
-
foldKnowledgeIntoContract(
|
|
51036
|
-
projectDir,
|
|
51037
|
-
project,
|
|
51038
|
-
foldTemplatesIntoContract(projectDir, project, rawContract)
|
|
51039
|
-
)
|
|
51040
|
-
);
|
|
51041
|
-
let packageId = project.manifest.id;
|
|
51042
|
-
if (packageId === null) {
|
|
51043
|
-
const pkg2 = await client.createPackage({
|
|
51044
|
-
name: project.manifest.name,
|
|
51045
|
-
description: project.manifest.description,
|
|
51046
|
-
// Kind is fixed at package creation and immutable after — a knowledge
|
|
51047
|
-
// package must be created as such so publish enforces its constraint.
|
|
51048
|
-
kind: project.manifest.kind
|
|
51049
|
-
});
|
|
51050
|
-
packageId = pkg2.id;
|
|
51051
|
-
project.manifest.id = packageId;
|
|
51052
|
-
writePackageManifest(projectDir, project);
|
|
51053
|
-
console.error(`Created package ${pkg2.name} (${pkg2.id}).`);
|
|
51054
|
-
}
|
|
51055
|
-
const bundle = await buildPackageBundle(projectDir);
|
|
51056
|
-
console.error("Publishing version...");
|
|
51057
|
-
const version2 = await client.publishPackageVersion(packageId, {
|
|
51058
|
-
contract,
|
|
51059
|
-
bundle,
|
|
51060
|
-
changelog: opts.changelog ?? null,
|
|
51061
|
-
channel: opts.channel ?? "release"
|
|
51062
|
-
});
|
|
51063
|
-
project.manifest.version = version2.version;
|
|
51064
|
-
writePackageManifest(projectDir, project);
|
|
51065
|
-
return { package_id: packageId, version: version2.version };
|
|
51066
|
-
}
|
|
51067
|
-
async function packagePublish(client, args) {
|
|
51068
|
-
const projectDir = path7.resolve(args.projectDir ?? process.cwd());
|
|
51069
|
-
const { package_id, version: version2 } = await publishVersion(client, projectDir, {
|
|
51070
|
-
changelog: args.changelog
|
|
51071
|
-
});
|
|
51072
|
-
console.error(`Published ${package_id} v${version2}.`);
|
|
51073
|
-
console.error(` Install it: lotics package install ${package_id} --version ${version2}`);
|
|
51074
|
-
}
|
|
51075
|
-
function assertDevWorkspace(workspace) {
|
|
51076
|
-
if (workspace.is_dev === true) return;
|
|
51077
|
-
throw new Error(
|
|
51078
|
-
`Workspace "${workspace.name}" (${workspace.id}) is not a dev workspace. "lotics package dev"/"sync" scaffold package tables into the target workspace, so it must be a throwaway dev workspace. Create one with "lotics workspace create <name> --dev" and pass --workspace <dev_ws>.`
|
|
51079
|
-
);
|
|
51080
|
-
}
|
|
51081
|
-
async function syncToDevWorkspace(client, projectDir) {
|
|
51082
|
-
const devWorkspaceId = client.getWorkspaceId();
|
|
51083
|
-
if (!devWorkspaceId) {
|
|
51084
|
-
throw new Error(
|
|
51085
|
-
"No dev workspace selected. Pass --workspace <dev_ws> (a workspace created with --dev)."
|
|
51086
|
-
);
|
|
51087
|
-
}
|
|
51088
|
-
const workspace = await client.getWorkspaceInfo(devWorkspaceId);
|
|
51089
|
-
if (!workspace) {
|
|
51090
|
-
throw new Error(
|
|
51091
|
-
`Workspace ${devWorkspaceId} is not accessible with these credentials. Pass --workspace <dev_ws> (a workspace created with --dev).`
|
|
51092
|
-
);
|
|
51093
|
-
}
|
|
51094
|
-
assertDevWorkspace(workspace);
|
|
51095
|
-
writePackageAppFields(projectDir);
|
|
51096
|
-
const { package_id, version: version2 } = await publishVersion(client, projectDir, {
|
|
51097
|
-
changelog: "dev sync",
|
|
51098
|
-
channel: "dev"
|
|
51099
|
-
});
|
|
51100
|
-
const project = readPackageProject(projectDir);
|
|
51101
|
-
const existing = project.manifest.dev[devWorkspaceId];
|
|
51102
|
-
let appId;
|
|
51103
|
-
if (existing) {
|
|
51104
|
-
console.error(`Upgrading dev installation ${existing.app_id} \u2192 v${version2}...`);
|
|
51105
|
-
const app = await client.upgradePackage(existing.app_id, { version: version2 });
|
|
51106
|
-
appId = app.id;
|
|
51107
|
-
} else {
|
|
51108
|
-
console.error(`Installing ${package_id} v${version2} into dev workspace ${devWorkspaceId}...`);
|
|
51109
|
-
const result = await client.installPackage(package_id, { version: version2 });
|
|
51110
|
-
if (result.kind !== "app") {
|
|
51111
|
-
throw new Error(
|
|
51112
|
-
`Package ${package_id} is a content package \u2014 "lotics package dev/sync" run an app installation. Install a content package with "lotics package install ${package_id}".`
|
|
51113
|
-
);
|
|
51114
|
-
}
|
|
51115
|
-
appId = result.app.id;
|
|
51116
|
-
}
|
|
51117
|
-
project.manifest.dev[devWorkspaceId] = { app_id: appId, version: version2 };
|
|
51118
|
-
writePackageManifest(projectDir, project);
|
|
51119
|
-
const installed = await client.getApp(appId);
|
|
51120
|
-
writeAppDts(projectDir, {
|
|
51121
|
-
workflows: installed.workflows ?? void 0,
|
|
51122
|
-
queries: installed.queries ?? void 0,
|
|
51123
|
-
agents: installed.agents ?? void 0
|
|
51124
|
-
});
|
|
51125
|
-
return { app_id: appId, app_name: installed.name, workspace_id: devWorkspaceId, version: version2 };
|
|
51126
|
-
}
|
|
51127
|
-
async function packageSync(client, args) {
|
|
51128
|
-
const projectDir = path7.resolve(args.projectDir ?? process.cwd());
|
|
51129
|
-
const result = await syncToDevWorkspace(client, projectDir);
|
|
51130
|
-
console.error(
|
|
51131
|
-
`Synced ${result.app_name} v${result.version} \u2192 ${result.app_id} (workspace ${result.workspace_id}).`
|
|
51132
|
-
);
|
|
51133
|
-
}
|
|
51134
|
-
async function packageDev(client, args) {
|
|
51135
|
-
const projectDir = path7.resolve(args.projectDir ?? process.cwd());
|
|
51136
|
-
const synced = await syncToDevWorkspace(client, projectDir);
|
|
51137
|
-
const handle = await startDevServer({
|
|
51138
|
-
projectDir,
|
|
51139
|
-
app_id: synced.app_id,
|
|
51140
|
-
app_name: synced.app_name,
|
|
51141
|
-
workspace_id: synced.workspace_id,
|
|
51142
|
-
api_url: client.baseUrl,
|
|
51143
|
-
port: args.port,
|
|
51144
|
-
vitePort: args.vitePort,
|
|
51145
|
-
client
|
|
51146
|
-
});
|
|
51147
|
-
await handle.ready;
|
|
51148
|
-
const url2 = `http://localhost:${handle.port}`;
|
|
51149
|
-
console.error(`
|
|
51150
|
-
lotics package dev`);
|
|
51151
|
-
console.error(` package: ${synced.app_name} (dev installation ${synced.app_id} v${synced.version})`);
|
|
51152
|
-
console.error(` workspace: ${synced.workspace_id} (dev)`);
|
|
51153
|
-
if (client.viewAsMemberId) {
|
|
51154
|
-
console.error(` view as: ${client.viewAsMemberId}`);
|
|
51155
|
-
}
|
|
51156
|
-
console.error(` vite: http://localhost:${handle.vitePort}/`);
|
|
51157
|
-
console.error(` open: ${url2}`);
|
|
51158
|
-
console.error(` rpc: ${client.baseUrl} (via Bearer API key)
|
|
51159
|
-
`);
|
|
51160
|
-
console.error(` Edit contract.json then re-run "lotics package dev" / "lotics package sync" to migrate.`);
|
|
51161
|
-
console.error(` Ctrl-C to stop.
|
|
51162
|
-
`);
|
|
51163
|
-
openBrowser(url2);
|
|
51164
|
-
await new Promise((resolve2) => {
|
|
51165
|
-
const onSig = () => {
|
|
51166
|
-
process.off("SIGINT", onSig);
|
|
51167
|
-
process.off("SIGTERM", onSig);
|
|
51168
|
-
resolve2();
|
|
51169
|
-
};
|
|
51170
|
-
process.on("SIGINT", onSig);
|
|
51171
|
-
process.on("SIGTERM", onSig);
|
|
51172
|
-
});
|
|
51173
|
-
console.error("\nStopping\u2026");
|
|
51174
|
-
await handle.stop();
|
|
51175
|
-
}
|
|
51176
|
-
async function packageReset(client, args) {
|
|
51177
|
-
const projectDir = path7.resolve(args.projectDir ?? process.cwd());
|
|
51178
|
-
const devWorkspaceId = client.getWorkspaceId();
|
|
51179
|
-
if (!devWorkspaceId) {
|
|
51180
|
-
throw new Error(
|
|
51181
|
-
"No dev workspace selected. Pass --workspace <dev_ws> (a workspace created with --dev)."
|
|
51182
|
-
);
|
|
51183
|
-
}
|
|
51184
|
-
const { manifest } = readPackageProject(projectDir);
|
|
51185
|
-
const pin = manifest.dev[devWorkspaceId];
|
|
51186
|
-
if (!pin) {
|
|
51187
|
-
throw new Error(
|
|
51188
|
-
`No dev installation recorded for workspace ${devWorkspaceId}. Run "lotics package dev --workspace ${devWorkspaceId}" first.`
|
|
51189
|
-
);
|
|
51190
|
-
}
|
|
51191
|
-
console.error(`Resetting dev installation ${pin.app_id} in workspace ${devWorkspaceId}...`);
|
|
51192
|
-
const app = await client.resetPackage(pin.app_id);
|
|
51193
|
-
console.error(`Reset ${app.name} \u2192 ${app.id}. The scaffolded tables were dropped and re-created clean.`);
|
|
51194
|
-
}
|
|
51195
|
-
function resolveInstallationAppId(client, explicit) {
|
|
50174
|
+
function resolveInstallationAppId(explicit) {
|
|
51196
50175
|
if (explicit) return explicit;
|
|
51197
|
-
|
|
51198
|
-
if (!workspaceId) {
|
|
51199
|
-
throw new Error("Pass an app id (lotics package doctor <app_id>) or select a workspace.");
|
|
51200
|
-
}
|
|
51201
|
-
let manifest;
|
|
51202
|
-
try {
|
|
51203
|
-
({ manifest } = readPackageProject(path7.resolve(process.cwd())));
|
|
51204
|
-
} catch {
|
|
51205
|
-
throw new Error(
|
|
51206
|
-
"No app id given and the current directory is not a package project. Pass one explicitly: lotics package doctor <app_id>."
|
|
51207
|
-
);
|
|
51208
|
-
}
|
|
51209
|
-
const pin = manifest.dev[workspaceId];
|
|
51210
|
-
if (!pin) {
|
|
51211
|
-
throw new Error(
|
|
51212
|
-
`No app id given and no dev installation recorded for workspace ${workspaceId}. Pass one explicitly: lotics package doctor <app_id>.`
|
|
51213
|
-
);
|
|
51214
|
-
}
|
|
51215
|
-
return pin.app_id;
|
|
50176
|
+
throw new Error("Pass an app id \u2014 e.g. lotics package doctor <app_id>.");
|
|
51216
50177
|
}
|
|
50178
|
+
var RESOLVE_VERBS = /* @__PURE__ */ new Set(["recreate", "revert", "keep", "apply", "archive", "unbind"]);
|
|
51217
50179
|
function parseResolveFlags(resolve2) {
|
|
51218
50180
|
const resolutions = {};
|
|
51219
50181
|
for (const entry of resolve2) {
|
|
51220
50182
|
const eq = entry.indexOf("=");
|
|
51221
50183
|
if (eq <= 0 || eq === entry.length - 1) {
|
|
51222
50184
|
throw new Error(
|
|
51223
|
-
`Invalid --resolve "${entry}" \u2014 expected <key
|
|
50185
|
+
`Invalid --resolve "${entry}" \u2014 expected <key>=<verb> (recreate|revert|keep|apply|archive|unbind) or <key>=<existing_id>.`
|
|
51224
50186
|
);
|
|
51225
50187
|
}
|
|
51226
50188
|
const key = entry.slice(0, eq);
|
|
51227
50189
|
const value = entry.slice(eq + 1);
|
|
51228
|
-
resolutions[key] = value
|
|
50190
|
+
resolutions[key] = RESOLVE_VERBS.has(value) ? value : { bind_to: value };
|
|
51229
50191
|
}
|
|
51230
50192
|
return resolutions;
|
|
51231
50193
|
}
|
|
51232
50194
|
async function packageDoctor(client, args) {
|
|
51233
|
-
const app_id = resolveInstallationAppId(
|
|
50195
|
+
const app_id = resolveInstallationAppId(args.app_id);
|
|
51234
50196
|
const health = await client.getPackageHealth(app_id);
|
|
51235
|
-
console.error(
|
|
50197
|
+
console.error(
|
|
50198
|
+
`${health.package_name} \u2014 installation ${health.app_id}` + (health.is_origin ? " (origin \u2014 this app IS installation #1, the release working copy)" : "")
|
|
50199
|
+
);
|
|
51236
50200
|
console.error(
|
|
51237
50201
|
` Installed: v${health.installed_version} Latest: v${health.latest_version}` + (health.update_available ? " \u2192 update available" : "")
|
|
51238
50202
|
);
|
|
@@ -51245,12 +50209,22 @@ async function packageDoctor(client, args) {
|
|
|
51245
50209
|
}
|
|
51246
50210
|
console.error(
|
|
51247
50211
|
` Resolve while upgrading:
|
|
51248
|
-
lotics
|
|
50212
|
+
lotics upgrade ${app_id} --resolve <namespace.alias>=recreate (or =<existing_id> to re-point)`
|
|
51249
50213
|
);
|
|
51250
50214
|
process.exitCode = 1;
|
|
51251
50215
|
}
|
|
51252
50216
|
if (health.modified.length === 0) {
|
|
51253
|
-
console.error(
|
|
50217
|
+
console.error(
|
|
50218
|
+
health.is_origin ? " Package artifacts: no changes since the last release." : " Package artifacts: pristine \u2014 no local edits an upgrade would revert."
|
|
50219
|
+
);
|
|
50220
|
+
} else if (health.is_origin) {
|
|
50221
|
+
console.error(
|
|
50222
|
+
` Changed since v${health.installed_version} (${health.modified.length}) \u2014 a release will publish these:`
|
|
50223
|
+
);
|
|
50224
|
+
for (const m of health.modified) {
|
|
50225
|
+
console.error(` - ${m.kind}.${m.alias}`);
|
|
50226
|
+
}
|
|
50227
|
+
console.error(` Cut the next version: lotics app release ${app_id} -m "<what changed>"`);
|
|
51254
50228
|
} else {
|
|
51255
50229
|
console.error(` Locally modified package artifacts (${health.modified.length}):`);
|
|
51256
50230
|
for (const m of health.modified) {
|
|
@@ -51258,7 +50232,7 @@ async function packageDoctor(client, args) {
|
|
|
51258
50232
|
}
|
|
51259
50233
|
console.error(
|
|
51260
50234
|
` Consent while upgrading:
|
|
51261
|
-
lotics
|
|
50235
|
+
lotics upgrade ${app_id} --resolve <kind.alias>=revert (or =keep to retain the edit)`
|
|
51262
50236
|
);
|
|
51263
50237
|
process.exitCode = 1;
|
|
51264
50238
|
}
|
|
@@ -51269,9 +50243,18 @@ async function packageDoctor(client, args) {
|
|
|
51269
50243
|
}
|
|
51270
50244
|
}
|
|
51271
50245
|
if (health.knowledge_modified.length > 0) {
|
|
51272
|
-
|
|
51273
|
-
|
|
51274
|
-
|
|
50246
|
+
if (health.is_origin) {
|
|
50247
|
+
console.error(
|
|
50248
|
+
` Knowledge changed since v${health.installed_version} (${health.knowledge_modified.length}) \u2014 a release will re-snapshot these:`
|
|
50249
|
+
);
|
|
50250
|
+
for (const m of health.knowledge_modified) {
|
|
50251
|
+
console.error(` - ${m.alias} "${m.name}"`);
|
|
50252
|
+
}
|
|
50253
|
+
} else {
|
|
50254
|
+
console.error(` Locally edited package knowledge (${health.knowledge_modified.length}):`);
|
|
50255
|
+
for (const m of health.knowledge_modified) {
|
|
50256
|
+
console.error(` - ${m.alias} "${m.name}" (an upgrade overwrites this unless kept)`);
|
|
50257
|
+
}
|
|
51275
50258
|
}
|
|
51276
50259
|
}
|
|
51277
50260
|
if (health.missing_expected_docs.length > 0) {
|
|
@@ -51280,14 +50263,14 @@ async function packageDoctor(client, args) {
|
|
|
51280
50263
|
console.error(` - "${name}" (the package's agents route to this name; no matching doc exists)`);
|
|
51281
50264
|
}
|
|
51282
50265
|
}
|
|
51283
|
-
if (health.knowledge_drift.length > 0 || health.knowledge_modified.length > 0) {
|
|
50266
|
+
if (health.knowledge_drift.length > 0 || health.knowledge_modified.length > 0 && !health.is_origin) {
|
|
51284
50267
|
process.exitCode = 1;
|
|
51285
50268
|
}
|
|
51286
50269
|
if (health.knowledge_drift.length === 0 && health.knowledge_modified.length === 0 && health.missing_expected_docs.length === 0) {
|
|
51287
50270
|
console.error(" Knowledge: healthy \u2014 bound docs resolve, none locally edited, expects met.");
|
|
51288
50271
|
}
|
|
51289
50272
|
if (health.update_available) {
|
|
51290
|
-
console.error(` Upgrade: lotics
|
|
50273
|
+
console.error(` Upgrade: lotics upgrade ${app_id}`);
|
|
51291
50274
|
}
|
|
51292
50275
|
}
|
|
51293
50276
|
async function packageUpgrade(client, args) {
|
|
@@ -51322,27 +50305,17 @@ async function packageUpgrade(client, args) {
|
|
|
51322
50305
|
);
|
|
51323
50306
|
process.exit(1);
|
|
51324
50307
|
}
|
|
51325
|
-
const
|
|
51326
|
-
|
|
51327
|
-
|
|
51328
|
-
|
|
51329
|
-
const { coreResolve, knowledgeResolutions } = routeAppUpgradeResolve(
|
|
51330
|
-
args.resolve,
|
|
51331
|
-
preview.knowledge,
|
|
51332
|
-
coreKeys
|
|
51333
|
-
);
|
|
51334
|
-
const resolutions = parseResolveFlags(coreResolve);
|
|
50308
|
+
const resolutions = parseResolveFlags(args.resolve);
|
|
50309
|
+
for (const [alias, id] of Object.entries(parseBindToFlags(args.bindTo))) {
|
|
50310
|
+
resolutions[`knowledge.${alias}`] = { bind_to: id };
|
|
50311
|
+
}
|
|
51335
50312
|
if (args.applyAll) {
|
|
51336
50313
|
for (const entry of preview.knowledge) {
|
|
51337
|
-
if (knowledgeEntryNeedsConsent(entry) &&
|
|
51338
|
-
|
|
50314
|
+
if (knowledgeEntryNeedsConsent(entry) && resolutions[`knowledge.${entry.alias}`] === void 0) {
|
|
50315
|
+
resolutions[`knowledge.${entry.alias}`] = knowledgeAcceptResolution(entry.change);
|
|
51339
50316
|
}
|
|
51340
50317
|
}
|
|
51341
50318
|
}
|
|
51342
|
-
const knowledge_resolutions = {
|
|
51343
|
-
resolutions: knowledgeResolutions,
|
|
51344
|
-
bind_to: parseBindToFlags(args.bindTo)
|
|
51345
|
-
};
|
|
51346
50319
|
const unresolvedDrift = preview.drift.filter(
|
|
51347
50320
|
(d) => resolutions[`${d.namespace}.${d.alias}`] === void 0
|
|
51348
50321
|
);
|
|
@@ -51350,7 +50323,7 @@ async function packageUpgrade(client, args) {
|
|
|
51350
50323
|
(m) => resolutions[`${m.kind}.${m.alias}`] === void 0
|
|
51351
50324
|
);
|
|
51352
50325
|
const unresolvedKnowledge = preview.knowledge.filter(
|
|
51353
|
-
(e) => knowledgeEntryNeedsConsent(e) &&
|
|
50326
|
+
(e) => knowledgeEntryNeedsConsent(e) && resolutions[`knowledge.${e.alias}`] === void 0
|
|
51354
50327
|
);
|
|
51355
50328
|
if (unresolvedDrift.length > 0 || unresolvedModified.length > 0 || unresolvedKnowledge.length > 0) {
|
|
51356
50329
|
if (unresolvedDrift.length > 0) {
|
|
@@ -51379,11 +50352,9 @@ async function packageUpgrade(client, args) {
|
|
|
51379
50352
|
}
|
|
51380
50353
|
process.exit(1);
|
|
51381
50354
|
}
|
|
51382
|
-
const hasKnowledgeResolutions = Object.keys(knowledge_resolutions.resolutions).length > 0 || Object.keys(knowledge_resolutions.bind_to).length > 0;
|
|
51383
50355
|
const app = await client.upgradePackage(args.app_id, {
|
|
51384
50356
|
...args.version !== void 0 ? { version: args.version } : {},
|
|
51385
|
-
...Object.keys(resolutions).length > 0 ? { resolutions } : {}
|
|
51386
|
-
...hasKnowledgeResolutions ? { knowledge_resolutions } : {}
|
|
50357
|
+
...Object.keys(resolutions).length > 0 ? { resolutions } : {}
|
|
51387
50358
|
});
|
|
51388
50359
|
console.error(`Upgraded ${app.name} \u2192 v${app.package_version} (${app.id}).`);
|
|
51389
50360
|
}
|
|
@@ -51433,82 +50404,11 @@ function formatKnowledgeEntryLine(entry) {
|
|
|
51433
50404
|
}
|
|
51434
50405
|
function formatKnowledgeResolveHint(entry) {
|
|
51435
50406
|
const note = entry.change === "changed" || entry.change === "removed" ? " (a local edit \u2014 apply/archive overwrites it; keep retains it)" : " (bound doc is gone; recreate from the package, or unbind)";
|
|
51436
|
-
return ` --resolve
|
|
51437
|
-
}
|
|
51438
|
-
function routeAppUpgradeResolve(resolve2, knowledgeEntries, coreKeys) {
|
|
51439
|
-
const byAlias = new Map(knowledgeEntries.map((e) => [e.alias, e]));
|
|
51440
|
-
const coreResolve = [];
|
|
51441
|
-
const knowledgeResolutions = {};
|
|
51442
|
-
for (const entry of resolve2) {
|
|
51443
|
-
const eq = entry.indexOf("=");
|
|
51444
|
-
if (eq <= 0 || eq === entry.length - 1) {
|
|
51445
|
-
throw new Error(`Invalid --resolve "${entry}" \u2014 expected <key>=<value>.`);
|
|
51446
|
-
}
|
|
51447
|
-
const key = entry.slice(0, eq);
|
|
51448
|
-
const value = entry.slice(eq + 1);
|
|
51449
|
-
const known = byAlias.get(key);
|
|
51450
|
-
if (known && coreKeys.has(key)) {
|
|
51451
|
-
throw new Error(
|
|
51452
|
-
`--resolve "${key}" is ambiguous \u2014 it names both a bundled knowledge doc and a core artifact in this upgrade. Rename one alias so it is unambiguous; refusing to guess.`
|
|
51453
|
-
);
|
|
51454
|
-
}
|
|
51455
|
-
if (known) {
|
|
51456
|
-
const valid = validKnowledgeResolutions(known.change);
|
|
51457
|
-
const match = valid.find((v) => v === value);
|
|
51458
|
-
if (match === void 0) {
|
|
51459
|
-
throw new Error(
|
|
51460
|
-
`Invalid --resolve value "${value}" for knowledge doc "${key}" (${known.change}) \u2014 expected ${valid.join("|")}.`
|
|
51461
|
-
);
|
|
51462
|
-
}
|
|
51463
|
-
knowledgeResolutions[key] = match;
|
|
51464
|
-
} else {
|
|
51465
|
-
coreResolve.push(entry);
|
|
51466
|
-
}
|
|
51467
|
-
}
|
|
51468
|
-
return { coreResolve, knowledgeResolutions };
|
|
51469
|
-
}
|
|
51470
|
-
function routeContentResolveFlags(resolve2, knowledgeAliases, templateAliases) {
|
|
51471
|
-
const knowledge = {};
|
|
51472
|
-
const templates = {};
|
|
51473
|
-
const knowledgeValid = /* @__PURE__ */ new Set(["apply", "keep", "archive", "recreate", "unbind"]);
|
|
51474
|
-
const templateValid = /* @__PURE__ */ new Set(["revert", "keep"]);
|
|
51475
|
-
for (const entry of resolve2) {
|
|
51476
|
-
const eq = entry.indexOf("=");
|
|
51477
|
-
if (eq <= 0 || eq === entry.length - 1) {
|
|
51478
|
-
throw new Error(`Invalid --resolve "${entry}" \u2014 expected <alias>=<value>.`);
|
|
51479
|
-
}
|
|
51480
|
-
const key = entry.slice(0, eq);
|
|
51481
|
-
const value = entry.slice(eq + 1);
|
|
51482
|
-
const isKnowledge = knowledgeAliases.has(key);
|
|
51483
|
-
const isTemplate = templateAliases.has(key);
|
|
51484
|
-
if (isKnowledge && isTemplate) {
|
|
51485
|
-
throw new Error(
|
|
51486
|
-
`--resolve "${key}" is ambiguous \u2014 it names both a knowledge doc and a template in this upgrade. Rename one alias in the package so the two content namespaces are disjoint.`
|
|
51487
|
-
);
|
|
51488
|
-
}
|
|
51489
|
-
if (isTemplate) {
|
|
51490
|
-
if (!templateValid.has(value)) {
|
|
51491
|
-
throw new Error(`Invalid --resolve value "${value}" for template "${key}" \u2014 expected revert|keep.`);
|
|
51492
|
-
}
|
|
51493
|
-
templates[key] = value;
|
|
51494
|
-
continue;
|
|
51495
|
-
}
|
|
51496
|
-
if (isKnowledge) {
|
|
51497
|
-
if (!knowledgeValid.has(value)) {
|
|
51498
|
-
throw new Error(
|
|
51499
|
-
`Invalid --resolve value "${value}" for knowledge alias "${key}" \u2014 expected apply|keep|archive|recreate|unbind.`
|
|
51500
|
-
);
|
|
51501
|
-
}
|
|
51502
|
-
knowledge[key] = value;
|
|
51503
|
-
continue;
|
|
51504
|
-
}
|
|
51505
|
-
throw new Error(`--resolve "${key}" does not name a knowledge doc or template in this upgrade.`);
|
|
51506
|
-
}
|
|
51507
|
-
return { knowledge, templates };
|
|
50407
|
+
return ` --resolve knowledge.${entry.alias}=${validKnowledgeResolutions(entry.change).join("|")}${note}`;
|
|
51508
50408
|
}
|
|
51509
50409
|
function formatTemplateResolveHint(entry) {
|
|
51510
50410
|
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)";
|
|
51511
|
-
return ` --resolve
|
|
50411
|
+
return ` --resolve template.${entry.alias}=revert|keep${note}`;
|
|
51512
50412
|
}
|
|
51513
50413
|
async function packageUpgradeKnowledge(client, args) {
|
|
51514
50414
|
const preview = await client.previewContentInstallationUpgrade(args.installation_id, {
|
|
@@ -51532,7 +50432,7 @@ async function packageUpgradeKnowledge(client, args) {
|
|
|
51532
50432
|
console.error(
|
|
51533
50433
|
" No changes needing consent \u2014 advancing the version pin; clean template updates apply automatically."
|
|
51534
50434
|
);
|
|
51535
|
-
await apply({
|
|
50435
|
+
await apply({});
|
|
51536
50436
|
return;
|
|
51537
50437
|
}
|
|
51538
50438
|
for (const entry of preview.entries) {
|
|
@@ -51541,30 +50441,27 @@ async function packageUpgradeKnowledge(client, args) {
|
|
|
51541
50441
|
for (const entry of preview.templates) {
|
|
51542
50442
|
console.error(` [template] ${entry.alias} (modified \u2014 needs consent)`);
|
|
51543
50443
|
}
|
|
51544
|
-
const
|
|
51545
|
-
|
|
51546
|
-
|
|
51547
|
-
|
|
51548
|
-
);
|
|
51549
|
-
const resolutions = {
|
|
51550
|
-
knowledge: { resolutions: { ...routed.knowledge }, bind_to: { ...args.bind_to } },
|
|
51551
|
-
templates: { ...routed.templates }
|
|
51552
|
-
};
|
|
50444
|
+
const resolutions = parseResolveFlags(args.resolve);
|
|
50445
|
+
for (const [alias, id] of Object.entries(args.bind_to)) {
|
|
50446
|
+
resolutions[`knowledge.${alias}`] = { bind_to: id };
|
|
50447
|
+
}
|
|
51553
50448
|
if (args.applyAll) {
|
|
51554
50449
|
for (const entry of preview.entries) {
|
|
51555
|
-
if (knowledgeEntryNeedsConsent(entry) && resolutions
|
|
51556
|
-
resolutions
|
|
50450
|
+
if (knowledgeEntryNeedsConsent(entry) && resolutions[`knowledge.${entry.alias}`] === void 0) {
|
|
50451
|
+
resolutions[`knowledge.${entry.alias}`] = knowledgeAcceptResolution(entry.change);
|
|
51557
50452
|
}
|
|
51558
50453
|
}
|
|
51559
50454
|
for (const entry of preview.templates) {
|
|
51560
|
-
if (resolutions
|
|
50455
|
+
if (resolutions[`template.${entry.alias}`] === void 0) {
|
|
50456
|
+
resolutions[`template.${entry.alias}`] = "revert";
|
|
50457
|
+
}
|
|
51561
50458
|
}
|
|
51562
50459
|
}
|
|
51563
50460
|
const unresolvedKnowledge = preview.entries.filter(
|
|
51564
|
-
(entry) => knowledgeEntryNeedsConsent(entry) && resolutions
|
|
50461
|
+
(entry) => knowledgeEntryNeedsConsent(entry) && resolutions[`knowledge.${entry.alias}`] === void 0
|
|
51565
50462
|
);
|
|
51566
50463
|
const unresolvedTemplates = preview.templates.filter(
|
|
51567
|
-
(entry) => resolutions
|
|
50464
|
+
(entry) => resolutions[`template.${entry.alias}`] === void 0
|
|
51568
50465
|
);
|
|
51569
50466
|
if (unresolvedKnowledge.length > 0 || unresolvedTemplates.length > 0) {
|
|
51570
50467
|
console.error(
|
|
@@ -51624,8 +50521,8 @@ async function packageInstall(client, args) {
|
|
|
51624
50521
|
);
|
|
51625
50522
|
}
|
|
51626
50523
|
warnMissingExpectedDocs(warnings.missing_expected_docs);
|
|
51627
|
-
console.error(` Upgrade later: lotics
|
|
51628
|
-
console.error(` Uninstall: lotics
|
|
50524
|
+
console.error(` Upgrade later: lotics upgrade ${installation.id}`);
|
|
50525
|
+
console.error(` Uninstall: lotics uninstall ${installation.id} [--keep-content]`);
|
|
51629
50526
|
return;
|
|
51630
50527
|
}
|
|
51631
50528
|
const { app, knowledge_warnings } = result;
|
|
@@ -51633,7 +50530,9 @@ async function packageInstall(client, args) {
|
|
|
51633
50530
|
console.error(`Installed ${app.name} ${versionLabel} \u2192 ${app.id} (workspace ${app.workspace_id}).`);
|
|
51634
50531
|
console.error(" The data model, queries, workflows, and agents are live.");
|
|
51635
50532
|
warnMissingExpectedDocs(knowledge_warnings.missing_expected_docs);
|
|
51636
|
-
console.error(
|
|
50533
|
+
console.error(` Pull it for local editing: lotics app pull ${app.id}`);
|
|
50534
|
+
console.error(` Upgrade later: lotics upgrade ${app.id}`);
|
|
50535
|
+
console.error(` Health / uninstall: lotics package doctor ${app.id} \xB7 lotics uninstall ${app.id} [--archive-tables]`);
|
|
51637
50536
|
}
|
|
51638
50537
|
async function packageUninstall(client, args) {
|
|
51639
50538
|
if (args.id.startsWith("pci_")) {
|
|
@@ -51702,10 +50601,9 @@ async function packageListContent(client) {
|
|
|
51702
50601
|
const name = inst.package_registry?.name ?? "(unknown package)";
|
|
51703
50602
|
const latest = inst.package_registry?.latest_version;
|
|
51704
50603
|
const updateAvailable = inst.package_registry?.update_available ?? false;
|
|
51705
|
-
const source = inst.app_id === null ? "standalone" : `app-bundled (${inst.app_id})`;
|
|
51706
50604
|
const versionLabel = latest !== void 0 && latest !== inst.package_version ? `v${inst.package_version} \u2192 latest v${latest}` : `v${inst.package_version}`;
|
|
51707
50605
|
console.error(
|
|
51708
|
-
` ${inst.id} ${name} ${versionLabel}
|
|
50606
|
+
` ${inst.id} ${name} ${versionLabel}` + (updateAvailable ? " \u2192 update available" : "")
|
|
51709
50607
|
);
|
|
51710
50608
|
}
|
|
51711
50609
|
}
|
|
@@ -51781,200 +50679,134 @@ async function packageConfig(client, args) {
|
|
|
51781
50679
|
console.log(` ${key} = ${JSON.stringify(config2[key])}${marker}`);
|
|
51782
50680
|
}
|
|
51783
50681
|
}
|
|
51784
|
-
async function
|
|
51785
|
-
|
|
50682
|
+
async function appUnpublish(client, args) {
|
|
50683
|
+
let packageId = args.id;
|
|
50684
|
+
if (args.id.startsWith("app_")) {
|
|
50685
|
+
const app = await client.getApp(args.id);
|
|
50686
|
+
if (!app.package_id) {
|
|
50687
|
+
throw new Error(
|
|
50688
|
+
`App ${args.id} is not a package installation \u2014 it has no package to unpublish. Pass the package id directly.`
|
|
50689
|
+
);
|
|
50690
|
+
}
|
|
50691
|
+
packageId = app.package_id;
|
|
50692
|
+
}
|
|
50693
|
+
const pkg2 = await client.retirePackage(packageId, { undo: args.undo });
|
|
51786
50694
|
if (pkg2.retired_at !== null) {
|
|
51787
50695
|
console.error(
|
|
51788
|
-
`
|
|
50696
|
+
`Unpublished ${pkg2.name} (${pkg2.id}). New installs refuse it and it is hidden from other orgs; existing installations keep working and may still upgrade. Undo: lotics app unpublish ${pkg2.id} --undo`
|
|
51789
50697
|
);
|
|
51790
50698
|
} else {
|
|
51791
|
-
console.error(`
|
|
50699
|
+
console.error(`Re-published ${pkg2.name} (${pkg2.id}) \u2014 installable again.`);
|
|
51792
50700
|
}
|
|
51793
50701
|
}
|
|
51794
|
-
function
|
|
51795
|
-
const
|
|
51796
|
-
const
|
|
51797
|
-
|
|
51798
|
-
|
|
51799
|
-
|
|
51800
|
-
knowledge.push({ alias: entry.alias, doc_id: entry.doc_id });
|
|
51801
|
-
}
|
|
51802
|
-
}
|
|
51803
|
-
}
|
|
51804
|
-
const knowledge_expects = Array.isArray(lotics.knowledge_expects) ? lotics.knowledge_expects.filter((v) => typeof v === "string") : [];
|
|
51805
|
-
return { knowledge, knowledge_expects };
|
|
51806
|
-
}
|
|
51807
|
-
function knowledgeManifestFromContract(contract) {
|
|
51808
|
-
const out = {};
|
|
51809
|
-
if (!isPlainObject2(contract) || !isPlainObject2(contract.knowledge)) return out;
|
|
51810
|
-
for (const [alias, entry] of Object.entries(contract.knowledge)) {
|
|
51811
|
-
if (!isPlainObject2(entry) || typeof entry.name !== "string") continue;
|
|
51812
|
-
out[alias] = {
|
|
51813
|
-
name: entry.name,
|
|
51814
|
-
description: typeof entry.description === "string" ? entry.description : null,
|
|
51815
|
-
active_by_default: typeof entry.active_by_default === "boolean" ? entry.active_by_default : true
|
|
51816
|
-
};
|
|
51817
|
-
}
|
|
51818
|
-
return out;
|
|
51819
|
-
}
|
|
51820
|
-
async function packageExtract(client, args) {
|
|
51821
|
-
const app = await client.getApp(args.app_id);
|
|
51822
|
-
if (!app.current_version_id) {
|
|
50702
|
+
async function appPublish(client, args) {
|
|
50703
|
+
const projectDir = path6.resolve(args.projectDir ?? process.cwd());
|
|
50704
|
+
const local = readLocalAppManifest(projectDir);
|
|
50705
|
+
const explicit = args.app_id !== void 0 && args.app_id !== "." ? args.app_id : void 0;
|
|
50706
|
+
const appId = explicit ?? local?.app_id ?? null;
|
|
50707
|
+
if (appId === null) {
|
|
51823
50708
|
throw new Error(
|
|
51824
|
-
|
|
50709
|
+
"No app id. Run `lotics app publish` from a pulled app project (lotics app pull <app_id>), or pass one: lotics app publish <app_id>."
|
|
51825
50710
|
);
|
|
51826
50711
|
}
|
|
51827
|
-
const
|
|
51828
|
-
|
|
51829
|
-
|
|
51830
|
-
}
|
|
51831
|
-
|
|
51832
|
-
const
|
|
51833
|
-
|
|
51834
|
-
|
|
51835
|
-
|
|
51836
|
-
|
|
51837
|
-
|
|
51838
|
-
|
|
51839
|
-
|
|
51840
|
-
|
|
51841
|
-
|
|
51842
|
-
|
|
51843
|
-
|
|
51844
|
-
|
|
51845
|
-
);
|
|
51846
|
-
const appKnowledge = readAppKnowledgeDeclaration(appPkgJson);
|
|
51847
|
-
const extracted = await client.extractPackage(args.app_id, {
|
|
51848
|
-
knowledge: appKnowledge.knowledge
|
|
51849
|
-
});
|
|
51850
|
-
const { lines, hasError } = formatExtractReport(extracted.report);
|
|
51851
|
-
if (lines.length > 0) {
|
|
51852
|
-
console.error(`Extraction report (${extracted.report.length}):`);
|
|
51853
|
-
for (const line of lines) console.error(line);
|
|
51854
|
-
} else {
|
|
51855
|
-
console.error("Extraction report: no findings.");
|
|
51856
|
-
}
|
|
51857
|
-
const draft = draftPackageProjectFromApp(appPkgJson, { name: app.name, description: null });
|
|
51858
|
-
draft.manifest.knowledge = knowledgeManifestFromContract(extracted.contract);
|
|
51859
|
-
draft.manifest.knowledge_expects = appKnowledge.knowledge_expects;
|
|
51860
|
-
const deps = isPlainObject2(draft.pkgJson.dependencies) ? draft.pkgJson.dependencies : {};
|
|
51861
|
-
const originRange = typeof deps["@lotics/app-sdk"] === "string" ? deps["@lotics/app-sdk"] : null;
|
|
51862
|
-
const originFloor = originRange?.replace(/^[\^~]/, "") ?? null;
|
|
51863
|
-
if (originFloor === null || cmpVersions(originFloor, STARTER_FALLBACK_SDK_VERSION) < 0) {
|
|
51864
|
-
const raised = packageSdkRange(await fetchLatestNpmVersion("@lotics/app-sdk"));
|
|
51865
|
-
draft.pkgJson.dependencies = { ...deps, "@lotics/app-sdk": raised };
|
|
51866
|
-
console.error(`Raised @lotics/app-sdk to ${raised} (generated app_fields needs getAppBinding)`);
|
|
51867
|
-
}
|
|
51868
|
-
writePackageManifest(targetPath, draft);
|
|
51869
|
-
fs6.writeFileSync(
|
|
51870
|
-
path7.join(targetPath, CONTRACT_FILE),
|
|
51871
|
-
JSON.stringify(extracted.contract, null, 2) + "\n"
|
|
51872
|
-
);
|
|
51873
|
-
console.error(`Wrote ${CONTRACT_FILE}`);
|
|
51874
|
-
writePackageAppFields(targetPath);
|
|
51875
|
-
const starterViteConfig = buildStarterTemplate({
|
|
51876
|
-
app_name: app.name,
|
|
51877
|
-
app_id: "",
|
|
51878
|
-
workspace_id: ""
|
|
51879
|
-
}).find((f) => f.path === "vite.config.ts");
|
|
51880
|
-
if (starterViteConfig === void 0) {
|
|
51881
|
-
throw new Error("starter template is missing vite.config.ts \u2014 cannot refresh the package project");
|
|
51882
|
-
}
|
|
51883
|
-
const viteConfigPath = path7.join(targetPath, "vite.config.ts");
|
|
51884
|
-
const originViteConfig = fs6.existsSync(viteConfigPath) ? fs6.readFileSync(viteConfigPath, "utf-8") : null;
|
|
51885
|
-
fs6.writeFileSync(viteConfigPath, starterViteConfig.content);
|
|
51886
|
-
if (originViteConfig !== null && originViteConfig !== starterViteConfig.content) {
|
|
51887
|
-
const stash = path7.join(dotLoticsDirEnsured(targetPath), "vite.config.origin.ts");
|
|
51888
|
-
fs6.writeFileSync(stash, originViteConfig);
|
|
50712
|
+
const knowledge = local && local.app_id === appId ? local.knowledge : [];
|
|
50713
|
+
const renames = parseRenameFlags(args.renames);
|
|
50714
|
+
const preview = await client.previewPublishAppPackage(appId, { renames, knowledge });
|
|
50715
|
+
const { lines, hasError } = formatExtractReport(preview.findings);
|
|
50716
|
+
console.error(`Publish preview \u2014 ${appId} as new package "${preview.package_name}" (v1):`);
|
|
50717
|
+
const groups = [
|
|
50718
|
+
["entities", preview.renamable_aliases.entities],
|
|
50719
|
+
["fields", preview.renamable_aliases.fields],
|
|
50720
|
+
["options", preview.renamable_aliases.options],
|
|
50721
|
+
["roles", preview.renamable_aliases.roles],
|
|
50722
|
+
["templates", preview.renamable_aliases.templates],
|
|
50723
|
+
["workflows", preview.renamable_aliases.workflows]
|
|
50724
|
+
];
|
|
50725
|
+
if (groups.some(([, vals]) => vals.length > 0)) {
|
|
50726
|
+
console.error(" Auto-minted aliases \u2014 rename any with --rename <alias>=<new> before v1 freezes them:");
|
|
50727
|
+
for (const [label, vals] of groups) {
|
|
50728
|
+
if (vals.length > 0) console.error(` ${`${label}:`.padEnd(11)} ${vals.join(", ")}`);
|
|
50729
|
+
}
|
|
51889
50730
|
console.error(
|
|
51890
|
-
"
|
|
50731
|
+
" (query / app-workflow / agent runtime aliases are fixed \u2014 the shipped source calls them verbatim.)"
|
|
51891
50732
|
);
|
|
51892
50733
|
} else {
|
|
51893
|
-
console.error("
|
|
51894
|
-
}
|
|
51895
|
-
|
|
51896
|
-
|
|
51897
|
-
|
|
51898
|
-
|
|
51899
|
-
if (path7.resolve(downloaded) !== path7.resolve(dest)) fs6.renameSync(downloaded, dest);
|
|
51900
|
-
console.error(`Wrote ${tf.bytes_ref}`);
|
|
51901
|
-
}
|
|
51902
|
-
for (const kf of extracted.knowledge_files) {
|
|
51903
|
-
const dest = path7.join(targetPath, kf.content_ref);
|
|
51904
|
-
fs6.mkdirSync(path7.dirname(dest), { recursive: true });
|
|
51905
|
-
fs6.writeFileSync(dest, kf.content);
|
|
51906
|
-
console.error(`Wrote ${kf.content_ref}`);
|
|
51907
|
-
}
|
|
51908
|
-
const dotLotics = path7.join(targetPath, ".lotics");
|
|
51909
|
-
fs6.mkdirSync(dotLotics, { recursive: true });
|
|
51910
|
-
fs6.writeFileSync(
|
|
51911
|
-
path7.join(dotLotics, ADOPT_BINDING_FILE),
|
|
51912
|
-
JSON.stringify(
|
|
51913
|
-
{
|
|
51914
|
-
app_id: app.id,
|
|
51915
|
-
workspace_id: app.workspace_id,
|
|
51916
|
-
binding: extracted.binding,
|
|
51917
|
-
knowledge_binding: extracted.knowledge_binding
|
|
51918
|
-
},
|
|
51919
|
-
null,
|
|
51920
|
-
2
|
|
51921
|
-
) + "\n"
|
|
51922
|
-
);
|
|
51923
|
-
console.error(`Wrote .lotics/${ADOPT_BINDING_FILE}`);
|
|
51924
|
-
console.error("Installing npm dependencies...");
|
|
51925
|
-
await runNpm(["install"], targetPath);
|
|
50734
|
+
console.error(" No renamable aliases.");
|
|
50735
|
+
}
|
|
50736
|
+
if (lines.length > 0) {
|
|
50737
|
+
console.error(` Findings (${preview.findings.length}):`);
|
|
50738
|
+
for (const line of lines) console.error(line);
|
|
50739
|
+
}
|
|
51926
50740
|
if (hasError) {
|
|
51927
50741
|
console.error(
|
|
51928
|
-
"\
|
|
51929
|
-
);
|
|
51930
|
-
console.error(
|
|
51931
|
-
"Fix the reported items above, then publish (which re-validates the contract)."
|
|
50742
|
+
"\nExtract found error findings \u2014 the app cannot be published as-is. Fix them in the app, redeploy, and retry."
|
|
51932
50743
|
);
|
|
51933
50744
|
process.exitCode = 1;
|
|
51934
50745
|
return;
|
|
51935
50746
|
}
|
|
51936
|
-
|
|
51937
|
-
|
|
51938
|
-
|
|
51939
|
-
|
|
51940
|
-
|
|
50747
|
+
if (!args.yes) {
|
|
50748
|
+
const idArg = explicit ?? ".";
|
|
50749
|
+
const renameArgs = args.renames.map((r) => ` --rename ${r}`).join("");
|
|
50750
|
+
const mArg = args.changelog ? ` -m ${JSON.stringify(args.changelog)}` : "";
|
|
50751
|
+
console.error(`
|
|
50752
|
+
Re-run with --yes to publish v1:`);
|
|
50753
|
+
console.error(` lotics app publish ${idArg}${renameArgs}${mArg} --yes`);
|
|
50754
|
+
process.exitCode = 1;
|
|
50755
|
+
return;
|
|
50756
|
+
}
|
|
50757
|
+
const result = await client.publishAppAsPackage(appId, {
|
|
50758
|
+
renames,
|
|
50759
|
+
changelog: args.changelog ?? null,
|
|
50760
|
+
knowledge
|
|
50761
|
+
});
|
|
50762
|
+
console.error(`Published ${result.package_id} v${result.version} from app ${appId}.`);
|
|
50763
|
+
console.error(` The app is now installation #1 \u2014 develop it in place, then release the next version:`);
|
|
50764
|
+
console.error(` lotics app pull ${appId} # edit, then lotics app deploy`);
|
|
50765
|
+
console.error(` lotics app release ${appId} -m "<what changed>"`);
|
|
50766
|
+
console.error(` Install it elsewhere: lotics install ${result.package_id}`);
|
|
50767
|
+
}
|
|
50768
|
+
function resolveOriginAppId(projectDir, explicit) {
|
|
50769
|
+
if (explicit !== void 0 && explicit !== ".") return explicit;
|
|
50770
|
+
const local = readLocalAppManifest(projectDir);
|
|
50771
|
+
if (local?.app_id) return local.app_id;
|
|
50772
|
+
throw new Error(
|
|
50773
|
+
"No app id. Run this from a pulled app project (lotics app pull <app_id>), or pass an app id explicitly."
|
|
50774
|
+
);
|
|
51941
50775
|
}
|
|
51942
|
-
async function
|
|
51943
|
-
const projectDir =
|
|
51944
|
-
const
|
|
51945
|
-
|
|
51946
|
-
|
|
51947
|
-
|
|
51948
|
-
|
|
50776
|
+
async function appRelease(client, args) {
|
|
50777
|
+
const projectDir = path6.resolve(args.projectDir ?? process.cwd());
|
|
50778
|
+
const appId = resolveOriginAppId(projectDir, args.app_id);
|
|
50779
|
+
const preview = await client.previewPackageRelease(appId);
|
|
50780
|
+
const { lines, hasError } = formatExtractReport(preview.findings);
|
|
50781
|
+
console.error(`Release preview \u2014 ${appId} \u2192 ${preview.package_id} v${preview.version}:`);
|
|
50782
|
+
if (preview.added_aliases.length > 0) {
|
|
50783
|
+
console.error(` New (${preview.added_aliases.length}): ${preview.added_aliases.join(", ")}`);
|
|
51949
50784
|
}
|
|
51950
|
-
|
|
51951
|
-
|
|
51952
|
-
throw new Error(
|
|
51953
|
-
"No version to adopt \u2014 publish this project first (lotics package publish) or pass --version N."
|
|
51954
|
-
);
|
|
50785
|
+
if (preview.changed_artifacts.length > 0) {
|
|
50786
|
+
console.error(` Changed (${preview.changed_artifacts.length}): ${preview.changed_artifacts.join(", ")}`);
|
|
51955
50787
|
}
|
|
51956
|
-
|
|
51957
|
-
|
|
51958
|
-
throw new Error(
|
|
51959
|
-
`No .lotics/${ADOPT_BINDING_FILE} in ${projectDir}. Adopt binds the origin app that "lotics package extract" recorded \u2014 run extract to produce this project.`
|
|
51960
|
-
);
|
|
50788
|
+
if (preview.added_aliases.length === 0 && preview.changed_artifacts.length === 0) {
|
|
50789
|
+
console.error(" No contract changes since the current version (a fresh code/dist snapshot still ships).");
|
|
51961
50790
|
}
|
|
51962
|
-
|
|
51963
|
-
|
|
51964
|
-
|
|
51965
|
-
|
|
51966
|
-
|
|
51967
|
-
|
|
51968
|
-
|
|
51969
|
-
|
|
51970
|
-
|
|
51971
|
-
|
|
51972
|
-
|
|
51973
|
-
|
|
51974
|
-
|
|
51975
|
-
|
|
51976
|
-
|
|
51977
|
-
|
|
50791
|
+
if (lines.length > 0) {
|
|
50792
|
+
console.error(` Findings (${preview.findings.length}):`);
|
|
50793
|
+
for (const line of lines) console.error(line);
|
|
50794
|
+
}
|
|
50795
|
+
if (hasError) {
|
|
50796
|
+
console.error("\nExtract found error findings \u2014 the origin cannot be released as-is. Fix them in the app and retry.");
|
|
50797
|
+
process.exitCode = 1;
|
|
50798
|
+
return;
|
|
50799
|
+
}
|
|
50800
|
+
if (!args.yes) {
|
|
50801
|
+
console.error(`
|
|
50802
|
+
Re-run with --yes to publish v${preview.version}:`);
|
|
50803
|
+
console.error(` lotics app release ${args.app_id ?? "."} -m ${JSON.stringify(args.changelog)} --yes`);
|
|
50804
|
+
process.exitCode = 1;
|
|
50805
|
+
return;
|
|
50806
|
+
}
|
|
50807
|
+
const result = await client.releasePackage(appId, { changelog: args.changelog });
|
|
50808
|
+
console.error(`Released ${result.package_id} v${result.version}.`);
|
|
50809
|
+
console.error(` The origin was re-pinned to v${result.version} \u2014 verify: lotics package doctor ${appId}`);
|
|
51978
50810
|
}
|
|
51979
50811
|
async function packageYank(client, args) {
|
|
51980
50812
|
const result = await client.yankPackageVersion(args.package_id, args.version, !args.undo);
|
|
@@ -52006,7 +50838,7 @@ async function packageFleetUpgrade(client, args) {
|
|
|
52006
50838
|
`${line}: breaking=${inst.blockers.breaking} drift=${inst.blockers.drift} modified=${inst.blockers.modified}`
|
|
52007
50839
|
);
|
|
52008
50840
|
console.error(
|
|
52009
|
-
` resolve via: lotics
|
|
50841
|
+
` resolve via: lotics upgrade ${inst.app_id} --version ${result.target_version} ...`
|
|
52010
50842
|
);
|
|
52011
50843
|
} else if (inst.message) {
|
|
52012
50844
|
console.error(`${line}: ${inst.message}`);
|
|
@@ -52038,7 +50870,6 @@ function parseArgs(argv) {
|
|
|
52038
50870
|
message: void 0,
|
|
52039
50871
|
local: false,
|
|
52040
50872
|
all: false,
|
|
52041
|
-
dev: false,
|
|
52042
50873
|
yes: false,
|
|
52043
50874
|
printCreated: false,
|
|
52044
50875
|
cleanup: false,
|
|
@@ -52047,7 +50878,7 @@ function parseArgs(argv) {
|
|
|
52047
50878
|
bindTo: [],
|
|
52048
50879
|
keepContent: false,
|
|
52049
50880
|
applyAll: false,
|
|
52050
|
-
|
|
50881
|
+
rename: [],
|
|
52051
50882
|
config: [],
|
|
52052
50883
|
set: [],
|
|
52053
50884
|
archiveTables: false,
|
|
@@ -52102,9 +50933,6 @@ function parseArgs(argv) {
|
|
|
52102
50933
|
case "--local":
|
|
52103
50934
|
flags.local = true;
|
|
52104
50935
|
break;
|
|
52105
|
-
case "--dev":
|
|
52106
|
-
flags.dev = true;
|
|
52107
|
-
break;
|
|
52108
50936
|
case "--all":
|
|
52109
50937
|
flags.all = true;
|
|
52110
50938
|
break;
|
|
@@ -52143,9 +50971,14 @@ function parseArgs(argv) {
|
|
|
52143
50971
|
case "--apply-all":
|
|
52144
50972
|
flags.applyAll = true;
|
|
52145
50973
|
break;
|
|
52146
|
-
case "--
|
|
52147
|
-
|
|
50974
|
+
case "--rename": {
|
|
50975
|
+
const value = argv[++i2];
|
|
50976
|
+
if (value === void 0 || value.startsWith("-")) {
|
|
50977
|
+
throw new Error("--rename requires a value: old=new (an alias to rename before v1 freezes it).");
|
|
50978
|
+
}
|
|
50979
|
+
flags.rename.push(value);
|
|
52148
50980
|
break;
|
|
50981
|
+
}
|
|
52149
50982
|
case "--config": {
|
|
52150
50983
|
const value = argv[++i2];
|
|
52151
50984
|
if (value === void 0 || value.startsWith("-")) {
|
|
@@ -52171,10 +51004,10 @@ function parseArgs(argv) {
|
|
|
52171
51004
|
case "--version":
|
|
52172
51005
|
case "-v": {
|
|
52173
51006
|
const next = argv[i2 + 1];
|
|
52174
|
-
if (command === "package") {
|
|
51007
|
+
if (command === "package" || command === "install" || command === "upgrade") {
|
|
52175
51008
|
if (next === void 0 || next.startsWith("-")) {
|
|
52176
51009
|
throw new Error(
|
|
52177
|
-
"--version requires a version number for package commands (e.g. --version 2)."
|
|
51010
|
+
"--version requires a version number for install/upgrade/package commands (e.g. --version 2)."
|
|
52178
51011
|
);
|
|
52179
51012
|
}
|
|
52180
51013
|
flags.packageVersion = next;
|
|
@@ -52236,6 +51069,34 @@ async function ingestJsonArgs(opts) {
|
|
|
52236
51069
|
// src/xlsx.ts
|
|
52237
51070
|
import fs7 from "node:fs";
|
|
52238
51071
|
|
|
51072
|
+
// src/file_command_io.ts
|
|
51073
|
+
import fs6 from "node:fs";
|
|
51074
|
+
import path7 from "node:path";
|
|
51075
|
+
var CliError = class extends Error {
|
|
51076
|
+
constructor(message) {
|
|
51077
|
+
super(message);
|
|
51078
|
+
this.name = "CliError";
|
|
51079
|
+
}
|
|
51080
|
+
};
|
|
51081
|
+
function fail(message) {
|
|
51082
|
+
throw new CliError(message);
|
|
51083
|
+
}
|
|
51084
|
+
function writeFileAtomic(filePath, bytes) {
|
|
51085
|
+
const dir = path7.dirname(path7.resolve(filePath));
|
|
51086
|
+
const tmp = path7.join(dir, `.${path7.basename(filePath)}.${process.pid}.${Date.now()}.tmp`);
|
|
51087
|
+
fs6.writeFileSync(tmp, bytes);
|
|
51088
|
+
try {
|
|
51089
|
+
fs6.renameSync(tmp, filePath);
|
|
51090
|
+
} catch (error51) {
|
|
51091
|
+
try {
|
|
51092
|
+
fs6.unlinkSync(tmp);
|
|
51093
|
+
} catch {
|
|
51094
|
+
}
|
|
51095
|
+
throw error51;
|
|
51096
|
+
}
|
|
51097
|
+
console.error(`Wrote ${filePath}`);
|
|
51098
|
+
}
|
|
51099
|
+
|
|
52239
51100
|
// ../xlsx/node_modules/fast-xml-parser/src/util.js
|
|
52240
51101
|
var nameStartChar = ":A-Za-z_\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD";
|
|
52241
51102
|
var nameChar = nameStartChar + "\\-.\\d\\u00B7\\u0300-\\u036F\\u203F-\\u2040";
|
|
@@ -68566,7 +67427,7 @@ async function runDocxCommand(subcommand, toolArgs, restArgs) {
|
|
|
68566
67427
|
import { spawn as spawn3 } from "node:child_process";
|
|
68567
67428
|
import { createServer } from "node:http";
|
|
68568
67429
|
import { readFileSync as readFileSync2, writeFileSync, existsSync, mkdtempSync, rmSync, readdirSync } from "node:fs";
|
|
68569
|
-
import { tmpdir as
|
|
67430
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
68570
67431
|
import { join, dirname, resolve, extname, basename } from "node:path";
|
|
68571
67432
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
68572
67433
|
import { setTimeout as sleep } from "node:timers/promises";
|
|
@@ -68659,7 +67520,7 @@ async function runPreviewCommand(filePath, flags) {
|
|
|
68659
67520
|
});
|
|
68660
67521
|
await new Promise((r) => server.listen(0, "127.0.0.1", () => r()));
|
|
68661
67522
|
const httpPort = server.address().port;
|
|
68662
|
-
const udd = mkdtempSync(join(
|
|
67523
|
+
const udd = mkdtempSync(join(tmpdir2(), "lotics-render-"));
|
|
68663
67524
|
const child = spawn3(chrome, [
|
|
68664
67525
|
"--headless=new",
|
|
68665
67526
|
"--disable-gpu",
|
|
@@ -68808,10 +67669,22 @@ COMMANDS
|
|
|
68808
67669
|
cat args.json | lotics run <tool> Read JSON args from stdin (large payloads)
|
|
68809
67670
|
lotics app create <name> [path] Create a new custom-code app + scaffold locally
|
|
68810
67671
|
lotics app pull <app_id> [path] Bootstrap full local env (source + npm install + types)
|
|
68811
|
-
lotics app deploy -m <message> Build + upload current dir as a new version
|
|
68812
|
-
(-m is required \u2014 it's the version's audit trail
|
|
68813
|
-
|
|
67672
|
+
lotics app deploy -m <message> Build + upload current dir as a new version \u2014 COMMIT
|
|
67673
|
+
(-m is required \u2014 it's the version's audit trail;
|
|
67674
|
+
carries code + queries only \u2014 workflow bindings are
|
|
68814
67675
|
managed by set_app_workflow / remove_app_workflow)
|
|
67676
|
+
lotics app publish [app_id|.] [--rename old=new ...] [-m <changelog>] [--yes]
|
|
67677
|
+
Make the app DISTRIBUTABLE \u2014 first-release it (v1) as a
|
|
67678
|
+
package; later versions ship via "lotics app release".
|
|
67679
|
+
Previews the auto-minted aliases + findings; --yes applies
|
|
67680
|
+
(extract \u2192 create \u2192 publish v1 \u2192 pin origin). --rename fixes
|
|
67681
|
+
an alias before v1 freezes; -m is an optional v1 changelog
|
|
67682
|
+
lotics app release [app_id|.] -m <changelog> [--yes]
|
|
67683
|
+
RELEASE the next package version from the origin app you
|
|
67684
|
+
are happy with (preview; --yes publishes + re-pins)
|
|
67685
|
+
lotics app unpublish <app_id|package_id> [--undo]
|
|
67686
|
+
Take the published package off the shelf (installations
|
|
67687
|
+
keep working); --undo restores it
|
|
68815
67688
|
lotics app versions [app_id] Show deploy history newest-first (version,
|
|
68816
67689
|
timestamp, deployer, build status, -m message;
|
|
68817
67690
|
* marks the currently served version)
|
|
@@ -68833,57 +67706,40 @@ COMMANDS
|
|
|
68833
67706
|
lotics app subdomain <new-subdomain> Rename the app's public <slug>.lotics.app address
|
|
68834
67707
|
lotics app rename "<new name>" Rename the app's display name (launcher title)
|
|
68835
67708
|
lotics app dev [path] Run the app locally with HMR (RPC forwarded to prod)
|
|
68836
|
-
|
|
68837
|
-
|
|
68838
|
-
|
|
68839
|
-
lotics package build [path] Build the publishable bundle (source + dist)
|
|
68840
|
-
lotics package publish [path] Publish a new immutable package version
|
|
68841
|
-
lotics package dev [path] Sync the package into a dev workspace + run the app
|
|
68842
|
-
dev server (--workspace <dev_ws> selects it)
|
|
68843
|
-
lotics package sync [path] Re-sync (additive migrate + materialize) into a dev ws
|
|
68844
|
-
lotics package reset [path] DEV-ONLY: drop scaffolded tables + re-scaffold clean
|
|
68845
|
-
lotics package install <package> [--version N] [--bind-to <alias>=<kdc_id> ...] [--config key=value ...]
|
|
67709
|
+
# PACKAGES \u2014 consumer verbs are top-level; low-traffic ops live on "lotics package"
|
|
67710
|
+
# (author verbs live on "lotics app")
|
|
67711
|
+
lotics install <package_id> [--version N] [--bind-to <alias>=<kdc_id> ...] [--config key=value ...]
|
|
68846
67712
|
Install a package (app: scaffolds/deploys/materializes,
|
|
68847
67713
|
--config sets its knobs; content: installs the doc corpus,
|
|
68848
67714
|
--bind-to consents to adopt a same-named doc on a collision)
|
|
68849
|
-
lotics
|
|
67715
|
+
lotics uninstall <app_id|pci_id> [--archive-tables] [--keep-content]
|
|
68850
67716
|
Uninstall \u2014 dispatched by id. App (app_id): archives
|
|
68851
67717
|
artifacts (+ --archive-tables also archives scaffolded
|
|
68852
67718
|
tables). Content (pci_): archives its package-bound docs
|
|
68853
67719
|
unless --keep-content
|
|
68854
|
-
lotics
|
|
68855
|
-
|
|
68856
|
-
|
|
68857
|
-
|
|
68858
|
-
|
|
68859
|
-
|
|
68860
|
-
|
|
68861
|
-
(standalone pci_ + app-bundled) \u2014 source for a pci_ id
|
|
68862
|
-
lotics package upgrade <app_id|pci_id> [--version N] [--resolve <key>=... ] [--bind-to ...] [--apply-all]
|
|
68863
|
-
Preview-then-apply an upgrade. App: refuses while any
|
|
68864
|
-
drift/modified/bundled-knowledge finding lacks a --resolve
|
|
68865
|
-
(a knowledge-alias --resolve routes to the doc). Content
|
|
68866
|
-
(pci_): --resolve <alias>=apply|keep|archive|recreate|unbind;
|
|
68867
|
-
--apply-all accepts the package's version for every doc
|
|
67720
|
+
lotics upgrade <app_id|pci_id|package_id> [--version N] [--resolve <key>=... ] [--bind-to ...] [--apply-all]
|
|
67721
|
+
Preview-then-apply an upgrade, dispatched by id. App
|
|
67722
|
+
(app_id): refuses while any drift/modified/bundled-knowledge
|
|
67723
|
+
finding lacks a --resolve. Content (pci_): --resolve
|
|
67724
|
+
<alias>=apply|keep|archive|recreate|unbind. Package (apg_):
|
|
67725
|
+
FLEET \u2014 every installation across your org (clean apply,
|
|
67726
|
+
findings skip). --apply-all accepts the package's version
|
|
68868
67727
|
lotics package doctor [app_id] Installation health: version pin vs latest, binding
|
|
68869
67728
|
drift, locally modified core, knowledge drift/edits
|
|
68870
67729
|
(exit 1 on findings)
|
|
68871
|
-
lotics package
|
|
68872
|
-
|
|
68873
|
-
(re-materializes at the pinned version)
|
|
67730
|
+
lotics package config <app_id> [--set key=value ...]
|
|
67731
|
+
Show or edit an installation's config knobs
|
|
68874
67732
|
lotics package eject <app_id> Sever an installation's package link
|
|
68875
67733
|
(re-deploys the pinned source as a bespoke app)
|
|
68876
|
-
lotics package
|
|
68877
|
-
|
|
68878
|
-
|
|
68879
|
-
lotics package adopt <app_id> [path] Bind the published package project onto the
|
|
68880
|
-
origin app (reads .lotics/adopt_binding.json;
|
|
68881
|
-
--version N pins a specific version)
|
|
68882
|
-
lotics package fleet-upgrade <package_id> [--version N]
|
|
67734
|
+
lotics package show <package_id> Registry metadata + version history
|
|
67735
|
+
lotics package list-content List the workspace's content installations
|
|
67736
|
+
(standalone content installs) \u2014 source for a pci_ id
|
|
68883
67737
|
lotics package yank <package_id> <version> [--undo]
|
|
68884
|
-
|
|
68885
|
-
|
|
68886
|
-
|
|
67738
|
+
Refuse new installs/upgrades of a broken published
|
|
67739
|
+
version (pinned installations keep running)
|
|
67740
|
+
lotics run publish_content '{"name":"\u2026","knowledge_doc_ids":[\u2026],"template_ids":[\u2026]}'
|
|
67741
|
+
Publish a SET of live docs + templates as a content
|
|
67742
|
+
package (v1 + origin pin); release_content cuts the next
|
|
68887
67743
|
lotics ui link <component> [--ui-src <path>] [--remove]
|
|
68888
67744
|
Dev-link @lotics/ui to packages/ui/src (Vite alias
|
|
68889
67745
|
+ tsc paths) for live HMR + typecheck. Monorepo apps
|
|
@@ -68912,13 +67768,12 @@ FLAGS
|
|
|
68912
67768
|
is_current_member / row-scoping resolve to them (also
|
|
68913
67769
|
LOTICS_VIEW_AS env; admin key only; writes stay yours)
|
|
68914
67770
|
--local Pin the current directory (lotics org use / auth api-key)
|
|
68915
|
-
--
|
|
68916
|
-
|
|
68917
|
-
--
|
|
68918
|
-
--bind-to <a>=<id> (lotics package install/upgrade, repeatable) Adopt an existing
|
|
67771
|
+
--rename <old>=<new> (lotics app publish, repeatable) Fix an auto-minted alias before
|
|
67772
|
+
the package's v1 contract freezes it
|
|
67773
|
+
--bind-to <a>=<id> (lotics install/upgrade, repeatable) Adopt an existing
|
|
68919
67774
|
same-named doc as package-managed (resolves a knowledge collision)
|
|
68920
|
-
--keep-content (lotics
|
|
68921
|
-
--apply-all (lotics
|
|
67775
|
+
--keep-content (lotics uninstall) Keep the docs instead of archiving them
|
|
67776
|
+
--apply-all (lotics upgrade, knowledge) Accept the package's version
|
|
68922
67777
|
for every doc (overwrites local edits)
|
|
68923
67778
|
--all (lotics auth logout) Remove every saved credential
|
|
68924
67779
|
--version Show version
|
|
@@ -69292,27 +68147,6 @@ async function main() {
|
|
|
69292
68147
|
console.error("Usage: lotics ui link <component> [--ui-src <abs path>] [--remove]");
|
|
69293
68148
|
process.exit(1);
|
|
69294
68149
|
}
|
|
69295
|
-
if (command === "package" && subcommand === "new") {
|
|
69296
|
-
const name = toolArgs;
|
|
69297
|
-
if (!name) {
|
|
69298
|
-
console.error("Usage: lotics package new <name> [path] [--kind app|content]");
|
|
69299
|
-
process.exit(1);
|
|
69300
|
-
}
|
|
69301
|
-
if (flags.kind !== void 0 && flags.kind !== "app" && flags.kind !== "content") {
|
|
69302
|
-
console.error(`Invalid --kind "${flags.kind}" \u2014 expected "app" or "content".`);
|
|
69303
|
-
process.exit(1);
|
|
69304
|
-
}
|
|
69305
|
-
await packageNew({
|
|
69306
|
-
name,
|
|
69307
|
-
targetPath: restArgs[0],
|
|
69308
|
-
kind: flags.kind === "content" ? "content" : "app"
|
|
69309
|
-
});
|
|
69310
|
-
return;
|
|
69311
|
-
}
|
|
69312
|
-
if (command === "package" && subcommand === "build") {
|
|
69313
|
-
await packageBuild({ projectDir: toolArgs });
|
|
69314
|
-
return;
|
|
69315
|
-
}
|
|
69316
68150
|
if (command === "app" && subcommand === "workflow" && toolArgs === "check") {
|
|
69317
68151
|
await appWorkflowCheck({ alias: restArgs[0] });
|
|
69318
68152
|
return;
|
|
@@ -69387,7 +68221,7 @@ async function main() {
|
|
|
69387
68221
|
}
|
|
69388
68222
|
return;
|
|
69389
68223
|
}
|
|
69390
|
-
if (command !== "tools" && command !== "upload" && command !== "run" && command !== "download" && command !== "workspace" && command !== "app" && command !== "package") {
|
|
68224
|
+
if (command !== "tools" && command !== "upload" && command !== "run" && command !== "download" && command !== "workspace" && command !== "app" && command !== "package" && command !== "install" && command !== "uninstall" && command !== "upgrade") {
|
|
69391
68225
|
console.error(`Unknown command: ${command}`);
|
|
69392
68226
|
console.error('Run "lotics --help" for usage.');
|
|
69393
68227
|
process.exit(1);
|
|
@@ -69407,30 +68241,27 @@ async function main() {
|
|
|
69407
68241
|
console.error(" lotics app subdomain <new-subdomain> Rename the app's public <slug>.lotics.app address");
|
|
69408
68242
|
console.error(` lotics app rename "<new name>" Rename the app's display name (launcher title)`);
|
|
69409
68243
|
console.error(" lotics app dev [path] Run the app locally with HMR + RPC forwarding");
|
|
68244
|
+
console.error(" lotics app publish [app_id|.] [--rename old=new ...] [-m <changelog>] [--yes] First-release the app as a package (v1; preview, --yes applies)");
|
|
68245
|
+
console.error(" lotics app release [app_id|.] -m <changelog> [--yes] Release the next package version from the origin");
|
|
68246
|
+
console.error(" lotics app unpublish <app_id|package_id> [--undo] Take a published package off the shelf (any kind \u2014 app or content)");
|
|
69410
68247
|
process.exit(1);
|
|
69411
68248
|
}
|
|
69412
68249
|
if (command === "package" && !subcommand) {
|
|
69413
|
-
console.error("Usage:");
|
|
69414
|
-
console.error("
|
|
69415
|
-
console.error("
|
|
69416
|
-
console.error("
|
|
69417
|
-
console.error("
|
|
69418
|
-
console.error(" lotics package sync [path] [--workspace <ws>] Re-sync (additive migrate + materialize) into a dev workspace");
|
|
69419
|
-
console.error(" lotics package reset [path] [--workspace <ws>] DEV-ONLY: drop scaffolded tables + re-scaffold clean");
|
|
69420
|
-
console.error(" lotics package install <package> [--version N] [--bind-to <alias>=<kdc_id>] [--config key=value ...] Install a package (app or content)");
|
|
69421
|
-
console.error(" lotics package uninstall <app_id|pci_id> [--archive-tables] [--keep-content] Uninstall \u2014 dispatched by id (app vs content)");
|
|
69422
|
-
console.error(" lotics package config <app_id> [--set key=value ...] Show or edit an installation's config knobs");
|
|
69423
|
-
console.error(" lotics package show <package_id> Registry metadata + version history (channel, yank, changelog)");
|
|
69424
|
-
console.error(" lotics package retire <package_id> [--undo] Retire a package (refuse new installs; installs keep working)");
|
|
69425
|
-
console.error(" lotics package list-content List the workspace's content installations (standalone + app-bundled)");
|
|
69426
|
-
console.error(" lotics package upgrade <app_id|pci_id> [--version N] [--resolve <key>=... ] [--bind-to ...] [--apply-all] Preview + apply an upgrade");
|
|
68250
|
+
console.error("Usage (low-traffic consumer / registry ops \u2014 author verbs live on `lotics app`):");
|
|
68251
|
+
console.error(" The high-traffic consumer verbs are top-level:");
|
|
68252
|
+
console.error(" lotics install <package_id> [--version N] [--bind-to <alias>=<kdc_id>] [--config key=value ...] Install a package (app or content)");
|
|
68253
|
+
console.error(" lotics uninstall <app_id|pci_id> [--archive-tables] [--keep-content] Uninstall \u2014 dispatched by id (app vs content)");
|
|
68254
|
+
console.error(" lotics upgrade <app_id|pci_id|package_id> [--version N] [--resolve <key>=... ] [--bind-to ...] [--apply-all] Upgrade \u2014 app / content / whole fleet (apg_)");
|
|
69427
68255
|
console.error(" lotics package doctor [app_id] Health: version pin vs latest + binding/knowledge drift");
|
|
69428
|
-
console.error(" lotics package
|
|
68256
|
+
console.error(" lotics package config <app_id> [--set key=value ...] Show or edit an installation's config knobs");
|
|
69429
68257
|
console.error(" lotics package eject <app_id> Sever an installation's package link");
|
|
69430
|
-
console.error(" lotics package
|
|
69431
|
-
console.error(" lotics package
|
|
69432
|
-
console.error(" lotics package fleet-upgrade <package_id> [--version N] Upgrade every org installation (clean ones apply; findings skip)");
|
|
68258
|
+
console.error(" lotics package show <package_id> Registry metadata + version history (channel, yank, changelog)");
|
|
68259
|
+
console.error(" lotics package list-content List the workspace's content installations (standalone content installs)");
|
|
69433
68260
|
console.error(" lotics package yank <package_id> <version> [--undo] Refuse new installs/upgrades of a broken published version (pinned installations keep running)");
|
|
68261
|
+
console.error(" A content package (a SET of live docs + templates) \u2014 publish + release with the tools, retire on `lotics app`:");
|
|
68262
|
+
console.error(` lotics run publish_content '{"name":"\u2026","knowledge_doc_ids":[\u2026],"template_ids":[\u2026]}' Publish v1 (a fixed set)`);
|
|
68263
|
+
console.error(` lotics run release_content '{"package_id":"apg_\u2026","changelog":"\u2026"}' Cut the next version (add "dry_run":true to preview)`);
|
|
68264
|
+
console.error(" lotics app unpublish <package_id> Take the whole package off the shelf (kind-agnostic \u2014 retires content too)");
|
|
69434
68265
|
process.exit(1);
|
|
69435
68266
|
}
|
|
69436
68267
|
if (command === "run" && !subcommand) {
|
|
@@ -69506,11 +68337,11 @@ Available workspaces:`);
|
|
|
69506
68337
|
if (subcommand === "create") {
|
|
69507
68338
|
const name = toolArgs;
|
|
69508
68339
|
if (!name) {
|
|
69509
|
-
console.error("Usage: lotics workspace create <name> [--timezone <tz>]
|
|
68340
|
+
console.error("Usage: lotics workspace create <name> [--timezone <tz>]");
|
|
69510
68341
|
process.exit(1);
|
|
69511
68342
|
}
|
|
69512
68343
|
const timezone = flags.timezone;
|
|
69513
|
-
const created = await client.createWorkspace({ name, timezone
|
|
68344
|
+
const created = await client.createWorkspace({ name, timezone });
|
|
69514
68345
|
setSelectedWorkspace(created.id);
|
|
69515
68346
|
client.setWorkspaceId(created.id);
|
|
69516
68347
|
if (flags.json) {
|
|
@@ -69572,48 +68403,88 @@ Available workspaces:`);
|
|
|
69572
68403
|
return;
|
|
69573
68404
|
}
|
|
69574
68405
|
await resolveWorkspace(client, ctx);
|
|
69575
|
-
if (command === "
|
|
69576
|
-
|
|
69577
|
-
|
|
69578
|
-
|
|
69579
|
-
|
|
69580
|
-
|
|
69581
|
-
|
|
68406
|
+
if (command === "install") {
|
|
68407
|
+
const packageId = subcommand;
|
|
68408
|
+
if (!packageId) {
|
|
68409
|
+
console.error("Usage: lotics install <package_id> [--version N] [--bind-to <alias>=<kdc_id> ...] [--config key=value ...]");
|
|
68410
|
+
process.exit(1);
|
|
68411
|
+
}
|
|
68412
|
+
let version2;
|
|
68413
|
+
if (flags.packageVersion !== void 0) {
|
|
68414
|
+
version2 = Number(flags.packageVersion);
|
|
68415
|
+
if (!Number.isInteger(version2) || version2 <= 0) {
|
|
68416
|
+
console.error(`Invalid --version "${flags.packageVersion}" \u2014 expected a positive integer.`);
|
|
69582
68417
|
process.exit(1);
|
|
69583
68418
|
}
|
|
69584
|
-
let version2;
|
|
69585
|
-
if (flags.packageVersion !== void 0) {
|
|
69586
|
-
version2 = Number(flags.packageVersion);
|
|
69587
|
-
if (!Number.isInteger(version2) || version2 <= 0) {
|
|
69588
|
-
console.error(`Invalid --version "${flags.packageVersion}" \u2014 expected a positive integer.`);
|
|
69589
|
-
process.exit(1);
|
|
69590
|
-
}
|
|
69591
|
-
}
|
|
69592
|
-
const config2 = flags.config.length > 0 ? parseInstallConfigFlags(flags.config) : void 0;
|
|
69593
|
-
await packageInstall(client, {
|
|
69594
|
-
package_id: packageId,
|
|
69595
|
-
version: version2,
|
|
69596
|
-
bind_to: parseBindToFlags(flags.bindTo),
|
|
69597
|
-
...config2 !== void 0 ? { config: config2 } : {}
|
|
69598
|
-
});
|
|
69599
|
-
return;
|
|
69600
68419
|
}
|
|
69601
|
-
|
|
69602
|
-
|
|
69603
|
-
|
|
69604
|
-
|
|
69605
|
-
|
|
69606
|
-
|
|
69607
|
-
|
|
68420
|
+
const config2 = flags.config.length > 0 ? parseInstallConfigFlags(flags.config) : void 0;
|
|
68421
|
+
await packageInstall(client, {
|
|
68422
|
+
package_id: packageId,
|
|
68423
|
+
version: version2,
|
|
68424
|
+
bind_to: parseBindToFlags(flags.bindTo),
|
|
68425
|
+
...config2 !== void 0 ? { config: config2 } : {}
|
|
68426
|
+
});
|
|
68427
|
+
return;
|
|
68428
|
+
}
|
|
68429
|
+
if (command === "uninstall") {
|
|
68430
|
+
const id = subcommand;
|
|
68431
|
+
if (!id) {
|
|
68432
|
+
console.error("Usage: lotics uninstall <app_id|pci_id> [--archive-tables] [--keep-content]");
|
|
68433
|
+
console.error(
|
|
68434
|
+
"Dispatched by id: an app installation (app_id; --archive-tables to also archive its scaffolded tables) or a standalone content installation (pci_ id from `lotics install` / lotics package list-content; --keep-content to retain its docs)."
|
|
68435
|
+
);
|
|
68436
|
+
process.exit(1);
|
|
68437
|
+
}
|
|
68438
|
+
await packageUninstall(client, {
|
|
68439
|
+
id,
|
|
68440
|
+
keep_content: flags.keepContent,
|
|
68441
|
+
archive_tables: flags.archiveTables
|
|
68442
|
+
});
|
|
68443
|
+
return;
|
|
68444
|
+
}
|
|
68445
|
+
if (command === "upgrade") {
|
|
68446
|
+
const target = subcommand;
|
|
68447
|
+
if (!target) {
|
|
68448
|
+
console.error(
|
|
68449
|
+
"Usage: lotics upgrade <app_id | pci_id | package_id> [--version N] [--resolve <key>=... ...] [--bind-to ...] [--apply-all]"
|
|
68450
|
+
);
|
|
68451
|
+
console.error(
|
|
68452
|
+
"Dispatched by id: an app installation (app_id), a standalone content installation (pci_ id), or a package id (apg_ \u2014 fleet-upgrades every installation across your org)."
|
|
68453
|
+
);
|
|
68454
|
+
process.exit(1);
|
|
68455
|
+
}
|
|
68456
|
+
let version2;
|
|
68457
|
+
if (flags.packageVersion !== void 0) {
|
|
68458
|
+
version2 = Number(flags.packageVersion);
|
|
68459
|
+
if (!Number.isInteger(version2) || version2 <= 0) {
|
|
68460
|
+
console.error(`Invalid --version "${flags.packageVersion}" \u2014 expected a positive integer.`);
|
|
69608
68461
|
process.exit(1);
|
|
69609
68462
|
}
|
|
69610
|
-
|
|
69611
|
-
|
|
69612
|
-
|
|
69613
|
-
|
|
68463
|
+
}
|
|
68464
|
+
if (target.startsWith("apg_")) {
|
|
68465
|
+
await packageFleetUpgrade(client, { package_id: target, version: version2 });
|
|
68466
|
+
return;
|
|
68467
|
+
}
|
|
68468
|
+
if (target.startsWith("pci_")) {
|
|
68469
|
+
await packageUpgradeKnowledge(client, {
|
|
68470
|
+
installation_id: target,
|
|
68471
|
+
version: version2,
|
|
68472
|
+
resolve: flags.resolve,
|
|
68473
|
+
bind_to: parseBindToFlags(flags.bindTo),
|
|
68474
|
+
applyAll: flags.applyAll
|
|
69614
68475
|
});
|
|
69615
68476
|
return;
|
|
69616
68477
|
}
|
|
68478
|
+
await packageUpgrade(client, {
|
|
68479
|
+
app_id: target,
|
|
68480
|
+
version: version2,
|
|
68481
|
+
resolve: flags.resolve,
|
|
68482
|
+
bindTo: flags.bindTo,
|
|
68483
|
+
applyAll: flags.applyAll
|
|
68484
|
+
});
|
|
68485
|
+
return;
|
|
68486
|
+
}
|
|
68487
|
+
if (command === "package") {
|
|
69617
68488
|
if (subcommand === "list-content") {
|
|
69618
68489
|
await packageListContent(client);
|
|
69619
68490
|
return;
|
|
@@ -69627,15 +68498,6 @@ Available workspaces:`);
|
|
|
69627
68498
|
await packageConfig(client, { app_id: appId, sets: flags.set });
|
|
69628
68499
|
return;
|
|
69629
68500
|
}
|
|
69630
|
-
if (subcommand === "retire") {
|
|
69631
|
-
const packageId = toolArgs;
|
|
69632
|
-
if (!packageId) {
|
|
69633
|
-
console.error("Usage: lotics package retire <package_id> [--undo]");
|
|
69634
|
-
process.exit(1);
|
|
69635
|
-
}
|
|
69636
|
-
await packageRetire(client, { package_id: packageId, undo: flags.undo });
|
|
69637
|
-
return;
|
|
69638
|
-
}
|
|
69639
68501
|
if (subcommand === "show") {
|
|
69640
68502
|
const packageId = toolArgs;
|
|
69641
68503
|
if (!packageId) {
|
|
@@ -69654,34 +68516,6 @@ Available workspaces:`);
|
|
|
69654
68516
|
await packageEject(client, { app_id: appId });
|
|
69655
68517
|
return;
|
|
69656
68518
|
}
|
|
69657
|
-
if (subcommand === "extract") {
|
|
69658
|
-
const appId = toolArgs;
|
|
69659
|
-
if (!appId) {
|
|
69660
|
-
console.error("Usage: lotics package extract <app_id> [path]");
|
|
69661
|
-
console.error("Promotes a bespoke app to a draft package project (contract + binding + templates).");
|
|
69662
|
-
process.exit(1);
|
|
69663
|
-
}
|
|
69664
|
-
await packageExtract(client, { app_id: appId, targetPath: restArgs[0] });
|
|
69665
|
-
return;
|
|
69666
|
-
}
|
|
69667
|
-
if (subcommand === "adopt") {
|
|
69668
|
-
const appId = toolArgs;
|
|
69669
|
-
if (!appId) {
|
|
69670
|
-
console.error("Usage: lotics package adopt <app_id> [path]");
|
|
69671
|
-
console.error("Binds the published package project (this dir, or [path]) onto the origin app.");
|
|
69672
|
-
process.exit(1);
|
|
69673
|
-
}
|
|
69674
|
-
let version2;
|
|
69675
|
-
if (flags.packageVersion !== void 0) {
|
|
69676
|
-
version2 = Number(flags.packageVersion);
|
|
69677
|
-
if (!Number.isInteger(version2) || version2 <= 0) {
|
|
69678
|
-
console.error(`Invalid --version "${flags.packageVersion}" \u2014 expected a positive integer.`);
|
|
69679
|
-
process.exit(1);
|
|
69680
|
-
}
|
|
69681
|
-
}
|
|
69682
|
-
await packageAdopt(client, { app_id: appId, version: version2, projectDir: restArgs[0] });
|
|
69683
|
-
return;
|
|
69684
|
-
}
|
|
69685
68519
|
if (subcommand === "yank") {
|
|
69686
68520
|
const packageId = toolArgs;
|
|
69687
68521
|
const version2 = Number(restArgs[0]);
|
|
@@ -69692,23 +68526,6 @@ Available workspaces:`);
|
|
|
69692
68526
|
await packageYank(client, { package_id: packageId, version: version2, undo: flags.undo });
|
|
69693
68527
|
return;
|
|
69694
68528
|
}
|
|
69695
|
-
if (subcommand === "fleet-upgrade") {
|
|
69696
|
-
const packageId = toolArgs;
|
|
69697
|
-
if (!packageId) {
|
|
69698
|
-
console.error("Usage: lotics package fleet-upgrade <package_id> [--version N]");
|
|
69699
|
-
process.exit(1);
|
|
69700
|
-
}
|
|
69701
|
-
let version2;
|
|
69702
|
-
if (flags.packageVersion !== void 0) {
|
|
69703
|
-
version2 = Number(flags.packageVersion);
|
|
69704
|
-
if (!Number.isInteger(version2) || version2 <= 0) {
|
|
69705
|
-
console.error(`Invalid --version "${flags.packageVersion}" \u2014 expected a positive integer.`);
|
|
69706
|
-
process.exit(1);
|
|
69707
|
-
}
|
|
69708
|
-
}
|
|
69709
|
-
await packageFleetUpgrade(client, { package_id: packageId, version: version2 });
|
|
69710
|
-
return;
|
|
69711
|
-
}
|
|
69712
68529
|
if (subcommand === "doctor") {
|
|
69713
68530
|
await packageDoctor(client, { app_id: toolArgs });
|
|
69714
68531
|
return;
|
|
@@ -69716,78 +68533,23 @@ Available workspaces:`);
|
|
|
69716
68533
|
if (subcommand === "rebind-role") {
|
|
69717
68534
|
const appId = toolArgs;
|
|
69718
68535
|
const [roleAlias, groupId] = restArgs;
|
|
69719
|
-
if (!appId || !roleAlias || !groupId) {
|
|
69720
|
-
console.error("Usage: lotics package rebind-role <app_id> <role_alias> <grp_id>");
|
|
69721
|
-
process.exit(1);
|
|
69722
|
-
}
|
|
69723
|
-
const result = await client.rebindPackageRole(appId, {
|
|
69724
|
-
role_alias: roleAlias,
|
|
69725
|
-
group_id: groupId
|
|
69726
|
-
});
|
|
69727
68536
|
console.error(
|
|
69728
|
-
|
|
68537
|
+
`\`lotics package rebind-role\` was folded into the upgrade grammar. Re-point a live role with:
|
|
68538
|
+
lotics upgrade ${appId ?? "<app_id>"} --resolve roles.${roleAlias ?? "<alias>"}=${groupId ?? "<grp_id>"}`
|
|
69729
68539
|
);
|
|
69730
|
-
|
|
69731
|
-
}
|
|
69732
|
-
if (subcommand === "upgrade") {
|
|
69733
|
-
const target = toolArgs;
|
|
69734
|
-
if (!target) {
|
|
69735
|
-
console.error(
|
|
69736
|
-
"Usage: lotics package upgrade <app_id | pci_installation_id> [--version N] [--resolve <key>=... ...]"
|
|
69737
|
-
);
|
|
69738
|
-
process.exit(1);
|
|
69739
|
-
}
|
|
69740
|
-
let version2;
|
|
69741
|
-
if (flags.packageVersion !== void 0) {
|
|
69742
|
-
version2 = Number(flags.packageVersion);
|
|
69743
|
-
if (!Number.isInteger(version2) || version2 <= 0) {
|
|
69744
|
-
console.error(`Invalid --version "${flags.packageVersion}" \u2014 expected a positive integer.`);
|
|
69745
|
-
process.exit(1);
|
|
69746
|
-
}
|
|
69747
|
-
}
|
|
69748
|
-
if (target.startsWith("pci_")) {
|
|
69749
|
-
await packageUpgradeKnowledge(client, {
|
|
69750
|
-
installation_id: target,
|
|
69751
|
-
version: version2,
|
|
69752
|
-
resolve: flags.resolve,
|
|
69753
|
-
bind_to: parseBindToFlags(flags.bindTo),
|
|
69754
|
-
applyAll: flags.applyAll
|
|
69755
|
-
});
|
|
69756
|
-
return;
|
|
69757
|
-
}
|
|
69758
|
-
await packageUpgrade(client, {
|
|
69759
|
-
app_id: target,
|
|
69760
|
-
version: version2,
|
|
69761
|
-
resolve: flags.resolve,
|
|
69762
|
-
bindTo: flags.bindTo,
|
|
69763
|
-
applyAll: flags.applyAll
|
|
69764
|
-
});
|
|
69765
|
-
return;
|
|
69766
|
-
}
|
|
69767
|
-
if (subcommand === "publish") {
|
|
69768
|
-
await packagePublish(client, { projectDir: toolArgs, changelog: flags.message });
|
|
69769
|
-
return;
|
|
69770
|
-
}
|
|
69771
|
-
if (subcommand === "sync") {
|
|
69772
|
-
await packageSync(client, { projectDir: toolArgs });
|
|
69773
|
-
return;
|
|
69774
|
-
}
|
|
69775
|
-
if (subcommand === "reset") {
|
|
69776
|
-
await packageReset(client, { projectDir: toolArgs });
|
|
69777
|
-
return;
|
|
68540
|
+
process.exit(1);
|
|
69778
68541
|
}
|
|
69779
|
-
|
|
69780
|
-
|
|
69781
|
-
|
|
69782
|
-
|
|
69783
|
-
|
|
69784
|
-
|
|
69785
|
-
|
|
69786
|
-
|
|
69787
|
-
|
|
69788
|
-
|
|
69789
|
-
|
|
69790
|
-
return;
|
|
68542
|
+
const movedVerbs = {
|
|
68543
|
+
install: "lotics install <package_id>",
|
|
68544
|
+
uninstall: "lotics uninstall <app_id|pci_id>",
|
|
68545
|
+
upgrade: "lotics upgrade <app_id|pci_id|package_id>",
|
|
68546
|
+
"fleet-upgrade": "lotics upgrade <package_id>"
|
|
68547
|
+
};
|
|
68548
|
+
if (subcommand !== void 0 && subcommand in movedVerbs) {
|
|
68549
|
+
console.error(
|
|
68550
|
+
`\`lotics package ${subcommand}\` moved to the top level \u2014 run \`${movedVerbs[subcommand]}\` instead.`
|
|
68551
|
+
);
|
|
68552
|
+
process.exit(1);
|
|
69791
68553
|
}
|
|
69792
68554
|
console.error(`Unknown package subcommand: ${subcommand}`);
|
|
69793
68555
|
console.error("Run 'lotics package' for usage.");
|
|
@@ -69824,6 +68586,34 @@ Available workspaces:`);
|
|
|
69824
68586
|
await appDeploy(client, { message });
|
|
69825
68587
|
return;
|
|
69826
68588
|
}
|
|
68589
|
+
if (subcommand === "publish") {
|
|
68590
|
+
await appPublish(client, {
|
|
68591
|
+
app_id: toolArgs,
|
|
68592
|
+
renames: flags.rename,
|
|
68593
|
+
changelog: flags.message,
|
|
68594
|
+
yes: flags.yes
|
|
68595
|
+
});
|
|
68596
|
+
return;
|
|
68597
|
+
}
|
|
68598
|
+
if (subcommand === "release") {
|
|
68599
|
+
if (!flags.message) {
|
|
68600
|
+
console.error("Usage: lotics app release [app_id|.] -m <changelog> [--yes]");
|
|
68601
|
+
console.error("Snapshots the origin app into its next package version (preview, then --yes to publish).");
|
|
68602
|
+
process.exit(1);
|
|
68603
|
+
}
|
|
68604
|
+
await appRelease(client, { app_id: toolArgs, changelog: flags.message, yes: flags.yes });
|
|
68605
|
+
return;
|
|
68606
|
+
}
|
|
68607
|
+
if (subcommand === "unpublish") {
|
|
68608
|
+
const id = toolArgs;
|
|
68609
|
+
if (!id) {
|
|
68610
|
+
console.error("Usage: lotics app unpublish <app_id|package_id> [--undo]");
|
|
68611
|
+
console.error("Takes a published package off the shelf; existing installations keep working.");
|
|
68612
|
+
process.exit(1);
|
|
68613
|
+
}
|
|
68614
|
+
await appUnpublish(client, { id, undo: flags.undo });
|
|
68615
|
+
return;
|
|
68616
|
+
}
|
|
69827
68617
|
if (subcommand === "subdomain") {
|
|
69828
68618
|
const newSubdomain = toolArgs;
|
|
69829
68619
|
if (!newSubdomain) {
|