@lotics/cli 0.76.0 → 0.83.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,14 @@
1
1
  /**
2
- * Dispatch smoke test over the BUILT CLI artifact — every `package` subcommand
3
- * must route to its own handler, proven by its usage/refusal line appearing on
4
- * a bare invocation. The dispatch in cli.ts is one hand-rolled argv walk with
5
- * subcommand names repeated across command families (`doctor` exists in both
6
- * `workspace` and `package`) and split positional conventions (`toolArgs` +
7
- * `restArgs`), and nothing else tests it: `package yank` shipped dispatched
8
- * inside the WORKSPACE family and surfaced only on the first live invocation.
2
+ * Dispatch smoke test over the BUILT CLI artifact — every `package` subcommand,
3
+ * the top-level consumer verbs (`install`/`uninstall`/`upgrade`), and the app
4
+ * author verbs (publish/release/unpublish) must route to their own handler,
5
+ * proven by the usage/refusal line appearing on a bare invocation. The dispatch
6
+ * in cli.ts is one hand-rolled argv walk with subcommand names repeated across
7
+ * command families (`doctor` exists in both `workspace` and `package`) and split
8
+ * positional conventions (top-level verbs read the id from `subcommand`; the
9
+ * `package` family from `toolArgs`), and nothing else tests it: `package yank`
10
+ * shipped dispatched inside the WORKSPACE family and surfaced only on the first
11
+ * live invocation.
9
12
  *
10
13
  * The fake API key satisfies `requireClient` (client construction is offline);
11
14
  * every asserted path exits before any network call. Spawns run in an empty
@@ -48,18 +51,11 @@ describe("lotics package <subcommand> dispatch", () => {
48
51
  // subcommand's OWN usage line — reaching it proves family routing AND that
49
52
  // the usage gate fires before any network/filesystem work.
50
53
  const usageCases = [
51
- ["install", /Usage: lotics package install/],
52
- ["uninstall", /Usage: lotics package uninstall/],
53
54
  ["config", /Usage: lotics package config/],
54
- ["retire", /Usage: lotics package retire <package_id>/],
55
55
  ["show", /Usage: lotics package show <package_id>/],
56
- ["upgrade", /Usage: lotics package upgrade/],
57
56
  ["yank", /Usage: lotics package yank <package_id> <version>/],
58
- ["rebind-role", /Usage: lotics package rebind-role/],
59
- ["fleet-upgrade", /Usage: lotics package fleet-upgrade/],
60
- ["extract", /Usage: lotics package extract/],
61
- ["adopt", /Usage: lotics package adopt/],
62
- ["new", /Usage: lotics package new/],
57
+ // Folded into the upgrade grammar — the removed verb must redirect loudly.
58
+ ["rebind-role", /folded into the upgrade grammar[\s\S]*--resolve roles\./],
63
59
  ];
64
60
  for (const [sub, usage] of usageCases) {
65
61
  it(`routes "package ${sub}" to its handler (usage on missing args)`, () => {
@@ -69,16 +65,30 @@ describe("lotics package <subcommand> dispatch", () => {
69
65
  expect(status).toBe(1);
70
66
  });
71
67
  }
72
- // Path-defaulting subcommands: in an empty cwd they must fail on the missing
73
- // package project their handler's message, not the dispatcher's. (`sync`
74
- // is excluded: it validates the dev workspace over the network BEFORE
75
- // reading the project, so it can't be smoke-tested offline.)
76
- const projectCases = ["build", "publish"];
77
- for (const sub of projectCases) {
78
- it(`routes "package ${sub}" to its handler (missing project in empty cwd)`, () => {
68
+ // The high-traffic consumer verbs MOVED to the top level. The old
69
+ // `lotics package <verb>` forms must REJECT with a redirect (never silently
70
+ // do nothing) proving the removal is loud and points to the new form.
71
+ const movedForms = [
72
+ ["install", /moved to the top level.*lotics install/],
73
+ ["uninstall", /moved to the top level.*lotics uninstall/],
74
+ ["upgrade", /moved to the top level.*lotics upgrade/],
75
+ ["fleet-upgrade", /moved to the top level.*lotics upgrade <package_id>/],
76
+ ];
77
+ for (const [sub, redirect] of movedForms) {
78
+ it(`rejects the moved "package ${sub}" form with a top-level redirect`, () => {
79
79
  const { status, stderr } = runCli(["package", sub]);
80
- expect(stderr).toMatch(/No package\.json in|not a package project/);
81
- expect(stderr).not.toMatch(/Unknown package subcommand/);
80
+ expect(stderr).toMatch(redirect);
81
+ expect(status).toBe(1);
82
+ });
83
+ }
84
+ // Deleted author-loop + package-project surfaces must be GONE, not silently
85
+ // absorbed: each now falls through to the unknown-subcommand refusal. Content
86
+ // packages are published via the `publish_content`/`release_content` tools
87
+ // (`lotics run …`), apps via `lotics app publish`/`release`.
88
+ for (const sub of ["dev", "sync", "reset", "extract", "adopt", "retire", "release", "new", "build", "publish"]) {
89
+ it(`rejects the removed "package ${sub}" subcommand`, () => {
90
+ const { status, stderr } = runCli(["package", sub]);
91
+ expect(stderr).toMatch(/Unknown package subcommand/);
82
92
  expect(status).toBe(1);
83
93
  });
84
94
  }
@@ -88,3 +98,45 @@ describe("lotics package <subcommand> dispatch", () => {
88
98
  expect(status).toBe(1);
89
99
  });
90
100
  });
101
+ describe("lotics top-level consumer-verb dispatch", () => {
102
+ // The noun-less consumer verbs route from the top level (the package id is the
103
+ // second positional → `subcommand`), each proven by its OWN usage line on a
104
+ // bare invocation — reaching it fires before any network work.
105
+ const usageCases = [
106
+ ["install", /Usage: lotics install <package_id>/],
107
+ ["uninstall", /Usage: lotics uninstall <app_id\|pci_id>/],
108
+ ["upgrade", /Usage: lotics upgrade <app_id \| pci_id \| package_id>/],
109
+ ];
110
+ for (const [verb, usage] of usageCases) {
111
+ it(`routes top-level "${verb}" to its handler (usage on missing id)`, () => {
112
+ const { status, stderr } = runCli([verb]);
113
+ expect(stderr).toMatch(usage);
114
+ expect(stderr).not.toMatch(/Unknown command/);
115
+ expect(status).toBe(1);
116
+ });
117
+ }
118
+ });
119
+ describe("lotics app author-verb dispatch", () => {
120
+ // `app publish` (no id, empty cwd) must fail on the missing local app
121
+ // manifest — its handler's message, proving routing without any network.
122
+ it('routes "app publish" to its handler (no app id in empty cwd)', () => {
123
+ const { status, stderr } = runCli(["app", "publish"]);
124
+ expect(stderr).toMatch(/No app id/);
125
+ expect(stderr).not.toMatch(/Unknown app subcommand/);
126
+ expect(status).toBe(1);
127
+ });
128
+ // `app release` requires -m before any resolution.
129
+ it('routes "app release" to its handler (usage on missing -m)', () => {
130
+ const { status, stderr } = runCli(["app", "release"]);
131
+ expect(stderr).toMatch(/Usage: lotics app release/);
132
+ expect(stderr).not.toMatch(/Unknown app subcommand/);
133
+ expect(status).toBe(1);
134
+ });
135
+ // `app unpublish` requires an id.
136
+ it('routes "app unpublish" to its handler (usage on missing id)', () => {
137
+ const { status, stderr } = runCli(["app", "unpublish"]);
138
+ expect(stderr).toMatch(/Usage: lotics app unpublish/);
139
+ expect(stderr).not.toMatch(/Unknown app subcommand/);
140
+ expect(status).toBe(1);
141
+ });
142
+ });