tileflow 0.1.0-alpha.19 → 0.1.0-alpha.20
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 +32 -0
- package/dist/index.js +273 -67
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -211,6 +211,30 @@ for CI, provide an appropriately scoped key through the secret environment varia
|
|
|
211
211
|
`TILEFLOW_API_KEY`. CI does not fall back to a saved personal login. Never put a key in a config,
|
|
212
212
|
browser bundle, copied prompt, or command-line argument.
|
|
213
213
|
|
|
214
|
+
If `login` finds the older local file containing an API key and API URL, it replaces that file only
|
|
215
|
+
after a new account login succeeds. The old API key is not converted into an account session.
|
|
216
|
+
Unrecognized local files are left untouched and stop login before authorization begins.
|
|
217
|
+
|
|
218
|
+
For commands accepting an API key, `--api-key` takes precedence over `TILEFLOW_API_KEY`;
|
|
219
|
+
only an absent key allows a saved personal session. An empty, invalid, expired or revoked key
|
|
220
|
+
fails without switching identities. `--api-url` takes precedence over `TILEFLOW_API_URL`.
|
|
221
|
+
This selection also applies to `models`, `tileset`, Icon Set commands and Map status. Keys
|
|
222
|
+
never appear in help output. When a personal account can manage several Teams, pass an
|
|
223
|
+
explicit `--team @team`; a Team key already selects its Team.
|
|
224
|
+
|
|
225
|
+
`login` always authorizes a personal session through the dashboard, even when an API key is
|
|
226
|
+
configured. `--no-browser` prints the URL without prompting to open it. Polling retries temporary
|
|
227
|
+
connection/server failures with bounded backoff and honors rate-limit waits until the authorization
|
|
228
|
+
expires. Denied or invalid authorizations stop immediately. A new session is validated and saved
|
|
229
|
+
before the previous session for that API origin is revoked. Failed validation or persistence
|
|
230
|
+
attempts to revoke the newly issued session and preserves the saved login. If cleanup cannot
|
|
231
|
+
reach the server, the CLI explains that the session must be revoked in Dashboard settings.
|
|
232
|
+
|
|
233
|
+
`logout` revokes only the saved personal session for the selected API origin. If server revocation
|
|
234
|
+
fails, local access is preserved so logout can be retried. API keys remain independent: an API key
|
|
235
|
+
in the environment still authorizes resource commands after logout. Personal session tokens stay
|
|
236
|
+
in the local auth file with restricted permissions; this CLI does not use an OS credential store.
|
|
237
|
+
|
|
214
238
|
```sh
|
|
215
239
|
npx tileflow login
|
|
216
240
|
npx tileflow whoami --json
|
|
@@ -226,6 +250,11 @@ with `--origin`, or `--any-website`. It prints the Map ID to use for later deplo
|
|
|
226
250
|
credential or the portable `id` in your config. CI always deploys an existing Map with a deploy key
|
|
227
251
|
for that destination. `logout` removes the saved personal session.
|
|
228
252
|
|
|
253
|
+
Deploy sends compact web cartography and data requirements, retaining concrete native artifacts.
|
|
254
|
+
The web budget is 2,000,000 UTF-8 bytes for the whole family; icon receipts and native payloads
|
|
255
|
+
have separate bounds.
|
|
256
|
+
Oversized maps fail before uploads with their size and limit; no automatic simplification occurs.
|
|
257
|
+
|
|
229
258
|
Deploy publishes the compiled concrete-theme family and managed icon assets; it does not ask the
|
|
230
259
|
server to compile TypeScript. Repeating an unchanged deployment reuses its version. Build writers
|
|
231
260
|
refuse to overwrite hosted manifests by default, and deploy refuses to replace a self-hosted
|
|
@@ -245,6 +274,9 @@ and uploads a managed dataset using Team data authority, not a map-only deploy k
|
|
|
245
274
|
uploads are resumable; a completed version becomes current at stable delivery URLs. Purge is
|
|
246
275
|
explicit, requires confirmation, and can be blocked by retained deployment dependencies.
|
|
247
276
|
|
|
277
|
+
Administratively disabled uploads return `TF_TILESET_UPLOAD_DISABLED` without retrying.
|
|
278
|
+
Temporary service failures retain bounded retries.
|
|
279
|
+
|
|
248
280
|
`inspect features` samples bounded vector-source features. `icons list` and `icons diff` inspect
|
|
249
281
|
prepared icon identities; they do not turn package directories into a separate hosted library.
|
|
250
282
|
Use the installed command's help for exact arguments and supported JSON output.
|
package/dist/index.js
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
tileflowMapIdSchema as tileflowMapIdSchema2
|
|
19
19
|
} from "@tileflow/core";
|
|
20
20
|
import {
|
|
21
|
+
compactTileflowDeploymentArtifact as compactTileflowDeploymentArtifact3,
|
|
21
22
|
createTileflowMapBuildManifest as createTileflowMapBuildManifest2
|
|
22
23
|
} from "@tileflow/core/build";
|
|
23
24
|
import {
|
|
@@ -101,6 +102,17 @@ var authConfigSchema = z.object({
|
|
|
101
102
|
}
|
|
102
103
|
}
|
|
103
104
|
});
|
|
105
|
+
var legacyAuthConfigSchema = z.object({
|
|
106
|
+
apiKey: z.string().min(1).max(4096),
|
|
107
|
+
apiUrl: z.url().max(2048).refine((value) => {
|
|
108
|
+
try {
|
|
109
|
+
normalizeApiOrigin(value);
|
|
110
|
+
return true;
|
|
111
|
+
} catch {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
}).strict();
|
|
104
116
|
function emptyAuthConfig() {
|
|
105
117
|
return Object.freeze({ sessions: Object.freeze({}), version: 2 });
|
|
106
118
|
}
|
|
@@ -119,12 +131,35 @@ async function readAuthFile(path = authConfigPath()) {
|
|
|
119
131
|
return { kind: "invalid" };
|
|
120
132
|
}
|
|
121
133
|
const config = authConfigSchema.safeParse(parsed);
|
|
122
|
-
|
|
134
|
+
if (config.success) return { config: freezeConfig(config.data), kind: "sessions" };
|
|
135
|
+
return legacyAuthConfigSchema.safeParse(parsed).success ? { kind: "legacy" } : { kind: "invalid" };
|
|
136
|
+
}
|
|
137
|
+
async function prepareAccountLogin(path = authConfigPath()) {
|
|
138
|
+
const state = await readAuthFile(path);
|
|
139
|
+
if (state.kind === "invalid") {
|
|
140
|
+
throw new Error("Tileflow auth state is invalid; no changes were made.");
|
|
141
|
+
}
|
|
142
|
+
const directory = dirname(path);
|
|
143
|
+
const probePath = `${path}.tmp-${process.pid}-${randomBytes(8).toString("hex")}`;
|
|
144
|
+
await mkdir(directory, { mode: 448, recursive: true });
|
|
145
|
+
const handle = await open(probePath, "wx", 384);
|
|
146
|
+
try {
|
|
147
|
+
await handle.sync();
|
|
148
|
+
} finally {
|
|
149
|
+
await handle.close();
|
|
150
|
+
await unlink(probePath);
|
|
151
|
+
}
|
|
152
|
+
return state;
|
|
123
153
|
}
|
|
124
154
|
async function loadAuthConfig(path = authConfigPath()) {
|
|
125
155
|
const state = await readAuthFile(path);
|
|
126
156
|
if (state.kind === "empty") return emptyAuthConfig();
|
|
127
157
|
if (state.kind === "sessions") return state.config;
|
|
158
|
+
if (state.kind === "legacy") {
|
|
159
|
+
throw new Error(
|
|
160
|
+
"Tileflow CLI login uses an older format. Run tileflow login to sign in again."
|
|
161
|
+
);
|
|
162
|
+
}
|
|
128
163
|
throw new Error("Tileflow auth state is invalid; no changes were made.");
|
|
129
164
|
}
|
|
130
165
|
async function installAccountSession(session, path = authConfigPath()) {
|
|
@@ -4129,6 +4164,7 @@ function printFeatureInspectError(error) {
|
|
|
4129
4164
|
|
|
4130
4165
|
// src/hosted-client.ts
|
|
4131
4166
|
import { serializeTileflowFontBundleManifest } from "@tileflow/core";
|
|
4167
|
+
import { compactTileflowDeploymentArtifact, tileflowDeploymentWebBytes } from "@tileflow/core/build";
|
|
4132
4168
|
|
|
4133
4169
|
// src/hosted-response.ts
|
|
4134
4170
|
import { z as z2 } from "zod";
|
|
@@ -4145,6 +4181,7 @@ var hostedStyleDeploymentResponseSchema = z2.object({
|
|
|
4145
4181
|
changed: z2.boolean().optional(),
|
|
4146
4182
|
deploymentId: safeIdentifierSchema.optional(),
|
|
4147
4183
|
mapId: z2.string().regex(/^map_[A-Za-z0-9_-]{16}$/u),
|
|
4184
|
+
familyUrl: publicHttpUrlSchema.optional(),
|
|
4148
4185
|
themes: z2.record(
|
|
4149
4186
|
safeIdentifierSchema,
|
|
4150
4187
|
z2.object({
|
|
@@ -4238,17 +4275,23 @@ function apiProfileCanTargetMap(profile, mapId) {
|
|
|
4238
4275
|
}
|
|
4239
4276
|
async function requestHostedJson(apiUrl, path, init = {}, options = {}) {
|
|
4240
4277
|
return withHostedResponse(apiUrl, path, init, options, async (response) => {
|
|
4278
|
+
const retryAfter = response.headers.get("retry-after");
|
|
4279
|
+
const retryAfterMs = retryAfter === null ? void 0 : /^\d+$/u.test(retryAfter) ? Number(retryAfter) * 1e3 : Math.max(0, Date.parse(retryAfter) - Date.now());
|
|
4280
|
+
const metadata = {
|
|
4281
|
+
ok: response.ok,
|
|
4282
|
+
status: response.status,
|
|
4283
|
+
...Number.isFinite(retryAfterMs) ? { retryAfterMs } : {}
|
|
4284
|
+
};
|
|
4241
4285
|
const source = await readBoundedResponseText(response, "Hosted API");
|
|
4242
|
-
if (!source) return { body: null, json: true,
|
|
4286
|
+
if (!source) return { body: null, json: true, ...metadata };
|
|
4243
4287
|
try {
|
|
4244
4288
|
return {
|
|
4245
4289
|
body: JSON.parse(source),
|
|
4246
4290
|
json: true,
|
|
4247
|
-
|
|
4248
|
-
status: response.status
|
|
4291
|
+
...metadata
|
|
4249
4292
|
};
|
|
4250
4293
|
} catch {
|
|
4251
|
-
return { body: null, json: false,
|
|
4294
|
+
return { body: null, json: false, ...metadata };
|
|
4252
4295
|
}
|
|
4253
4296
|
});
|
|
4254
4297
|
}
|
|
@@ -4272,24 +4315,57 @@ async function startDeviceAuthorization(appUrl, body, options = {}) {
|
|
|
4272
4315
|
return response.body;
|
|
4273
4316
|
}
|
|
4274
4317
|
async function pollDeviceToken(appUrl, authorization, input, options = {}) {
|
|
4275
|
-
const
|
|
4276
|
-
const
|
|
4318
|
+
const now = input.now ?? Date.now;
|
|
4319
|
+
const expiresAt = input.expiresAt ?? now() + authorization.expiresIn * 1e3;
|
|
4320
|
+
let intervalMs = Math.max(authorization.interval, 1) * 1e3;
|
|
4321
|
+
let delayMs = intervalMs;
|
|
4277
4322
|
const wait = input.sleep ?? ((milliseconds) => new Promise((resolve13) => setTimeout(resolve13, milliseconds)));
|
|
4278
|
-
while (
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4323
|
+
while (now() < expiresAt) {
|
|
4324
|
+
options.signal?.throwIfAborted();
|
|
4325
|
+
await wait(Math.min(delayMs, expiresAt - now()));
|
|
4326
|
+
options.signal?.throwIfAborted();
|
|
4327
|
+
if (now() >= expiresAt) break;
|
|
4328
|
+
let response;
|
|
4329
|
+
try {
|
|
4330
|
+
response = await requestHostedJson(
|
|
4331
|
+
appUrl,
|
|
4332
|
+
"/api/cli/device/token",
|
|
4333
|
+
jsonRequest("POST", {
|
|
4334
|
+
codeVerifier: input.codeVerifier,
|
|
4335
|
+
deviceCode: authorization.deviceCode
|
|
4336
|
+
}),
|
|
4337
|
+
{
|
|
4338
|
+
...options,
|
|
4339
|
+
timeoutMs: Math.min(
|
|
4340
|
+
options.timeoutMs ?? defaultHostedRequestTimeoutMs,
|
|
4341
|
+
expiresAt - now()
|
|
4342
|
+
)
|
|
4343
|
+
}
|
|
4344
|
+
);
|
|
4345
|
+
} catch (error2) {
|
|
4346
|
+
options.signal?.throwIfAborted();
|
|
4347
|
+
if (!(error2 instanceof TypeError) && !(error2 instanceof Error && error2.name === "TimeoutError"))
|
|
4348
|
+
throw error2;
|
|
4349
|
+
delayMs = Math.min(Math.max(delayMs * 2, intervalMs), 6e4);
|
|
4350
|
+
continue;
|
|
4351
|
+
}
|
|
4289
4352
|
if (response.ok && isDeviceToken(response.body)) return response.body;
|
|
4290
4353
|
const error = asRecord(response.body).error;
|
|
4291
|
-
if (error === "authorization_pending") continue;
|
|
4292
4354
|
if (error === "access_denied") throw new Error("CLI authorization was denied.");
|
|
4355
|
+
if (error === "expired_token") break;
|
|
4356
|
+
if (error === "slow_down" || response.status === 429) {
|
|
4357
|
+
intervalMs += 5e3;
|
|
4358
|
+
delayMs = Math.max(intervalMs, response.retryAfterMs ?? 0);
|
|
4359
|
+
continue;
|
|
4360
|
+
}
|
|
4361
|
+
if (error === "authorization_pending") {
|
|
4362
|
+
delayMs = intervalMs;
|
|
4363
|
+
continue;
|
|
4364
|
+
}
|
|
4365
|
+
if ([408, 500, 502, 503, 504].includes(response.status)) {
|
|
4366
|
+
delayMs = Math.max(Math.min(delayMs * 2, 6e4), response.retryAfterMs ?? 0);
|
|
4367
|
+
continue;
|
|
4368
|
+
}
|
|
4293
4369
|
throw new Error(`CLI authorization failed (${response.status}).`);
|
|
4294
4370
|
}
|
|
4295
4371
|
throw new Error("CLI authorization expired. Run `tileflow login` again.");
|
|
@@ -4530,11 +4606,14 @@ function isMapSetupTeam(value) {
|
|
|
4530
4606
|
);
|
|
4531
4607
|
}
|
|
4532
4608
|
async function publishHostedStyle(api, body, responseLabel, options = {}) {
|
|
4609
|
+
const input = body;
|
|
4610
|
+
const compactBody = { ...input, artifact: compactTileflowDeploymentArtifact(input.artifact) };
|
|
4611
|
+
assertHostedStyleBudget(compactBody);
|
|
4533
4612
|
const response = await requestHostedJson(
|
|
4534
4613
|
api.apiUrl,
|
|
4535
4614
|
"/v1/styles",
|
|
4536
4615
|
{
|
|
4537
|
-
...jsonRequest("POST",
|
|
4616
|
+
...jsonRequest("POST", compactBody),
|
|
4538
4617
|
headers: { ...authorizationHeaders(api.apiKey, api.mapId), "Content-Type": "application/json" }
|
|
4539
4618
|
},
|
|
4540
4619
|
options
|
|
@@ -4545,6 +4624,15 @@ async function publishHostedStyle(api, body, responseLabel, options = {}) {
|
|
|
4545
4624
|
value: parseResponse(response, hostedStyleDeploymentResponseSchema, responseLabel)
|
|
4546
4625
|
};
|
|
4547
4626
|
}
|
|
4627
|
+
function assertHostedStyleBudget(body) {
|
|
4628
|
+
const observedBytes = tileflowDeploymentWebBytes(body);
|
|
4629
|
+
const limitBytes = 2e6;
|
|
4630
|
+
if (observedBytes > limitBytes)
|
|
4631
|
+
throw Object.assign(
|
|
4632
|
+
new Error(`Style content is ${observedBytes} bytes; the limit is ${limitBytes} bytes.`),
|
|
4633
|
+
{ code: "STYLE_DEPLOYMENT_TOO_LARGE", scope: "web", observedBytes, limitBytes }
|
|
4634
|
+
);
|
|
4635
|
+
}
|
|
4548
4636
|
async function uploadHostedIconPackage(api, iconPackage, options = {}) {
|
|
4549
4637
|
const formData = new FormData();
|
|
4550
4638
|
const fieldNames = {
|
|
@@ -4792,6 +4880,7 @@ import {
|
|
|
4792
4880
|
serializeCanonicalJson as serializeCanonicalJson3
|
|
4793
4881
|
} from "@tileflow/core";
|
|
4794
4882
|
import {
|
|
4883
|
+
compactTileflowDeploymentArtifact as compactTileflowDeploymentArtifact2,
|
|
4795
4884
|
createTileflowMapBuildManifest,
|
|
4796
4885
|
parseTileflowRendererDeploymentArtifact,
|
|
4797
4886
|
tileflowRendererDeploymentResponseSchema
|
|
@@ -4978,7 +5067,11 @@ async function prepareNativeHostedDeploy(options) {
|
|
|
4978
5067
|
])
|
|
4979
5068
|
)
|
|
4980
5069
|
};
|
|
4981
|
-
const styles = createTileflowStyles(project, {
|
|
5070
|
+
const styles = createTileflowStyles(project, {
|
|
5071
|
+
apiBaseUrl: apiOrigin,
|
|
5072
|
+
mapAssets,
|
|
5073
|
+
runtimeColors: true
|
|
5074
|
+
});
|
|
4982
5075
|
const local = await prepareTileflowLocalTilesets(project, styles, {
|
|
4983
5076
|
assetBaseUrl: apiOrigin,
|
|
4984
5077
|
baseDirectory,
|
|
@@ -5074,6 +5167,21 @@ async function runHostedNativeDeploy(options, ports) {
|
|
|
5074
5167
|
const preflight = await parseTileflowRendererDeploymentArtifact(prepared.artifact);
|
|
5075
5168
|
if (preflight.mapId !== prepared.mapName)
|
|
5076
5169
|
throw failure("The prepared map does not match its artifact.");
|
|
5170
|
+
const requestBody = (artifact2) => ({
|
|
5171
|
+
artifact: compactTileflowDeploymentArtifact2(artifact2),
|
|
5172
|
+
environment: prepared.mapName,
|
|
5173
|
+
managedMapId: mapId,
|
|
5174
|
+
usageMode: "session",
|
|
5175
|
+
source: ports.source,
|
|
5176
|
+
...prepared.iconBinding ? {
|
|
5177
|
+
iconPackage: {
|
|
5178
|
+
contentHash: prepared.iconBinding.packageHash,
|
|
5179
|
+
label: prepared.iconBinding.label
|
|
5180
|
+
}
|
|
5181
|
+
} : {},
|
|
5182
|
+
...prepared.iconComposition ? { iconComposition: prepared.iconComposition } : {}
|
|
5183
|
+
});
|
|
5184
|
+
assertHostedStyleBudget(requestBody(preflight));
|
|
5077
5185
|
const existing = await ports.loadManifest(options.manifest, {
|
|
5078
5186
|
overwriteSelfHosted: options.overwriteSelfHostedManifest === true
|
|
5079
5187
|
});
|
|
@@ -5109,6 +5217,8 @@ async function runHostedNativeDeploy(options, ports) {
|
|
|
5109
5217
|
if (prepared.iconComposition && prepared.iconComposition.packageHash !== iconPackage?.contentHash)
|
|
5110
5218
|
throw failure("Icon composition does not match the confirmed effective package.");
|
|
5111
5219
|
let response;
|
|
5220
|
+
const body = requestBody(artifact);
|
|
5221
|
+
assertHostedStyleBudget(body);
|
|
5112
5222
|
try {
|
|
5113
5223
|
response = await requestHostedJson(
|
|
5114
5224
|
apiOrigin,
|
|
@@ -5120,20 +5230,7 @@ async function runHostedNativeDeploy(options, ports) {
|
|
|
5120
5230
|
"X-Tileflow-Map-Id": mapId,
|
|
5121
5231
|
"Content-Type": "application/json"
|
|
5122
5232
|
},
|
|
5123
|
-
body: serializeCanonicalJson3(
|
|
5124
|
-
artifact,
|
|
5125
|
-
environment: prepared.mapName,
|
|
5126
|
-
managedMapId: mapId,
|
|
5127
|
-
usageMode: "session",
|
|
5128
|
-
source: ports.source,
|
|
5129
|
-
...iconPackage && prepared.iconBinding ? {
|
|
5130
|
-
iconPackage: {
|
|
5131
|
-
contentHash: iconPackage.contentHash,
|
|
5132
|
-
label: prepared.iconBinding.label
|
|
5133
|
-
}
|
|
5134
|
-
} : {},
|
|
5135
|
-
...prepared.iconComposition ? { iconComposition: prepared.iconComposition } : {}
|
|
5136
|
-
})
|
|
5233
|
+
body: serializeCanonicalJson3(body)
|
|
5137
5234
|
},
|
|
5138
5235
|
ports.request
|
|
5139
5236
|
);
|
|
@@ -5161,13 +5258,14 @@ async function runHostedNativeDeploy(options, ports) {
|
|
|
5161
5258
|
const build = artifact.renderers.web.buildManifest.maps[artifact.mapId];
|
|
5162
5259
|
const manifest = parseTileflowRuntimeManifest({
|
|
5163
5260
|
apiUrl: apiOrigin,
|
|
5164
|
-
version: build.timeOfDay || Object.values(existing?.maps ?? {}).some((map) => map.timeOfDay) ? 2 : 1,
|
|
5261
|
+
version: result.renderers.web.familyUrl || Object.values(existing?.maps ?? {}).some((map) => map.familyUrl) ? 3 : build.timeOfDay || Object.values(existing?.maps ?? {}).some((map) => map.timeOfDay) ? 2 : 1,
|
|
5165
5262
|
maps: {
|
|
5166
5263
|
...existing?.maps ?? {},
|
|
5167
5264
|
[prepared.mapName]: {
|
|
5168
5265
|
defaultTheme: build.defaultTheme,
|
|
5169
5266
|
environment: prepared.mapName,
|
|
5170
5267
|
mapId,
|
|
5268
|
+
...result.renderers.web.familyUrl ? { familyUrl: result.renderers.web.familyUrl } : {},
|
|
5171
5269
|
...build.systemThemes ? { systemThemes: build.systemThemes } : {},
|
|
5172
5270
|
...build.timeOfDay ? { timeOfDay: build.timeOfDay } : {},
|
|
5173
5271
|
...artifact.view ? { view: artifact.view } : {},
|
|
@@ -5204,6 +5302,21 @@ async function runHostedNativeDeploy(options, ports) {
|
|
|
5204
5302
|
}
|
|
5205
5303
|
}
|
|
5206
5304
|
|
|
5305
|
+
// src/hosted-options.ts
|
|
5306
|
+
import { Option } from "commander";
|
|
5307
|
+
function hostedApiKeyOption(description = "Tileflow API key") {
|
|
5308
|
+
return new Option("--api-key <key>", description).env("TILEFLOW_API_KEY");
|
|
5309
|
+
}
|
|
5310
|
+
function hostedApiUrlOption() {
|
|
5311
|
+
return new Option("--api-url <url>", "Tileflow API URL").env("TILEFLOW_API_URL");
|
|
5312
|
+
}
|
|
5313
|
+
function isHostedApiKey(value) {
|
|
5314
|
+
return /^tf_live_[0-9a-f]{48}$/u.test(value);
|
|
5315
|
+
}
|
|
5316
|
+
function allowsStoredAccountSession(env = process.env) {
|
|
5317
|
+
return !env.CI && env.GITHUB_ACTIONS !== "true" && env.GITLAB_CI !== "true";
|
|
5318
|
+
}
|
|
5319
|
+
|
|
5207
5320
|
// src/icon-diff-command.ts
|
|
5208
5321
|
import { dirname as dirname8, resolve as resolve7 } from "path";
|
|
5209
5322
|
import { z as z3 } from "zod";
|
|
@@ -5777,7 +5890,7 @@ var tileflowIconDiffDocumentSchema = z3.object({
|
|
|
5777
5890
|
hasChanges: z3.boolean()
|
|
5778
5891
|
}).strict();
|
|
5779
5892
|
function registerIconDiffCommand(icons, dependencies) {
|
|
5780
|
-
icons.command("diff").description("Preview a local icon package against an active hosted revision").requiredOption("--against <environment>", "named map environment to compare").option("-c, --config <path>", "config path", dependencies.defaultConfigPath).
|
|
5893
|
+
icons.command("diff").description("Preview a local icon package against an active hosted revision").requiredOption("--against <environment>", "named map environment to compare").option("-c, --config <path>", "config path", dependencies.defaultConfigPath).addOption(hostedApiUrlOption()).addOption(hostedApiKeyOption("Tileflow API key")).requiredOption("--map-id <id>", "managed Map destination").option("--json", "print deterministic schema-version-1 JSON").option("--cache-dir <path>", "verified Icon Set artifact cache root").option("--offline", "fail a locked Icon Set cache miss instead of hydrating it").option("--report <path>", "write a self-contained HTML visual report").option("--open", "open an explicitly requested report").option("--force", "replace a different explicitly requested report").action(async (options) => {
|
|
5781
5894
|
try {
|
|
5782
5895
|
await runIconDiff(options, dependencies);
|
|
5783
5896
|
} catch (error) {
|
|
@@ -6134,8 +6247,8 @@ async function resolveTeamAuthority(options, scopes, dependencies) {
|
|
|
6134
6247
|
emitFailure(options.json, "invalid_api_url", safeMessage(error));
|
|
6135
6248
|
return null;
|
|
6136
6249
|
}
|
|
6137
|
-
if (options.apiKey) {
|
|
6138
|
-
if (
|
|
6250
|
+
if (options.apiKey !== void 0) {
|
|
6251
|
+
if (!isHostedApiKey(options.apiKey)) {
|
|
6139
6252
|
emitFailure(options.json, "invalid_team_data_key", "Team data API key is invalid.");
|
|
6140
6253
|
return null;
|
|
6141
6254
|
}
|
|
@@ -6149,6 +6262,14 @@ async function resolveTeamAuthority(options, scopes, dependencies) {
|
|
|
6149
6262
|
}
|
|
6150
6263
|
return { apiOrigin, credential: options.apiKey, team: null };
|
|
6151
6264
|
}
|
|
6265
|
+
if (!allowsStoredAccountSession()) {
|
|
6266
|
+
emitFailure(
|
|
6267
|
+
options.json,
|
|
6268
|
+
"api_key_required",
|
|
6269
|
+
"CI requires TILEFLOW_API_KEY or --api-key. Saved personal logins are not used in CI."
|
|
6270
|
+
);
|
|
6271
|
+
return null;
|
|
6272
|
+
}
|
|
6152
6273
|
let config;
|
|
6153
6274
|
try {
|
|
6154
6275
|
config = await dependencies.loadAuthConfig();
|
|
@@ -6589,7 +6710,7 @@ function registerIconSetCommands(program2, dependencies) {
|
|
|
6589
6710
|
}
|
|
6590
6711
|
}
|
|
6591
6712
|
function withTeamOptions(command2) {
|
|
6592
|
-
return command2.option("--team <team>", "target Team as @team").
|
|
6713
|
+
return command2.option("--team <team>", "target Team as @team").addOption(hostedApiUrlOption()).addOption(hostedApiKeyOption("Team data API key")).option("--json", "print deterministic schema-version-1 JSON");
|
|
6593
6714
|
}
|
|
6594
6715
|
async function readAllPages(authority, path, query, schema, select, identify) {
|
|
6595
6716
|
const items = [];
|
|
@@ -6949,7 +7070,7 @@ function registerIconLockCommands(program2, dependencies) {
|
|
|
6949
7070
|
}
|
|
6950
7071
|
}
|
|
6951
7072
|
function withLockOptions(command2, dependencies) {
|
|
6952
|
-
return command2.option("-c, --config <path>", "config path", dependencies.defaultConfigPath).option("--team <team>", "target Team as @team").
|
|
7073
|
+
return command2.option("-c, --config <path>", "config path", dependencies.defaultConfigPath).option("--team <team>", "target Team as @team").addOption(hostedApiUrlOption()).addOption(hostedApiKeyOption("Team data API key")).option("--json", "print deterministic schema-version-1 JSON");
|
|
6953
7074
|
}
|
|
6954
7075
|
function collectDeclaredReferences(project) {
|
|
6955
7076
|
const references = [];
|
|
@@ -7532,7 +7653,7 @@ function registerModelCommands(program2, dependencies) {
|
|
|
7532
7653
|
});
|
|
7533
7654
|
}
|
|
7534
7655
|
function withTeamOptions2(command2) {
|
|
7535
|
-
return command2.option("--team <team>", "Team, as @team, when the account has several").
|
|
7656
|
+
return command2.option("--team <team>", "Team, as @team, when the account has several").addOption(hostedApiKeyOption("Team data API key with the 3D models permission")).addOption(hostedApiUrlOption()).option("--json", "machine-readable output");
|
|
7536
7657
|
}
|
|
7537
7658
|
async function readDetail(authority, name, json2) {
|
|
7538
7659
|
const response = await authorizedTeamRequest(
|
|
@@ -8439,7 +8560,9 @@ function createHostedTeamTilesetUploadTransport(input) {
|
|
|
8439
8560
|
}
|
|
8440
8561
|
}
|
|
8441
8562
|
if (response?.ok) return response.body;
|
|
8442
|
-
if (response && !isRetryableStatus(response.status))
|
|
8563
|
+
if (response && (asRecord3(response.body).code === "TF_TILESET_UPLOAD_DISABLED" || !isRetryableStatus(response.status))) {
|
|
8564
|
+
throw responseError(response);
|
|
8565
|
+
}
|
|
8443
8566
|
if (attempt === maximumControlAttempts) {
|
|
8444
8567
|
if (response) throw responseError(response);
|
|
8445
8568
|
break;
|
|
@@ -8707,6 +8830,7 @@ function invalidRemoteResponse() {
|
|
|
8707
8830
|
);
|
|
8708
8831
|
}
|
|
8709
8832
|
function failureMessage(code) {
|
|
8833
|
+
if (code === "TF_TILESET_UPLOAD_DISABLED") return "New tileset uploads are disabled.";
|
|
8710
8834
|
if (code === "TF_TILESET_SCHEMA_INCOMPATIBLE") {
|
|
8711
8835
|
return "Tileset schema is incompatible with retained Map deployments.";
|
|
8712
8836
|
}
|
|
@@ -8863,7 +8987,7 @@ function registerTilesetInspectCommand(parent, name = "inspect") {
|
|
|
8863
8987
|
function registerTilesetCommands(program2, dependencies) {
|
|
8864
8988
|
const tileset = program2.command("tileset").description("Inspect and manage geospatial tilesets");
|
|
8865
8989
|
registerTilesetInspectCommand(tileset);
|
|
8866
|
-
tileset.command("publish").description("Register and publish one PMTiles archive to a Team").argument("<archive>", "repository-local PMTiles archive").requiredOption("--id <id>", "Team-local logical tileset ID").option("--name <name>", "display name").option("--attribution <text>", "data attribution").option("--team <team>", "target Team as @team").
|
|
8990
|
+
tileset.command("publish").description("Register and publish one PMTiles archive to a Team").argument("<archive>", "repository-local PMTiles archive").requiredOption("--id <id>", "Team-local logical tileset ID").option("--name <name>", "display name").option("--attribution <text>", "data attribution").option("--team <team>", "target Team as @team").addOption(hostedApiUrlOption()).addOption(hostedApiKeyOption("Team data API key")).option("--json", "print deterministic schema-version-2 JSON").action(
|
|
8867
8991
|
async (archive, options) => {
|
|
8868
8992
|
let uploadFile = null;
|
|
8869
8993
|
try {
|
|
@@ -8954,7 +9078,7 @@ function registerTilesetCommands(program2, dependencies) {
|
|
|
8954
9078
|
}
|
|
8955
9079
|
}
|
|
8956
9080
|
);
|
|
8957
|
-
tileset.command("list").description("List tilesets owned by one Team").option("--team <team>", "target Team as @team").
|
|
9081
|
+
tileset.command("list").description("List tilesets owned by one Team").option("--team <team>", "target Team as @team").addOption(hostedApiUrlOption()).addOption(hostedApiKeyOption("Team data API key")).option("--json", "print deterministic schema-version-1 JSON").action(async (options) => {
|
|
8958
9082
|
try {
|
|
8959
9083
|
const authority = await resolveTeamAuthority(options, ["tilesets:read"], dependencies);
|
|
8960
9084
|
if (!authority) return;
|
|
@@ -8969,7 +9093,7 @@ function registerTilesetCommands(program2, dependencies) {
|
|
|
8969
9093
|
emitFailure(options.json, "tileset_list_failed", "Tileset list failed.");
|
|
8970
9094
|
}
|
|
8971
9095
|
});
|
|
8972
|
-
tileset.command("status").description("Show one Team tileset").argument("<id>", "Team-local logical ID or opaque resource ID").option("--team <team>", "target Team as @team").
|
|
9096
|
+
tileset.command("status").description("Show one Team tileset").argument("<id>", "Team-local logical ID or opaque resource ID").option("--team <team>", "target Team as @team").addOption(hostedApiUrlOption()).addOption(hostedApiKeyOption("Team data API key")).option("--json", "print deterministic schema-version-1 JSON").action(async (id, options) => {
|
|
8973
9097
|
try {
|
|
8974
9098
|
if (!isTilesetId(id))
|
|
8975
9099
|
return emitFailure(options.json, "invalid_tileset_id", "Tileset ID is invalid.");
|
|
@@ -8998,7 +9122,7 @@ function registerTilesetCommands(program2, dependencies) {
|
|
|
8998
9122
|
emitFailure(options.json, "tileset_status_failed", "Tileset status failed.");
|
|
8999
9123
|
}
|
|
9000
9124
|
});
|
|
9001
|
-
tileset.command("purge").description("Permanently purge one unreferenced Team tileset").argument("<id>", "Team-local logical ID or opaque resource ID").requiredOption("--confirm <id>", "repeat the exact tileset ID").option("--team <team>", "target Team as @team").
|
|
9125
|
+
tileset.command("purge").description("Permanently purge one unreferenced Team tileset").argument("<id>", "Team-local logical ID or opaque resource ID").requiredOption("--confirm <id>", "repeat the exact tileset ID").option("--team <team>", "target Team as @team").addOption(hostedApiUrlOption()).addOption(hostedApiKeyOption("Team data API key")).option("--json", "print deterministic schema-version-1 JSON").action(async (id, options) => {
|
|
9002
9126
|
try {
|
|
9003
9127
|
if (!isTilesetId(id) || options.confirm !== id) {
|
|
9004
9128
|
return emitFailure(
|
|
@@ -10014,7 +10138,7 @@ program.command("init").description("Create a starter tileflow.config.ts").optio
|
|
|
10014
10138
|
`Validate the config with ${command("tileflow validate")}.`
|
|
10015
10139
|
]);
|
|
10016
10140
|
});
|
|
10017
|
-
program.command("login").description("Authorize this machine for your Tileflow account").
|
|
10141
|
+
program.command("login").description("Authorize this machine for your Tileflow account").addOption(hostedApiUrlOption()).option(
|
|
10018
10142
|
"--app-url <url>",
|
|
10019
10143
|
"Tileflow Dashboard URL",
|
|
10020
10144
|
process.env.TILEFLOW_APP_URL ?? defaultAppUrl
|
|
@@ -10025,7 +10149,7 @@ program.command("login").description("Authorize this machine for your Tileflow a
|
|
|
10025
10149
|
await loginWithDeviceFlow({
|
|
10026
10150
|
expectedApiUrl: apiUrl,
|
|
10027
10151
|
appUrl,
|
|
10028
|
-
noBrowser:
|
|
10152
|
+
noBrowser: options.browser === false
|
|
10029
10153
|
});
|
|
10030
10154
|
} catch (error) {
|
|
10031
10155
|
const message = error instanceof Error ? error.message : "Login failed.";
|
|
@@ -10033,7 +10157,7 @@ program.command("login").description("Authorize this machine for your Tileflow a
|
|
|
10033
10157
|
process.exitCode = 1;
|
|
10034
10158
|
}
|
|
10035
10159
|
});
|
|
10036
|
-
program.command("logout").description("Revoke this origin's Tileflow account session").
|
|
10160
|
+
program.command("logout").description("Revoke this origin's Tileflow account session").addOption(hostedApiUrlOption()).action(async (options) => {
|
|
10037
10161
|
const apiUrl = normalizeApiOrigin(options.apiUrl ?? defaultApiUrl);
|
|
10038
10162
|
const config = await loadAuthConfig().catch((error) => {
|
|
10039
10163
|
logError(safeAuthError(error));
|
|
@@ -10061,7 +10185,7 @@ program.command("logout").description("Revoke this origin's Tileflow account ses
|
|
|
10061
10185
|
}
|
|
10062
10186
|
logSuccess("Signed out of this Tileflow origin.");
|
|
10063
10187
|
});
|
|
10064
|
-
program.command("whoami").description("Show the authenticated Tileflow account").
|
|
10188
|
+
program.command("whoami").description("Show the authenticated Tileflow account").addOption(hostedApiUrlOption()).option("--json", "print deterministic schema-version-1 JSON").action(async (options) => {
|
|
10065
10189
|
const config = await loadAuthConfig().catch((error) => {
|
|
10066
10190
|
logAuthCommandError(options.json, safeAuthError(error));
|
|
10067
10191
|
return null;
|
|
@@ -10580,7 +10704,7 @@ addMapSetupOptions(
|
|
|
10580
10704
|
program.command("deploy").description(
|
|
10581
10705
|
"Deploy maps to Tileflow and write the frontend manifest; without --map-id, create the Map first"
|
|
10582
10706
|
)
|
|
10583
|
-
).option("-c, --config <path>", "config path", defaultConfigPath).option("--manifest <path>", "manifest path written for frontend bundlers", defaultManifestPath).
|
|
10707
|
+
).option("-c, --config <path>", "config path", defaultConfigPath).option("--manifest <path>", "manifest path written for frontend bundlers", defaultManifestPath).addOption(hostedApiUrlOption()).addOption(hostedApiKeyOption("Tileflow API key")).option("--map-id <id>", "managed Map destination; omit it to create a new Map").option("--with-native", "publish web and native-v1 together for the selected managed Map").option("--map <name>", "configured map to connect when the repository contains multiple maps").option(
|
|
10584
10708
|
"--overwrite-self-hosted-manifest",
|
|
10585
10709
|
"explicitly replace an existing self-hosted manifest at --manifest"
|
|
10586
10710
|
).option("--cache-dir <path>", "verified Icon Set artifact cache root").option("--offline", "fail a locked Icon Set cache miss instead of hydrating it").action(
|
|
@@ -10653,6 +10777,7 @@ addMapSetupOptions(
|
|
|
10653
10777
|
mapAssetsWithBuildingModels(modelAssets, { hosted: true })
|
|
10654
10778
|
);
|
|
10655
10779
|
const compiledPreflightStyles = createTileflowStyles2(deploymentProject, {
|
|
10780
|
+
runtimeColors: true,
|
|
10656
10781
|
apiBaseUrl: apiUrl,
|
|
10657
10782
|
mapAssets: preflightMapAssets
|
|
10658
10783
|
});
|
|
@@ -10684,6 +10809,56 @@ addMapSetupOptions(
|
|
|
10684
10809
|
process.exitCode = 1;
|
|
10685
10810
|
return;
|
|
10686
10811
|
}
|
|
10812
|
+
for (const mapName of mapNames) {
|
|
10813
|
+
const map = deploymentProject.maps[mapName];
|
|
10814
|
+
const family = prepareTileflowHostedThemeFamily(
|
|
10815
|
+
mapName,
|
|
10816
|
+
map,
|
|
10817
|
+
preflightFonts.styles[mapName]
|
|
10818
|
+
);
|
|
10819
|
+
const binding = bindingsByMap.get(mapName);
|
|
10820
|
+
const iconPackage = binding && packagesByHash.get(binding.packageHash);
|
|
10821
|
+
const manifest = await createTileflowMapBuildManifest2(
|
|
10822
|
+
{
|
|
10823
|
+
[mapName]: {
|
|
10824
|
+
map,
|
|
10825
|
+
lineage: deploymentProject.mapMetadata?.[mapName]?.lineage ?? [
|
|
10826
|
+
{ id: map.id, mapVersion: map.version }
|
|
10827
|
+
],
|
|
10828
|
+
assets: (iconPackage?.files ?? []).map((file) => ({
|
|
10829
|
+
contentType: file.contentType,
|
|
10830
|
+
fileName: `icons/${mapName}/${file.fileName}`,
|
|
10831
|
+
source: file.source
|
|
10832
|
+
})),
|
|
10833
|
+
sourceAssets: {
|
|
10834
|
+
fonts: preflightFonts.sourceIdentities[mapName] ?? [],
|
|
10835
|
+
icons: compiledIcons.sourceIdentities[mapName] ?? [],
|
|
10836
|
+
...compiledIcons.compositions[mapName] ? { iconComposition: compiledIcons.compositions[mapName] } : {},
|
|
10837
|
+
...hostedModelSourceIdentities(modelAssets, mapName) ? { models: hostedModelSourceIdentities(modelAssets, mapName) } : {}
|
|
10838
|
+
},
|
|
10839
|
+
styles: family.styles
|
|
10840
|
+
}
|
|
10841
|
+
},
|
|
10842
|
+
{ provenance: await createTileflowBuildProvenance2(process.cwd()) }
|
|
10843
|
+
);
|
|
10844
|
+
assertHostedStyleBudget({
|
|
10845
|
+
artifact: compactTileflowDeploymentArtifact3({
|
|
10846
|
+
kind: "tileflow-map-deployment",
|
|
10847
|
+
schemaVersion: 1,
|
|
10848
|
+
mapId: mapName,
|
|
10849
|
+
buildManifest: manifest,
|
|
10850
|
+
styles: family.styles,
|
|
10851
|
+
teamSources: family.teamSources
|
|
10852
|
+
}),
|
|
10853
|
+
environment: mapName,
|
|
10854
|
+
managedMapId: options.mapId ?? "map_preflight0000000",
|
|
10855
|
+
usageMode: "session",
|
|
10856
|
+
source,
|
|
10857
|
+
...iconPackage ? { iconPackage: { contentHash: iconPackage.contentHash, label: binding.label } } : {},
|
|
10858
|
+
...compiledIcons.compositions[mapName] ? { iconComposition: compiledIcons.compositions[mapName] } : {},
|
|
10859
|
+
...teamModelSources(modelAssets, mapName) ? { teamModelSources: teamModelSources(modelAssets, mapName) } : {}
|
|
10860
|
+
});
|
|
10861
|
+
}
|
|
10687
10862
|
let outputManifest = null;
|
|
10688
10863
|
try {
|
|
10689
10864
|
outputManifest = await loadExistingDeployManifest(options.manifest, {
|
|
@@ -10749,6 +10924,7 @@ addMapSetupOptions(
|
|
|
10749
10924
|
mapAssetsWithBuildingModels(modelAssets, { hosted: true })
|
|
10750
10925
|
);
|
|
10751
10926
|
const compiledHostedStyles = createTileflowStyles2(deploymentProject, {
|
|
10927
|
+
runtimeColors: true,
|
|
10752
10928
|
apiBaseUrl: api.apiUrl,
|
|
10753
10929
|
mapAssets: hostedMapAssets
|
|
10754
10930
|
});
|
|
@@ -10956,6 +11132,7 @@ addMapSetupOptions(
|
|
|
10956
11132
|
environment: mapName,
|
|
10957
11133
|
mapId: body.mapId,
|
|
10958
11134
|
...resolvedMap.systemThemes ? { systemThemes: resolvedMap.systemThemes } : {},
|
|
11135
|
+
...body.familyUrl ? { familyUrl: body.familyUrl } : {},
|
|
10959
11136
|
...resolvedMap.timeOfDay && Object.keys(resolvedMap.timeOfDay).length ? { timeOfDay: resolvedMap.timeOfDay } : {},
|
|
10960
11137
|
themes: Object.fromEntries(
|
|
10961
11138
|
themeNames.map((themeName) => {
|
|
@@ -10994,8 +11171,8 @@ addMapSetupOptions(
|
|
|
10994
11171
|
apiUrl: api.apiUrl,
|
|
10995
11172
|
maps: { ...existingManifest?.maps ?? {}, ...deployedMaps },
|
|
10996
11173
|
version: Object.values({ ...existingManifest?.maps, ...deployedMaps }).some(
|
|
10997
|
-
(map) => map.
|
|
10998
|
-
) ? 2 : 1
|
|
11174
|
+
(map) => map.familyUrl
|
|
11175
|
+
) ? 3 : Object.values({ ...existingManifest?.maps, ...deployedMaps }).some((map) => map.timeOfDay) ? 2 : 1
|
|
10999
11176
|
});
|
|
11000
11177
|
logSuccess("Deployed Tileflow maps.");
|
|
11001
11178
|
printKeyValue("Manifest", pathLabel(manifestPath));
|
|
@@ -11009,7 +11186,7 @@ addMapSetupOptions(
|
|
|
11009
11186
|
}
|
|
11010
11187
|
}
|
|
11011
11188
|
);
|
|
11012
|
-
program.command("status").description("Show deployed compiled styles").
|
|
11189
|
+
program.command("status").description("Show deployed compiled styles").addOption(hostedApiUrlOption()).addOption(hostedApiKeyOption("Tileflow API key")).option("--map-id <id>", "managed Map destination").option("--json", "print raw JSON").action(async (options) => {
|
|
11013
11190
|
if (!options.mapId || !/^map_[A-Za-z0-9_-]{16}$/u.test(options.mapId)) {
|
|
11014
11191
|
if (options.json) console.error("Status requires a valid --map-id.");
|
|
11015
11192
|
else logError("Status requires a valid --map-id.");
|
|
@@ -11389,13 +11566,23 @@ function closeNodeServer(server) {
|
|
|
11389
11566
|
return new Promise((resolveClose) => server.close(() => resolveClose()));
|
|
11390
11567
|
}
|
|
11391
11568
|
async function loginWithDeviceFlow(options) {
|
|
11569
|
+
const authState = await prepareAccountLogin();
|
|
11570
|
+
if (authState.kind === "legacy") {
|
|
11571
|
+
logWarning("An older Tileflow CLI login will be replaced after authorization.");
|
|
11572
|
+
}
|
|
11392
11573
|
const codeVerifier = randomBase64Url(32);
|
|
11393
11574
|
const codeChallenge = createPkceChallenge(codeVerifier);
|
|
11394
11575
|
printTitle("Tileflow login", "Authorize this machine from the Tileflow dashboard.");
|
|
11576
|
+
const authorizationStartedAt = Date.now();
|
|
11395
11577
|
const authorization = await startDeviceAuthorization(options.appUrl, {
|
|
11396
11578
|
codeChallenge,
|
|
11397
11579
|
deviceName: hostname()
|
|
11398
11580
|
});
|
|
11581
|
+
const apiUrl = normalizeApiOrigin(options.expectedApiUrl);
|
|
11582
|
+
if (normalizeApiOrigin(authorization.apiUrl) !== apiUrl) {
|
|
11583
|
+
throw new Error("The dashboard returned an unexpected Tileflow API origin.");
|
|
11584
|
+
}
|
|
11585
|
+
const previousSession = authState.kind === "sessions" ? authState.config.sessions[apiUrl] : void 0;
|
|
11399
11586
|
printKeyValue("Code", pc9.bold(authorization.userCode));
|
|
11400
11587
|
printKeyValue("URL", link(authorization.verificationUriComplete));
|
|
11401
11588
|
if (options.noBrowser) {
|
|
@@ -11416,12 +11603,9 @@ async function loginWithDeviceFlow(options) {
|
|
|
11416
11603
|
}
|
|
11417
11604
|
logInfo("Waiting for authorization...");
|
|
11418
11605
|
const token = await pollDeviceToken(options.appUrl, authorization, {
|
|
11419
|
-
codeVerifier
|
|
11606
|
+
codeVerifier,
|
|
11607
|
+
expiresAt: authorizationStartedAt + authorization.expiresIn * 1e3
|
|
11420
11608
|
});
|
|
11421
|
-
const apiUrl = normalizeApiOrigin(token.apiUrl);
|
|
11422
|
-
if (apiUrl !== normalizeApiOrigin(options.expectedApiUrl)) {
|
|
11423
|
-
throw new Error(`The dashboard returned an unexpected Tileflow API origin: ${apiUrl}.`);
|
|
11424
|
-
}
|
|
11425
11609
|
const session = {
|
|
11426
11610
|
account: token.account,
|
|
11427
11611
|
accountSession: token.accountSession,
|
|
@@ -11430,13 +11614,30 @@ async function loginWithDeviceFlow(options) {
|
|
|
11430
11614
|
expiresAt: token.expiresAt,
|
|
11431
11615
|
sessionId: token.sessionId
|
|
11432
11616
|
};
|
|
11433
|
-
|
|
11434
|
-
|
|
11435
|
-
|
|
11617
|
+
let account = session.account;
|
|
11618
|
+
try {
|
|
11619
|
+
if (normalizeApiOrigin(token.apiUrl) !== apiUrl) {
|
|
11620
|
+
throw new Error("The dashboard returned an unexpected Tileflow API origin.");
|
|
11621
|
+
}
|
|
11622
|
+
const profile = await validateAccountSession(session);
|
|
11623
|
+
if (!profile.ok) throw new Error(profile.error);
|
|
11624
|
+
account = profile.value.account;
|
|
11625
|
+
await installAccountSession({ ...session, account });
|
|
11626
|
+
} catch (error) {
|
|
11627
|
+
if (!await revokeHostedAccountSession(apiUrl, session.accountSession)) {
|
|
11628
|
+
logWarning(
|
|
11629
|
+
"Could not revoke the new account session after login failed. Revoke it in Dashboard settings."
|
|
11630
|
+
);
|
|
11631
|
+
}
|
|
11632
|
+
throw error;
|
|
11633
|
+
}
|
|
11634
|
+
if (previousSession && previousSession.accountSession !== session.accountSession && !await revokeHostedAccountSession(apiUrl, previousSession.accountSession)) {
|
|
11635
|
+
logWarning(
|
|
11636
|
+
"The new login was saved, but the previous session could not be revoked. Revoke it in Dashboard settings."
|
|
11637
|
+
);
|
|
11436
11638
|
}
|
|
11437
|
-
await installAccountSession({ ...session, account: profile.value.account });
|
|
11438
11639
|
logSuccess("Signed in to Tileflow.");
|
|
11439
|
-
printAccountDetails({ ...session, account
|
|
11640
|
+
printAccountDetails({ ...session, account });
|
|
11440
11641
|
printKeyValue("Dashboard", link(options.appUrl));
|
|
11441
11642
|
}
|
|
11442
11643
|
function createPkceChallenge(verifier) {
|
|
@@ -11529,7 +11730,7 @@ function validateMapSetupTarget(options, source) {
|
|
|
11529
11730
|
}
|
|
11530
11731
|
const createFirst = `Create the Map with ${command("tileflow maps create")} after ${command("tileflow login")}, then pass --map-id.`;
|
|
11531
11732
|
if (options.withNative) return fail("--with-native requires one valid --map-id.", [createFirst]);
|
|
11532
|
-
if (options.apiKey || !allowsStoredDeployCredential(source)) {
|
|
11733
|
+
if (options.apiKey !== void 0 || !allowsStoredDeployCredential(source)) {
|
|
11533
11734
|
return fail("Managed deploy with a Tileflow credential or in CI requires --map-id.", [
|
|
11534
11735
|
createFirst
|
|
11535
11736
|
]);
|
|
@@ -11597,7 +11798,12 @@ async function requireApiOptions(options, behavior = {}) {
|
|
|
11597
11798
|
process.exitCode = 1;
|
|
11598
11799
|
return null;
|
|
11599
11800
|
}
|
|
11600
|
-
if (options.apiKey) {
|
|
11801
|
+
if (options.apiKey !== void 0) {
|
|
11802
|
+
if (!isHostedApiKey(options.apiKey)) {
|
|
11803
|
+
if (!behavior.silent) logError("Tileflow API key is invalid.");
|
|
11804
|
+
process.exitCode = 1;
|
|
11805
|
+
return null;
|
|
11806
|
+
}
|
|
11601
11807
|
if (behavior.assertExplicitKeyMap) {
|
|
11602
11808
|
const profile = await validateApiKey(apiUrl, options.apiKey);
|
|
11603
11809
|
if (!profile.ok) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tileflow",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.20",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "CLI-only tools for Tileflow config, capture, visual testing, and deploy.",
|
|
6
6
|
"type": "module",
|
|
@@ -35,12 +35,12 @@
|
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@hono/node-server": "2.1.3",
|
|
38
|
-
"@tileflow/capture": ">=0.1.0-alpha.
|
|
38
|
+
"@tileflow/capture": ">=0.1.0-alpha.37 <0.1.0-beta.0",
|
|
39
39
|
"@tileflow/coordinates": ">=0.1.0-alpha.3 <0.1.0-beta.0",
|
|
40
40
|
"@tileflow/coordinates-runtime": ">=0.1.0-alpha.6 <0.1.0-beta.0",
|
|
41
|
-
"@tileflow/core": ">=0.1.0-alpha.
|
|
42
|
-
"@tileflow/dev": ">=0.1.0-alpha.
|
|
43
|
-
"@tileflow/maps": ">=0.1.0-alpha.
|
|
41
|
+
"@tileflow/core": ">=0.1.0-alpha.45 <0.1.0-beta.0",
|
|
42
|
+
"@tileflow/dev": ">=0.1.0-alpha.41 <0.1.0-beta.0",
|
|
43
|
+
"@tileflow/maps": ">=0.1.0-alpha.18 <0.1.0-beta.0",
|
|
44
44
|
"commander": "^15.0.0",
|
|
45
45
|
"hono": "4.13.14",
|
|
46
46
|
"picocolors": "^1.1.1",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"cli": "tsx src/index.ts",
|
|
56
56
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
57
57
|
"lint": "tsc -p tsconfig.json --noEmit",
|
|
58
|
-
"test": "tsx --test test/*.test.ts",
|
|
58
|
+
"test": "tsx --test --test-concurrency=2 test/*.test.ts",
|
|
59
59
|
"verify": "pnpm run typecheck && pnpm run test"
|
|
60
60
|
}
|
|
61
61
|
}
|