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
@@ -1,27 +1,28 @@
1
- import { Promisify } from "@kizlo/shared";
1
+ import { JsonValue, Promisify } from "@kizlo/shared";
2
2
 
3
3
  //#region src/adapters/auth.d.ts
4
4
  interface AuthUser {
5
- /** The WordPress user id this caller maps to. */
6
- id: number;
7
- /** The user's email, if known. */
8
- email?: string;
9
- /** The user's phone number, if known. */
10
- phone?: string;
11
- /** The user's first name. */
12
- firstName: string;
13
- /** The user's last name. */
14
- lastName: string;
5
+ /** The third-party auth id. This is the identity provider's own id, never a WordPress user id. */
6
+ id: string;
7
+ /** The user's email. The single join key that maps this session to a WordPress user. */
8
+ email: string;
9
+ /** The user's first name, if known. */
10
+ firstName?: string;
11
+ /** The user's last name, if known. */
12
+ lastName?: string;
13
+ /** Arbitrary claims the auth service carries alongside the identity. */
14
+ meta?: Record<string, JsonValue>;
15
15
  }
16
- type AuthGetUser = (request: Request | null) => Promisify<AuthUser | null>;
16
+ type AuthGetSession = (request: Request | null) => Promisify<AuthUser | null>;
17
17
  interface AuthAdapter {
18
- /** Resolve the authenticated user from the request, or `null` when there is no session. Receives `null` for server-side (non-HTTP) invocations. */
19
- getUser: AuthGetUser;
18
+ /** Resolve the authenticated session from the request, or `null` when there is no session. Receives `null` for server-side (non-HTTP) invocations. */
19
+ getSession: AuthGetSession;
20
20
  }
21
21
  /** Author a custom auth adapter, typed against the {@link AuthAdapter} contract. */
22
22
  declare function createAuthAdapter(adapter: AuthAdapter): AuthAdapter;
23
23
  declare function authMock(options?: {
24
- mockUserId?: number;
24
+ id?: string;
25
+ email?: string;
25
26
  }): AuthAdapter;
26
27
  //#endregion
27
- export { AuthAdapter, AuthGetUser, AuthUser, authMock, createAuthAdapter };
28
+ export { AuthAdapter, AuthGetSession, AuthUser, authMock, createAuthAdapter };
@@ -4,13 +4,12 @@ function createAuthAdapter(adapter) {
4
4
  return adapter;
5
5
  }
