devflare 1.0.0-next.21 → 1.0.0-next.23
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/LLM.md +144 -5
- package/dist/account-j8nfggg4.js +475 -0
- package/dist/account-qhe8vtds.js +475 -0
- package/dist/bridge/gateway-runtime.d.ts +1 -1
- package/dist/bridge/gateway-runtime.d.ts.map +1 -1
- package/dist/bridge/miniflare.d.ts +1 -1
- package/dist/bridge/miniflare.d.ts.map +1 -1
- package/dist/bridge/proxy.d.ts +2 -0
- package/dist/bridge/proxy.d.ts.map +1 -1
- package/dist/bridge/server.d.ts.map +1 -1
- package/dist/browser.d.ts +13 -13
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +5 -3
- package/dist/build-qsgnme4z.js +54 -0
- package/dist/build-vy95gy3f.js +54 -0
- package/dist/build-yzx0gsaj.js +54 -0
- package/dist/cli/commands/build-artifacts.d.ts.map +1 -1
- package/dist/cli/commands/config.d.ts.map +1 -1
- package/dist/cli/commands/type-generation/generator.d.ts +4 -2
- package/dist/cli/commands/type-generation/generator.d.ts.map +1 -1
- package/dist/cli/commands/types.d.ts.map +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/config/compiler/types.d.ts +1 -1
- package/dist/config/compiler/types.d.ts.map +1 -1
- package/dist/config/define.d.ts +7 -4
- package/dist/config/define.d.ts.map +1 -1
- package/dist/config/env-vars.d.ts +309 -0
- package/dist/config/env-vars.d.ts.map +1 -0
- package/dist/config/index.d.ts +2 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/local-dev-vars.d.ts +2 -2
- package/dist/config/local-dev-vars.d.ts.map +1 -1
- package/dist/config/schema-env.d.ts +6 -6
- package/dist/config/schema-types-bindings-platform.d.ts +378 -0
- package/dist/config/schema-types-bindings-platform.d.ts.map +1 -0
- package/dist/config/schema-types-bindings-resources.d.ts +551 -0
- package/dist/config/schema-types-bindings-resources.d.ts.map +1 -0
- package/dist/config/schema-types-bindings.d.ts +254 -0
- package/dist/config/schema-types-bindings.d.ts.map +1 -0
- package/dist/config/schema-types-build.d.ts +86 -0
- package/dist/config/schema-types-build.d.ts.map +1 -0
- package/dist/config/schema-types-runtime.d.ts +882 -0
- package/dist/config/schema-types-runtime.d.ts.map +1 -0
- package/dist/config/schema-types.d.ts +377 -0
- package/dist/config/schema-types.d.ts.map +1 -0
- package/dist/config/schema.d.ts +14 -15
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config-entry.d.ts +2 -0
- package/dist/config-entry.d.ts.map +1 -1
- package/dist/config-entry.js +3 -1
- package/dist/config-gq5jh4cx.js +105 -0
- package/dist/config-vec13050.js +105 -0
- package/dist/deploy-01j0ep5n.js +1055 -0
- package/dist/deploy-nh5tbv45.js +1055 -0
- package/dist/deploy-tjypkhg7.js +1055 -0
- package/dist/dev-bh581ew3.js +2597 -0
- package/dist/dev-cme5de75.js +2551 -0
- package/dist/dev-gn5y93z9.js +2597 -0
- package/dist/dev-server/server.d.ts.map +1 -1
- package/dist/doctor-h5q28qt1.js +259 -0
- package/dist/doctor-khk550tw.js +259 -0
- package/dist/env.d.ts +10 -0
- package/dist/env.d.ts.map +1 -1
- package/dist/index-0bv2qjs1.js +1555 -0
- package/dist/index-35bmgpfw.js +573 -0
- package/dist/index-3tkzn06q.js +413 -0
- package/dist/index-4se6krdj.js +574 -0
- package/dist/index-8fyz6gcm.js +699 -0
- package/dist/index-97z629zr.js +109 -0
- package/dist/index-b28c4yr4.js +1205 -0
- package/dist/index-c1cj9085.js +2250 -0
- package/dist/index-c8p4njqy.js +479 -0
- package/dist/index-cr06zrgw.js +1033 -0
- package/dist/index-cwjjdtgn.js +74 -0
- package/dist/index-dref9ecb.js +476 -0
- package/dist/index-e151t4ge.js +895 -0
- package/dist/index-e7kakw0j.js +1033 -0
- package/dist/index-f1g5jdm8.js +1426 -0
- package/dist/index-f46984zs.js +1554 -0
- package/dist/index-grk8pzhr.js +185 -0
- package/dist/index-hbxkmb1q.js +1426 -0
- package/dist/index-hzmpecq9.js +52 -0
- package/dist/index-j1csb7gb.js +581 -0
- package/dist/index-j7x7f72h.js +185 -0
- package/dist/index-jkqbjwt2.js +476 -0
- package/dist/index-jwd3fanx.js +412 -0
- package/dist/index-mh5renra.js +895 -0
- package/dist/index-p9xq83p7.js +147 -0
- package/dist/index-q15nj71j.js +52 -0
- package/dist/index-qqp65pyv.js +699 -0
- package/dist/index-s0fmwxbk.js +74 -0
- package/dist/index-s9q605sq.js +1033 -0
- package/dist/index-stzx8nc4.js +111 -0
- package/dist/index-th4vrnbk.js +1205 -0
- package/dist/index-vtcmsgaf.js +581 -0
- package/dist/index-w36q6819.js +895 -0
- package/dist/index-x2k3awjs.js +147 -0
- package/dist/index-x8x547tz.js +1426 -0
- package/dist/index-xp0qkkxf.js +68 -0
- package/dist/index-xxxd0mvw.js +109 -0
- package/dist/index-zawn5tte.js +109 -0
- package/dist/index-zpy9caxn.js +1193 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -7
- package/dist/login-280p2cm9.js +77 -0
- package/dist/login-4n266whq.js +77 -0
- package/dist/previews-3m3ffpaw.js +1337 -0
- package/dist/previews-tr8sm03d.js +1337 -0
- package/dist/productions-62y489ff.js +505 -0
- package/dist/productions-cgn3fz7d.js +505 -0
- package/dist/runtime/exports.d.ts +23 -0
- package/dist/runtime/exports.d.ts.map +1 -1
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +6 -4
- package/dist/secrets-4050kqf5.js +91 -0
- package/dist/secrets-p112cajt.js +91 -0
- package/dist/sveltekit/index.js +8 -7
- package/dist/sveltekit/local-bindings.d.ts.map +1 -1
- package/dist/test/index.js +24 -12
- package/dist/test/resolve-service-bindings.d.ts +1 -1
- package/dist/test/resolve-service-bindings.d.ts.map +1 -1
- package/dist/test/simple-context-lifecycle.d.ts.map +1 -1
- package/dist/types-apmt10yj.js +705 -0
- package/dist/types-ttrrgdfj.js +705 -0
- package/dist/vite/index.js +5 -5
- package/dist/vite/plugin-context.d.ts.map +1 -1
- package/dist/vite/plugin-programmatic.d.ts.map +1 -1
- package/dist/worker-2k1jyr6p.js +513 -0
- package/dist/worker-jqgn6jyj.js +513 -0
- package/package.json +1 -1
package/LLM.md
CHANGED
|
@@ -13,7 +13,7 @@ It is meant to read like a proper markdown handbook rather than a second source
|
|
|
13
13
|
- Links use the same `/docs/...` routes as the documentation site.
|
|
14
14
|
|
|
15
15
|
## Documentation map
|
|
16
|
-
This export covers
|
|
16
|
+
This export covers 147 pages across 5 top-level groups.
|
|
17
17
|
|
|
18
18
|
### Quickstart
|
|
19
19
|
See why Devflare exists, build the smallest safe first worker, and move into routes, bindings, previews, and tests when the app needs them.
|
|
@@ -40,6 +40,7 @@ Keep the day-to-day Devflare surfaces easy to scan: runtime model, HTTP split, a
|
|
|
40
40
|
- [Worker surfaces](/docs/worker-surfaces) — Devflare can compose or wrap several Worker surfaces into one generated entrypoint, but the authored source of truth should stay in explicit files such as `src/fetch.ts`, `src/queue.ts`, `src/scheduled.ts`, and `src/email.ts`.
|
|
41
41
|
- [Generated types](/docs/generated-types) — `devflare types` turns config, discovered Durable Objects, named entrypoints, and cross-worker references into one generated TypeScript contract instead of a pile of hand-maintained env guesswork.
|
|
42
42
|
- [Environments](/docs/config-environments) — Keep one base config, layer environment-specific overrides with `config.env`, and let Devflare resolve preview or production details only in the commands that actually need them.
|
|
43
|
+
- [Typed env vars](/docs/typed-env-vars) — Use `env.NAME` descriptors inside `defineConfig({ vars })`, parse or default them in config, and read the resulting typed values at runtime with `import { vars } from "devflare"`.
|
|
43
44
|
- [Previews](/docs/config-previews) — Use `preview.scope()` for bindings that should belong to one preview scope. Devflare materializes names like `notes-db-next`, provisions or reuses the preview-only resources it can manage, and lets you clean them up by the same scope later without touching production resources.
|
|
44
45
|
- [Runtime & deploy settings](/docs/runtime-deploy-settings) — Use config for account context, compatibility posture, assets, deployment routes, WebSocket proxy rules, migrations, observability, limits, and preview cron behavior instead of rediscovering those settings in scripts later.
|
|
45
46
|
|
|
@@ -2207,7 +2208,7 @@ That is what this page is for. The example below touches the major current top-l
|
|
|
2207
2208
|
Hover any property in the config to see what that lane means. The example is intentionally broad, but the dedicated pages still own the deeper caveats and richer nested variants.
|
|
2208
2209
|
|
|
2209
2210
|
```ts
|
|
2210
|
-
import { defineConfig } from 'devflare/config'
|
|
2211
|
+
import { defineConfig, env } from 'devflare/config'
|
|
2211
2212
|
|
|
2212
2213
|
export default defineConfig({
|
|
2213
2214
|
name: 'docs-platform',
|
|
@@ -2294,7 +2295,12 @@ export default defineConfig({
|
|
|
2294
2295
|
crons: ['0 */6 * * *']
|
|
2295
2296
|
},
|
|
2296
2297
|
vars: {
|
|
2297
|
-
APP_ENV: 'development'
|
|
2298
|
+
APP_ENV: 'development',
|
|
2299
|
+
mongo: {
|
|
2300
|
+
uri: env.MONGOURI,
|
|
2301
|
+
database: env.MONGODATABASE
|
|
2302
|
+
},
|
|
2303
|
+
retries: env.RETRIES.parse(Number)
|
|
2298
2304
|
},
|
|
2299
2305
|
secrets: {
|
|
2300
2306
|
API_TOKEN: {
|
|
@@ -2904,8 +2910,8 @@ The replace-arrays rule is the one most likely to surprise someone arriving from
|
|
|
2904
2910
|
|
|
2905
2911
|
##### Key points
|
|
2906
2912
|
|
|
2907
|
-
- Use `.env`
|
|
2908
|
-
- Use `vars` for
|
|
2913
|
+
- Use `.env` and `.env.dev` for config-time inputs. Devflare reads those files itself from the config directory upward, with closer files winning and `.env` overriding `.env.dev` in the same directory.
|
|
2914
|
+
- Use `vars` for values that should compile into Worker-facing output, including nested typed values produced by `env.NAME` descriptors.
|
|
2909
2915
|
- Use `secrets` to declare runtime secret binding names, not to store those secret values in config. Today that is mostly schema and type metadata: the schema accepts `{ required: false }`, but generated env typing still treats declared secrets as present and Devflare does not currently turn that flag into a separate deploy-time guarantee.
|
|
2910
2916
|
- Use `.env.example` to document config-time inputs for the team instead of leaving those values to memory or chat scrollback.
|
|
2911
2917
|
|
|
@@ -2915,6 +2921,139 @@ The replace-arrays rule is the one most likely to surprise someone arriving from
|
|
|
2915
2921
|
|
|
2916
2922
|
---
|
|
2917
2923
|
|
|
2924
|
+
### Resolve `.env` values through typed config vars instead of scattering process env reads
|
|
2925
|
+
|
|
2926
|
+
> Use `env.NAME` descriptors inside `defineConfig({ vars })`, parse or default them in config, and read the resulting typed values at runtime with `import { vars } from "devflare"`.
|
|
2927
|
+
|
|
2928
|
+
| Field | Value |
|
|
2929
|
+
| --- | --- |
|
|
2930
|
+
| Route | [`/docs/typed-env-vars`](/docs/typed-env-vars) |
|
|
2931
|
+
| Group | Devflare |
|
|
2932
|
+
| Navigation title | Typed env vars |
|
|
2933
|
+
| Eyebrow | Configuration |
|
|
2934
|
+
|
|
2935
|
+
Devflare vars can now be a typed bridge from local `.env` files into Worker runtime code. The config owns which variables are required, optional, parsed, or dev-only, while application code reads the resolved shape through the `vars` runtime helper.
|
|
2936
|
+
|
|
2937
|
+
#### At a glance
|
|
2938
|
+
|
|
2939
|
+
| Fact | Value |
|
|
2940
|
+
| --- | --- |
|
|
2941
|
+
| Config import | `import { defineConfig, env } from 'devflare/config'` |
|
|
2942
|
+
| Runtime import | `import { vars } from 'devflare'` |
|
|
2943
|
+
| File order | Parents first, then closer directories; `.env.dev` first, `.env` last |
|
|
2944
|
+
| Missing build vars | Build fails with a nested missing-variable report |
|
|
2945
|
+
|
|
2946
|
+
#### Declare the runtime shape in config
|
|
2947
|
+
|
|
2948
|
+
The `env` export from `devflare/config` does not read the variable immediately. It creates a descriptor that Devflare resolves when it starts dev, builds artifacts, or prints a phase-resolved config.
|
|
2949
|
+
|
|
2950
|
+
That keeps config import cheap and lets Devflare report every missing variable at once, using the nested path from `vars` instead of a generic process-env crash.
|
|
2951
|
+
|
|
2952
|
+
##### Example — Nested vars with required, optional, parsed, defaulted, and dev-only values
|
|
2953
|
+
|
|
2954
|
+
```ts
|
|
2955
|
+
import { defineConfig, env } from 'devflare/config'
|
|
2956
|
+
|
|
2957
|
+
export default defineConfig({
|
|
2958
|
+
name: 'voices-api',
|
|
2959
|
+
vars: {
|
|
2960
|
+
secret: env.SECRET,
|
|
2961
|
+
mongo: {
|
|
2962
|
+
uri: env.MONGOURI,
|
|
2963
|
+
database: env.MONGODATABASE
|
|
2964
|
+
},
|
|
2965
|
+
retries: env.RETRIES.parse(Number),
|
|
2966
|
+
optionalLabel: env.OPTIONAL_LABEL.optional(),
|
|
2967
|
+
mode: env.APP_MODE.default('local'),
|
|
2968
|
+
mockTenantId: env.MOCK_TENANT_ID.dev(123)
|
|
2969
|
+
}
|
|
2970
|
+
})
|
|
2971
|
+
```
|
|
2972
|
+
|
|
2973
|
+
#### Read resolved values through the runtime `vars` helper
|
|
2974
|
+
|
|
2975
|
+
At runtime, Devflare exposes the resolved values on the Worker environment and through the `vars` helper. The helper is typed from `devflare types`, so parser return values and nested objects stay visible to TypeScript.
|
|
2976
|
+
|
|
2977
|
+
Unparsed environment descriptors resolve to strings. Parsed descriptors use the parser return type, defaults contribute their value type, and optional descriptors become optional properties.
|
|
2978
|
+
|
|
2979
|
+
##### Example — Runtime code can use the nested shape directly
|
|
2980
|
+
|
|
2981
|
+
###### File — src/fetch.ts
|
|
2982
|
+
|
|
2983
|
+
```ts
|
|
2984
|
+
import { vars } from 'devflare'
|
|
2985
|
+
|
|
2986
|
+
export default {
|
|
2987
|
+
async fetch() {
|
|
2988
|
+
return Response.json({
|
|
2989
|
+
database: vars.mongo.database,
|
|
2990
|
+
retries: vars.retries
|
|
2991
|
+
})
|
|
2992
|
+
}
|
|
2993
|
+
}
|
|
2994
|
+
```
|
|
2995
|
+
|
|
2996
|
+
#### Let Devflare parse `.env` files itself
|
|
2997
|
+
|
|
2998
|
+
Devflare reads `.env.dev` and `.env` from the config directory and every parent directory. Parent files load first, then closer files override them. Within one directory, `.env.dev` loads first and `.env` wins last.
|
|
2999
|
+
|
|
3000
|
+
The parser does not expand `$OTHER_VARIABLE` references. Values such as passwords, MongoDB connection strings, and shell-looking fragments are read as written instead of being interpreted by Bun.
|
|
3001
|
+
|
|
3002
|
+
> **Note — Process env still wins over files**
|
|
3003
|
+
>
|
|
3004
|
+
> CI-provided environment variables and explicit shell exports override `.env` file values. Dotenv files fill in missing process variables; they do not stomp values the process already had.
|
|
3005
|
+
|
|
3006
|
+
##### Example — The later `.env` value overrides the earlier `.env.dev` value
|
|
3007
|
+
|
|
3008
|
+
###### File — .env
|
|
3009
|
+
|
|
3010
|
+
```dotenv
|
|
3011
|
+
# .env.dev
|
|
3012
|
+
SECRET=local-secret
|
|
3013
|
+
MONGOURI=mongodb://127.0.0.1:27017
|
|
3014
|
+
MONGODATABASE=voices_dev
|
|
3015
|
+
RETRIES=1
|
|
3016
|
+
|
|
3017
|
+
# .env
|
|
3018
|
+
MONGODATABASE=voices
|
|
3019
|
+
```
|
|
3020
|
+
|
|
3021
|
+
#### Missing required values fail build and pause dev
|
|
3022
|
+
|
|
3023
|
+
Required is the default because a config variable usually means the Worker cannot run honestly without that value. Build and config-inspection commands fail with a grouped report that points at the nested `vars` path and the missing environment variable name.
|
|
3024
|
+
|
|
3025
|
+
Dev mode is gentler. It prints the same report, waits for `.env` or `.env.dev` to change, and then retries startup. That makes the local loop fixable without restarting the command.
|
|
3026
|
+
|
|
3027
|
+
##### Example — Missing variables are grouped by the config path that required them
|
|
3028
|
+
|
|
3029
|
+
###### File — missing-env-vars.txt
|
|
3030
|
+
|
|
3031
|
+
```text
|
|
3032
|
+
These environment variables are missing:
|
|
3033
|
+
|
|
3034
|
+
secret: SECRET
|
|
3035
|
+
mongo:
|
|
3036
|
+
uri: MONGOURI
|
|
3037
|
+
```
|
|
3038
|
+
|
|
3039
|
+
#### Use helpers to make intent explicit
|
|
3040
|
+
|
|
3041
|
+
##### Reference table
|
|
3042
|
+
|
|
3043
|
+
| Helper | Meaning | Example |
|
|
3044
|
+
| --- | --- | --- |
|
|
3045
|
+
| `env.NAME` | Required string value. | `env.SECRET` |
|
|
3046
|
+
| `.optional()` | Missing value is allowed and omitted. | `env.OPTIONAL_LABEL.optional()` |
|
|
3047
|
+
| `.parse(fn)` / `.parser(fn)` | Transform the string from env files into a typed runtime value. | `env.RETRIES.parse(Number)` |
|
|
3048
|
+
| `.default(value)` | Use a fallback in every mode when the env value is missing. | `env.APP_MODE.default('local')` |
|
|
3049
|
+
| `.dev(value)` | Use a fallback only in dev when the env value is missing. | `env.MOCK_TENANT_ID.dev(123)` |
|
|
3050
|
+
|
|
3051
|
+
> **Warning — Dev-only defaults are still required in build**
|
|
3052
|
+
>
|
|
3053
|
+
> `.dev(value)` is intentionally local-only. If the same variable may be missing in build too, use `.default(value)` or `.optional()` instead.
|
|
3054
|
+
|
|
3055
|
+
---
|
|
3056
|
+
|
|
2918
3057
|
### Author preview-scoped bindings so preview deploys can own disposable infrastructure
|
|
2919
3058
|
|
|
2920
3059
|
> Use `preview.scope()` for bindings that should belong to one preview scope. Devflare materializes names like `notes-db-next`, provisions or reuses the preview-only resources it can manage, and lets you clean them up by the same scope later without touching production resources.
|
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getConfiguredAccountId
|
|
3
|
+
} from "./index-s0fmwxbk.js";
|
|
4
|
+
import {
|
|
5
|
+
account
|
|
6
|
+
} from "./index-0m6e4mxz.js";
|
|
7
|
+
import"./index-k7m5f1dg.js";
|
|
8
|
+
import"./index-3jme4hgw.js";
|
|
9
|
+
import {
|
|
10
|
+
bold,
|
|
11
|
+
createCliTheme,
|
|
12
|
+
dim,
|
|
13
|
+
formatCommand,
|
|
14
|
+
formatLabelValue,
|
|
15
|
+
green,
|
|
16
|
+
logLine,
|
|
17
|
+
logTable,
|
|
18
|
+
whiteDim,
|
|
19
|
+
yellow
|
|
20
|
+
} from "./index-stgn34cr.js";
|
|
21
|
+
import {
|
|
22
|
+
CYAN,
|
|
23
|
+
CYAN_BOLD,
|
|
24
|
+
DIM,
|
|
25
|
+
RESET
|
|
26
|
+
} from "./index-3t6rypgc.js";
|
|
27
|
+
import"./index-15fpa5tx.js";
|
|
28
|
+
import"./index-0bv2qjs1.js";
|
|
29
|
+
import {
|
|
30
|
+
getGlobalDefaultAccountId,
|
|
31
|
+
getWorkspaceAccountId,
|
|
32
|
+
setGlobalDefaultAccountId,
|
|
33
|
+
setWorkspaceAccountId
|
|
34
|
+
} from "./index-1d4jg11n.js";
|
|
35
|
+
import {
|
|
36
|
+
AuthenticationError,
|
|
37
|
+
CloudflareAPIError
|
|
38
|
+
} from "./index-mg8vwqxf.js";
|
|
39
|
+
import"./index-c8p4njqy.js";
|
|
40
|
+
import"./index-37x76zdn.js";
|
|
41
|
+
|
|
42
|
+
// src/cli/commands/account.ts
|
|
43
|
+
var ACCOUNT_SUBCOMMANDS = [
|
|
44
|
+
"info",
|
|
45
|
+
"workers",
|
|
46
|
+
"kv",
|
|
47
|
+
"d1",
|
|
48
|
+
"r2",
|
|
49
|
+
"vectorize",
|
|
50
|
+
"limits",
|
|
51
|
+
"usage",
|
|
52
|
+
"global",
|
|
53
|
+
"workspace"
|
|
54
|
+
];
|
|
55
|
+
function isAccountSubcommand(value) {
|
|
56
|
+
return ACCOUNT_SUBCOMMANDS.includes(value);
|
|
57
|
+
}
|
|
58
|
+
var CLI_API_OPTIONS = { timeout: 1e4 };
|
|
59
|
+
function formatDate(date) {
|
|
60
|
+
if (!date)
|
|
61
|
+
return "N/A";
|
|
62
|
+
return date.toLocaleDateString("en-US", {
|
|
63
|
+
year: "numeric",
|
|
64
|
+
month: "short",
|
|
65
|
+
day: "numeric"
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
function formatPercent(value) {
|
|
69
|
+
if (value === undefined)
|
|
70
|
+
return "N/A";
|
|
71
|
+
return `${value.toFixed(1)}%`;
|
|
72
|
+
}
|
|
73
|
+
function logSection(logger, title, theme, count, accent = "cyan") {
|
|
74
|
+
logLine(logger);
|
|
75
|
+
const heading = accent === "yellow" ? yellow(title, theme) : accent === "green" ? green(title, theme) : bold(title, theme);
|
|
76
|
+
logLine(logger, `${heading}${count === undefined ? "" : ` ${dim(`(${count})`, theme)}`}`);
|
|
77
|
+
}
|
|
78
|
+
function logEmptyState(logger, message, theme) {
|
|
79
|
+
logLine(logger);
|
|
80
|
+
logLine(logger, dim(message, theme));
|
|
81
|
+
logLine(logger);
|
|
82
|
+
return { exitCode: 0 };
|
|
83
|
+
}
|
|
84
|
+
async function runAccountCommand(parsed, logger, options) {
|
|
85
|
+
const theme = createCliTheme(parsed.options);
|
|
86
|
+
const isAuth = await account.isAuthenticated();
|
|
87
|
+
if (!isAuth) {
|
|
88
|
+
logger.error("Not authenticated with Cloudflare");
|
|
89
|
+
logLine(logger, dim("Run: devflare login", theme));
|
|
90
|
+
return { exitCode: 1 };
|
|
91
|
+
}
|
|
92
|
+
const subcommand = parsed.args[0];
|
|
93
|
+
const rawSubcommand = parsed.args[0];
|
|
94
|
+
if (rawSubcommand && !isAccountSubcommand(rawSubcommand)) {
|
|
95
|
+
logger.error(`Unknown account subcommand: ${rawSubcommand}`);
|
|
96
|
+
logLine(logger, dim(`Available account subcommands: ${ACCOUNT_SUBCOMMANDS.join(", ")}`, theme));
|
|
97
|
+
return { exitCode: 1 };
|
|
98
|
+
}
|
|
99
|
+
if (subcommand === "global") {
|
|
100
|
+
return await selectGlobalAccount(logger, theme);
|
|
101
|
+
}
|
|
102
|
+
if (subcommand === "workspace") {
|
|
103
|
+
return await selectWorkspaceAccount(logger, theme);
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
let accountId = parsed.options.account;
|
|
107
|
+
if (!accountId) {
|
|
108
|
+
accountId = await getConfiguredAccountId(options.cwd ?? process.cwd());
|
|
109
|
+
}
|
|
110
|
+
if (!accountId) {
|
|
111
|
+
const primary = await account.getPrimaryAccount();
|
|
112
|
+
if (!primary) {
|
|
113
|
+
logger.error("No Cloudflare accounts found");
|
|
114
|
+
return { exitCode: 1 };
|
|
115
|
+
}
|
|
116
|
+
accountId = primary.id;
|
|
117
|
+
}
|
|
118
|
+
switch (subcommand) {
|
|
119
|
+
case "workers":
|
|
120
|
+
return await showWorkers(accountId, logger, theme);
|
|
121
|
+
case "kv":
|
|
122
|
+
return await showKV(accountId, logger, theme);
|
|
123
|
+
case "d1":
|
|
124
|
+
return await showD1(accountId, logger, theme);
|
|
125
|
+
case "r2":
|
|
126
|
+
return await showR2(accountId, logger, theme);
|
|
127
|
+
case "vectorize":
|
|
128
|
+
return await showVectorize(accountId, logger, theme);
|
|
129
|
+
case "limits":
|
|
130
|
+
return await handleLimits(accountId, parsed, logger, theme);
|
|
131
|
+
case "usage":
|
|
132
|
+
return await showUsage(accountId, logger, theme);
|
|
133
|
+
case "info":
|
|
134
|
+
default:
|
|
135
|
+
return await showAccountOverview(accountId, logger, theme);
|
|
136
|
+
}
|
|
137
|
+
} catch (error) {
|
|
138
|
+
if (error instanceof AuthenticationError) {
|
|
139
|
+
logger.error(error.message);
|
|
140
|
+
return { exitCode: 1 };
|
|
141
|
+
}
|
|
142
|
+
if (error instanceof CloudflareAPIError) {
|
|
143
|
+
logger.error(`API Error: ${error.message}`);
|
|
144
|
+
return { exitCode: 1 };
|
|
145
|
+
}
|
|
146
|
+
if (error instanceof Error) {
|
|
147
|
+
if (error.name === "AbortError" || error.message.includes("timed out")) {
|
|
148
|
+
logger.error("Request timed out. The Cloudflare API is slow or unavailable.");
|
|
149
|
+
return { exitCode: 1 };
|
|
150
|
+
}
|
|
151
|
+
logger.error(`Error: ${error.message}`);
|
|
152
|
+
return { exitCode: 1 };
|
|
153
|
+
}
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async function showAccountOverview(accountId, logger, theme) {
|
|
158
|
+
logSection(logger, "Accounts", theme);
|
|
159
|
+
let accounts = [];
|
|
160
|
+
let limitedAccountView = false;
|
|
161
|
+
try {
|
|
162
|
+
accounts = await account.getAccounts();
|
|
163
|
+
} catch {
|
|
164
|
+
const fallbackAccount = await account.getAccountById(accountId);
|
|
165
|
+
if (!fallbackAccount) {
|
|
166
|
+
logger.error("Could not inspect Cloudflare accounts with the current credentials");
|
|
167
|
+
return { exitCode: 1 };
|
|
168
|
+
}
|
|
169
|
+
accounts = [fallbackAccount];
|
|
170
|
+
limitedAccountView = true;
|
|
171
|
+
}
|
|
172
|
+
if (accounts.length === 0) {
|
|
173
|
+
logger.error("No Cloudflare accounts found");
|
|
174
|
+
return { exitCode: 1 };
|
|
175
|
+
}
|
|
176
|
+
const workspaceId = getWorkspaceAccountId();
|
|
177
|
+
const globalId = await getGlobalDefaultAccountId(accountId);
|
|
178
|
+
if (limitedAccountView) {
|
|
179
|
+
logLine(logger, dim("Using the configured account directly because the current credentials cannot enumerate all Cloudflare accounts.", theme));
|
|
180
|
+
}
|
|
181
|
+
for (let i = 0;i < accounts.length; i++) {
|
|
182
|
+
const acc = accounts[i];
|
|
183
|
+
const isWorkspace = acc.id === workspaceId;
|
|
184
|
+
const isGlobal = acc.id === globalId;
|
|
185
|
+
let badge = "";
|
|
186
|
+
if (isWorkspace) {
|
|
187
|
+
badge = ` ${CYAN_BOLD}(workspace)${RESET}`;
|
|
188
|
+
} else if (isGlobal) {
|
|
189
|
+
badge = workspaceId ? ` ${DIM}(global)${RESET}` : ` ${CYAN}(global)${RESET}`;
|
|
190
|
+
}
|
|
191
|
+
if (i > 0) {
|
|
192
|
+
logLine(logger);
|
|
193
|
+
}
|
|
194
|
+
logLine(logger, `${dim("account", theme)} ${green(acc.name, theme)}${badge}`);
|
|
195
|
+
logLine(logger, formatLabelValue("id", whiteDim(acc.id, theme), theme, 10));
|
|
196
|
+
logLine(logger, formatLabelValue("type", acc.type, theme, 10));
|
|
197
|
+
}
|
|
198
|
+
logSection(logger, "Commands", theme);
|
|
199
|
+
logLine(logger, formatCommand("devflare account global", "Set global default account", theme));
|
|
200
|
+
logLine(logger, formatCommand("devflare account workspace", "Set workspace account", theme));
|
|
201
|
+
logLine(logger, formatCommand("devflare account workers", "List Workers", theme));
|
|
202
|
+
logLine(logger, formatCommand("devflare account kv", "List KV namespaces", theme));
|
|
203
|
+
logLine(logger, formatCommand("devflare account d1", "List D1 databases", theme));
|
|
204
|
+
logLine(logger, formatCommand("devflare account r2", "List R2 buckets", theme));
|
|
205
|
+
logLine(logger, formatCommand("devflare account vectorize", "List Vectorize indexes", theme));
|
|
206
|
+
logLine(logger, formatCommand("devflare account limits", "View or set usage limits", theme));
|
|
207
|
+
logLine(logger, formatCommand("devflare account usage", "View detailed usage", theme));
|
|
208
|
+
logLine(logger, formatCommand("devflare ai", "View AI models and pricing", theme));
|
|
209
|
+
logLine(logger);
|
|
210
|
+
return { exitCode: 0 };
|
|
211
|
+
}
|
|
212
|
+
async function selectGlobalAccount(logger, theme) {
|
|
213
|
+
const accounts = await account.getAccounts();
|
|
214
|
+
if (accounts.length === 0) {
|
|
215
|
+
logger.error("No Cloudflare accounts found");
|
|
216
|
+
return { exitCode: 1 };
|
|
217
|
+
}
|
|
218
|
+
if (accounts.length === 1) {
|
|
219
|
+
await setGlobalDefaultAccountId(accounts[0].id);
|
|
220
|
+
logger.success(`Global default set to: ${accounts[0].name}`);
|
|
221
|
+
return { exitCode: 0 };
|
|
222
|
+
}
|
|
223
|
+
const currentGlobal = await getGlobalDefaultAccountId(accounts[0].id);
|
|
224
|
+
const options = accounts.map((acc) => {
|
|
225
|
+
const isCurrent = acc.id === currentGlobal;
|
|
226
|
+
return {
|
|
227
|
+
label: isCurrent ? `${acc.name} ${CYAN}(default)${RESET}` : acc.name,
|
|
228
|
+
value: acc.id,
|
|
229
|
+
hint: acc.id.substring(0, 8) + "..."
|
|
230
|
+
};
|
|
231
|
+
});
|
|
232
|
+
const selected = await logger.prompt("Select global default account:", {
|
|
233
|
+
type: "select",
|
|
234
|
+
options,
|
|
235
|
+
initial: currentGlobal ?? accounts[0].id
|
|
236
|
+
});
|
|
237
|
+
if (!selected || typeof selected === "symbol") {
|
|
238
|
+
logLine(logger, dim("Cancelled", theme));
|
|
239
|
+
return { exitCode: 0 };
|
|
240
|
+
}
|
|
241
|
+
await setGlobalDefaultAccountId(selected, accounts[0].id);
|
|
242
|
+
const selectedAccount = accounts.find((a) => a.id === selected);
|
|
243
|
+
logger.success(`Global default set to: ${selectedAccount?.name}`);
|
|
244
|
+
logLine(logger, `${dim("saved to", theme)} ~/.devflare/preferences.json + cloud KV`);
|
|
245
|
+
return { exitCode: 0 };
|
|
246
|
+
}
|
|
247
|
+
async function selectWorkspaceAccount(logger, theme) {
|
|
248
|
+
const accounts = await account.getAccounts();
|
|
249
|
+
if (accounts.length === 0) {
|
|
250
|
+
logger.error("No Cloudflare accounts found");
|
|
251
|
+
return { exitCode: 1 };
|
|
252
|
+
}
|
|
253
|
+
if (accounts.length === 1) {
|
|
254
|
+
const pkgPath2 = setWorkspaceAccountId(accounts[0].id);
|
|
255
|
+
logger.success(`Workspace account set to: ${accounts[0].name}`);
|
|
256
|
+
logLine(logger, `${dim("saved to", theme)} ${pkgPath2}`);
|
|
257
|
+
return { exitCode: 0 };
|
|
258
|
+
}
|
|
259
|
+
const currentWorkspace = getWorkspaceAccountId();
|
|
260
|
+
const options = accounts.map((acc) => {
|
|
261
|
+
const isCurrent = acc.id === currentWorkspace;
|
|
262
|
+
return {
|
|
263
|
+
label: isCurrent ? `${acc.name} ${CYAN}(workspace)${RESET}` : acc.name,
|
|
264
|
+
value: acc.id,
|
|
265
|
+
hint: acc.id.substring(0, 8) + "..."
|
|
266
|
+
};
|
|
267
|
+
});
|
|
268
|
+
const selected = await logger.prompt("Select workspace account:", {
|
|
269
|
+
type: "select",
|
|
270
|
+
options,
|
|
271
|
+
initial: currentWorkspace ?? accounts[0].id
|
|
272
|
+
});
|
|
273
|
+
if (!selected || typeof selected === "symbol") {
|
|
274
|
+
logLine(logger, dim("Cancelled", theme));
|
|
275
|
+
return { exitCode: 0 };
|
|
276
|
+
}
|
|
277
|
+
const pkgPath = setWorkspaceAccountId(selected);
|
|
278
|
+
const selectedAccount = accounts.find((a) => a.id === selected);
|
|
279
|
+
logger.success(`Workspace account set to: ${selectedAccount?.name}`);
|
|
280
|
+
logLine(logger, `${dim("saved to", theme)} ${pkgPath}`);
|
|
281
|
+
return { exitCode: 0 };
|
|
282
|
+
}
|
|
283
|
+
async function showWorkers(accountId, logger, theme) {
|
|
284
|
+
const workers = await account.workers(accountId, CLI_API_OPTIONS);
|
|
285
|
+
if (workers.length === 0) {
|
|
286
|
+
return logEmptyState(logger, "No Workers found", theme);
|
|
287
|
+
}
|
|
288
|
+
logSection(logger, "Workers", theme, workers.length, "green");
|
|
289
|
+
logTable(logger, {
|
|
290
|
+
title: "Worker list",
|
|
291
|
+
rows: workers,
|
|
292
|
+
columns: [
|
|
293
|
+
{ label: "Name", width: 30, value: (worker) => worker.name },
|
|
294
|
+
{ label: "Modified", width: 20, value: (worker) => whiteDim(formatDate(worker.modifiedOn), theme) }
|
|
295
|
+
],
|
|
296
|
+
theme,
|
|
297
|
+
titleAccent: "green"
|
|
298
|
+
});
|
|
299
|
+
logLine(logger);
|
|
300
|
+
return { exitCode: 0 };
|
|
301
|
+
}
|
|
302
|
+
async function showKV(accountId, logger, theme) {
|
|
303
|
+
const namespaces = await account.kv(accountId, CLI_API_OPTIONS);
|
|
304
|
+
if (namespaces.length === 0) {
|
|
305
|
+
return logEmptyState(logger, "No KV namespaces found", theme);
|
|
306
|
+
}
|
|
307
|
+
logSection(logger, "KV namespaces", theme, namespaces.length);
|
|
308
|
+
logTable(logger, {
|
|
309
|
+
title: "Namespace list",
|
|
310
|
+
rows: namespaces,
|
|
311
|
+
columns: [
|
|
312
|
+
{ label: "Name", width: 35, value: (ns) => ns.name },
|
|
313
|
+
{ label: "ID", width: 35, value: (ns) => whiteDim(ns.id, theme) }
|
|
314
|
+
],
|
|
315
|
+
theme,
|
|
316
|
+
titleAccent: "cyan"
|
|
317
|
+
});
|
|
318
|
+
logLine(logger);
|
|
319
|
+
return { exitCode: 0 };
|
|
320
|
+
}
|
|
321
|
+
async function showD1(accountId, logger, theme) {
|
|
322
|
+
const databases = await account.d1(accountId, CLI_API_OPTIONS);
|
|
323
|
+
if (databases.length === 0) {
|
|
324
|
+
return logEmptyState(logger, "No D1 databases found", theme);
|
|
325
|
+
}
|
|
326
|
+
logSection(logger, "D1 databases", theme, databases.length, "yellow");
|
|
327
|
+
logTable(logger, {
|
|
328
|
+
title: "Database list",
|
|
329
|
+
rows: databases,
|
|
330
|
+
columns: [
|
|
331
|
+
{ label: "Name", width: 25, value: (db) => db.name },
|
|
332
|
+
{ label: "ID", width: 40, value: (db) => whiteDim(db.id, theme) },
|
|
333
|
+
{ label: "Tables", width: 8, value: (db) => String(db.tableCount ?? "N/A") }
|
|
334
|
+
],
|
|
335
|
+
theme,
|
|
336
|
+
titleAccent: "yellow"
|
|
337
|
+
});
|
|
338
|
+
logLine(logger);
|
|
339
|
+
return { exitCode: 0 };
|
|
340
|
+
}
|
|
341
|
+
async function showR2(accountId, logger, theme) {
|
|
342
|
+
const buckets = await account.r2(accountId, CLI_API_OPTIONS);
|
|
343
|
+
if (buckets.length === 0) {
|
|
344
|
+
return logEmptyState(logger, "No R2 buckets found", theme);
|
|
345
|
+
}
|
|
346
|
+
logSection(logger, "R2 buckets", theme, buckets.length, "green");
|
|
347
|
+
logTable(logger, {
|
|
348
|
+
title: "Bucket list",
|
|
349
|
+
rows: buckets,
|
|
350
|
+
columns: [
|
|
351
|
+
{ label: "Name", width: 30, value: (bucket) => bucket.name },
|
|
352
|
+
{ label: "Created", width: 20, value: (bucket) => whiteDim(formatDate(bucket.createdOn), theme) },
|
|
353
|
+
{ label: "Location", width: 10, value: (bucket) => bucket.location ?? "auto" }
|
|
354
|
+
],
|
|
355
|
+
theme,
|
|
356
|
+
titleAccent: "green"
|
|
357
|
+
});
|
|
358
|
+
logLine(logger);
|
|
359
|
+
return { exitCode: 0 };
|
|
360
|
+
}
|
|
361
|
+
async function showVectorize(accountId, logger, theme) {
|
|
362
|
+
const indexes = await account.vectorize(accountId, CLI_API_OPTIONS);
|
|
363
|
+
if (indexes.length === 0) {
|
|
364
|
+
return logEmptyState(logger, "No Vectorize indexes found", theme);
|
|
365
|
+
}
|
|
366
|
+
logSection(logger, "Vectorize indexes", theme, indexes.length, "yellow");
|
|
367
|
+
logTable(logger, {
|
|
368
|
+
title: "Index list",
|
|
369
|
+
rows: indexes,
|
|
370
|
+
columns: [
|
|
371
|
+
{ label: "Name", width: 25, value: (idx) => idx.name },
|
|
372
|
+
{ label: "Dimensions", width: 12, value: (idx) => String(idx.dimensions) },
|
|
373
|
+
{ label: "Metric", width: 15, value: (idx) => idx.metric }
|
|
374
|
+
],
|
|
375
|
+
theme,
|
|
376
|
+
titleAccent: "yellow"
|
|
377
|
+
});
|
|
378
|
+
logLine(logger);
|
|
379
|
+
return { exitCode: 0 };
|
|
380
|
+
}
|
|
381
|
+
async function showUsage(accountId, logger, theme) {
|
|
382
|
+
const usages = await account.getAllUsageSummaries(accountId);
|
|
383
|
+
const limits = await account.getLimits(accountId);
|
|
384
|
+
logSection(logger, "Usage", theme, undefined, "yellow");
|
|
385
|
+
logLine(logger, formatLabelValue("limits", limits.enabled ? green("enabled", theme) : dim("disabled", theme), theme, 12));
|
|
386
|
+
if (usages.length === 0) {
|
|
387
|
+
return logEmptyState(logger, "No usage tracked yet", theme);
|
|
388
|
+
}
|
|
389
|
+
logTable(logger, {
|
|
390
|
+
title: "Usage by service",
|
|
391
|
+
rows: usages,
|
|
392
|
+
columns: [
|
|
393
|
+
{ label: "Service", width: 15, value: (usage) => usage.service },
|
|
394
|
+
{ label: "Today", width: 10, value: (usage) => String(usage.today) },
|
|
395
|
+
{ label: "Limit", width: 10, value: (usage) => usage.limit?.toString() ?? "∞" },
|
|
396
|
+
{ label: "%", width: 10, value: (usage) => formatPercent(usage.percentUsed) },
|
|
397
|
+
{ label: "Status", width: 10, value: (usage) => usage.withinLimit ? green("ok", theme) : yellow("limit", theme) }
|
|
398
|
+
],
|
|
399
|
+
theme,
|
|
400
|
+
titleAccent: "yellow"
|
|
401
|
+
});
|
|
402
|
+
logLine(logger);
|
|
403
|
+
return { exitCode: 0 };
|
|
404
|
+
}
|
|
405
|
+
async function handleLimits(accountId, parsed, logger, theme) {
|
|
406
|
+
const action = parsed.args[1];
|
|
407
|
+
switch (action) {
|
|
408
|
+
case "set":
|
|
409
|
+
return await setLimit(accountId, parsed, logger, theme);
|
|
410
|
+
case "enable":
|
|
411
|
+
await account.setLimitsEnabled(accountId, true);
|
|
412
|
+
logger.success("Usage limits enabled");
|
|
413
|
+
return { exitCode: 0 };
|
|
414
|
+
case "disable":
|
|
415
|
+
await account.setLimitsEnabled(accountId, false);
|
|
416
|
+
logger.success("Usage limits disabled");
|
|
417
|
+
return { exitCode: 0 };
|
|
418
|
+
default:
|
|
419
|
+
return await showLimits(accountId, logger, theme);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
async function showLimits(accountId, logger, theme) {
|
|
423
|
+
const limits = await account.getLimits(accountId);
|
|
424
|
+
logSection(logger, "Usage limits", theme, undefined, "yellow");
|
|
425
|
+
logLine(logger, formatLabelValue("status", limits.enabled ? green("enabled", theme) : dim("disabled", theme), theme, 16));
|
|
426
|
+
logLine(logger);
|
|
427
|
+
logLine(logger, dim("current limits", theme));
|
|
428
|
+
logLine(logger, formatLabelValue("AI Requests/Day", String(limits.aiRequestsPerDay ?? "Unlimited"), theme, 18));
|
|
429
|
+
logLine(logger, formatLabelValue("AI Tokens/Day", String(limits.aiTokensPerDay ?? "Unlimited"), theme, 18));
|
|
430
|
+
logLine(logger, formatLabelValue("Vectorize Ops/Day", String(limits.vectorizeOpsPerDay ?? "Unlimited"), theme, 18));
|
|
431
|
+
logSection(logger, "Commands", theme);
|
|
432
|
+
logLine(logger, formatCommand("devflare account limits set ai-requests 50", "Set the AI request daily limit", theme));
|
|
433
|
+
logLine(logger, formatCommand("devflare account limits set ai-tokens 5000", "Set the AI token daily limit", theme));
|
|
434
|
+
logLine(logger, formatCommand("devflare account limits set vectorize-ops 500", "Set the Vectorize daily limit", theme));
|
|
435
|
+
logLine(logger, formatCommand("devflare account limits enable", "Enable usage limits", theme));
|
|
436
|
+
logLine(logger, formatCommand("devflare account limits disable", "Disable usage limits", theme));
|
|
437
|
+
logLine(logger);
|
|
438
|
+
return { exitCode: 0 };
|
|
439
|
+
}
|
|
440
|
+
async function setLimit(accountId, parsed, logger, theme) {
|
|
441
|
+
const limitName = parsed.args[2];
|
|
442
|
+
const limitValue = parsed.args[3];
|
|
443
|
+
if (!limitName || !limitValue) {
|
|
444
|
+
logger.error("Usage: devflare account limits set <limit-name> <value>");
|
|
445
|
+
logLine(logger, dim("Limit names: ai-requests, ai-tokens, vectorize-ops", theme));
|
|
446
|
+
return { exitCode: 1 };
|
|
447
|
+
}
|
|
448
|
+
const value = parseInt(limitValue, 10);
|
|
449
|
+
if (isNaN(value) || value < 0) {
|
|
450
|
+
logger.error("Limit value must be a positive number");
|
|
451
|
+
return { exitCode: 1 };
|
|
452
|
+
}
|
|
453
|
+
switch (limitName) {
|
|
454
|
+
case "ai-requests":
|
|
455
|
+
await account.setLimits(accountId, { aiRequestsPerDay: value });
|
|
456
|
+
logger.success(`AI requests limit set to ${value}/day`);
|
|
457
|
+
break;
|
|
458
|
+
case "ai-tokens":
|
|
459
|
+
await account.setLimits(accountId, { aiTokensPerDay: value });
|
|
460
|
+
logger.success(`AI tokens limit set to ${value}/day`);
|
|
461
|
+
break;
|
|
462
|
+
case "vectorize-ops":
|
|
463
|
+
await account.setLimits(accountId, { vectorizeOpsPerDay: value });
|
|
464
|
+
logger.success(`Vectorize ops limit set to ${value}/day`);
|
|
465
|
+
break;
|
|
466
|
+
default:
|
|
467
|
+
logger.error(`Unknown limit: ${limitName}`);
|
|
468
|
+
logLine(logger, dim("Valid limits: ai-requests, ai-tokens, vectorize-ops", theme));
|
|
469
|
+
return { exitCode: 1 };
|
|
470
|
+
}
|
|
471
|
+
return { exitCode: 0 };
|
|
472
|
+
}
|
|
473
|
+
export {
|
|
474
|
+
runAccountCommand
|
|
475
|
+
};
|