kizlo 0.17.0 → 0.19.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.
Files changed (58) hide show
  1. package/dist/adapters/auth.d.ts +17 -16
  2. package/dist/adapters/auth.js +4 -5
  3. package/dist/category/index.js +4 -4
  4. package/dist/category/schema.d.ts +153 -0
  5. package/dist/category/schema.js +4 -3
  6. package/dist/cli/commands/_wiring.js +9 -8
  7. package/dist/cli/commands/check.js +11 -15
  8. package/dist/cli/commands/dev.js +3 -3
  9. package/dist/cli/commands/generate.js +15 -17
  10. package/dist/cli/commands/test.js +3 -3
  11. package/dist/cli/daemon/config.js +148 -82
  12. package/dist/cli/daemon/generate.js +54 -54
  13. package/dist/cli/daemon/watch.js +41 -54
  14. package/dist/cli/wp/constants.d.ts +2 -4
  15. package/dist/cli/wp/constants.js +2 -4
  16. package/dist/cli/wp/types.d.ts +6 -3
  17. package/dist/cli/wp/utils.d.ts +3 -3
  18. package/dist/cli/wp/utils.js +9 -3
  19. package/dist/comment/errors.js +0 -1
  20. package/dist/comment/index.js +6 -6
  21. package/dist/config.d.ts +61 -47
  22. package/dist/context.d.ts +7 -7
  23. package/dist/context.js +4 -4
  24. package/dist/index.d.ts +9 -4
  25. package/dist/index.js +8 -3
  26. package/dist/package.js +1 -1
  27. package/dist/page/index.js +3 -3
  28. package/dist/page/schema.d.ts +226 -0
  29. package/dist/page/schema.js +4 -3
  30. package/dist/post/index.js +3 -3
  31. package/dist/post/schema.d.ts +268 -0
  32. package/dist/post/schema.js +4 -3
  33. package/dist/procedures.d.ts +35 -902
  34. package/dist/seo/index.js +1 -1
  35. package/dist/seo/schema.d.ts +5 -5
  36. package/dist/shared/contract.d.ts +1 -1
  37. package/dist/shared/procedure.d.ts +12 -3
  38. package/dist/shared/procedure.js +4 -0
  39. package/dist/shared/result.d.ts +16 -7
  40. package/dist/shared/types.d.ts +1 -1
  41. package/dist/tag/index.js +3 -3
  42. package/dist/tag/schema.d.ts +151 -0
  43. package/dist/tag/schema.js +4 -3
  44. package/dist/test/auth.js +2 -3
  45. package/dist/test/users.d.ts +2 -4
  46. package/dist/test/users.js +4 -9
  47. package/dist/test.d.ts +2 -2
  48. package/dist/test.js +2 -2
  49. package/dist/wordpress/endpoint.js +2 -1
  50. package/dist/wordpress/error.d.ts +1 -0
  51. package/dist/wordpress/error.js +2 -0
  52. package/dist/wordpress/fetch-introspection.js +1 -1
  53. package/dist/wordpress/generate.js +51 -7
  54. package/dist/wordpress/index.d.ts +1 -1
  55. package/dist/wordpress/transport.js +5 -3
  56. package/dist/wordpress/types.d.ts +38 -12
  57. package/dist/wordpress/utils.d.ts +1 -0
  58. package/package.json +1 -1
@@ -2,9 +2,9 @@ import { integrationEnv, resolveWordPressConnection } from "../../kizlo.js";
2
2
  import { node } from "../../integrations/node/integration.js";
3
3
  import { log } from "./logger.js";
4
4
  import { loadEnvFiles } from "../utils.js";
5
- import { resolveConfig, resolveWordPressClientDir } from "./config.js";
5
+ import { resolveConfig } from "./config.js";
6
6
  import { IntrospectionFetchError } from "../../wordpress/fetch-introspection.js";
7
- import { generateOnce, generateWordPressOnce, generateWorkspaceClientOnce, reportGenerationError } from "./generate.js";
7
+ import { generateIntrospectionOnce, generateOnce, reportGenerationError } from "./generate.js";
8
8
  import { acquire, lockPath, release } from "./lock.js";
9
9
  import path from "node:path";
10
10
  import { FSWatcher } from "chokidar";
@@ -19,51 +19,49 @@ function debounce(fn, delay) {
19
19
  }
20
20
  async function regenerate(cfg, credentials) {
21
21
  try {
22
- if (await generateOnce(cfg, { credentials })) log.success("Contract updated");
23
- else log.warn(`No Kizlo server found in ${cfg.serverEntry}`);
22
+ const result = await generateOnce(cfg, { credentials });
23
+ if (result.contract === "built") log.success("Contract updated");
24
+ else if (result.contract === "empty" && cfg.server) log.warn(`No Kizlo server found in ${cfg.server.entry}`);
24
25
  } catch (error) {
25
26
  reportGenerationError("Failed to update the Kizlo contract:", error);
26
27
  }
27
28
  }
