@lotics/cli 0.75.0 → 0.76.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 +5 -0
- package/dist/app_commands.d.ts +16 -0
- package/dist/app_commands.js +1 -1
- package/dist/args.d.ts +8 -0
- package/dist/args.js +26 -0
- package/dist/cli.js +68 -14
- package/dist/cli_dispatch.test.d.ts +1 -0
- package/dist/cli_dispatch.test.js +90 -0
- package/dist/client.d.ts +63 -0
- package/dist/client.js +33 -0
- package/dist/package_commands.d.ts +37 -0
- package/dist/package_commands.js +182 -6
- package/dist/src/cli.js +636 -19
- package/dist/starter_template.d.ts +19 -0
- package/dist/starter_template.js +389 -0
- package/dist/starter_template.test.js +69 -1
- package/package.json +1 -1
package/dist/src/cli.js
CHANGED
|
@@ -29775,6 +29775,33 @@ var LoticsClient = class {
|
|
|
29775
29775
|
body
|
|
29776
29776
|
);
|
|
29777
29777
|
}
|
|
29778
|
+
/**
|
|
29779
|
+
* Uninstall a package installation (backs `lotics package uninstall`). Does
|
|
29780
|
+
* everything DELETE does plus archives the installation's lifecycle
|
|
29781
|
+
* artifacts; with `archive_tables` it also archives the scaffolded entity
|
|
29782
|
+
* tables — refused server-side unless this installation created them
|
|
29783
|
+
* (provenance) and nothing else references them. Admin-only.
|
|
29784
|
+
*/
|
|
29785
|
+
async uninstallAppPackage(app_id, body) {
|
|
29786
|
+
return this.request("POST", `/v1/apps/${encodeURIComponent(app_id)}/uninstall`, body);
|
|
29787
|
+
}
|
|
29788
|
+
/**
|
|
29789
|
+
* Partial-merge a package installation's config (backs `lotics package config
|
|
29790
|
+
* --set`). Only the provided keys change; validated against the installed
|
|
29791
|
+
* contract. Returns the full effective config. Admin-only.
|
|
29792
|
+
*/
|
|
29793
|
+
async updateAppPackageConfig(app_id, body) {
|
|
29794
|
+
return this.request("PATCH", `/v1/apps/${encodeURIComponent(app_id)}/package-config`, body);
|
|
29795
|
+
}
|
|
29796
|
+
/**
|
|
29797
|
+
* Retire (or `undo` un-retire) a registry package (backs `lotics package
|
|
29798
|
+
* retire`). Retiring refuses NEW installs and hides the package from
|
|
29799
|
+
* non-owning orgs; existing installations keep working and may still upgrade.
|
|
29800
|
+
* Owner-org admin-only.
|
|
29801
|
+
*/
|
|
29802
|
+
async retireAppPackage(package_id, body) {
|
|
29803
|
+
return this.request("POST", `/v1/app-packages/${encodeURIComponent(package_id)}/retire`, body);
|
|
29804
|
+
}
|
|
29778
29805
|
/**
|
|
29779
29806
|
* Eject an installation from its package — re-deploy the pinned version's
|
|
29780
29807
|
* source as a workspace-owned app version, then sever the package link
|
|
@@ -29854,6 +29881,10 @@ var LoticsClient = class {
|
|
|
29854
29881
|
async getAppPackage(package_id) {
|
|
29855
29882
|
return this.request("GET", `/v1/app-packages/${encodeURIComponent(package_id)}`);
|
|
29856
29883
|
}
|
|
29884
|
+
/** Version history newest-first (no contract payloads) — backs `lotics package show`. Admin-only. */
|
|
29885
|
+
async listAppPackageVersions(package_id) {
|
|
29886
|
+
return this.request("GET", `/v1/app-packages/${encodeURIComponent(package_id)}/versions`);
|
|
29887
|
+
}
|
|
29857
29888
|
/**
|
|
29858
29889
|
* Publish a new immutable package version — multipart upload of the alias-keyed
|
|
29859
29890
|
* contract (JSON) + the prebuilt code bundle (a gzipped tarball carrying
|
|
@@ -29870,6 +29901,7 @@ var LoticsClient = class {
|
|
|
29870
29901
|
"bundle.tar.gz"
|
|
29871
29902
|
);
|
|
29872
29903
|
if (args.changelog) formData.append("changelog", args.changelog);
|
|
29904
|
+
if (args.channel) formData.append("channel", args.channel);
|
|
29873
29905
|
const url = `${this.baseUrl}/v1/app-packages/${encodeURIComponent(package_id)}/versions`;
|
|
29874
29906
|
const response = await fetch(url, {
|
|
29875
29907
|
method: "POST",
|
|
@@ -31270,6 +31302,379 @@ See https://lotics.ai/docs/app-sdk for the SDK reference.
|
|
|
31270
31302
|
}
|
|
31271
31303
|
];
|
|
31272
31304
|
}
|
|
31305
|
+
function buildPackageStarterOverrides(args) {
|
|
31306
|
+
const nameLit = JSON.stringify(args.app_name);
|
|
31307
|
+
return [
|
|
31308
|
+
{
|
|
31309
|
+
// The package App.tsx imports `../.lotics/app_fields` — a DETERMINISTIC
|
|
31310
|
+
// pure function of contract.json (generate_package_fields.ts) — so unlike
|
|
31311
|
+
// the app starter's fully-ignored `.lotics`, it must be committed or the
|
|
31312
|
+
// scaffold's own shipped CI (npm ci → typecheck/test/build on a clean
|
|
31313
|
+
// clone) fails on the missing module. The sync-written `.d.ts` companions
|
|
31314
|
+
// stay ignored: they need a live workspace and their absence only degrades
|
|
31315
|
+
// hooks to untyped overloads, never breaks the build.
|
|
31316
|
+
path: ".gitignore",
|
|
31317
|
+
content: `node_modules
|
|
31318
|
+
dist
|
|
31319
|
+
*.tsbuildinfo
|
|
31320
|
+
.DS_Store
|
|
31321
|
+
.lotics/*
|
|
31322
|
+
!.lotics/app_fields.ts
|
|
31323
|
+
coverage
|
|
31324
|
+
`
|
|
31325
|
+
},
|
|
31326
|
+
{
|
|
31327
|
+
path: "src/App.tsx",
|
|
31328
|
+
content: `import { useMemo, type ReactNode } from "react";
|
|
31329
|
+
import { ScrollView, View } from "react-native";
|
|
31330
|
+
import { useConfig, useQuery, readSelect, row } from "@lotics/app-sdk";
|
|
31331
|
+
import { AppRouter } from "@lotics/app-sdk/router";
|
|
31332
|
+
import { Text } from "@lotics/ui/text";
|
|
31333
|
+
import { Card } from "@lotics/ui/card";
|
|
31334
|
+
import { Button } from "@lotics/ui/button";
|
|
31335
|
+
import { OptionBadge } from "@lotics/ui/option_badge";
|
|
31336
|
+
import { Skeleton } from "@lotics/ui/skeleton";
|
|
31337
|
+
import { EmptyState } from "@lotics/ui/empty_state";
|
|
31338
|
+
import { Callout, CalloutTitle, CalloutText, CalloutActions } from "@lotics/ui/callout";
|
|
31339
|
+
import { F, OPT } from "../.lotics/app_fields";
|
|
31340
|
+
|
|
31341
|
+
// This is a PACKAGE starter \u2014 a workspace-agnostic blueprint installed into many
|
|
31342
|
+
// workspaces, each binding the contract's aliases to DIFFERENT concrete ids. So a
|
|
31343
|
+
// screen never hardcodes a \`fld_\u2026\`/\`opt_\u2026\` id: it addresses a field by contract
|
|
31344
|
+
// alias through \`F\` and a select option through \`OPT\`, both from the generated
|
|
31345
|
+
// \`.lotics/app_fields.ts\` (which resolves every alias to THIS installation's id at
|
|
31346
|
+
// module load from the binding). The aliases come from contract.json \u2014 entity
|
|
31347
|
+
// \`item\`, fields name/notes/status, options open/done, query \`items\`, config knob
|
|
31348
|
+
// \`heading\`. Edit contract.json, then \`lotics package sync\` to regenerate F/OPT.
|
|
31349
|
+
|
|
31350
|
+
interface Item {
|
|
31351
|
+
id: string;
|
|
31352
|
+
name: string;
|
|
31353
|
+
notes: string;
|
|
31354
|
+
status: ReturnType<typeof readSelect>[number] | null;
|
|
31355
|
+
done: boolean;
|
|
31356
|
+
}
|
|
31357
|
+
|
|
31358
|
+
// The \`items\` query is a bare from_entity, so a row is keyed by FIELD id: read a
|
|
31359
|
+
// cell as \`r[F.ITEM.<field>]\` and decode it with the SDK's pure readers (\`row.text\`,
|
|
31360
|
+
// \`readSelect\`). \`OPT.ITEM.status.done\` is this install's \`opt_\u2026\` id, so comparing
|
|
31361
|
+
// the cell's stored option key to it marks a done row.
|
|
31362
|
+
function decode(r: Record<string, unknown>): Item {
|
|
31363
|
+
const status = readSelect(r[F.ITEM.status])[0] ?? null;
|
|
31364
|
+
return {
|
|
31365
|
+
id: row.text(r.__source_record_id),
|
|
31366
|
+
name: row.text(r[F.ITEM.name]),
|
|
31367
|
+
notes: row.text(r[F.ITEM.notes]),
|
|
31368
|
+
status,
|
|
31369
|
+
done: status?.key === OPT.ITEM.status.done,
|
|
31370
|
+
};
|
|
31371
|
+
}
|
|
31372
|
+
|
|
31373
|
+
// Outer <View flex:1> claims the iframe height (index.html sets html/body/#root to
|
|
31374
|
+
// 100% + #root is a flex column); the list scrolls beneath the heading. Keep this
|
|
31375
|
+
// flex chain plain (not @lotics/ui/stack) so a fill-remaining-space child gets height.
|
|
31376
|
+
function Screen({ children }: { children: ReactNode }) {
|
|
31377
|
+
return (
|
|
31378
|
+
<View style={{ flex: 1 }}>
|
|
31379
|
+
<ScrollView contentContainerStyle={{ padding: 24, alignItems: "center" }}>
|
|
31380
|
+
<View style={{ maxWidth: 640, width: "100%", gap: 16 }}>{children}</View>
|
|
31381
|
+
</ScrollView>
|
|
31382
|
+
</View>
|
|
31383
|
+
);
|
|
31384
|
+
}
|
|
31385
|
+
|
|
31386
|
+
function ItemsScreen() {
|
|
31387
|
+
// \`heading\` is a contract config knob \u2014 an installation overrides it and
|
|
31388
|
+
// useConfig() renders the customized value; the literal here is only the fallback.
|
|
31389
|
+
const { config } = useConfig({ heading: ${nameLit} });
|
|
31390
|
+
const itemsQ = useQuery("items");
|
|
31391
|
+
const items = useMemo(() => itemsQ.rows.map(decode), [itemsQ.rows]);
|
|
31392
|
+
// First-load only: a skeleton while the very first fetch is in flight with no rows
|
|
31393
|
+
// yet \u2014 never blank already-loaded rows to a spinner on a background refetch.
|
|
31394
|
+
const firstLoad = itemsQ.loading && items.length === 0;
|
|
31395
|
+
|
|
31396
|
+
return (
|
|
31397
|
+
<Screen>
|
|
31398
|
+
<Text size="xxl" weight="semibold" level={1}>
|
|
31399
|
+
{config.heading}
|
|
31400
|
+
</Text>
|
|
31401
|
+
|
|
31402
|
+
{itemsQ.error ? (
|
|
31403
|
+
<Callout tone="error">
|
|
31404
|
+
<CalloutTitle>Couldn't load items</CalloutTitle>
|
|
31405
|
+
<CalloutText>{itemsQ.error}</CalloutText>
|
|
31406
|
+
<CalloutActions>
|
|
31407
|
+
<Button title="Try again" onPress={() => itemsQ.refetch()} />
|
|
31408
|
+
</CalloutActions>
|
|
31409
|
+
</Callout>
|
|
31410
|
+
) : firstLoad ? (
|
|
31411
|
+
<View style={{ gap: 10 }}>
|
|
31412
|
+
{[0, 1, 2].map((i) => (
|
|
31413
|
+
<Skeleton key={i} height={64} radius={12} />
|
|
31414
|
+
))}
|
|
31415
|
+
</View>
|
|
31416
|
+
) : items.length > 0 ? (
|
|
31417
|
+
<View style={{ gap: 10 }}>
|
|
31418
|
+
{items.map((item) => (
|
|
31419
|
+
<Card key={item.id}>
|
|
31420
|
+
<View style={{ flexDirection: "row", alignItems: "center", gap: 12 }}>
|
|
31421
|
+
<View style={{ flex: 1, gap: 2 }}>
|
|
31422
|
+
<Text weight="medium" color={item.done ? "muted" : undefined}>
|
|
31423
|
+
{item.name}
|
|
31424
|
+
</Text>
|
|
31425
|
+
{item.notes.length > 0 ? (
|
|
31426
|
+
<Text size="sm" color="muted" numberOfLines={1}>
|
|
31427
|
+
{item.notes}
|
|
31428
|
+
</Text>
|
|
31429
|
+
) : null}
|
|
31430
|
+
</View>
|
|
31431
|
+
<OptionBadge value={item.status} />
|
|
31432
|
+
</View>
|
|
31433
|
+
</Card>
|
|
31434
|
+
))}
|
|
31435
|
+
</View>
|
|
31436
|
+
) : (
|
|
31437
|
+
<EmptyState
|
|
31438
|
+
icon="list-checks"
|
|
31439
|
+
message="No items yet"
|
|
31440
|
+
hint="Rows in this package's table appear here."
|
|
31441
|
+
/>
|
|
31442
|
+
)}
|
|
31443
|
+
</Screen>
|
|
31444
|
+
);
|
|
31445
|
+
}
|
|
31446
|
+
|
|
31447
|
+
const routes = [{ path: "/", element: <ItemsScreen /> }];
|
|
31448
|
+
|
|
31449
|
+
export default function App() {
|
|
31450
|
+
return <AppRouter routes={routes} />;
|
|
31451
|
+
}
|
|
31452
|
+
`
|
|
31453
|
+
},
|
|
31454
|
+
{
|
|
31455
|
+
path: "src/App.test.tsx",
|
|
31456
|
+
content: `import { describe, test, expect, beforeEach, afterEach, vi } from "vitest";
|
|
31457
|
+
import { render, screen, cleanup } from "@testing-library/react";
|
|
31458
|
+
import App from "./App";
|
|
31459
|
+
|
|
31460
|
+
// Mock @lotics/app-sdk \u2014 the app's one external boundary (data + RPC + the package
|
|
31461
|
+
// binding). \`importOriginal\` keeps the PURE cell readers (\`row\`, \`readSelect\`) real,
|
|
31462
|
+
// so mock rows decode exactly like wire rows; only the hooks and getAppBinding are
|
|
31463
|
+
// stubbed. The binding ids below are what F.ITEM.* / OPT.ITEM.status.* resolve to;
|
|
31464
|
+
// the query rows are keyed by the SAME ids.
|
|
31465
|
+
const h = vi.hoisted(() => ({
|
|
31466
|
+
state: {
|
|
31467
|
+
rows: [] as Array<Record<string, unknown>>,
|
|
31468
|
+
loading: false,
|
|
31469
|
+
error: null as string | null,
|
|
31470
|
+
},
|
|
31471
|
+
binding: {
|
|
31472
|
+
fields: { "item.name": "fld_name", "item.notes": "fld_notes", "item.status": "fld_status" },
|
|
31473
|
+
options: { "item.status:open": "opt_open", "item.status:done": "opt_done" },
|
|
31474
|
+
roles: {},
|
|
31475
|
+
},
|
|
31476
|
+
}));
|
|
31477
|
+
|
|
31478
|
+
vi.mock("@lotics/app-sdk", async (importOriginal) => {
|
|
31479
|
+
const actual = await importOriginal<typeof import("@lotics/app-sdk")>();
|
|
31480
|
+
return {
|
|
31481
|
+
...actual,
|
|
31482
|
+
getAppBinding: async () => h.binding,
|
|
31483
|
+
useConfig: (defaults: Record<string, unknown>) => ({ config: defaults, loading: false }),
|
|
31484
|
+
useQuery: () => ({
|
|
31485
|
+
rows: h.state.rows,
|
|
31486
|
+
loading: h.state.loading,
|
|
31487
|
+
isValidating: false,
|
|
31488
|
+
error: h.state.error,
|
|
31489
|
+
refetch: () => {},
|
|
31490
|
+
}),
|
|
31491
|
+
};
|
|
31492
|
+
});
|
|
31493
|
+
|
|
31494
|
+
const rowOf = (id: string, name: string, notes: string, statusKey: string) => ({
|
|
31495
|
+
__source_record_id: id,
|
|
31496
|
+
fld_name: name,
|
|
31497
|
+
fld_notes: notes,
|
|
31498
|
+
fld_status: [{ key: statusKey, label: statusKey === "opt_done" ? "Done" : "Open" }],
|
|
31499
|
+
});
|
|
31500
|
+
|
|
31501
|
+
beforeEach(() => {
|
|
31502
|
+
h.state.rows = [];
|
|
31503
|
+
h.state.loading = false;
|
|
31504
|
+
h.state.error = null;
|
|
31505
|
+
});
|
|
31506
|
+
|
|
31507
|
+
// vitest globals are off, so @testing-library/react's automatic afterEach cleanup is
|
|
31508
|
+
// never registered \u2014 clean up explicitly or renders accumulate across tests and a
|
|
31509
|
+
// second render's duplicate matches fail getByText.
|
|
31510
|
+
afterEach(cleanup);
|
|
31511
|
+
|
|
31512
|
+
describe("App", () => {
|
|
31513
|
+
test("renders the heading and the loaded items with status badges", () => {
|
|
31514
|
+
h.state.rows = [
|
|
31515
|
+
rowOf("1", "First item", "with a note", "opt_open"),
|
|
31516
|
+
rowOf("2", "Second item", "", "opt_done"),
|
|
31517
|
+
];
|
|
31518
|
+
render(<App />);
|
|
31519
|
+
|
|
31520
|
+
expect(screen.getByText(${nameLit})).toBeTruthy();
|
|
31521
|
+
expect(screen.getByText("First item")).toBeTruthy();
|
|
31522
|
+
expect(screen.getByText("Second item")).toBeTruthy();
|
|
31523
|
+
expect(screen.getByText("with a note")).toBeTruthy();
|
|
31524
|
+
expect(screen.getByText("Open")).toBeTruthy();
|
|
31525
|
+
expect(screen.getByText("Done")).toBeTruthy();
|
|
31526
|
+
expect(screen.queryByText("No items yet")).toBeNull();
|
|
31527
|
+
});
|
|
31528
|
+
|
|
31529
|
+
test("shows the empty state when there are no items", () => {
|
|
31530
|
+
render(<App />);
|
|
31531
|
+
expect(screen.getByText("No items yet")).toBeTruthy();
|
|
31532
|
+
});
|
|
31533
|
+
|
|
31534
|
+
test("does not flash the list or empty state on first load", () => {
|
|
31535
|
+
h.state.loading = true;
|
|
31536
|
+
render(<App />);
|
|
31537
|
+
expect(screen.queryByText("No items yet")).toBeNull();
|
|
31538
|
+
});
|
|
31539
|
+
|
|
31540
|
+
test("surfaces a load error loudly", () => {
|
|
31541
|
+
h.state.error = "Network unreachable";
|
|
31542
|
+
render(<App />);
|
|
31543
|
+
expect(screen.getByText("Couldn't load items")).toBeTruthy();
|
|
31544
|
+
expect(screen.getByText("Network unreachable")).toBeTruthy();
|
|
31545
|
+
});
|
|
31546
|
+
});
|
|
31547
|
+
`
|
|
31548
|
+
},
|
|
31549
|
+
{
|
|
31550
|
+
path: "README.md",
|
|
31551
|
+
content: `# ${escapeHtml(args.app_name)}
|
|
31552
|
+
|
|
31553
|
+
A Lotics **app package** \u2014 a versioned, installable blueprint (a \`contract.json\`
|
|
31554
|
+
data model + app source) that installs into many workspaces. Authored locally,
|
|
31555
|
+
published and run through the \`lotics package\` CLI. See \`docs/app_packages.md\`.
|
|
31556
|
+
|
|
31557
|
+
## Dev loop
|
|
31558
|
+
|
|
31559
|
+
\`\`\`bash
|
|
31560
|
+
lotics workspace create "${escapeHtml(args.app_name)} dev" --dev # a throwaway dev workspace
|
|
31561
|
+
lotics package dev --workspace <dev_ws> # sync into it + run the dev server
|
|
31562
|
+
# edit contract.json \u2192 re-run \`lotics package sync\` to migrate + re-materialize
|
|
31563
|
+
# edit src/* \u2192 hot reload
|
|
31564
|
+
\`\`\`
|
|
31565
|
+
|
|
31566
|
+
\`sync\` / \`dev\` regenerate \`.lotics/app_fields.ts\` (the runtime \`F\` / \`OPT\` / \`ROLE\`
|
|
31567
|
+
surface) from \`contract.json\`, plus the typed \`.lotics/app_{queries,workflows,agents}.d.ts\`
|
|
31568
|
+
companions from the live installation \u2014 so \`useQuery\` / \`useWorkflow\` stay typed in the
|
|
31569
|
+
dev loop.
|
|
31570
|
+
|
|
31571
|
+
## Quality
|
|
31572
|
+
|
|
31573
|
+
\`\`\`bash
|
|
31574
|
+
npm run typecheck
|
|
31575
|
+
npm run lint
|
|
31576
|
+
npm test
|
|
31577
|
+
\`\`\`
|
|
31578
|
+
|
|
31579
|
+
## Publish
|
|
31580
|
+
|
|
31581
|
+
\`\`\`bash
|
|
31582
|
+
lotics package publish -m "v1" # build + publish an immutable version
|
|
31583
|
+
lotics package install <package_id> # install into any workspace
|
|
31584
|
+
\`\`\`
|
|
31585
|
+
|
|
31586
|
+
## The app screen
|
|
31587
|
+
|
|
31588
|
+
\`src/App.tsx\` reads the contract BY ALIAS through \`F\` / \`OPT\` (from the generated
|
|
31589
|
+
\`.lotics/app_fields.ts\`) \u2014 never a raw \`fld_\u2026\` / \`opt_\u2026\` id, since every install binds
|
|
31590
|
+
different concrete ids. It lists the \`items\` query and renders the \`heading\` config knob.
|
|
31591
|
+
\`@lotics/ui\` primitives render via react-native-web (aliased in \`vite.config.ts\`).
|
|
31592
|
+
|
|
31593
|
+
## Contract reference
|
|
31594
|
+
|
|
31595
|
+
\`contract.json\` is the package's alias-keyed data model
|
|
31596
|
+
(schema: \`@lotics/shared/schemas/app_packages\`). Every cross-reference is by **alias**;
|
|
31597
|
+
the materializer resolves alias \u2192 this workspace's concrete id at install.
|
|
31598
|
+
|
|
31599
|
+
### Aliases
|
|
31600
|
+
|
|
31601
|
+
- Entity / field / option / role / template / config aliases are lowercase slugs
|
|
31602
|
+
matching \`^[a-z][a-z0-9_]*$\`. A field's fully-qualified key is \`<entity>.<field>\`
|
|
31603
|
+
(e.g. \`item.status\`); a select option's is \`<entity>.<field>:<option>\`
|
|
31604
|
+
(e.g. \`item.status:done\`).
|
|
31605
|
+
- Query / workflow / agent aliases are runtime lookup keys (1\u2013200 chars) \u2014 the app
|
|
31606
|
+
source invokes them verbatim (\`useQuery("items")\`), so renaming one severs the call.
|
|
31607
|
+
|
|
31608
|
+
### Entities & fields
|
|
31609
|
+
|
|
31610
|
+
\`\`\`jsonc
|
|
31611
|
+
{ "alias": "item", "label": "Item", "fields": [
|
|
31612
|
+
{ "alias": "name", "label": "Name", "type": "text", "required": true },
|
|
31613
|
+
{ "alias": "status", "label": "Status", "type": "select",
|
|
31614
|
+
"options": [ { "alias": "open", "label": "Open", "color": "blue" },
|
|
31615
|
+
{ "alias": "done", "label": "Done", "color": "green" } ] } ] }
|
|
31616
|
+
\`\`\`
|
|
31617
|
+
|
|
31618
|
+
Field \`type\`: \`text\`, \`number\`, \`date\`, \`boolean\`, \`select\` (+ \`options\`),
|
|
31619
|
+
\`select_member\`, \`select_record_link\` (\`target_entity\` = an entity alias), \`files\`,
|
|
31620
|
+
\`formula\` (expression references same-entity fields as \`{alias}\`), \`rollup\`, \`lookup\`,
|
|
31621
|
+
\`autonumber\`. \`required\` is advisory (app / workflow-layer UX only \u2014 the table model
|
|
31622
|
+
has no required constraint). Select \`options\` are \`{ alias, label, color }\`.
|
|
31623
|
+
|
|
31624
|
+
### Queries
|
|
31625
|
+
|
|
31626
|
+
Alias-form AST \u2014 the same node kinds as the runtime query engine, except a
|
|
31627
|
+
\`from_table\` node carries \`from_entity\` (an entity alias) instead of a \`table_id\`:
|
|
31628
|
+
|
|
31629
|
+
\`\`\`jsonc
|
|
31630
|
+
{ "alias": "items", "ast": {
|
|
31631
|
+
"kind": "from_table", "from_entity": "item",
|
|
31632
|
+
"sort": [ { "field_key": "name", "order": "asc" } ] } }
|
|
31633
|
+
\`\`\`
|
|
31634
|
+
|
|
31635
|
+
### Workflows
|
|
31636
|
+
|
|
31637
|
+
The sole app-side mutation path. Each workflow has a \`trigger\`, typed \`inputs\` /
|
|
31638
|
+
\`outputs\`, and a JS-subset \`body\`.
|
|
31639
|
+
|
|
31640
|
+
- Trigger \u2014 app-invoked or table-lifecycle:
|
|
31641
|
+
- \`{ "type": "app" }\` \u2014 called by alias from the app (\`useWorkflow("<alias>")\`).
|
|
31642
|
+
- \`{ "type": "entity_lifecycle", "entity": "<entity>", "event": "<event>" }\` \u2014 fires
|
|
31643
|
+
on the bound table. \`event\` \u2208 \`before_create\`, \`after_create\`, \`before_update\`,
|
|
31644
|
+
\`after_update\`, \`before_delete\`, \`after_delete\`.
|
|
31645
|
+
- Typed \`inputs\` (\`text\` / \`number\` / \`date\` / \`member\` / \`select\` / \`record_link\`,
|
|
31646
|
+
plus \`object\` / \`array\`):
|
|
31647
|
+
- \`record_link\` \u2014 its \`table_id\` names an **entity alias**.
|
|
31648
|
+
- \`member\` \u2014 its \`group\` names a **role alias**.
|
|
31649
|
+
- \`select\` \u2014 an option \`value\` in \`entity.field:option\` form binds to that option's
|
|
31650
|
+
id (a plain value is a literal, left as-is).
|
|
31651
|
+
- Body **sentinel tokens** \u2014 a body addresses package objects by reserved tokens the
|
|
31652
|
+
materializer rewrites to concrete ids at install. Always inside a **string literal**:
|
|
31653
|
+
- \`@@entity:<entity>@@\`
|
|
31654
|
+
- \`@@field:<entity>.<field>@@\`
|
|
31655
|
+
- \`@@option:<entity>.<field>:<option>@@\`
|
|
31656
|
+
- \`@@role:<role>@@\`
|
|
31657
|
+
- \`@@template:<template>@@\`
|
|
31658
|
+
|
|
31659
|
+
### Config knobs
|
|
31660
|
+
|
|
31661
|
+
Typed customization knobs the app reads via \`useConfig()\`. Each has an \`alias\`,
|
|
31662
|
+
\`label\`, \`type\`, and \`default\`:
|
|
31663
|
+
|
|
31664
|
+
- \`text\` \u2192 a string default; \`boolean\` \u2192 a boolean; \`number\` \u2192 a number;
|
|
31665
|
+
\`color\` \u2192 a palette color token; \`select\` \u2192 \`options: [{ value, label }]\` with the
|
|
31666
|
+
\`default\` being one of those \`value\`s.
|
|
31667
|
+
|
|
31668
|
+
\`\`\`jsonc
|
|
31669
|
+
{ "alias": "heading", "label": "List heading", "type": "text", "default": "Items" }
|
|
31670
|
+
\`\`\`
|
|
31671
|
+
|
|
31672
|
+
Publish validates the whole contract (alias uniqueness, every cross-reference
|
|
31673
|
+
resolves, sentinel tokens name declared objects) before storing the version.
|
|
31674
|
+
`
|
|
31675
|
+
}
|
|
31676
|
+
];
|
|
31677
|
+
}
|
|
31273
31678
|
function escapeHtml(s) {
|
|
31274
31679
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
31275
31680
|
}
|
|
@@ -33716,6 +34121,9 @@ async function packageNew(args) {
|
|
|
33716
34121
|
ui_version: uiLatest ? `^${uiLatest}` : void 0,
|
|
33717
34122
|
sdk_version: packageSdkRange(sdkLatest)
|
|
33718
34123
|
});
|
|
34124
|
+
const overrides = new Map(
|
|
34125
|
+
buildPackageStarterOverrides({ app_name: args.name }).map((f) => [f.path, f.content])
|
|
34126
|
+
);
|
|
33719
34127
|
for (const file of files) {
|
|
33720
34128
|
const fullPath = path7.join(targetPath, file.path);
|
|
33721
34129
|
if (file.path === "package.json") {
|
|
@@ -33733,7 +34141,7 @@ async function packageNew(args) {
|
|
|
33733
34141
|
continue;
|
|
33734
34142
|
}
|
|
33735
34143
|
fs6.mkdirSync(path7.dirname(fullPath), { recursive: true });
|
|
33736
|
-
fs6.writeFileSync(fullPath, file.content);
|
|
34144
|
+
fs6.writeFileSync(fullPath, overrides.get(file.path) ?? file.content);
|
|
33737
34145
|
}
|
|
33738
34146
|
fs6.writeFileSync(
|
|
33739
34147
|
path7.join(targetPath, CONTRACT_FILE),
|
|
@@ -33777,7 +34185,8 @@ async function publishVersion(client, projectDir, opts) {
|
|
|
33777
34185
|
const version = await client.publishAppPackageVersion(packageId, {
|
|
33778
34186
|
contract,
|
|
33779
34187
|
bundle,
|
|
33780
|
-
changelog: opts.changelog ?? null
|
|
34188
|
+
changelog: opts.changelog ?? null,
|
|
34189
|
+
channel: opts.channel ?? "release"
|
|
33781
34190
|
});
|
|
33782
34191
|
project.manifest.version = version.version;
|
|
33783
34192
|
writePackageManifest(projectDir, project);
|
|
@@ -33813,7 +34222,8 @@ async function syncToDevWorkspace(client, projectDir) {
|
|
|
33813
34222
|
assertDevWorkspace(workspace);
|
|
33814
34223
|
writePackageAppFields(projectDir);
|
|
33815
34224
|
const { package_id, version } = await publishVersion(client, projectDir, {
|
|
33816
|
-
changelog: "dev sync"
|
|
34225
|
+
changelog: "dev sync",
|
|
34226
|
+
channel: "dev"
|
|
33817
34227
|
});
|
|
33818
34228
|
const project = readPackageProject(projectDir);
|
|
33819
34229
|
const existing = project.manifest.dev[devWorkspaceId];
|
|
@@ -33830,6 +34240,11 @@ async function syncToDevWorkspace(client, projectDir) {
|
|
|
33830
34240
|
project.manifest.dev[devWorkspaceId] = { app_id: appId, version };
|
|
33831
34241
|
writePackageManifest(projectDir, project);
|
|
33832
34242
|
const installed = await client.getApp(appId);
|
|
34243
|
+
writeAppDts(projectDir, {
|
|
34244
|
+
workflows: installed.workflows ?? void 0,
|
|
34245
|
+
queries: installed.queries ?? void 0,
|
|
34246
|
+
agents: installed.agents ?? void 0
|
|
34247
|
+
});
|
|
33833
34248
|
return { app_id: appId, app_name: installed.name, workspace_id: devWorkspaceId, version };
|
|
33834
34249
|
}
|
|
33835
34250
|
async function packageSync(client, args) {
|
|
@@ -34031,13 +34446,37 @@ async function packageUpgrade(client, args) {
|
|
|
34031
34446
|
});
|
|
34032
34447
|
console.error(`Upgraded ${app.name} \u2192 v${app.package_version} (${app.id}).`);
|
|
34033
34448
|
}
|
|
34034
|
-
|
|
34449
|
+
function trustBadge(pkg2) {
|
|
34450
|
+
if (pkg2.is_official) return "official Lotics package";
|
|
34451
|
+
if (pkg2.owned_by_caller === true) return "your organization's package";
|
|
34452
|
+
return "third-party package (runs under your authority once installed)";
|
|
34453
|
+
}
|
|
34454
|
+
async function packageShow(client, args) {
|
|
34035
34455
|
const pkg2 = await client.getAppPackage(args.package_id);
|
|
34456
|
+
const { versions } = await client.listAppPackageVersions(args.package_id);
|
|
34457
|
+
console.error(`${pkg2.name} (${pkg2.id}) \u2014 ${trustBadge(pkg2)}`);
|
|
34458
|
+
if (pkg2.description) console.error(` ${pkg2.description}`);
|
|
34459
|
+
if (pkg2.retired_at) console.error(` RETIRED ${pkg2.retired_at}`);
|
|
34036
34460
|
console.error(
|
|
34037
|
-
|
|
34461
|
+
` latest installable: ${pkg2.latest_version > 0 ? `v${pkg2.latest_version}` : "none"}`
|
|
34038
34462
|
);
|
|
34463
|
+
console.error("");
|
|
34464
|
+
for (const v of versions) {
|
|
34465
|
+
const marks = [
|
|
34466
|
+
v.version === pkg2.latest_version ? "*" : " ",
|
|
34467
|
+
v.channel === "dev" ? "dev" : " ",
|
|
34468
|
+
v.yanked_at ? "YANKED" : " "
|
|
34469
|
+
].join(" ");
|
|
34470
|
+
console.log(`${marks} v${v.version} ${v.created_at} ${v.changelog ?? ""}`.trimEnd());
|
|
34471
|
+
}
|
|
34472
|
+
if (versions.length === 0) console.error(" (no published versions)");
|
|
34473
|
+
}
|
|
34474
|
+
async function packageInstall(client, args) {
|
|
34475
|
+
const pkg2 = await client.getAppPackage(args.package_id);
|
|
34476
|
+
console.error(`Installing ${pkg2.name} \u2014 ${trustBadge(pkg2)}...`);
|
|
34039
34477
|
const app = await client.installAppPackage(args.package_id, {
|
|
34040
|
-
...args.version !== void 0 ? { version: args.version } : {}
|
|
34478
|
+
...args.version !== void 0 ? { version: args.version } : {},
|
|
34479
|
+
...args.config !== void 0 ? { config: args.config } : {}
|
|
34041
34480
|
});
|
|
34042
34481
|
const versionLabel = app.package_version !== null ? `v${app.package_version}` : "(unknown version)";
|
|
34043
34482
|
console.error(
|
|
@@ -34053,6 +34492,104 @@ async function packageEject(client, args) {
|
|
|
34053
34492
|
console.error(" Its data model, queries, workflows, and templates are unchanged.");
|
|
34054
34493
|
console.error(" Pull the pinned source for local editing: lotics app pull " + app.id);
|
|
34055
34494
|
}
|
|
34495
|
+
function parseConfigAssignment(entry, flag, knownType) {
|
|
34496
|
+
const eq = entry.indexOf("=");
|
|
34497
|
+
if (eq <= 0 || eq === entry.length - 1) {
|
|
34498
|
+
throw new Error(`Invalid ${flag} "${entry}" \u2014 expected key=value.`);
|
|
34499
|
+
}
|
|
34500
|
+
const key = entry.slice(0, eq);
|
|
34501
|
+
const raw = entry.slice(eq + 1);
|
|
34502
|
+
if (knownType === "number") {
|
|
34503
|
+
const n = Number(raw);
|
|
34504
|
+
if (!Number.isFinite(n)) throw new Error(`Config key "${key}" is a number knob \u2014 "${raw}" is not numeric.`);
|
|
34505
|
+
return { key, value: n };
|
|
34506
|
+
}
|
|
34507
|
+
if (knownType === "boolean") {
|
|
34508
|
+
if (raw !== "true" && raw !== "false") {
|
|
34509
|
+
throw new Error(`Config key "${key}" is a boolean knob \u2014 expected true or false, got "${raw}".`);
|
|
34510
|
+
}
|
|
34511
|
+
return { key, value: raw === "true" };
|
|
34512
|
+
}
|
|
34513
|
+
if (knownType === "string") return { key, value: raw };
|
|
34514
|
+
if (raw === "true" || raw === "false") return { key, value: raw === "true" };
|
|
34515
|
+
if (/^-?\d+(\.\d+)?$/.test(raw)) return { key, value: Number(raw) };
|
|
34516
|
+
return { key, value: raw };
|
|
34517
|
+
}
|
|
34518
|
+
function parseInstallConfigFlags(config) {
|
|
34519
|
+
const out = {};
|
|
34520
|
+
for (const entry of config) {
|
|
34521
|
+
const { key, value } = parseConfigAssignment(entry, "--config");
|
|
34522
|
+
out[key] = value;
|
|
34523
|
+
}
|
|
34524
|
+
return out;
|
|
34525
|
+
}
|
|
34526
|
+
async function packageConfig(client, args) {
|
|
34527
|
+
const app = await client.getApp(args.app_id);
|
|
34528
|
+
if (!app.package_id) {
|
|
34529
|
+
throw new Error(`App ${args.app_id} is not a package installation \u2014 it has no config.`);
|
|
34530
|
+
}
|
|
34531
|
+
const current = app.config ?? {};
|
|
34532
|
+
if (args.sets.length === 0) {
|
|
34533
|
+
const keys2 = Object.keys(current).sort();
|
|
34534
|
+
if (keys2.length === 0) {
|
|
34535
|
+
console.error(`${app.name} (${app.id}) \u2014 no config knobs.`);
|
|
34536
|
+
return;
|
|
34537
|
+
}
|
|
34538
|
+
console.error(`${app.name} (${app.id}) config:`);
|
|
34539
|
+
for (const key of keys2) console.log(` ${key} = ${JSON.stringify(current[key])}`);
|
|
34540
|
+
return;
|
|
34541
|
+
}
|
|
34542
|
+
const overrides = {};
|
|
34543
|
+
for (const entry of args.sets) {
|
|
34544
|
+
const key = entry.slice(0, Math.max(0, entry.indexOf("=")));
|
|
34545
|
+
const knownType = typeof current[key];
|
|
34546
|
+
const { value } = parseConfigAssignment(
|
|
34547
|
+
entry,
|
|
34548
|
+
"--set",
|
|
34549
|
+
knownType === "number" || knownType === "boolean" || knownType === "string" ? knownType : void 0
|
|
34550
|
+
);
|
|
34551
|
+
overrides[key] = value;
|
|
34552
|
+
}
|
|
34553
|
+
const { config } = await client.updateAppPackageConfig(args.app_id, { config: overrides });
|
|
34554
|
+
console.error(`Updated config for ${app.name} (${app.id}):`);
|
|
34555
|
+
for (const key of Object.keys(config).sort()) {
|
|
34556
|
+
const marker = key in overrides ? " *" : "";
|
|
34557
|
+
console.log(` ${key} = ${JSON.stringify(config[key])}${marker}`);
|
|
34558
|
+
}
|
|
34559
|
+
}
|
|
34560
|
+
async function packageUninstall(client, args) {
|
|
34561
|
+
const app = await client.getApp(args.app_id);
|
|
34562
|
+
if (!app.package_id) {
|
|
34563
|
+
throw new Error(
|
|
34564
|
+
`App ${args.app_id} is not a package installation \u2014 use the app delete flow for a bespoke app.`
|
|
34565
|
+
);
|
|
34566
|
+
}
|
|
34567
|
+
const lifecycleCount = Object.keys(app.binding?.workflows ?? {}).length;
|
|
34568
|
+
const boundCount = Object.keys(app.workflows ?? {}).length;
|
|
34569
|
+
const tableCount = Object.keys(app.binding?.entities ?? {}).length;
|
|
34570
|
+
console.error(`Uninstalling ${app.name} (${app.id}):`);
|
|
34571
|
+
console.error(` Archives ${boundCount + lifecycleCount} workflow(s) (${boundCount} app, ${lifecycleCount} lifecycle).`);
|
|
34572
|
+
console.error(
|
|
34573
|
+
args.archive_tables ? ` Archives ${tableCount} scaffolded table(s) \u2014 refused if they were adopted or are still referenced.` : ` Leaves the data model (${tableCount} table(s)) intact. Pass --archive-tables to also archive them.`
|
|
34574
|
+
);
|
|
34575
|
+
const result = await client.uninstallAppPackage(args.app_id, {
|
|
34576
|
+
archive_tables: args.archive_tables
|
|
34577
|
+
});
|
|
34578
|
+
console.error(`Uninstalled ${app.name} (${app.id}).`);
|
|
34579
|
+
if (result.archived_table_ids.length > 0) {
|
|
34580
|
+
console.error(` Archived tables: ${result.archived_table_ids.join(", ")}`);
|
|
34581
|
+
}
|
|
34582
|
+
}
|
|
34583
|
+
async function packageRetire(client, args) {
|
|
34584
|
+
const pkg2 = await client.retireAppPackage(args.package_id, { undo: args.undo });
|
|
34585
|
+
if (pkg2.retired_at !== null) {
|
|
34586
|
+
console.error(
|
|
34587
|
+
`Retired ${pkg2.name} (${pkg2.id}). New installs refuse it and it is hidden from other orgs; existing installations keep working and may still upgrade.`
|
|
34588
|
+
);
|
|
34589
|
+
} else {
|
|
34590
|
+
console.error(`Un-retired ${pkg2.name} (${pkg2.id}) \u2014 installable again.`);
|
|
34591
|
+
}
|
|
34592
|
+
}
|
|
34056
34593
|
async function packageExtract(client, args) {
|
|
34057
34594
|
const app = await client.getApp(args.app_id);
|
|
34058
34595
|
if (!app.current_version_id) {
|
|
@@ -34261,6 +34798,10 @@ function parseArgs(argv) {
|
|
|
34261
34798
|
cleanup: false,
|
|
34262
34799
|
packageVersion: void 0,
|
|
34263
34800
|
resolve: [],
|
|
34801
|
+
config: [],
|
|
34802
|
+
set: [],
|
|
34803
|
+
archiveTables: false,
|
|
34804
|
+
undo: false,
|
|
34264
34805
|
version: false,
|
|
34265
34806
|
help: false
|
|
34266
34807
|
};
|
|
@@ -34338,6 +34879,28 @@ function parseArgs(argv) {
|
|
|
34338
34879
|
flags.resolve.push(value);
|
|
34339
34880
|
break;
|
|
34340
34881
|
}
|
|
34882
|
+
case "--config": {
|
|
34883
|
+
const value = argv[++i2];
|
|
34884
|
+
if (value === void 0 || value.startsWith("-")) {
|
|
34885
|
+
throw new Error("--config requires a value: key=value (repeatable).");
|
|
34886
|
+
}
|
|
34887
|
+
flags.config.push(value);
|
|
34888
|
+
break;
|
|
34889
|
+
}
|
|
34890
|
+
case "--set": {
|
|
34891
|
+
const value = argv[++i2];
|
|
34892
|
+
if (value === void 0 || value.startsWith("-")) {
|
|
34893
|
+
throw new Error("--set requires a value: key=value (repeatable).");
|
|
34894
|
+
}
|
|
34895
|
+
flags.set.push(value);
|
|
34896
|
+
break;
|
|
34897
|
+
}
|
|
34898
|
+
case "--archive-tables":
|
|
34899
|
+
flags.archiveTables = true;
|
|
34900
|
+
break;
|
|
34901
|
+
case "--undo":
|
|
34902
|
+
flags.undo = true;
|
|
34903
|
+
break;
|
|
34341
34904
|
case "--version":
|
|
34342
34905
|
case "-v": {
|
|
34343
34906
|
const next = argv[i2 + 1];
|
|
@@ -51010,9 +51573,18 @@ COMMANDS
|
|
|
51010
51573
|
dev server (--workspace <dev_ws> selects it)
|
|
51011
51574
|
lotics package sync [path] Re-sync (additive migrate + materialize) into a dev ws
|
|
51012
51575
|
lotics package reset [path] DEV-ONLY: drop scaffolded tables + re-scaffold clean
|
|
51013
|
-
lotics package install <package> [--version N]
|
|
51576
|
+
lotics package install <package> [--version N] [--config key=value ...]
|
|
51014
51577
|
Install an app package into this workspace
|
|
51015
51578
|
(scaffolds the data model + deploys + materializes)
|
|
51579
|
+
lotics package uninstall <app_id> [--archive-tables]
|
|
51580
|
+
Remove an installation (archives artifacts; with the
|
|
51581
|
+
flag also archives the scaffolded tables it created)
|
|
51582
|
+
lotics package config <app_id> [--set key=value ...]
|
|
51583
|
+
Show or edit an installation's config knobs
|
|
51584
|
+
lotics package show <package_id> Registry metadata + version history
|
|
51585
|
+
lotics package retire <package_id> [--undo]
|
|
51586
|
+
Retire a package (refuse new installs, hide from other
|
|
51587
|
+
orgs); existing installations keep working + upgrade
|
|
51016
51588
|
lotics package upgrade <app_id> [--version N] [--resolve <key>=recreate|revert|keep|<id> ...]
|
|
51017
51589
|
Preview-then-apply a package upgrade; refuses while
|
|
51018
51590
|
any drift/modified finding lacks a --resolve, and
|
|
@@ -51554,7 +52126,11 @@ async function main() {
|
|
|
51554
52126
|
console.error(" lotics package dev [path] [--workspace <ws>] Sync into a dev workspace + run the app dev server");
|
|
51555
52127
|
console.error(" lotics package sync [path] [--workspace <ws>] Re-sync (additive migrate + materialize) into a dev workspace");
|
|
51556
52128
|
console.error(" lotics package reset [path] [--workspace <ws>] DEV-ONLY: drop scaffolded tables + re-scaffold clean");
|
|
51557
|
-
console.error(" lotics package install <package> [--version N] Install a package into this workspace");
|
|
52129
|
+
console.error(" lotics package install <package> [--version N] [--config key=value ...] Install a package into this workspace");
|
|
52130
|
+
console.error(" lotics package uninstall <app_id> [--archive-tables] Remove an installation (opt-in table archival)");
|
|
52131
|
+
console.error(" lotics package config <app_id> [--set key=value ...] Show or edit an installation's config knobs");
|
|
52132
|
+
console.error(" lotics package show <package_id> Registry metadata + version history (channel, yank, changelog)");
|
|
52133
|
+
console.error(" lotics package retire <package_id> [--undo] Retire a package (refuse new installs; installs keep working)");
|
|
51558
52134
|
console.error(" lotics package upgrade <app_id> [--version N] [--resolve ns.alias=recreate|<id>] Preview + apply an upgrade");
|
|
51559
52135
|
console.error(" lotics package doctor [app_id] Health: version pin vs latest + binding drift");
|
|
51560
52136
|
console.error(" lotics package rebind-role <app_id> <alias> <grp_id> Re-point a package role at another group");
|
|
@@ -51591,16 +52167,6 @@ async function main() {
|
|
|
51591
52167
|
}
|
|
51592
52168
|
const { client, ctx } = requireClient(flags);
|
|
51593
52169
|
if (command === "workspace") {
|
|
51594
|
-
if (subcommand === "yank") {
|
|
51595
|
-
const [packageId, versionRaw, maybeUndo] = toolArgs ? toolArgs.split(/\s+/) : [];
|
|
51596
|
-
const version = Number(versionRaw);
|
|
51597
|
-
if (!packageId || !Number.isInteger(version) || version <= 0) {
|
|
51598
|
-
console.error("Usage: lotics package yank <package_id> <version> [--undo]");
|
|
51599
|
-
process.exit(1);
|
|
51600
|
-
}
|
|
51601
|
-
await packageYank(client, { package_id: packageId, version, undo: maybeUndo === "--undo" });
|
|
51602
|
-
return;
|
|
51603
|
-
}
|
|
51604
52170
|
if (subcommand === "doctor") {
|
|
51605
52171
|
await resolveWorkspace(client, ctx);
|
|
51606
52172
|
const dangling = await client.getWorkspaceDanglingReferences();
|
|
@@ -51729,7 +52295,48 @@ Available workspaces:`);
|
|
|
51729
52295
|
process.exit(1);
|
|
51730
52296
|
}
|
|
51731
52297
|
}
|
|
51732
|
-
|
|
52298
|
+
const config = flags.config.length > 0 ? parseInstallConfigFlags(flags.config) : void 0;
|
|
52299
|
+
await packageInstall(client, {
|
|
52300
|
+
package_id: packageId,
|
|
52301
|
+
version,
|
|
52302
|
+
...config !== void 0 ? { config } : {}
|
|
52303
|
+
});
|
|
52304
|
+
return;
|
|
52305
|
+
}
|
|
52306
|
+
if (subcommand === "uninstall") {
|
|
52307
|
+
const appId = toolArgs;
|
|
52308
|
+
if (!appId) {
|
|
52309
|
+
console.error("Usage: lotics package uninstall <app_id> [--archive-tables]");
|
|
52310
|
+
process.exit(1);
|
|
52311
|
+
}
|
|
52312
|
+
await packageUninstall(client, { app_id: appId, archive_tables: flags.archiveTables });
|
|
52313
|
+
return;
|
|
52314
|
+
}
|
|
52315
|
+
if (subcommand === "config") {
|
|
52316
|
+
const appId = toolArgs;
|
|
52317
|
+
if (!appId) {
|
|
52318
|
+
console.error("Usage: lotics package config <app_id> [--set key=value ...]");
|
|
52319
|
+
process.exit(1);
|
|
52320
|
+
}
|
|
52321
|
+
await packageConfig(client, { app_id: appId, sets: flags.set });
|
|
52322
|
+
return;
|
|
52323
|
+
}
|
|
52324
|
+
if (subcommand === "retire") {
|
|
52325
|
+
const packageId = toolArgs;
|
|
52326
|
+
if (!packageId) {
|
|
52327
|
+
console.error("Usage: lotics package retire <package_id> [--undo]");
|
|
52328
|
+
process.exit(1);
|
|
52329
|
+
}
|
|
52330
|
+
await packageRetire(client, { package_id: packageId, undo: flags.undo });
|
|
52331
|
+
return;
|
|
52332
|
+
}
|
|
52333
|
+
if (subcommand === "show") {
|
|
52334
|
+
const packageId = toolArgs;
|
|
52335
|
+
if (!packageId) {
|
|
52336
|
+
console.error("Usage: lotics package show <package_id>");
|
|
52337
|
+
process.exit(1);
|
|
52338
|
+
}
|
|
52339
|
+
await packageShow(client, { package_id: packageId });
|
|
51733
52340
|
return;
|
|
51734
52341
|
}
|
|
51735
52342
|
if (subcommand === "eject") {
|
|
@@ -51769,6 +52376,16 @@ Available workspaces:`);
|
|
|
51769
52376
|
await packageAdopt(client, { app_id: appId, version, projectDir: restArgs[0] });
|
|
51770
52377
|
return;
|
|
51771
52378
|
}
|
|
52379
|
+
if (subcommand === "yank") {
|
|
52380
|
+
const packageId = toolArgs;
|
|
52381
|
+
const version = Number(restArgs[0]);
|
|
52382
|
+
if (!packageId || !Number.isInteger(version) || version <= 0) {
|
|
52383
|
+
console.error("Usage: lotics package yank <package_id> <version> [--undo]");
|
|
52384
|
+
process.exit(1);
|
|
52385
|
+
}
|
|
52386
|
+
await packageYank(client, { package_id: packageId, version, undo: flags.undo });
|
|
52387
|
+
return;
|
|
52388
|
+
}
|
|
51772
52389
|
if (subcommand === "fleet-upgrade") {
|
|
51773
52390
|
const packageId = toolArgs;
|
|
51774
52391
|
if (!packageId) {
|