@sequenceholdings/studio-cli 0.1.24 → 0.1.26
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 -6
- package/dist/app/commands.js +5 -2
- package/dist/app/readme.d.ts +33 -0
- package/dist/app/readme.js +294 -0
- package/dist/app/scaffold.js +116 -13
- package/dist/auth.d.ts +9 -5
- package/dist/auth.js +121 -8
- package/dist/config.d.ts +7 -3
- package/dist/config.js +77 -5
- package/dist/envs/commands.js +9 -6
- package/dist/functions/bundle.js +3 -3
- package/dist/functions/commands.js +18 -5
- package/dist/functions/manifest.d.ts +3 -0
- package/dist/functions/manifest.js +48 -0
- package/dist/login.js +3 -3
- package/dist/pipeline/commands.js +6 -2
- package/dist/pipeline/lifecycle.js +93 -16
- package/dist/preview.d.ts +4 -3
- package/dist/preview.js +5 -4
- package/dist/repos/commands.d.ts +1 -1
- package/dist/repos/commands.js +1 -1
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -150,14 +150,19 @@ without it.
|
|
|
150
150
|
|
|
151
151
|
| Command | What it does |
|
|
152
152
|
|---------|--------------|
|
|
153
|
-
| `seq-studio init <dir> --with <kinds>` | Scaffold an app monorepo + `sequence.app.yml` + selected primitive subfolders |
|
|
153
|
+
| `seq-studio init <dir> --with <kinds>` | Scaffold an app monorepo + `sequence.app.yml` + a Sequence app `README.md` + selected primitive subfolders |
|
|
154
154
|
| `seq-studio add function|artifact <name>` | Add a function or Artifact Studio UI to the current app (updates the manifest) |
|
|
155
155
|
| `seq-studio deploy -e <env> [--yes] [--only id1,id2] [--dry-run]` | Deploy every primitive in `deploy.order` (orm → functions → artifact) |
|
|
156
156
|
|
|
157
157
|
`init` supports v0 kinds `orm`, `function`, and `artifact`; pass them as a
|
|
158
158
|
comma-list (`--with orm,function,artifact`) or boolean flags (`--orm`). `add`
|
|
159
159
|
currently supports functions and one Artifact Studio UI; ORM is created only by
|
|
160
|
-
`init`.
|
|
160
|
+
`init`. Creating a platform git-service repo (`seq-studio repos create` or the
|
|
161
|
+
Repositories UI) writes the same class of README into the initial commit, so a
|
|
162
|
+
fresh clone already has the primitives / [docs.seqholdings.com](https://docs.seqholdings.com/)
|
|
163
|
+
guide. `seq-studio init .` is allowed in a clone whose README contains the
|
|
164
|
+
seq-studio seed marker; it merges Sequence ignores into an existing
|
|
165
|
+
`.gitignore` and will not overwrite an ordinary docs-only README.
|
|
161
166
|
|
|
162
167
|
```bash
|
|
163
168
|
# Full-stack toy (ORM + function + artifact) — workspace / internal CLI only
|
|
@@ -405,6 +410,27 @@ limits:
|
|
|
405
410
|
`min_instances` defaults to `0`, cannot exceed `max_instances`, and incurs
|
|
406
411
|
Cloud Run idle-instance charges while warm.
|
|
407
412
|
|
|
413
|
+
### Invocation IP allowlist
|
|
414
|
+
|
|
415
|
+
Declare the function-level source-IP restriction in `managed-function.yml`:
|
|
416
|
+
|
|
417
|
+
```yaml
|
|
418
|
+
invocation:
|
|
419
|
+
allowed_ip_ranges:
|
|
420
|
+
- 203.0.113.0/24
|
|
421
|
+
- 2001:db8::/48
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
`seq-studio functions build` validates IPv4/IPv6 addresses and CIDRs locally;
|
|
425
|
+
Atlas performs authoritative normalization during upload. `/0` ranges are
|
|
426
|
+
rejected. Omitting `invocation` or declaring `allowed_ip_ranges: []` means
|
|
427
|
+
unrestricted source IPs.
|
|
428
|
+
|
|
429
|
+
The policy is part of the immutable function version. It becomes live atomically
|
|
430
|
+
when that version activates, a failed deploy leaves the current policy unchanged,
|
|
431
|
+
and promote or rollback restores the selected version's policy. The gateway
|
|
432
|
+
checks a non-empty policy before invocation permissions.
|
|
433
|
+
|
|
408
434
|
### Server-owned resource authorization
|
|
409
435
|
|
|
410
436
|
Some functions expose regulated external resources whose authorization must be
|
|
@@ -588,7 +614,7 @@ JSON API — the same repos `--repo <ns>/<name>` sources build from.
|
|
|
588
614
|
| `seq-studio repos list -e <env> [--namespace <slug>] [--mine]` | repos visible on the environment (permission-filtered) |
|
|
589
615
|
| `seq-studio repos namespaces [create <slug>] -e <env>` | list namespaces, or create one (creator becomes owner) |
|
|
590
616
|
| `seq-studio repos show <ns>/<name> -e <env>` | detail: id, branches, clone URL; artifact project id when slug matches |
|
|
591
|
-
| `seq-studio repos create <ns>/<name> -e <env> [--default-branch <b>]` | create
|
|
617
|
+
| `seq-studio repos create <ns>/<name> -e <env> [--default-branch <b>]` | create a repo seeded with a Sequence app README (needs namespace write) |
|
|
592
618
|
| `seq-studio repos clone <ns>/<name> \| --url <clone-url> \| --id <uuid> -e <env> [--ref <r>] [--out <dir>] [--force]` | smart-HTTP `git clone` when `ATLAS_GIT_PAT` is set (`--url`/`--id` need no seqapi); otherwise JSON materialize + PAT hint |
|
|
593
619
|
| `seq-studio repos pull <ns>/<name> -e <env> [--ref <r>] [--out <dir>] [--force]` | always materialize via JSON API (no `.git` dir); refuses a non-empty destination unless `--force` |
|
|
594
620
|
| `seq-studio repos delete <ns>/<name> -e <env> [--yes]` | delete a repo — interactive confirm unless `--yes` |
|
|
@@ -632,8 +658,8 @@ install it alongside the CLI to use this family.
|
|
|
632
658
|
|---------|--------------|
|
|
633
659
|
| `seq-studio pipeline init --type ingestion\|transformation\|serving <name> [--dir <dir>]` | Scaffold `<name>.stage.yml` (commented per-kind template) plus a `src/` Databricks-notebook entrypoint stub (begins with `# Databricks notebook source`; serving stages are declarative — no stub). Refuses to overwrite an existing spec |
|
|
634
660
|
| `seq-studio pipeline validate [dir] [--assets <file\|url>] [--orm-contracts <file\|url>] [--json]` | Run the full offline spec gate: envelope + body validation, `schema_ref` resolution, and repo-level graph validation (reference resolution, single-writer, cycles, column subsets, serving projection checks). Auto-loads `orm-contracts.json` from the pipeline dir when present. Exit 0/1 |
|
|
635
|
-
| `seq-studio pipeline plan --repo pipelines/<slug> --ref <sha\|branch> -e <env> [--target <id>] [--json]` |
|
|
636
|
-
| `seq-studio pipeline deploy --repo pipelines/<slug> --ref <sha\|branch> -e <env> [--target <id>] [--approved-by <sub>] [--no-wait]` | Plan then enqueue deploy; Trigger runs `bundle validate` then `bundle deploy` against reviewed bytes. Polls to terminal unless `--no-wait`. Targets that require approval need `--approved-by` naming the authenticated caller. |
|
|
661
|
+
| `seq-studio pipeline plan --repo pipelines/<slug> --ref <sha\|branch> -e <env> [--target <id>] [--json]` | Enqueue and poll a durable Pipeline plan (materialize → SDK/`validateSpecGraph` → compile → live-diff → provision findings). Fails closed listing **every** missing target binding (alert channels, workspace, Databricks `source.credential`) plus Data Sync edge-worker machine/operation/`credEnvFamilies` mismatches before registry writes. Does **not** run Databricks `bundle validate` (that is a Trigger deploy-path hard gate). Exit 1 on destructive findings (CI-safe). `--json` emits the completed stable plan envelope |
|
|
662
|
+
| `seq-studio pipeline deploy --repo pipelines/<slug> --ref <sha\|branch> -e <env> [--target <id>] [--approved-by <sub>] [--run-now] [--no-wait]` | Plan then enqueue deploy; Trigger runs `bundle validate` then `bundle deploy` against reviewed bytes. Default is plant-only (jobs are created, not run). `--run-now` runs in-unit producer roots after bundle deploy and waits before serving sync create. Polls to terminal unless `--no-wait`. Targets that require approval need `--approved-by` naming the authenticated caller. |
|
|
637
663
|
| `seq-studio pipeline adopt --stage <slug> --ref <sha\|branch> -e <env> [--target <id>] --native-id <id> --approved-by <you> [--resource-key <key>] [--kind job\|dlt_pipeline] [--old-source-removal-pr <url>] [--repo pipelines/<slug>]` | Bind a live Databricks job/pipeline into the stage without recreation (`bundle deployment bind` on Trigger). Always requires `--approved-by` naming the caller. When the key is still in the monorepo DAB, pass `--old-source-removal-pr` and follow the returned cutover checklist: unbind the old bundle state without deleting the remote, then remove its DAB declaration and add the target-specific adopted-resource entry in the same PR before redeploying. |
|
|
638
664
|
| `seq-studio pipeline unbind --stage <slug> --ref <sha\|branch> -e <env> [--target <id>] --approved-by <you> [--resource-key <key>] [--repo pipelines/<slug>]` | Release an adopted binding on Trigger; the remote object stays live (never deleted) |
|
|
639
665
|
| `seq-studio pipeline run-now --stage <slug> -e <env> [--target <id>] [--repo pipelines/<slug>] [--json]` | Run the stage's active job or DLT pipeline immediately and print its Databricks run URL |
|
|
@@ -644,7 +670,7 @@ install it alongside the CLI to use this family.
|
|
|
644
670
|
pipeline target advertised by that endpoint. It is optional when the alias
|
|
645
671
|
matches a target id or when the endpoint has exactly one target.
|
|
646
672
|
|
|
647
|
-
Unless `--no-wait` is set, `deploy`, `promote`, and `rollback` report status or
|
|
673
|
+
Unless `--no-wait` is set, `plan`, `deploy`, `promote`, and `rollback` report status or
|
|
648
674
|
status-detail changes while waiting, then emit a 20-second progress heartbeat.
|
|
649
675
|
Terminal output includes the deployment ID and elapsed time; failures include
|
|
650
676
|
the status detail, and any available Trigger run ID is shown. `--json` output
|
package/dist/app/commands.js
CHANGED
|
@@ -15,8 +15,11 @@ export const APP_INIT_USAGE = `usage:
|
|
|
15
15
|
seq-studio init <dir> --with <kinds> [options]
|
|
16
16
|
seq-studio init <dir> --orm --function --artifact [options]
|
|
17
17
|
|
|
18
|
-
Scaffold an app monorepo with ${APP_MANIFEST_FILENAME}
|
|
19
|
-
|
|
18
|
+
Scaffold an app monorepo with ${APP_MANIFEST_FILENAME}, a Sequence app
|
|
19
|
+
README.md (docs.seqholdings.com + primitives), and one subfolder per
|
|
20
|
+
selected primitive. At least one kind is required. A git-service clone
|
|
21
|
+
whose README.md contains the seq-studio seed marker is a valid target;
|
|
22
|
+
ordinary docs-only repositories are refused so we do not overwrite them.
|
|
20
23
|
|
|
21
24
|
Kinds: ${PRIMITIVE_KINDS.join(', ')}
|
|
22
25
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* README seeded by `seq-studio init` (and kept equivalent to the git-service
|
|
3
|
+
* createRepo initial commit). Third-party developers should be able to clone
|
|
4
|
+
* a new app repo and start building on Artifact Studio, the ORM, and
|
|
5
|
+
* Managed Functions without guessing the platform model.
|
|
6
|
+
*
|
|
7
|
+
* Keep in sync with atlas/src/server/services/git-service/initial-readme.ts.
|
|
8
|
+
*/
|
|
9
|
+
import type { PrimitiveKind } from './manifest.js';
|
|
10
|
+
export declare const SEQUENCE_DOCS_URL = "https://docs.seqholdings.com/";
|
|
11
|
+
/**
|
|
12
|
+
* Hidden marker written into the git-service createRepo README.
|
|
13
|
+
* `seq-studio init .` only overwrites a non-empty target when this is present,
|
|
14
|
+
* so an ordinary docs-only repository is not treated as a seed clone.
|
|
15
|
+
*
|
|
16
|
+
* Keep identical to atlas/src/server/services/git-service/initial-readme.ts.
|
|
17
|
+
*/
|
|
18
|
+
export declare const GIT_SERVICE_SEED_MARKER = "<!-- seq-studio:git-service-seed -->";
|
|
19
|
+
export declare function isGitServiceSeedReadme(body: string): boolean;
|
|
20
|
+
export declare const README_REQUIRED_SECTIONS: readonly ["https://docs.seqholdings.com/", "Artifact Studio", "ORM", "Managed Function", "seq-studio init", "seq.functions.invoke"];
|
|
21
|
+
export interface AppReadmeInput {
|
|
22
|
+
appId: string;
|
|
23
|
+
title?: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
primitives: readonly PrimitiveKind[];
|
|
26
|
+
functionName?: string;
|
|
27
|
+
}
|
|
28
|
+
export declare function renderAppReadme(input: AppReadmeInput): string;
|
|
29
|
+
/** README for a brand-new git-service repo that does not yet have an app scaffold. */
|
|
30
|
+
export declare function renderEmptyRepoReadme({ repoName, namespaceSlug, }: {
|
|
31
|
+
repoName: string;
|
|
32
|
+
namespaceSlug?: string;
|
|
33
|
+
}): string;
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* README seeded by `seq-studio init` (and kept equivalent to the git-service
|
|
3
|
+
* createRepo initial commit). Third-party developers should be able to clone
|
|
4
|
+
* a new app repo and start building on Artifact Studio, the ORM, and
|
|
5
|
+
* Managed Functions without guessing the platform model.
|
|
6
|
+
*
|
|
7
|
+
* Keep in sync with atlas/src/server/services/git-service/initial-readme.ts.
|
|
8
|
+
*/
|
|
9
|
+
import { titleizeSlug } from './manifest.js';
|
|
10
|
+
export const SEQUENCE_DOCS_URL = 'https://docs.seqholdings.com/';
|
|
11
|
+
/**
|
|
12
|
+
* Hidden marker written into the git-service createRepo README.
|
|
13
|
+
* `seq-studio init .` only overwrites a non-empty target when this is present,
|
|
14
|
+
* so an ordinary docs-only repository is not treated as a seed clone.
|
|
15
|
+
*
|
|
16
|
+
* Keep identical to atlas/src/server/services/git-service/initial-readme.ts.
|
|
17
|
+
*/
|
|
18
|
+
export const GIT_SERVICE_SEED_MARKER = '<!-- seq-studio:git-service-seed -->';
|
|
19
|
+
export function isGitServiceSeedReadme(body) {
|
|
20
|
+
return body.includes(GIT_SERVICE_SEED_MARKER);
|
|
21
|
+
}
|
|
22
|
+
export const README_REQUIRED_SECTIONS = [
|
|
23
|
+
'https://docs.seqholdings.com/',
|
|
24
|
+
'Artifact Studio',
|
|
25
|
+
'ORM',
|
|
26
|
+
'Managed Function',
|
|
27
|
+
'seq-studio init',
|
|
28
|
+
'seq.functions.invoke',
|
|
29
|
+
];
|
|
30
|
+
export function renderAppReadme(input) {
|
|
31
|
+
const title = input.title?.trim() || titleizeSlug(input.appId);
|
|
32
|
+
const description = input.description?.trim() ||
|
|
33
|
+
'A Sequence Platform app. Keep source in Git and deploy with seq-studio.';
|
|
34
|
+
const kinds = new Set(input.primitives);
|
|
35
|
+
const functionName = input.functionName ?? 'hello';
|
|
36
|
+
const tree = renderAppTree({
|
|
37
|
+
appId: input.appId,
|
|
38
|
+
kinds,
|
|
39
|
+
functionName,
|
|
40
|
+
});
|
|
41
|
+
const primitiveRows = [
|
|
42
|
+
kinds.has('artifact')
|
|
43
|
+
? '| **Artifact Studio** | Frontend | `artifact/` — React UI + `artifact.bundle.yml` |'
|
|
44
|
+
: null,
|
|
45
|
+
kinds.has('orm')
|
|
46
|
+
? `| **ORM** | Data | \`orm/${snake(input.appId)}/\` — tables, policies, named GraphQL operations |`
|
|
47
|
+
: null,
|
|
48
|
+
kinds.has('function')
|
|
49
|
+
? `| **Managed Function** | Backend | \`functions/${functionName}/\` — TypeScript handler + \`managed-function.yml\` |`
|
|
50
|
+
: null,
|
|
51
|
+
]
|
|
52
|
+
.filter((row) => row !== null)
|
|
53
|
+
.join('\n');
|
|
54
|
+
return `# ${title}
|
|
55
|
+
|
|
56
|
+
${description}
|
|
57
|
+
|
|
58
|
+
This folder is a **seq-studio app**. You keep source in Git (this repository, or a platform git-service clone). You validate, plan, and deploy with \`seq-studio\`. You do not need a checkout of Sequence Platform itself.
|
|
59
|
+
|
|
60
|
+
**Platform documentation:** [${SEQUENCE_DOCS_URL}](${SEQUENCE_DOCS_URL})
|
|
61
|
+
|
|
62
|
+
You still need an environment name, an Atlas URL, and resource grants from your platform administrator before Preview or deploy. Login proves identity; it does not grant every action.
|
|
63
|
+
|
|
64
|
+
## What this repository is
|
|
65
|
+
|
|
66
|
+
A Sequence app is a folder with a \`sequence.app.yml\` manifest plus one subdirectory per platform primitive. The CLI owns that manifest (\`seq-studio init\` / \`seq-studio add\`). Do not invent a new layout by hand.
|
|
67
|
+
|
|
68
|
+
\`\`\`
|
|
69
|
+
${tree}
|
|
70
|
+
\`\`\`
|
|
71
|
+
|
|
72
|
+
This app currently includes:
|
|
73
|
+
|
|
74
|
+
| Primitive | Role | Where it lives |
|
|
75
|
+
| --- | --- | --- |
|
|
76
|
+
${primitiveRows}
|
|
77
|
+
|
|
78
|
+
Add another function later with \`seq-studio add function <name>\` from this directory. \`seq-studio add\` supports \`function\` and \`artifact\` only — an ORM namespace is created at init.
|
|
79
|
+
|
|
80
|
+
## Platform primitives
|
|
81
|
+
|
|
82
|
+
Build each app from these three primitives. Use only the ones the product needs.
|
|
83
|
+
|
|
84
|
+
| Primitive | Role | What you write | What the platform runs |
|
|
85
|
+
| --- | --- | --- | --- |
|
|
86
|
+
| **Artifact Studio** | Frontend | React UI + \`artifact.bundle.yml\` | Sandboxed browser app inside Atlas |
|
|
87
|
+
| **ORM** | Data | TypeScript tables, row policies, named GraphQL operations | Governed Data API in front of Postgres (you never open a connection) |
|
|
88
|
+
| **Managed Function** | Backend | TypeScript handler + \`managed-function.yml\` | Server-side code with declared secrets, egress, and invoke permissions |
|
|
89
|
+
|
|
90
|
+
\`\`\`
|
|
91
|
+
Browser user
|
|
92
|
+
│
|
|
93
|
+
▼
|
|
94
|
+
Artifact Studio UI
|
|
95
|
+
│ seq.api.* / seq.functions.invoke (declared capabilities only)
|
|
96
|
+
├──► ORM Data API persisted rows, policy-scoped to the caller
|
|
97
|
+
└──► Managed Function secrets, egress, work that must not run in the browser
|
|
98
|
+
\`\`\`
|
|
99
|
+
|
|
100
|
+
**Artifact Studio** is the screen. In Atlas the UI runs in a sandboxed iframe and talks to the platform through \`seq\` from \`@sequenceholdings/artifact-studio\`. It has no raw network access and must not contain secrets. Every table, operation, API path, or function the UI uses must be listed in \`artifact.bundle.yml\` first. Prefer \`@sequenceholdings/atlas-ui\`. Docs: [Artifact Studio](${SEQUENCE_DOCS_URL}artifact-studio/overview).
|
|
101
|
+
|
|
102
|
+
**ORM** is the data layer. You author tables and policies in TypeScript (\`defineNamespace\`, \`defineTable\`) and persist reads/writes as named GraphQL documents. \`seq-studio orm apply\` compiles that into a reviewed migration and publishes persisted operations. The UI calls \`{ op: <hash>, variables }\` on the Atlas Data API. Policies are the row boundary — stamp identity from \`claim('sub')\` rather than trusting the browser. There is no ad-hoc SQL; exotic writes go through a declared table action. Public docs do not yet have an ORM guide; \`seq-studio orm --help\` and a reference app (for example Pokedex: Artifact UI + trainer-scoped \`pokemon\` rows) are the working model.
|
|
103
|
+
|
|
104
|
+
**Managed Function** is the backend. Use it for an external API, a write-only secret, or any work that must not run in the browser. The handler receives JSON and returns JSON. The manifest declares secret *names* (never values) and an egress allowlist — functions do not get arbitrary outbound network. Docs: [Managed Functions](${SEQUENCE_DOCS_URL}managed-functions/overview), [Managed Secrets](${SEQUENCE_DOCS_URL}managed-secrets/overview).
|
|
105
|
+
|
|
106
|
+
How the examples split:
|
|
107
|
+
|
|
108
|
+
| Example | Primitives | Use it for |
|
|
109
|
+
| --- | --- | --- |
|
|
110
|
+
| Pokedex-style app | Artifact + ORM | UI that reads and writes governed tables |
|
|
111
|
+
| [Basketball player lookup](${SEQUENCE_DOCS_URL}examples/basketball-player-lookup) | Artifact + Function + Secret | UI that calls protected server-side code |
|
|
112
|
+
| \`seq-studio init --with orm,function,artifact\` | All three | The intended app shape, once the ORM package is available (see below) |
|
|
113
|
+
|
|
114
|
+
A function calling the ORM under a service account is **not** a supported public path yet. Keep user-scoped table access in the artifact; keep secrets and egress in the function.
|
|
115
|
+
|
|
116
|
+
The public [CLI command reference](${SEQUENCE_DOCS_URL}seq-studio/commands) covers standalone \`artifact\` / \`functions\` / \`secrets\` commands. App-monorepo verbs (\`init\`, \`add\`, \`deploy\`) and \`orm\` are on \`seq-studio --help\`. Prefer \`--help\` when the two disagree.
|
|
117
|
+
|
|
118
|
+
## Before you write code
|
|
119
|
+
|
|
120
|
+
Ask your administrator for:
|
|
121
|
+
|
|
122
|
+
1. **Environment name** — the exact \`-e\` / \`--env\` value. Do not invent it from this repository name.
|
|
123
|
+
2. **Atlas URL** — the origin used with \`seq-studio envs add\`. It is not docs.seqholdings.com. The CLI discovers login config at \`{url}/api/auth/cli-config\`.
|
|
124
|
+
3. **What you should create** — new Artifact Studio project, new function, new ORM namespace, or updates to existing ones.
|
|
125
|
+
4. **Grants** — create/deploy an artifact; runtime access to open the deployed UI; create/deploy/invoke a function; create secrets if you need them; apply an ORM namespace if you will persist data; permission to **share** the artifact (scaffolds create \`visibility: private\`, and a later deploy will not change that).
|
|
126
|
+
5. **\`@sequenceholdings/orm\` v2** — \`seq-studio init --with orm\` and namespace \`package.json\` files depend on \`@sequenceholdings/orm@^2.0.0\`, which is **not published**. Public npm's latest is a v1 package. Without a v2 tarball you can still ship Artifact + Function apps.
|
|
127
|
+
|
|
128
|
+
\`local\`, \`staging\`, \`production\`, and \`banksouth\` are **reserved built-in CLI names**. \`seq-studio envs add banksouth …\` fails. Use the OpCo name you were given.
|
|
129
|
+
|
|
130
|
+
See [Before you build](${SEQUENCE_DOCS_URL}getting-started/understand-your-workspace).
|
|
131
|
+
|
|
132
|
+
## Install and sign in
|
|
133
|
+
|
|
134
|
+
| Tool | Requirement |
|
|
135
|
+
| --- | --- |
|
|
136
|
+
| Node.js | 20 or later (22 is the documented baseline) |
|
|
137
|
+
| pnpm | 10.28.2 |
|
|
138
|
+
| Git | Required for official Artifact Studio deploys |
|
|
139
|
+
| \`seq-studio\` | \`@sequenceholdings/studio-cli\` (match \`studio.min_cli\` in \`sequence.app.yml\`) |
|
|
140
|
+
|
|
141
|
+
\`\`\`bash
|
|
142
|
+
corepack enable
|
|
143
|
+
corepack prepare pnpm@10.28.2 --activate
|
|
144
|
+
pnpm setup # once; restart the terminal afterwards
|
|
145
|
+
pnpm add -g @sequenceholdings/studio-cli
|
|
146
|
+
seq-studio version
|
|
147
|
+
|
|
148
|
+
seq-studio envs add <environment-name> <atlas-url>
|
|
149
|
+
seq-studio login --env <environment-name>
|
|
150
|
+
seq-studio artifact list -e <environment-name>
|
|
151
|
+
seq-studio functions list -e <environment-name>
|
|
152
|
+
seq-studio doctor -e <environment-name>
|
|
153
|
+
\`\`\`
|
|
154
|
+
|
|
155
|
+
Details: [Install seq-studio](${SEQUENCE_DOCS_URL}getting-started/prerequisites), [Authenticate](${SEQUENCE_DOCS_URL}getting-started/authentication).
|
|
156
|
+
|
|
157
|
+
\`local\` (\`http://localhost:5001\`) is a real Atlas process, not Vite. After init the CLI may print \`seq-studio deploy -e local --yes\` — ignore that unless you actually run Atlas locally.
|
|
158
|
+
|
|
159
|
+
## Implement this app
|
|
160
|
+
|
|
161
|
+
${renderImplementSection({ kinds, functionName, appId: input.appId })}
|
|
162
|
+
|
|
163
|
+
## Everyday loop
|
|
164
|
+
|
|
165
|
+
| You want to… | Command |
|
|
166
|
+
| --- | --- |
|
|
167
|
+
| Validate / build an artifact | \`seq-studio artifact validate artifact\` / \`artifact build artifact\` |
|
|
168
|
+
| Preview uncommitted UI | \`seq-studio artifact dev artifact -e <env>\` |
|
|
169
|
+
| Official UI deploy | \`seq-studio artifact deploy artifact -e <env>\` from clean \`main\` |
|
|
170
|
+
| Build / deploy a function | \`seq-studio functions build --dir functions/<name>\` then \`functions deploy --dir … -e <env>\` |
|
|
171
|
+
| Function logs | \`seq-studio functions logs --fn <slug> -e <env>\` |
|
|
172
|
+
| Apply an ORM namespace | \`seq-studio orm apply orm/<namespace> -e <env>\` |
|
|
173
|
+
| Deploy every primitive | \`seq-studio deploy -e <env>\` from this directory |
|
|
174
|
+
|
|
175
|
+
Official Artifact Studio deploys require a **clean \`main\`** with a committed revision. Uncommitted work is Preview (\`artifact dev\`). Open the URL the CLI prints.
|
|
176
|
+
|
|
177
|
+
## Rules that waste a day if you skip them
|
|
178
|
+
|
|
179
|
+
- **Manifests are the authorization surface.** A new \`seq.api\` call, ORM mutation, secret, or outbound host in source is not enough. Update \`artifact.bundle.yml\` or \`managed-function.yml\` and validate again.
|
|
180
|
+
- **Never commit secret values.** \`.env\` is gitignored. Never put secrets in the artifact — that bundle is browser code.
|
|
181
|
+
- **Delete the Chainguard \`.npmrc\`** in a new function unless you have Sequence-internal registry credentials. Without them \`pnpm install\` 401s. Removing the file is the supported public path.
|
|
182
|
+
- **Invoke functions with \`seq.functions.invoke('<slug>', body)\`**, not a raw \`seq.api.post\` to \`/api/managed-functions/…\`. Declare the slug under \`capabilities.functions.invoke\`.
|
|
183
|
+
- **Atlas wraps many \`seq.api\` responses** as \`{ success, data }\`. Use the scaffold \`unwrap\` helper. \`seq.functions.invoke\` is already the payload.
|
|
184
|
+
- **Do not reuse another app's identities.** If you copy files, change \`app.id\`, every primitive \`id\`, the ORM \`namespace\`, the artifact \`project_id\`, capability paths, and \`targets\`.
|
|
185
|
+
- **Do not edit ORM \`migrations/\`.** Change \`schema/*.ts\` / \`graphql/**/*.ts\`, then generate/apply.
|
|
186
|
+
- **Do not hardcode user emails** in policies or UI. Use \`claim('sub')\` / \`claim('org')\` and the Permissions / sharing UI.
|
|
187
|
+
|
|
188
|
+
## Further reading
|
|
189
|
+
|
|
190
|
+
- [Sequence Platform docs](${SEQUENCE_DOCS_URL})
|
|
191
|
+
- [Create your first project](${SEQUENCE_DOCS_URL}projects/create-first-project)
|
|
192
|
+
- [Artifact Studio](${SEQUENCE_DOCS_URL}artifact-studio/overview)
|
|
193
|
+
- [Managed Functions](${SEQUENCE_DOCS_URL}managed-functions/overview)
|
|
194
|
+
- [Managed Secrets](${SEQUENCE_DOCS_URL}managed-secrets/overview)
|
|
195
|
+
- [Basketball player lookup](${SEQUENCE_DOCS_URL}examples/basketball-player-lookup)
|
|
196
|
+
- [CLI command reference](${SEQUENCE_DOCS_URL}seq-studio/commands)
|
|
197
|
+
- [Troubleshooting](${SEQUENCE_DOCS_URL}troubleshooting)
|
|
198
|
+
`;
|
|
199
|
+
}
|
|
200
|
+
/** README for a brand-new git-service repo that does not yet have an app scaffold. */
|
|
201
|
+
export function renderEmptyRepoReadme({ repoName, namespaceSlug, }) {
|
|
202
|
+
const title = titleizeSlug(repoName);
|
|
203
|
+
const qualified = namespaceSlug ? `${namespaceSlug}/${repoName}` : repoName;
|
|
204
|
+
return `${GIT_SERVICE_SEED_MARKER}\n${renderAppReadme({
|
|
205
|
+
appId: repoName,
|
|
206
|
+
title,
|
|
207
|
+
description: `Sequence Platform git-service repository \`${qualified}\`. Scaffold an app in this clone with \`seq-studio init\`, then validate and deploy with the same CLI.`,
|
|
208
|
+
primitives: ['orm', 'function', 'artifact'],
|
|
209
|
+
functionName: 'hello',
|
|
210
|
+
}).replace('## Implement this app', `## Scaffold the app
|
|
211
|
+
|
|
212
|
+
This repository was created empty except for this README. From the clone root:
|
|
213
|
+
|
|
214
|
+
\`\`\`bash
|
|
215
|
+
# into this directory (keeps the git-service remote)
|
|
216
|
+
seq-studio init . --with function,artifact --function-name hello --description "${title}"
|
|
217
|
+
# or the intended three-primitive shape, once @sequenceholdings/orm v2 is available:
|
|
218
|
+
# seq-studio init . --with orm,function,artifact --function-name hello
|
|
219
|
+
\`\`\`
|
|
220
|
+
|
|
221
|
+
\`init\` into a clone that only has this seed README (and \`.git\`) is allowed — it writes \`sequence.app.yml\` and the primitive folders, and replaces this file with an app-specific copy. \`init\` will not overwrite a README that does not contain the seed marker.
|
|
222
|
+
|
|
223
|
+
## Implement this app`)}`;
|
|
224
|
+
}
|
|
225
|
+
function snake(appId) {
|
|
226
|
+
return appId.replace(/-/g, '_');
|
|
227
|
+
}
|
|
228
|
+
function renderAppTree({ appId, kinds, functionName, }) {
|
|
229
|
+
const lines = [
|
|
230
|
+
`${appId}/`,
|
|
231
|
+
' README.md',
|
|
232
|
+
' sequence.app.yml primitives + deploy order',
|
|
233
|
+
];
|
|
234
|
+
if (kinds.has('artifact')) {
|
|
235
|
+
lines.push(' artifact/ Artifact Studio UI');
|
|
236
|
+
}
|
|
237
|
+
if (kinds.has('orm')) {
|
|
238
|
+
lines.push(` orm/${snake(appId)}/ ORM namespace`);
|
|
239
|
+
}
|
|
240
|
+
if (kinds.has('function')) {
|
|
241
|
+
lines.push(` functions/${functionName}/ Managed Function`);
|
|
242
|
+
}
|
|
243
|
+
return lines.join('\n');
|
|
244
|
+
}
|
|
245
|
+
function renderImplementSection({ kinds, functionName, appId, }) {
|
|
246
|
+
const steps = [];
|
|
247
|
+
if (kinds.has('function')) {
|
|
248
|
+
steps.push(`1. **Function** — implement \`functions/${functionName}/index.ts\`. Declare every outbound host under \`egress\` in \`managed-function.yml\` (the scaffold ships with none). Declare secret *names* in \`secrets:\` and keep values in an ignored \`functions/${functionName}/.env\`.
|
|
249
|
+
|
|
250
|
+
**Before \`pnpm install\`, delete \`functions/${functionName}/.npmrc\`** unless you have Sequence-internal Chainguard credentials. Without them that install 401s. Removing the file is the supported public path — the deploy worker re-resolves dependencies server-side.`);
|
|
251
|
+
}
|
|
252
|
+
if (kinds.has('orm')) {
|
|
253
|
+
steps.push(`2. **ORM** — author tables and named operations under \`orm/${snake(appId)}/\` (\`sequence.config.ts\`, \`schema/*.ts\`, \`graphql/**/*.ts\`). Then:
|
|
254
|
+
|
|
255
|
+
\`\`\`bash
|
|
256
|
+
seq-studio orm generate orm/${snake(appId)}
|
|
257
|
+
seq-studio orm plan orm/${snake(appId)} -e <environment-name>
|
|
258
|
+
seq-studio orm apply orm/${snake(appId)} -e <environment-name>
|
|
259
|
+
\`\`\`
|
|
260
|
+
|
|
261
|
+
Grant the artifact matching \`capabilities.data\` entries (\`<namespace>/<table>\` for reads, \`<namespace>/ops/<MutationName>\` for named writes) and the Data API paths you call.`);
|
|
262
|
+
}
|
|
263
|
+
if (kinds.has('artifact')) {
|
|
264
|
+
const n = steps.length + 1;
|
|
265
|
+
steps.push(`${n}. **UI** — \`artifact/src/App.tsx\` with \`@sequenceholdings/atlas-ui\`. Delete the scaffold's \`alert()\` Atlas ping; native \`alert\` / \`confirm\` / \`prompt\` are blocked in the sandbox.
|
|
266
|
+
|
|
267
|
+
If this app has a function, declare it and invoke it:
|
|
268
|
+
|
|
269
|
+
\`\`\`yaml
|
|
270
|
+
capabilities:
|
|
271
|
+
functions:
|
|
272
|
+
invoke:
|
|
273
|
+
- ${functionName}
|
|
274
|
+
\`\`\`
|
|
275
|
+
|
|
276
|
+
\`\`\`ts
|
|
277
|
+
import { seq } from './lib/api'
|
|
278
|
+
const result = await seq.functions.invoke('${functionName}', { /* input */ })
|
|
279
|
+
\`\`\`
|
|
280
|
+
|
|
281
|
+
\`pnpm dev\` in \`artifact/\` is standalone Vite. Platform calls only work inside Preview or an official Atlas deployment.`);
|
|
282
|
+
}
|
|
283
|
+
steps.push(`${steps.length + 1}. **Targets** — put your environment in \`artifact.bundle.yml\` \`targets\` (if this app has an artifact). Scaffolds create \`visibility: private\`. Set \`shared\` before first create if other people must open the app.
|
|
284
|
+
|
|
285
|
+
${steps.length + 2}. **Validate, then deploy** from this directory:
|
|
286
|
+
|
|
287
|
+
\`\`\`bash
|
|
288
|
+
seq-studio deploy -e <environment-name> --dry-run
|
|
289
|
+
seq-studio deploy -e <environment-name> --yes
|
|
290
|
+
\`\`\`
|
|
291
|
+
|
|
292
|
+
\`--dry-run\` lists which primitives would run. Until \`git status\` is clean on \`main\`, use \`seq-studio artifact dev artifact -e <environment-name>\` for the UI.`);
|
|
293
|
+
return steps.join('\n\n');
|
|
294
|
+
}
|
package/dist/app/scaffold.js
CHANGED
|
@@ -9,8 +9,48 @@ import { functionsInitCommand } from '../functions/commands.js';
|
|
|
9
9
|
import { parseArgs } from '../process/commands.js';
|
|
10
10
|
import { currentVersion } from '../update-check.js';
|
|
11
11
|
import { defaultDeployOrder, extendDeployOrder, loadAppManifest, ormNamespaceFromAppId, titleizeSlug, writeAppManifest, } from './manifest.js';
|
|
12
|
+
import { isGitServiceSeedReadme, renderAppReadme } from './readme.js';
|
|
12
13
|
const LOG = '[seq-studio]';
|
|
13
14
|
const APP_MANIFEST_LABEL = 'sequence.app.yml';
|
|
15
|
+
/** Metadata a checkout may have without counting as a docs-only repository. */
|
|
16
|
+
const INIT_METADATA_ONLY = new Set(['.git', '.gitattributes', '.DS_Store']);
|
|
17
|
+
/**
|
|
18
|
+
* Extra names allowed beside a git-service seed README. Presence of any of
|
|
19
|
+
* these without that seed marker is treated as a non-empty user directory.
|
|
20
|
+
*/
|
|
21
|
+
const INIT_ALLOWED_EXISTING = new Set([
|
|
22
|
+
...INIT_METADATA_ONLY,
|
|
23
|
+
'.gitignore',
|
|
24
|
+
'README.md',
|
|
25
|
+
'LICENSE',
|
|
26
|
+
'LICENSE.md',
|
|
27
|
+
]);
|
|
28
|
+
const DEFAULT_GITIGNORE_LINES = [
|
|
29
|
+
'node_modules/',
|
|
30
|
+
'dist/',
|
|
31
|
+
'.env',
|
|
32
|
+
'.env.*',
|
|
33
|
+
'!.env.example',
|
|
34
|
+
'.DS_Store',
|
|
35
|
+
'*.log',
|
|
36
|
+
];
|
|
37
|
+
/** Append missing Sequence ignores; keep existing lines and comments. */
|
|
38
|
+
function mergeGitignore(existing) {
|
|
39
|
+
const defaults = DEFAULT_GITIGNORE_LINES;
|
|
40
|
+
if (existing === null) {
|
|
41
|
+
return `${defaults.join('\n')}\n`;
|
|
42
|
+
}
|
|
43
|
+
const present = new Set(existing
|
|
44
|
+
.split(/\r?\n/)
|
|
45
|
+
.map((line) => line.trim())
|
|
46
|
+
.filter((line) => line.length > 0 && !line.startsWith('#')));
|
|
47
|
+
const missing = defaults.filter((line) => !present.has(line));
|
|
48
|
+
if (missing.length === 0) {
|
|
49
|
+
return existing.endsWith('\n') ? existing : `${existing}\n`;
|
|
50
|
+
}
|
|
51
|
+
const base = existing.endsWith('\n') ? existing : `${existing}\n`;
|
|
52
|
+
return `${base}${missing.join('\n')}\n`;
|
|
53
|
+
}
|
|
14
54
|
export function defaultScaffoldNames(_appId) {
|
|
15
55
|
return { functionName: 'hello' };
|
|
16
56
|
}
|
|
@@ -130,17 +170,36 @@ export async function initAppMonorepo({ rootDir, appId, kinds, names = defaultSc
|
|
|
130
170
|
if (existsSync(join(appRoot, 'sequence.app.yml'))) {
|
|
131
171
|
throw new Error(`${join(appRoot, 'sequence.app.yml')} already exists — refusing to re-init`);
|
|
132
172
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
173
|
+
const { readdir } = await import('node:fs/promises');
|
|
174
|
+
const existed = existsSync(appRoot);
|
|
175
|
+
const entries = existed
|
|
176
|
+
? await readdir(appRoot).catch((error) => {
|
|
136
177
|
if (error.code === 'ENOENT')
|
|
137
178
|
return [];
|
|
138
179
|
throw error;
|
|
139
|
-
})
|
|
140
|
-
|
|
141
|
-
|
|
180
|
+
})
|
|
181
|
+
: [];
|
|
182
|
+
const unexpected = entries.filter((name) => !INIT_ALLOWED_EXISTING.has(name));
|
|
183
|
+
if (unexpected.length > 0) {
|
|
184
|
+
throw new Error(`${appRoot} is not empty — init into a new directory, a git-service clone whose README contains the seq-studio seed marker, or remove existing files first`);
|
|
185
|
+
}
|
|
186
|
+
const contentful = entries.filter((name) => !INIT_METADATA_ONLY.has(name));
|
|
187
|
+
if (contentful.length > 0) {
|
|
188
|
+
const readmePath = join(appRoot, 'README.md');
|
|
189
|
+
if (!existsSync(readmePath)) {
|
|
190
|
+
throw new Error(`${appRoot} is not empty — init into a git-service clone only when README.md contains the seq-studio git-service seed marker`);
|
|
191
|
+
}
|
|
192
|
+
const existingReadme = await readFile(readmePath, 'utf8');
|
|
193
|
+
if (!isGitServiceSeedReadme(existingReadme)) {
|
|
194
|
+
throw new Error(`${appRoot} already has a README.md that is not a git-service seed — refusing to overwrite it. Init into a new directory, or keep the platform-generated README from \`seq-studio repos create\`.`);
|
|
142
195
|
}
|
|
143
196
|
}
|
|
197
|
+
const previousReadme = existsSync(join(appRoot, 'README.md'))
|
|
198
|
+
? await readFile(join(appRoot, 'README.md'), 'utf8')
|
|
199
|
+
: null;
|
|
200
|
+
const previousGitignore = existsSync(join(appRoot, '.gitignore'))
|
|
201
|
+
? await readFile(join(appRoot, '.gitignore'), 'utf8')
|
|
202
|
+
: null;
|
|
144
203
|
await mkdir(appRoot, { recursive: true });
|
|
145
204
|
try {
|
|
146
205
|
const version = currentVersion();
|
|
@@ -168,15 +227,59 @@ export async function initAppMonorepo({ rootDir, appId, kinds, names = defaultSc
|
|
|
168
227
|
},
|
|
169
228
|
};
|
|
170
229
|
await writeAppManifest({ rootDir: appRoot, manifest });
|
|
171
|
-
await writeFile(join(appRoot, '.gitignore'),
|
|
230
|
+
await writeFile(join(appRoot, '.gitignore'), mergeGitignore(previousGitignore), 'utf8');
|
|
231
|
+
await writeFile(join(appRoot, 'README.md'), renderAppReadme({
|
|
232
|
+
appId,
|
|
233
|
+
title: manifest.app.title,
|
|
234
|
+
description: manifest.app.description,
|
|
235
|
+
primitives: kinds,
|
|
236
|
+
functionName: names.functionName,
|
|
237
|
+
}), 'utf8');
|
|
172
238
|
return manifest;
|
|
173
239
|
}
|
|
174
240
|
catch (error) {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
241
|
+
if (!existed) {
|
|
242
|
+
// Directory was newly created — remove the partial scaffold.
|
|
243
|
+
await rm(appRoot, { recursive: true, force: true }).catch(() => {
|
|
244
|
+
/* best-effort */
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
// Reused a git-service clone — only remove what this invocation added.
|
|
249
|
+
await rm(join(appRoot, 'sequence.app.yml'), { force: true }).catch(() => {
|
|
250
|
+
/* best-effort */
|
|
251
|
+
});
|
|
252
|
+
for (const kind of kinds) {
|
|
253
|
+
const name = kind === 'function' ? names.functionName : appId;
|
|
254
|
+
const relPath = relativePathFor({ kind, appId, name }).replace(/\\/g, '/');
|
|
255
|
+
const top = relPath.split('/')[0];
|
|
256
|
+
if (top) {
|
|
257
|
+
await rm(join(appRoot, top), { recursive: true, force: true }).catch(() => {
|
|
258
|
+
/* best-effort */
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (previousReadme !== null) {
|
|
263
|
+
await writeFile(join(appRoot, 'README.md'), previousReadme, 'utf8').catch(() => {
|
|
264
|
+
/* best-effort */
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
await rm(join(appRoot, 'README.md'), { force: true }).catch(() => {
|
|
269
|
+
/* best-effort */
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
if (previousGitignore !== null) {
|
|
273
|
+
await writeFile(join(appRoot, '.gitignore'), previousGitignore, 'utf8').catch(() => {
|
|
274
|
+
/* best-effort */
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
await rm(join(appRoot, '.gitignore'), { force: true }).catch(() => {
|
|
279
|
+
/* best-effort */
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
}
|
|
180
283
|
throw error;
|
|
181
284
|
}
|
|
182
285
|
}
|
|
@@ -238,7 +341,7 @@ export function printInitNextSteps({ appRoot, manifest, }) {
|
|
|
238
341
|
const rel = relative(process.cwd(), appRoot);
|
|
239
342
|
const display = !rel || rel.startsWith('..') ? appRoot : rel;
|
|
240
343
|
console.log(`${LOG} scaffolded app "${manifest.app.id}" in ${display}`);
|
|
241
|
-
console.log(`${LOG} wrote ${APP_MANIFEST_LABEL} with ${manifest.primitives.length} primitive(s):`);
|
|
344
|
+
console.log(`${LOG} wrote README.md (Sequence app docs) and ${APP_MANIFEST_LABEL} with ${manifest.primitives.length} primitive(s):`);
|
|
242
345
|
for (const entry of manifest.primitives) {
|
|
243
346
|
console.log(`${LOG} - ${entry.kind.padEnd(9)} ${entry.id} (${entry.path})`);
|
|
244
347
|
}
|
package/dist/auth.d.ts
CHANGED
|
@@ -53,14 +53,17 @@ export interface SeqapiTokens {
|
|
|
53
53
|
access_token?: string;
|
|
54
54
|
expires_at?: number;
|
|
55
55
|
}
|
|
56
|
+
interface RealmResolutionOptions {
|
|
57
|
+
fetchImpl?: typeof fetch;
|
|
58
|
+
targetUrl?: string;
|
|
59
|
+
}
|
|
56
60
|
/**
|
|
57
61
|
* Resolve the auth realm for an environment name. Undefined, built-ins, and
|
|
58
|
-
* per-PR preview targets map to the shared Sequence realm
|
|
59
|
-
*
|
|
60
|
-
* other explicit name must have a valid seqapi registry entry
|
|
61
|
-
* closed so a shared Sequence bearer token can never be sent to a tenant URL.
|
|
62
|
+
* per-PR preview targets map to the shared Sequence realm, except loopback
|
|
63
|
+
* targets can advertise the isolated Sequence-staging realm used by contractor
|
|
64
|
+
* local dev. Every other explicit name must have a valid seqapi registry entry.
|
|
62
65
|
*/
|
|
63
|
-
export declare function realmForEnv(envName?: string): Promise<AuthRealm>;
|
|
66
|
+
export declare function realmForEnv(envName?: string, options?: RealmResolutionOptions): Promise<AuthRealm>;
|
|
64
67
|
/** Env var(s) carrying a realm's M2M client secret — the Sequence realm keeps
|
|
65
68
|
* the legacy bare name; OpCo realms use a suffixed name so one shell can hold
|
|
66
69
|
* several credentials unambiguously. Mirrors seqapi's `_m2m_secret_env_names`. */
|
|
@@ -153,3 +156,4 @@ export declare function currentIdentitySubject(options?: {
|
|
|
153
156
|
export declare function loadCachedUserTokens(realmName?: string): Promise<SeqapiTokens | null>;
|
|
154
157
|
export declare function saveTokens(tokens: SeqapiTokens, realmName?: string): Promise<void>;
|
|
155
158
|
export declare function deleteRealmTokens(realmName: string): Promise<void>;
|
|
159
|
+
export {};
|