@lotics/cli 0.71.0 → 0.74.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 +38 -0
- package/dist/app_commands.d.ts +13 -0
- package/dist/app_commands.js +3 -3
- package/dist/args.d.ts +17 -0
- package/dist/args.js +35 -2
- package/dist/args.test.js +49 -0
- package/dist/cli.js +235 -3
- package/dist/client.d.ts +321 -0
- package/dist/client.js +164 -0
- package/dist/dev/rpc_handler.d.ts +1 -1
- package/dist/dev/rpc_handler.js +10 -2
- package/dist/dev/rpc_handler.test.js +24 -6
- package/dist/generate_app_fields.d.ts +2 -0
- package/dist/generate_app_fields.js +1 -1
- package/dist/generate_package_fields.d.ts +48 -0
- package/dist/generate_package_fields.js +104 -0
- package/dist/generate_package_fields.test.d.ts +1 -0
- package/dist/generate_package_fields.test.js +51 -0
- package/dist/package_commands.d.ts +227 -0
- package/dist/package_commands.js +957 -0
- package/dist/package_commands.test.d.ts +1 -0
- package/dist/package_commands.test.js +303 -0
- package/dist/src/cli.js +1420 -138
- package/dist/starter_template.d.ts +1 -1
- package/dist/starter_template.js +10 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -195,6 +195,44 @@ lotics ui link card --remove # finalize: PR + publish, then drop t
|
|
|
195
195
|
|
|
196
196
|
`app codegen` reads `package.json#lotics.queries` to decide which tables to put in `app_fields.ts`; widen the set with `package.json#lotics.codegen.tables` (an array of `tbl_…` ids) for tables the app only writes via workflows.
|
|
197
197
|
|
|
198
|
+
## App packages
|
|
199
|
+
|
|
200
|
+
A maintained, versioned app library: author once, install into many workspaces, upgrade per-workspace. Any org authors its own packages (publishing is owner-org-only); Lotics-backed packages carry the `official` badge. See `docs/app_packages.md` for the model.
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
# Author (any org admin; the package is owned by your org)
|
|
204
|
+
lotics package new my-crm # scaffold project + contract.json
|
|
205
|
+
lotics package publish -m "v2: adds deals" # build + publish an immutable version
|
|
206
|
+
|
|
207
|
+
# Dev loop against a dev workspace (created with: lotics workspace create <name> --dev)
|
|
208
|
+
lotics package dev --workspace wsp_dev # scaffold-sync + run the app dev server
|
|
209
|
+
lotics package sync --workspace wsp_dev # migrate the dev installation only
|
|
210
|
+
lotics package reset --workspace wsp_dev # DEV-ONLY: drop scaffolded tables, re-scaffold clean
|
|
211
|
+
|
|
212
|
+
# Operate installations (workspace admin)
|
|
213
|
+
lotics package install apg_... --version 2 # scaffold + materialize + deploy + pin
|
|
214
|
+
lotics package doctor app_... # version pin vs latest + drift + local edits (exit 1 on findings)
|
|
215
|
+
lotics package rebind-role app_... <alias> <grp_id> # re-point a package role at another group (re-materializes)
|
|
216
|
+
lotics package upgrade app_... # preview, then apply (additive; overlay preserved)
|
|
217
|
+
lotics package fleet-upgrade apg_... # upgrade EVERY org installation (clean ones apply; findings skip, exit 1)
|
|
218
|
+
lotics package upgrade app_... --resolve fields.deal.stage=recreate # resolve reported drift
|
|
219
|
+
lotics package upgrade app_... --resolve queries.tasks=keep # consent for a local edit (or =revert)
|
|
220
|
+
lotics package eject app_... # one-way: sever the package link
|
|
221
|
+
lotics workspace doctor # dangling schema references across the workspace (exit 1 on findings)
|
|
222
|
+
|
|
223
|
+
# Promote an EXISTING bespoke app to a package (build it in a real workspace first,
|
|
224
|
+
# distribute once it proves itself — no re-authoring)
|
|
225
|
+
lotics package extract app_... # → draft project: contract + binding + templates
|
|
226
|
+
# prints a findings report; always writes the draft,
|
|
227
|
+
# exits 1 on error findings (fix them, publish re-validates)
|
|
228
|
+
cd <slug-of-app-name> # review contract.json aliases
|
|
229
|
+
lotics package publish -m "v1" # publish the reviewed project
|
|
230
|
+
lotics package adopt app_... # bind the package onto the origin app (same workspace);
|
|
231
|
+
# verifies the binding is faithful, then pins installation #1
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Extract writes `.lotics/adopt_binding.json` (the origin pin, excluded from published bundles); `adopt` reads it back and refuses a pin recorded for a different app. After promotion the package project is the master — iterate contract-first (`package dev`/`sync` → `publish`), never extract again.
|
|
235
|
+
|
|
198
236
|
## SDK
|
|
199
237
|
|
|
200
238
|
```typescript
|
package/dist/app_commands.d.ts
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
import { LoticsClient } from "./client.js";
|
|
2
|
+
/**
|
|
3
|
+
* Resolve the latest published version of a package from the npm registry.
|
|
4
|
+
* Returns null on any failure (network error, 404, malformed payload) so
|
|
5
|
+
* callers can fall back to a static pin rather than crashing `app create`.
|
|
6
|
+
*
|
|
7
|
+
* 1.5s timeout — npm registry is fast when reachable; the fallback is fine
|
|
8
|
+
* the rare times it isn't, and we don't want to block scaffold on a hang.
|
|
9
|
+
*/
|
|
10
|
+
export declare function fetchLatestNpmVersion(packageName: string): Promise<string | null>;
|
|
2
11
|
/**
|
|
3
12
|
* Manifest declaration for one workflow alias:
|
|
4
13
|
* `"alias": { workflow_id, inputs?: { key: { type, … } }, outputs?: { key: { type, … } } }`
|
|
@@ -86,6 +95,10 @@ export declare function writeWorkflowFile(projectDir: string, alias: string, sou
|
|
|
86
95
|
* real source, not bookkeeping, and must not be silently eaten.
|
|
87
96
|
*/
|
|
88
97
|
export declare function stripWorkflowHeader(content: string): string;
|
|
98
|
+
/** Run `tar` and resolve when it exits cleanly. Throws with stderr on failure. */
|
|
99
|
+
export declare function runTar(args: string[], cwd: string): Promise<void>;
|
|
100
|
+
/** Run `npm` (run/install/etc.) inheriting stdio so the user sees progress. */
|
|
101
|
+
export declare function runNpm(args: string[], cwd: string): Promise<void>;
|
|
89
102
|
/**
|
|
90
103
|
* Heal a pre-existing app's `tsconfig.json` so its generated types load and its
|
|
91
104
|
* `npm run typecheck` stays honest. Idempotent, run on every pull / codegen /
|
package/dist/app_commands.js
CHANGED
|
@@ -32,7 +32,7 @@ import { loadProjectTypescript, checkWorkflowBodies, } from "./app_workflow_chec
|
|
|
32
32
|
* 1.5s timeout — npm registry is fast when reachable; the fallback is fine
|
|
33
33
|
* the rare times it isn't, and we don't want to block scaffold on a hang.
|
|
34
34
|
*/
|
|
35
|
-
async function fetchLatestNpmVersion(packageName) {
|
|
35
|
+
export async function fetchLatestNpmVersion(packageName) {
|
|
36
36
|
try {
|
|
37
37
|
const controller = new AbortController();
|
|
38
38
|
const timeout = setTimeout(() => controller.abort(), 1500);
|
|
@@ -258,7 +258,7 @@ async function fetchWorkflowGlobals(client, projectDir, app_id, alias) {
|
|
|
258
258
|
}
|
|
259
259
|
}
|
|
260
260
|
/** Run `tar` and resolve when it exits cleanly. Throws with stderr on failure. */
|
|
261
|
-
function runTar(args, cwd) {
|
|
261
|
+
export function runTar(args, cwd) {
|
|
262
262
|
return new Promise((resolve, reject) => {
|
|
263
263
|
const proc = spawn("tar", args, { cwd, stdio: ["ignore", "ignore", "pipe"] });
|
|
264
264
|
let stderr = "";
|
|
@@ -275,7 +275,7 @@ function runTar(args, cwd) {
|
|
|
275
275
|
});
|
|
276
276
|
}
|
|
277
277
|
/** Run `npm` (run/install/etc.) inheriting stdio so the user sees progress. */
|
|
278
|
-
function runNpm(args, cwd) {
|
|
278
|
+
export function runNpm(args, cwd) {
|
|
279
279
|
return new Promise((resolve, reject) => {
|
|
280
280
|
const proc = spawn("npm", args, { cwd, stdio: "inherit", env: ipv4ChildEnv(process.env) });
|
|
281
281
|
proc.on("error", reject);
|
package/dist/args.d.ts
CHANGED
|
@@ -30,12 +30,29 @@ export declare function parseArgs(argv: string[]): {
|
|
|
30
30
|
message?: string;
|
|
31
31
|
local: boolean;
|
|
32
32
|
all: boolean;
|
|
33
|
+
/** `--dev`: mark a workspace as an app-package dev workspace (`workspace create`). */
|
|
34
|
+
dev: boolean;
|
|
33
35
|
/** `--yes` (alias `-y`): confirm a destructive command (e.g. `workspace delete`) non-interactively. */
|
|
34
36
|
yes: boolean;
|
|
35
37
|
/** `--print-created` (alias `--report-effects`): print the honest post-run side-effect harvest. */
|
|
36
38
|
printCreated: boolean;
|
|
37
39
|
/** `--cleanup`: also delete the harvested created records (records only). */
|
|
38
40
|
cleanup: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* `--version <n>`: a package version number (`lotics package install … --version 2`).
|
|
43
|
+
* Distinct from the boolean `version` flag below: `--version` followed by an
|
|
44
|
+
* integer captures the number; a bare `--version` (or `-v`) prints the CLI
|
|
45
|
+
* version. No other command takes a numeric value after `--version`, so the
|
|
46
|
+
* peek is unambiguous.
|
|
47
|
+
*/
|
|
48
|
+
packageVersion?: string;
|
|
49
|
+
/**
|
|
50
|
+
* `--resolve <key>=recreate|revert|keep|<id>` (repeatable): upgrade
|
|
51
|
+
* resolutions — drift entries (`<namespace.alias>`) take `recreate` or an
|
|
52
|
+
* existing id; modified-core entries (`<kind>.<alias>`) take `revert` or
|
|
53
|
+
* `keep`.
|
|
54
|
+
*/
|
|
55
|
+
resolve: string[];
|
|
39
56
|
version: boolean;
|
|
40
57
|
help: boolean;
|
|
41
58
|
};
|
package/dist/args.js
CHANGED
|
@@ -25,9 +25,12 @@ export function parseArgs(argv) {
|
|
|
25
25
|
message: undefined,
|
|
26
26
|
local: false,
|
|
27
27
|
all: false,
|
|
28
|
+
dev: false,
|
|
28
29
|
yes: false,
|
|
29
30
|
printCreated: false,
|
|
30
31
|
cleanup: false,
|
|
32
|
+
packageVersion: undefined,
|
|
33
|
+
resolve: [],
|
|
31
34
|
version: false,
|
|
32
35
|
help: false,
|
|
33
36
|
};
|
|
@@ -78,6 +81,9 @@ export function parseArgs(argv) {
|
|
|
78
81
|
case "--local":
|
|
79
82
|
flags.local = true;
|
|
80
83
|
break;
|
|
84
|
+
case "--dev":
|
|
85
|
+
flags.dev = true;
|
|
86
|
+
break;
|
|
81
87
|
case "--all":
|
|
82
88
|
flags.all = true;
|
|
83
89
|
break;
|
|
@@ -92,10 +98,37 @@ export function parseArgs(argv) {
|
|
|
92
98
|
case "--cleanup":
|
|
93
99
|
flags.cleanup = true;
|
|
94
100
|
break;
|
|
101
|
+
case "--resolve": {
|
|
102
|
+
const value = argv[++i];
|
|
103
|
+
if (value === undefined || value.startsWith("-")) {
|
|
104
|
+
throw new Error("--resolve requires a value: <key>=recreate|revert|keep or <key>=<existing_id> (drift: <namespace.alias>; modified core: <kind>.<alias>).");
|
|
105
|
+
}
|
|
106
|
+
flags.resolve.push(value);
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
95
109
|
case "--version":
|
|
96
|
-
case "-v":
|
|
97
|
-
|
|
110
|
+
case "-v": {
|
|
111
|
+
// Inside a `package` command, `--version` carries a value (the
|
|
112
|
+
// package version to install) and the value is REQUIRED — a bare or
|
|
113
|
+
// malformed `--version` here must never fall back to the CLI-version
|
|
114
|
+
// early-exit boolean, which would print the CLI version and exit 0
|
|
115
|
+
// (a provisioning script reads that as a successful install). The
|
|
116
|
+
// value is captured raw; the consuming command validates
|
|
117
|
+
// integer-ness with a loud error. Outside `package`, this is the
|
|
118
|
+
// CLI-version boolean.
|
|
119
|
+
const next = argv[i + 1];
|
|
120
|
+
if (command === "package") {
|
|
121
|
+
if (next === undefined || next.startsWith("-")) {
|
|
122
|
+
throw new Error("--version requires a version number for package commands (e.g. --version 2).");
|
|
123
|
+
}
|
|
124
|
+
flags.packageVersion = next;
|
|
125
|
+
i++;
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
flags.version = true;
|
|
129
|
+
}
|
|
98
130
|
break;
|
|
131
|
+
}
|
|
99
132
|
case "--help":
|
|
100
133
|
case "-h":
|
|
101
134
|
flags.help = true;
|
package/dist/args.test.js
CHANGED
|
@@ -23,6 +23,49 @@ describe("parseArgs", () => {
|
|
|
23
23
|
const r = parseArgs(["app", "deploy"]);
|
|
24
24
|
expect(r.flags.message).toBeUndefined();
|
|
25
25
|
});
|
|
26
|
+
it("captures --version <int> as packageVersion, not the boolean version flag", () => {
|
|
27
|
+
const r = parseArgs(["package", "install", "apg_123", "--version", "2"]);
|
|
28
|
+
expect(r.command).toBe("package");
|
|
29
|
+
expect(r.subcommand).toBe("install");
|
|
30
|
+
expect(r.toolArgs).toBe("apg_123");
|
|
31
|
+
expect(r.flags.packageVersion).toBe("2");
|
|
32
|
+
expect(r.flags.version).toBe(false);
|
|
33
|
+
expect(r.restArgs).toEqual([]);
|
|
34
|
+
});
|
|
35
|
+
it("treats a bare --version as the boolean CLI-version flag", () => {
|
|
36
|
+
const r = parseArgs(["--version"]);
|
|
37
|
+
expect(r.flags.version).toBe(true);
|
|
38
|
+
expect(r.flags.packageVersion).toBeUndefined();
|
|
39
|
+
});
|
|
40
|
+
it("treats -v as the boolean CLI-version flag", () => {
|
|
41
|
+
const r = parseArgs(["-v"]);
|
|
42
|
+
expect(r.flags.version).toBe(true);
|
|
43
|
+
expect(r.flags.packageVersion).toBeUndefined();
|
|
44
|
+
});
|
|
45
|
+
it("captures a non-integer --version value under package commands for loud command-level validation", () => {
|
|
46
|
+
const r = parseArgs(["package", "install", "apg_123", "--version", "2.0"]);
|
|
47
|
+
expect(r.flags.packageVersion).toBe("2.0");
|
|
48
|
+
expect(r.flags.version).toBe(false);
|
|
49
|
+
});
|
|
50
|
+
it("collects repeated --resolve values", () => {
|
|
51
|
+
const r = parseArgs([
|
|
52
|
+
"package",
|
|
53
|
+
"upgrade",
|
|
54
|
+
"app_1",
|
|
55
|
+
"--resolve",
|
|
56
|
+
"fields.deal.stage=recreate",
|
|
57
|
+
"--resolve",
|
|
58
|
+
"templates.quote=dtl_abc",
|
|
59
|
+
]);
|
|
60
|
+
expect(r.flags.resolve).toEqual(["fields.deal.stage=recreate", "templates.quote=dtl_abc"]);
|
|
61
|
+
});
|
|
62
|
+
it("errors on --resolve without a value", () => {
|
|
63
|
+
expect(() => parseArgs(["package", "upgrade", "app_1", "--resolve"])).toThrow(/--resolve requires a value/);
|
|
64
|
+
});
|
|
65
|
+
it("errors loudly on a bare --version under package commands instead of printing the CLI version", () => {
|
|
66
|
+
expect(() => parseArgs(["package", "install", "apg_123", "--version"])).toThrow(/requires a version number/);
|
|
67
|
+
expect(() => parseArgs(["package", "install", "apg_123", "--version", "--json"])).toThrow(/requires a version number/);
|
|
68
|
+
});
|
|
26
69
|
it("parses --workspace as a value flag", () => {
|
|
27
70
|
const r = parseArgs(["run", "query_tables", "{}", "--workspace", "wsp_123"]);
|
|
28
71
|
expect(r.flags.workspace).toBe("wsp_123");
|
|
@@ -63,6 +106,12 @@ describe("parseArgs", () => {
|
|
|
63
106
|
expect(parseArgs(["app", "workflow", "run", "wf"]).flags.cleanup).toBe(false);
|
|
64
107
|
expect(parseArgs(["app", "workflow", "run", "wf"]).flags.printCreated).toBe(false);
|
|
65
108
|
});
|
|
109
|
+
it("parses --dev as a boolean flag (default false)", () => {
|
|
110
|
+
expect(parseArgs(["workspace", "create", "Dev WS", "--dev"]).flags.dev).toBe(true);
|
|
111
|
+
expect(parseArgs(["workspace", "create", "Dev WS"]).flags.dev).toBe(false);
|
|
112
|
+
// The boolean flag does not consume the workspace name positional.
|
|
113
|
+
expect(parseArgs(["workspace", "create", "Dev WS", "--dev"]).toolArgs).toBe("Dev WS");
|
|
114
|
+
});
|
|
66
115
|
it("parses --yes and -y as the same boolean confirmation flag (default false)", () => {
|
|
67
116
|
expect(parseArgs(["workspace", "delete", "wsp_1", "--yes"]).flags.yes).toBe(true);
|
|
68
117
|
expect(parseArgs(["workspace", "delete", "wsp_1", "-y"]).flags.yes).toBe(true);
|
package/dist/cli.js
CHANGED
|
@@ -14,6 +14,7 @@ import { LoticsClient, API_BASE_URL } from "./client.js";
|
|
|
14
14
|
import { resolveContext, deleteConfig, getConfigPath, loadGlobalConfig, saveGlobalConfig, loadLocalConfig, upsertProfile, removeProfile, setActiveOrg, setSelectedWorkspace, resolveProfileByNameOrId, checkForUpdate, } from "./config.js";
|
|
15
15
|
import { VERSION } from "./version.js";
|
|
16
16
|
import { appCreate, appPull, appDeploy, appDev, appSetSubdomain, appRename, appVersions, appCodegen, appExecuteWorkflow, appWorkflowSet, appWorkflowPull, appWorkflowCheck, appQuerySet, appUiLink, } from "./app_commands.js";
|
|
17
|
+
import { packageInstall, packageEject, packageDoctor, packageUpgrade, parseResolveFlags, packageNew, packageBuild, packagePublish, packageDev, packageSync, packageReset, packageExtract, packageAdopt, packageFleetUpgrade, } from "./package_commands.js";
|
|
17
18
|
import { parseArgs } from "./args.js";
|
|
18
19
|
import { ingestJsonArgs } from "./inputs.js";
|
|
19
20
|
import { runXlsxCommand } from "./xlsx.js";
|
|
@@ -62,6 +63,7 @@ COMMANDS
|
|
|
62
63
|
lotics workspace select <id> Switch to a different workspace
|
|
63
64
|
lotics workspace create <name> Create a new workspace (admin only)
|
|
64
65
|
lotics workspace delete <id> --yes Delete a workspace (admin only; soft delete, recoverable)
|
|
66
|
+
lotics workspace doctor Report dangling schema references (admin only)
|
|
65
67
|
lotics tools List all available tools
|
|
66
68
|
lotics tools <name> Show tool description and input schema
|
|
67
69
|
lotics run <tool> '<json>' Execute a tool
|
|
@@ -94,6 +96,37 @@ COMMANDS
|
|
|
94
96
|
lotics app subdomain <new-subdomain> Rename the app's public <slug>.lotics.app address
|
|
95
97
|
lotics app rename "<new name>" Rename the app's display name (launcher title)
|
|
96
98
|
lotics app dev [path] Run the app locally with HMR (RPC forwarded to prod)
|
|
99
|
+
lotics package new <name> [path] Scaffold a package project (contract + app source)
|
|
100
|
+
lotics package build [path] Build the publishable bundle (source + dist)
|
|
101
|
+
lotics package publish [path] Publish a new immutable package version
|
|
102
|
+
lotics package dev [path] Sync the package into a dev workspace + run the app
|
|
103
|
+
dev server (--workspace <dev_ws> selects it)
|
|
104
|
+
lotics package sync [path] Re-sync (additive migrate + materialize) into a dev ws
|
|
105
|
+
lotics package reset [path] DEV-ONLY: drop scaffolded tables + re-scaffold clean
|
|
106
|
+
lotics package install <package> [--version N]
|
|
107
|
+
Install an app package into this workspace
|
|
108
|
+
(scaffolds the data model + deploys + materializes)
|
|
109
|
+
lotics package upgrade <app_id> [--version N] [--resolve <key>=recreate|revert|keep|<id> ...]
|
|
110
|
+
Preview-then-apply a package upgrade; refuses while
|
|
111
|
+
any drift/modified finding lacks a --resolve, and
|
|
112
|
+
hard-stops on breaking contract changes
|
|
113
|
+
lotics package doctor [app_id] Installation health: version pin vs latest, binding
|
|
114
|
+
drift, locally modified core (exit 1 on findings)
|
|
115
|
+
lotics package rebind-role <app_id> <alias> <grp_id>
|
|
116
|
+
Re-point a package role at a different group
|
|
117
|
+
(re-materializes at the pinned version)
|
|
118
|
+
lotics package eject <app_id> Sever an installation's package link
|
|
119
|
+
(re-deploys the pinned source as a bespoke app)
|
|
120
|
+
lotics package extract <app_id> [path] Promote a bespoke app to a draft package
|
|
121
|
+
project (contract + binding + templates); always
|
|
122
|
+
writes the draft, exits 1 on error findings
|
|
123
|
+
lotics package adopt <app_id> [path] Bind the published package project onto the
|
|
124
|
+
origin app (reads .lotics/adopt_binding.json;
|
|
125
|
+
--version N pins a specific version)
|
|
126
|
+
lotics package fleet-upgrade <package_id> [--version N]
|
|
127
|
+
Upgrade EVERY installation of the package across
|
|
128
|
+
your org: applies where the preview is clean,
|
|
129
|
+
skips + reports findings (exit 1 unless all current)
|
|
97
130
|
lotics ui link <component> [--ui-src <path>] [--remove]
|
|
98
131
|
Dev-link @lotics/ui to packages/ui/src (Vite alias
|
|
99
132
|
+ tsc paths) for live HMR + typecheck. Monorepo apps
|
|
@@ -122,6 +155,8 @@ FLAGS
|
|
|
122
155
|
is_current_member / row-scoping resolve to them (also
|
|
123
156
|
LOTICS_VIEW_AS env; admin key only; writes stay yours)
|
|
124
157
|
--local Pin the current directory (lotics org use / auth api-key)
|
|
158
|
+
--dev (lotics workspace create) Mark a throwaway app-package dev
|
|
159
|
+
workspace — required for "lotics package reset"
|
|
125
160
|
--all (lotics auth logout) Remove every saved credential
|
|
126
161
|
--version Show version
|
|
127
162
|
|
|
@@ -536,6 +571,20 @@ async function main() {
|
|
|
536
571
|
console.error("Usage: lotics ui link <component> [--ui-src <abs path>] [--remove]");
|
|
537
572
|
process.exit(1);
|
|
538
573
|
}
|
|
574
|
+
// --- lotics package new / build — local scaffold + build, no auth required ---
|
|
575
|
+
if (command === "package" && subcommand === "new") {
|
|
576
|
+
const name = toolArgs;
|
|
577
|
+
if (!name) {
|
|
578
|
+
console.error("Usage: lotics package new <name> [path]");
|
|
579
|
+
process.exit(1);
|
|
580
|
+
}
|
|
581
|
+
await packageNew({ name, targetPath: restArgs[0] });
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
if (command === "package" && subcommand === "build") {
|
|
585
|
+
await packageBuild({ projectDir: toolArgs });
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
539
588
|
// --- lotics app workflow check [alias] — local typecheck, no auth, no network ---
|
|
540
589
|
// Reads src/workflows/*.ts + .lotics/workflows/*.globals.d.ts and the app's own
|
|
541
590
|
// typescript; builds one isolated program per alias. Handled before the auth
|
|
@@ -618,7 +667,7 @@ async function main() {
|
|
|
618
667
|
return;
|
|
619
668
|
}
|
|
620
669
|
// --- Validate command before auth ---
|
|
621
|
-
if (command !== "tools" && command !== "upload" && command !== "run" && command !== "download" && command !== "workspace" && command !== "app") {
|
|
670
|
+
if (command !== "tools" && command !== "upload" && command !== "run" && command !== "download" && command !== "workspace" && command !== "app" && command !== "package") {
|
|
622
671
|
console.error(`Unknown command: ${command}`);
|
|
623
672
|
console.error('Run "lotics --help" for usage.');
|
|
624
673
|
process.exit(1);
|
|
@@ -642,6 +691,24 @@ async function main() {
|
|
|
642
691
|
console.error(" lotics app dev [path] Run the app locally with HMR + RPC forwarding");
|
|
643
692
|
process.exit(1);
|
|
644
693
|
}
|
|
694
|
+
if (command === "package" && !subcommand) {
|
|
695
|
+
console.error("Usage:");
|
|
696
|
+
console.error(" lotics package new <name> [path] Scaffold a package project (contract + app source)");
|
|
697
|
+
console.error(" lotics package build [path] Build the publishable bundle (source + dist)");
|
|
698
|
+
console.error(" lotics package publish [path] [-m <changelog>] Publish a new immutable package version");
|
|
699
|
+
console.error(" lotics package dev [path] [--workspace <ws>] Sync into a dev workspace + run the app dev server");
|
|
700
|
+
console.error(" lotics package sync [path] [--workspace <ws>] Re-sync (additive migrate + materialize) into a dev workspace");
|
|
701
|
+
console.error(" lotics package reset [path] [--workspace <ws>] DEV-ONLY: drop scaffolded tables + re-scaffold clean");
|
|
702
|
+
console.error(" lotics package install <package> [--version N] Install a package into this workspace");
|
|
703
|
+
console.error(" lotics package upgrade <app_id> [--version N] [--resolve ns.alias=recreate|<id>] Preview + apply an upgrade");
|
|
704
|
+
console.error(" lotics package doctor [app_id] Health: version pin vs latest + binding drift");
|
|
705
|
+
console.error(" lotics package rebind-role <app_id> <alias> <grp_id> Re-point a package role at another group");
|
|
706
|
+
console.error(" lotics package eject <app_id> Sever an installation's package link");
|
|
707
|
+
console.error(" lotics package extract <app_id> [path] Promote a bespoke app to a draft package project");
|
|
708
|
+
console.error(" lotics package adopt <app_id> [path] Bind the published project onto the origin app");
|
|
709
|
+
console.error(" lotics package fleet-upgrade <package_id> [--version N] Upgrade every org installation (clean ones apply; findings skip)");
|
|
710
|
+
process.exit(1);
|
|
711
|
+
}
|
|
645
712
|
if (command === "run" && !subcommand) {
|
|
646
713
|
console.error("Usage: lotics run <tool> '<json_args>'\n" +
|
|
647
714
|
" lotics run <tool> @args.json (read args from a file — for large payloads)\n" +
|
|
@@ -669,6 +736,24 @@ async function main() {
|
|
|
669
736
|
const { client, ctx } = requireClient(flags);
|
|
670
737
|
// lotics workspace / lotics workspace list / lotics workspace select <id>
|
|
671
738
|
if (command === "workspace") {
|
|
739
|
+
if (subcommand === "doctor") {
|
|
740
|
+
// The workspace command family runs before the global workspace
|
|
741
|
+
// resolution — doctor needs the same first-workspace fallback every
|
|
742
|
+
// data command gets.
|
|
743
|
+
await resolveWorkspace(client, ctx);
|
|
744
|
+
const dangling = await client.getWorkspaceDanglingReferences();
|
|
745
|
+
if (dangling.length === 0) {
|
|
746
|
+
console.error("Workspace references: healthy — every referenced schema id resolves.");
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
console.error(`Dangling schema references (${dangling.length}):`);
|
|
750
|
+
for (const d of dangling) {
|
|
751
|
+
console.error(` - ${d.referent.kind} "${d.referent.name}" (${d.referent.id}) → ${d.namespace} ${d.id} (missing)`);
|
|
752
|
+
}
|
|
753
|
+
console.error("Each referent points at schema that no longer exists — edit or archive it, or recreate the missing schema.");
|
|
754
|
+
process.exitCode = 1;
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
672
757
|
const workspaces = await client.listWorkspaces();
|
|
673
758
|
const currentWorkspaceId = ctx.workspaceId;
|
|
674
759
|
if (subcommand === "select") {
|
|
@@ -695,11 +780,11 @@ async function main() {
|
|
|
695
780
|
if (subcommand === "create") {
|
|
696
781
|
const name = toolArgs;
|
|
697
782
|
if (!name) {
|
|
698
|
-
console.error('Usage: lotics workspace create <name> [--timezone <tz>]');
|
|
783
|
+
console.error('Usage: lotics workspace create <name> [--timezone <tz>] [--dev]');
|
|
699
784
|
process.exit(1);
|
|
700
785
|
}
|
|
701
786
|
const timezone = flags.timezone;
|
|
702
|
-
const created = await client.createWorkspace({ name, timezone });
|
|
787
|
+
const created = await client.createWorkspace({ name, timezone, is_dev: flags.dev || undefined });
|
|
703
788
|
setSelectedWorkspace(created.id);
|
|
704
789
|
client.setWorkspaceId(created.id);
|
|
705
790
|
if (flags.json) {
|
|
@@ -768,6 +853,153 @@ async function main() {
|
|
|
768
853
|
}
|
|
769
854
|
// Ensure workspace is resolved for all remaining commands
|
|
770
855
|
await resolveWorkspace(client, ctx);
|
|
856
|
+
// lotics package install <package> [--version N]
|
|
857
|
+
if (command === "package") {
|
|
858
|
+
if (subcommand === "install") {
|
|
859
|
+
const packageId = toolArgs;
|
|
860
|
+
if (!packageId) {
|
|
861
|
+
console.error("Usage: lotics package install <package> [--version N]");
|
|
862
|
+
process.exit(1);
|
|
863
|
+
}
|
|
864
|
+
let version;
|
|
865
|
+
if (flags.packageVersion !== undefined) {
|
|
866
|
+
version = Number(flags.packageVersion);
|
|
867
|
+
if (!Number.isInteger(version) || version <= 0) {
|
|
868
|
+
console.error(`Invalid --version "${flags.packageVersion}" — expected a positive integer.`);
|
|
869
|
+
process.exit(1);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
await packageInstall(client, { package_id: packageId, version });
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
if (subcommand === "eject") {
|
|
876
|
+
const appId = toolArgs;
|
|
877
|
+
if (!appId) {
|
|
878
|
+
console.error("Usage: lotics package eject <app_id>");
|
|
879
|
+
process.exit(1);
|
|
880
|
+
}
|
|
881
|
+
await packageEject(client, { app_id: appId });
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
if (subcommand === "extract") {
|
|
885
|
+
const appId = toolArgs;
|
|
886
|
+
if (!appId) {
|
|
887
|
+
console.error("Usage: lotics package extract <app_id> [path]");
|
|
888
|
+
console.error("Promotes a bespoke app to a draft package project (contract + binding + templates).");
|
|
889
|
+
process.exit(1);
|
|
890
|
+
}
|
|
891
|
+
await packageExtract(client, { app_id: appId, targetPath: restArgs[0] });
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
if (subcommand === "adopt") {
|
|
895
|
+
const appId = toolArgs;
|
|
896
|
+
if (!appId) {
|
|
897
|
+
console.error("Usage: lotics package adopt <app_id> [path]");
|
|
898
|
+
console.error("Binds the published package project (this dir, or [path]) onto the origin app.");
|
|
899
|
+
process.exit(1);
|
|
900
|
+
}
|
|
901
|
+
let version;
|
|
902
|
+
if (flags.packageVersion !== undefined) {
|
|
903
|
+
version = Number(flags.packageVersion);
|
|
904
|
+
if (!Number.isInteger(version) || version <= 0) {
|
|
905
|
+
console.error(`Invalid --version "${flags.packageVersion}" — expected a positive integer.`);
|
|
906
|
+
process.exit(1);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
await packageAdopt(client, { app_id: appId, version, projectDir: restArgs[0] });
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
if (subcommand === "fleet-upgrade") {
|
|
913
|
+
const packageId = toolArgs;
|
|
914
|
+
if (!packageId) {
|
|
915
|
+
console.error("Usage: lotics package fleet-upgrade <package_id> [--version N]");
|
|
916
|
+
process.exit(1);
|
|
917
|
+
}
|
|
918
|
+
let version;
|
|
919
|
+
if (flags.packageVersion !== undefined) {
|
|
920
|
+
version = Number(flags.packageVersion);
|
|
921
|
+
if (!Number.isInteger(version) || version <= 0) {
|
|
922
|
+
console.error(`Invalid --version "${flags.packageVersion}" — expected a positive integer.`);
|
|
923
|
+
process.exit(1);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
await packageFleetUpgrade(client, { package_id: packageId, version });
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
if (subcommand === "doctor") {
|
|
930
|
+
await packageDoctor(client, { app_id: toolArgs });
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
if (subcommand === "rebind-role") {
|
|
934
|
+
const appId = toolArgs;
|
|
935
|
+
const [roleAlias, groupId] = restArgs;
|
|
936
|
+
if (!appId || !roleAlias || !groupId) {
|
|
937
|
+
console.error("Usage: lotics package rebind-role <app_id> <role_alias> <grp_id>");
|
|
938
|
+
process.exit(1);
|
|
939
|
+
}
|
|
940
|
+
const result = await client.rebindAppPackageRole(appId, {
|
|
941
|
+
role_alias: roleAlias,
|
|
942
|
+
group_id: groupId,
|
|
943
|
+
});
|
|
944
|
+
console.error(`Rebound role "${result.role_alias}" → ${result.group_id}` +
|
|
945
|
+
(result.previous_group_id ? ` (was ${result.previous_group_id})` : "") +
|
|
946
|
+
". Package workflows/agents were re-materialized against the new group.");
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
if (subcommand === "upgrade") {
|
|
950
|
+
const appId = toolArgs;
|
|
951
|
+
if (!appId) {
|
|
952
|
+
console.error("Usage: lotics package upgrade <app_id> [--version N] [--resolve <namespace.alias>=recreate|<id> ...]");
|
|
953
|
+
process.exit(1);
|
|
954
|
+
}
|
|
955
|
+
let version;
|
|
956
|
+
if (flags.packageVersion !== undefined) {
|
|
957
|
+
version = Number(flags.packageVersion);
|
|
958
|
+
if (!Number.isInteger(version) || version <= 0) {
|
|
959
|
+
console.error(`Invalid --version "${flags.packageVersion}" — expected a positive integer.`);
|
|
960
|
+
process.exit(1);
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
await packageUpgrade(client, {
|
|
964
|
+
app_id: appId,
|
|
965
|
+
version,
|
|
966
|
+
resolutions: parseResolveFlags(flags.resolve),
|
|
967
|
+
});
|
|
968
|
+
return;
|
|
969
|
+
}
|
|
970
|
+
if (subcommand === "publish") {
|
|
971
|
+
await packagePublish(client, { projectDir: toolArgs, changelog: flags.message });
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
if (subcommand === "sync") {
|
|
975
|
+
await packageSync(client, { projectDir: toolArgs });
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
978
|
+
if (subcommand === "reset") {
|
|
979
|
+
await packageReset(client, { projectDir: toolArgs });
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
982
|
+
if (subcommand === "dev") {
|
|
983
|
+
// First positional is an optional project path; --port / --vite-port
|
|
984
|
+
// override the wrapper / Vite ports (same shape as `lotics app dev`).
|
|
985
|
+
const projectDir = toolArgs;
|
|
986
|
+
let port;
|
|
987
|
+
let vitePort;
|
|
988
|
+
for (const a of restArgs) {
|
|
989
|
+
const portMatch = /^--port=(\d+)$/.exec(a);
|
|
990
|
+
const vitePortMatch = /^--vite-port=(\d+)$/.exec(a);
|
|
991
|
+
if (portMatch)
|
|
992
|
+
port = Number(portMatch[1]);
|
|
993
|
+
else if (vitePortMatch)
|
|
994
|
+
vitePort = Number(vitePortMatch[1]);
|
|
995
|
+
}
|
|
996
|
+
await packageDev(client, { projectDir, port, vitePort });
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
999
|
+
console.error(`Unknown package subcommand: ${subcommand}`);
|
|
1000
|
+
console.error("Run 'lotics package' for usage.");
|
|
1001
|
+
process.exit(1);
|
|
1002
|
+
}
|
|
771
1003
|
// lotics app create / pull / deploy
|
|
772
1004
|
if (command === "app") {
|
|
773
1005
|
if (subcommand === "create") {
|