@sequenceholdings/studio-cli 0.1.25 → 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 +8 -3
- 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/repos/commands.d.ts +1 -1
- package/dist/repos/commands.js +1 -1
- package/package.json +2 -2
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
|
|
@@ -609,7 +614,7 @@ JSON API — the same repos `--repo <ns>/<name>` sources build from.
|
|
|
609
614
|
| `seq-studio repos list -e <env> [--namespace <slug>] [--mine]` | repos visible on the environment (permission-filtered) |
|
|
610
615
|
| `seq-studio repos namespaces [create <slug>] -e <env>` | list namespaces, or create one (creator becomes owner) |
|
|
611
616
|
| `seq-studio repos show <ns>/<name> -e <env>` | detail: id, branches, clone URL; artifact project id when slug matches |
|
|
612
|
-
| `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) |
|
|
613
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 |
|
|
614
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` |
|
|
615
620
|
| `seq-studio repos delete <ns>/<name> -e <env> [--yes]` | delete a repo — interactive confirm unless `--yes` |
|
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/repos/commands.d.ts
CHANGED
|
@@ -96,6 +96,6 @@ export declare function reposCiRequireCommand(args: ParsedArgs): Promise<number>
|
|
|
96
96
|
export declare function reposCiImportCommand(args: ParsedArgs): Promise<number>;
|
|
97
97
|
export declare function reposCiCommand(args: ParsedArgs): Promise<number>;
|
|
98
98
|
export declare function reposDeleteCommand(args: ParsedArgs): Promise<number>;
|
|
99
|
-
export declare const REPOS_USAGE = "usage:\n seq-studio repos list -e <env> [--namespace <slug>] [--mine] repos visible on the environment\n seq-studio repos namespaces [create <slug>] -e <env> list or create namespaces\n seq-studio repos show <ns>/<name> -e <env> repo detail (branches, clone URL)\n seq-studio repos create <ns>/<name> -e <env> [--default-branch b] create
|
|
99
|
+
export declare const REPOS_USAGE = "usage:\n seq-studio repos list -e <env> [--namespace <slug>] [--mine] repos visible on the environment\n seq-studio repos namespaces [create <slug>] -e <env> list or create namespaces\n seq-studio repos show <ns>/<name> -e <env> repo detail (branches, clone URL)\n seq-studio repos create <ns>/<name> -e <env> [--default-branch b] create a repo seeded with a Sequence app README\n seq-studio repos clone <ns>/<name> -e <env> [--ref r] [--out dir] [--force]\n seq-studio repos clone --url <https://\u2026/repos/<id>/git> -e <env> [--ref r] [--out dir]\n seq-studio repos clone --id <uuid> -e <env> [--ref r] [--out dir]\n smart-HTTP when ATLAS_GIT_PAT is set;\n otherwise JSON materialize (<ns>/<name>)\n seq-studio repos pull <ns>/<name> -e <env> [--ref r] [--out dir] [--force]\n materialize the tree at a ref (JSON API)\n seq-studio repos delete <ns>/<name> -e <env> [--yes] delete a repo (confirm prompt)\n seq-studio repos ci show <ns>/<name> -e <env> [--ref r] preview discovered CI checks\n seq-studio repos ci require <ns>/<name> --check <name> -e <env> reserved (refuses write until sandboxed runner)\n seq-studio repos ci import <ns>/<name> -e <env> [--ref r] reserved (refuses write until sandboxed runner)\n\n Flags: -e/--env <env> (required; see: seq-studio envs list)\n\n clone prefers real git clone (PAT via askpass \u2014 never written into the remote\n URL). Without ATLAS_GIT_PAT, <ns>/<name> falls back to the JSON API and prints\n how to get a PAT (seq-studio or Atlas UI /settings/tokens).\n PAT without Auth0 login: use --url from Repositories \u2192 Clone (or --id <uuid>).\n --ref accepts a branch, tag, or commit SHA (SHA \u2192 clone then checkout).\n\n Authenticate JSON API calls with: seq-studio login\n Authenticate git clone/push with: ATLAS_GIT_PAT (from Atlas Settings \u2192 Tokens)\n";
|
|
100
100
|
export declare function runReposCommand(sub: string | undefined, args: ParsedArgs): Promise<number>;
|
|
101
101
|
export {};
|
package/dist/repos/commands.js
CHANGED
|
@@ -748,7 +748,7 @@ export const REPOS_USAGE = `usage:
|
|
|
748
748
|
seq-studio repos list -e <env> [--namespace <slug>] [--mine] repos visible on the environment
|
|
749
749
|
seq-studio repos namespaces [create <slug>] -e <env> list or create namespaces
|
|
750
750
|
seq-studio repos show <ns>/<name> -e <env> repo detail (branches, clone URL)
|
|
751
|
-
seq-studio repos create <ns>/<name> -e <env> [--default-branch b] create
|
|
751
|
+
seq-studio repos create <ns>/<name> -e <env> [--default-branch b] create a repo seeded with a Sequence app README
|
|
752
752
|
seq-studio repos clone <ns>/<name> -e <env> [--ref r] [--out dir] [--force]
|
|
753
753
|
seq-studio repos clone --url <https://…/repos/<id>/git> -e <env> [--ref r] [--out dir]
|
|
754
754
|
seq-studio repos clone --id <uuid> -e <env> [--ref r] [--out dir]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sequenceholdings/studio-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.26",
|
|
4
4
|
"description": "Unified Sequence Studio CLI — `seq-studio init` / `add` / `deploy` (app monorepos), `seq-studio agents`, `seq-studio process` (Lattice), `seq-studio artifact`, `seq-studio functions` / `secrets`, `seq-studio repos`, and `seq-studio auth pat`. Includes Auth0 browser login shared with seqapi.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"repository": {
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"smol-toml": "^1.4.2",
|
|
41
41
|
"tsx": "^4.20.3",
|
|
42
42
|
"zod": "^4.1.13",
|
|
43
|
-
"@sequenceholdings/agent-spec": "0.1.
|
|
43
|
+
"@sequenceholdings/agent-spec": "0.1.2",
|
|
44
44
|
"@sequenceholdings/artifact-studio": "0.2.2",
|
|
45
45
|
"@sequenceholdings/lattice": "0.1.2"
|
|
46
46
|
},
|