kizlo 0.14.1 → 0.15.1
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/dist/category/error.js +10 -4
- package/dist/category/index.js +9 -9
- package/dist/category/schema.js +10 -1
- package/dist/category/utils.js +3 -3
- package/dist/cli/commands/_setup.js +5 -3
- package/dist/cli/commands/_test-wordpress.js +27 -0
- package/dist/cli/commands/_wiring.js +2 -1
- package/dist/cli/commands/check.js +99 -0
- package/dist/cli/commands/create.js +6 -3
- package/dist/cli/commands/dev.js +27 -18
- package/dist/cli/commands/generate.js +54 -11
- package/dist/cli/commands/init.js +6 -3
- package/dist/cli/commands/test.js +9 -1
- package/dist/cli/daemon/config.js +73 -14
- package/dist/cli/daemon/generate.js +213 -6
- package/dist/cli/daemon/lock.js +31 -9
- package/dist/cli/daemon/watch.js +135 -27
- package/dist/cli/index.js +1 -0
- package/dist/cli/process-owner.js +96 -0
- package/dist/cli/utils.js +28 -14
- package/dist/cli/wp/bootstrap.js +6 -2
- package/dist/cli/wp/compose/docker-compose.yml +11 -2
- package/dist/cli/wp/compose/override.js +20 -6
- package/dist/cli/wp/compose/override.test.ts +97 -40
- package/dist/cli/wp/compose/override.ts +21 -6
- package/dist/cli/wp/constants.d.ts +19 -1
- package/dist/cli/wp/constants.js +19 -1
- package/dist/cli/wp/dev.js +27 -8
- package/dist/cli/wp/docker.d.ts +29 -3
- package/dist/cli/wp/docker.js +38 -2
- package/dist/cli/wp/ports.js +3 -1
- package/dist/cli/wp/session.js +12 -30
- package/dist/cli/wp/settings.js +2 -2
- package/dist/cli/wp/setup.js +6 -4
- package/dist/cli/wp/stack.js +2 -0
- package/dist/cli/wp/types.d.ts +28 -3
- package/dist/cli/wp/utils.d.ts +19 -3
- package/dist/cli/wp/utils.js +23 -4
- package/dist/cli/wp/version.js +57 -0
- package/dist/cli/wp/watchdog.js +37 -15
- package/dist/comment/index.js +68 -56
- package/dist/comment/utils.js +16 -8
- package/dist/config.d.ts +45 -0
- package/dist/context.d.ts +17 -12
- package/dist/context.js +71 -22
- package/dist/email/service.d.ts +2 -2
- package/dist/email/service.js +1 -7
- package/dist/index.d.ts +9 -24
- package/dist/index.js +6 -18
- package/dist/integrations/nextjs/revalidate.js +1 -1
- package/dist/kizlo.d.ts +11 -4
- package/dist/kizlo.js +34 -16
- package/dist/menu/fixture.js +26 -13
- package/dist/menu/index.js +22 -8
- package/dist/menu/schema.js +15 -1
- package/dist/menu/utils.js +10 -11
- package/dist/package.js +1 -1
- package/dist/page/error.js +10 -4
- package/dist/page/index.js +28 -33
- package/dist/page/schema.js +21 -1
- package/dist/page/utils.js +9 -8
- package/dist/post/error.js +10 -4
- package/dist/post/fixture.js +33 -20
- package/dist/post/index.js +31 -36
- package/dist/post/schema.js +33 -1
- package/dist/post/utils.js +10 -9
- package/dist/post-type/status.js +19 -0
- package/dist/router.d.ts +27 -11
- package/dist/seo/index.js +43 -11
- package/dist/seo/types.d.ts +11 -79
- package/dist/seo/utils.js +4 -1
- package/dist/settings/service.d.ts +14 -16
- package/dist/settings/service.interface.d.ts +7 -5
- package/dist/settings/service.js +18 -17
- package/dist/shared/extension.d.ts +33 -2
- package/dist/shared/extension.js +31 -1
- package/dist/tag/error.js +10 -4
- package/dist/tag/index.js +9 -9
- package/dist/tag/schema.js +10 -1
- package/dist/tag/utils.js +3 -3
- package/dist/test.d.ts +5 -7
- package/dist/test.js +4 -6
- package/dist/wordpress/constants.js +7 -5
- package/dist/wordpress/endpoint.d.ts +23 -0
- package/dist/wordpress/endpoint.js +178 -0
- package/dist/wordpress/fetch-introspection.js +74 -0
- package/dist/wordpress/generate.js +413 -0
- package/dist/wordpress/index.d.ts +3 -18
- package/dist/wordpress/introspection.js +144 -0
- package/dist/wordpress/{service.d.ts → transport.d.ts} +18 -25
- package/dist/wordpress/transport.js +264 -0
- package/dist/wordpress/typecheck.js +122 -0
- package/dist/wordpress/types.d.ts +136 -17
- package/package.json +8 -3
- package/dist/post-type/service.js +0 -48
- package/dist/taxonomy/service.js +0 -48
- package/dist/test/client.d.ts +0 -16
- package/dist/test/client.js +0 -19
- package/dist/test/server.d.ts +0 -16
- package/dist/test/server.js +0 -38
- package/dist/wordpress/category/service.d.ts +0 -28
- package/dist/wordpress/category/service.js +0 -55
- package/dist/wordpress/category/types.d.ts +0 -120
- package/dist/wordpress/category/types.js +0 -15
- package/dist/wordpress/comment/service.d.ts +0 -28
- package/dist/wordpress/comment/service.js +0 -55
- package/dist/wordpress/comment/types.d.ts +0 -206
- package/dist/wordpress/constants.d.ts +0 -14
- package/dist/wordpress/menu/items/service.d.ts +0 -28
- package/dist/wordpress/menu/items/service.js +0 -55
- package/dist/wordpress/menu/items/types.d.ts +0 -217
- package/dist/wordpress/menu/items/types.js +0 -22
- package/dist/wordpress/menu/service.d.ts +0 -30
- package/dist/wordpress/menu/service.js +0 -58
- package/dist/wordpress/menu/types.d.ts +0 -115
- package/dist/wordpress/page/service.d.ts +0 -28
- package/dist/wordpress/page/service.js +0 -55
- package/dist/wordpress/page/types.d.ts +0 -270
- package/dist/wordpress/page/types.js +0 -27
- package/dist/wordpress/post/service.d.ts +0 -28
- package/dist/wordpress/post/service.js +0 -55
- package/dist/wordpress/post/types.d.ts +0 -294
- package/dist/wordpress/post/types.js +0 -41
- package/dist/wordpress/service.js +0 -200
- package/dist/wordpress/tag/service.d.ts +0 -28
- package/dist/wordpress/tag/service.js +0 -55
- package/dist/wordpress/tag/types.d.ts +0 -113
- package/dist/wordpress/tag/types.js +0 -15
- package/dist/wordpress/user/service.d.ts +0 -28
- package/dist/wordpress/user/service.js +0 -55
- package/dist/wordpress/user/types.d.ts +0 -221
package/dist/category/error.js
CHANGED
|
@@ -5,10 +5,16 @@ const GET_CATEGORY_ERROR_MAP = defineErrorMap({ CATEGORY_NOT_FOUND: {
|
|
|
5
5
|
status: 404,
|
|
6
6
|
message: "Category not found"
|
|
7
7
|
} });
|
|
8
|
-
const LIST_CATEGORY_ERROR_MAP = defineErrorMap({
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
const LIST_CATEGORY_ERROR_MAP = defineErrorMap({
|
|
9
|
+
CATEGORY_INVALID_PAGE: {
|
|
10
|
+
status: 400,
|
|
11
|
+
message: "The requested page does not exist."
|
|
12
|
+
},
|
|
13
|
+
CATEGORY_NOT_FOUND: {
|
|
14
|
+
status: 404,
|
|
15
|
+
message: "Categories are not available."
|
|
16
|
+
}
|
|
17
|
+
});
|
|
12
18
|
|
|
13
19
|
//#endregion
|
|
14
20
|
export { GET_CATEGORY_ERROR_MAP, LIST_CATEGORY_ERROR_MAP };
|
package/dist/category/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { createProcedure } from "../shared/procedure.js";
|
|
2
2
|
import { parseIdentifier } from "../shared/identifier.js";
|
|
3
3
|
import { deserializeListMetadata } from "../shared/serialize.js";
|
|
4
|
-
import { getTaxonomyService } from "../taxonomy/service.js";
|
|
5
4
|
import { GET_CATEGORY_ERROR_MAP, LIST_CATEGORY_ERROR_MAP } from "./error.js";
|
|
6
5
|
import { Category, CategoryList, ListCategoryInput, RetrieveCategoryInput } from "./schema.js";
|
|
7
6
|
import { deserializeCategory } from "./utils.js";
|
|
7
|
+
import { normalizeArrayableValue } from "@kizlo/shared";
|
|
8
8
|
|
|
9
9
|
//#region src/category/index.ts
|
|
10
10
|
const CATEGORY_ROUTER_MAP = {
|
|
@@ -16,14 +16,14 @@ const CATEGORY_ROUTER_MAP = {
|
|
|
16
16
|
output: Category,
|
|
17
17
|
errors: GET_CATEGORY_ERROR_MAP
|
|
18
18
|
}, async ({ input, context, errors }) => {
|
|
19
|
-
const taxonomy = getTaxonomyService("category", context.wordpress);
|
|
20
19
|
const identifier = parseIdentifier(input.params.identifier);
|
|
21
20
|
if (!identifier) throw errors.CATEGORY_NOT_FOUND();
|
|
22
|
-
const response = await
|
|
21
|
+
const response = await context.wordpress.taxonomies.category.retrieve({ identifier: String(identifier.value) });
|
|
23
22
|
if (response.error) switch (response.error.code) {
|
|
24
23
|
case "invalid_taxonomy":
|
|
25
24
|
case "term_not_found":
|
|
26
|
-
case "rest_term_invalid":
|
|
25
|
+
case "rest_term_invalid":
|
|
26
|
+
case "rest_no_route": throw errors.CATEGORY_NOT_FOUND();
|
|
27
27
|
default:
|
|
28
28
|
context.logger.error("Get category unhandled error", response.error, {
|
|
29
29
|
identifier,
|
|
@@ -41,22 +41,22 @@ const CATEGORY_ROUTER_MAP = {
|
|
|
41
41
|
output: CategoryList,
|
|
42
42
|
errors: LIST_CATEGORY_ERROR_MAP
|
|
43
43
|
}, async ({ input, context, errors }) => {
|
|
44
|
-
const response = await
|
|
45
|
-
context: "edit",
|
|
44
|
+
const response = await context.wordpress.taxonomies.category.list({
|
|
46
45
|
page: input.query?.page,
|
|
47
46
|
per_page: input.query?.perPage,
|
|
48
47
|
search: input.query?.search,
|
|
49
|
-
exclude: input.query?.exclude,
|
|
50
|
-
include: input.query?.include,
|
|
48
|
+
exclude: normalizeArrayableValue(input.query?.exclude),
|
|
49
|
+
include: normalizeArrayableValue(input.query?.include),
|
|
51
50
|
order: input.query?.order,
|
|
52
51
|
orderby: input.query?.orderBy,
|
|
53
52
|
hide_empty: input.query?.hideEmpty,
|
|
54
53
|
parent: input.query?.parent,
|
|
55
54
|
post: input.query?.post,
|
|
56
|
-
slug: input.query?.slug
|
|
55
|
+
slug: normalizeArrayableValue(input.query?.slug)
|
|
57
56
|
});
|
|
58
57
|
if (response.error) switch (response.error.code) {
|
|
59
58
|
case "rest_post_invalid_page_number": throw errors.CATEGORY_INVALID_PAGE();
|
|
59
|
+
case "rest_no_route": throw errors.CATEGORY_NOT_FOUND();
|
|
60
60
|
default:
|
|
61
61
|
context.logger.error("List categories unhandled error", response.error, { code: response.error.code });
|
|
62
62
|
throw errors.INTERNAL_SERVER_ERROR();
|
package/dist/category/schema.js
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import { IdentifierInput, ListMetadata, ListOrder } from "../shared/schema.js";
|
|
2
|
-
import { WP_CATEGORY_ORDER_BYS } from "../wordpress/category/types.js";
|
|
3
2
|
import { Seo } from "../seo/schema.js";
|
|
4
3
|
import { BooleanLike, Metadata, NumberLike, arrayable, lenient } from "@kizlo/shared";
|
|
5
4
|
import z from "zod/v4";
|
|
6
5
|
|
|
7
6
|
//#region src/category/schema.ts
|
|
7
|
+
const WP_CATEGORY_ORDER_BYS = [
|
|
8
|
+
"id",
|
|
9
|
+
"include",
|
|
10
|
+
"name",
|
|
11
|
+
"slug",
|
|
12
|
+
"include_slugs",
|
|
13
|
+
"term_group",
|
|
14
|
+
"description",
|
|
15
|
+
"count"
|
|
16
|
+
];
|
|
8
17
|
const Category = z.object({
|
|
9
18
|
id: z.number(),
|
|
10
19
|
name: z.string(),
|
package/dist/category/utils.js
CHANGED
|
@@ -7,12 +7,12 @@ function deserializeCategory(data) {
|
|
|
7
7
|
id: data.id,
|
|
8
8
|
name: data.name,
|
|
9
9
|
slug: data.slug,
|
|
10
|
-
url: data.kizlo.url
|
|
10
|
+
url: data.kizlo.url,
|
|
11
11
|
description: data.description.length > 0 ? data.description : null,
|
|
12
12
|
parent: data.parent > 0 ? data.parent : null,
|
|
13
13
|
postCount: data.count,
|
|
14
|
-
seo: data.kizlo
|
|
15
|
-
meta: stringifiedMetaRecord(data.meta
|
|
14
|
+
seo: "seo" in data.kizlo ? deserializeSeo(data.kizlo.seo) : null,
|
|
15
|
+
meta: stringifiedMetaRecord(data.meta)
|
|
16
16
|
};
|
|
17
17
|
}
|
|
18
18
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { LOCAL_DIR_REL } from "../wp/constants.js";
|
|
1
|
+
import { DEFAULT_WORDPRESS_TAG, LOCAL_DIR_REL } from "../wp/constants.js";
|
|
2
2
|
import { composeStop, createStack, dockerHint, dockerStatus } from "../wp/docker.js";
|
|
3
3
|
import { DEFAULT_ENV_KEYS, availablePackageManagers, detectInvokingPackageManager, ensureGitignored, envGroups, envKeysPresent, mergeEnv, pickStackPort, writeFileIfAbsent } from "../utils.js";
|
|
4
|
-
import { promptFragment, resolvePromptDefault } from "../presets/template.js";
|
|
5
4
|
import { DEFAULT_DEV_DB_PORT, DEFAULT_DEV_PORT, resolveStackName, stackProject } from "../daemon/config.js";
|
|
5
|
+
import { promptFragment, resolvePromptDefault } from "../presets/template.js";
|
|
6
6
|
import { createAdminAppPassword } from "../wp/bootstrap.js";
|
|
7
7
|
import { bootstrapDev } from "../wp/dev.js";
|
|
8
8
|
import { removeProjectContainers } from "../wp/session.js";
|
|
@@ -129,7 +129,8 @@ async function selectPackageManager(message) {
|
|
|
129
129
|
}
|
|
130
130
|
/** The choices summary shows a prompt under a short label — the title-cased token (`linter` → `Linter`). */
|
|
131
131
|
function promptLabel(token) {
|
|
132
|
-
|
|
132
|
+
const spaced = token.replace(/([a-z0-9])([A-Z])/g, "$1 $2");
|
|
133
|
+
return spaced.charAt(0).toUpperCase() + spaced.slice(1);
|
|
133
134
|
}
|
|
134
135
|
/** What a resolved prompt answer reads as in the choices summary: the option label, `Yes`/`No`, or the text. */
|
|
135
136
|
function promptDisplay(prompt, answer) {
|
|
@@ -327,6 +328,7 @@ function devConfigFor(cwd) {
|
|
|
327
328
|
portExplicit: false,
|
|
328
329
|
dbPort: DEFAULT_DEV_DB_PORT,
|
|
329
330
|
dbPortExplicit: false,
|
|
331
|
+
wordpressTag: DEFAULT_WORDPRESS_TAG,
|
|
330
332
|
fixtures: [],
|
|
331
333
|
wordpressPath: LOCAL_DIR_REL,
|
|
332
334
|
wordpressDir: path.resolve(cwd, LOCAL_DIR_REL)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { resolveTestConfig } from "../daemon/config.js";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
|
|
4
|
+
//#region src/cli/commands/_test-wordpress.ts
|
|
5
|
+
/** Turn the test harness artifact into the credentials accepted by the introspection transport. */
|
|
6
|
+
function wordPressCredentialsFromTestArtifact(artifact, project) {
|
|
7
|
+
if (artifact.project && artifact.project !== project) throw new Error("The saved test credentials belong to another WordPress stack. Run `kizlo test` first.");
|
|
8
|
+
return {
|
|
9
|
+
url: artifact.url,
|
|
10
|
+
username: artifact.users.admin.username,
|
|
11
|
+
password: artifact.users.admin.applicationPassword
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/** Read credentials written by `kizlo test`, ensuring they belong to this branch's configured stack. */
|
|
15
|
+
async function testWordPressCredentials(cwd) {
|
|
16
|
+
const config = await resolveTestConfig(cwd);
|
|
17
|
+
let artifact;
|
|
18
|
+
try {
|
|
19
|
+
artifact = JSON.parse(fs.readFileSync(config.credentialsPath, "utf8"));
|
|
20
|
+
} catch {
|
|
21
|
+
throw new Error("No test WordPress credentials found. Run `kizlo test` first.");
|
|
22
|
+
}
|
|
23
|
+
return wordPressCredentialsFromTestArtifact(artifact, config.project);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
//#endregion
|
|
27
|
+
export { testWordPressCredentials };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { aliasWithSlash, resolveModuleImport, writeFileIfAbsent } from "../utils.js";
|
|
2
|
+
import { CONTRACT_BARREL, WORDPRESS_STUB } from "../daemon/generate.js";
|
|
2
3
|
import { resolvePatch } from "../presets/template.js";
|
|
3
4
|
import { orCancel } from "./_setup.js";
|
|
4
|
-
import { CONTRACT_BARREL } from "../daemon/generate.js";
|
|
5
5
|
import { applyPatchToSource, patchChanged, renderPatchCode, resolvePatchTargetPath } from "../presets/patch.js";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import fs from "node:fs";
|
|
@@ -77,6 +77,7 @@ function writeGeneratedContract(cwd, serverDirRel) {
|
|
|
77
77
|
const generatedDirRel = path.join(serverDirRel, "generated");
|
|
78
78
|
writeFileIfAbsent(path.join(cwd, generatedDirRel, "contract.json"), "{}\n");
|
|
79
79
|
writeFileIfAbsent(path.join(cwd, generatedDirRel, "index.ts"), CONTRACT_BARREL);
|
|
80
|
+
writeFileIfAbsent(path.join(cwd, generatedDirRel, "wordpress.ts"), WORDPRESS_STUB);
|
|
80
81
|
}
|
|
81
82
|
/**
|
|
82
83
|
* Apply every patch a template declares, merging Kizlo wiring into files the project already owns
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { log } from "../daemon/logger.js";
|
|
2
|
+
import { resolveConfig, resolveWordPressClientDir } from "../daemon/config.js";
|
|
3
|
+
import { generateWordPressSource, reportGenerationError } from "../daemon/generate.js";
|
|
4
|
+
import { testWordPressCredentials } from "./_test-wordpress.js";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import fs from "node:fs";
|
|
7
|
+
import { defineCommand } from "citty";
|
|
8
|
+
import { createTwoFilesPatch } from "diff";
|
|
9
|
+
|
|
10
|
+
//#region src/cli/commands/check.ts
|
|
11
|
+
/** Compare generated output with every configured copy without changing the files. */
|
|
12
|
+
function findGeneratedFileMismatches(files, expected) {
|
|
13
|
+
const mismatches = [];
|
|
14
|
+
for (const file of files) {
|
|
15
|
+
const actual = fs.existsSync(file) ? fs.readFileSync(file, "utf8") : "";
|
|
16
|
+
if (actual !== expected) mismatches.push({
|
|
17
|
+
file,
|
|
18
|
+
actual,
|
|
19
|
+
expected
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
return mismatches;
|
|
23
|
+
}
|
|
24
|
+
/** A standard unified diff, labelled so CI says which side is committed and which is current. */
|
|
25
|
+
function formatGeneratedFileDiff(cwd, mismatch) {
|
|
26
|
+
const file = path.relative(cwd, mismatch.file) || path.basename(mismatch.file);
|
|
27
|
+
return createTwoFilesPatch(file, file, mismatch.actual, mismatch.expected, "committed", "generated", { context: 3 });
|
|
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"));
|
|
34
|
+
const config = await resolveConfig(cwd, { dir });
|
|
35
|
+
if (config) files.add(path.resolve(cwd, config.wordpressPath));
|
|
36
|
+
return [...files];
|
|
37
|
+
}
|
|
38
|
+
const check = defineCommand({
|
|
39
|
+
meta: {
|
|
40
|
+
name: "check",
|
|
41
|
+
description: "Check the Kizlo project without changing files"
|
|
42
|
+
},
|
|
43
|
+
args: {
|
|
44
|
+
dir: {
|
|
45
|
+
type: "string",
|
|
46
|
+
description: "Override the Kizlo directory (defaults to kizlo.config.ts)"
|
|
47
|
+
},
|
|
48
|
+
test: {
|
|
49
|
+
type: "boolean",
|
|
50
|
+
description: "Check against WordPress left running by kizlo test"
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
async run({ args }) {
|
|
54
|
+
const cwd = process.cwd();
|
|
55
|
+
const files = await wordPressClientFiles(cwd, args.dir);
|
|
56
|
+
if (files.length === 0) {
|
|
57
|
+
log.info("No generated WordPress client configured, nothing to check.");
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
let credentials;
|
|
61
|
+
if (args.test) try {
|
|
62
|
+
credentials = await testWordPressCredentials(cwd);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
log.error(error instanceof Error ? error.message : String(error));
|
|
65
|
+
process.exitCode = 1;
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
let expected;
|
|
69
|
+
try {
|
|
70
|
+
expected = await generateWordPressSource(cwd, {
|
|
71
|
+
credentials,
|
|
72
|
+
strict: true
|
|
73
|
+
});
|
|
74
|
+
} catch (error) {
|
|
75
|
+
reportGenerationError("Failed to check the generated WordPress client:", error);
|
|
76
|
+
process.exitCode = 1;
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const mismatches = findGeneratedFileMismatches(files, expected);
|
|
80
|
+
if (mismatches.length === 0) {
|
|
81
|
+
log.success(`${files.length === 1 ? "WordPress client is" : "WordPress clients are"} current`);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
for (const mismatch of mismatches) {
|
|
85
|
+
log.error(`${path.relative(cwd, mismatch.file)} is stale`);
|
|
86
|
+
process.stderr.write(`${formatGeneratedFileDiff(cwd, mismatch)}\n`);
|
|
87
|
+
}
|
|
88
|
+
const command = [
|
|
89
|
+
"kizlo generate",
|
|
90
|
+
args.test ? "--test" : "",
|
|
91
|
+
args.dir ? `--dir ${args.dir}` : ""
|
|
92
|
+
].filter(Boolean).join(" ");
|
|
93
|
+
log.info(`Run \`${command}\` and commit the updated client.`);
|
|
94
|
+
process.exitCode = 1;
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
//#endregion
|
|
99
|
+
export { check };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { approveBuildsCommand, cleanupWorkspaceArtifacts, detectInvokingPackageManager, detectPackageManager, ensureGitignored, findWorkspaceRoot, initGitRepository, installArgs, isCommandAvailable, isGitRepository, isWorkspaceMember, runCommandCapturedAsync, tokenizeCommand } from "../utils.js";
|
|
1
|
+
import { aliasWithSlash, approveBuildsCommand, cleanupWorkspaceArtifacts, detectInvokingPackageManager, detectPackageManager, effectiveAlias, ensureGitignored, findWorkspaceRoot, initGitRepository, installArgs, isCommandAvailable, isGitRepository, isWorkspaceMember, runCommandCapturedAsync, tokenizeCommand } from "../utils.js";
|
|
2
2
|
import { fetchRegistry, listTemplates, resolveRegistry } from "../presets/source.js";
|
|
3
3
|
import { adaptFile, changesFor, fileEntries, minCliError, patchEntries, readManifest, resolveDependencies } from "../presets/template.js";
|
|
4
4
|
import { collectConnectionFromEnv, collectConnectionInteractively, collectTemplatePrompts, nextStepsLines, orCancel, pickAppPort, provisionLocalWordPress, readEnvExample, resolveEnvKeys, selectPackageManager, syncRemote, validate, withApiPath, writeEnv } from "./_setup.js";
|
|
@@ -183,13 +183,16 @@ function bootstrapArgs(manifest, pm, name, promptArgs = {}) {
|
|
|
183
183
|
* files are written with `force`, silently replacing the framework defaults.
|
|
184
184
|
*/
|
|
185
185
|
async function applyManifestWiring(dir, templateDir, manifest, opts) {
|
|
186
|
-
const {
|
|
186
|
+
const { kizloPath } = manifest.config;
|
|
187
|
+
const hasSrcDir = fs.existsSync(path.join(dir, "src"));
|
|
188
|
+
const alias = aliasWithSlash(effectiveAlias(dir, kizloPath, manifest.config.alias));
|
|
187
189
|
const scaffold = buildScaffoldContext(dir, {
|
|
188
190
|
dirRel: kizloPath,
|
|
189
|
-
hasSrcDir
|
|
191
|
+
hasSrcDir,
|
|
190
192
|
alias,
|
|
191
193
|
clientUrl: void 0
|
|
192
194
|
});
|
|
195
|
+
if (manifest.config.alias && !alias) p.log.warn(`The ${manifest.name ?? manifest.id} scaffold declares no tsconfig path for \`${aliasWithSlash(manifest.config.alias)}\`, so Kizlo wrote relative imports instead.`);
|
|
193
196
|
recordDependencies(dir, manifest);
|
|
194
197
|
const changes = changesFor(manifest, "create", { includeExamples: opts.includeExamples });
|
|
195
198
|
const files = [{
|
package/dist/cli/commands/dev.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createStack, dockerHint, dockerStatus } from "../wp/docker.js";
|
|
1
|
+
import { createStack, dockerHint, dockerStatus, stackStatus } from "../wp/docker.js";
|
|
2
2
|
import { findConfigDir } from "../wp/utils.js";
|
|
3
3
|
import { log } from "../daemon/logger.js";
|
|
4
4
|
import { DEFAULT_ENV_KEYS, ensureGitignored, envGroups, groupDefault, mergeEnv, pickStackPort, withSpinner } from "../utils.js";
|
|
@@ -117,8 +117,8 @@ function updateWpEnv(cfg, info) {
|
|
|
117
117
|
* The full fresh-install write goes through {@link updateWpEnv} instead; this covers the resume path it
|
|
118
118
|
* skips. Returns true when it changed the file. The app still needs a restart to read the new value.
|
|
119
119
|
*/
|
|
120
|
-
function syncLocalUrl(
|
|
121
|
-
const envPath = join(
|
|
120
|
+
function syncLocalUrl(configDir, url) {
|
|
121
|
+
const envPath = join(configDir, ".env");
|
|
122
122
|
if (!existsSync(envPath)) return false;
|
|
123
123
|
const existing = readFileSync(envPath, "utf8");
|
|
124
124
|
if (readEnvValue(existing, "KIZLO_LOCAL_WP_URL") === url) return false;
|
|
@@ -134,23 +134,23 @@ function syncLocalUrl(cfg, url) {
|
|
|
134
134
|
* `next dev` child server — nothing stops them automatically when we die. A dying process
|
|
135
135
|
* also can't be trusted to finish async work (a closed terminal breaks its stdout
|
|
136
136
|
* mid-write), so this process does no docker work on the way out. It only needs to
|
|
137
|
-
* *exit*; the watchdog
|
|
138
|
-
* we're gone
|
|
137
|
+
* *exit*; the watchdog verifies our process identity from its own session and stops it
|
|
138
|
+
* the moment we're gone, by Ctrl+C, SIGTERM, a closed tab, SIGKILL, or a crash alike.
|
|
139
139
|
*
|
|
140
140
|
* We trigger that exit two ways, because neither alone is enough: signal handlers (for
|
|
141
141
|
* Ctrl+C / `kill`), and an stdin/TTY-close watch (for a closed terminal, whose SIGHUP
|
|
142
142
|
* an `npm`/`pnpm` wrapper silently swallows — leaving us orphaned but alive otherwise).
|
|
143
143
|
*/
|
|
144
|
-
function armForegroundTeardown(cfg) {
|
|
145
|
-
registerSession(cfg.project);
|
|
146
|
-
spawnWatchdog(cfg.project);
|
|
144
|
+
async function armForegroundTeardown(cfg) {
|
|
145
|
+
const owner = await registerSession(cfg.project);
|
|
146
|
+
spawnWatchdog(cfg.project, owner);
|
|
147
147
|
let exiting = false;
|
|
148
|
-
const shutdown = (
|
|
148
|
+
const shutdown = (line) => {
|
|
149
149
|
if (exiting) return;
|
|
150
150
|
exiting = true;
|
|
151
151
|
unregisterSession(cfg.project);
|
|
152
152
|
try {
|
|
153
|
-
|
|
153
|
+
if (line) note(line);
|
|
154
154
|
} catch {}
|
|
155
155
|
process.exit(0);
|
|
156
156
|
};
|
|
@@ -158,15 +158,20 @@ function armForegroundTeardown(cfg) {
|
|
|
158
158
|
"SIGINT",
|
|
159
159
|
"SIGTERM",
|
|
160
160
|
"SIGHUP"
|
|
161
|
-
]) process.on(signal, () => shutdown(signal));
|
|
161
|
+
]) process.on(signal, () => shutdown(stopping(signal)));
|
|
162
162
|
if (process.stdin.isTTY) {
|
|
163
163
|
process.stdin.resume();
|
|
164
164
|
for (const event of [
|
|
165
165
|
"end",
|
|
166
166
|
"close",
|
|
167
167
|
"error"
|
|
168
|
-
]) process.stdin.on(event, () => shutdown("terminal closed"));
|
|
168
|
+
]) process.stdin.on(event, () => shutdown(stopping("terminal closed")));
|
|
169
169
|
}
|
|
170
|
+
return shutdown;
|
|
171
|
+
}
|
|
172
|
+
/** The teardown line for a session leaving while its stack is still up, which this one is stopping. */
|
|
173
|
+
function stopping(reason) {
|
|
174
|
+
return `Stopping local WordPress (${reason})…`;
|
|
170
175
|
}
|
|
171
176
|
/**
|
|
172
177
|
* Bring the dev stack's containers up for `cfg`: clear any stale containers, resolve the published
|
|
@@ -218,7 +223,7 @@ async function report(cfg, creds, reloaded, start) {
|
|
|
218
223
|
});
|
|
219
224
|
if (!sync.ok) note(`Could not sync the site settings to WordPress (${sync.error}) — make sure the kizlo plugin is active, then set them from the Kizlo settings.`);
|
|
220
225
|
}
|
|
221
|
-
} else if (syncLocalUrl(cfg, creds.url)) note("Updated .env WordPress URL to the current network address — restart your app to pick it up");
|
|
226
|
+
} else if (syncLocalUrl(cfg.configDir, creds.url)) note("Updated .env WordPress URL to the current network address — restart your app to pick it up");
|
|
222
227
|
printSummary(creds);
|
|
223
228
|
const { green, dim, bold, reset } = palette();
|
|
224
229
|
const ms = Date.now() - start;
|
|
@@ -259,17 +264,21 @@ async function rebootStack(cwd, label) {
|
|
|
259
264
|
* teardown first (so a mid-startup cancel still stops partial containers), boots and prints the
|
|
260
265
|
* summary, then parks. While parked it watches `.env` and `kizlo.config.*`: a change reboots the whole
|
|
261
266
|
* stack in place so a new port, connection, or fixture takes effect without rerunning the command. The
|
|
262
|
-
* reboot runs in the same process
|
|
263
|
-
* guarded so the credential/URL writes it makes to `.env` don't trigger another reboot. A referenced
|
|
267
|
+
* reboot runs in the same process. The watchdog and session keep watching this owner throughout, and it
|
|
268
|
+
* is guarded so the credential/URL writes it makes to `.env` don't trigger another reboot. A referenced
|
|
264
269
|
* timer holds the event loop open — an unresolved promise alone would not, so without it Node would
|
|
265
270
|
* empty its loop and exit straight after printing.
|
|
266
271
|
*/
|
|
267
272
|
async function startForeground(cfg) {
|
|
268
273
|
const cwd = process.cwd();
|
|
269
274
|
let ready = await prepareStack(cfg);
|
|
270
|
-
armForegroundTeardown(ready);
|
|
275
|
+
const shutdown = await armForegroundTeardown(ready);
|
|
271
276
|
await bootAndReport(ready);
|
|
272
|
-
|
|
277
|
+
const stack = {
|
|
278
|
+
status: () => stackStatus(ready.project),
|
|
279
|
+
onStopped: () => shutdown()
|
|
280
|
+
};
|
|
281
|
+
let stopContract = await startWatcher(ready.configDir, { stack });
|
|
273
282
|
let reloading = false;
|
|
274
283
|
const reload = async (changed) => {
|
|
275
284
|
if (reloading) return;
|
|
@@ -277,7 +286,7 @@ async function startForeground(cfg) {
|
|
|
277
286
|
try {
|
|
278
287
|
stopContract?.();
|
|
279
288
|
ready = await rebootStack(cwd, `${changed} changed — restarting the session`);
|
|
280
|
-
stopContract = await startWatcher(ready.configDir);
|
|
289
|
+
stopContract = await startWatcher(ready.configDir, { stack });
|
|
281
290
|
} catch (error) {
|
|
282
291
|
log.error("Failed to reload the dev session:", error);
|
|
283
292
|
} finally {
|
|
@@ -1,30 +1,73 @@
|
|
|
1
1
|
import { log } from "../daemon/logger.js";
|
|
2
|
-
import { resolveConfig } from "../daemon/config.js";
|
|
3
|
-
import { generateOnce } from "../daemon/generate.js";
|
|
2
|
+
import { resolveConfig, resolveWordPressClientDir } from "../daemon/config.js";
|
|
3
|
+
import { PartialContractError, generateOnce, generateWorkspaceClientOnce, reportGenerationError } from "../daemon/generate.js";
|
|
4
|
+
import { testWordPressCredentials } from "./_test-wordpress.js";
|
|
5
|
+
import path from "node:path";
|
|
4
6
|
import { defineCommand } from "citty";
|
|
5
7
|
|
|
6
8
|
//#region src/cli/commands/generate.ts
|
|
9
|
+
/**
|
|
10
|
+
* Report and exit non-zero. A refused partial contract says one thing here that it cannot say
|
|
11
|
+
* anywhere else, since its diagnostics are already on screen and the useful detail is what strict
|
|
12
|
+
* mode did not do to the files on disk.
|
|
13
|
+
*/
|
|
14
|
+
function fail(message, error) {
|
|
15
|
+
if (error instanceof PartialContractError) log.error(`${error.message} The generated client on disk is unchanged.`);
|
|
16
|
+
else reportGenerationError(message, error);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
7
19
|
const generate = defineCommand({
|
|
8
20
|
meta: {
|
|
9
21
|
name: "generate",
|
|
10
22
|
description: "Generate the Kizlo contract once (for builds and CI)"
|
|
11
23
|
},
|
|
12
|
-
args: {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
24
|
+
args: {
|
|
25
|
+
dir: {
|
|
26
|
+
type: "string",
|
|
27
|
+
description: "Override the Kizlo directory (defaults to kizlo.config.ts)"
|
|
28
|
+
},
|
|
29
|
+
strict: {
|
|
30
|
+
type: "boolean",
|
|
31
|
+
description: "Fail instead of generating a client WordPress had to exclude routes or types from"
|
|
32
|
+
},
|
|
33
|
+
test: {
|
|
34
|
+
type: "boolean",
|
|
35
|
+
description: "Generate against WordPress left running by kizlo test"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
16
38
|
async run({ args }) {
|
|
17
|
-
const
|
|
39
|
+
const cwd = process.cwd();
|
|
40
|
+
const strict = args.strict === true;
|
|
41
|
+
let credentials;
|
|
42
|
+
if (args.test) try {
|
|
43
|
+
credentials = await testWordPressCredentials(cwd);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
log.error(error instanceof Error ? error.message : String(error));
|
|
46
|
+
process.exitCode = 1;
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const options = {
|
|
50
|
+
credentials,
|
|
51
|
+
strict
|
|
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
|
+
const cfg = await resolveConfig(cwd, { dir: args.dir });
|
|
18
62
|
if (!cfg) {
|
|
19
|
-
log.info("No Kizlo server directory configured, nothing to generate. Set `dir` in kizlo.config.ts or pass --dir.");
|
|
63
|
+
if (!wordpressClientDir) log.info("No Kizlo server directory configured, nothing to generate. Set `dir` in kizlo.config.ts or pass --dir.");
|
|
20
64
|
return;
|
|
21
65
|
}
|
|
22
66
|
let ok;
|
|
23
67
|
try {
|
|
24
|
-
ok = await generateOnce(cfg);
|
|
68
|
+
ok = await generateOnce(cfg, options);
|
|
25
69
|
} catch (error) {
|
|
26
|
-
|
|
27
|
-
process.exit(1);
|
|
70
|
+
fail("Failed to generate the Kizlo contract:", error);
|
|
28
71
|
}
|
|
29
72
|
if (!ok) {
|
|
30
73
|
log.error(`No Kizlo server found in ${cfg.serverEntry}`);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { addDependencyArgs, aliasWithSlash, detectImportAlias, detectInvokingPackageManager, detectPackageManager, ensureGitignored, getVersion, loadEnvFiles, persistPackageManagerField, readPersistedAlias, runCommandAsync } from "../utils.js";
|
|
1
|
+
import { addDependencyArgs, aliasWithSlash, detectImportAlias, detectInvokingPackageManager, detectPackageManager, effectiveAlias, ensureGitignored, getVersion, loadEnvFiles, persistPackageManagerField, readPersistedAlias, runCommandAsync } from "../utils.js";
|
|
2
2
|
import { DEFAULT_REGISTRY, detectTemplates, fetchRegistry, isSingleTemplate, listTemplates, resolveRegistry } from "../presets/source.js";
|
|
3
3
|
import { adaptFile, changesFor, fileEntries, isOlderVersion, minCliError, patchEntries, readManifest, renderNote, resolveDependencies, unmetRequirement } from "../presets/template.js";
|
|
4
4
|
import { collectConnectionFromEnv, collectConnectionInteractively, dirPath, envConflicts, nextStepsNote, orCancel, provisionLocalWordPress, readEnvExample, resolveEnvKeys, selectPackageManager, syncRemote, validate, withApiPath, writeEnv } from "./_setup.js";
|
|
@@ -254,13 +254,15 @@ const init = defineCommand({
|
|
|
254
254
|
...aliasCtx,
|
|
255
255
|
envKeys
|
|
256
256
|
}) : await collectInteractively(aliasCtx);
|
|
257
|
-
|
|
257
|
+
const dirRel = setup.dir.replace(/^\.\//, "").replace(/\/+$/, "");
|
|
258
|
+
const requestedAlias = aliasWithSlash(setup.alias);
|
|
259
|
+
setup.alias = aliasWithSlash(effectiveAlias(cwd, dirRel, setup.alias));
|
|
260
|
+
const droppedAlias = requestedAlias && !setup.alias ? requestedAlias : void 0;
|
|
258
261
|
if (apiPath && setup.baseUrl) setup.baseUrl = withApiPath(setup.baseUrl, apiPath);
|
|
259
262
|
const includeExamples = yes ? true : orCancel(await p.confirm({
|
|
260
263
|
message: "Add example pages?",
|
|
261
264
|
initialValue: true
|
|
262
265
|
}));
|
|
263
|
-
const dirRel = setup.dir.replace(/^\.\//, "").replace(/\/+$/, "");
|
|
264
266
|
const serverDirRel = path.join(dirRel, "server");
|
|
265
267
|
const clientUrl = setup.siteUrl && !sameOrigin(setup.siteUrl, setup.baseUrl) ? setup.baseUrl : void 0;
|
|
266
268
|
const scaffold = buildScaffoldContext(cwd, {
|
|
@@ -306,6 +308,7 @@ const init = defineCommand({
|
|
|
306
308
|
}
|
|
307
309
|
if (shouldPersistPm) persistPackageManagerField(cwd, pm);
|
|
308
310
|
const warnings = [];
|
|
311
|
+
if (droppedAlias) warnings.push(`No tsconfig path maps \`${droppedAlias}\` onto ${dirRel}, so relative imports were written instead. Add \`"${droppedAlias}*": ["./${hasSrcDir ? "src/" : ""}*"]\` to your tsconfig \`paths\` and re-run to use the alias.`);
|
|
309
312
|
const scaffolded = [];
|
|
310
313
|
let gitignore = "present";
|
|
311
314
|
const kizloSpec = `kizlo@${manifest.dependencies?.kizlo ?? `^${getVersion()}`}`;
|
|
@@ -3,6 +3,7 @@ import { isSeeded, recordedPort } from "../wp/utils.js";
|
|
|
3
3
|
import { log } from "../daemon/logger.js";
|
|
4
4
|
import { isFree } from "../wp/ports.js";
|
|
5
5
|
import { groupDefault, pickStackPort, withSpinner } from "../utils.js";
|
|
6
|
+
import { warnVersionDrift } from "../wp/version.js";
|
|
6
7
|
import { resolveTestConfig } from "../daemon/config.js";
|
|
7
8
|
import { testStack } from "../wp/stack.js";
|
|
8
9
|
import { runPluginPhpunit } from "../wp/phpunit.js";
|
|
@@ -37,7 +38,7 @@ async function resolveTestPort(cfg, stack, fresh) {
|
|
|
37
38
|
if (!fresh) {
|
|
38
39
|
const live = await stack.publishedPort();
|
|
39
40
|
if (live !== void 0) return live;
|
|
40
|
-
const recorded = recordedPort();
|
|
41
|
+
const recorded = recordedPort(cfg.project);
|
|
41
42
|
if (recorded !== void 0) {
|
|
42
43
|
if (await isFree(recorded)) return recorded;
|
|
43
44
|
log.error(`The test WordPress port ${recorded} (from a previous run) is now held by something else.\nFree that port, or run \`kizlo test reset\` to rebuild on a fresh one.`);
|
|
@@ -60,9 +61,16 @@ async function bringUp(cfg, stack, fresh = false) {
|
|
|
60
61
|
port
|
|
61
62
|
}));
|
|
62
63
|
await withSpinner("Starting test WordPress", () => bound.composeUp(), "Test WordPress ready");
|
|
64
|
+
await warnVersionDrift({
|
|
65
|
+
wpCli: bound.wpCli,
|
|
66
|
+
tag: cfg.wordpressTag,
|
|
67
|
+
resetCommand: "kizlo test reset",
|
|
68
|
+
warn: (message) => log.warn(message)
|
|
69
|
+
});
|
|
63
70
|
if (await isSeeded()) log.info("Test WordPress already seeded — skipping seed.");
|
|
64
71
|
else await withSpinner("Seeding WordPress", () => runSeeds({
|
|
65
72
|
port,
|
|
73
|
+
project: cfg.project,
|
|
66
74
|
fixtures: cfg.fixtures
|
|
67
75
|
}), "WordPress seeded");
|
|
68
76
|
return {
|