@stetcms/cli 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -9
- package/bin/stet.mjs +18 -0
- package/dist/index.mjs +179 -32
- package/package.json +19 -12
package/README.md
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
# @stetcms/cli
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://github.com/jamiedavenport/stet/actions/workflows/ci.yml)
|
|
4
|
+
[](https://docs.stetcms.com/reference/cli)
|
|
5
|
+
[](./LICENSE)
|
|
6
|
+
|
|
7
|
+
[Stet](https://stetcms.com) from the command line, for scripting, seeding, and CI.
|
|
8
|
+
|
|
9
|
+
Stet is the CMS where marketing owns the content model and engineering gets a typed client generated from it.
|
|
4
10
|
|
|
5
11
|
## Install
|
|
6
12
|
|
|
@@ -12,9 +18,31 @@ npx @stetcms/cli login
|
|
|
12
18
|
|
|
13
19
|
## Usage
|
|
14
20
|
|
|
21
|
+
### `stet init`
|
|
22
|
+
|
|
23
|
+
Write a `stet.config.ts` to start from, with the analytics block present but commented out. No network calls, so it works before you have a key.
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npx stet init
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
| Flag | Description |
|
|
30
|
+
| ----------------- | ---------------------------------------------------------------------- |
|
|
31
|
+
| `--config <path>` | Where to write the config. Defaults to `./stet.config.ts`. |
|
|
32
|
+
| `--output <path>` | Where the generated content client should go. Written into the config. |
|
|
33
|
+
| `--force` | Overwrite an existing config. |
|
|
34
|
+
|
|
35
|
+
`--output` defaults to `src/stet.gen.ts` when the project has a `src` directory and `stet.gen.ts` when it does not.
|
|
36
|
+
|
|
37
|
+
Without `--force`, an existing config is never replaced. Detection looks in every place [`@stetcms/vite`](https://docs.stetcms.com/reference/codegen) and this CLI look, so writing to the default location cannot shadow the config your project already loads: with `--force` and no `--config`, it overwrites the file it found rather than adding one at the root. An explicit `--config` is honoured as given, and if that path is somewhere the resolver will not look, the command says so rather than leaving you a config nothing reads.
|
|
38
|
+
|
|
39
|
+
The next steps it prints depend on the project. A project that builds with Vite is told to install the plugin and add `stet()` to `vite.config.ts`; anywhere else — Next.js, another bundler, a CI checkout — is told to install [`@stetcms/client`](https://docs.stetcms.com/reference/client) and run `stet generate`. The install line follows whichever package manager the project uses.
|
|
40
|
+
|
|
41
|
+
The config carries no `apiKey`. Export `STET_API_KEY` instead, so the file stays safe to commit. `init` does not edit your `package.json`.
|
|
42
|
+
|
|
15
43
|
### `stet generate`
|
|
16
44
|
|
|
17
|
-
Generate the typed content client from your organization's content model, exactly as [`@stetcms/vite`](https://
|
|
45
|
+
Generate the typed content client from your organization's content model, exactly as [`@stetcms/vite`](https://docs.stetcms.com/reference/codegen) does at build time. Use it where there is no Vite: Next.js apps, other bundlers, or CI.
|
|
18
46
|
|
|
19
47
|
```bash
|
|
20
48
|
STET_API_KEY=stet_... stet generate
|
|
@@ -27,14 +55,17 @@ stet generate --key stet_... --url https://stet.example.com --output lib/stet.ge
|
|
|
27
55
|
| `--key <api-key>` | Organization API key. Defaults to `$STET_API_KEY`. |
|
|
28
56
|
| `--output <path>` | Where the generated module goes. Defaults to `src/stet.gen.ts`. |
|
|
29
57
|
| `--config <path>` | Path to `stet.config.ts`. Auto-detected by default. |
|
|
58
|
+
| `--if-key` | Succeed without generating when no API key is set. |
|
|
59
|
+
|
|
60
|
+
`--url`, `--key` and `--output` each override the matching key in `stet.config.ts`; `--config` chooses which file that is. Both this CLI and the Vite plugin read it (see [`@stetcms/config`](https://docs.stetcms.com/reference/configuration)).
|
|
30
61
|
|
|
31
|
-
|
|
62
|
+
`--if-key` exists for build scripts, so `stet generate --if-key && next build` can run in environments that have no `STET_API_KEY`, such as a CI job building against a committed generated client. It is the same behaviour [`@stetcms/vite`](https://docs.stetcms.com/reference/codegen) has without a key: say so, keep the file on disk, and carry on. Only the missing key is forgiven; with a key set, an unreachable server or a rejected key still fails the command.
|
|
32
63
|
|
|
33
|
-
The generated file imports [`@stetcms/client`](https://
|
|
64
|
+
The generated file imports [`@stetcms/client`](https://docs.stetcms.com/reference/client), so add that to your app's dependencies. It never contains the key: at runtime the client reads `STET_API_KEY` from the environment again, so the file is safe to commit. It reads `STET_ORIGIN` at runtime too, falling back to the origin it was generated against.
|
|
34
65
|
|
|
35
66
|
### `stet sync`
|
|
36
67
|
|
|
37
|
-
Publish your analytics tracking plan, so Stet can chart events your code declares before anyone has fired one. The same job [`@stetcms/vite`](https://
|
|
68
|
+
Publish your analytics tracking plan, so Stet can chart events your code declares before anyone has fired one. The same job [`@stetcms/vite`](https://docs.stetcms.com/reference/codegen) does on dev-server and build start; use this where there is no Vite, or in CI.
|
|
38
69
|
|
|
39
70
|
```bash
|
|
40
71
|
STET_API_KEY=stet_... stet sync
|
|
@@ -72,6 +103,14 @@ stet whoami --json
|
|
|
72
103
|
|
|
73
104
|
`--json` prints the user, session, and active organization as JSON for scripting. The session token is never included in the output.
|
|
74
105
|
|
|
106
|
+
### `stet logout`
|
|
107
|
+
|
|
108
|
+
Clear the session stored on this machine. Running the command when already logged out also succeeds.
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
stet logout
|
|
112
|
+
```
|
|
113
|
+
|
|
75
114
|
### `stet org`
|
|
76
115
|
|
|
77
116
|
Show the organization an API key is scoped to, including its billing plan.
|
|
@@ -81,20 +120,33 @@ stet org --api-key stet_... # or set STET_API_KEY
|
|
|
81
120
|
stet org --json
|
|
82
121
|
```
|
|
83
122
|
|
|
123
|
+
| Flag | Description |
|
|
124
|
+
| ----------------- | --------------------------------------------------------------------- |
|
|
125
|
+
| `--api-key <key>` | Organization API key. Defaults to `$STET_API_KEY`. |
|
|
126
|
+
| `--url <origin>` | Stet server origin. Defaults to `$STET_API_URL`, then the hosted app. |
|
|
127
|
+
| `--json` | Print the organization as JSON. |
|
|
128
|
+
|
|
84
129
|
## Configuration
|
|
85
130
|
|
|
86
|
-
- `STET_API_URL`: default server origin for `
|
|
87
|
-
|
|
131
|
+
- `STET_API_URL`: default server origin for the account commands (`login`,
|
|
132
|
+
`whoami`, `org`). `generate` and `sync` resolve theirs through
|
|
133
|
+
`stet.config.ts` and `STET_ORIGIN` instead.
|
|
134
|
+
- The session token is stored at `~/.config/stet/auth.json` (`$XDG_CONFIG_HOME/stet/auth.json` when set) with `0600` permissions. Run `stet logout` to remove it.
|
|
88
135
|
|
|
89
136
|
## Development
|
|
90
137
|
|
|
91
|
-
This package lives in the [Stet](https://github.com/jamiedavenport/stet)
|
|
138
|
+
This package lives in the [Stet monorepo](https://github.com/jamiedavenport/stet) under `published/cli` and is built with `vp pack` (Vite+). Releases are versioned with Changesets and published from CI.
|
|
92
139
|
|
|
93
140
|
```bash
|
|
94
141
|
vp install
|
|
95
|
-
vp run build
|
|
142
|
+
vp run cli#build
|
|
96
143
|
```
|
|
97
144
|
|
|
145
|
+
The `stet` bin is `bin/stet.mjs`, a committed launcher that defers to the
|
|
146
|
+
bundled `dist/index.mjs`. It exists so pnpm can link the bin at install time
|
|
147
|
+
inside the monorepo, where `dist` is gitignored and only exists after the
|
|
148
|
+
build above; before that build it exits with a message saying to run it.
|
|
149
|
+
|
|
98
150
|
## License
|
|
99
151
|
|
|
100
152
|
Apache-2.0
|
package/bin/stet.mjs
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// The CLI itself is bundled into dist/ by `vp pack` and ships in the npm
|
|
3
|
+
// package. This launcher is committed because pnpm links a bin only when its
|
|
4
|
+
// target exists at install time: in the monorepo dist/ is gitignored and
|
|
5
|
+
// built after install, so pointing the bin straight at it would leave every
|
|
6
|
+
// fresh checkout without a `stet` command.
|
|
7
|
+
import { access } from 'node:fs/promises';
|
|
8
|
+
|
|
9
|
+
const entry = new URL('../dist/index.mjs', import.meta.url);
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
await access(entry);
|
|
13
|
+
} catch {
|
|
14
|
+
console.error('stet: the CLI is not built yet. Run `vp run cli#build` from the repository root.');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
await import(entry.href);
|
package/dist/index.mjs
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
4
|
-
import path, { dirname, resolve } from "node:path";
|
|
4
|
+
import path, { dirname, relative, resolve } from "node:path";
|
|
5
5
|
import { fetchContentModel, renderContentModule } from "@stetcms/client/codegen";
|
|
6
6
|
import { resolveStetConfig } from "@stetcms/config";
|
|
7
7
|
import { findStetConfig, loadStetConfig, trackingPlanEvents } from "@stetcms/vite/config";
|
|
8
8
|
import { intro, log, note, outro, spinner } from "@clack/prompts";
|
|
9
9
|
import pc from "picocolors";
|
|
10
|
+
import fs, { existsSync, readFileSync } from "node:fs";
|
|
11
|
+
import { resolveCommand } from "package-manager-detector/commands";
|
|
12
|
+
import { detect } from "package-manager-detector/detect";
|
|
10
13
|
import open from "open";
|
|
11
14
|
import { createAuthClient } from "better-auth/client";
|
|
12
15
|
import { deviceAuthorizationClient, organizationClient } from "better-auth/client/plugins";
|
|
13
|
-
import fs from "node:fs";
|
|
14
16
|
import { homedir } from "node:os";
|
|
15
17
|
import Conf from "conf";
|
|
16
|
-
import { createStetClient, safe } from "@stetcms/client";
|
|
17
18
|
import { syncTrackingPlan } from "@stetcms/analytics/sync";
|
|
18
19
|
//#region package.json
|
|
19
|
-
var version = "0.
|
|
20
|
+
var version = "0.2.0";
|
|
20
21
|
//#endregion
|
|
21
22
|
//#region src/config.ts
|
|
22
23
|
/**
|
|
@@ -52,8 +53,16 @@ function fail(message) {
|
|
|
52
53
|
outro(pc.red("Something went wrong."));
|
|
53
54
|
process.exit(1);
|
|
54
55
|
}
|
|
56
|
+
/** A boxed, titled aside: several lines the reader is meant to act on. */
|
|
57
|
+
function note$1(message, title) {
|
|
58
|
+
note(message, title);
|
|
59
|
+
}
|
|
60
|
+
/** Something worth knowing that is not a failure, so the command carries on. */
|
|
61
|
+
function warn(message) {
|
|
62
|
+
log.warn(pc.yellow(message));
|
|
63
|
+
}
|
|
55
64
|
function showVerification(verificationUri, userCode) {
|
|
56
|
-
note([`${pc.dim("Visit")} ${verificationUri}`, `${pc.dim("Code")} ${pc.bold(userCode)}`].join("\n"), "Confirm this device");
|
|
65
|
+
note$1([`${pc.dim("Visit")} ${verificationUri}`, `${pc.dim("Code")} ${pc.bold(userCode)}`].join("\n"), "Confirm this device");
|
|
57
66
|
}
|
|
58
67
|
function progress() {
|
|
59
68
|
if (process.stdout.isTTY) {
|
|
@@ -74,10 +83,16 @@ function keyValueRows(rows) {
|
|
|
74
83
|
}
|
|
75
84
|
//#endregion
|
|
76
85
|
//#region src/commands/generate.ts
|
|
77
|
-
|
|
78
|
-
|
|
86
|
+
/** The command's whole body, extracted so tests can drive it without a process. */
|
|
87
|
+
async function runGenerate(options) {
|
|
79
88
|
const { origin, apiKey, output } = await settleOptions(options);
|
|
80
|
-
if (apiKey === void 0
|
|
89
|
+
if (apiKey === void 0 || apiKey === "") {
|
|
90
|
+
if (options.ifKey === true) {
|
|
91
|
+
done("No API key set; kept the generated client already on disk.");
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
fail("No API key. Pass --key or set STET_API_KEY.");
|
|
95
|
+
}
|
|
81
96
|
const fetching = progress();
|
|
82
97
|
fetching.start(`Fetching the content model from ${origin}…`);
|
|
83
98
|
let code;
|
|
@@ -94,6 +109,137 @@ const generateCommand = new Command("generate").description("Generate the typed
|
|
|
94
109
|
await mkdir(dirname(target), { recursive: true });
|
|
95
110
|
await writeFile(target, code, "utf8");
|
|
96
111
|
done(`Generated ${output}`);
|
|
112
|
+
}
|
|
113
|
+
const generateCommand = new Command("generate").description("Generate the typed content client from your content model").option("--url <origin>", "Stet server origin (defaults to $STET_ORIGIN or the hosted app)").option("--key <api-key>", "Organization API key (defaults to $STET_API_KEY)").option("--output <path>", "Where the generated module goes").option("--config <path>", "Path to stet.config.ts (auto-detected by default)").option("--if-key", "Succeed without generating when no API key is set").action(async (options) => {
|
|
114
|
+
begin("stet generate");
|
|
115
|
+
await runGenerate(options);
|
|
116
|
+
});
|
|
117
|
+
//#endregion
|
|
118
|
+
//#region src/commands/init.ts
|
|
119
|
+
/**
|
|
120
|
+
* The default `output`, which is where a developer would have put it anyway:
|
|
121
|
+
* projects with a `src/` keep generated code inside it, and flatter ones do
|
|
122
|
+
* not have the directory to write into.
|
|
123
|
+
*/
|
|
124
|
+
function defaultOutput(root) {
|
|
125
|
+
if (existsSync(resolve(root, "src"))) return "src/stet.gen.ts";
|
|
126
|
+
return "stet.gen.ts";
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Decides which file to write, and reports what the project already has.
|
|
130
|
+
*
|
|
131
|
+
* Detection runs through {@link findStetConfig}, the resolver `generate` and
|
|
132
|
+
* `sync` settle their options against, so writing to the default location can
|
|
133
|
+
* never shadow the config the project loads. An explicit `--config` is honoured
|
|
134
|
+
* as given and can land somewhere that resolver will not look, which is why
|
|
135
|
+
* `detected` is reported separately for the caller to warn about.
|
|
136
|
+
*/
|
|
137
|
+
function resolveTarget(root, options) {
|
|
138
|
+
const detected = findStetConfig(root);
|
|
139
|
+
if (options.config !== void 0) {
|
|
140
|
+
const path = resolve(root, options.config);
|
|
141
|
+
if (existsSync(path)) return {
|
|
142
|
+
path,
|
|
143
|
+
existing: path,
|
|
144
|
+
detected
|
|
145
|
+
};
|
|
146
|
+
return {
|
|
147
|
+
path,
|
|
148
|
+
existing: void 0,
|
|
149
|
+
detected
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
if (detected !== void 0) return {
|
|
153
|
+
path: detected,
|
|
154
|
+
existing: detected,
|
|
155
|
+
detected
|
|
156
|
+
};
|
|
157
|
+
return {
|
|
158
|
+
path: resolve(root, "stet.config.ts"),
|
|
159
|
+
existing: void 0,
|
|
160
|
+
detected
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
/** The `stet.config.ts` init writes. */
|
|
164
|
+
function renderStetConfig(output) {
|
|
165
|
+
return `import { defineStet } from '@stetcms/config';
|
|
166
|
+
// import { defineAnalytics, event } from '@stetcms/analytics';
|
|
167
|
+
// import { z } from 'zod';
|
|
168
|
+
|
|
169
|
+
export default defineStet({
|
|
170
|
+
output: '${output}',
|
|
171
|
+
|
|
172
|
+
// A typed tracking plan, published with \`stet sync\`.
|
|
173
|
+
// analytics: defineAnalytics({
|
|
174
|
+
// events: { signup: event({ plan: z.enum(['free', 'paid']) }) },
|
|
175
|
+
// }),
|
|
176
|
+
});
|
|
177
|
+
`;
|
|
178
|
+
}
|
|
179
|
+
const VITE_CONFIGS = [
|
|
180
|
+
"vite.config.ts",
|
|
181
|
+
"vite.config.js",
|
|
182
|
+
"vite.config.mjs",
|
|
183
|
+
"vite.config.mts",
|
|
184
|
+
"vite.config.cjs"
|
|
185
|
+
];
|
|
186
|
+
/**
|
|
187
|
+
* Whether the plugin is any use here. Next.js, Astro's own config, and a bare
|
|
188
|
+
* CI checkout all run codegen through `stet generate` instead, so telling them
|
|
189
|
+
* to install `@stetcms/vite` would be telling them to install nothing.
|
|
190
|
+
*/
|
|
191
|
+
function usesVite(root) {
|
|
192
|
+
if (VITE_CONFIGS.some((candidate) => existsSync(resolve(root, candidate)))) return true;
|
|
193
|
+
const manifest = resolve(root, "package.json");
|
|
194
|
+
if (!existsSync(manifest)) return false;
|
|
195
|
+
const { dependencies, devDependencies } = JSON.parse(readFileSync(manifest, "utf8"));
|
|
196
|
+
return "vite" in {
|
|
197
|
+
...dependencies,
|
|
198
|
+
...devDependencies
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* The install line for whichever package manager the project uses, resolved by
|
|
203
|
+
* `package-manager-detector`: it reads the `packageManager` field and walks up
|
|
204
|
+
* to the workspace root, so a package inside a monorepo is told the truth.
|
|
205
|
+
*/
|
|
206
|
+
async function installCommand(root, packages) {
|
|
207
|
+
const resolved = resolveCommand((await detect({ cwd: root }))?.agent ?? "npm", "add", packages);
|
|
208
|
+
if (resolved === null) return `npm install ${packages.join(" ")}`;
|
|
209
|
+
return `${resolved.command} ${resolved.args.join(" ")}`;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* What to do once the config exists. `@stetcms/client` is what the generated
|
|
213
|
+
* module imports and so is always needed; the plugin only earns its place in a
|
|
214
|
+
* project that builds with Vite, and codegen without one is `stet generate`.
|
|
215
|
+
*/
|
|
216
|
+
async function nextSteps(root) {
|
|
217
|
+
const key = "export STET_API_KEY=\"...\"";
|
|
218
|
+
if (usesVite(root)) return [
|
|
219
|
+
key,
|
|
220
|
+
await installCommand(root, ["@stetcms/vite", "@stetcms/client"]),
|
|
221
|
+
"Add stet() to vite.config.ts"
|
|
222
|
+
].join("\n");
|
|
223
|
+
return [
|
|
224
|
+
key,
|
|
225
|
+
await installCommand(root, ["@stetcms/client"]),
|
|
226
|
+
"npx stet generate"
|
|
227
|
+
].join("\n");
|
|
228
|
+
}
|
|
229
|
+
/** The command's whole body, taking the project root so tests need no `chdir`. */
|
|
230
|
+
async function runInit(root, options) {
|
|
231
|
+
const { path, existing, detected } = resolveTarget(root, options);
|
|
232
|
+
if (existing !== void 0 && options.force !== true) fail(`${relative(root, existing)} already exists. Pass --force to overwrite it.`);
|
|
233
|
+
const output = options.output ?? defaultOutput(root);
|
|
234
|
+
await mkdir(dirname(path), { recursive: true });
|
|
235
|
+
await writeFile(path, renderStetConfig(output), "utf8");
|
|
236
|
+
if (detected !== void 0 && detected !== path) warn(`${relative(root, detected)} is the config Stet loads. Pass --config to point the CLI and the plugin at ${relative(root, path)}, or this file goes unread.`);
|
|
237
|
+
note$1(await nextSteps(root), "Next");
|
|
238
|
+
done(`Wrote ${relative(root, path)}`);
|
|
239
|
+
}
|
|
240
|
+
const initCommand = new Command("init").description("Write a stet.config.ts to start from").option("--config <path>", "Where to write the config (defaults to ./stet.config.ts)").option("--output <path>", "Where the generated content client should go").option("--force", "Overwrite an existing config").action(async (options) => {
|
|
241
|
+
begin("stet init");
|
|
242
|
+
await runInit(process.cwd(), options);
|
|
97
243
|
});
|
|
98
244
|
//#endregion
|
|
99
245
|
//#region src/client.ts
|
|
@@ -134,6 +280,10 @@ function saveAuth(auth) {
|
|
|
134
280
|
createStore().set(auth);
|
|
135
281
|
fs.chmodSync(configDir(), 448);
|
|
136
282
|
}
|
|
283
|
+
function clearAuth() {
|
|
284
|
+
const store = createStore();
|
|
285
|
+
fs.rmSync(store.path, { force: true });
|
|
286
|
+
}
|
|
137
287
|
function loadAuth() {
|
|
138
288
|
const store = createStore();
|
|
139
289
|
const origin = store.get("origin");
|
|
@@ -203,27 +353,31 @@ const loginCommand = new Command("login").description("Log in to Stet from this
|
|
|
203
353
|
done(`Logged in as ${session?.user.email ?? "your account"}`);
|
|
204
354
|
});
|
|
205
355
|
//#endregion
|
|
356
|
+
//#region src/commands/logout.ts
|
|
357
|
+
const logoutCommand = new Command("logout").description("Log out of Stet on this machine").action(() => {
|
|
358
|
+
begin("stet logout");
|
|
359
|
+
const stored = loadAuth();
|
|
360
|
+
clearAuth();
|
|
361
|
+
if (stored === null) {
|
|
362
|
+
done("Already logged out.");
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
done("Logged out.");
|
|
366
|
+
});
|
|
367
|
+
//#endregion
|
|
206
368
|
//#region src/commands/org.ts
|
|
207
369
|
const orgCommand = new Command("org").description("Show the organization your API key is scoped to").option("--api-key <key>", "Organization API key (defaults to STET_API_KEY)").option("--url <url>", "Origin of the Stet deployment").option("--json", "Print the organization as JSON").action(async (options) => {
|
|
370
|
+
const { createStetClient, safe } = await import("@stetcms/client");
|
|
208
371
|
const apiKey = options.apiKey ?? process.env.STET_API_KEY;
|
|
209
|
-
if (apiKey === void 0 || apiKey === "")
|
|
210
|
-
console.error("No API key. Pass --api-key or set STET_API_KEY.");
|
|
211
|
-
process.exit(1);
|
|
212
|
-
}
|
|
372
|
+
if (apiKey === void 0 || apiKey === "") fail("No API key. Pass --api-key or set STET_API_KEY.");
|
|
213
373
|
const origin = resolveOrigin(options.url);
|
|
214
374
|
const client = createStetClient({
|
|
215
375
|
origin,
|
|
216
376
|
apiKey
|
|
217
377
|
});
|
|
218
378
|
const { data, error, isDefined } = await safe(client.org.current());
|
|
219
|
-
if (isDefined && error.code === "UNAUTHORIZED")
|
|
220
|
-
|
|
221
|
-
process.exit(1);
|
|
222
|
-
}
|
|
223
|
-
if (error !== null) {
|
|
224
|
-
console.error(error.message !== "" ? error.message : `Could not reach ${origin}.`);
|
|
225
|
-
process.exit(1);
|
|
226
|
-
}
|
|
379
|
+
if (isDefined && error.code === "UNAUTHORIZED") fail("Invalid or revoked API key.");
|
|
380
|
+
if (error !== null) fail(error.message !== "" ? error.message : `Could not reach ${origin}.`);
|
|
227
381
|
if (options.json === true) {
|
|
228
382
|
console.log(JSON.stringify(data, null, 2));
|
|
229
383
|
return;
|
|
@@ -267,21 +421,12 @@ const syncCommand = new Command("sync").description("Publish your analytics trac
|
|
|
267
421
|
//#region src/commands/whoami.ts
|
|
268
422
|
const whoamiCommand = new Command("whoami").description("Show the account you are logged in as").option("--json", "Print the session as JSON").action(async (options) => {
|
|
269
423
|
const stored = loadAuth();
|
|
270
|
-
if (stored === null)
|
|
271
|
-
console.error("Not logged in. Run `stet login` first.");
|
|
272
|
-
process.exit(1);
|
|
273
|
-
}
|
|
424
|
+
if (stored === null) fail("Not logged in. Run `stet login` first.");
|
|
274
425
|
const client = createClient(stored.origin);
|
|
275
426
|
const fetchOptions = { headers: { Authorization: `Bearer ${stored.token}` } };
|
|
276
427
|
const { data: session, error } = await client.getSession({ fetchOptions });
|
|
277
|
-
if (error !== null) {
|
|
278
|
-
|
|
279
|
-
process.exit(1);
|
|
280
|
-
}
|
|
281
|
-
if (session === null) {
|
|
282
|
-
console.error("Your session has expired. Run `stet login` again.");
|
|
283
|
-
process.exit(1);
|
|
284
|
-
}
|
|
428
|
+
if (error !== null) fail(error.message ?? `Could not reach ${stored.origin}.`);
|
|
429
|
+
if (session === null) fail("Your session has expired. Run `stet login` again.");
|
|
285
430
|
const { data: organizations } = await client.organization.list({ fetchOptions });
|
|
286
431
|
const activeOrganization = organizations?.find((organization) => organization.id === session.session.activeOrganizationId);
|
|
287
432
|
if (options.json === true) {
|
|
@@ -304,7 +449,9 @@ const whoamiCommand = new Command("whoami").description("Show the account you ar
|
|
|
304
449
|
const program = new Command();
|
|
305
450
|
program.name("stet").description("Stet from the command line").version(version);
|
|
306
451
|
program.addCommand(generateCommand);
|
|
452
|
+
program.addCommand(initCommand);
|
|
307
453
|
program.addCommand(loginCommand);
|
|
454
|
+
program.addCommand(logoutCommand);
|
|
308
455
|
program.addCommand(orgCommand);
|
|
309
456
|
program.addCommand(syncCommand);
|
|
310
457
|
program.addCommand(whoamiCommand);
|
package/package.json
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stetcms/cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Command line interface for Stet.",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Command line interface for Stet, the CMS for marketing and engineering: generate a typed content client and publish your tracking plan.",
|
|
5
5
|
"keywords": [
|
|
6
|
-
"auth",
|
|
7
6
|
"cli",
|
|
8
|
-
"
|
|
9
|
-
"
|
|
7
|
+
"cms",
|
|
8
|
+
"codegen",
|
|
9
|
+
"content",
|
|
10
|
+
"headless-cms",
|
|
11
|
+
"stet",
|
|
12
|
+
"stetcms",
|
|
13
|
+
"typescript"
|
|
10
14
|
],
|
|
11
|
-
"homepage": "https://
|
|
15
|
+
"homepage": "https://docs.stetcms.com/reference/cli",
|
|
12
16
|
"bugs": "https://github.com/jamiedavenport/stet/issues",
|
|
13
17
|
"license": "Apache-2.0",
|
|
14
18
|
"author": "Jamie Davenport (https://jxd.dev)",
|
|
@@ -18,9 +22,10 @@
|
|
|
18
22
|
"directory": "published/cli"
|
|
19
23
|
},
|
|
20
24
|
"bin": {
|
|
21
|
-
"stet": "./
|
|
25
|
+
"stet": "./bin/stet.mjs"
|
|
22
26
|
},
|
|
23
27
|
"files": [
|
|
28
|
+
"bin",
|
|
24
29
|
"dist"
|
|
25
30
|
],
|
|
26
31
|
"type": "module",
|
|
@@ -33,11 +38,12 @@
|
|
|
33
38
|
"commander": "^15.0.0",
|
|
34
39
|
"conf": "^15.1.0",
|
|
35
40
|
"open": "^11.0.0",
|
|
41
|
+
"package-manager-detector": "^1.8.0",
|
|
36
42
|
"picocolors": "^1.1.1",
|
|
37
|
-
"@stetcms/analytics": "0.
|
|
38
|
-
"@stetcms/client": "0.
|
|
39
|
-
"@stetcms/
|
|
40
|
-
"@stetcms/
|
|
43
|
+
"@stetcms/analytics": "0.2.0",
|
|
44
|
+
"@stetcms/client": "0.2.0",
|
|
45
|
+
"@stetcms/vite": "0.2.0",
|
|
46
|
+
"@stetcms/config": "0.1.1"
|
|
41
47
|
},
|
|
42
48
|
"devDependencies": {
|
|
43
49
|
"@types/node": "^26.1.1",
|
|
@@ -50,6 +56,7 @@
|
|
|
50
56
|
},
|
|
51
57
|
"scripts": {
|
|
52
58
|
"build": "vp pack",
|
|
53
|
-
"tc": "tsc --noEmit"
|
|
59
|
+
"tc": "tsc --noEmit",
|
|
60
|
+
"test": "vp test run"
|
|
54
61
|
}
|
|
55
62
|
}
|