6
6
  function authMock(options) {
7
- return { getUser() {
7
+ return { getSession() {
8
8
  return {
9
- id: options?.mockUserId ?? 0,
9
+ id: options?.id ?? "auth-mock-user",
10
+ email: options?.email ?? "karan@gmail.com",
10
11
  firstName: "Karan",
11
- lastName: "Gill",
12
- email: "karan@gmail.con",
13
- phone: "12345678901"
12
+ lastName: "Gill"
14
13
  };
15
14
  } };
16
15
  }
@@ -1,8 +1,8 @@
1
- import { createProcedure } from "../shared/procedure.js";
1
+ import { Category, CategoryList, ListCategoryInput, RetrieveCategoryInput } from "./schema.js";
2
+ import { createProcedure, schemaType } from "../shared/procedure.js";
2
3
  import { parseIdentifier } from "../shared/identifier.js";
3
4
  import { deserializeListMetadata } from "../shared/serialize.js";
4
5
  import { GET_CATEGORY_ERROR_MAP, LIST_CATEGORY_ERROR_MAP } from "./error.js";
5
- import { Category, CategoryList, ListCategoryInput, RetrieveCategoryInput } from "./schema.js";
6
6
  import { deserializeCategory } from "./utils.js";
7
7
  import { normalizeArrayableValue } from "@kizlo/shared";
8
8
 
@@ -13,7 +13,7 @@ const CATEGORY_PROCEDURES = {
13
13
  method: "GET",
14
14
  path: "/categories/{identifier}",
15
15
  params: RetrieveCategoryInput.pick({ identifier: true }),
16
- output: Category,
16
+ output: schemaType(Category),
17
17
  errors: GET_CATEGORY_ERROR_MAP
18
18
  }, async ({ input, context, errors }) => {
19
19
  const identifier = parseIdentifier(input.params.identifier);
@@ -38,7 +38,7 @@ const CATEGORY_PROCEDURES = {
38
38
  method: "GET",
39
39
  path: "/categories",
40
40
  query: ListCategoryInput.optional(),
41
- output: CategoryList,
41
+ output: schemaType(CategoryList),
42
42
  errors: LIST_CATEGORY_ERROR_MAP
43
43
  }, async ({ input, context, errors }) => {
44
44
  const response = await context.wordpress.taxonomies.category.list({
@@ -0,0 +1,153 @@
1
+ import { WP_CustomFields } from "../wordpress/types.js";
2
+ import "../wordpress/index.js";
3
+ import * as _kizlo_shared0 from "@kizlo/shared";
4
+ import z from "zod/v4";
5
+
6
+ //#region src/category/schema.d.ts
7
+ type CategoryCustomFields = WP_CustomFields<"taxonomies.category">;
8
+ declare const CategoryCustomFieldsSchema: z.ZodType<CategoryCustomFields, CategoryCustomFields>;
9
+ declare const Category: z.ZodObject<{
10
+ id: z.ZodNumber;
11
+ name: z.ZodString;
12
+ slug: z.ZodString;
13
+ url: z.ZodNullable<z.ZodString>;
14
+ description: z.ZodNullable<z.ZodString>;
15
+ parent: z.ZodNullable<z.ZodNumber>;
16
+ postCount: z.ZodNumber;
17
+ seo: z.ZodNullable<z.ZodObject<{
18
+ head: z.ZodObject<{
19
+ title: z.ZodString;
20
+ canonical: z.ZodString;
21
+ robots: z.ZodObject<{
22
+ index: z.ZodString;
23
+ follow: z.ZodString;
24
+ maxSnippet: z.ZodString;
25
+ maxImagePreview: z.ZodString;
26
+ maxVideoPreview: z.ZodString;
27
+ }, z.core.$strip>;
28
+ og: z.ZodObject<{
29
+ locale: z.ZodString;
30
+ type: z.ZodString;
31
+ title: z.ZodString;
32
+ url: z.ZodString;
33
+ siteName: z.ZodString;
34
+ description: z.ZodString;
35
+ image: z.ZodNullable<z.ZodObject<{
36
+ url: z.ZodString;
37
+ width: z.ZodNullable<z.ZodNumber>;
38
+ height: z.ZodNullable<z.ZodNumber>;
39
+ type: z.ZodNullable<z.ZodString>;
40
+ alt: z.ZodNullable<z.ZodString>;
41
+ }, z.core.$strip>>;
42
+ }, z.core.$strip>;
43
+ twitter: z.ZodObject<{
44
+ card: z.ZodEnum<{
45
+ summary: "summary";
46
+ summary_large_image: "summary_large_image";
47
+ }>;
48
+ title: z.ZodString;
49
+ site: z.ZodNullable<z.ZodString>;
50
+ creator: z.ZodNullable<z.ZodString>;
51
+ description: z.ZodString;
52
+ image: z.ZodNullable<z.ZodString>;
53
+ imageAlt: z.ZodNullable<z.ZodString>;
54
+ }, z.core.$strip>;
55
+ article: z.ZodNullable<z.ZodObject<{
56
+ publishedTime: z.ZodString;
57
+ modifiedTime: z.ZodString;
58
+ author: z.ZodString;
59
+ authorUrl: z.ZodString;
60
+ section: z.ZodString;
61
+ tags: z.ZodArray<z.ZodString>;
62
+ }, z.core.$strip>>;
63
+ }, z.core.$strip>;
64
+ schema: z.ZodObject<{
65
+ "@context": z.ZodLiteral<"https://schema.org">;
66
+ "@graph": z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
67
+ }, z.core.$strip>;
68
+ }, z.core.$strip>>;
69
+ custom: z.ZodType<Record<string, unknown>, Record<string, unknown>, z.core.$ZodTypeInternals<Record<string, unknown>, Record<string, unknown>>>;
70
+ meta: z.ZodRecord<z.ZodString, z.ZodType<_kizlo_shared0.JSONValue, unknown, z.core.$ZodTypeInternals<_kizlo_shared0.JSONValue, unknown>>>;
71
+ }, z.core.$strip>;
72
+ type Category = Omit<z.output<typeof Category>, "custom"> & {
73
+ custom: CategoryCustomFields;
74
+ };
75
+ declare const CategoryList: z.ZodObject<{
76
+ items: z.ZodArray<z.ZodObject<{
77
+ id: z.ZodNumber;
78
+ name: z.ZodString;
79
+ slug: z.ZodString;
80
+ url: z.ZodNullable<z.ZodString>;
81
+ description: z.ZodNullable<z.ZodString>;
82
+ parent: z.ZodNullable<z.ZodNumber>;
83
+ postCount: z.ZodNumber;
84
+ seo: z.ZodNullable<z.ZodObject<{
85
+ head: z.ZodObject<{
86
+ title: z.ZodString;
87
+ canonical: z.ZodString;
88
+ robots: z.ZodObject<{
89
+ index: z.ZodString;
90
+ follow: z.ZodString;
91
+ maxSnippet: z.ZodString;
92
+ maxImagePreview: z.ZodString;
93
+ maxVideoPreview: z.ZodString;
94
+ }, z.core.$strip>;
95
+ og: z.ZodObject<{
96
+ locale: z.ZodString;
97
+ type: z.ZodString;
98
+ title: z.ZodString;
99
+ url: z.ZodString;
100
+ siteName: z.ZodString;
101
+ description: z.ZodString;
102
+ image: z.ZodNullable<z.ZodObject<{
103
+ url: z.ZodString;
104
+ width: z.ZodNullable<z.ZodNumber>;
105
+ height: z.ZodNullable<z.ZodNumber>;
106
+ type: z.ZodNullable<z.ZodString>;
107
+ alt: z.ZodNullable<z.ZodString>;
108
+ }, z.core.$strip>>;
109
+ }, z.core.$strip>;
110
+ twitter: z.ZodObject<{
111
+ card: z.ZodEnum<{
112
+ summary: "summary";
113
+ summary_large_image: "summary_large_image";
114
+ }>;
115
+ title: z.ZodString;
116
+ site: z.ZodNullable<z.ZodString>;
117
+ creator: z.ZodNullable<z.ZodString>;
118
+ description: z.ZodString;
119
+ image: z.ZodNullable<z.ZodString>;
120
+ imageAlt: z.ZodNullable<z.ZodString>;
121
+ }, z.core.$strip>;
122
+ article: z.ZodNullable<z.ZodObject<{
123
+ publishedTime: z.ZodString;
124
+ modifiedTime: z.ZodString;
125
+ author: z.ZodString;
126
+ authorUrl: z.ZodString;
127
+ section: z.ZodString;
128
+ tags: z.ZodArray<z.ZodString>;
129
+ }, z.core.$strip>>;
130
+ }, z.core.$strip>;
131
+ schema: z.ZodObject<{
132
+ "@context": z.ZodLiteral<"https://schema.org">;
133
+ "@graph": z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
134
+ }, z.core.$strip>;
135
+ }, z.core.$strip>>;
136
+ custom: z.ZodType<Record<string, unknown>, Record<string, unknown>, z.core.$ZodTypeInternals<Record<string, unknown>, Record<string, unknown>>>;
137
+ meta: z.ZodRecord<z.ZodString, z.ZodType<_kizlo_shared0.JSONValue, unknown, z.core.$ZodTypeInternals<_kizlo_shared0.JSONValue, unknown>>>;
138
+ }, z.core.$strip>>;
139
+ meta: z.ZodObject<{
140
+ page: z.ZodNumber;
141
+ totalItems: z.ZodNumber;
142
+ totalPages: z.ZodNumber;
143
+ nextPage: z.ZodNullable<z.ZodNumber>;
144
+ prevPage: z.ZodNullable<z.ZodNumber>;
145
+ hasNextPage: z.ZodBoolean;
146
+ hasPrevPage: z.ZodBoolean;
147
+ }, z.core.$strip>;
148
+ }, z.core.$strip>;
149
+ type CategoryList = Omit<z.output<typeof CategoryList>, "items"> & {
150
+ items: Category[];
151
+ };
152
+ //#endregion
153
+ export { Category, CategoryCustomFields, CategoryCustomFieldsSchema, CategoryList };
@@ -1,5 +1,5 @@
1
- import { IdentifierInput, ListMetadata, ListOrder, customFieldsSchema } from "../shared/schema.js";
2
1
  import { Seo } from "../seo/schema.js";
2
+ import { IdentifierInput, ListMetadata, ListOrder, customFieldsSchema } from "../shared/schema.js";
3
3
  import { BooleanLike, Metadata, NumberLike, arrayable, lenient } from "@kizlo/shared";
4
4
  import z from "zod/v4";
5
5
 
@@ -14,6 +14,7 @@ const WP_CATEGORY_ORDER_BYS = [
14
14
  "description",
15
15
  "count"
16
16
  ];
17
+ const CategoryCustomFieldsSchema = customFieldsSchema();
17
18
  const Category = z.object({
18
19
  id: z.number(),
19
20
  name: z.string(),
@@ -23,7 +24,7 @@ const Category = z.object({
23
24
  parent: z.number().nullable(),
24
25
  postCount: z.number(),
25
26
  seo: Seo.nullable(),
26
- custom: customFieldsSchema(),
27
+ custom: CategoryCustomFieldsSchema,
27
28
  meta: Metadata
28
29
  });
29
30
  const RetrieveCategoryInput = z.object({ identifier: IdentifierInput });
@@ -47,4 +48,4 @@ const ListCategoryInput = z.object({
47
48
  });
48
49
 
49
50
  //#endregion
50
- export { Category, CategoryList, ListCategoryInput, RetrieveCategoryInput };
51
+ export { Category, CategoryCustomFieldsSchema, CategoryList, ListCategoryInput, RetrieveCategoryInput };
@@ -1,5 +1,5 @@
1
1
  import { aliasWithSlash, resolveModuleImport, writeFileIfAbsent } from "../utils.js";
2
- import { CONTRACT_BARREL, WORDPRESS_STUB } from "../daemon/generate.js";
2
+ import { CONTRACT_BARREL, INTROSPECTION_STUB } from "../daemon/generate.js";
3
3
  import { resolvePatch } from "../presets/template.js";
4
4
  import { orCancel } from "./_setup.js";
5
5
  import { applyPatchToSource, patchChanged, renderPatchCode, resolvePatchTargetPath } from "../presets/patch.js";
@@ -8,16 +8,17 @@ import fs from "node:fs";
8
8
  import * as p from "@clack/prompts";
9
9
 
10
10
  //#region src/cli/commands/_wiring.ts
11
- /** The kizlo.config.ts a scaffolded project gets: the Kizlo directory, the import-alias preference, and —
12
- * when local WordPress is chosen — `dev.local` + `test.local` so `kizlo dev` and `kizlo test` boot the
13
- * fixed `.kizlo/local` install. The alias is always written — `""` for relative imports included — so it
14
- * records a made decision a later `kizlo init` reads back instead of prompting for it again. The alias
15
- * is written in its canonical `@/` form (never a bare `@`) so it reads like the imports it produces. */
11
+ /** The kizlo.config.ts a scaffolded project gets: the Kizlo directory, the import-alias preference, and,
12
+ * when local WordPress is chosen, `local: true` so `kizlo dev` and `kizlo test` boot the fixed
13
+ * `.kizlo/local` install (both stacks on with defaults; the object form is for configuring them). The
14
+ * alias is always written (`""` for relative imports included) so it records a made decision a later
15
+ * `kizlo init` reads back instead of prompting for it again. The alias is written in its canonical `@/`
16
+ * form (never a bare `@`) so it reads like the imports it produces. */
16
17
  function kizloConfigTemplate(dir, alias, localWordPress = false) {
17
18
  return `import { defineConfig } from "kizlo/config"
18
19
 
19
20
  export default defineConfig({
20
- dir: "${dir}",${`\n\talias: "${aliasWithSlash(alias)}",`}${localWordPress ? `\n\tdev: { local: true },\n\ttest: { local: true },` : ""}
21
+ dir: "${dir}",${`\n\talias: "${aliasWithSlash(alias)}",`}${localWordPress ? `\n\tlocal: true,` : ""}
21
22
  })
22
23
  `;
23
24
  }
@@ -77,7 +78,7 @@ function writeGeneratedContract(cwd, serverDirRel) {
77
78
  const generatedDirRel = path.join(serverDirRel, "generated");
78
79
  writeFileIfAbsent(path.join(cwd, generatedDirRel, "contract.json"), "{}\n");
79
80
  writeFileIfAbsent(path.join(cwd, generatedDirRel, "index.ts"), CONTRACT_BARREL);
80
- writeFileIfAbsent(path.join(cwd, generatedDirRel, "wordpress.ts"), WORDPRESS_STUB);
81
+ writeFileIfAbsent(path.join(cwd, generatedDirRel, "introspection.ts"), INTROSPECTION_STUB);
81
82
  }
82
83
  /**
83
84
  * Apply every patch a template declares, merging Kizlo wiring into files the project already owns
@@ -1,6 +1,6 @@
1
1
  import { log } from "../daemon/logger.js";
2
- import { resolveConfig, resolveWordPressClientDir } from "../daemon/config.js";
3
- import { generateWordPressSource, reportGenerationError } from "../daemon/generate.js";
2
+ import { resolveConfig } from "../daemon/config.js";
3
+ import { generateIntrospectionSource, reportGenerationError } from "../daemon/generate.js";
4
4
  import { testWordPressCredentials } from "./_test-wordpress.js";
5
5
  import path from "node:path";
6
6
  import fs from "node:fs";
@@ -26,14 +26,10 @@ function formatGeneratedFileDiff(cwd, mismatch) {
26
26
  const file = path.relative(cwd, mismatch.file) || path.basename(mismatch.file);
27
27
  return createTwoFilesPatch(file, file, mismatch.actual, mismatch.expected, "committed", "generated", { context: 3 });
28
28
  }
29
- /** Every WordPress client the project configuration says it owns, with duplicates collapsed. */
30
- async function wordPressClientFiles(cwd, dir) {
31
- const files = /* @__PURE__ */ new Set();
32
- const workspaceDir = await resolveWordPressClientDir(cwd);
33
- if (workspaceDir) files.add(path.resolve(cwd, workspaceDir, "wordpress.ts"));
29
+ /** The generated introspection the project configuration owns, if any. */
30
+ async function introspectionFiles(cwd, dir) {
34
31
  const config = await resolveConfig(cwd, { dir });
35
- if (config) files.add(path.resolve(cwd, config.wordpressPath));
36
- return [...files];
32
+ return config ? [path.resolve(cwd, config.introspectionPath)] : [];
37
33
  }
38
34
  const check = defineCommand({
39
35
  meta: {
@@ -52,9 +48,9 @@ const check = defineCommand({
52
48
  },
53
49
  async run({ args }) {
54
50
  const cwd = process.cwd();
55
- const files = await wordPressClientFiles(cwd, args.dir);
51
+ const files = await introspectionFiles(cwd, args.dir);
56
52
  if (files.length === 0) {
57
- log.info("No generated WordPress client configured, nothing to check.");
53
+ log.info("No generated WordPress introspection configured, nothing to check.");
58
54
  return;
59
55
  }
60
56
  let credentials;
@@ -67,18 +63,18 @@ const check = defineCommand({
67
63
  }
68
64
  let expected;
69
65
  try {
70
- expected = await generateWordPressSource(cwd, {
66
+ expected = await generateIntrospectionSource(cwd, {
71
67
  credentials,
72
68
  strict: true
73
69
  });
74
70
  } catch (error) {
75
- reportGenerationError("Failed to check the generated WordPress client:", error);
71
+ reportGenerationError("Failed to check the generated WordPress introspection:", error);
76
72
  process.exitCode = 1;
77
73
  return;
78
74
  }
79
75
  const mismatches = findGeneratedFileMismatches(files, expected);
80
76
  if (mismatches.length === 0) {
81
- log.success(`${files.length === 1 ? "WordPress client is" : "WordPress clients are"} current`);
77
+ log.success("WordPress introspection is current");
82
78
  return;
83
79
  }
84
80
  for (const mismatch of mismatches) {
@@ -90,7 +86,7 @@ const check = defineCommand({
90
86
  args.test ? "--test" : "",
91
87
  args.dir ? `--dir ${args.dir}` : ""
92
88
  ].filter(Boolean).join(" ");
93
- log.info(`Run \`${command}\` and commit the updated client.`);
89
+ log.info(`Run \`${command}\` and commit the updated introspection.`);
94
90
  process.exitCode = 1;
95
91
  }
96
92
  });
@@ -25,12 +25,12 @@ async function resolve$1(cwd) {
25
25
  }
26
26
  /**
27
27
  * The lifecycle subcommands (`stop`/`reset`) only act on local WordPress. A project pointing at its
28
- * own WordPress (no `dev.local`) has nothing to manage, so print a plain note and exit cleanly. Returns
28
+ * own WordPress (no `local`) has nothing to manage, so print a plain note and exit cleanly. Returns
29
29
  * true when local WordPress is configured and the caller may proceed.
30
30
  */
31
31
  async function requireLocalWordPress(cwd) {
32
32
  if (await usesLocalWordPress(cwd)) return true;
33
- note("This project doesn't run local WordPress — set `dev.local: true` in kizlo.config.ts to add it.");
33
+ note("This project doesn't run local WordPress — set `local: true` in kizlo.config.ts to add it.");
34
34
  return false;
35
35
  }
36
36
  /** A dim, untagged one-liner (no consola timestamp) for the clean dev output. */
@@ -351,7 +351,7 @@ async function watchOnly(cwd) {
351
351
  async function bringUp() {
352
352
  const cwd = process.cwd();
353
353
  if (!await usesLocalWordPress(cwd)) {
354
- note("This project doesn't run local WordPress — running the contract watcher only. Set `dev.local` in kizlo.config.ts to add it.");
354
+ note("This project doesn't run local WordPress — running the contract watcher only. Set `local` in kizlo.config.ts to add it.");
355
355
  await watchOnly(cwd);
356
356
  return;
357
357
  }
@@ -1,6 +1,6 @@
1
1
  import { log } from "../daemon/logger.js";
2
- import { resolveConfig, resolveWordPressClientDir } from "../daemon/config.js";
3
- import { PartialContractError, generateOnce, generateWorkspaceClientOnce, reportGenerationError } from "../daemon/generate.js";
2
+ import { resolveConfig } from "../daemon/config.js";
3
+ import { PartialContractError, generateOnce, reportGenerationError } from "../daemon/generate.js";
4
4
  import { testWordPressCredentials } from "./_test-wordpress.js";
5
5
  import path from "node:path";
6
6
  import { defineCommand } from "citty";
@@ -12,7 +12,7 @@ import { defineCommand } from "citty";
12
12
  * mode did not do to the files on disk.
13
13
  */
14
14
  function fail(message, error) {
15
- if (error instanceof PartialContractError) log.error(`${error.message} The generated client on disk is unchanged.`);
15
+ if (error instanceof PartialContractError) log.error(`${error.message} The generated introspection on disk is unchanged.`);
16
16
  else reportGenerationError(message, error);
17
17
  process.exit(1);
18
18
  }
@@ -28,7 +28,7 @@ const generate = defineCommand({
28
28
  },
29
29
  strict: {
30
30
  type: "boolean",
31
- description: "Fail instead of generating a client WordPress had to exclude routes or types from"
31
+ description: "Fail instead of generating an introspection WordPress had to exclude routes or types from"
32
32
  },
33
33
  test: {
34
34
  type: "boolean",
@@ -50,29 +50,27 @@ const generate = defineCommand({
50
50
  credentials,
51
51
  strict
52
52
  };
53
- const wordpressClientDir = await resolveWordPressClientDir(cwd);
54
- if (wordpressClientDir) try {
55
- const result = await generateWorkspaceClientOnce(cwd, wordpressClientDir, options);
56
- const file = path.resolve(cwd, wordpressClientDir, "wordpress.ts");
57
- log.success(`WordPress client ${result === "generated" ? "written to" : "already current at"} ${file}`);
58
- } catch (error) {
59
- fail("Failed to generate the WordPress client:", error);
60
- }
61
53
  const cfg = await resolveConfig(cwd, { dir: args.dir });
62
54
  if (!cfg) {
63
- if (!wordpressClientDir) log.info("No Kizlo server directory configured, nothing to generate. Set `dir` in kizlo.config.ts or pass --dir.");
55
+ log.info("Nothing to generate. Set `dir` in kizlo.config.ts or pass --dir.");
64
56
  return;
65
57
  }
66
- let ok;
58
+ let result;
67
59
  try {
68
- ok = await generateOnce(cfg, options);
60
+ result = await generateOnce(cfg, options);
69
61
  } catch (error) {
70
62
  fail("Failed to generate the Kizlo contract:", error);
71
63
  }
72
- if (!ok) {
73
- log.error(`No Kizlo server found in ${cfg.serverEntry}`);
64
+ if (result.contract === "empty" && cfg.server) {
65
+ log.error(`No Kizlo server found in ${cfg.server.entry}`);
74
66
  process.exit(1);
75
67
  }
68
+ if (result.contract === "none") {
69
+ const file = path.resolve(cwd, cfg.introspectionPath);
70
+ log.success(`WordPress introspection ${result.introspection === "generated" ? "written to" : "already current at"} ${file}`);
71
+ return;
72
+ }
73
+ if (result.introspection === "generated") log.success("WordPress introspection generated");
76
74
  log.success("Contract generated");
77
75
  }
78
76
  });
@@ -114,9 +114,9 @@ const runArgs = {
114
114
  }
115
115
  };
116
116
  /**
117
- * Run the project's tests. When `test.local` is set, boot (+ seed) local WordPress first, run the
118
- * suite (JS then the plugin's PHPUnit) against it, then leave it up (or tear down). Without
119
- * `test.local`, there's no Docker WordPress to manage — just run the project's own test script.
117
+ * Run the project's tests. When the test stack is enabled (`local` with `test.enable !== false`), boot
118
+ * (+ seed) local WordPress first, run the suite (JS then the plugin's PHPUnit) against it, then leave it
119
+ * up (or tear down). Otherwise there's no Docker WordPress to manage — just run the project's own test script.
120
120
  */
121
121
  async function runSuite({ args }) {
122
122
  const cfg = await resolveTestConfig(process.cwd());