@wp-typia/project-tools 0.22.10 → 0.23.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/runtime/cli-add-collision.d.ts +25 -0
- package/dist/runtime/cli-add-collision.js +76 -0
- package/dist/runtime/cli-add-help.js +11 -2
- package/dist/runtime/cli-add-kind-ids.d.ts +1 -1
- package/dist/runtime/cli-add-kind-ids.js +3 -0
- package/dist/runtime/cli-add-types.d.ts +117 -0
- package/dist/runtime/cli-add-types.js +26 -0
- package/dist/runtime/cli-add-validation.d.ts +90 -1
- package/dist/runtime/cli-add-validation.js +304 -1
- package/dist/runtime/cli-add-workspace-admin-view-scaffold.js +74 -19
- package/dist/runtime/cli-add-workspace-admin-view-source.js +11 -2
- package/dist/runtime/cli-add-workspace-admin-view-templates.d.ts +20 -2
- package/dist/runtime/cli-add-workspace-admin-view-templates.js +359 -3
- package/dist/runtime/cli-add-workspace-admin-view-types.d.ts +21 -0
- package/dist/runtime/cli-add-workspace-admin-view-types.js +22 -0
- package/dist/runtime/cli-add-workspace-ai-anchors.js +121 -31
- package/dist/runtime/cli-add-workspace-contract-source-emitters.d.ts +15 -0
- package/dist/runtime/cli-add-workspace-contract-source-emitters.js +42 -0
- package/dist/runtime/cli-add-workspace-contract.d.ts +15 -0
- package/dist/runtime/cli-add-workspace-contract.js +65 -0
- package/dist/runtime/cli-add-workspace-integration-env.d.ts +24 -0
- package/dist/runtime/cli-add-workspace-integration-env.js +391 -0
- package/dist/runtime/cli-add-workspace-post-meta-anchors.d.ts +23 -0
- package/dist/runtime/cli-add-workspace-post-meta-anchors.js +244 -0
- package/dist/runtime/cli-add-workspace-post-meta-source-emitters.d.ts +63 -0
- package/dist/runtime/cli-add-workspace-post-meta-source-emitters.js +179 -0
- package/dist/runtime/cli-add-workspace-post-meta.d.ts +15 -0
- package/dist/runtime/cli-add-workspace-post-meta.js +107 -0
- package/dist/runtime/cli-add-workspace-rest-anchors.d.ts +1 -0
- package/dist/runtime/cli-add-workspace-rest-anchors.js +285 -21
- package/dist/runtime/cli-add-workspace-rest-source-emitters.d.ts +90 -2
- package/dist/runtime/cli-add-workspace-rest-source-emitters.js +302 -29
- package/dist/runtime/cli-add-workspace-rest.d.ts +15 -2
- package/dist/runtime/cli-add-workspace-rest.js +329 -21
- package/dist/runtime/cli-add-workspace.d.ts +15 -0
- package/dist/runtime/cli-add-workspace.js +15 -0
- package/dist/runtime/cli-add.d.ts +1 -1
- package/dist/runtime/cli-add.js +1 -1
- package/dist/runtime/cli-core.d.ts +2 -1
- package/dist/runtime/cli-core.js +2 -1
- package/dist/runtime/cli-doctor-environment.js +1 -3
- package/dist/runtime/cli-doctor-workspace-features.js +128 -10
- package/dist/runtime/cli-doctor-workspace-package.d.ts +25 -3
- package/dist/runtime/cli-doctor-workspace-package.js +35 -13
- package/dist/runtime/cli-doctor-workspace-shared.d.ts +2 -0
- package/dist/runtime/cli-doctor-workspace-shared.js +2 -0
- package/dist/runtime/cli-doctor-workspace.js +8 -3
- package/dist/runtime/cli-help.js +7 -0
- package/dist/runtime/cli-init-templates.js +11 -1
- package/dist/runtime/contract-artifacts.d.ts +14 -0
- package/dist/runtime/contract-artifacts.js +15 -0
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.js +1 -1
- package/dist/runtime/rest-resource-artifacts.d.ts +57 -1
- package/dist/runtime/rest-resource-artifacts.js +97 -1
- package/dist/runtime/template-render.d.ts +1 -1
- package/dist/runtime/template-render.js +1 -1
- package/dist/runtime/template-source-cache-markers.d.ts +37 -0
- package/dist/runtime/template-source-cache-markers.js +125 -0
- package/dist/runtime/template-source-cache.d.ts +1 -4
- package/dist/runtime/template-source-cache.js +16 -122
- package/dist/runtime/template-source-external.d.ts +4 -2
- package/dist/runtime/template-source-external.js +4 -2
- package/dist/runtime/template-source-remote.d.ts +8 -4
- package/dist/runtime/template-source-remote.js +8 -4
- package/dist/runtime/workspace-inventory-mutations.js +52 -3
- package/dist/runtime/workspace-inventory-parser.d.ts +3 -2
- package/dist/runtime/workspace-inventory-parser.js +126 -5
- package/dist/runtime/workspace-inventory-read.d.ts +9 -2
- package/dist/runtime/workspace-inventory-read.js +9 -2
- package/dist/runtime/workspace-inventory-templates.d.ts +16 -1
- package/dist/runtime/workspace-inventory-templates.js +74 -4
- package/dist/runtime/workspace-inventory-types.d.ts +51 -2
- package/dist/runtime/workspace-inventory.d.ts +2 -2
- package/dist/runtime/workspace-inventory.js +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { quoteTsString, } from "./cli-add-shared.js";
|
|
2
|
+
import { toTitleCase } from "./string-case.js";
|
|
3
|
+
/**
|
|
4
|
+
* Render one `CONTRACTS` inventory entry for `scripts/block-config.ts`.
|
|
5
|
+
*
|
|
6
|
+
* @param contractSlug Stable kebab-case contract id.
|
|
7
|
+
* @param sourceTypeName Exported TypeScript type/interface used for schema generation.
|
|
8
|
+
*/
|
|
9
|
+
export function buildContractConfigEntry(contractSlug, sourceTypeName) {
|
|
10
|
+
return [
|
|
11
|
+
"\t{",
|
|
12
|
+
`\t\tschemaFile: ${quoteTsString(`src/contracts/${contractSlug}.schema.json`)},`,
|
|
13
|
+
`\t\tslug: ${quoteTsString(contractSlug)},`,
|
|
14
|
+
`\t\tsourceTypeName: ${quoteTsString(sourceTypeName)},`,
|
|
15
|
+
`\t\ttypesFile: ${quoteTsString(`src/contracts/${contractSlug}.ts`)},`,
|
|
16
|
+
"\t},",
|
|
17
|
+
].join("\n");
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Render a small starter TypeScript contract that users can replace with their
|
|
21
|
+
* real external route, smoke-test, or PHP integration payload shape.
|
|
22
|
+
*
|
|
23
|
+
* @param contractSlug Stable kebab-case contract id.
|
|
24
|
+
* @param sourceTypeName Exported TypeScript type/interface used for schema generation.
|
|
25
|
+
*/
|
|
26
|
+
export function buildContractTypesSource(contractSlug, sourceTypeName) {
|
|
27
|
+
const title = toTitleCase(contractSlug);
|
|
28
|
+
return `/**
|
|
29
|
+
* ${title} is a standalone wire contract.
|
|
30
|
+
*
|
|
31
|
+
* It does not register a WordPress REST route. Edit this type, then run
|
|
32
|
+
* \`wp-typia sync-rest\` or \`wp-typia sync\` to refresh the JSON Schema
|
|
33
|
+
* artifact referenced from scripts/block-config.ts.
|
|
34
|
+
*/
|
|
35
|
+
export interface ${sourceTypeName} {
|
|
36
|
+
\tid: string;
|
|
37
|
+
\tstatus: 'pending' | 'ready';
|
|
38
|
+
\tupdatedAt: string;
|
|
39
|
+
\tmessage?: string;
|
|
40
|
+
}
|
|
41
|
+
`;
|
|
42
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type RunAddContractCommandOptions } from "./cli-add-shared.js";
|
|
2
|
+
/**
|
|
3
|
+
* Scaffold a standalone TypeScript wire contract and synchronize its JSON
|
|
4
|
+
* Schema artifact without generating PHP route glue.
|
|
5
|
+
*
|
|
6
|
+
* @param options Command options for the standalone contract workflow.
|
|
7
|
+
* @returns Resolved scaffold metadata for the created contract.
|
|
8
|
+
*/
|
|
9
|
+
export declare function runAddContractCommand({ contractName, cwd, typeName, }: RunAddContractCommandOptions): Promise<{
|
|
10
|
+
contractSlug: string;
|
|
11
|
+
projectDir: string;
|
|
12
|
+
schemaFile: string;
|
|
13
|
+
sourceTypeName: string;
|
|
14
|
+
typesFile: string;
|
|
15
|
+
}>;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { promises as fsp } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { pathExists } from "./fs-async.js";
|
|
4
|
+
import { assertContractDoesNotExist, assertValidGeneratedSlug, assertValidTypeScriptIdentifier, normalizeBlockSlug, } from "./cli-add-shared.js";
|
|
5
|
+
import { executeWorkspaceMutationPlan } from "./cli-add-workspace-mutation.js";
|
|
6
|
+
import { ensureContractSyncScriptAnchors } from "./cli-add-workspace-rest-anchors.js";
|
|
7
|
+
import { buildContractConfigEntry, buildContractTypesSource, } from "./cli-add-workspace-contract-source-emitters.js";
|
|
8
|
+
import { syncStandaloneContractArtifacts } from "./contract-artifacts.js";
|
|
9
|
+
import { toPascalCase } from "./string-case.js";
|
|
10
|
+
import { appendWorkspaceInventoryEntries, readWorkspaceInventoryAsync, } from "./workspace-inventory.js";
|
|
11
|
+
import { resolveWorkspaceProject } from "./workspace-project.js";
|
|
12
|
+
const ADD_CONTRACT_USAGE = "wp-typia add contract <name> [--type <ExportedTypeName>]";
|
|
13
|
+
/**
|
|
14
|
+
* Scaffold a standalone TypeScript wire contract and synchronize its JSON
|
|
15
|
+
* Schema artifact without generating PHP route glue.
|
|
16
|
+
*
|
|
17
|
+
* @param options Command options for the standalone contract workflow.
|
|
18
|
+
* @returns Resolved scaffold metadata for the created contract.
|
|
19
|
+
*/
|
|
20
|
+
export async function runAddContractCommand({ contractName, cwd = process.cwd(), typeName, }) {
|
|
21
|
+
const workspace = resolveWorkspaceProject(cwd);
|
|
22
|
+
const contractSlug = assertValidGeneratedSlug("Contract name", normalizeBlockSlug(contractName), ADD_CONTRACT_USAGE);
|
|
23
|
+
const sourceTypeName = assertValidTypeScriptIdentifier("Contract type", typeName ?? toPascalCase(contractSlug), ADD_CONTRACT_USAGE);
|
|
24
|
+
const inventory = await readWorkspaceInventoryAsync(workspace.projectDir);
|
|
25
|
+
assertContractDoesNotExist(workspace.projectDir, contractSlug, inventory);
|
|
26
|
+
const blockConfigPath = path.join(workspace.projectDir, "scripts", "block-config.ts");
|
|
27
|
+
const syncRestScriptPath = path.join(workspace.projectDir, "scripts", "sync-rest-contracts.ts");
|
|
28
|
+
const contractDir = path.join(workspace.projectDir, "src", "contracts");
|
|
29
|
+
const typesFile = `src/contracts/${contractSlug}.ts`;
|
|
30
|
+
const schemaFile = `src/contracts/${contractSlug}.schema.json`;
|
|
31
|
+
const typesFilePath = path.join(workspace.projectDir, typesFile);
|
|
32
|
+
const schemaFilePath = path.join(workspace.projectDir, schemaFile);
|
|
33
|
+
const contractDirExisted = await pathExists(contractDir);
|
|
34
|
+
return executeWorkspaceMutationPlan({
|
|
35
|
+
filePaths: [blockConfigPath, syncRestScriptPath],
|
|
36
|
+
targetPaths: [
|
|
37
|
+
typesFilePath,
|
|
38
|
+
schemaFilePath,
|
|
39
|
+
...(contractDirExisted ? [] : [contractDir]),
|
|
40
|
+
],
|
|
41
|
+
run: async () => {
|
|
42
|
+
await fsp.mkdir(contractDir, { recursive: true });
|
|
43
|
+
await ensureContractSyncScriptAnchors(workspace);
|
|
44
|
+
await fsp.writeFile(typesFilePath, buildContractTypesSource(contractSlug, sourceTypeName), "utf8");
|
|
45
|
+
await syncStandaloneContractArtifacts({
|
|
46
|
+
projectDir: workspace.projectDir,
|
|
47
|
+
schemaFile,
|
|
48
|
+
sourceTypeName,
|
|
49
|
+
typesFile,
|
|
50
|
+
});
|
|
51
|
+
await appendWorkspaceInventoryEntries(workspace.projectDir, {
|
|
52
|
+
contractEntries: [
|
|
53
|
+
buildContractConfigEntry(contractSlug, sourceTypeName),
|
|
54
|
+
],
|
|
55
|
+
});
|
|
56
|
+
return {
|
|
57
|
+
contractSlug,
|
|
58
|
+
projectDir: workspace.projectDir,
|
|
59
|
+
schemaFile,
|
|
60
|
+
sourceTypeName,
|
|
61
|
+
typesFile,
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type IntegrationEnvServiceId, type RunAddIntegrationEnvCommandOptions } from "./cli-add-shared.js";
|
|
2
|
+
/**
|
|
3
|
+
* Runtime result returned after adding an integration environment starter.
|
|
4
|
+
*
|
|
5
|
+
* @property integrationEnvSlug Normalized slug used for generated script and
|
|
6
|
+
* documentation paths.
|
|
7
|
+
* @property projectDir Absolute official workspace directory that was updated.
|
|
8
|
+
* @property service Canonical local service starter id selected for the scaffold.
|
|
9
|
+
* @property warnings Optional non-fatal preservation notices for existing files
|
|
10
|
+
* or scripts.
|
|
11
|
+
* @property withWpEnv Whether the generated scaffold included the wp-env preset.
|
|
12
|
+
*/
|
|
13
|
+
export interface RunAddIntegrationEnvCommandResult {
|
|
14
|
+
integrationEnvSlug: string;
|
|
15
|
+
projectDir: string;
|
|
16
|
+
service: IntegrationEnvServiceId;
|
|
17
|
+
warnings?: string[];
|
|
18
|
+
withWpEnv: boolean;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Add an opt-in local WordPress integration environment starter to an official
|
|
22
|
+
* workspace.
|
|
23
|
+
*/
|
|
24
|
+
export declare function runAddIntegrationEnvCommand({ cwd, integrationEnvName, service, withWpEnv, }: RunAddIntegrationEnvCommandOptions): Promise<RunAddIntegrationEnvCommandResult>;
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
import { promises as fsp } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { assertValidGeneratedSlug, assertValidIntegrationEnvService, normalizeBlockSlug, } from "./cli-add-shared.js";
|
|
4
|
+
import { formatRunScript, } from "./package-managers.js";
|
|
5
|
+
import { pathExists, readOptionalUtf8File } from "./fs-async.js";
|
|
6
|
+
import { executeWorkspaceMutationPlan } from "./cli-add-workspace-mutation.js";
|
|
7
|
+
import { resolveWorkspaceProject } from "./workspace-project.js";
|
|
8
|
+
import { toTitleCase } from "./string-case.js";
|
|
9
|
+
const WP_ENV_PACKAGE_VERSION = "^11.2.0";
|
|
10
|
+
function buildWpEnvConfigSource() {
|
|
11
|
+
return `${JSON.stringify({
|
|
12
|
+
$schema: "https://schemas.wp.org/trunk/wp-env.json",
|
|
13
|
+
core: null,
|
|
14
|
+
port: 8888,
|
|
15
|
+
testsEnvironment: false,
|
|
16
|
+
plugins: ["."],
|
|
17
|
+
config: {
|
|
18
|
+
WP_DEBUG: true,
|
|
19
|
+
WP_DEBUG_LOG: true,
|
|
20
|
+
WP_DEBUG_DISPLAY: false,
|
|
21
|
+
SCRIPT_DEBUG: true,
|
|
22
|
+
WP_ENVIRONMENT_TYPE: "local",
|
|
23
|
+
},
|
|
24
|
+
}, null, 2)}\n`;
|
|
25
|
+
}
|
|
26
|
+
function buildDockerComposeSource() {
|
|
27
|
+
return `services:
|
|
28
|
+
integration-service:
|
|
29
|
+
image: node:22-alpine
|
|
30
|
+
working_dir: /workspace
|
|
31
|
+
volumes:
|
|
32
|
+
- .:/workspace
|
|
33
|
+
command: >
|
|
34
|
+
node -e "require('node:http').createServer((request, response) => {
|
|
35
|
+
response.writeHead(request.url === '/health' ? 200 : 404, {
|
|
36
|
+
'content-type': 'application/json'
|
|
37
|
+
});
|
|
38
|
+
response.end(JSON.stringify({
|
|
39
|
+
ok: request.url === '/health',
|
|
40
|
+
service: 'wp-typia-integration-starter'
|
|
41
|
+
}));
|
|
42
|
+
}).listen(3000, '0.0.0.0')"
|
|
43
|
+
ports:
|
|
44
|
+
- "3000:3000"
|
|
45
|
+
`;
|
|
46
|
+
}
|
|
47
|
+
function buildIntegrationSmokeScriptSource(integrationEnvSlug) {
|
|
48
|
+
return `import fs from "node:fs";
|
|
49
|
+
import path from "node:path";
|
|
50
|
+
import { fileURLToPath } from "node:url";
|
|
51
|
+
|
|
52
|
+
const ROOT_DIR = path.resolve(
|
|
53
|
+
fileURLToPath(new URL("../..", import.meta.url)),
|
|
54
|
+
);
|
|
55
|
+
const ENV_FILE = path.join(ROOT_DIR, ".env");
|
|
56
|
+
|
|
57
|
+
function parseEnvValue(value) {
|
|
58
|
+
const trimmed = value.trim();
|
|
59
|
+
if (
|
|
60
|
+
(trimmed.startsWith('"') && trimmed.endsWith('"')) ||
|
|
61
|
+
(trimmed.startsWith("'") && trimmed.endsWith("'"))
|
|
62
|
+
) {
|
|
63
|
+
return trimmed.slice(1, -1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return trimmed;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function readEnvFile(filePath) {
|
|
70
|
+
if (!fs.existsSync(filePath)) {
|
|
71
|
+
return {};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return Object.fromEntries(
|
|
75
|
+
fs
|
|
76
|
+
.readFileSync(filePath, "utf8")
|
|
77
|
+
.split(/\\r?\\n/u)
|
|
78
|
+
.map((line) => line.trim())
|
|
79
|
+
.filter((line) => line.length > 0 && !line.startsWith("#"))
|
|
80
|
+
.map((line) => {
|
|
81
|
+
const separatorIndex = line.indexOf("=");
|
|
82
|
+
if (separatorIndex === -1) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return [
|
|
87
|
+
line.slice(0, separatorIndex).trim(),
|
|
88
|
+
parseEnvValue(line.slice(separatorIndex + 1)),
|
|
89
|
+
];
|
|
90
|
+
})
|
|
91
|
+
.filter(Boolean),
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const envFile = readEnvFile(ENV_FILE);
|
|
96
|
+
|
|
97
|
+
function getEnv(name, fallback) {
|
|
98
|
+
return process.env[name] ?? envFile[name] ?? fallback;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function resolveEndpointUrl(baseUrl, endpointPath) {
|
|
102
|
+
const normalizedBaseUrl = new URL(baseUrl);
|
|
103
|
+
if (!normalizedBaseUrl.pathname.endsWith("/")) {
|
|
104
|
+
normalizedBaseUrl.pathname = \`\${normalizedBaseUrl.pathname}/\`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const relativePath = endpointPath.replace(/^\\/+/u, "");
|
|
108
|
+
return new URL(relativePath, normalizedBaseUrl);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function assertJsonEndpoint(label, url) {
|
|
112
|
+
const response = await fetch(url, {
|
|
113
|
+
headers: {
|
|
114
|
+
accept: "application/json",
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
if (!response.ok) {
|
|
119
|
+
throw new Error(
|
|
120
|
+
\`\${label} failed at \${url} with HTTP \${response.status}.\`,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
125
|
+
if (!contentType.includes("application/json")) {
|
|
126
|
+
throw new Error(
|
|
127
|
+
\`\${label} at \${url} did not return JSON (content-type: \${contentType || "missing"}).\`,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return response.json();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const baseUrl = new URL(
|
|
135
|
+
getEnv("WP_TYPIA_SMOKE_BASE_URL", "http://localhost:8888"),
|
|
136
|
+
);
|
|
137
|
+
const serviceUrl = getEnv("WP_TYPIA_SERVICE_URL", "").trim();
|
|
138
|
+
|
|
139
|
+
await assertJsonEndpoint(
|
|
140
|
+
"WordPress REST index",
|
|
141
|
+
resolveEndpointUrl(baseUrl, "wp-json/"),
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
if (serviceUrl.length > 0) {
|
|
145
|
+
await assertJsonEndpoint(
|
|
146
|
+
"Local integration service healthcheck",
|
|
147
|
+
resolveEndpointUrl(serviceUrl, "health"),
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
console.log("wp-typia integration smoke passed: ${integrationEnvSlug}");
|
|
152
|
+
`;
|
|
153
|
+
}
|
|
154
|
+
function buildEnvExampleSource(service) {
|
|
155
|
+
return [
|
|
156
|
+
"# wp-typia integration smoke settings",
|
|
157
|
+
"WP_TYPIA_SMOKE_BASE_URL=http://localhost:8888",
|
|
158
|
+
"WP_TYPIA_SMOKE_USERNAME=admin",
|
|
159
|
+
"WP_TYPIA_SMOKE_PASSWORD=password",
|
|
160
|
+
...(service === "docker-compose"
|
|
161
|
+
? [
|
|
162
|
+
"",
|
|
163
|
+
"# Optional docker-compose integration service starter.",
|
|
164
|
+
"WP_TYPIA_SERVICE_URL=http://localhost:3000",
|
|
165
|
+
]
|
|
166
|
+
: [
|
|
167
|
+
"",
|
|
168
|
+
"# Set this when your smoke test needs a project-specific service.",
|
|
169
|
+
"# WP_TYPIA_SERVICE_URL=http://localhost:3000",
|
|
170
|
+
]),
|
|
171
|
+
"",
|
|
172
|
+
].join("\n");
|
|
173
|
+
}
|
|
174
|
+
function buildIntegrationEnvReadmeSource({ integrationEnvSlug, service, withWpEnv, }) {
|
|
175
|
+
const title = toTitleCase(integrationEnvSlug);
|
|
176
|
+
const setupSteps = [
|
|
177
|
+
"Copy `.env.example` to `.env` and adjust the URLs or credentials for your local project.",
|
|
178
|
+
...(withWpEnv
|
|
179
|
+
? [
|
|
180
|
+
"Run `npm run wp-env:start` to start the generated WordPress environment.",
|
|
181
|
+
]
|
|
182
|
+
: [
|
|
183
|
+
"Point `WP_TYPIA_SMOKE_BASE_URL` at the WordPress environment you already run locally.",
|
|
184
|
+
]),
|
|
185
|
+
...(service === "docker-compose"
|
|
186
|
+
? [
|
|
187
|
+
"Run `npm run service:start` if you want the placeholder docker-compose service available at `WP_TYPIA_SERVICE_URL`.",
|
|
188
|
+
]
|
|
189
|
+
: [
|
|
190
|
+
"Set `WP_TYPIA_SERVICE_URL` only when your integration smoke needs a local service dependency.",
|
|
191
|
+
]),
|
|
192
|
+
`Run \`npm run smoke:${integrationEnvSlug}\` to execute the starter smoke check.`,
|
|
193
|
+
];
|
|
194
|
+
return `# ${title} Integration Environment
|
|
195
|
+
|
|
196
|
+
This starter keeps local WordPress integration smoke checks opt-in and editable.
|
|
197
|
+
It does not change default block scaffolds or require wp-env unless this add
|
|
198
|
+
workflow was run with \`--wp-env\`.
|
|
199
|
+
|
|
200
|
+
## Setup
|
|
201
|
+
|
|
202
|
+
${setupSteps.map((step, index) => `${index + 1}. ${step}`).join("\n")}
|
|
203
|
+
|
|
204
|
+
## Adapting the Starter
|
|
205
|
+
|
|
206
|
+
- Extend \`scripts/integration-smoke/${integrationEnvSlug}.mjs\` with the REST,
|
|
207
|
+
editor, or service assertions that matter for this project.
|
|
208
|
+
- Keep secrets in \`.env\`; \`.env.example\` should document only safe defaults.
|
|
209
|
+
- If your project uses a real service stack, replace the placeholder
|
|
210
|
+
\`docker-compose.integration.yml\` service with your database, queue, API, or
|
|
211
|
+
emulator containers.
|
|
212
|
+
- Keep the smoke script focused on high-signal integration checks so CI and
|
|
213
|
+
local debugging stay fast.
|
|
214
|
+
`;
|
|
215
|
+
}
|
|
216
|
+
async function appendMissingLines(filePath, lines) {
|
|
217
|
+
const current = (await readOptionalUtf8File(filePath)) ?? "";
|
|
218
|
+
const missingLines = lines.filter((line) => !current.includes(`${line}\n`) && !current.endsWith(line));
|
|
219
|
+
if (missingLines.length === 0) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
const separator = current.length === 0 || current.endsWith("\n") ? "" : "\n";
|
|
223
|
+
await fsp.mkdir(path.dirname(filePath), { recursive: true });
|
|
224
|
+
await fsp.writeFile(filePath, `${current}${separator}${missingLines.join("\n")}\n`, "utf8");
|
|
225
|
+
}
|
|
226
|
+
async function writeFileIfAbsent({ filePath, source, warnings, }) {
|
|
227
|
+
if (await pathExists(filePath)) {
|
|
228
|
+
warnings.push(`Preserved existing ${path.basename(filePath)}; review it manually if you need different local integration settings.`);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
await fsp.mkdir(path.dirname(filePath), { recursive: true });
|
|
232
|
+
await fsp.writeFile(filePath, source, "utf8");
|
|
233
|
+
}
|
|
234
|
+
async function writeNewScaffoldFile(filePath, source) {
|
|
235
|
+
if (await pathExists(filePath)) {
|
|
236
|
+
throw new Error(`An integration environment scaffold already exists at ${filePath}. Choose a different name.`);
|
|
237
|
+
}
|
|
238
|
+
await fsp.mkdir(path.dirname(filePath), { recursive: true });
|
|
239
|
+
await fsp.writeFile(filePath, source, "utf8");
|
|
240
|
+
}
|
|
241
|
+
function addScriptIfMissing({ scriptName, scripts, scriptValue, warnings, }) {
|
|
242
|
+
if (scripts[scriptName] === undefined) {
|
|
243
|
+
scripts[scriptName] = scriptValue;
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
if (scripts[scriptName] !== scriptValue) {
|
|
247
|
+
warnings.push(`Preserved existing package script "${scriptName}"; add "${scriptValue}" manually if you want the generated integration command.`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
async function mutatePackageJson(projectDir, mutate) {
|
|
251
|
+
const packageJsonPath = path.join(projectDir, "package.json");
|
|
252
|
+
const packageJson = JSON.parse(await fsp.readFile(packageJsonPath, "utf8"));
|
|
253
|
+
mutate(packageJson);
|
|
254
|
+
await fsp.writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, "\t")}\n`, "utf8");
|
|
255
|
+
}
|
|
256
|
+
function addIntegrationEnvPackageJsonEntries({ integrationEnvSlug, packageManager, packageJson, service, warnings, withWpEnv, }) {
|
|
257
|
+
const devDependencies = {
|
|
258
|
+
...(packageJson.devDependencies ?? {}),
|
|
259
|
+
};
|
|
260
|
+
if (withWpEnv && devDependencies["@wordpress/env"] === undefined) {
|
|
261
|
+
devDependencies["@wordpress/env"] = WP_ENV_PACKAGE_VERSION;
|
|
262
|
+
}
|
|
263
|
+
packageJson.devDependencies = devDependencies;
|
|
264
|
+
const scripts = {
|
|
265
|
+
...(packageJson.scripts ?? {}),
|
|
266
|
+
};
|
|
267
|
+
addScriptIfMissing({
|
|
268
|
+
scriptName: `smoke:${integrationEnvSlug}`,
|
|
269
|
+
scriptValue: `node scripts/integration-smoke/${integrationEnvSlug}.mjs`,
|
|
270
|
+
scripts,
|
|
271
|
+
warnings,
|
|
272
|
+
});
|
|
273
|
+
addScriptIfMissing({
|
|
274
|
+
scriptName: "smoke:integration",
|
|
275
|
+
scriptValue: formatRunScript(packageManager, `smoke:${integrationEnvSlug}`),
|
|
276
|
+
scripts,
|
|
277
|
+
warnings,
|
|
278
|
+
});
|
|
279
|
+
if (withWpEnv) {
|
|
280
|
+
addScriptIfMissing({
|
|
281
|
+
scriptName: "wp-env:start",
|
|
282
|
+
scriptValue: "wp-env start",
|
|
283
|
+
scripts,
|
|
284
|
+
warnings,
|
|
285
|
+
});
|
|
286
|
+
addScriptIfMissing({
|
|
287
|
+
scriptName: "wp-env:stop",
|
|
288
|
+
scriptValue: "wp-env stop",
|
|
289
|
+
scripts,
|
|
290
|
+
warnings,
|
|
291
|
+
});
|
|
292
|
+
addScriptIfMissing({
|
|
293
|
+
scriptName: "wp-env:reset",
|
|
294
|
+
scriptValue: "wp-env destroy all && wp-env start",
|
|
295
|
+
scripts,
|
|
296
|
+
warnings,
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
if (service === "docker-compose") {
|
|
300
|
+
addScriptIfMissing({
|
|
301
|
+
scriptName: "service:start",
|
|
302
|
+
scriptValue: "docker compose -f docker-compose.integration.yml up -d",
|
|
303
|
+
scripts,
|
|
304
|
+
warnings,
|
|
305
|
+
});
|
|
306
|
+
addScriptIfMissing({
|
|
307
|
+
scriptName: "service:stop",
|
|
308
|
+
scriptValue: "docker compose -f docker-compose.integration.yml down",
|
|
309
|
+
scripts,
|
|
310
|
+
warnings,
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
packageJson.scripts = scripts;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Add an opt-in local WordPress integration environment starter to an official
|
|
317
|
+
* workspace.
|
|
318
|
+
*/
|
|
319
|
+
export async function runAddIntegrationEnvCommand({ cwd = process.cwd(), integrationEnvName, service, withWpEnv = false, }) {
|
|
320
|
+
const workspace = resolveWorkspaceProject(cwd);
|
|
321
|
+
const integrationEnvSlug = assertValidGeneratedSlug("Integration environment name", normalizeBlockSlug(integrationEnvName), "wp-typia add integration-env <name> [--wp-env]");
|
|
322
|
+
const serviceId = assertValidIntegrationEnvService(service);
|
|
323
|
+
const warnings = [];
|
|
324
|
+
const packageJsonPath = path.join(workspace.projectDir, "package.json");
|
|
325
|
+
const gitignorePath = path.join(workspace.projectDir, ".gitignore");
|
|
326
|
+
const envExamplePath = path.join(workspace.projectDir, ".env.example");
|
|
327
|
+
const wpEnvPath = path.join(workspace.projectDir, ".wp-env.json");
|
|
328
|
+
const dockerComposePath = path.join(workspace.projectDir, "docker-compose.integration.yml");
|
|
329
|
+
const smokeDir = path.join(workspace.projectDir, "scripts", "integration-smoke");
|
|
330
|
+
const docsDir = path.join(workspace.projectDir, "docs", "integration-env");
|
|
331
|
+
const smokeScriptPath = path.join(smokeDir, `${integrationEnvSlug}.mjs`);
|
|
332
|
+
const docsPath = path.join(docsDir, `${integrationEnvSlug}.md`);
|
|
333
|
+
const shouldRemoveSmokeDirOnRollback = !(await pathExists(smokeDir));
|
|
334
|
+
const shouldRemoveDocsDirOnRollback = !(await pathExists(docsDir));
|
|
335
|
+
await executeWorkspaceMutationPlan({
|
|
336
|
+
filePaths: [
|
|
337
|
+
packageJsonPath,
|
|
338
|
+
gitignorePath,
|
|
339
|
+
envExamplePath,
|
|
340
|
+
...(withWpEnv ? [wpEnvPath] : []),
|
|
341
|
+
...(serviceId === "docker-compose" ? [dockerComposePath] : []),
|
|
342
|
+
],
|
|
343
|
+
targetPaths: [
|
|
344
|
+
smokeScriptPath,
|
|
345
|
+
docsPath,
|
|
346
|
+
...(shouldRemoveSmokeDirOnRollback ? [smokeDir] : []),
|
|
347
|
+
...(shouldRemoveDocsDirOnRollback ? [docsDir] : []),
|
|
348
|
+
],
|
|
349
|
+
run: async () => {
|
|
350
|
+
await writeNewScaffoldFile(smokeScriptPath, buildIntegrationSmokeScriptSource(integrationEnvSlug));
|
|
351
|
+
await writeNewScaffoldFile(docsPath, buildIntegrationEnvReadmeSource({
|
|
352
|
+
integrationEnvSlug,
|
|
353
|
+
service: serviceId,
|
|
354
|
+
withWpEnv,
|
|
355
|
+
}));
|
|
356
|
+
await appendMissingLines(envExamplePath, [
|
|
357
|
+
...buildEnvExampleSource(serviceId).trimEnd().split("\n"),
|
|
358
|
+
]);
|
|
359
|
+
await appendMissingLines(gitignorePath, [".env", ".env.local"]);
|
|
360
|
+
if (withWpEnv) {
|
|
361
|
+
await writeFileIfAbsent({
|
|
362
|
+
filePath: wpEnvPath,
|
|
363
|
+
source: buildWpEnvConfigSource(),
|
|
364
|
+
warnings,
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
if (serviceId === "docker-compose") {
|
|
368
|
+
await writeFileIfAbsent({
|
|
369
|
+
filePath: dockerComposePath,
|
|
370
|
+
source: buildDockerComposeSource(),
|
|
371
|
+
warnings,
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
await mutatePackageJson(workspace.projectDir, (packageJson) => addIntegrationEnvPackageJsonEntries({
|
|
375
|
+
integrationEnvSlug,
|
|
376
|
+
packageManager: workspace.packageManager,
|
|
377
|
+
packageJson,
|
|
378
|
+
service: serviceId,
|
|
379
|
+
warnings,
|
|
380
|
+
withWpEnv,
|
|
381
|
+
}));
|
|
382
|
+
},
|
|
383
|
+
});
|
|
384
|
+
return {
|
|
385
|
+
integrationEnvSlug,
|
|
386
|
+
projectDir: workspace.projectDir,
|
|
387
|
+
service: serviceId,
|
|
388
|
+
warnings: warnings.length > 0 ? warnings : undefined,
|
|
389
|
+
withWpEnv,
|
|
390
|
+
};
|
|
391
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { WorkspaceProject } from "./workspace-project.js";
|
|
2
|
+
/**
|
|
3
|
+
* Ensure the workspace bootstrap loads generated post-meta PHP modules.
|
|
4
|
+
*
|
|
5
|
+
* Inserts the generated loader function, appends its `init` hook, and rejects
|
|
6
|
+
* incompatible existing loader functions that omit the generated post-meta glob.
|
|
7
|
+
*
|
|
8
|
+
* @param workspace Resolved official workspace metadata and paths.
|
|
9
|
+
* @returns A promise that resolves after the bootstrap has been patched.
|
|
10
|
+
* @throws {Error} When the bootstrap cannot be read, written, or safely patched.
|
|
11
|
+
*/
|
|
12
|
+
export declare function ensurePostMetaBootstrapAnchors(workspace: WorkspaceProject): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Ensure `scripts/sync-rest-contracts.ts` handles post-meta schema contracts.
|
|
15
|
+
*
|
|
16
|
+
* Patches inventory imports, type guards, no-resource checks, sync loops, and
|
|
17
|
+
* success copy so generated post-meta schemas participate in REST contract sync.
|
|
18
|
+
*
|
|
19
|
+
* @param workspace Resolved official workspace metadata and paths.
|
|
20
|
+
* @returns A promise that resolves after the sync script has been patched.
|
|
21
|
+
* @throws {Error} When expected anchors are missing or file IO fails.
|
|
22
|
+
*/
|
|
23
|
+
export declare function ensurePostMetaSyncScriptAnchors(workspace: WorkspaceProject): Promise<void>;
|