@wp-typia/project-tools 0.16.7 → 0.16.9
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/README.md +26 -1
- package/dist/runtime/block-generator-service.d.ts +9 -1
- package/dist/runtime/block-generator-service.js +137 -16
- package/dist/runtime/block-generator-tool-contract.d.ts +93 -0
- package/dist/runtime/block-generator-tool-contract.js +157 -0
- package/dist/runtime/built-in-block-artifacts.js +171 -423
- package/dist/runtime/built-in-block-code-artifacts.js +96 -46
- package/dist/runtime/built-in-block-code-templates.d.ts +36 -0
- package/dist/runtime/built-in-block-code-templates.js +2233 -0
- package/dist/runtime/cli-add-block.d.ts +2 -1
- package/dist/runtime/cli-add-block.js +163 -25
- package/dist/runtime/cli-add-shared.d.ts +7 -0
- package/dist/runtime/cli-add-shared.js +4 -6
- package/dist/runtime/cli-add-workspace.js +56 -17
- package/dist/runtime/cli-core.d.ts +4 -0
- package/dist/runtime/cli-core.js +3 -0
- package/dist/runtime/cli-diagnostics.d.ts +58 -0
- package/dist/runtime/cli-diagnostics.js +101 -0
- package/dist/runtime/cli-doctor.d.ts +2 -1
- package/dist/runtime/cli-doctor.js +16 -5
- package/dist/runtime/cli-help.js +4 -4
- package/dist/runtime/cli-scaffold.d.ts +5 -1
- package/dist/runtime/cli-scaffold.js +138 -111
- package/dist/runtime/external-layer-selection.d.ts +14 -0
- package/dist/runtime/external-layer-selection.js +35 -0
- package/dist/runtime/index.d.ts +4 -2
- package/dist/runtime/index.js +2 -1
- package/dist/runtime/migration-render.d.ts +23 -1
- package/dist/runtime/migration-render.js +58 -10
- package/dist/runtime/migration-ui-capability.js +17 -8
- package/dist/runtime/migration-utils.d.ts +7 -6
- package/dist/runtime/migration-utils.js +76 -73
- package/dist/runtime/migrations.js +2 -2
- package/dist/runtime/object-utils.d.ts +8 -1
- package/dist/runtime/object-utils.js +21 -1
- package/dist/runtime/scaffold-apply-utils.d.ts +14 -2
- package/dist/runtime/scaffold-apply-utils.js +19 -6
- package/dist/runtime/scaffold-repository-reference.d.ts +22 -0
- package/dist/runtime/scaffold-repository-reference.js +119 -0
- package/dist/runtime/scaffold.d.ts +5 -1
- package/dist/runtime/scaffold.js +15 -37
- package/dist/runtime/template-builtins.d.ts +11 -1
- package/dist/runtime/template-builtins.js +118 -25
- package/dist/runtime/template-layers.d.ts +37 -0
- package/dist/runtime/template-layers.js +184 -0
- package/dist/runtime/template-render.d.ts +28 -2
- package/dist/runtime/template-render.js +122 -55
- package/dist/runtime/template-source.d.ts +23 -5
- package/dist/runtime/template-source.js +296 -217
- package/package.json +8 -3
- package/templates/_shared/base/src/validator-toolkit.ts.mustache +2 -2
- package/templates/_shared/compound/core/scripts/add-compound-child.ts.mustache +58 -12
- package/templates/_shared/migration-ui/common/src/migrations/helpers.ts +19 -47
- package/templates/_shared/migration-ui/common/src/migrations/index.ts +40 -11
|
@@ -1,25 +1,32 @@
|
|
|
1
1
|
/// <reference path="./external-template-modules.d.ts" />
|
|
2
|
-
import fs from
|
|
3
|
-
import { promises as fsp } from
|
|
4
|
-
import { createRequire } from
|
|
5
|
-
import os from
|
|
6
|
-
import path from
|
|
7
|
-
import { execFileSync } from
|
|
8
|
-
import { pathToFileURL } from
|
|
9
|
-
import npa from
|
|
10
|
-
import semver from
|
|
11
|
-
import { x as extractTarball } from
|
|
12
|
-
import { BUILTIN_TEMPLATE_IDS, OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE, PROJECT_TOOLS_PACKAGE_ROOT, isBuiltInTemplateId, } from
|
|
13
|
-
import { isPlainObject } from
|
|
14
|
-
import { getRemovedBuiltInTemplateMessage, isRemovedBuiltInTemplateId, } from
|
|
15
|
-
import { getBuiltInTemplateLayerDirs, isOmittableBuiltInTemplateLayerDir, resolveBuiltInTemplateSource, } from
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
const
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import { promises as fsp } from 'node:fs';
|
|
4
|
+
import { createRequire } from 'node:module';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { execFileSync } from 'node:child_process';
|
|
8
|
+
import { pathToFileURL } from 'node:url';
|
|
9
|
+
import npa from 'npm-package-arg';
|
|
10
|
+
import semver from 'semver';
|
|
11
|
+
import { x as extractTarball } from 'tar';
|
|
12
|
+
import { BUILTIN_TEMPLATE_IDS, OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE, PROJECT_TOOLS_PACKAGE_ROOT, isBuiltInTemplateId, } from './template-registry.js';
|
|
13
|
+
import { isPlainObject } from './object-utils.js';
|
|
14
|
+
import { getRemovedBuiltInTemplateMessage, isRemovedBuiltInTemplateId, } from './template-defaults.js';
|
|
15
|
+
import { getBuiltInTemplateLayerDirs, isOmittableBuiltInTemplateLayerDir, resolveBuiltInTemplateSource, } from './template-builtins.js';
|
|
16
|
+
import { loadExternalTemplateLayerManifest } from './template-layers.js';
|
|
17
|
+
import { getPackageVersions } from './package-versions.js';
|
|
18
|
+
import { toSegmentPascalCase } from './string-case.js';
|
|
19
|
+
import { copyRawDirectory, copyRenderedDirectory } from './template-render.js';
|
|
20
|
+
const EXTERNAL_TEMPLATE_ENTRY_CANDIDATES = [
|
|
21
|
+
'index.js',
|
|
22
|
+
'index.cjs',
|
|
23
|
+
'index.mjs',
|
|
24
|
+
];
|
|
25
|
+
const TEMPLATE_WARNING_MESSAGE = 'wp-typia owns package/tooling/sync setup for generated projects, so this external template setting is ignored.';
|
|
21
26
|
function isTemplatePathLocator(templateId) {
|
|
22
|
-
return path.isAbsolute(templateId) ||
|
|
27
|
+
return (path.isAbsolute(templateId) ||
|
|
28
|
+
templateId.startsWith('./') ||
|
|
29
|
+
templateId.startsWith('../'));
|
|
23
30
|
}
|
|
24
31
|
function getTemplateWarning(key) {
|
|
25
32
|
return `Ignoring external template config key "${key}": ${TEMPLATE_WARNING_MESSAGE}`;
|
|
@@ -27,7 +34,7 @@ function getTemplateWarning(key) {
|
|
|
27
34
|
function resolveSourceSubpath(sourceDir, relativePath) {
|
|
28
35
|
const targetPath = path.resolve(sourceDir, relativePath);
|
|
29
36
|
const relativeTarget = path.relative(sourceDir, targetPath);
|
|
30
|
-
if (relativeTarget.startsWith(
|
|
37
|
+
if (relativeTarget.startsWith('..') || path.isAbsolute(relativeTarget)) {
|
|
31
38
|
throw new Error(`Template path "${relativePath}" must stay within ${sourceDir}.`);
|
|
32
39
|
}
|
|
33
40
|
return targetPath;
|
|
@@ -42,38 +49,40 @@ function getExternalTemplateEntry(sourceDir) {
|
|
|
42
49
|
return null;
|
|
43
50
|
}
|
|
44
51
|
function selectRegistryVersion(metadata, locator) {
|
|
45
|
-
const distTags = isPlainObject(metadata[
|
|
52
|
+
const distTags = isPlainObject(metadata['dist-tags'])
|
|
53
|
+
? metadata['dist-tags']
|
|
54
|
+
: {};
|
|
46
55
|
const versions = isPlainObject(metadata.versions) ? metadata.versions : {};
|
|
47
56
|
const versionKeys = Object.keys(versions);
|
|
48
|
-
if (locator.type ===
|
|
57
|
+
if (locator.type === 'version') {
|
|
49
58
|
if (!versions[locator.fetchSpec]) {
|
|
50
59
|
throw new Error(`npm template package version not found: ${locator.raw}`);
|
|
51
60
|
}
|
|
52
61
|
return locator.fetchSpec;
|
|
53
62
|
}
|
|
54
|
-
if (locator.type ===
|
|
63
|
+
if (locator.type === 'tag') {
|
|
55
64
|
const taggedVersion = distTags[locator.fetchSpec];
|
|
56
|
-
if (typeof taggedVersion !==
|
|
65
|
+
if (typeof taggedVersion !== 'string') {
|
|
57
66
|
throw new Error(`npm template package tag not found: ${locator.raw}`);
|
|
58
67
|
}
|
|
59
68
|
return taggedVersion;
|
|
60
69
|
}
|
|
61
|
-
const range = locator.fetchSpec.trim().length > 0 ? locator.fetchSpec :
|
|
70
|
+
const range = locator.fetchSpec.trim().length > 0 ? locator.fetchSpec : '*';
|
|
62
71
|
const matchedVersion = semver.maxSatisfying(versionKeys, range);
|
|
63
72
|
if (matchedVersion) {
|
|
64
73
|
return matchedVersion;
|
|
65
74
|
}
|
|
66
75
|
if (locator.fetchSpec.trim().length > 0) {
|
|
67
|
-
throw new Error(`Unable to resolve npm template version for ${locator.raw}. Requested "${locator.fetchSpec}" but available versions are: ${versionKeys.join(
|
|
76
|
+
throw new Error(`Unable to resolve npm template version for ${locator.raw}. Requested "${locator.fetchSpec}" but available versions are: ${versionKeys.join(', ') || '(none)'}.`);
|
|
68
77
|
}
|
|
69
78
|
const latestVersion = distTags.latest;
|
|
70
|
-
if (typeof latestVersion ===
|
|
79
|
+
if (typeof latestVersion === 'string' && versions[latestVersion]) {
|
|
71
80
|
return latestVersion;
|
|
72
81
|
}
|
|
73
82
|
throw new Error(`Unable to resolve a published npm template version for ${locator.raw}.`);
|
|
74
83
|
}
|
|
75
84
|
async function fetchNpmTemplateSource(locator) {
|
|
76
|
-
const registryBase = (process.env.NPM_CONFIG_REGISTRY ??
|
|
85
|
+
const registryBase = (process.env.NPM_CONFIG_REGISTRY ?? 'https://registry.npmjs.org').replace(/\/$/, '');
|
|
77
86
|
const metadataResponse = await fetch(`${registryBase}/${encodeURIComponent(locator.name)}`);
|
|
78
87
|
if (!metadataResponse.ok) {
|
|
79
88
|
throw new Error(`Failed to fetch npm template metadata for ${locator.raw}: ${metadataResponse.status}`);
|
|
@@ -86,10 +95,10 @@ async function fetchNpmTemplateSource(locator) {
|
|
|
86
95
|
throw new Error(`npm template metadata is missing dist information for ${locator.raw}@${resolvedVersion}.`);
|
|
87
96
|
}
|
|
88
97
|
const tarballUrl = versionMetadata.dist.tarball;
|
|
89
|
-
if (typeof tarballUrl !==
|
|
98
|
+
if (typeof tarballUrl !== 'string' || tarballUrl.length === 0) {
|
|
90
99
|
throw new Error(`npm template metadata is missing tarball URL for ${locator.raw}@${resolvedVersion}.`);
|
|
91
100
|
}
|
|
92
|
-
const tempRoot = await fsp.mkdtemp(path.join(os.tmpdir(),
|
|
101
|
+
const tempRoot = await fsp.mkdtemp(path.join(os.tmpdir(), 'wp-typia-template-source-'));
|
|
93
102
|
const cleanup = async () => {
|
|
94
103
|
await fsp.rm(tempRoot, { force: true, recursive: true });
|
|
95
104
|
};
|
|
@@ -98,8 +107,8 @@ async function fetchNpmTemplateSource(locator) {
|
|
|
98
107
|
if (!tarballResponse.ok) {
|
|
99
108
|
throw new Error(`Failed to download npm template tarball for ${locator.raw}: ${tarballResponse.status}`);
|
|
100
109
|
}
|
|
101
|
-
const tarballPath = path.join(tempRoot,
|
|
102
|
-
const unpackDir = path.join(tempRoot,
|
|
110
|
+
const tarballPath = path.join(tempRoot, 'template.tgz');
|
|
111
|
+
const unpackDir = path.join(tempRoot, 'source');
|
|
103
112
|
await fsp.mkdir(unpackDir, { recursive: true });
|
|
104
113
|
await fsp.writeFile(tarballPath, Buffer.from(await tarballResponse.arrayBuffer()));
|
|
105
114
|
await extractTarball({
|
|
@@ -120,14 +129,14 @@ async function fetchNpmTemplateSource(locator) {
|
|
|
120
129
|
}
|
|
121
130
|
}
|
|
122
131
|
async function normalizeWpTypiaTemplateSeed(seed) {
|
|
123
|
-
const tempRoot = await fsp.mkdtemp(path.join(os.tmpdir(),
|
|
124
|
-
const normalizedDir = path.join(tempRoot,
|
|
132
|
+
const tempRoot = await fsp.mkdtemp(path.join(os.tmpdir(), 'wp-typia-template-source-'));
|
|
133
|
+
const normalizedDir = path.join(tempRoot, 'template');
|
|
125
134
|
try {
|
|
126
135
|
await copyRawDirectory(seed.blockDir, normalizedDir, {
|
|
127
136
|
filter: (sourcePath, _targetPath, entry) => {
|
|
128
137
|
const mustacheVariantPath = path.join(path.dirname(sourcePath), `${entry.name}.mustache`);
|
|
129
138
|
return !(entry.isFile() &&
|
|
130
|
-
(entry.name ===
|
|
139
|
+
(entry.name === 'package.json' || entry.name === 'README.md') &&
|
|
131
140
|
fs.existsSync(mustacheVariantPath));
|
|
132
141
|
},
|
|
133
142
|
});
|
|
@@ -154,21 +163,23 @@ async function normalizeWpTypiaTemplateSeed(seed) {
|
|
|
154
163
|
* use an already-installed template without forcing a registry fetch.
|
|
155
164
|
*/
|
|
156
165
|
function resolveInstalledNpmTemplateSource(locator, cwd) {
|
|
157
|
-
if (locator.rawSpec !==
|
|
166
|
+
if (locator.rawSpec !== '' && locator.rawSpec !== '*') {
|
|
158
167
|
return null;
|
|
159
168
|
}
|
|
160
|
-
const workspacePackagesRoot = path.resolve(PROJECT_TOOLS_PACKAGE_ROOT,
|
|
169
|
+
const workspacePackagesRoot = path.resolve(PROJECT_TOOLS_PACKAGE_ROOT, '..');
|
|
161
170
|
if (fs.existsSync(workspacePackagesRoot)) {
|
|
162
|
-
for (const entry of fs.readdirSync(workspacePackagesRoot, {
|
|
171
|
+
for (const entry of fs.readdirSync(workspacePackagesRoot, {
|
|
172
|
+
withFileTypes: true,
|
|
173
|
+
})) {
|
|
163
174
|
if (!entry.isDirectory()) {
|
|
164
175
|
continue;
|
|
165
176
|
}
|
|
166
177
|
const packageDir = path.join(workspacePackagesRoot, entry.name);
|
|
167
|
-
const packageJsonPath = path.join(packageDir,
|
|
178
|
+
const packageJsonPath = path.join(packageDir, 'package.json');
|
|
168
179
|
if (!fs.existsSync(packageJsonPath)) {
|
|
169
180
|
continue;
|
|
170
181
|
}
|
|
171
|
-
const manifest = JSON.parse(fs.readFileSync(packageJsonPath,
|
|
182
|
+
const manifest = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
172
183
|
if (manifest.name === locator.name) {
|
|
173
184
|
return {
|
|
174
185
|
blockDir: packageDir,
|
|
@@ -177,7 +188,7 @@ function resolveInstalledNpmTemplateSource(locator, cwd) {
|
|
|
177
188
|
}
|
|
178
189
|
}
|
|
179
190
|
}
|
|
180
|
-
const workspaceRequire = createRequire(path.join(path.resolve(cwd),
|
|
191
|
+
const workspaceRequire = createRequire(path.join(path.resolve(cwd), '__wp_typia_template_resolver__.cjs'));
|
|
181
192
|
try {
|
|
182
193
|
const packageJsonPath = fs.realpathSync(workspaceRequire.resolve(`${locator.name}/package.json`));
|
|
183
194
|
const sourceDir = path.dirname(packageJsonPath);
|
|
@@ -187,13 +198,14 @@ function resolveInstalledNpmTemplateSource(locator, cwd) {
|
|
|
187
198
|
};
|
|
188
199
|
}
|
|
189
200
|
catch (error) {
|
|
190
|
-
const errorCode = typeof error ===
|
|
201
|
+
const errorCode = typeof error === 'object' && error !== null && 'code' in error
|
|
191
202
|
? String(error.code)
|
|
192
|
-
:
|
|
193
|
-
if (errorCode ===
|
|
194
|
-
errorCode ===
|
|
195
|
-
for (const basePath of workspaceRequire.resolve.paths(locator.name) ??
|
|
196
|
-
|
|
203
|
+
: '';
|
|
204
|
+
if (errorCode === 'MODULE_NOT_FOUND' ||
|
|
205
|
+
errorCode === 'ERR_PACKAGE_PATH_NOT_EXPORTED') {
|
|
206
|
+
for (const basePath of workspaceRequire.resolve.paths(locator.name) ??
|
|
207
|
+
[]) {
|
|
208
|
+
const packageJsonPath = path.join(basePath, locator.name, 'package.json');
|
|
197
209
|
if (!fs.existsSync(packageJsonPath)) {
|
|
198
210
|
continue;
|
|
199
211
|
}
|
|
@@ -209,12 +221,12 @@ function resolveInstalledNpmTemplateSource(locator, cwd) {
|
|
|
209
221
|
}
|
|
210
222
|
}
|
|
211
223
|
function isOfficialWorkspaceTemplateSeed(seed) {
|
|
212
|
-
const packageJsonPath = path.join(seed.rootDir,
|
|
224
|
+
const packageJsonPath = path.join(seed.rootDir, 'package.json');
|
|
213
225
|
if (!fs.existsSync(packageJsonPath)) {
|
|
214
226
|
return false;
|
|
215
227
|
}
|
|
216
228
|
try {
|
|
217
|
-
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath,
|
|
229
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
218
230
|
return packageJson.name === OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE;
|
|
219
231
|
}
|
|
220
232
|
catch {
|
|
@@ -222,11 +234,13 @@ function isOfficialWorkspaceTemplateSeed(seed) {
|
|
|
222
234
|
}
|
|
223
235
|
}
|
|
224
236
|
export function parseGitHubTemplateLocator(templateId) {
|
|
225
|
-
if (!templateId.startsWith(
|
|
237
|
+
if (!templateId.startsWith('github:')) {
|
|
226
238
|
return null;
|
|
227
239
|
}
|
|
228
|
-
const [locatorBody, refSegment] = templateId
|
|
229
|
-
|
|
240
|
+
const [locatorBody, refSegment] = templateId
|
|
241
|
+
.slice('github:'.length)
|
|
242
|
+
.split('#', 2);
|
|
243
|
+
const segments = locatorBody.split('/').filter(Boolean);
|
|
230
244
|
if (segments.length < 3) {
|
|
231
245
|
throw new Error(`GitHub template locators must look like github:owner/repo/path[#ref]. Received: ${templateId}`);
|
|
232
246
|
}
|
|
@@ -235,11 +249,13 @@ export function parseGitHubTemplateLocator(templateId) {
|
|
|
235
249
|
owner,
|
|
236
250
|
repo,
|
|
237
251
|
ref: refSegment ?? null,
|
|
238
|
-
sourcePath: sourcePathSegments.join(
|
|
252
|
+
sourcePath: sourcePathSegments.join('/'),
|
|
239
253
|
};
|
|
240
254
|
}
|
|
241
255
|
export function parseNpmTemplateLocator(templateId) {
|
|
242
|
-
if (isBuiltInTemplateId(templateId) ||
|
|
256
|
+
if (isBuiltInTemplateId(templateId) ||
|
|
257
|
+
isTemplatePathLocator(templateId) ||
|
|
258
|
+
templateId.startsWith('github:')) {
|
|
243
259
|
return null;
|
|
244
260
|
}
|
|
245
261
|
try {
|
|
@@ -248,9 +264,11 @@ export function parseNpmTemplateLocator(templateId) {
|
|
|
248
264
|
return null;
|
|
249
265
|
}
|
|
250
266
|
const parsedWithRawSpec = parsed;
|
|
251
|
-
const rawSpec = typeof parsedWithRawSpec.rawSpec ===
|
|
267
|
+
const rawSpec = typeof parsedWithRawSpec.rawSpec === 'string'
|
|
268
|
+
? parsedWithRawSpec.rawSpec
|
|
269
|
+
: '';
|
|
252
270
|
return {
|
|
253
|
-
fetchSpec: typeof parsed.fetchSpec ===
|
|
271
|
+
fetchSpec: typeof parsed.fetchSpec === 'string' ? parsed.fetchSpec : '',
|
|
254
272
|
name: parsed.name,
|
|
255
273
|
raw: templateId,
|
|
256
274
|
rawSpec,
|
|
@@ -267,21 +285,22 @@ export function parseTemplateLocator(templateId) {
|
|
|
267
285
|
}
|
|
268
286
|
const githubLocator = parseGitHubTemplateLocator(templateId);
|
|
269
287
|
if (githubLocator) {
|
|
270
|
-
return { kind:
|
|
288
|
+
return { kind: 'github', locator: githubLocator };
|
|
271
289
|
}
|
|
272
290
|
if (isTemplatePathLocator(templateId)) {
|
|
273
|
-
return { kind:
|
|
291
|
+
return { kind: 'path', templatePath: templateId };
|
|
274
292
|
}
|
|
275
293
|
const npmLocator = parseNpmTemplateLocator(templateId);
|
|
276
294
|
if (npmLocator) {
|
|
277
|
-
return { kind:
|
|
295
|
+
return { kind: 'npm', locator: npmLocator };
|
|
278
296
|
}
|
|
279
|
-
throw new Error(`Unknown template "${templateId}". Expected one of: ${BUILTIN_TEMPLATE_IDS.join(
|
|
297
|
+
throw new Error(`Unknown template "${templateId}". Expected one of: ${BUILTIN_TEMPLATE_IDS.join(', ')}, a local path, github:owner/repo/path[#ref], or an npm package spec.`);
|
|
280
298
|
}
|
|
281
299
|
function getDefaultCategoryFromBlockJson(blockJson) {
|
|
282
|
-
return typeof blockJson.category ===
|
|
300
|
+
return typeof blockJson.category === 'string' &&
|
|
301
|
+
blockJson.category.trim().length > 0
|
|
283
302
|
? blockJson.category.trim()
|
|
284
|
-
:
|
|
303
|
+
: 'widgets';
|
|
285
304
|
}
|
|
286
305
|
function getDefaultCategory(sourceDir) {
|
|
287
306
|
try {
|
|
@@ -289,7 +308,7 @@ function getDefaultCategory(sourceDir) {
|
|
|
289
308
|
return getDefaultCategoryFromBlockJson(blockJson);
|
|
290
309
|
}
|
|
291
310
|
catch {
|
|
292
|
-
return
|
|
311
|
+
return 'widgets';
|
|
293
312
|
}
|
|
294
313
|
}
|
|
295
314
|
function getTemplateVariableContext(variables) {
|
|
@@ -312,31 +331,41 @@ function getTemplateVariableContext(variables) {
|
|
|
312
331
|
};
|
|
313
332
|
}
|
|
314
333
|
async function detectTemplateSourceFormat(sourceDir) {
|
|
315
|
-
if (fs.existsSync(path.join(sourceDir,
|
|
316
|
-
return
|
|
334
|
+
if (fs.existsSync(path.join(sourceDir, 'package.json.mustache'))) {
|
|
335
|
+
return 'wp-typia';
|
|
336
|
+
}
|
|
337
|
+
if (await loadExternalTemplateLayerManifest(sourceDir)) {
|
|
338
|
+
throw new Error(`Template source at ${sourceDir} is an external layer package. External layers currently compose only through built-in scaffolds via the runtime API, not as standalone template ids.`);
|
|
317
339
|
}
|
|
318
340
|
if (getExternalTemplateEntry(sourceDir)) {
|
|
319
|
-
return
|
|
341
|
+
return 'create-block-external';
|
|
320
342
|
}
|
|
321
|
-
const sourceRoot = fs.existsSync(path.join(sourceDir,
|
|
343
|
+
const sourceRoot = fs.existsSync(path.join(sourceDir, 'src'))
|
|
344
|
+
? path.join(sourceDir, 'src')
|
|
345
|
+
: sourceDir;
|
|
322
346
|
const blockJsonCandidates = [
|
|
323
|
-
path.join(sourceDir,
|
|
324
|
-
path.join(sourceRoot,
|
|
347
|
+
path.join(sourceDir, 'block.json'),
|
|
348
|
+
path.join(sourceRoot, 'block.json'),
|
|
325
349
|
];
|
|
326
350
|
const hasBlockJson = blockJsonCandidates.some((candidate) => fs.existsSync(candidate));
|
|
327
|
-
const hasIndexFile = [
|
|
328
|
-
const hasEditFile = [
|
|
329
|
-
const hasSaveFile = [
|
|
351
|
+
const hasIndexFile = ['index.js', 'index.jsx', 'index.ts', 'index.tsx'].some((filename) => fs.existsSync(path.join(sourceRoot, filename)));
|
|
352
|
+
const hasEditFile = ['edit.js', 'edit.jsx', 'edit.ts', 'edit.tsx'].some((filename) => fs.existsSync(path.join(sourceRoot, filename)));
|
|
353
|
+
const hasSaveFile = ['save.js', 'save.jsx', 'save.ts', 'save.tsx'].some((filename) => fs.existsSync(path.join(sourceRoot, filename)));
|
|
330
354
|
if (hasBlockJson && hasIndexFile && hasEditFile && hasSaveFile) {
|
|
331
|
-
return
|
|
355
|
+
return 'create-block-subset';
|
|
332
356
|
}
|
|
333
357
|
throw new Error(`Unsupported template source at ${sourceDir}. Expected a wp-typia template directory, an official create-block external template config, or a create-block subset with block.json and src/index/edit/save files.`);
|
|
334
358
|
}
|
|
335
359
|
function readRemoteBlockJson(blockDir) {
|
|
336
|
-
const sourceRoot = fs.existsSync(path.join(blockDir,
|
|
337
|
-
|
|
360
|
+
const sourceRoot = fs.existsSync(path.join(blockDir, 'src'))
|
|
361
|
+
? path.join(blockDir, 'src')
|
|
362
|
+
: blockDir;
|
|
363
|
+
for (const candidate of [
|
|
364
|
+
path.join(blockDir, 'block.json'),
|
|
365
|
+
path.join(sourceRoot, 'block.json'),
|
|
366
|
+
]) {
|
|
338
367
|
if (fs.existsSync(candidate)) {
|
|
339
|
-
return JSON.parse(fs.readFileSync(candidate,
|
|
368
|
+
return JSON.parse(fs.readFileSync(candidate, 'utf8'));
|
|
340
369
|
}
|
|
341
370
|
}
|
|
342
371
|
throw new Error(`Unable to locate block.json in ${blockDir}`);
|
|
@@ -354,72 +383,83 @@ async function assertNoSymlinks(sourceDir) {
|
|
|
354
383
|
}
|
|
355
384
|
}
|
|
356
385
|
function renderTypeScriptLiteral(value) {
|
|
357
|
-
if (typeof value ===
|
|
386
|
+
if (typeof value === 'string') {
|
|
358
387
|
return JSON.stringify(value);
|
|
359
388
|
}
|
|
360
|
-
if (typeof value ===
|
|
389
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
361
390
|
return String(value);
|
|
362
391
|
}
|
|
363
|
-
return
|
|
392
|
+
return 'undefined';
|
|
364
393
|
}
|
|
365
394
|
function renderTagsForAttribute(attribute) {
|
|
366
395
|
const tags = [];
|
|
367
|
-
if (typeof attribute.default ===
|
|
396
|
+
if (typeof attribute.default === 'string' ||
|
|
397
|
+
typeof attribute.default === 'number' ||
|
|
398
|
+
typeof attribute.default === 'boolean') {
|
|
368
399
|
tags.push(`tags.Default<${renderTypeScriptLiteral(attribute.default)}>`);
|
|
369
400
|
}
|
|
370
401
|
return tags;
|
|
371
402
|
}
|
|
372
403
|
function renderAttributeBaseType(attributeName, attribute) {
|
|
373
404
|
if (Array.isArray(attribute.enum) && attribute.enum.length > 0) {
|
|
374
|
-
return attribute.enum
|
|
405
|
+
return attribute.enum
|
|
406
|
+
.map((item) => renderTypeScriptLiteral(item))
|
|
407
|
+
.join(' | ');
|
|
375
408
|
}
|
|
376
409
|
switch (attribute.type) {
|
|
377
|
-
case
|
|
378
|
-
return
|
|
379
|
-
case
|
|
380
|
-
return
|
|
381
|
-
case
|
|
382
|
-
return
|
|
383
|
-
case
|
|
384
|
-
return
|
|
385
|
-
case
|
|
386
|
-
return
|
|
410
|
+
case 'string':
|
|
411
|
+
return 'string';
|
|
412
|
+
case 'number':
|
|
413
|
+
return 'number';
|
|
414
|
+
case 'boolean':
|
|
415
|
+
return 'boolean';
|
|
416
|
+
case 'array':
|
|
417
|
+
return 'unknown[]';
|
|
418
|
+
case 'object':
|
|
419
|
+
return 'Record<string, unknown>';
|
|
387
420
|
default:
|
|
388
|
-
if (typeof attributeName ===
|
|
389
|
-
|
|
421
|
+
if (typeof attributeName === 'string' &&
|
|
422
|
+
attributeName.toLowerCase().includes('class')) {
|
|
423
|
+
return 'string';
|
|
390
424
|
}
|
|
391
|
-
return
|
|
425
|
+
return 'unknown';
|
|
392
426
|
}
|
|
393
427
|
}
|
|
394
428
|
function buildRemoteTypesSource(blockJson, context) {
|
|
395
429
|
const attributes = (blockJson.attributes ?? {});
|
|
396
|
-
const lines = [
|
|
430
|
+
const lines = [
|
|
431
|
+
'import { tags } from "typia";',
|
|
432
|
+
'',
|
|
433
|
+
`export interface ${context.pascalCase}Attributes {`,
|
|
434
|
+
];
|
|
397
435
|
for (const [name, attribute] of Object.entries(attributes)) {
|
|
398
436
|
const baseType = renderAttributeBaseType(name, attribute);
|
|
399
437
|
const tagList = renderTagsForAttribute(attribute);
|
|
400
|
-
const baseTypeWithGrouping = tagList.length > 0 && baseType.includes(
|
|
401
|
-
|
|
438
|
+
const baseTypeWithGrouping = tagList.length > 0 && baseType.includes(' | ')
|
|
439
|
+
? `(${baseType})`
|
|
440
|
+
: baseType;
|
|
441
|
+
const renderedType = [baseTypeWithGrouping, ...tagList].join(' & ');
|
|
402
442
|
lines.push(` ${JSON.stringify(name)}?: ${renderedType};`);
|
|
403
443
|
}
|
|
404
|
-
lines.push(
|
|
405
|
-
return lines.join(
|
|
444
|
+
lines.push('}', '');
|
|
445
|
+
return lines.join('\n');
|
|
406
446
|
}
|
|
407
447
|
function buildRemoteBlockJsonTemplate(blockJson) {
|
|
408
448
|
const merged = {
|
|
409
449
|
...blockJson,
|
|
410
|
-
description:
|
|
411
|
-
name:
|
|
412
|
-
textdomain:
|
|
413
|
-
title:
|
|
450
|
+
description: '{{description}}',
|
|
451
|
+
name: '{{namespace}}/{{slug}}',
|
|
452
|
+
textdomain: '{{textDomain}}',
|
|
453
|
+
title: '{{title}}',
|
|
414
454
|
};
|
|
415
455
|
if (!Array.isArray(merged.keywords) || merged.keywords.length === 0) {
|
|
416
|
-
merged.keywords = [
|
|
456
|
+
merged.keywords = ['{{keyword}}', 'typia', 'block'];
|
|
417
457
|
}
|
|
418
|
-
return `${JSON.stringify(merged, null,
|
|
458
|
+
return `${JSON.stringify(merged, null, '\t')}\n`;
|
|
419
459
|
}
|
|
420
460
|
async function rewriteBlockJsonImports(directory) {
|
|
421
|
-
const textExtensions = new Set([
|
|
422
|
-
const targetBlockJsonPath = path.join(directory,
|
|
461
|
+
const textExtensions = new Set(['.js', '.jsx', '.ts', '.tsx']);
|
|
462
|
+
const targetBlockJsonPath = path.join(directory, 'block.json');
|
|
423
463
|
async function visit(currentPath) {
|
|
424
464
|
const stats = await fsp.stat(currentPath);
|
|
425
465
|
if (stats.isDirectory()) {
|
|
@@ -432,32 +472,36 @@ async function rewriteBlockJsonImports(directory) {
|
|
|
432
472
|
if (!textExtensions.has(path.extname(currentPath))) {
|
|
433
473
|
return;
|
|
434
474
|
}
|
|
435
|
-
const content = await fsp.readFile(currentPath,
|
|
436
|
-
const relativeSpecifier = path
|
|
437
|
-
|
|
475
|
+
const content = await fsp.readFile(currentPath, 'utf8');
|
|
476
|
+
const relativeSpecifier = path
|
|
477
|
+
.relative(path.dirname(currentPath), targetBlockJsonPath)
|
|
478
|
+
.replace(/\\/g, '/');
|
|
479
|
+
const normalizedSpecifier = relativeSpecifier.startsWith('.')
|
|
480
|
+
? relativeSpecifier
|
|
481
|
+
: `./${relativeSpecifier}`;
|
|
438
482
|
const next = content.replace(/(['"])\.{1,2}\/[^'"]*block\.json\1/g, `$1${normalizedSpecifier}$1`);
|
|
439
483
|
if (next !== content) {
|
|
440
|
-
await fsp.writeFile(currentPath, next,
|
|
484
|
+
await fsp.writeFile(currentPath, next, 'utf8');
|
|
441
485
|
}
|
|
442
486
|
}
|
|
443
487
|
await visit(directory);
|
|
444
488
|
}
|
|
445
489
|
async function patchRemotePackageJson(templateDir, needsInteractivity) {
|
|
446
|
-
const packageJsonPath = path.join(templateDir,
|
|
447
|
-
const packageJson = JSON.parse(await fsp.readFile(packageJsonPath,
|
|
490
|
+
const packageJsonPath = path.join(templateDir, 'package.json.mustache');
|
|
491
|
+
const packageJson = JSON.parse(await fsp.readFile(packageJsonPath, 'utf8'));
|
|
448
492
|
const existingDependencies = { ...(packageJson.dependencies ?? {}) };
|
|
449
493
|
const existingDevDependencies = { ...(packageJson.devDependencies ?? {}) };
|
|
450
|
-
delete existingDependencies[
|
|
451
|
-
delete existingDevDependencies[
|
|
494
|
+
delete existingDependencies['@wp-typia/project-tools'];
|
|
495
|
+
delete existingDevDependencies['@wp-typia/project-tools'];
|
|
452
496
|
packageJson.devDependencies = {
|
|
453
|
-
|
|
454
|
-
|
|
497
|
+
'@wp-typia/block-runtime': '{{blockRuntimePackageVersion}}',
|
|
498
|
+
'@wp-typia/block-types': '{{blockTypesPackageVersion}}',
|
|
455
499
|
...existingDevDependencies,
|
|
456
500
|
};
|
|
457
501
|
if (needsInteractivity) {
|
|
458
502
|
packageJson.dependencies = {
|
|
459
503
|
...existingDependencies,
|
|
460
|
-
|
|
504
|
+
'@wordpress/interactivity': '^6.29.0',
|
|
461
505
|
};
|
|
462
506
|
}
|
|
463
507
|
else if (Object.keys(existingDependencies).length > 0) {
|
|
@@ -466,13 +510,18 @@ async function patchRemotePackageJson(templateDir, needsInteractivity) {
|
|
|
466
510
|
else {
|
|
467
511
|
delete packageJson.dependencies;
|
|
468
512
|
}
|
|
469
|
-
await fsp.writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`,
|
|
513
|
+
await fsp.writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`, 'utf8');
|
|
470
514
|
}
|
|
471
515
|
function getSeedSourceRoot(blockDir) {
|
|
472
|
-
return fs.existsSync(path.join(blockDir,
|
|
516
|
+
return fs.existsSync(path.join(blockDir, 'src'))
|
|
517
|
+
? path.join(blockDir, 'src')
|
|
518
|
+
: blockDir;
|
|
473
519
|
}
|
|
474
520
|
function findSeedRenderPhp(seed) {
|
|
475
|
-
for (const candidate of [
|
|
521
|
+
for (const candidate of [
|
|
522
|
+
path.join(seed.blockDir, 'render.php'),
|
|
523
|
+
path.join(seed.rootDir, 'render.php'),
|
|
524
|
+
]) {
|
|
476
525
|
if (fs.existsSync(candidate)) {
|
|
477
526
|
return candidate;
|
|
478
527
|
}
|
|
@@ -481,49 +530,49 @@ function findSeedRenderPhp(seed) {
|
|
|
481
530
|
}
|
|
482
531
|
async function removeSeedEntryConflicts(templateDir) {
|
|
483
532
|
for (const filename of [
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
533
|
+
'block.json',
|
|
534
|
+
'block.json.mustache',
|
|
535
|
+
'edit.js',
|
|
536
|
+
'edit.jsx',
|
|
537
|
+
'edit.ts',
|
|
538
|
+
'edit.tsx',
|
|
539
|
+
'edit.tsx.mustache',
|
|
540
|
+
'hooks.ts',
|
|
541
|
+
'hooks.ts.mustache',
|
|
542
|
+
'index.js',
|
|
543
|
+
'index.jsx',
|
|
544
|
+
'index.ts',
|
|
545
|
+
'index.tsx',
|
|
546
|
+
'index.tsx.mustache',
|
|
547
|
+
'save.js',
|
|
548
|
+
'save.jsx',
|
|
549
|
+
'save.ts',
|
|
550
|
+
'save.tsx',
|
|
551
|
+
'save.tsx.mustache',
|
|
552
|
+
'style.css',
|
|
553
|
+
'style.scss',
|
|
554
|
+
'style.scss.mustache',
|
|
555
|
+
'types.ts',
|
|
556
|
+
'types.ts.mustache',
|
|
557
|
+
'validators.ts',
|
|
558
|
+
'validators.ts.mustache',
|
|
559
|
+
'view.js',
|
|
560
|
+
'view.jsx',
|
|
561
|
+
'view.ts',
|
|
562
|
+
'view.tsx',
|
|
514
563
|
]) {
|
|
515
|
-
await fsp.rm(path.join(templateDir,
|
|
564
|
+
await fsp.rm(path.join(templateDir, 'src', filename), { force: true });
|
|
516
565
|
}
|
|
517
566
|
}
|
|
518
567
|
async function normalizeCreateBlockSubset(seed, context) {
|
|
519
|
-
const tempRoot = await fsp.mkdtemp(path.join(os.tmpdir(),
|
|
520
|
-
const templateDir = path.join(tempRoot,
|
|
568
|
+
const tempRoot = await fsp.mkdtemp(path.join(os.tmpdir(), 'wp-typia-remote-template-'));
|
|
569
|
+
const templateDir = path.join(tempRoot, 'template');
|
|
521
570
|
const blockJson = readRemoteBlockJson(seed.blockDir);
|
|
522
571
|
const sourceRoot = getSeedSourceRoot(seed.blockDir);
|
|
523
572
|
await fsp.mkdir(templateDir, { recursive: true });
|
|
524
|
-
for (const layerDir of getBuiltInTemplateLayerDirs(
|
|
573
|
+
for (const layerDir of getBuiltInTemplateLayerDirs('basic')) {
|
|
525
574
|
if (!fs.existsSync(layerDir)) {
|
|
526
|
-
if (isOmittableBuiltInTemplateLayerDir(
|
|
575
|
+
if (isOmittableBuiltInTemplateLayerDir('basic', layerDir)) {
|
|
527
576
|
continue;
|
|
528
577
|
}
|
|
529
578
|
throw new Error(`Built-in template layer is missing: ${layerDir}`);
|
|
@@ -534,31 +583,41 @@ async function normalizeCreateBlockSubset(seed, context) {
|
|
|
534
583
|
});
|
|
535
584
|
}
|
|
536
585
|
await removeSeedEntryConflicts(templateDir);
|
|
537
|
-
await fsp.cp(sourceRoot, path.join(templateDir,
|
|
586
|
+
await fsp.cp(sourceRoot, path.join(templateDir, 'src'), {
|
|
587
|
+
recursive: true,
|
|
588
|
+
force: true,
|
|
589
|
+
});
|
|
538
590
|
const remoteRenderPath = findSeedRenderPhp(seed);
|
|
539
591
|
if (remoteRenderPath) {
|
|
540
|
-
await fsp.copyFile(remoteRenderPath, path.join(templateDir,
|
|
592
|
+
await fsp.copyFile(remoteRenderPath, path.join(templateDir, 'render.php'));
|
|
541
593
|
}
|
|
542
594
|
if (seed.assetsDir && fs.existsSync(seed.assetsDir)) {
|
|
543
|
-
await fsp.cp(seed.assetsDir, path.join(templateDir,
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
fs.existsSync(path.join(templateDir,
|
|
554
|
-
fs.existsSync(path.join(templateDir,
|
|
595
|
+
await fsp.cp(seed.assetsDir, path.join(templateDir, 'assets'), {
|
|
596
|
+
recursive: true,
|
|
597
|
+
force: true,
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
await fsp.writeFile(path.join(templateDir, 'src', 'types.ts'), buildRemoteTypesSource(blockJson, context), 'utf8');
|
|
601
|
+
await fsp.writeFile(path.join(templateDir, 'src', 'block.json'), buildRemoteBlockJsonTemplate(blockJson), 'utf8');
|
|
602
|
+
await rewriteBlockJsonImports(path.join(templateDir, 'src'));
|
|
603
|
+
const needsInteractivity = typeof blockJson.viewScriptModule === 'string' ||
|
|
604
|
+
typeof blockJson.viewScript === 'string' ||
|
|
605
|
+
fs.existsSync(path.join(templateDir, 'src', 'view.js')) ||
|
|
606
|
+
fs.existsSync(path.join(templateDir, 'src', 'view.ts')) ||
|
|
607
|
+
fs.existsSync(path.join(templateDir, 'src', 'view.tsx')) ||
|
|
608
|
+
fs.existsSync(path.join(templateDir, 'src', 'interactivity.js')) ||
|
|
609
|
+
fs.existsSync(path.join(templateDir, 'src', 'interactivity.ts'));
|
|
555
610
|
await patchRemotePackageJson(templateDir, needsInteractivity);
|
|
556
611
|
return {
|
|
557
|
-
id:
|
|
612
|
+
id: 'remote:create-block-subset',
|
|
558
613
|
defaultCategory: getDefaultCategoryFromBlockJson(blockJson),
|
|
559
|
-
description:
|
|
560
|
-
features: [
|
|
561
|
-
|
|
614
|
+
description: 'A wp-typia scaffold normalized from a create-block subset source',
|
|
615
|
+
features: [
|
|
616
|
+
'Remote source',
|
|
617
|
+
'create-block adapter',
|
|
618
|
+
'Typia metadata pipeline',
|
|
619
|
+
],
|
|
620
|
+
format: 'create-block-subset',
|
|
562
621
|
selectedVariant: seed.selectedVariant ?? null,
|
|
563
622
|
templateDir,
|
|
564
623
|
warnings: seed.warnings ?? [],
|
|
@@ -583,15 +642,15 @@ async function loadExternalTemplateConfig(sourceDir) {
|
|
|
583
642
|
}
|
|
584
643
|
const warnings = [];
|
|
585
644
|
for (const ignoredKey of [
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
645
|
+
'pluginTemplatesPath',
|
|
646
|
+
'wpScripts',
|
|
647
|
+
'wpEnv',
|
|
648
|
+
'customScripts',
|
|
649
|
+
'npmDependencies',
|
|
650
|
+
'npmDevDependencies',
|
|
651
|
+
'customPackageJSON',
|
|
652
|
+
'pluginReadme',
|
|
653
|
+
'pluginHeader',
|
|
595
654
|
]) {
|
|
596
655
|
if (ignoredKey in loadedConfig) {
|
|
597
656
|
warnings.push(getTemplateWarning(ignoredKey));
|
|
@@ -621,11 +680,11 @@ function getVariantConfig(config, requestedVariant) {
|
|
|
621
680
|
}
|
|
622
681
|
const selectedVariant = requestedVariant ?? variantKeys[0];
|
|
623
682
|
if (!selectedVariant || !isPlainObject(config.variants?.[selectedVariant])) {
|
|
624
|
-
throw new Error(`Unknown template variant "${requestedVariant}". Expected one of: ${variantKeys.join(
|
|
683
|
+
throw new Error(`Unknown template variant "${requestedVariant}". Expected one of: ${variantKeys.join(', ')}`);
|
|
625
684
|
}
|
|
626
685
|
return {
|
|
627
686
|
selectedVariant,
|
|
628
|
-
variantConfig: config.variants?.[selectedVariant],
|
|
687
|
+
variantConfig: config.variants?.[selectedVariant] ?? {},
|
|
629
688
|
};
|
|
630
689
|
}
|
|
631
690
|
function extractVariantRenderValues(variantConfig) {
|
|
@@ -651,7 +710,7 @@ async function buildExternalTemplateView(context, config, selectedVariant, varia
|
|
|
651
710
|
}
|
|
652
711
|
const transformed = await config.transformer(mergedView);
|
|
653
712
|
if (!isPlainObject(transformed)) {
|
|
654
|
-
throw new Error(
|
|
713
|
+
throw new Error('External template transformer(view) must return an object.');
|
|
655
714
|
}
|
|
656
715
|
return {
|
|
657
716
|
...mergedView,
|
|
@@ -661,28 +720,35 @@ async function buildExternalTemplateView(context, config, selectedVariant, varia
|
|
|
661
720
|
async function renderCreateBlockExternalTemplate(sourceDir, context, requestedVariant) {
|
|
662
721
|
const { config, warnings } = await loadExternalTemplateConfig(sourceDir);
|
|
663
722
|
const { selectedVariant, variantConfig } = getVariantConfig(config, requestedVariant);
|
|
664
|
-
const blockTemplatesPath = (typeof variantConfig.blockTemplatesPath ===
|
|
665
|
-
|
|
723
|
+
const blockTemplatesPath = (typeof variantConfig.blockTemplatesPath === 'string'
|
|
724
|
+
? variantConfig.blockTemplatesPath
|
|
725
|
+
: config.blockTemplatesPath) ?? null;
|
|
666
726
|
if (!blockTemplatesPath) {
|
|
667
|
-
throw new Error(
|
|
727
|
+
throw new Error('External template config must define blockTemplatesPath.');
|
|
668
728
|
}
|
|
669
|
-
const tempRoot = await fsp.mkdtemp(path.join(os.tmpdir(),
|
|
729
|
+
const tempRoot = await fsp.mkdtemp(path.join(os.tmpdir(), 'wp-typia-create-block-external-'));
|
|
670
730
|
const cleanup = async () => {
|
|
671
731
|
await fsp.rm(tempRoot, { force: true, recursive: true });
|
|
672
732
|
};
|
|
673
733
|
try {
|
|
674
|
-
const renderedRoot = path.join(tempRoot,
|
|
675
|
-
const folderName = (typeof variantConfig.folderName ===
|
|
734
|
+
const renderedRoot = path.join(tempRoot, 'rendered');
|
|
735
|
+
const folderName = (typeof variantConfig.folderName === 'string'
|
|
736
|
+
? variantConfig.folderName
|
|
737
|
+
: config.folderName) || '.';
|
|
676
738
|
const blockDir = resolveSourceSubpath(renderedRoot, folderName);
|
|
677
739
|
const view = await buildExternalTemplateView(context, config, selectedVariant, variantConfig);
|
|
678
740
|
const blockTemplateDir = resolveSourceSubpath(sourceDir, blockTemplatesPath);
|
|
679
741
|
await copyRenderedDirectory(blockTemplateDir, blockDir, view);
|
|
680
|
-
const assetsPath = typeof variantConfig.assetsPath ===
|
|
681
|
-
|
|
682
|
-
|
|
742
|
+
const assetsPath = typeof variantConfig.assetsPath === 'string'
|
|
743
|
+
? variantConfig.assetsPath
|
|
744
|
+
: config.assetsPath;
|
|
745
|
+
if (typeof assetsPath === 'string' && assetsPath.trim().length > 0) {
|
|
746
|
+
await copyRawDirectory(resolveSourceSubpath(sourceDir, assetsPath), path.join(tempRoot, 'assets'));
|
|
683
747
|
}
|
|
684
748
|
return {
|
|
685
|
-
assetsDir: fs.existsSync(path.join(tempRoot,
|
|
749
|
+
assetsDir: fs.existsSync(path.join(tempRoot, 'assets'))
|
|
750
|
+
? path.join(tempRoot, 'assets')
|
|
751
|
+
: undefined,
|
|
686
752
|
blockDir,
|
|
687
753
|
cleanup,
|
|
688
754
|
rootDir: tempRoot,
|
|
@@ -696,22 +762,23 @@ async function renderCreateBlockExternalTemplate(sourceDir, context, requestedVa
|
|
|
696
762
|
}
|
|
697
763
|
}
|
|
698
764
|
async function resolveGitHubTemplateSource(locator) {
|
|
699
|
-
const remoteRoot = await fsp.mkdtemp(path.join(os.tmpdir(),
|
|
765
|
+
const remoteRoot = await fsp.mkdtemp(path.join(os.tmpdir(), 'wp-typia-template-source-'));
|
|
700
766
|
const cleanup = async () => {
|
|
701
767
|
await fsp.rm(remoteRoot, { force: true, recursive: true });
|
|
702
768
|
};
|
|
703
|
-
const checkoutDir = path.join(remoteRoot,
|
|
769
|
+
const checkoutDir = path.join(remoteRoot, 'source');
|
|
704
770
|
try {
|
|
705
|
-
const args = [
|
|
771
|
+
const args = ['clone', '--depth', '1'];
|
|
706
772
|
if (locator.ref) {
|
|
707
|
-
args.push(
|
|
773
|
+
args.push('--branch', locator.ref);
|
|
708
774
|
}
|
|
709
775
|
args.push(`https://github.com/${locator.owner}/${locator.repo}.git`, checkoutDir);
|
|
710
|
-
execFileSync(
|
|
776
|
+
execFileSync('git', args, { stdio: 'ignore' });
|
|
711
777
|
const sourceDir = path.resolve(checkoutDir, locator.sourcePath);
|
|
712
778
|
const relativeSourceDir = path.relative(checkoutDir, sourceDir);
|
|
713
|
-
if (relativeSourceDir.startsWith(
|
|
714
|
-
|
|
779
|
+
if (relativeSourceDir.startsWith('..') ||
|
|
780
|
+
path.isAbsolute(relativeSourceDir)) {
|
|
781
|
+
throw new Error('GitHub template path must stay within the cloned repository.');
|
|
715
782
|
}
|
|
716
783
|
if (!fs.existsSync(sourceDir)) {
|
|
717
784
|
throw new Error(`GitHub template path does not exist: ${locator.sourcePath}`);
|
|
@@ -728,8 +795,16 @@ async function resolveGitHubTemplateSource(locator) {
|
|
|
728
795
|
throw error;
|
|
729
796
|
}
|
|
730
797
|
}
|
|
731
|
-
|
|
732
|
-
|
|
798
|
+
/**
|
|
799
|
+
* Resolves a template locator into a local seed source directory.
|
|
800
|
+
*
|
|
801
|
+
* @param locator Remote template locator describing a local path, GitHub source, or npm package.
|
|
802
|
+
* @param cwd Current working directory used to resolve local template paths.
|
|
803
|
+
* @returns A local seed source containing the resolved root and block directory, plus optional cleanup.
|
|
804
|
+
* @throws When the locator is invalid, the source cannot be fetched, or filesystem validation fails.
|
|
805
|
+
*/
|
|
806
|
+
export async function resolveTemplateSeed(locator, cwd) {
|
|
807
|
+
if (locator.kind === 'path') {
|
|
733
808
|
const sourceDir = path.resolve(cwd, locator.templatePath);
|
|
734
809
|
if (!fs.existsSync(sourceDir)) {
|
|
735
810
|
throw new Error(`Template path does not exist: ${sourceDir}`);
|
|
@@ -740,7 +815,7 @@ async function resolveTemplateSeed(locator, cwd) {
|
|
|
740
815
|
rootDir: sourceDir,
|
|
741
816
|
};
|
|
742
817
|
}
|
|
743
|
-
if (locator.kind ===
|
|
818
|
+
if (locator.kind === 'github') {
|
|
744
819
|
return resolveGitHubTemplateSource(locator.locator);
|
|
745
820
|
}
|
|
746
821
|
const installedSource = resolveInstalledNpmTemplateSource(locator.locator, cwd);
|
|
@@ -756,8 +831,8 @@ export async function resolveTemplateSource(templateId, cwd, variables, variant)
|
|
|
756
831
|
throw new Error(`--variant is only supported for official external template configs. Received variant "${variant}" for built-in template "${templateId}".`);
|
|
757
832
|
}
|
|
758
833
|
return resolveBuiltInTemplateSource(templateId, {
|
|
759
|
-
persistenceEnabled: variables.compoundPersistenceEnabled ===
|
|
760
|
-
persistencePolicy: variables.persistencePolicy ===
|
|
834
|
+
persistenceEnabled: variables.compoundPersistenceEnabled === 'true',
|
|
835
|
+
persistencePolicy: variables.persistencePolicy === 'public' ? 'public' : 'authenticated',
|
|
761
836
|
});
|
|
762
837
|
}
|
|
763
838
|
const locator = parseTemplateLocator(templateId);
|
|
@@ -767,7 +842,7 @@ export async function resolveTemplateSource(templateId, cwd, variables, variant)
|
|
|
767
842
|
let normalizedSeed = null;
|
|
768
843
|
try {
|
|
769
844
|
const format = await detectTemplateSourceFormat(seed.blockDir);
|
|
770
|
-
if (format ===
|
|
845
|
+
if (format === 'wp-typia') {
|
|
771
846
|
if (variant) {
|
|
772
847
|
throw new Error(`--variant is only supported for official external template configs. Received variant "${variant}" for "${templateId}".`);
|
|
773
848
|
}
|
|
@@ -775,8 +850,8 @@ export async function resolveTemplateSource(templateId, cwd, variables, variant)
|
|
|
775
850
|
return {
|
|
776
851
|
id: templateId,
|
|
777
852
|
defaultCategory: getDefaultCategory(seed.blockDir),
|
|
778
|
-
description:
|
|
779
|
-
features: [
|
|
853
|
+
description: 'A remote wp-typia template source',
|
|
854
|
+
features: ['Remote source', 'wp-typia format'],
|
|
780
855
|
format,
|
|
781
856
|
isOfficialWorkspaceTemplate,
|
|
782
857
|
templateDir: normalizedSeed.blockDir,
|
|
@@ -784,14 +859,14 @@ export async function resolveTemplateSource(templateId, cwd, variables, variant)
|
|
|
784
859
|
};
|
|
785
860
|
}
|
|
786
861
|
normalizedSeed =
|
|
787
|
-
format ===
|
|
862
|
+
format === 'create-block-external'
|
|
788
863
|
? await renderCreateBlockExternalTemplate(seed.blockDir, context, variant)
|
|
789
864
|
: variant
|
|
790
865
|
? (() => {
|
|
791
866
|
throw new Error(`--variant is only supported for official external template configs. Received variant "${variant}" for "${templateId}".`);
|
|
792
867
|
})()
|
|
793
868
|
: seed;
|
|
794
|
-
if (format ===
|
|
869
|
+
if (format === 'create-block-external') {
|
|
795
870
|
const normalized = await normalizeCreateBlockSubset(normalizedSeed, context);
|
|
796
871
|
return {
|
|
797
872
|
...normalized,
|
|
@@ -799,10 +874,14 @@ export async function resolveTemplateSource(templateId, cwd, variables, variant)
|
|
|
799
874
|
await normalized.cleanup?.();
|
|
800
875
|
await seed.cleanup?.();
|
|
801
876
|
},
|
|
802
|
-
description:
|
|
803
|
-
features: [
|
|
877
|
+
description: 'A wp-typia scaffold normalized from an official create-block external template',
|
|
878
|
+
features: [
|
|
879
|
+
'Remote source',
|
|
880
|
+
'official external template',
|
|
881
|
+
'Typia metadata pipeline',
|
|
882
|
+
],
|
|
804
883
|
format,
|
|
805
|
-
id:
|
|
884
|
+
id: 'remote:create-block-external',
|
|
806
885
|
isOfficialWorkspaceTemplate,
|
|
807
886
|
selectedVariant: normalizedSeed.selectedVariant ?? null,
|
|
808
887
|
warnings: normalizedSeed.warnings ?? [],
|