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