kizlo 0.14.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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/dev.js +27 -18
- package/dist/cli/commands/generate.js +54 -11
- 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/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 +35 -17
- 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
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { LOCAL_DIR_REL } from "../wp/constants.js";
|
|
1
|
+
import { DEFAULT_WORDPRESS_TAG, LOCAL_DIR_REL, WORDPRESS_META_REL } from "../wp/constants.js";
|
|
2
2
|
import { credentialsPath, findConfigDir } from "../wp/utils.js";
|
|
3
3
|
import { log } from "./logger.js";
|
|
4
4
|
import { detectPackageManager } from "../utils.js";
|
|
5
|
+
import { WORDPRESS_TAG_PATTERN } from "../wp/version.js";
|
|
5
6
|
import { importIgnoringVirtualModules } from "./jiti.js";
|
|
6
7
|
import z from "zod/v4";
|
|
7
8
|
import path from "node:path";
|
|
@@ -9,20 +10,30 @@ import fs from "node:fs";
|
|
|
9
10
|
|
|
10
11
|
//#region src/cli/daemon/config.ts
|
|
11
12
|
const fixtureSchema = z.custom((value) => typeof value === "object" && value !== null && typeof value.name === "string", { message: "must be a fixture object with a string `name` (use defineFixture)" });
|
|
13
|
+
/**
|
|
14
|
+
* `dev.version` / `test.version`. Validated against Docker's tag grammar here rather than at boot,
|
|
15
|
+
* so `"wordpress:7.1.0"`, which would resolve to `wordpress:wordpress:7.1.0`, is named as a config
|
|
16
|
+
* mistake instead of surfacing as a pull failure seconds into starting a stack.
|
|
17
|
+
*/
|
|
18
|
+
const versionSchema = z.string().regex(WORDPRESS_TAG_PATTERN, "must be a WordPress image tag, like \"7.1.0\" or \"7.1.0-php8.3-apache\" (no \"wordpress:\" prefix)");
|
|
12
19
|
/** Runtime shape of `kizlo.config.*` — mirrors {@link KizloGlobalConfig}. */
|
|
13
20
|
const configSchema = z.object({
|
|
14
21
|
dir: z.string().optional(),
|
|
15
22
|
alias: z.string().optional(),
|
|
16
23
|
name: z.string().optional(),
|
|
24
|
+
worktrees: z.boolean().optional(),
|
|
25
|
+
wordpressClientDir: z.string().optional(),
|
|
17
26
|
dev: z.object({
|
|
18
27
|
local: z.boolean().optional(),
|
|
19
28
|
port: z.number().int().positive().optional(),
|
|
29
|
+
version: versionSchema.optional(),
|
|
20
30
|
dbPort: z.number().int().positive().optional(),
|
|
21
31
|
fixtures: z.array(fixtureSchema).optional()
|
|
22
32
|
}).optional(),
|
|
23
33
|
test: z.object({
|
|
24
34
|
local: z.boolean().optional(),
|
|
25
35
|
port: z.number().int().positive().optional(),
|
|
36
|
+
version: versionSchema.optional(),
|
|
26
37
|
fixtures: z.array(fixtureSchema).optional(),
|
|
27
38
|
packageManager: z.enum([
|
|
28
39
|
"npm",
|
|
@@ -80,29 +91,69 @@ async function resolveConfig(cwd, flags) {
|
|
|
80
91
|
serverEntry: path.join(serverDir, "index.ts"),
|
|
81
92
|
generatedDir,
|
|
82
93
|
contractPath: path.join(generatedDir, "contract.json"),
|
|
83
|
-
barrelPath: path.join(generatedDir, "index.ts")
|
|
94
|
+
barrelPath: path.join(generatedDir, "index.ts"),
|
|
95
|
+
wordpressPath: path.join(generatedDir, "wordpress.ts"),
|
|
96
|
+
wordpressMetaPath: WORDPRESS_META_REL
|
|
84
97
|
};
|
|
85
98
|
}
|
|
86
99
|
/**
|
|
100
|
+
* The directory `wordpress.ts` is written to for a workspace with no Kizlo server, from `wordpressClientDir`
|
|
101
|
+
* in `kizlo.config.*`. Resolved on its own rather than through {@link resolveConfig}, which describes a
|
|
102
|
+
* server layout and returns nothing without `dir` — the two are independent.
|
|
103
|
+
*/
|
|
104
|
+
async function resolveWordPressClientDir(cwd) {
|
|
105
|
+
return (await loadConfigFile(cwd))?.wordpressClientDir;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
87
108
|
* Sanitize an arbitrary package/dir name into a valid Docker compose project id:
|
|
88
109
|
* lowercase, `@scope/pkg` → `scope-pkg`, dropping anything outside `[a-z0-9_-]`.
|
|
89
110
|
*/
|
|
90
111
|
function sanitizeProjectName(raw) {
|
|
91
112
|
return raw.toLowerCase().replace(/^@/, "").replace(/\//g, "-").replace(/[^a-z0-9_-]/g, "").replace(/^[-_]+|[-_]+$/g, "") || "kizlo";
|
|
92
113
|
}
|
|
114
|
+
const HEAD_REF_PREFIX = "ref: refs/heads/";
|
|
115
|
+
/**
|
|
116
|
+
* The branch checked out at `dir`, read out of git's own files rather than by running git, so this
|
|
117
|
+
* stays synchronous and works where git isn't on PATH. `.git` is a directory in a main checkout and
|
|
118
|
+
* a file holding `gitdir: <path>` in a linked worktree, which is what makes each worktree report its
|
|
119
|
+
* own branch. Returns undefined when there is no branch to name: no repository, an unreadable one, or
|
|
120
|
+
* a detached `HEAD`, which holds a commit id instead of a ref.
|
|
121
|
+
*/
|
|
122
|
+
function currentBranch(dir) {
|
|
123
|
+
try {
|
|
124
|
+
const dotGit = path.join(dir, ".git");
|
|
125
|
+
let gitDir = dotGit;
|
|
126
|
+
if (fs.statSync(dotGit).isFile()) {
|
|
127
|
+
const pointer = fs.readFileSync(dotGit, "utf8").match(/^gitdir:\s*(.+)$/m);
|
|
128
|
+
if (!pointer?.[1]) return void 0;
|
|
129
|
+
gitDir = path.resolve(dir, pointer[1].trim());
|
|
130
|
+
}
|
|
131
|
+
const head = fs.readFileSync(path.join(gitDir, "HEAD"), "utf8").trim();
|
|
132
|
+
return head.startsWith(HEAD_REF_PREFIX) ? head.slice(16) : void 0;
|
|
133
|
+
} catch {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
93
137
|
/**
|
|
94
138
|
* Base name for the local stacks: the config `name` if set, else the `package.json`
|
|
95
139
|
* `name` at `configDir`, else the config dir basename — sanitized to a Docker id.
|
|
140
|
+
*
|
|
141
|
+
* With `worktrees` on, the checked-out branch is appended rather than replacing that name, so
|
|
142
|
+
* sibling checkouts of one project separate while two unrelated projects sitting on the same branch
|
|
143
|
+
* stay apart. Replacing it would collide those two, which is this option's own bug one level up.
|
|
96
144
|
*/
|
|
97
|
-
function resolveStackName(configDir,
|
|
98
|
-
|
|
99
|
-
|
|
145
|
+
function resolveStackName(configDir, { name, worktrees } = {}) {
|
|
146
|
+
const base = name ? sanitizeProjectName(name) : sanitizeProjectName(packageName(configDir) ?? path.basename(configDir));
|
|
147
|
+
if (!worktrees) return base;
|
|
148
|
+
const branch = currentBranch(configDir);
|
|
149
|
+
return branch ? `${base}-${sanitizeProjectName(branch)}` : base;
|
|
150
|
+
}
|
|
151
|
+
function packageName(configDir) {
|
|
100
152
|
try {
|
|
101
|
-
|
|
153
|
+
return JSON.parse(fs.readFileSync(path.join(configDir, "package.json"), "utf8")).name;
|
|
102
154
|
} catch {
|
|
103
|
-
|
|
155
|
+
return;
|
|
104
156
|
}
|
|
105
|
-
return sanitizeProjectName(pkgName ?? path.basename(configDir));
|
|
106
157
|
}
|
|
107
158
|
/**
|
|
108
159
|
* Compose project id for a stack: `kizlo-<name>-<kind>`. The leading `kizlo-` is what
|
|
@@ -124,10 +175,14 @@ async function resolveTestConfig(cwd) {
|
|
|
124
175
|
return {
|
|
125
176
|
configDir,
|
|
126
177
|
local: Boolean(test.local),
|
|
127
|
-
project: stackProject(resolveStackName(configDir,
|
|
178
|
+
project: stackProject(resolveStackName(configDir, {
|
|
179
|
+
name: fileConfig?.name,
|
|
180
|
+
worktrees: fileConfig?.worktrees
|
|
181
|
+
}), "test"),
|
|
128
182
|
command: test.command,
|
|
129
183
|
port: test.port ?? DEFAULT_TEST_PORT,
|
|
130
|
-
portExplicit: test.port !== void 0,
|
|
184
|
+
portExplicit: test.port !== void 0 && !fileConfig?.worktrees,
|
|
185
|
+
wordpressTag: test.version ?? DEFAULT_WORDPRESS_TAG,
|
|
131
186
|
fixtures: test.fixtures ?? [],
|
|
132
187
|
credentialsPath: credentialsPath(cwd),
|
|
133
188
|
packageManager: test.packageManager ?? await detectPackageManager(configDir) ?? "npm"
|
|
@@ -156,11 +211,15 @@ async function resolveDevConfig(cwd) {
|
|
|
156
211
|
const dev = fileConfig?.dev ?? {};
|
|
157
212
|
return {
|
|
158
213
|
configDir,
|
|
159
|
-
project: stackProject(resolveStackName(configDir,
|
|
214
|
+
project: stackProject(resolveStackName(configDir, {
|
|
215
|
+
name: fileConfig?.name,
|
|
216
|
+
worktrees: fileConfig?.worktrees
|
|
217
|
+
}), "dev"),
|
|
160
218
|
port: dev.port ?? DEFAULT_DEV_PORT,
|
|
161
|
-
portExplicit: dev.port !== void 0,
|
|
219
|
+
portExplicit: dev.port !== void 0 && !fileConfig?.worktrees,
|
|
162
220
|
dbPort: dev.dbPort ?? DEFAULT_DEV_DB_PORT,
|
|
163
|
-
dbPortExplicit: dev.dbPort !== void 0,
|
|
221
|
+
dbPortExplicit: dev.dbPort !== void 0 && !fileConfig?.worktrees,
|
|
222
|
+
wordpressTag: dev.version ?? DEFAULT_WORDPRESS_TAG,
|
|
164
223
|
fixtures: dev.fixtures ?? [],
|
|
165
224
|
wordpressPath: LOCAL_DIR_REL,
|
|
166
225
|
wordpressDir: path.resolve(configDir, LOCAL_DIR_REL)
|
|
@@ -168,4 +227,4 @@ async function resolveDevConfig(cwd) {
|
|
|
168
227
|
}
|
|
169
228
|
|
|
170
229
|
//#endregion
|
|
171
|
-
export { CONFIG_FILES, DEFAULT_DEV_DB_PORT, DEFAULT_DEV_PORT, resolveConfig, resolveDevConfig, resolveStackName, resolveTestConfig, stackProject, usesLocalWordPress };
|
|
230
|
+
export { CONFIG_FILES, DEFAULT_DEV_DB_PORT, DEFAULT_DEV_PORT, resolveConfig, resolveDevConfig, resolveStackName, resolveTestConfig, resolveWordPressClientDir, stackProject, usesLocalWordPress };
|
|
@@ -1,39 +1,246 @@
|
|
|
1
1
|
import { generateContract } from "../../shared/contract.js";
|
|
2
|
+
import { resolveWordPressConnection } from "../../kizlo.js";
|
|
3
|
+
import { WORDPRESS_CLIENT_META_REL } from "../wp/constants.js";
|
|
4
|
+
import { log } from "./logger.js";
|
|
2
5
|
import { loadEnvFiles } from "../utils.js";
|
|
3
6
|
import { importIgnoringVirtualModules } from "./jiti.js";
|
|
7
|
+
import { IntrospectionVersionError } from "../../wordpress/introspection.js";
|
|
8
|
+
import { fetchIntrospection } from "../../wordpress/fetch-introspection.js";
|
|
9
|
+
import { generateWordPressModule } from "../../wordpress/generate.js";
|
|
10
|
+
import { GeneratedClientTypeError, assertGeneratedClientCompiles } from "../../wordpress/typecheck.js";
|
|
11
|
+
import { isPluginVersionSupported, pluginUpdateMessage } from "@kizlo/shared";
|
|
4
12
|
import path from "node:path";
|
|
5
13
|
import fs from "node:fs";
|
|
6
14
|
|
|
7
15
|
//#region src/cli/daemon/generate.ts
|
|
8
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* The generated barrel — also written as a stub by `kizlo init`. It re-exports `endpoints` rather
|
|
18
|
+
* than pre-wrapping it: a server entry passes `wordpress: { endpoints }` itself, so the rest of the
|
|
19
|
+
* WordPress options stay visible at the call site instead of hiding inside a generated object.
|
|
20
|
+
*/
|
|
9
21
|
const CONTRACT_BARREL = [
|
|
10
22
|
`import type { router } from ".."`,
|
|
11
23
|
`import contractJson from "./contract.json"`,
|
|
12
24
|
``,
|
|
13
25
|
`export const contract = contractJson as unknown as typeof router`,
|
|
26
|
+
`export { endpoints, type WordPressClient } from "./wordpress"`,
|
|
27
|
+
``
|
|
28
|
+
].join("\n");
|
|
29
|
+
/**
|
|
30
|
+
* `wordpress.ts` before the first generation. WordPress is reachable but untyped until then, so a
|
|
31
|
+
* freshly scaffolded project compiles — including kizlo's own procedures — before it has ever
|
|
32
|
+
* connected. `kizlo dev` / `kizlo generate` replaces this with the real client. `any` rather than an
|
|
33
|
+
* empty tree on purpose: every endpoint a procedure names has to resolve to something until the
|
|
34
|
+
* real shapes arrive, which `WP_Client` preserves by passing `any` through.
|
|
35
|
+
*/
|
|
36
|
+
const WORDPRESS_STUB = [
|
|
37
|
+
`import type { WP_Client } from "kizlo"`,
|
|
38
|
+
``,
|
|
39
|
+
`export const endpoints = {} as any`,
|
|
40
|
+
``,
|
|
41
|
+
`export type WordPressClient = WP_Client<typeof endpoints>`,
|
|
42
|
+
``,
|
|
43
|
+
`declare module "kizlo" {`,
|
|
44
|
+
`\tinterface WordPressClientRegistry {`,
|
|
45
|
+
`\t\tendpoints: typeof endpoints`,
|
|
46
|
+
`\t}`,
|
|
47
|
+
`}`,
|
|
14
48
|
``
|
|
15
49
|
].join("\n");
|
|
16
50
|
/** Write atomically: a reader never observes a half-written file. */
|
|
51
|
+
let atomicWriteSequence = 0;
|
|
17
52
|
function atomicWrite(file, data) {
|
|
18
|
-
const tmp = `${file}.${process.pid}.tmp`;
|
|
19
|
-
|
|
20
|
-
|
|
53
|
+
const tmp = `${file}.${process.pid}.${atomicWriteSequence++}.tmp`;
|
|
54
|
+
try {
|
|
55
|
+
fs.writeFileSync(tmp, data);
|
|
56
|
+
fs.renameSync(tmp, file);
|
|
57
|
+
} finally {
|
|
58
|
+
if (fs.existsSync(tmp)) fs.unlinkSync(tmp);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** Enough of a generated file to hold its header: `// Generated by Kizlo` then `// Introspection <hash>`. */
|
|
62
|
+
const INTROSPECTION_STAMP_BYTES = 256;
|
|
63
|
+
/** The introspection hash a generated file was written from, read without loading the whole file. */
|
|
64
|
+
function introspectionStamp(file) {
|
|
65
|
+
let handle;
|
|
66
|
+
try {
|
|
67
|
+
handle = fs.openSync(file, "r");
|
|
68
|
+
const buffer = Buffer.alloc(INTROSPECTION_STAMP_BYTES);
|
|
69
|
+
const bytes = fs.readSync(handle, buffer, 0, buffer.length, 0);
|
|
70
|
+
return /^\/\/ Introspection (\S+)$/m.exec(buffer.toString("utf8", 0, bytes))?.[1];
|
|
71
|
+
} catch {
|
|
72
|
+
return;
|
|
73
|
+
} finally {
|
|
74
|
+
if (handle !== void 0) fs.closeSync(handle);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The ETag to revalidate with, or undefined to fetch afresh. An ETag is only worth sending when the
|
|
79
|
+
* file it describes is still on disk and was written from the same introspection document it
|
|
80
|
+
* names: a stub, a hand-edit, or output from an older generator all have to be replaced, and
|
|
81
|
+
* answering 304 to any of them would leave the project compiling against something WordPress never served.
|
|
82
|
+
*/
|
|
83
|
+
function readWordPressMeta(metaPath, generatedFile) {
|
|
84
|
+
try {
|
|
85
|
+
const value = JSON.parse(fs.readFileSync(metaPath, "utf8"));
|
|
86
|
+
if (typeof value.etag !== "string" || typeof value.hash !== "string") return void 0;
|
|
87
|
+
if (introspectionStamp(generatedFile) !== value.hash) return void 0;
|
|
88
|
+
return value;
|
|
89
|
+
} catch {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/** The fetch cache a generated file revalidates against, keyed to the document it was written from. */
|
|
94
|
+
function wordPressMeta(etag, hash) {
|
|
95
|
+
return `${JSON.stringify({
|
|
96
|
+
etag,
|
|
97
|
+
hash
|
|
98
|
+
}, null, " ")}\n`;
|
|
99
|
+
}
|
|
100
|
+
/** Where a diagnostic landed: `schema_id > api_id > path > operation > pointer > keyword`, as sent. */
|
|
101
|
+
function diagnosticLocation(diagnostic) {
|
|
102
|
+
return Object.values(diagnostic.data).filter(Boolean).join(" > ");
|
|
103
|
+
}
|
|
104
|
+
function formatDiagnostic(diagnostic) {
|
|
105
|
+
const location = diagnosticLocation(diagnostic);
|
|
106
|
+
return `${diagnostic.type}: ${location ? `${location}: ` : ""}${diagnostic.message}`;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* WordPress publishes everything that survived validation and names what it had to drop, so what a
|
|
110
|
+
* partial contract is worth is the generator's call rather than the transport's. Taking it is the
|
|
111
|
+
* default: a third-party plugin's broken declaration then costs its own routes and nothing else,
|
|
112
|
+
* where refusing the document costs the project every route that still works, for a mistake in code
|
|
113
|
+
* it does not own. Strict generation makes the opposite trade for CI, where a client silently short
|
|
114
|
+
* of routes typechecks clean and ships the gap.
|
|
115
|
+
*/
|
|
116
|
+
var PartialContractError = class extends Error {
|
|
117
|
+
excluded;
|
|
118
|
+
constructor(excluded) {
|
|
119
|
+
super(`WordPress excluded ${excluded} ${excluded === 1 ? "contribution" : "contributions"} from the contract.`);
|
|
120
|
+
this.name = "PartialContractError";
|
|
121
|
+
this.excluded = excluded;
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
/**
|
|
125
|
+
* Report a failed generation under `message`. A version mismatch and a refused partial contract are
|
|
126
|
+
* diagnoses rather than faults: the message is the whole report, and the stack consola would print
|
|
127
|
+
* beneath it buries the one line the user has to act on. Anything else is a fault and keeps its stack.
|
|
128
|
+
*/
|
|
129
|
+
function reportGenerationError(message, error) {
|
|
130
|
+
if (error instanceof IntrospectionVersionError || error instanceof PartialContractError) log.error(error.message);
|
|
131
|
+
else if (error instanceof GeneratedClientTypeError) log.error(error.message);
|
|
132
|
+
else log.error(message, error);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Say what the document says about itself, then apply the policy. Warnings mean a declaration lost a
|
|
136
|
+
* constraint and still types correctly, so they never gate anything; errors mean a contribution was
|
|
137
|
+
* dropped and its routes and types are absent from what follows.
|
|
138
|
+
*/
|
|
139
|
+
function reportDiagnostics(document, strict) {
|
|
140
|
+
for (const diagnostic of document.diagnostics) if (diagnostic.type === "warning") log.warn(formatDiagnostic(diagnostic));
|
|
141
|
+
else log.error(formatDiagnostic(diagnostic));
|
|
142
|
+
const excluded = new Set(document.diagnostics.filter((diagnostic) => diagnostic.type === "error").map(diagnosticLocation));
|
|
143
|
+
if (!excluded.size) return;
|
|
144
|
+
if (strict) throw new PartialContractError(excluded.size);
|
|
145
|
+
log.warn(`Generating without the ${excluded.size} excluded ${excluded.size === 1 ? "contribution" : "contributions"} above. Their routes and types are missing from the client; \`kizlo generate --strict\` refuses the contract instead.`);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Plugin versions already named as outdated. `kizlo dev` refetches every few seconds, and the answer
|
|
149
|
+
* cannot change until the plugin does, so the warning is worth one line per version rather than one
|
|
150
|
+
* per poll. An updated plugin that is still too old is a version this has not seen, and says so again.
|
|
151
|
+
*/
|
|
152
|
+
const reportedPluginVersions = /* @__PURE__ */ new Set();
|
|
153
|
+
/**
|
|
154
|
+
* Name a plugin too old for this package at generation, where it is still actionable, rather than
|
|
155
|
+
* leaving it to the runtime's first request. A warning either way, strict included: strict decides
|
|
156
|
+
* what to do about routes WordPress excluded, and an outdated plugin excludes nothing by itself.
|
|
157
|
+
*/
|
|
158
|
+
function reportPluginVersion(installed) {
|
|
159
|
+
if (isPluginVersionSupported(installed)) return;
|
|
160
|
+
const key = installed ?? "";
|
|
161
|
+
if (reportedPluginVersions.has(key)) return;
|
|
162
|
+
reportedPluginVersions.add(key);
|
|
163
|
+
log.warn(pluginUpdateMessage(installed));
|
|
164
|
+
}
|
|
165
|
+
/** Fetch `/introspect`, reporting its diagnostics in the CLI's voice. Returns undefined on a 304. */
|
|
166
|
+
async function fetchDocument(cwd, options, etag) {
|
|
167
|
+
loadEnvFiles(cwd);
|
|
168
|
+
const result = await fetchIntrospection(options.credentials ?? resolveWordPressConnection(void 0).credentials, {
|
|
169
|
+
etag: options.strict ? void 0 : etag,
|
|
170
|
+
fetch: options.fetch
|
|
171
|
+
});
|
|
172
|
+
reportPluginVersion(result.pluginVersion);
|
|
173
|
+
if (result.status === "not-modified") return void 0;
|
|
174
|
+
if (!result.document) throw new Error("WordPress introspection returned no document.");
|
|
175
|
+
reportDiagnostics(result.document, options.strict === true);
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Produce the WordPress client without writing it or its ETag cache. Health checks use this to ask
|
|
180
|
+
* the same generator as `kizlo generate` what should be on disk, while leaving a contributor's
|
|
181
|
+
* checkout untouched. Callers choose strictness just as generation does.
|
|
182
|
+
*/
|
|
183
|
+
async function generateWordPressSource(cwd, options = {}) {
|
|
184
|
+
const result = await fetchDocument(cwd, options);
|
|
185
|
+
if (!result?.document) throw new Error("WordPress introspection returned no document.");
|
|
186
|
+
const generated = generateWordPressModule(result.document);
|
|
187
|
+
await assertGeneratedClientCompiles(generated);
|
|
188
|
+
return generated.source;
|
|
189
|
+
}
|
|
190
|
+
async function generateWordPressOnce(cfg, options = {}) {
|
|
191
|
+
const etag = readWordPressMeta(path.resolve(cfg.cwd, cfg.wordpressMetaPath), path.resolve(cfg.cwd, cfg.wordpressPath))?.etag;
|
|
192
|
+
const result = await fetchDocument(cfg.cwd, options, etag);
|
|
193
|
+
if (!result?.document) return "unchanged";
|
|
194
|
+
const generated = generateWordPressModule(result.document);
|
|
195
|
+
await assertGeneratedClientCompiles(generated);
|
|
196
|
+
writeGeneratedWordPress(cfg, generated.source, wordPressMeta(result.etag, result.document.hash));
|
|
197
|
+
return "generated";
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Write `<dir>/wordpress.ts` once for a whole workspace. Every package that ships procedures compiles
|
|
201
|
+
* against the same file, so there is one description of WordPress in the repo rather than one per
|
|
202
|
+
* package drifting apart. One shape too: a package that only wants the types today still gets the
|
|
203
|
+
* class, so reaching for the runtime later needs no regeneration.
|
|
204
|
+
*/
|
|
205
|
+
async function generateWorkspaceClientOnce(cwd, dir, options = {}) {
|
|
206
|
+
const file = path.resolve(cwd, dir, "wordpress.ts");
|
|
207
|
+
const metaPath = path.resolve(cwd, WORDPRESS_CLIENT_META_REL);
|
|
208
|
+
const result = await fetchDocument(cwd, options, readWordPressMeta(metaPath, file)?.etag);
|
|
209
|
+
if (!result?.document) return "unchanged";
|
|
210
|
+
const generated = generateWordPressModule(result.document);
|
|
211
|
+
await assertGeneratedClientCompiles(generated);
|
|
212
|
+
const contents = generated.source;
|
|
213
|
+
fs.mkdirSync(path.dirname(metaPath), { recursive: true });
|
|
214
|
+
atomicWrite(metaPath, wordPressMeta(result.etag, result.document.hash));
|
|
215
|
+
if (fs.existsSync(file) && fs.readFileSync(file, "utf8") === contents) return "unchanged";
|
|
216
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
217
|
+
atomicWrite(file, contents);
|
|
218
|
+
return "generated";
|
|
219
|
+
}
|
|
220
|
+
function writeGeneratedWordPress(cfg, client, meta) {
|
|
221
|
+
const metaPath = path.resolve(cfg.cwd, cfg.wordpressMetaPath);
|
|
222
|
+
fs.mkdirSync(path.resolve(cfg.cwd, cfg.generatedDir), { recursive: true });
|
|
223
|
+
fs.mkdirSync(path.dirname(metaPath), { recursive: true });
|
|
224
|
+
atomicWrite(path.resolve(cfg.cwd, cfg.wordpressPath), client);
|
|
225
|
+
atomicWrite(metaPath, meta);
|
|
21
226
|
}
|
|
22
227
|
/**
|
|
23
228
|
* Loads the Kizlo server, builds its contract, and writes `contract.json` and
|
|
24
229
|
* the generated barrel. Returns false when the entry has no Kizlo server.
|
|
25
230
|
*/
|
|
26
|
-
async function generateOnce(cfg) {
|
|
231
|
+
async function generateOnce(cfg, options = {}) {
|
|
27
232
|
loadEnvFiles(cfg.cwd);
|
|
28
233
|
const entry = path.resolve(cfg.cwd, cfg.serverEntry);
|
|
29
234
|
const { router } = await importIgnoringVirtualModules(cfg.cwd, entry);
|
|
30
235
|
if (!router) return false;
|
|
31
236
|
const contract = JSON.stringify(await generateContract(router));
|
|
237
|
+
const wordpress = await generateWordPressOnce(cfg, options);
|
|
32
238
|
fs.mkdirSync(path.resolve(cfg.cwd, cfg.generatedDir), { recursive: true });
|
|
33
239
|
atomicWrite(path.resolve(cfg.cwd, cfg.contractPath), contract);
|
|
34
240
|
atomicWrite(path.resolve(cfg.cwd, cfg.barrelPath), CONTRACT_BARREL);
|
|
241
|
+
if (wordpress === "generated") log.success("WordPress client generated");
|
|
35
242
|
return true;
|
|
36
243
|
}
|
|
37
244
|
|
|
38
245
|
//#endregion
|
|
39
|
-
export { CONTRACT_BARREL, generateOnce };
|
|
246
|
+
export { CONTRACT_BARREL, PartialContractError, WORDPRESS_STUB, generateOnce, generateWordPressOnce, generateWordPressSource, generateWorkspaceClientOnce, reportGenerationError };
|
package/dist/cli/daemon/lock.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { currentProcessOwner, ownerAlive } from "../process-owner.js";
|
|
1
2
|
import path from "node:path";
|
|
2
3
|
import fs from "node:fs";
|
|
3
4
|
|
|
@@ -5,21 +6,42 @@ import fs from "node:fs";
|
|
|
5
6
|
function lockPath(cwd) {
|
|
6
7
|
return path.join(cwd, "node_modules/.cache/kizlo/dev.lock");
|
|
7
8
|
}
|
|
8
|
-
/** True when a live daemon
|
|
9
|
-
function isLocked(file) {
|
|
9
|
+
/** True when a live daemon still owns the process identity recorded by the lock. */
|
|
10
|
+
async function isLocked(file) {
|
|
10
11
|
if (!fs.existsSync(file)) return false;
|
|
11
|
-
const
|
|
12
|
-
|
|
12
|
+
const contents = fs.readFileSync(file, "utf8").trim();
|
|
13
|
+
let owner;
|
|
13
14
|
try {
|
|
14
|
-
|
|
15
|
-
return true;
|
|
15
|
+
owner = contents.startsWith("{") ? JSON.parse(contents) : { pid: Number(contents) };
|
|
16
16
|
} catch {
|
|
17
17
|
return false;
|
|
18
18
|
}
|
|
19
|
+
return ownerAlive(owner);
|
|
20
|
+
}
|
|
21
|
+
/** Claim the file for this process, or report that it was already there. */
|
|
22
|
+
function claim(file, owner) {
|
|
23
|
+
try {
|
|
24
|
+
fs.writeFileSync(file, JSON.stringify(owner), { flag: "wx" });
|
|
25
|
+
return true;
|
|
26
|
+
} catch (error) {
|
|
27
|
+
if (error.code !== "EEXIST") throw error;
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
19
30
|
}
|
|
20
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Take the lock for this process, answering false when a live daemon already holds it. The file is
|
|
33
|
+
* created with `wx`, so two `kizlo dev` starting together cannot both come away believing they won.
|
|
34
|
+
* A lock whose owner has died or whose PID belongs to another process is removed and taken over.
|
|
35
|
+
* Losing the second claim means another process reached the same stale lock first, so this one
|
|
36
|
+
* stands down rather than trying again.
|
|
37
|
+
*/
|
|
38
|
+
async function acquire(file) {
|
|
21
39
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
22
|
-
|
|
40
|
+
const owner = await currentProcessOwner();
|
|
41
|
+
if (claim(file, owner)) return true;
|
|
42
|
+
if (await isLocked(file)) return false;
|
|
43
|
+
fs.rmSync(file, { force: true });
|
|
44
|
+
return claim(file, owner);
|
|
23
45
|
}
|
|
24
46
|
function release(file) {
|
|
25
47
|
try {
|
|
@@ -28,4 +50,4 @@ function release(file) {
|
|
|
28
50
|
}
|
|
29
51
|
|
|
30
52
|
//#endregion
|
|
31
|
-
export { acquire,
|
|
53
|
+
export { acquire, lockPath, release };
|
package/dist/cli/daemon/watch.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { resolveWordPressConnection } from "../../kizlo.js";
|
|
1
2
|
import { log } from "./logger.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { loadEnvFiles } from "../utils.js";
|
|
4
|
+
import { resolveConfig, resolveWordPressClientDir } from "./config.js";
|
|
5
|
+
import { IntrospectionFetchError } from "../../wordpress/fetch-introspection.js";
|
|
6
|
+
import { generateOnce, generateWordPressOnce, generateWorkspaceClientOnce, reportGenerationError } from "./generate.js";
|
|
7
|
+
import { acquire, lockPath, release } from "./lock.js";
|
|
5
8
|
import path from "node:path";
|
|
6
9
|
import { FSWatcher } from "chokidar";
|
|
7
10
|
|
|
@@ -13,27 +16,117 @@ function debounce(fn, delay) {
|
|
|
13
16
|
timer = setTimeout(() => void fn(...args), delay);
|
|
14
17
|
});
|
|
15
18
|
}
|
|
16
|
-
async function regenerate(cfg) {
|
|
19
|
+
async function regenerate(cfg, credentials) {
|
|
17
20
|
try {
|
|
18
|
-
if (await generateOnce(cfg)) log.success("Contract updated");
|
|
21
|
+
if (await generateOnce(cfg, { credentials })) log.success("Contract updated");
|
|
19
22
|
else log.warn(`No Kizlo server found in ${cfg.serverEntry}`);
|
|
20
23
|
} catch (error) {
|
|
21
|
-
|
|
24
|
+
reportGenerationError("Failed to update the Kizlo contract:", error);
|
|
22
25
|
}
|
|
23
26
|
}
|
|
24
27
|
/** Watches the server directory and regenerates the contract on change. */
|
|
25
|
-
async function watch(cfg) {
|
|
28
|
+
async function watch(cfg, credentials) {
|
|
26
29
|
const watcher = new FSWatcher({
|
|
27
30
|
persistent: true,
|
|
28
31
|
ignoreInitial: true,
|
|
29
32
|
ignored: path.resolve(cfg.cwd, cfg.generatedDir)
|
|
30
33
|
});
|
|
31
|
-
const onChange = debounce(() => regenerate(cfg), 300);
|
|
34
|
+
const onChange = debounce(() => regenerate(cfg, credentials), 300);
|
|
32
35
|
watcher.add(path.resolve(cfg.cwd, cfg.serverDir));
|
|
33
36
|
watcher.on("all", () => void onChange());
|
|
34
37
|
return watcher;
|
|
35
38
|
}
|
|
36
39
|
/**
|
|
40
|
+
* Report one generation's pass under its own name. The poll refreshes two, and one message for both sent
|
|
41
|
+
* a client-only workspace looking for a WordPress service its project does not have.
|
|
42
|
+
*
|
|
43
|
+
* Each reporter carries the last failure it reported, because most of what can fail here cannot clear
|
|
44
|
+
* without the user acting — WordPress down, credentials wrong, a document that will not parse, an
|
|
45
|
+
* introspection version this package does not speak — and repeating the same line every few seconds says
|
|
46
|
+
* nothing the first one did not. A failure that reads differently is a different answer and is reported.
|
|
47
|
+
* The state is per reporter rather than per process, so restarting the watcher reports afresh, and one
|
|
48
|
+
* generation failing never silences the other.
|
|
49
|
+
*/
|
|
50
|
+
function reportGeneration(subject, stack) {
|
|
51
|
+
let reported;
|
|
52
|
+
return async (run) => {
|
|
53
|
+
try {
|
|
54
|
+
if (await run() === "generated") log.success(`WordPress ${subject} updated`);
|
|
55
|
+
stack?.answered();
|
|
56
|
+
if (reported !== void 0) {
|
|
57
|
+
reported = void 0;
|
|
58
|
+
log.success(`Updating the WordPress ${subject} again`);
|
|
59
|
+
}
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if (error instanceof IntrospectionFetchError && error.unreachable && await stack?.ended()) return;
|
|
62
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
63
|
+
if (message === reported) return;
|
|
64
|
+
reported = message;
|
|
65
|
+
reportGenerationError(`Failed to update the WordPress ${subject}:`, error);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* How often the guard may ask Docker while the poll keeps failing. The poll retries every 3 seconds,
|
|
71
|
+
* and asking on every failing pass would spawn a subprocess that often for as long as WordPress is
|
|
72
|
+
* unreachable — which, when the stack is up and WordPress is merely broken, is indefinitely. A stack
|
|
73
|
+
* does not stop and restart between two passes, so the slower clock costs at most one interval before
|
|
74
|
+
* the session ends and saves the other four probes.
|
|
75
|
+
*/
|
|
76
|
+
const STACK_PROBE_INTERVAL_MS = 15e3;
|
|
77
|
+
function endOnStoppedStack(stack, now = Date.now) {
|
|
78
|
+
let done = false;
|
|
79
|
+
let probedAt;
|
|
80
|
+
return {
|
|
81
|
+
answered: () => {
|
|
82
|
+
probedAt = void 0;
|
|
83
|
+
},
|
|
84
|
+
ended: async () => {
|
|
85
|
+
if (done) return true;
|
|
86
|
+
const at = now();
|
|
87
|
+
if (probedAt !== void 0 && at - probedAt < STACK_PROBE_INTERVAL_MS) return false;
|
|
88
|
+
probedAt = at;
|
|
89
|
+
if (await stack.status() !== "stopped") return false;
|
|
90
|
+
done = true;
|
|
91
|
+
log.error("Local WordPress is no longer running. Ending the dev session.");
|
|
92
|
+
stack.onStopped();
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* One pass of the WordPress poll. `cfg` covers an app's client next to its contract; `wordpressClientDir`
|
|
99
|
+
* covers a workspace that has only the client. A project has one or the other, but both are refreshed
|
|
100
|
+
* together so the plugin's PHP changing is picked up either way. Each is refreshed independently, so a
|
|
101
|
+
* service that cannot generate does not cost the pass its client.
|
|
102
|
+
*
|
|
103
|
+
* Both reporters share one stack guard, so a stopped stack ends the session once rather than once per
|
|
104
|
+
* generation the pass refreshes.
|
|
105
|
+
*/
|
|
106
|
+
function createWordPressRefresh(cwd, cfg, wordpressClientDir, options, stack) {
|
|
107
|
+
const guard = stack ? endOnStoppedStack(stack) : void 0;
|
|
108
|
+
const service = reportGeneration("service", guard);
|
|
109
|
+
const client = reportGeneration("client", guard);
|
|
110
|
+
return async () => {
|
|
111
|
+
if (cfg) await service(() => generateWordPressOnce(cfg, options));
|
|
112
|
+
if (wordpressClientDir) await client(() => generateWorkspaceClientOnce(cwd, wordpressClientDir, options));
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/** Run {@link createWordPressRefresh} on a timer, skipping a tick while the previous one is still going. */
|
|
116
|
+
function refreshWordPress(cwd, cfg, wordpressClientDir, credentials, stack) {
|
|
117
|
+
const refresh = createWordPressRefresh(cwd, cfg, wordpressClientDir, { credentials }, stack);
|
|
118
|
+
let refreshing = false;
|
|
119
|
+
const timer = setInterval(() => {
|
|
120
|
+
if (refreshing) return;
|
|
121
|
+
refreshing = true;
|
|
122
|
+
refresh().finally(() => {
|
|
123
|
+
refreshing = false;
|
|
124
|
+
});
|
|
125
|
+
}, 3e3);
|
|
126
|
+
timer.unref();
|
|
127
|
+
return timer;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
37
130
|
* Acquire the single-instance lock, generate the contract once, and start the file
|
|
38
131
|
* watcher. Returns a synchronous `stop()` that closes the watcher and releases the
|
|
39
132
|
* lock — or `undefined` when another watcher already holds the lock (a framework dev
|
|
@@ -43,30 +136,45 @@ async function watch(cfg) {
|
|
|
43
136
|
*/
|
|
44
137
|
async function startWatcher(cwd, opts) {
|
|
45
138
|
const lock = lockPath(cwd);
|
|
46
|
-
if (
|
|
139
|
+
if (!await acquire(lock)) {
|
|
47
140
|
log.info("Watcher already running — skipping the contract watcher.");
|
|
48
141
|
return;
|
|
49
142
|
}
|
|
50
|
-
|
|
51
|
-
if (!cfg) {
|
|
52
|
-
log.info("skipping the contract generation.");
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
acquire(lock);
|
|
143
|
+
let handedOver = false;
|
|
56
144
|
try {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
145
|
+
const cfg = await resolveConfig(cwd, { dir: opts?.dir });
|
|
146
|
+
const wordpressClientDir = await resolveWordPressClientDir(cwd);
|
|
147
|
+
if (!cfg && !wordpressClientDir) {
|
|
148
|
+
log.info("skipping the contract generation.");
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
loadEnvFiles(cwd);
|
|
152
|
+
const { credentials } = resolveWordPressConnection(void 0);
|
|
153
|
+
if (wordpressClientDir) try {
|
|
154
|
+
if (await generateWorkspaceClientOnce(cwd, wordpressClientDir, { credentials }) === "generated") log.success("WordPress client generated");
|
|
155
|
+
} catch (error) {
|
|
156
|
+
reportGenerationError("Failed to generate the WordPress client:", error);
|
|
157
|
+
}
|
|
158
|
+
if (cfg) try {
|
|
159
|
+
if (await generateOnce(cfg, { credentials })) log.success("Contract generated");
|
|
160
|
+
else log.warn(`No Kizlo server found in ${cfg.serverEntry}`);
|
|
161
|
+
} catch (error) {
|
|
162
|
+
reportGenerationError("Failed to generate the Kizlo contract:", error);
|
|
163
|
+
}
|
|
164
|
+
const watcher = cfg ? await watch(cfg, credentials) : void 0;
|
|
165
|
+
const wordpressRefresh = refreshWordPress(cwd, cfg, wordpressClientDir, credentials, opts?.stack);
|
|
166
|
+
let stopped = false;
|
|
167
|
+
handedOver = true;
|
|
168
|
+
return () => {
|
|
169
|
+
if (stopped) return;
|
|
170
|
+
stopped = true;
|
|
171
|
+
release(lock);
|
|
172
|
+
clearInterval(wordpressRefresh);
|
|
173
|
+
watcher?.close();
|
|
174
|
+
};
|
|
175
|
+
} finally {
|
|
176
|
+
if (!handedOver) release(lock);
|
|
61
177
|
}
|
|
62
|
-
const watcher = await watch(cfg);
|
|
63
|
-
let stopped = false;
|
|
64
|
-
return () => {
|
|
65
|
-
if (stopped) return;
|
|
66
|
-
stopped = true;
|
|
67
|
-
release(lock);
|
|
68
|
-
watcher.close();
|
|
69
|
-
};
|
|
70
178
|
}
|
|
71
179
|
|
|
72
180
|
//#endregion
|