@getxflow/cli 0.24.0 → 0.25.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.
@@ -92,13 +92,10 @@ function logout(args) {
92
92
  return;
93
93
  }
94
94
  const orgs = (0, credentials_1.listOrgs)(apiUrl);
95
+ // The pointer always lands on a stored key, so no active one means no keys.
95
96
  const active = orgs.find((org) => org.active);
96
97
  if (!active) {
97
- // A record from an older CLI names no organization: forget the whole address.
98
- if ((0, credentials_1.forgetAllOrgs)(apiUrl))
99
- (0, ui_1.ok)(`The key for ${apiUrl} is deleted`);
100
- else
101
- (0, ui_1.warn)(`There is no stored key for ${apiUrl}`);
98
+ (0, ui_1.warn)(`There is no stored key for ${apiUrl}`);
102
99
  return;
103
100
  }
104
101
  const { nextActive } = (0, credentials_1.forgetOrg)(apiUrl, active.organizationId);
@@ -18,13 +18,7 @@ function orgList() {
18
18
  const apiUrl = (0, config_1.apiUrlFor)((0, config_1.localConfig)());
19
19
  const orgs = (0, credentials_1.listOrgs)(apiUrl);
20
20
  if (orgs.length === 0) {
21
- if ((0, credentials_1.storedKeyInputs)(apiUrl).legacyToken) {
22
- (0, ui_1.note)(`One key is stored for ${apiUrl}, its organization is unknown`);
23
- (0, ui_1.note)((0, ui_1.dim)(' Sign in again to refresh it: xflow login'));
24
- }
25
- else {
26
- (0, ui_1.note)(`No keys for ${apiUrl}. Sign in: xflow login`);
27
- }
21
+ (0, ui_1.note)(`No keys for ${apiUrl}. Sign in: xflow login`);
28
22
  return;
29
23
  }
30
24
  (0, ui_1.table)(orgs.map((org) => [org.active ? '*' : ' ', org.name ?? '(unnamed)', org.organizationId]));
@@ -62,9 +56,6 @@ function orgSwitch(args) {
62
56
  const apiUrl = (0, config_1.apiUrlFor)((0, config_1.localConfig)());
63
57
  const orgs = (0, credentials_1.listOrgs)(apiUrl);
64
58
  if (orgs.length === 0) {
65
- if ((0, credentials_1.storedKeyInputs)(apiUrl).legacyToken) {
66
- throw new errors_1.CliError(`The stored key for ${apiUrl} names no organization`, 'Sign in again to refresh it: xflow login');
67
- }
68
59
  throw new errors_1.CliError(`No keys for ${apiUrl}`, 'Sign in: xflow login');
69
60
  }
70
61
  const byId = orgs.find((org) => org.organizationId === wanted);
@@ -47,56 +47,38 @@ function writeStore(store) {
47
47
  function isLegacy(value) {
48
48
  return !!value && typeof value.token === 'string';
49
49
  }
50
- /** The mirror for older CLIs: the key of the active organization, or nothing. */
51
- function syncLegacy(store, apiUrl) {
52
- const address = store.orgs?.[apiUrl];
53
- if (!address)
54
- return;
55
- const active = address.active ? address.keys[address.active] : undefined;
56
- if (active && address.active) {
57
- store[apiUrl] = { token: active.token, organizationId: address.active, savedAt: active.savedAt };
58
- address.mirror = address.active;
59
- }
60
- else {
61
- delete store[apiUrl];
62
- address.mirror = null;
63
- }
64
- }
65
50
  /**
66
- * Fold writes made by an older CLI back into the map. Its login rewrites only
67
- * the mirror record, its logout deletes only it; both are treated as what the
68
- * person meant: "sign in to this organization" and "forget the key I am using".
51
+ * Fold a record predating the map into it and erase the record. A record naming
52
+ * no organization cannot be placed in the map and is dropped: the answer is one
53
+ * xflow login, while leaving it would leave the key where the old proxy looks.
69
54
  * Returns true when the store changed and has to be written back.
70
55
  */
71
56
  function reconcile(store, apiUrl) {
72
57
  const legacy = store[apiUrl];
73
- let address = store.orgs?.[apiUrl];
74
58
  let changed = false;
75
- if (isLegacy(legacy) && legacy.organizationId) {
76
- const orgs = address ?? { active: null, keys: {} };
77
- const known = orgs.keys[legacy.organizationId];
78
- if (!known || known.token !== legacy.token || orgs.active !== legacy.organizationId) {
79
- orgs.keys[legacy.organizationId] = { ...known, token: legacy.token, savedAt: legacy.savedAt };
80
- orgs.active = legacy.organizationId;
59
+ if (isLegacy(legacy)) {
60
+ if (legacy.organizationId) {
61
+ const orgs = store.orgs?.[apiUrl] ?? { active: null, keys: {} };
62
+ // The map wins: it is what this CLI wrote last.
63
+ if (!orgs.keys[legacy.organizationId]) {
64
+ orgs.keys[legacy.organizationId] = { token: legacy.token, savedAt: legacy.savedAt };
65
+ }
81
66
  store.orgs = store.orgs ?? {};
82
67
  store.orgs[apiUrl] = orgs;
83
- address = orgs;
84
- changed = true;
85
- }
86
- }
87
- if (!isLegacy(legacy) && address?.active && address.keys[address.active]) {
88
- if (address.mirror === address.active) {
89
- // The mirror we wrote for this key is gone: an older CLI logged out.
90
- delete address.keys[address.active];
91
- address.active = null;
92
68
  }
93
- // Otherwise no mirror was ever written for this key (a hand-made or
94
- // restored file): deleting it would lose a key nobody asked to forget.
95
- // Either way the store changed: the mirror has to be rebuilt.
69
+ delete store[apiUrl];
96
70
  changed = true;
97
71
  }
98
- // The pointer must land on a stored key, or on nothing.
72
+ const address = store.orgs?.[apiUrl];
99
73
  if (address) {
74
+ // The field the erased record was tracked by. Left in place, a 0.24.0 still on
75
+ // the machine reads "the mirror I wrote is gone" as a logout and drops the key.
76
+ const stale = address;
77
+ if ('mirror' in stale) {
78
+ delete stale.mirror;
79
+ changed = true;
80
+ }
81
+ // The pointer must land on a stored key, or on nothing.
100
82
  const ids = Object.keys(address.keys);
101
83
  const active = address.active && address.keys[address.active] ? address.active : (ids[0] ?? null);
102
84
  if (address.active !== active) {
@@ -104,8 +86,6 @@ function reconcile(store, apiUrl) {
104
86
  changed = true;
105
87
  }
106
88
  }
107
- if (changed)
108
- syncLegacy(store, apiUrl);
109
89
  return changed;
110
90
  }
111
91
  /** Read the store with the drift of older CLIs healed, persisting the healing. */
@@ -116,7 +96,6 @@ function load(apiUrl) {
116
96
  return store;
117
97
  }
118
98
  function commit(store, apiUrl) {
119
- syncLegacy(store, apiUrl);
120
99
  const address = store.orgs?.[apiUrl];
121
100
  if (address && Object.keys(address.keys).length === 0) {
122
101
  delete store.orgs?.[apiUrl];
@@ -158,32 +137,38 @@ function saveOrgCredential(apiUrl, organizationId, token, name) {
158
137
  /** Cache the display name; a stale one is refreshed by any login or whoami. */
159
138
  function rememberOrgName(apiUrl, organizationId, name) {
160
139
  const store = readStore();
161
- reconcile(store, apiUrl);
140
+ let changed = reconcile(store, apiUrl);
162
141
  const entry = store.orgs?.[apiUrl]?.keys[organizationId];
163
- if (!entry || entry.name === name)
164
- return;
165
- entry.name = name;
166
- commit(store, apiUrl);
142
+ if (entry && entry.name !== name) {
143
+ entry.name = name;
144
+ changed = true;
145
+ }
146
+ if (changed)
147
+ commit(store, apiUrl);
167
148
  }
168
149
  function setActiveOrg(apiUrl, organizationId) {
169
150
  const store = readStore();
170
- reconcile(store, apiUrl);
151
+ let changed = reconcile(store, apiUrl);
171
152
  const orgs = store.orgs?.[apiUrl];
172
- if (!orgs?.keys[organizationId])
173
- return false;
174
- if (orgs.active !== organizationId) {
153
+ const known = !!orgs?.keys[organizationId];
154
+ if (orgs && known && orgs.active !== organizationId) {
175
155
  orgs.active = organizationId;
176
- commit(store, apiUrl);
156
+ changed = true;
177
157
  }
178
- return true;
158
+ if (changed)
159
+ commit(store, apiUrl);
160
+ return known;
179
161
  }
180
162
  /** Forget one organization; the pointer moves to any remaining one. */
181
163
  function forgetOrg(apiUrl, organizationId) {
182
164
  const store = readStore();
183
- reconcile(store, apiUrl);
165
+ const healed = reconcile(store, apiUrl);
184
166
  const orgs = store.orgs?.[apiUrl];
185
- if (!orgs?.keys[organizationId])
167
+ if (!orgs?.keys[organizationId]) {
168
+ if (healed)
169
+ commit(store, apiUrl);
186
170
  return { removed: false, nextActive: null };
171
+ }
187
172
  delete orgs.keys[organizationId];
188
173
  if (orgs.active === organizationId)
189
174
  orgs.active = Object.keys(orgs.keys)[0] ?? null;
@@ -199,8 +184,9 @@ function forgetOrg(apiUrl, organizationId) {
199
184
  /** Forget every key of the address; other addresses are untouched. */
200
185
  function forgetAllOrgs(apiUrl) {
201
186
  const store = readStore();
202
- reconcile(store, apiUrl);
203
- const had = !!store.orgs?.[apiUrl] || isLegacy(store[apiUrl]);
187
+ // A record erased by reconcile was a key as well: counted here, or the address
188
+ // looks empty and the erasure never reaches the file.
189
+ const had = reconcile(store, apiUrl) || !!store.orgs?.[apiUrl];
204
190
  if (!had)
205
191
  return false;
206
192
  if (store.orgs) {
@@ -236,16 +222,9 @@ function pickKey(inputs) {
236
222
  const active = activeId ? inputs.orgs?.keys[activeId] : undefined;
237
223
  if (active && activeId)
238
224
  return { token: active.token, source: 'active', organizationId: activeId };
239
- if (inputs.legacyToken)
240
- return { token: inputs.legacyToken, source: 'active', organizationId: null };
241
225
  return null;
242
226
  }
243
227
  /** The store-backed half of pickKey's inputs; the caller adds env and the folder binding. */
244
228
  function storedKeyInputs(apiUrl) {
245
- const store = load(apiUrl);
246
- const legacy = store[apiUrl];
247
- return {
248
- orgs: store.orgs?.[apiUrl] ?? null,
249
- legacyToken: isLegacy(legacy) ? legacy.token : null,
250
- };
229
+ return { orgs: load(apiUrl).orgs?.[apiUrl] ?? null };
251
230
  }
package/dist/help.js CHANGED
@@ -547,8 +547,10 @@ so the cloud functions answer here the way they do on the platform. The key is p
547
547
  as for every other command (${(0, ui_1.bold)('xflow help org')}): with several organizations signed
548
548
  in, the right one is found and the folder is bound to it.
549
549
 
550
- Use it instead of a bare ${(0, ui_1.bold)('npm run dev')}, which gets no key from here: its function
551
- calls fail, or go out under the key of whichever organization is active.
550
+ Use it instead of a bare ${(0, ui_1.bold)('npm run dev')}: that one is handed no key, so its calls to
551
+ cloud functions fail. In a project made before this command the failure reads "No access
552
+ key for the platform: run xflow login", and signing in will not help, because the key is
553
+ no longer left in a file for that dev server to pick up.
552
554
 
553
555
  The platform address comes from the CLI too, not from .env, so the key goes to the
554
556
  platform that issued it and nowhere else. It never reaches the browser: the page gets
package/dist/session.js CHANGED
@@ -84,7 +84,7 @@ async function findProjectOrg(apiUrl, projectId) {
84
84
  */
85
85
  async function connectProject(projectId, root, config) {
86
86
  const session = connect(config, { ignoreFolder: root === null });
87
- if (session.source !== 'active' || session.organizationId === null)
87
+ if (session.source !== 'active')
88
88
  return session;
89
89
  const orgs = (0, credentials_1.listOrgs)(session.apiUrl);
90
90
  if (orgs.length <= 1)
package/dist/version.js CHANGED
@@ -2,6 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DEFAULT_API_URL = exports.CLI_VERSION = void 0;
4
4
  /** Keep in sync with cli/package.json. */
5
- exports.CLI_VERSION = '0.24.0';
5
+ exports.CLI_VERSION = '0.25.0';
6
6
  /** Overridden by XFLOW_API_URL or the `api` field in xflow.json. */
7
7
  exports.DEFAULT_API_URL = 'https://app.getxflow.com';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getxflow/cli",
3
- "version": "0.24.0",
3
+ "version": "0.25.0",
4
4
  "description": "CLI for the XFlow platform: source sync, deployment and publishing of applications",
5
5
  "license": "UNLICENSED",
6
6
  "engines": {
@@ -22,7 +22,7 @@ contains the fix.
22
22
 
23
23
  ## Keeping these instructions current
24
24
 
25
- These instructions ship with xflow CLI 0.24.0, so the copy you are reading can be older than
25
+ These instructions ship with xflow CLI 0.25.0, so the copy you are reading can be older than
26
26
  the CLI answering your commands.
27
27
 
28
28
  When the CLI says this file does not match it, run `xflow skills --refresh` and then **read
@@ -602,7 +602,8 @@ own devtools.
602
602
 
603
603
  Run the app locally with `xflow dev`, not a bare `npm run dev`: it hands the dev server
604
604
  the key of the project's organization, which cloud functions need to answer, and their
605
- current addresses, including functions someone else deployed. The server answers
605
+ current addresses, including functions someone else deployed. A bare `npm run dev` gets
606
+ no key at all, so its function calls fail whatever the error says. The server answers
606
607
  `127.0.0.1` alone, because it holds that key: a phone on the same network cannot open it,
607
608
  and neither can anybody else on that network.
608
609