28
- /** Watches the server directory and regenerates the contract on change. */
29
- async function watch(cfg, credentials) {
29
+ /** Watches the server directory and regenerates the contract on change. Only called when a server is present. */
30
+ async function watch(cfg, server, credentials) {
30
31
  const watcher = new FSWatcher({
31
32
  persistent: true,
32
33
  ignoreInitial: true,
33
- ignored: path.resolve(cfg.cwd, cfg.generatedDir)
34
+ ignored: path.resolve(cfg.cwd, server.contractDir)
34
35
  });
35
36
  const onChange = debounce(() => regenerate(cfg, credentials), 300);
36
- watcher.add(path.resolve(cfg.cwd, cfg.serverDir));
37
+ watcher.add(path.resolve(cfg.cwd, server.dir));
37
38
  watcher.on("all", () => void onChange());
38
39
  return watcher;
39
40
  }
40
41
  /**
41
- * Report one generation's pass under its own name. The poll refreshes two, and one message for both sent
42
- * a client-only workspace looking for a WordPress service its project does not have.
43
- *
44
- * Each reporter carries the last failure it reported, because most of what can fail here cannot clear
45
- * without the user acting — WordPress down, credentials wrong, a document that will not parse, an
46
- * introspection version this package does not speak — and repeating the same line every few seconds says
47
- * nothing the first one did not. A failure that reads differently is a different answer and is reported.
48
- * The state is per reporter rather than per process, so restarting the watcher reports afresh, and one
49
- * generation failing never silences the other.
42
+ * Report the introspection poll's pass. It carries the last failure it reported, because most of what
43
+ * can fail here cannot clear without the user acting (WordPress down, credentials wrong, a document
44
+ * that will not parse, an introspection version this package does not speak), and repeating the same
45
+ * line every few seconds says nothing the first one did not. A failure that reads differently is a
46
+ * different answer and is reported. The state is per reporter rather than per process, so restarting the
47
+ * watcher reports afresh.
50
48
  */
51
- function reportGeneration(subject, stack) {
49
+ function reportGeneration(stack) {
52
50
  let reported;
53
51
  return async (run) => {
54
52
  try {
55
- if (await run() === "generated") log.success(`WordPress ${subject} updated`);
53
+ if (await run() === "generated") log.success("WordPress introspection updated");
56
54
  stack?.answered();
57
55
  if (reported !== void 0) {
58
56
  reported = void 0;
59
- log.success(`Updating the WordPress ${subject} again`);
57
+ log.success("Updating the WordPress introspection again");
60
58
  }
61
59
  } catch (error) {
62
60
  if (error instanceof IntrospectionFetchError && error.unreachable && await stack?.ended()) return;
63
61
  const message = error instanceof Error ? error.message : String(error);
64
62
  if (message === reported) return;
65
63
  reported = message;
66
- reportGenerationError(`Failed to update the WordPress ${subject}:`, error);
64
+ reportGenerationError("Failed to update the WordPress introspection:", error);
67
65
  }
68
66
  };
69
67
  }
@@ -96,26 +94,20 @@ function endOnStoppedStack(stack, now = Date.now) {
96
94
  };
97
95
  }
98
96
  /**
99
- * One pass of the WordPress poll. `cfg` covers an app's client next to its contract; `wordpressClientDir`
100
- * covers a workspace that has only the client. A project has one or the other, but both are refreshed
101
- * together so the plugin's PHP changing is picked up either way. Each is refreshed independently, so a
102
- * service that cannot generate does not cost the pass its client.
103
- *
104
- * Both reporters share one stack guard, so a stopped stack ends the session once rather than once per
105
- * generation the pass refreshes.
97
+ * One pass of the introspection poll: refetch and rewrite `introspection.ts` when WordPress has changed.
98
+ * A server-backed app and a package that ships only procedures both take this same pass, since each has
99
+ * exactly one introspection to keep current; the plugin's PHP changing is picked up either way. The
100
+ * contract is not refreshed here; the file watcher rebuilds it on a server source change.
106
101
  */
107
- function createWordPressRefresh(cwd, cfg, wordpressClientDir, options, stack) {
108
- const guard = stack ? endOnStoppedStack(stack) : void 0;
109
- const service = reportGeneration("service", guard);
110
- const client = reportGeneration("client", guard);
102
+ function createWordPressRefresh(cfg, options, stack) {
103
+ const introspection = reportGeneration(stack ? endOnStoppedStack(stack) : void 0);
111
104
  return async () => {
112
- if (cfg) await service(() => generateWordPressOnce(cfg, options));
113
- if (wordpressClientDir) await client(() => generateWorkspaceClientOnce(cwd, wordpressClientDir, options));
105
+ await introspection(() => generateIntrospectionOnce(cfg, options));
114
106
  };
115
107
  }
116
108
  /** Run {@link createWordPressRefresh} on a timer, skipping a tick while the previous one is still going. */
117
- function refreshWordPress(cwd, cfg, wordpressClientDir, credentials, stack) {
118
- const refresh = createWordPressRefresh(cwd, cfg, wordpressClientDir, { credentials }, stack);
109
+ function refreshWordPress(cfg, credentials, stack) {
110
+ const refresh = createWordPressRefresh(cfg, { credentials }, stack);
119
111
  let refreshing = false;
120
112
  const timer = setInterval(() => {
121
113
  if (refreshing) return;
@@ -128,12 +120,11 @@ function refreshWordPress(cwd, cfg, wordpressClientDir, credentials, stack) {
128
120
  return timer;
129
121
  }
130
122
  /**
131
- * Acquire the single-instance lock, generate the contract once, and start the file
132
- * watcher. Returns a synchronous `stop()` that closes the watcher and releases the
133
- * lock — or `undefined` when another watcher already holds the lock (a framework dev
134
- * script, or a second `kizlo dev`), or when no server `dir` is configured, in which case
135
- * the caller carries on without watching. Used by `kizlo dev`, both when it boots a local
136
- * stack and when it runs the watcher alone, so a single terminal covers the whole dev loop.
123
+ * Acquire the single-instance lock, generate once, and start the file watcher. Returns a synchronous
124
+ * `stop()` that closes the watcher and releases the lock, or `undefined` when another watcher already
125
+ * holds the lock (a framework dev script, or a second `kizlo dev`), or when nothing is configured to
126
+ * generate, in which case the caller carries on without watching. Used by `kizlo dev`, both when it
127
+ * boots a local stack and when it runs the watcher alone, so a single terminal covers the whole dev loop.
137
128
  */
138
129
  async function startWatcher(cwd, opts) {
139
130
  const lock = lockPath(cwd);
@@ -144,26 +135,22 @@ async function startWatcher(cwd, opts) {
144
135
  let handedOver = false;
145
136
  try {
146
137
  const cfg = await resolveConfig(cwd, { dir: opts?.dir });
147
- const wordpressClientDir = await resolveWordPressClientDir(cwd);
148
- if (!cfg && !wordpressClientDir) {
138
+ if (!cfg) {
149
139
  log.info("skipping the contract generation.");
150
140
  return;
151
141
  }
152
142
  loadEnvFiles(cwd);
153
143
  const { credentials } = resolveWordPressConnection(void 0, integrationEnv([node()]));
154
- if (wordpressClientDir) try {
155
- if (await generateWorkspaceClientOnce(cwd, wordpressClientDir, { credentials }) === "generated") log.success("WordPress client generated");
156
- } catch (error) {
157
- reportGenerationError("Failed to generate the WordPress client:", error);
158
- }
159
- if (cfg) try {
160
- if (await generateOnce(cfg, { credentials })) log.success("Contract generated");
161
- else log.warn(`No Kizlo server found in ${cfg.serverEntry}`);
144
+ try {
145
+ const result = await generateOnce(cfg, { credentials });
146
+ if (result.contract === "built") log.success("Contract generated");
147
+ else if (result.contract === "empty" && cfg.server) log.warn(`No Kizlo server found in ${cfg.server.entry}`);
148
+ else if (result.introspection === "generated") log.success("WordPress introspection generated");
162
149
  } catch (error) {
163
150
  reportGenerationError("Failed to generate the Kizlo contract:", error);
164
151
  }
165
- const watcher = cfg ? await watch(cfg, credentials) : void 0;
166
- const wordpressRefresh = refreshWordPress(cwd, cfg, wordpressClientDir, credentials, opts?.stack);
152
+ const watcher = cfg.server ? await watch(cfg, cfg.server, credentials) : void 0;
153
+ const wordpressRefresh = refreshWordPress(cfg, credentials, opts?.stack);
167
154
  let stopped = false;
168
155
  handedOver = true;
169
156
  return () => {
@@ -34,9 +34,7 @@ declare const CREDENTIALS_REL = ".kizlo/test.json";
34
34
  * working dir rather than next to the generated files on purpose: it describes one machine's last
35
35
  * fetch, so a fresh clone has to regenerate rather than trust a cache it never populated.
36
36
  */
37
- declare const WORDPRESS_META_REL = ".kizlo/wordpress.meta.json";
38
- /** Same, for the standalone client written by `wordpressClientDir`. Separate so the two never share one ETag. */
39
- declare const WORDPRESS_CLIENT_META_REL = ".kizlo/wordpress.client.meta.json";
37
+ declare const INTROSPECTION_META_REL = ".kizlo/introspection.meta.json";
40
38
  /**
41
39
  * Fixed folder the local WordPress install lives in, relative to the config root. The whole install
42
40
  * (core, themes, uploads, plugins) is bind-mounted here; `kizlo dev reset` wipes it. It sits under
@@ -56,4 +54,4 @@ declare const DEFAULT_PLUGINS: PluginSource[];
56
54
  declare const TEST_ADMIN: Omit<TestAdminUser, "id" | "applicationPassword">;
57
55
  declare const TEST_USER: Omit<TestUser, "id">;
58
56
  //#endregion
59
- export { COMPOSE_FILE, CONFIG_FILES, CREDENTIALS_REL, DEFAULT_PLUGINS, DEFAULT_PORT, DEFAULT_WORDPRESS_TAG, LOCAL_DIR_REL, OPCACHE_INI, REMAP_ENTRYPOINT, SEED_MARKER_OPTION, SEED_VERSION, TEST_ADMIN, TEST_USER, WORDPRESS_CLIENT_META_REL, WORDPRESS_META_REL, githubRelease, kizloRelease };
57
+ export { COMPOSE_FILE, CONFIG_FILES, CREDENTIALS_REL, DEFAULT_PLUGINS, DEFAULT_PORT, DEFAULT_WORDPRESS_TAG, INTROSPECTION_META_REL, LOCAL_DIR_REL, OPCACHE_INI, REMAP_ENTRYPOINT, SEED_MARKER_OPTION, SEED_VERSION, TEST_ADMIN, TEST_USER, githubRelease, kizloRelease };
@@ -44,9 +44,7 @@ const CREDENTIALS_REL = ".kizlo/test.json";
44
44
  * working dir rather than next to the generated files on purpose: it describes one machine's last
45
45
  * fetch, so a fresh clone has to regenerate rather than trust a cache it never populated.
46
46
  */
47
- const WORDPRESS_META_REL = ".kizlo/wordpress.meta.json";
48
- /** Same, for the standalone client written by `wordpressClientDir`. Separate so the two never share one ETag. */
49
- const WORDPRESS_CLIENT_META_REL = ".kizlo/wordpress.client.meta.json";
47
+ const INTROSPECTION_META_REL = ".kizlo/introspection.meta.json";
50
48
  /**
51
49
  * Fixed folder the local WordPress install lives in, relative to the config root. The whole install
52
50
  * (core, themes, uploads, plugins) is bind-mounted here; `kizlo dev reset` wipes it. It sits under
@@ -83,4 +81,4 @@ const TEST_USER = {
83
81
  };
84
82
 
85
83
  //#endregion
86
- export { COMPOSE_FILE, CONFIG_FILES, CREDENTIALS_REL, DEFAULT_PLUGINS, DEFAULT_PORT, DEFAULT_WORDPRESS_TAG, LOCAL_DIR_REL, OPCACHE_INI, REMAP_ENTRYPOINT, SEED_MARKER_OPTION, SEED_VERSION, TEST_ADMIN, TEST_USER, WORDPRESS_CLIENT_META_REL, WORDPRESS_META_REL, githubRelease, kizloRelease };
84
+ export { COMPOSE_FILE, CONFIG_FILES, CREDENTIALS_REL, DEFAULT_PLUGINS, DEFAULT_PORT, DEFAULT_WORDPRESS_TAG, INTROSPECTION_META_REL, LOCAL_DIR_REL, OPCACHE_INI, REMAP_ENTRYPOINT, SEED_MARKER_OPTION, SEED_VERSION, TEST_ADMIN, TEST_USER, githubRelease, kizloRelease };
@@ -53,13 +53,16 @@ interface SeedContext {
53
53
  /**
54
54
  * A plugin to ensure installed + active during bootstrap. A bare string is a
55
55
  * wp.org slug (the installed name and the install source are the same). The
56
- * object form is for any other source — a zip URL or a local path — where the
57
- * installed slug (`name`, used for activation checks) can't be derived from the
58
- * source. `source` is passed straight to `wp plugin install`.
56
+ * object form names an install source explicitly, either a wp.org slug or a zip
57
+ * URL, when the installed slug (`name`, used for activation checks) cannot be
58
+ * derived from the source or the wp.org release must be pinned. `version` is
59
+ * passed to `wp plugin install --version` when present. `source` is passed
60
+ * straight to `wp plugin install`.
59
61
  */
60
62
  type PluginSource = string | {
61
63
  name: string;
62
64
  source: string;
65
+ version?: string;
63
66
  };
64
67
  /**
65
68
  * A local plugin directory to bind-mount into the dev stack (`kizlo dev`) instead
@@ -14,15 +14,15 @@ import { DevPluginSource, Fixture, PluginSource, SettleContext, TestCredentials
14
14
  * Bumping `SEED_VERSION` invalidates older marks and forces a reseed.
15
15
  */
16
16
  declare function isSeeded(): Promise<boolean>;
17
- /** Resolve a `PluginSource` to the `(name, source)` pair `ensurePlugin` needs. */
18
- declare function resolvePluginSource(plugin: PluginSource): [name: string, source: string];
17
+ /** Resolve a `PluginSource` to the arguments `ensurePlugin` needs. */
18
+ declare function resolvePluginSource(plugin: PluginSource): [name: string, source: string, version?: string];
19
19
  /**
20
20
  * Ensure a plugin is active without ever re-downloading. Plugin files persist in
21
21
  * the `wp_data` volume, so an already-installed plugin is just activated; the
22
22
  * download (`source` = release zip URL or wp.org slug) happens only when the files
23
23
  * are absent (i.e. after `wp reset` wipes the volume).
24
24
  */
25
- declare function ensurePlugin(name: string, source: string): Promise<void>;
25
+ declare function ensurePlugin(name: string, source: string, version?: string): Promise<void>;
26
26
  /**
27
27
  * Activate a bind-mounted local plugin by its directory basename (the slug the
28
28
  * mount lands at under `wp-content/plugins`). Idempotent: the files arrive via the
@@ -29,9 +29,14 @@ async function isSeeded() {
29
29
  ]);
30
30
  return marker.code === 0 && marker.stdout.trim() === SEED_VERSION;
31
31
  }
32
- /** Resolve a `PluginSource` to the `(name, source)` pair `ensurePlugin` needs. */
32
+ /** Resolve a `PluginSource` to the arguments `ensurePlugin` needs. */
33
33
  function resolvePluginSource(plugin) {
34
- return typeof plugin === "string" ? [plugin, plugin] : [plugin.name, plugin.source];
34
+ if (typeof plugin === "string") return [plugin, plugin];
35
+ return plugin.version === void 0 ? [plugin.name, plugin.source] : [
36
+ plugin.name,
37
+ plugin.source,
38
+ plugin.version
39
+ ];
35
40
  }
36
41
  /**
37
42
  * Ensure a plugin is active without ever re-downloading. Plugin files persist in
@@ -39,7 +44,7 @@ function resolvePluginSource(plugin) {
39
44
  * download (`source` = release zip URL or wp.org slug) happens only when the files
40
45
  * are absent (i.e. after `wp reset` wipes the volume).
41
46
  */
42
- async function ensurePlugin(name, source) {
47
+ async function ensurePlugin(name, source, version) {
43
48
  if ((await compose([
44
49
  "exec",
45
50
  "-T",
@@ -69,6 +74,7 @@ async function ensurePlugin(name, source) {
69
74
  "plugin",
70
75
  "install",
71
76
  source,
77
+ ...version === void 0 ? [] : [`--version=${version}`],
72
78
  "--activate"
73
79
  ]);
74
80
  }
@@ -22,7 +22,6 @@ const LIST_COMMENT_ERROR_MAP = defineErrorMap({
22
22
  }
23
23
  });
24
24
  const SUBMIT_COMMENT_ERROR_MAP = defineErrorMap({
25
- COMMENT_USER_NOT_FOUND: {},
26
25
  COMMENT_ID_NOT_FOUND: {},
27
26
  COMMENT_CLOSED: {},
28
27
  COMMENT_LOGIN_REQUIRED: {},
@@ -98,26 +98,26 @@ const COMMENT_PROCEDURES = {
98
98
  const connInfo = await context.getConnInfo();
99
99
  if (!connInfo?.ip) throw new Error("Connection IP is required.");
100
100
  if (!connInfo?.userAgent) throw new Error("Connection user agent is required.");
101
- const user = await context.getAuthUser();
102
- if (!user) {
101
+ const session = await context.getSession();
102
+ if (!session) {
103
103
  if (!input.body.captchaToken) throw errors.COMMENT_CAPTCHA_REQUIRED();
104
104
  if (!await context.verifyCaptcha(input.body.captchaToken)) throw errors.COMMENT_CAPTCHA_INVALID();
105
105
  }
106
- const response = await context.wordpress.kizlo.comments.create({
107
- user_id: user?.id,
106
+ const submission = {
108
107
  author_ip: connInfo.ip,
109
108
  user_agent: connInfo.userAgent,
110
109
  content: input.body.content,
111
110
  post_id: input.body.postId,
111
+ user_email: session?.email,
112
112
  author_email: input.body.authorEmail,
113
113
  author_name: input.body.authorName,
114
114
  author_url: input.body.authorUrl,
115
115
  parent: input.body.parentId
116
- });
116
+ };
117
+ const response = await context.wordpress.kizlo.comments.create(submission);
117
118
  if (response.error) switch (response.error.code) {
118
119
  case "require_name_email": throw errors.COMMENT_NAME_EMAIL_REQUIRED();
119
120
  case "require_valid_email": throw errors.COMMENT_INVALID_EMAIL();
120
- case "kizlo_invalid_user": throw errors.COMMENT_USER_NOT_FOUND();
121
121
  case "require_valid_comment": throw errors.COMMENT_EMPTY_CONTENT();
122
122
  case "not_logged_in": throw errors.COMMENT_LOGIN_REQUIRED();
123
123
  case "comment_closed": throw errors.COMMENT_CLOSED();
package/dist/config.d.ts CHANGED
@@ -2,14 +2,28 @@ import { Fixture } from "./cli/wp/types.js";
2
2
  import "./test.js";
3
3
 
4
4
  //#region src/config.d.ts
5
+
6
+ /**
7
+ * Where Kizlo generates, and what it watches. A string is the home directory Kizlo owns the layout
8
+ * under: sources in `<dir>/server`, and the generated files (`contract.json`, the barrel, and
9
+ * `introspection.ts`) under `<dir>/server/generated`. The object form sets each path on its own. Omit
10
+ * `server` and there are no sources to watch and no contract is built, so only the introspection is
11
+ * generated (the shape a package that ships procedures but no server takes).
12
+ */
13
+ type KizloDir = string | {
14
+ /** Server sources (extensions and procedures). Watched by `kizlo dev`; its presence is what builds a contract. */
15
+ server?: string;
16
+ /** Where `contract.json` and the generated barrel are written. Defaults to `generated/` under `server`. */
17
+ contract?: string;
18
+ /** Where the generated `introspection.ts` is written. Defaults to `generated/` under `server`. */
19
+ introspection?: string;
20
+ };
5
21
  interface KizloDevConfig {
6
22
  /**
7
- * Run local WordPress under `kizlo dev`. When `true`, `kizlo dev` boots a Docker WordPress in
8
- * `.kizlo/local` alongside the contract watcher; when unset or `false`, it runs the watcher alone
9
- * (for a project pointing at your own WordPress). Set for you by `kizlo create` / `kizlo init`
10
- * when you choose local WordPress.
23
+ * Run the dev stack. On by default when local WordPress is enabled; set `false` to keep local
24
+ * WordPress on for `kizlo test` while `kizlo dev` runs the contract watcher alone.
11
25
  */
12
- local?: boolean;
26
+ enable?: boolean;
13
27
  /** Published WP port (default 8080). */
14
28
  port?: number;
15
29
  /**
@@ -17,8 +31,8 @@ interface KizloDevConfig {
17
31
  * Hub: a bare version (`"7.1.0"`), or a full tag (`"7.1.0-php8.3-apache"`) when you need
18
32
  * a specific PHP. Defaults to `latest`, so an unconfigured project gets current WordPress.
19
33
  *
20
- * Worth setting once you commit a generated WordPress client, which is derived from
21
- * whatever the stack serves: on `latest` that client goes stale the day WordPress ships.
34
+ * Worth setting once you commit a generated introspection, which is derived from
35
+ * whatever the stack serves: on `latest` that introspection goes stale the day WordPress ships.
22
36
  *
23
37
  * An existing install keeps the core files it was provisioned with, so changing this
24
38
  * takes effect on the next `kizlo dev reset` and Kizlo says so when the two disagree.
@@ -45,27 +59,24 @@ interface KizloDevConfig {
45
59
  }
46
60
  interface KizloTestConfig {
47
61
  /**
48
- * Run local WordPress under `kizlo test`. When `true`, `kizlo test` boots a disposable Docker
49
- * WordPress, seeds it, and runs your suite against it; when unset or `false`, `kizlo test` just
50
- * runs your project's test script. Set for you by `kizlo create` / `kizlo init` when you choose
51
- * local WordPress.
62
+ * Run the test stack. On by default when local WordPress is enabled; set `false` to keep local
63
+ * WordPress on for `kizlo dev` while `kizlo test` just runs the project's own test script.
64
+ */
65
+ enable?: boolean;
66
+ /**
67
+ * Fall back to the dev stack's `version` and `fixtures` when this stack leaves them unset (default
68
+ * `true`), so the two stacks a local-WordPress project always wants together are declared once.
69
+ * Set `false` to configure the test stack independently, taking Kizlo's defaults where it is silent.
52
70
  */
53
- local?: boolean;
71
+ inherit?: boolean;
54
72
  /** Published WP port (default 8889). */
55
73
  port?: number;
56
74
  /**
57
75
  * WordPress version the local stack boots, given as the tag after `wordpress:` on Docker
58
- * Hub: a bare version (`"7.1.0"`), or a full tag (`"7.1.0-php8.3-apache"`) when you need
59
- * a specific PHP. Defaults to `latest`, so an unconfigured project gets current WordPress.
60
- *
61
- * Worth setting once you commit a generated WordPress client, which is derived from
62
- * whatever the stack serves: on `latest` that client goes stale the day WordPress ships.
63
- *
64
- * An existing install keeps the core files it was provisioned with, so changing this
65
- * takes effect on the next `kizlo test reset` and Kizlo says so when the two disagree.
76
+ * Hub. Falls back to the dev stack's `version` unless `inherit` is `false`; defaults to `latest`.
66
77
  */
67
78
  version?: string;
68
- /** Integration fixtures to install + seed. */
79
+ /** Integration fixtures to install + seed. Falls back to the dev stack's `fixtures` unless `inherit` is `false`. */
69
80
  fixtures?: Fixture[];
70
81
  /**
71
82
  * Package manager used to launch the test script (default: auto-detected from
@@ -78,20 +89,12 @@ interface KizloTestConfig {
78
89
  */
79
90
  command?: string;
80
91
  }
81
- interface KizloGlobalConfig {
82
- /**
83
- * Kizlo's home directory. Kizlo owns the layout inside it: your server
84
- * (`server/`, whose `index.ts` exports `procedures` plus your integrations), the
85
- * browser `client.ts`, and the generated contract.
86
- * @default 'src/lib/kizlo' (or 'lib/kizlo' without a src dir)
87
- */
88
- dir?: string;
92
+ interface KizloLocalConfig {
89
93
  /**
90
- * Import alias prefix for generated imports (e.g. `@`). Recorded by `kizlo init`
91
- * from your `--alias` flag or its prompt, so later runs reuse it instead of asking
92
- * again; an empty string (`""`) is the recorded choice to use relative imports.
94
+ * Enable local Docker WordPress (default `true` in this object form). Set `false` to keep the
95
+ * stacks configured but off, the same as omitting `local` entirely.
93
96
  */
94
- alias?: string;
97
+ enable?: boolean;
95
98
  /**
96
99
  * Base name for the local Docker stacks (`kizlo-<name>-dev`, `kizlo-<name>-test`). Defaults
97
100
  * to the sanitized `package.json` name, falling back to the config dir basename.
@@ -99,8 +102,8 @@ interface KizloGlobalConfig {
99
102
  name?: string;
100
103
  /**
101
104
  * Give each branch its own local Docker stacks, by appending the checked-out branch to
102
- * {@link name} (`kizlo-<name>-<branch>-dev`). Off unless set, since the stacks it isolates
103
- * are the ones an existing project is already using.
105
+ * {@link KizloLocalConfig.name} (`kizlo-<name>-<branch>-dev`). Off unless set, since the stacks it
106
+ * isolates are the ones an existing project is already using.
104
107
  *
105
108
  * Turn it on when you work in several checkouts at once, typically git worktrees: without it
106
109
  * every checkout of a project resolves to one stack, so parallel `kizlo dev` and `kizlo test`
@@ -109,20 +112,31 @@ interface KizloGlobalConfig {
109
112
  * A detached `HEAD` names no branch and keeps the unsuffixed stack.
110
113
  */
111
114
  worktrees?: boolean;
112
- /**
113
- * Directory to write the WordPress client into, as `<wordpressClientDir>/wordpress.ts`. For a package or
114
- * workspace that ships procedures but no Kizlo server: there is no procedure tree to build a contract from,
115
- * yet those procedures still call the generated tree, so they need the client on its own.
116
- *
117
- * Independent of `dir`. An app leaves this unset and gets its client inside `dir` alongside the
118
- * contract; set both only if a project is somehow each of those things at once.
119
- */
120
- wordpressClientDir?: string;
121
- /** Local WordPress and contract watcher run by `kizlo dev`. */
115
+ /** The dev stack run by `kizlo dev`. */
122
116
  dev?: KizloDevConfig;
123
- /** WordPress test environment run by `kizlo test`. */
117
+ /** The test stack run by `kizlo test`. Inherits `version` and `fixtures` from {@link KizloLocalConfig.dev}. */
124
118
  test?: KizloTestConfig;
125
119
  }
120
+ interface KizloGlobalConfig {
121
+ /**
122
+ * Where Kizlo generates, and what it watches. A string names the home directory Kizlo owns the
123
+ * layout under; the object form sets the server, contract, and introspection paths independently.
124
+ * @default 'src/lib/kizlo' (or 'lib/kizlo' without a src dir)
125
+ */
126
+ dir?: KizloDir;
127
+ /**
128
+ * Import alias prefix for generated imports (e.g. `@`). Recorded by `kizlo init`
129
+ * from your `--alias` flag or its prompt, so later runs reuse it instead of asking
130
+ * again; an empty string (`""`) is the recorded choice to use relative imports.
131
+ */
132
+ alias?: string;
133
+ /**
134
+ * Local Docker WordPress, off unless set. `true` enables both the dev and test stacks with
135
+ * defaults; the object form enables and configures them, and is where the stack name, the
136
+ * per-branch `worktrees` toggle, and the two stacks live.
137
+ */
138
+ local?: boolean | KizloLocalConfig;
139
+ }
126
140
  declare function defineConfig(config: KizloGlobalConfig): KizloGlobalConfig;
127
141
  //#endregion
128
- export { KizloDevConfig, KizloGlobalConfig, KizloTestConfig, defineConfig };
142
+ export { KizloDevConfig, KizloDir, KizloGlobalConfig, KizloLocalConfig, KizloTestConfig, defineConfig };
package/dist/context.d.ts CHANGED
@@ -20,7 +20,7 @@ interface ContextConfig {
20
20
  /** WordPress plugins the registered integrations need, checked against what each response reports. */
21
21
  integrationPlugins?: IntegrationPluginRequirement[];
22
22
  }
23
- type AuthUserFn = () => Promise<AuthUser | null>;
23
+ type SessionFn = () => Promise<AuthUser | null>;
24
24
  type ConnInfoFn = () => Promise<ConnInfo | null>;
25
25
  type VerifyCaptchaFn = (token: string) => Promise<boolean>;
26
26
  type VerifyPreviewTokenFn = (token: string) => Promise<PreviewTokenPayload | null>;
@@ -46,8 +46,8 @@ interface ProcedureContext {
46
46
  config: ContextConfig;
47
47
  /** Resolve the caller's connection info (IP, geo) via the geo adapter, or `null`. */
48
48
  getConnInfo: ConnInfoFn;
49
- /** Resolve the authenticated user via the auth adapter, or `null`. */
50
- getAuthUser: AuthUserFn;
49
+ /** Resolve the authenticated session via the auth adapter, or `null`. */
50
+ getSession: SessionFn;
51
51
  /** Read and write cookies through the cookies adapter. */
52
52
  cookies: CookiesStorage;
53
53
  /** Verify a CAPTCHA token via the captcha adapter. */
@@ -84,7 +84,7 @@ declare class Context {
84
84
  email: EmailService;
85
85
  getConnInfo: ConnInfoFn;
86
86
  verifyCaptcha: VerifyCaptchaFn;
87
- getAuthUser: AuthUserFn;
87
+ getSession: SessionFn;
88
88
  verifyPreviewToken: VerifyPreviewTokenFn;
89
89
  cookies: CookiesStorage;
90
90
  invokedBy: "client";
@@ -98,7 +98,7 @@ declare class Context {
98
98
  wordpress: WordPressTransport;
99
99
  settings: SettingsService;
100
100
  email: EmailService;
101
- getAuthUser: AuthUserFn;
101
+ getSession: SessionFn;
102
102
  getConnInfo: ConnInfoFn;
103
103
  verifyCaptcha: VerifyCaptchaFn;
104
104
  verifyPreviewToken: VerifyPreviewTokenFn;
@@ -107,9 +107,9 @@ declare class Context {
107
107
  private createRestCookieStorage;
108
108
  private createVerifyCaptchaFn;
109
109
  private createConnInfoFn;
110
- private createGetUserFn;
110
+ private createGetSessionFn;
111
111
  private createVerifyPreviewTokenFn;
112
112
  private createLogger;
113
113
  }
114
114
  //#endregion
115
- export { AuthUserFn, ConnInfoFn, Context, ContextConfig, ProcedureContext, VerifyCaptchaFn, VerifyPreviewTokenFn };
115
+ export { ConnInfoFn, Context, ContextConfig, ProcedureContext, SessionFn, VerifyCaptchaFn, VerifyPreviewTokenFn };
package/dist/context.js CHANGED
@@ -102,7 +102,7 @@ var Context = class {
102
102
  email: this.email,
103
103
  getConnInfo: this.createConnInfoFn(request),
104
104
  verifyCaptcha: this.createVerifyCaptchaFn(request),
105
- getAuthUser: this.createGetUserFn(request),
105
+ getSession: this.createGetSessionFn(request),
106
106
  verifyPreviewToken: this.createVerifyPreviewTokenFn(),
107
107
  cookies: this.createRestCookieStorage(request, headers),
108
108
  invokedBy: "client"
@@ -118,7 +118,7 @@ var Context = class {
118
118
  wordpress: this.wordpress,
119
119
  settings: this.settings,
120
120
  email: this.email,
121
- getAuthUser: this.createGetUserFn(null),
121
+ getSession: this.createGetSessionFn(null),
122
122
  getConnInfo: this.createConnInfoFn(null),
123
123
  verifyCaptcha: this.createVerifyCaptchaFn(null),
124
124
  verifyPreviewToken: this.createVerifyPreviewTokenFn(),
@@ -176,9 +176,9 @@ var Context = class {
176
176
  return await this.config.adapters?.geo?.getConnInfo?.(request) ?? null;
177
177
  };
178
178
  }
179
- createGetUserFn(request) {
179
+ createGetSessionFn(request) {
180
180
  return async () => {
181
- return await Promise.resolve(this.config.adapters?.auth?.getUser?.(request)) ?? null;
181
+ return await Promise.resolve(this.config.adapters?.auth?.getSession?.(request)) ?? null;
182
182
  };
183
183
  }
184
184
  createVerifyPreviewTokenFn() {