@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 CHANGED
@@ -1,6 +1,12 @@
1
1
  # @stetcms/cli
2
2
 
3
- Stet from the command line, for scripting, seeding, and CI.
3
+ [![CI](https://github.com/jamiedavenport/stet/actions/workflows/ci.yml/badge.svg)](https://github.com/jamiedavenport/stet/actions/workflows/ci.yml)
4
+ [![Docs](https://img.shields.io/badge/docs-stetcms.com-black.svg)](https://docs.stetcms.com/reference/cli)
5
+ [![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./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://github.com/jamiedavenport/stet/tree/main/published/vite) does at build time. Use it where there is no Vite: Next.js apps, other bundlers, or CI.
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
- Every flag overrides the same key in `stet.config.ts`, which both this CLI and the Vite plugin read (see [`@stetcms/config`](https://github.com/jamiedavenport/stet/tree/main/published/config)).
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://github.com/jamiedavenport/stet/tree/main/published/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.
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://github.com/jamiedavenport/stet/tree/main/published/vite) does on dev-server and build start; use this where there is no Vite, or in CI.
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 `stet login`.
87
- - The session token is stored at `~/.config/stet/auth.json` (`$XDG_CONFIG_HOME/stet/auth.json` when set) with `0600` permissions. Delete the file to log out.
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) monorepo under `published/cli` and is built with `vp pack` (Vite+). Releases are versioned with Changesets and published from CI.
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 --filter @stetcms/cli
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.1.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
- 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)").action(async (options) => {
78
- begin("stet generate");
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) fail("No API key. Pass --key or set STET_API_KEY.");
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
- console.error("Invalid or revoked API key.");
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
- console.error(error.message ?? `Could not reach ${stored.origin}.`);
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.1.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
- "device-flow",
9
- "stet"
7
+ "cms",
8
+ "codegen",
9
+ "content",
10
+ "headless-cms",
11
+ "stet",
12
+ "stetcms",
13
+ "typescript"
10
14
  ],
11
- "homepage": "https://github.com/jamiedavenport/stet/tree/main/published/cli#readme",
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": "./dist/index.mjs"
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.1.0",
38
- "@stetcms/client": "0.1.0",
39
- "@stetcms/config": "0.1.0",
40
- "@stetcms/vite": "0.1.0"
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
  }