@lotics/cli 0.76.1 → 0.86.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 +57 -52
- 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 +214 -255
- package/dist/cli_dispatch.test.js +90 -25
- package/dist/client.d.ts +134 -176
- package/dist/client.js +72 -95
- package/dist/generate_app_workflows_dts.js +11 -2
- package/dist/generate_app_workflows_dts.test.js +3 -3
- 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 +116 -332
- package/dist/package_commands.js +420 -1316
- package/dist/package_commands.test.js +194 -537
- package/dist/src/cli.js +721 -1846
- 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/README.md
CHANGED
|
@@ -195,78 +195,83 @@ 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
|
-
##
|
|
198
|
+
## Packages
|
|
199
199
|
|
|
200
|
-
A maintained, versioned
|
|
200
|
+
A maintained, versioned package library: author once, install into many workspaces, upgrade per-workspace. A package carries an app, content (knowledge docs and/or document templates), or both. **Nothing starts as a package** — build a bespoke app first (the `lotics app` loop above), then first-release it as a package when it proves worth distributing. Any org authors its own packages (publishing is owner-org-only); Lotics-backed packages carry the `official` badge. See `docs/packages.md` for the model.
|
|
201
201
|
|
|
202
202
|
```bash
|
|
203
|
-
#
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
#
|
|
208
|
-
lotics
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
#
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
lotics
|
|
203
|
+
# 1. Make the app distributable — first-release it as a package (one call: extract
|
|
204
|
+
# an alias-keyed contract, create the registry package, publish v1 from the
|
|
205
|
+
# deployed source + dist, pin the app as installation #1). Blocking findings
|
|
206
|
+
# (a hardcoded fld_/opt_ id in src/ or a workflow body, a button field) → 409.
|
|
207
|
+
lotics app publish # from the pulled app project (or: app publish app_...)
|
|
208
|
+
lotics app publish --rename item=deal -m "v1" # fix an auto-minted alias before v1 freezes it
|
|
209
|
+
|
|
210
|
+
# 2. Iterate IN PLACE — the origin app is the permanent working copy
|
|
211
|
+
lotics app pull app_... # the same loop as any app: pull, edit, dev
|
|
212
|
+
lotics app deploy -m "Add quote drawer" # commit a new app version
|
|
213
|
+
|
|
214
|
+
# 3. Release the next package version from the origin you are happy with
|
|
215
|
+
lotics app release -m "v2: adds deals" # preview (new/changed aliases + findings)
|
|
216
|
+
lotics app release -m "v2: adds deals" --yes # publish v2 + re-pin the origin
|
|
217
|
+
|
|
218
|
+
# Take it off the shelf (existing installations keep working + upgrading)
|
|
219
|
+
lotics app unpublish app_... [--undo] # by app id (resolves the package) or package id
|
|
220
|
+
|
|
221
|
+
# Operate installations (workspace admin). The high-traffic verbs are top-level
|
|
222
|
+
# (install / uninstall / upgrade); low-traffic ops live on `lotics package`.
|
|
223
|
+
lotics package show apg_... # registry metadata + version history (trust badge, yank)
|
|
224
|
+
lotics install apg_... --version 2 # scaffold + materialize + deploy + pin
|
|
225
|
+
lotics install apg_... --config heading="Ops board" # per-knob overrides over contract defaults
|
|
216
226
|
lotics package config app_... --set show_done=false # edit a live installation's config (partial merge)
|
|
217
|
-
lotics
|
|
218
|
-
lotics package retire apg_... [--undo] # owner-org: refuse NEW installs (existing keep working + upgrading)
|
|
227
|
+
lotics uninstall app_... [--archive-tables] # remove an installation; workflows stop firing
|
|
219
228
|
lotics package doctor app_... # version pin vs latest + drift + local edits (exit 1 on findings)
|
|
220
|
-
lotics
|
|
221
|
-
lotics
|
|
222
|
-
lotics
|
|
223
|
-
lotics
|
|
224
|
-
lotics
|
|
225
|
-
lotics
|
|
229
|
+
lotics upgrade app_... # preview, then apply (additive; overlay preserved)
|
|
230
|
+
lotics upgrade apg_... # APP package → FLEET: upgrade EVERY org installation (clean ones apply; findings skip, exit 1)
|
|
231
|
+
lotics upgrade app_... --resolve fields.deal.stage=recreate # resolve reported drift
|
|
232
|
+
lotics upgrade app_... --resolve queries.tasks=keep # consent for a local edit (or =revert)
|
|
233
|
+
lotics upgrade app_... --resolve template.quote=revert # a locally-edited template (clean ones auto-update; or =keep)
|
|
234
|
+
lotics upgrade app_... --resolve knowledge.playbook=apply # a bundled knowledge doc: apply|keep|archive|recreate|unbind
|
|
235
|
+
lotics upgrade app_... --resolve roles.approver=grp_... # re-point a LIVE package role at another group
|
|
236
|
+
lotics package yank apg_... 3 [--undo] # refuse new installs/upgrades of a broken version
|
|
226
237
|
lotics package eject app_... # one-way: sever the package link
|
|
227
238
|
lotics workspace doctor # dangling schema references across the workspace (exit 1 on findings)
|
|
228
|
-
|
|
229
|
-
# Promote an EXISTING bespoke app to a package (build it in a real workspace first,
|
|
230
|
-
# distribute once it proves itself — no re-authoring)
|
|
231
|
-
lotics package extract app_... # → draft project: contract + binding + templates
|
|
232
|
-
# prints a findings report; always writes the draft,
|
|
233
|
-
# exits 1 on error findings (fix them, publish re-validates)
|
|
234
|
-
cd <slug-of-app-name> # review contract.json aliases
|
|
235
|
-
lotics package publish -m "v1" # publish the reviewed project
|
|
236
|
-
lotics package adopt app_... # bind the package onto the origin app (same workspace);
|
|
237
|
-
# verifies the binding is faithful, then pins installation #1
|
|
238
239
|
```
|
|
239
240
|
|
|
240
|
-
|
|
241
|
+
After the first publish **the origin is the master** — develop it in its workspace (`app pull` / `app deploy` / `app workflow set` / table tools), and `lotics app release` snapshots each version. Its **binding-aware re-extract** keeps aliases stable through the origin's current binding, so re-extraction never churns the upgrade diff (which is why extract-again is the loop, not a hazard). Release is also the **bundled-knowledge declaration surface**: run from the app project, the manifest's `lotics.knowledge` (`[{alias,doc_id}]`) re-declares which docs the package bundles — an alias added ships as a new doc, a bound alias dropped surfaces to consumers as `removed` (archive|keep), a present one re-snapshots; a bare id from elsewhere reconstructs the current corpus from the pin (never drops it). So a published app package evolves its bundled docs across versions without ejecting.
|
|
241
242
|
|
|
242
243
|
### Content packages
|
|
243
244
|
|
|
244
245
|
A **content** package ships **knowledge docs and/or document templates** — no app, just the artifacts. Knowledge is the reference corpus an agent looks up by name; templates are standalone document templates. Content comes two ways: bundled *with an app* (installing the app delivers its agents' docs) or as a standalone content package (a versioned corpus). Install delivers, upgrade refreshes — preserving stable `kdc_`/`tmpl_` ids + a version snapshot per change. See `docs/packages.md` § Content.
|
|
245
246
|
|
|
246
247
|
```bash
|
|
247
|
-
#
|
|
248
|
-
#
|
|
249
|
-
|
|
250
|
-
lotics
|
|
248
|
+
# Publish a content package from a SET of live knowledge docs + templates — the
|
|
249
|
+
# publish_content tool (there is no content-package project). Aliases + shas are
|
|
250
|
+
# computed from live content; v1 is created and the origin workspace is pinned.
|
|
251
|
+
lotics run publish_content '{"name":"My SOPs","knowledge_doc_ids":["kdc_..."],"template_ids":["tmpl_..."]}'
|
|
252
|
+
|
|
253
|
+
# Cut the next version after editing those docs/templates in place (add "dry_run":true to preview the diff)
|
|
254
|
+
lotics run release_content '{"package_id":"apg_...","changelog":"v2: revised SOPs"}'
|
|
251
255
|
|
|
252
256
|
# Install into a workspace (a same-named local DOC collides — consent with --bind-to; templates never collide)
|
|
253
|
-
lotics
|
|
254
|
-
lotics
|
|
255
|
-
|
|
256
|
-
# Discover installed content (
|
|
257
|
-
lotics package list-content # id · package · version
|
|
258
|
-
|
|
259
|
-
# Refresh
|
|
260
|
-
|
|
261
|
-
lotics
|
|
262
|
-
lotics
|
|
263
|
-
lotics
|
|
264
|
-
lotics
|
|
257
|
+
lotics install apg_... # app OR content, shape-branched
|
|
258
|
+
lotics install apg_... --bind-to policy=kdc_... # adopt an existing doc as package-managed
|
|
259
|
+
|
|
260
|
+
# Discover installed content (each row leads with the PACKAGE id)
|
|
261
|
+
lotics package list-content # package id · package · version (standalone content installs)
|
|
262
|
+
|
|
263
|
+
# Refresh this workspace's content install — addressed by PACKAGE id (one install per
|
|
264
|
+
# package per workspace, so the package id is the address; one gate over docs + templates)
|
|
265
|
+
lotics upgrade apg_... # CONTENT package → preview, then apply (this workspace's install)
|
|
266
|
+
lotics upgrade apg_... --resolve knowledge.policy=apply # a doc: apply|keep|archive|recreate|unbind
|
|
267
|
+
lotics upgrade apg_... --resolve template.quote=revert # a locally-edited template: revert|keep (clean ones auto-update)
|
|
268
|
+
lotics upgrade apg_... --apply-all # accept upstream for every doc + template (OVERWRITES local edits)
|
|
269
|
+
lotics uninstall apg_... --keep-content # remove the pin; keep docs + templates (default archives them)
|
|
265
270
|
```
|
|
266
271
|
|
|
267
|
-
A package's knowledge can also arrive **bundled with an app**. Upgrading such an app (`lotics
|
|
272
|
+
A package's knowledge can also arrive **bundled with an app**. Upgrading such an app (`lotics upgrade <app_id>`) previews its bundled-knowledge changes alongside the plan and resolves them in the SAME namespaced grammar — `--resolve knowledge.<alias>=apply|keep|archive|recreate|unbind` (the flags pass to the server verbatim; no routing, no ambiguity), `--bind-to` consents an added-doc collision, `--apply-all` takes the package's version for every consent-requiring doc.
|
|
268
273
|
|
|
269
|
-
To package the knowledge an existing app's agents route to, declare it in the app project's `package.json#lotics.knowledge` (`[{ alias, doc_id }]`, + `lotics.knowledge_expects` for docs the agents assume but the package doesn't own), then `lotics
|
|
274
|
+
To package the knowledge an existing app's agents route to, declare it in the app project's `package.json#lotics.knowledge` (`[{ alias, doc_id }]`, + `lotics.knowledge_expects` for docs the agents assume but the package doesn't own), then `lotics app publish` from the app project — it forwards the declaration, and the docs ship with the package.
|
|
270
275
|
|
|
271
276
|
## SDK
|
|
272
277
|
|
package/dist/app_commands.d.ts
CHANGED
|
@@ -137,10 +137,15 @@ export declare function writeAppDts(projectDir: string, manifest: {
|
|
|
137
137
|
* `lotics app codegen [path]` — regenerate every `.lotics/` artifact from the
|
|
138
138
|
* manifest + workspace schema, WITHOUT a deploy. The `.d.ts` companions are
|
|
139
139
|
* always written (synchronous, no network). When a `client` is available, the
|
|
140
|
-
* runtime `app_fields.ts` is also regenerated
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
140
|
+
* runtime `app_fields.ts` is also regenerated — **branched on whether the app is
|
|
141
|
+
* a package installation** (`getApp().package_id` set): a **linked/published**
|
|
142
|
+
* app emits the BINDING-resolved form (`F`/`OPT`/`ROLE` resolved from the live
|
|
143
|
+
* binding at module load via `getAppBinding()`, so the source stays portable
|
|
144
|
+
* across every install), a **bespoke** app emits the baked form (this
|
|
145
|
+
* workspace's concrete `fld_…`/`opt_…` ids, from the live schema of the tables
|
|
146
|
+
* its queries reference + the allowlist). A network failure is non-fatal (warn,
|
|
147
|
+
* keep the last-generated file) — so codegen still does useful work offline,
|
|
148
|
+
* mirroring `app create`'s tolerance of an offline npm registry.
|
|
144
149
|
*/
|
|
145
150
|
export declare function appCodegen(args: {
|
|
146
151
|
projectDir?: string;
|
package/dist/app_commands.js
CHANGED
|
@@ -23,6 +23,7 @@ import { generateAppAgentsDts } from "./generate_app_agents_dts.js";
|
|
|
23
23
|
import { generateAppQueriesDts } from "./generate_app_queries_dts.js";
|
|
24
24
|
import { collectQueryTableIds } from "@lotics/shared/app_query_ast";
|
|
25
25
|
import { generateAppFields } from "./generate_app_fields.js";
|
|
26
|
+
import { generatePackageAppFields } from "./generate_package_fields.js";
|
|
26
27
|
import { loadProjectTypescript, checkWorkflowBodies, } from "./app_workflow_check.js";
|
|
27
28
|
/**
|
|
28
29
|
* Resolve the latest published version of a package from the npm registry.
|
|
@@ -442,14 +443,33 @@ function writeAppFields(projectDir, tables) {
|
|
|
442
443
|
fs.writeFileSync(file, generateAppFields(tables));
|
|
443
444
|
return file;
|
|
444
445
|
}
|
|
446
|
+
/**
|
|
447
|
+
* Write the BINDING-resolved `.lotics/app_fields.ts` for a package installation
|
|
448
|
+
* — the portable twin of `writeAppFields`. Instead of baking this workspace's
|
|
449
|
+
* concrete ids, the module resolves `F`/`OPT`/`ROLE` from the installation's
|
|
450
|
+
* live binding at load (`getAppBinding()`), so a published origin's source runs
|
|
451
|
+
* unchanged in every workspace that installs the package. Returns the path.
|
|
452
|
+
*/
|
|
453
|
+
function writeBindingAppFields(projectDir, binding) {
|
|
454
|
+
const dotLotics = path.join(projectDir, ".lotics");
|
|
455
|
+
fs.mkdirSync(dotLotics, { recursive: true });
|
|
456
|
+
const file = path.join(dotLotics, "app_fields.ts");
|
|
457
|
+
fs.writeFileSync(file, generatePackageAppFields(binding));
|
|
458
|
+
return file;
|
|
459
|
+
}
|
|
445
460
|
/**
|
|
446
461
|
* `lotics app codegen [path]` — regenerate every `.lotics/` artifact from the
|
|
447
462
|
* manifest + workspace schema, WITHOUT a deploy. The `.d.ts` companions are
|
|
448
463
|
* always written (synchronous, no network). When a `client` is available, the
|
|
449
|
-
* runtime `app_fields.ts` is also regenerated
|
|
450
|
-
*
|
|
451
|
-
*
|
|
452
|
-
*
|
|
464
|
+
* runtime `app_fields.ts` is also regenerated — **branched on whether the app is
|
|
465
|
+
* a package installation** (`getApp().package_id` set): a **linked/published**
|
|
466
|
+
* app emits the BINDING-resolved form (`F`/`OPT`/`ROLE` resolved from the live
|
|
467
|
+
* binding at module load via `getAppBinding()`, so the source stays portable
|
|
468
|
+
* across every install), a **bespoke** app emits the baked form (this
|
|
469
|
+
* workspace's concrete `fld_…`/`opt_…` ids, from the live schema of the tables
|
|
470
|
+
* its queries reference + the allowlist). A network failure is non-fatal (warn,
|
|
471
|
+
* keep the last-generated file) — so codegen still does useful work offline,
|
|
472
|
+
* mirroring `app create`'s tolerance of an offline npm registry.
|
|
453
473
|
*/
|
|
454
474
|
export async function appCodegen(args) {
|
|
455
475
|
const projectDir = path.resolve(args.projectDir ?? process.cwd());
|
|
@@ -467,18 +487,32 @@ export async function appCodegen(args) {
|
|
|
467
487
|
"Run authenticated (or set LOTICS_API_KEY) to regenerate field/option ids.");
|
|
468
488
|
return;
|
|
469
489
|
}
|
|
470
|
-
const tableIds = resolveCodegenTableIds(projectDir, meta.queries ?? {});
|
|
471
490
|
try {
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
491
|
+
// Branch on linked-ness: a package installation (its own published origin OR
|
|
492
|
+
// an install of another org's package) must ship PORTABLE source, so
|
|
493
|
+
// app_fields resolves F/OPT/ROLE from the live binding at module load; a
|
|
494
|
+
// bespoke app bakes this workspace's concrete ids. getApp is the authority —
|
|
495
|
+
// `package_id` is set iff the app is a package installation.
|
|
496
|
+
const app = await args.client.getApp(meta.app_id);
|
|
497
|
+
if (app.package_id) {
|
|
498
|
+
const binding = await args.client.appBinding(meta.app_id);
|
|
499
|
+
const fieldsPath = writeBindingAppFields(projectDir, binding);
|
|
500
|
+
const count = Object.keys(binding.fields).length;
|
|
501
|
+
console.error(`Regenerated ${fieldsPath} (binding form — ${count} field alias${count === 1 ? "" : "es"} resolved per-install)`);
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
const tableIds = resolveCodegenTableIds(projectDir, meta.queries ?? {});
|
|
505
|
+
const tables = await args.client.getWorkspaceSchema(tableIds);
|
|
506
|
+
const fieldsPath = writeAppFields(projectDir, tables);
|
|
507
|
+
console.error(`Regenerated ${fieldsPath} (${tables.length} table${tables.length === 1 ? "" : "s"})`);
|
|
508
|
+
}
|
|
475
509
|
}
|
|
476
510
|
catch (err) {
|
|
477
511
|
// Non-fatal: keep the last-generated app_fields.ts so an offline/transient
|
|
478
512
|
// failure doesn't strip the app's field aliases (same tolerance as the npm
|
|
479
513
|
// registry lookup on `app create`).
|
|
480
|
-
console.error(`⚠ Could not
|
|
481
|
-
`Kept the existing
|
|
514
|
+
console.error(`⚠ Could not regenerate .lotics/app_fields.ts (${err instanceof Error ? err.message : String(err)}). ` +
|
|
515
|
+
`Kept the existing file.`);
|
|
482
516
|
}
|
|
483
517
|
// Refresh each bound workflow's ambient globals + re-wrap its EXISTING local
|
|
484
518
|
// body in the current envelope (GAP-59). Codegen never re-fetches the body
|
|
@@ -206,6 +206,75 @@ describe("appCodegen (.d.ts-only path)", () => {
|
|
|
206
206
|
expect(second.equals(first)).toBe(true);
|
|
207
207
|
});
|
|
208
208
|
});
|
|
209
|
+
/**
|
|
210
|
+
* `appCodegen` (with a client) branches the runtime `app_fields.ts` on whether
|
|
211
|
+
* the app is a package installation: a LINKED app (`getApp().package_id` set)
|
|
212
|
+
* emits the binding-resolved form (portable — resolves through `getAppBinding()`
|
|
213
|
+
* at load); a BESPOKE app bakes this workspace's concrete ids from the schema.
|
|
214
|
+
*/
|
|
215
|
+
describe("appCodegen app_fields branch (linked vs bespoke)", () => {
|
|
216
|
+
let workDir;
|
|
217
|
+
beforeEach(() => {
|
|
218
|
+
workDir = fs.mkdtempSync(path.join(tmpdir(), "lotics-codegen-branch-"));
|
|
219
|
+
vi.spyOn(console, "error").mockImplementation(() => undefined);
|
|
220
|
+
});
|
|
221
|
+
afterEach(() => {
|
|
222
|
+
fs.rmSync(workDir, { recursive: true, force: true });
|
|
223
|
+
vi.restoreAllMocks();
|
|
224
|
+
});
|
|
225
|
+
function writeManifest() {
|
|
226
|
+
fs.writeFileSync(path.join(workDir, "package.json"), JSON.stringify({
|
|
227
|
+
name: "sample-app",
|
|
228
|
+
lotics: {
|
|
229
|
+
app_id: "app_1",
|
|
230
|
+
workspace_id: "wks_1",
|
|
231
|
+
current_version_id: "apv_1",
|
|
232
|
+
version_number: 1,
|
|
233
|
+
queries: { all: { ast: { kind: "from_table", table_id: "tbl_1" } } },
|
|
234
|
+
},
|
|
235
|
+
}, null, 2));
|
|
236
|
+
}
|
|
237
|
+
const readFields = () => fs.readFileSync(path.join(workDir, ".lotics", "app_fields.ts"), "utf-8");
|
|
238
|
+
it("emits the BAKED form for a bespoke app (no package_id)", async () => {
|
|
239
|
+
writeManifest();
|
|
240
|
+
const client = {
|
|
241
|
+
getApp: async () => ({ package_id: null }),
|
|
242
|
+
getWorkspaceSchema: async () => [
|
|
243
|
+
{ id: "tbl_1", name: "Tasks", fields: [{ id: "fld_title", name: "Title" }] },
|
|
244
|
+
],
|
|
245
|
+
};
|
|
246
|
+
await appCodegen({ projectDir: workDir, client });
|
|
247
|
+
const src = readFields();
|
|
248
|
+
expect(src).not.toContain("getAppBinding");
|
|
249
|
+
// Concrete id baked into the value position.
|
|
250
|
+
expect(src).toContain('"fld_title"');
|
|
251
|
+
expect(src).toMatch(/TASKS: \{/);
|
|
252
|
+
});
|
|
253
|
+
it("emits the BINDING form for a linked/published app (package_id set)", async () => {
|
|
254
|
+
writeManifest();
|
|
255
|
+
const client = {
|
|
256
|
+
getApp: async () => ({ package_id: "apg_1" }),
|
|
257
|
+
appBinding: async () => ({
|
|
258
|
+
fields: { "tasks.title": "fld_title" },
|
|
259
|
+
options: {},
|
|
260
|
+
roles: { approver: "grp_x" },
|
|
261
|
+
}),
|
|
262
|
+
// Must NOT be consulted on the linked branch — a call would fail the type
|
|
263
|
+
// shape it returns, and the assertions below prove the baked path is dead.
|
|
264
|
+
getWorkspaceSchema: async () => {
|
|
265
|
+
throw new Error("getWorkspaceSchema must not run for a linked app");
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
await appCodegen({ projectDir: workDir, client });
|
|
269
|
+
const src = readFields();
|
|
270
|
+
expect(src).toContain('import { getAppBinding } from "@lotics/app-sdk";');
|
|
271
|
+
expect(src).toContain("const binding = await getAppBinding();");
|
|
272
|
+
expect(src).toContain('title: bound(binding.fields, "tasks.title", "field")');
|
|
273
|
+
expect(src).toContain('approver: bound(binding.roles, "approver", "role")');
|
|
274
|
+
// No concrete id baked into any value position.
|
|
275
|
+
expect(src).not.toMatch(/"(fld|opt|tbl|grp)_/);
|
|
276
|
+
});
|
|
277
|
+
});
|
|
209
278
|
/**
|
|
210
279
|
* `appUiLink` edits the app's vite.config.ts resolve.alias to dev-link
|
|
211
280
|
* @lotics/ui at packages/ui/src — auto-found in a monorepo checkout, or given
|
package/dist/args.d.ts
CHANGED
|
@@ -30,8 +30,6 @@ 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;
|
|
35
33
|
/** `--yes` (alias `-y`): confirm a destructive command (e.g. `workspace delete`) non-interactively. */
|
|
36
34
|
yes: boolean;
|
|
37
35
|
/** `--print-created` (alias `--report-effects`): print the honest post-run side-effect harvest. */
|
|
@@ -39,7 +37,7 @@ export declare function parseArgs(argv: string[]): {
|
|
|
39
37
|
/** `--cleanup`: also delete the harvested created records (records only). */
|
|
40
38
|
cleanup: boolean;
|
|
41
39
|
/**
|
|
42
|
-
* `--version <n>`: a package version number (`lotics
|
|
40
|
+
* `--version <n>`: a package version number (`lotics install … --version 2`).
|
|
43
41
|
* Distinct from the boolean `version` flag below: `--version` followed by an
|
|
44
42
|
* integer captures the number; a bare `--version` (or `-v`) prints the CLI
|
|
45
43
|
* version. No other command takes a numeric value after `--version`, so the
|
|
@@ -64,8 +62,11 @@ export declare function parseArgs(argv: string[]): {
|
|
|
64
62
|
keepContent: boolean;
|
|
65
63
|
/** `--apply-all` (knowledge `package upgrade`): accept the package's version for every consent entry. */
|
|
66
64
|
applyAll: boolean;
|
|
67
|
-
/**
|
|
68
|
-
|
|
65
|
+
/**
|
|
66
|
+
* `--rename <old>=<new>` (repeatable, `lotics app publish`): fix an auto-minted
|
|
67
|
+
* alias before the package's v1 contract freezes it.
|
|
68
|
+
*/
|
|
69
|
+
rename: string[];
|
|
69
70
|
/** `--config key=value` (repeatable): per-knob config overrides at `package install`. */
|
|
70
71
|
config: string[];
|
|
71
72
|
/** `--set key=value` (repeatable): per-knob config edits at `package config`. */
|
package/dist/args.js
CHANGED
|
@@ -25,7 +25,6 @@ export function parseArgs(argv) {
|
|
|
25
25
|
message: undefined,
|
|
26
26
|
local: false,
|
|
27
27
|
all: false,
|
|
28
|
-
dev: false,
|
|
29
28
|
yes: false,
|
|
30
29
|
printCreated: false,
|
|
31
30
|
cleanup: false,
|
|
@@ -34,7 +33,7 @@ export function parseArgs(argv) {
|
|
|
34
33
|
bindTo: [],
|
|
35
34
|
keepContent: false,
|
|
36
35
|
applyAll: false,
|
|
37
|
-
|
|
36
|
+
rename: [],
|
|
38
37
|
config: [],
|
|
39
38
|
set: [],
|
|
40
39
|
archiveTables: false,
|
|
@@ -89,9 +88,6 @@ export function parseArgs(argv) {
|
|
|
89
88
|
case "--local":
|
|
90
89
|
flags.local = true;
|
|
91
90
|
break;
|
|
92
|
-
case "--dev":
|
|
93
|
-
flags.dev = true;
|
|
94
|
-
break;
|
|
95
91
|
case "--all":
|
|
96
92
|
flags.all = true;
|
|
97
93
|
break;
|
|
@@ -128,9 +124,14 @@ export function parseArgs(argv) {
|
|
|
128
124
|
case "--apply-all":
|
|
129
125
|
flags.applyAll = true;
|
|
130
126
|
break;
|
|
131
|
-
case "--
|
|
132
|
-
|
|
127
|
+
case "--rename": {
|
|
128
|
+
const value = argv[++i];
|
|
129
|
+
if (value === undefined || value.startsWith("-")) {
|
|
130
|
+
throw new Error("--rename requires a value: old=new (an alias to rename before v1 freezes it).");
|
|
131
|
+
}
|
|
132
|
+
flags.rename.push(value);
|
|
133
133
|
break;
|
|
134
|
+
}
|
|
134
135
|
case "--config": {
|
|
135
136
|
const value = argv[++i];
|
|
136
137
|
if (value === undefined || value.startsWith("-")) {
|
|
@@ -155,18 +156,18 @@ export function parseArgs(argv) {
|
|
|
155
156
|
break;
|
|
156
157
|
case "--version":
|
|
157
158
|
case "-v": {
|
|
158
|
-
//
|
|
159
|
-
//
|
|
160
|
-
//
|
|
161
|
-
//
|
|
162
|
-
//
|
|
163
|
-
// value is captured raw;
|
|
164
|
-
// integer-ness with a loud error.
|
|
165
|
-
// CLI-version boolean.
|
|
159
|
+
// For a package version-taking command (`package …`, or the top-level
|
|
160
|
+
// consumer verbs `install` / `upgrade`), `--version` carries a value
|
|
161
|
+
// (the package version) and the value is REQUIRED — a bare or malformed
|
|
162
|
+
// `--version` here must never fall back to the CLI-version early-exit
|
|
163
|
+
// boolean, which would print the CLI version and exit 0 (a provisioning
|
|
164
|
+
// script reads that as a successful install). The value is captured raw;
|
|
165
|
+
// the consuming command validates integer-ness with a loud error.
|
|
166
|
+
// Everywhere else, this is the CLI-version boolean.
|
|
166
167
|
const next = argv[i + 1];
|
|
167
|
-
if (command === "package") {
|
|
168
|
+
if (command === "package" || command === "install" || command === "upgrade") {
|
|
168
169
|
if (next === undefined || next.startsWith("-")) {
|
|
169
|
-
throw new Error("--version requires a version number for package commands (e.g. --version 2).");
|
|
170
|
+
throw new Error("--version requires a version number for install/upgrade/package commands (e.g. --version 2).");
|
|
170
171
|
}
|
|
171
172
|
flags.packageVersion = next;
|
|
172
173
|
i++;
|
package/dist/args.test.js
CHANGED
|
@@ -23,15 +23,21 @@ 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
|
|
27
|
-
const r = parseArgs(["
|
|
28
|
-
expect(r.command).toBe("
|
|
29
|
-
expect(r.subcommand).toBe("
|
|
30
|
-
expect(r.toolArgs).toBe("apg_123");
|
|
26
|
+
it("captures --version <int> as packageVersion on the top-level install verb", () => {
|
|
27
|
+
const r = parseArgs(["install", "apg_123", "--version", "2"]);
|
|
28
|
+
expect(r.command).toBe("install");
|
|
29
|
+
expect(r.subcommand).toBe("apg_123");
|
|
31
30
|
expect(r.flags.packageVersion).toBe("2");
|
|
32
31
|
expect(r.flags.version).toBe(false);
|
|
33
32
|
expect(r.restArgs).toEqual([]);
|
|
34
33
|
});
|
|
34
|
+
it("captures --version <int> as packageVersion on the top-level upgrade verb", () => {
|
|
35
|
+
const r = parseArgs(["upgrade", "app_1", "--version", "3"]);
|
|
36
|
+
expect(r.command).toBe("upgrade");
|
|
37
|
+
expect(r.subcommand).toBe("app_1");
|
|
38
|
+
expect(r.flags.packageVersion).toBe("3");
|
|
39
|
+
expect(r.flags.version).toBe(false);
|
|
40
|
+
});
|
|
35
41
|
it("treats a bare --version as the boolean CLI-version flag", () => {
|
|
36
42
|
const r = parseArgs(["--version"]);
|
|
37
43
|
expect(r.flags.version).toBe(true);
|
|
@@ -42,14 +48,13 @@ describe("parseArgs", () => {
|
|
|
42
48
|
expect(r.flags.version).toBe(true);
|
|
43
49
|
expect(r.flags.packageVersion).toBeUndefined();
|
|
44
50
|
});
|
|
45
|
-
it("captures a non-integer --version value under
|
|
46
|
-
const r = parseArgs(["
|
|
51
|
+
it("captures a non-integer --version value under version-taking commands for loud command-level validation", () => {
|
|
52
|
+
const r = parseArgs(["install", "apg_123", "--version", "2.0"]);
|
|
47
53
|
expect(r.flags.packageVersion).toBe("2.0");
|
|
48
54
|
expect(r.flags.version).toBe(false);
|
|
49
55
|
});
|
|
50
56
|
it("collects repeated --resolve values", () => {
|
|
51
57
|
const r = parseArgs([
|
|
52
|
-
"package",
|
|
53
58
|
"upgrade",
|
|
54
59
|
"app_1",
|
|
55
60
|
"--resolve",
|
|
@@ -60,11 +65,11 @@ describe("parseArgs", () => {
|
|
|
60
65
|
expect(r.flags.resolve).toEqual(["fields.deal.stage=recreate", "templates.quote=dtl_abc"]);
|
|
61
66
|
});
|
|
62
67
|
it("errors on --resolve without a value", () => {
|
|
63
|
-
expect(() => parseArgs(["
|
|
68
|
+
expect(() => parseArgs(["upgrade", "app_1", "--resolve"])).toThrow(/--resolve requires a value/);
|
|
64
69
|
});
|
|
65
|
-
it("errors loudly on a bare --version under
|
|
66
|
-
expect(() => parseArgs(["
|
|
67
|
-
expect(() => parseArgs(["
|
|
70
|
+
it("errors loudly on a bare --version under version-taking commands instead of printing the CLI version", () => {
|
|
71
|
+
expect(() => parseArgs(["install", "apg_123", "--version"])).toThrow(/requires a version number/);
|
|
72
|
+
expect(() => parseArgs(["install", "apg_123", "--version", "--json"])).toThrow(/requires a version number/);
|
|
68
73
|
});
|
|
69
74
|
it("parses --workspace as a value flag", () => {
|
|
70
75
|
const r = parseArgs(["run", "query_tables", "{}", "--workspace", "wsp_123"]);
|
|
@@ -106,11 +111,14 @@ describe("parseArgs", () => {
|
|
|
106
111
|
expect(parseArgs(["app", "workflow", "run", "wf"]).flags.cleanup).toBe(false);
|
|
107
112
|
expect(parseArgs(["app", "workflow", "run", "wf"]).flags.printCreated).toBe(false);
|
|
108
113
|
});
|
|
109
|
-
it("
|
|
110
|
-
|
|
111
|
-
expect(
|
|
112
|
-
|
|
113
|
-
expect(parseArgs(["
|
|
114
|
+
it("collects repeated --rename old=new flags (default empty)", () => {
|
|
115
|
+
const r = parseArgs(["app", "publish", ".", "--rename", "item=deal", "--rename", "don_hang=orders"]);
|
|
116
|
+
expect(r.flags.rename).toEqual(["item=deal", "don_hang=orders"]);
|
|
117
|
+
expect(r.toolArgs).toBe(".");
|
|
118
|
+
expect(parseArgs(["app", "publish"]).flags.rename).toEqual([]);
|
|
119
|
+
});
|
|
120
|
+
it("throws when --rename has no value", () => {
|
|
121
|
+
expect(() => parseArgs(["app", "publish", "--rename"])).toThrow(/--rename requires a value/);
|
|
114
122
|
});
|
|
115
123
|
it("parses --yes and -y as the same boolean confirmation flag (default false)", () => {
|
|
116
124
|
expect(parseArgs(["workspace", "delete", "wsp_1", "--yes"]).flags.yes).toBe(true);
|