@wp-typia/project-tools 0.22.8 → 0.22.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/dist/runtime/ai-artifacts.js +3 -4
- package/dist/runtime/ai-feature-artifacts.js +2 -4
- package/dist/runtime/cli-add-block.js +12 -1
- package/dist/runtime/cli-add-filesystem.js +2 -15
- package/dist/runtime/cli-add-types.d.ts +8 -0
- package/dist/runtime/cli-add-types.js +13 -0
- package/dist/runtime/cli-add.d.ts +3 -2
- package/dist/runtime/cli-add.js +2 -2
- package/dist/runtime/cli-core.d.ts +4 -2
- package/dist/runtime/cli-core.js +3 -2
- package/dist/runtime/cli-doctor-workspace-shared.js +3 -0
- package/dist/runtime/cli-doctor-workspace.d.ts +1 -1
- package/dist/runtime/cli-doctor-workspace.js +8 -3
- package/dist/runtime/cli-doctor.js +1 -1
- package/dist/runtime/cli-scaffold.js +3 -3
- package/dist/runtime/create-template-validation.js +2 -28
- package/dist/runtime/fs-async.d.ts +7 -0
- package/dist/runtime/fs-async.js +11 -2
- package/dist/runtime/id-suggestions.d.ts +21 -0
- package/dist/runtime/id-suggestions.js +48 -0
- package/dist/runtime/index.d.ts +5 -2
- package/dist/runtime/index.js +3 -1
- package/dist/runtime/migration-maintenance-verify.js +5 -0
- package/dist/runtime/migration-render-generated.js +4 -0
- package/dist/runtime/package-versions.js +3 -7
- package/dist/runtime/scaffold-repository-reference.js +3 -7
- package/dist/runtime/scaffold.js +3 -3
- package/dist/runtime/template-builtins.js +11 -8
- package/dist/runtime/template-layers.js +2 -2
- package/dist/runtime/template-source-external.d.ts +3 -0
- package/dist/runtime/template-source-external.js +5 -2
- package/dist/runtime/template-source-remote.d.ts +6 -0
- package/dist/runtime/template-source-remote.js +8 -2
- package/dist/runtime/template-source-seeds.d.ts +13 -0
- package/dist/runtime/template-source-seeds.js +36 -8
- package/dist/runtime/template-source.js +2 -2
- package/dist/runtime/typia-llm.js +3 -4
- package/dist/runtime/workspace-inventory-mutations.d.ts +24 -0
- package/dist/runtime/workspace-inventory-mutations.js +132 -0
- package/dist/runtime/workspace-inventory-parser.d.ts +52 -0
- package/dist/runtime/workspace-inventory-parser.js +511 -0
- package/dist/runtime/workspace-inventory-read.d.ts +44 -0
- package/dist/runtime/workspace-inventory-read.js +91 -0
- package/dist/runtime/workspace-inventory-templates.d.ts +35 -0
- package/dist/runtime/workspace-inventory-templates.js +198 -0
- package/dist/runtime/workspace-inventory-types.d.ts +171 -0
- package/dist/runtime/workspace-inventory-types.js +1 -0
- package/dist/runtime/workspace-inventory.d.ts +5 -238
- package/dist/runtime/workspace-inventory.js +4 -901
- package/package.json +7 -2
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { getOptionalNodeErrorCode } from "./fs-async.js";
|
|
4
5
|
import { PROJECT_TOOLS_PACKAGE_ROOT } from "./template-registry.js";
|
|
5
6
|
const require = createRequire(import.meta.url);
|
|
6
7
|
/**
|
|
@@ -8,17 +9,12 @@ const require = createRequire(import.meta.url);
|
|
|
8
9
|
* can be resolved from the current runtime package manifests.
|
|
9
10
|
*/
|
|
10
11
|
export const DEFAULT_SCAFFOLD_REPOSITORY_REFERENCE = "imjlk/wp-typia";
|
|
11
|
-
function getErrorCode(error) {
|
|
12
|
-
return typeof error === "object" && error !== null && "code" in error
|
|
13
|
-
? String(error.code)
|
|
14
|
-
: undefined;
|
|
15
|
-
}
|
|
16
12
|
function readRepositoryPackageManifest(packageJsonPath) {
|
|
17
13
|
try {
|
|
18
14
|
return JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
19
15
|
}
|
|
20
16
|
catch (error) {
|
|
21
|
-
if (
|
|
17
|
+
if (getOptionalNodeErrorCode(error) === "ENOENT") {
|
|
22
18
|
return null;
|
|
23
19
|
}
|
|
24
20
|
throw error;
|
|
@@ -29,7 +25,7 @@ function resolveInstalledPackageManifestPath(packageName) {
|
|
|
29
25
|
return require.resolve(`${packageName}/package.json`);
|
|
30
26
|
}
|
|
31
27
|
catch (error) {
|
|
32
|
-
if (
|
|
28
|
+
if (getOptionalNodeErrorCode(error) === "MODULE_NOT_FOUND") {
|
|
33
29
|
return null;
|
|
34
30
|
}
|
|
35
31
|
throw error;
|
package/dist/runtime/scaffold.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
1
|
import { promises as fsp } from "node:fs";
|
|
3
2
|
import path from "node:path";
|
|
4
3
|
import { getPackageManager } from "./package-managers.js";
|
|
@@ -14,6 +13,7 @@ import { BlockGeneratorService, } from "./block-generator-service.js";
|
|
|
14
13
|
import { getTemplateVariables } from "./scaffold-template-variables.js";
|
|
15
14
|
import { getScaffoldTemplateVariableGroups } from "./scaffold-template-variable-groups.js";
|
|
16
15
|
import { assertExternalLayerCompositionOptions, } from "./cli-validation.js";
|
|
16
|
+
import { pathExists } from "./fs-async.js";
|
|
17
17
|
export const DATA_STORAGE_MODES = ["post-meta", "custom-table"];
|
|
18
18
|
export const PERSISTENCE_POLICIES = ["authenticated", "public"];
|
|
19
19
|
export { buildBlockCssClassName } from "./scaffold-identifiers.js";
|
|
@@ -149,7 +149,7 @@ export async function scaffoldProject({ projectDir, templateId, answers, alterna
|
|
|
149
149
|
title: "Finalizing scaffold output",
|
|
150
150
|
});
|
|
151
151
|
const readmePath = path.join(projectDir, "README.md");
|
|
152
|
-
if (!
|
|
152
|
+
if (!(await pathExists(readmePath))) {
|
|
153
153
|
await fsp.writeFile(readmePath, buildReadme(resolvedTemplateId, variables, resolvedPackageManager, {
|
|
154
154
|
withMigrationUi: isBuiltInTemplate || isWorkspace ? withMigrationUi : false,
|
|
155
155
|
withTestPreset: isBuiltInTemplate ? withTestPreset : false,
|
|
@@ -157,7 +157,7 @@ export async function scaffoldProject({ projectDir, templateId, answers, alterna
|
|
|
157
157
|
}), "utf8");
|
|
158
158
|
}
|
|
159
159
|
const gitignorePath = path.join(projectDir, ".gitignore");
|
|
160
|
-
const existingGitignore =
|
|
160
|
+
const existingGitignore = (await pathExists(gitignorePath))
|
|
161
161
|
? await fsp.readFile(gitignorePath, "utf8")
|
|
162
162
|
: "";
|
|
163
163
|
await fsp.writeFile(gitignorePath, mergeTextLines(buildGitignore(), existingGitignore), "utf8");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
1
|
import path from "node:path";
|
|
3
2
|
import { promises as fsp } from "node:fs";
|
|
3
|
+
import { pathExists } from "./fs-async.js";
|
|
4
4
|
import { createManagedTempRoot } from "./temp-roots.js";
|
|
5
5
|
import { getTemplateById, SHARED_BASE_TEMPLATE_ROOT, SHARED_COMPOUND_TEMPLATE_ROOT, SHARED_MIGRATION_UI_TEMPLATE_ROOT, SHARED_PERSISTENCE_TEMPLATE_ROOT, SHARED_PRESET_TEMPLATE_ROOT, SHARED_REST_HELPER_TEMPLATE_ROOT, SHARED_WORKSPACE_TEMPLATE_ROOT, } from "./template-registry.js";
|
|
6
6
|
const BUILT_IN_SHARED_TEMPLATE_LAYERS = Object.freeze([
|
|
@@ -138,20 +138,23 @@ export function isOmittableBuiltInTemplateLayerDir(templateId, layerDir) {
|
|
|
138
138
|
return (OMITTABLE_BUILT_IN_OVERLAY_TEMPLATE_IDS.has(templateId) &&
|
|
139
139
|
layerDir === getTemplateById(templateId).templateDir);
|
|
140
140
|
}
|
|
141
|
-
function resolveMaterializedTemplateLayerDirs(templateId, layerDirs) {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
141
|
+
async function resolveMaterializedTemplateLayerDirs(templateId, layerDirs) {
|
|
142
|
+
const materializedLayerDirs = [];
|
|
143
|
+
for (const layerDir of layerDirs) {
|
|
144
|
+
if (await pathExists(layerDir)) {
|
|
145
|
+
materializedLayerDirs.push(layerDir);
|
|
146
|
+
continue;
|
|
145
147
|
}
|
|
146
148
|
if (isOmittableBuiltInTemplateLayerDir(templateId, layerDir)) {
|
|
147
|
-
|
|
149
|
+
continue;
|
|
148
150
|
}
|
|
149
151
|
throw new Error(`Built-in template layer is missing: ${layerDir}`);
|
|
150
|
-
}
|
|
152
|
+
}
|
|
153
|
+
return materializedLayerDirs;
|
|
151
154
|
}
|
|
152
155
|
async function materializeBuiltInTemplateSource(templateId, layerDirs) {
|
|
153
156
|
const template = getTemplateById(templateId);
|
|
154
|
-
const materializedLayerDirs = resolveMaterializedTemplateLayerDirs(templateId, layerDirs);
|
|
157
|
+
const materializedLayerDirs = await resolveMaterializedTemplateLayerDirs(templateId, layerDirs);
|
|
155
158
|
const { path: tempRoot, cleanup } = await createManagedTempRoot("wp-typia-template-");
|
|
156
159
|
const templateDir = path.join(tempRoot, templateId);
|
|
157
160
|
try {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
1
|
import path from "node:path";
|
|
3
2
|
import { promises as fsp } from "node:fs";
|
|
3
|
+
import { pathExists } from "./fs-async.js";
|
|
4
4
|
import { isPlainObject } from "./object-utils.js";
|
|
5
5
|
import { getBuiltInSharedTemplateLayerDir, isBuiltInSharedTemplateLayerId, } from "./template-builtins.js";
|
|
6
6
|
import { listInterpolatedDirectoryOutputs } from "./template-render.js";
|
|
@@ -56,7 +56,7 @@ function parseLayerDefinition(layerId, value) {
|
|
|
56
56
|
}
|
|
57
57
|
export async function loadExternalTemplateLayerManifest(sourceRoot) {
|
|
58
58
|
const manifestPath = path.join(sourceRoot, TEMPLATE_LAYER_MANIFEST_FILENAME);
|
|
59
|
-
if (!
|
|
59
|
+
if (!(await pathExists(manifestPath))) {
|
|
60
60
|
return null;
|
|
61
61
|
}
|
|
62
62
|
const raw = JSON.parse(await fsp.readFile(manifestPath, "utf8"));
|
|
@@ -7,6 +7,9 @@ export declare const EXTERNAL_TEMPLATE_TRUST_WARNING = "External template config
|
|
|
7
7
|
/**
|
|
8
8
|
* Search a source directory for the first supported external template entry.
|
|
9
9
|
*
|
|
10
|
+
* @deprecated Use `findExternalTemplateEntry()` from async template-source
|
|
11
|
+
* paths. This synchronous helper remains only for compatibility callers.
|
|
12
|
+
*
|
|
10
13
|
* @param sourceDir Directory that may contain an external template config entry.
|
|
11
14
|
* @returns The first matching entry path, or null when no supported entry exists.
|
|
12
15
|
*/
|
|
@@ -33,6 +33,9 @@ function resolveSourceSubpath(sourceDir, relativePath) {
|
|
|
33
33
|
/**
|
|
34
34
|
* Search a source directory for the first supported external template entry.
|
|
35
35
|
*
|
|
36
|
+
* @deprecated Use `findExternalTemplateEntry()` from async template-source
|
|
37
|
+
* paths. This synchronous helper remains only for compatibility callers.
|
|
38
|
+
*
|
|
36
39
|
* @param sourceDir Directory that may contain an external template config entry.
|
|
37
40
|
* @returns The first matching entry path, or null when no supported entry exists.
|
|
38
41
|
*/
|
|
@@ -205,11 +208,11 @@ export async function renderCreateBlockExternalTemplate(sourceDir, context, requ
|
|
|
205
208
|
const view = await buildExternalTemplateView(context, config, selectedVariant, variantConfig);
|
|
206
209
|
const blockTemplateDir = resolveSourceSubpath(sourceDir, templatePath);
|
|
207
210
|
await copyRenderedDirectory(blockTemplateDir, blockDir, view, {
|
|
208
|
-
filter: (sourcePath, _destinationPath, entry) => {
|
|
211
|
+
filter: async (sourcePath, _destinationPath, entry) => {
|
|
209
212
|
const mustacheVariantPath = path.join(path.dirname(sourcePath), `${entry.name}.mustache`);
|
|
210
213
|
return !(entry.isFile() &&
|
|
211
214
|
(entry.name === 'package.json' || entry.name === 'README.md') &&
|
|
212
|
-
|
|
215
|
+
(await pathExists(mustacheVariantPath)));
|
|
213
216
|
},
|
|
214
217
|
});
|
|
215
218
|
const assetsPath = typeof variantConfig.assetsPath === 'string'
|
|
@@ -2,6 +2,9 @@ import type { ResolvedTemplateSource, SeedSource, TemplateVariableContext } from
|
|
|
2
2
|
/**
|
|
3
3
|
* Read a remote block source and return its default block category.
|
|
4
4
|
*
|
|
5
|
+
* @deprecated Use `getDefaultCategoryAsync()` from async template-source paths.
|
|
6
|
+
* This synchronous helper remains only for compatibility callers.
|
|
7
|
+
*
|
|
5
8
|
* @param sourceDir Block source directory that may contain a block.json file.
|
|
6
9
|
* @returns The declared block category, or "widgets" when detection fails.
|
|
7
10
|
*/
|
|
@@ -16,6 +19,9 @@ export declare function getDefaultCategoryAsync(sourceDir: string): Promise<stri
|
|
|
16
19
|
/**
|
|
17
20
|
* Read `wpTypia.projectType` from a rendered or source template package
|
|
18
21
|
* manifest and return it when present.
|
|
22
|
+
*
|
|
23
|
+
* @deprecated Use `getTemplateProjectTypeAsync()` from async template-source
|
|
24
|
+
* paths. This synchronous helper remains only for compatibility callers.
|
|
19
25
|
*/
|
|
20
26
|
export declare function getTemplateProjectType(sourceDir: string): string | null;
|
|
21
27
|
/**
|
|
@@ -59,6 +59,9 @@ async function readRemoteBlockJsonAsync(blockDir) {
|
|
|
59
59
|
/**
|
|
60
60
|
* Read a remote block source and return its default block category.
|
|
61
61
|
*
|
|
62
|
+
* @deprecated Use `getDefaultCategoryAsync()` from async template-source paths.
|
|
63
|
+
* This synchronous helper remains only for compatibility callers.
|
|
64
|
+
*
|
|
62
65
|
* @param sourceDir Block source directory that may contain a block.json file.
|
|
63
66
|
* @returns The declared block category, or "widgets" when detection fails.
|
|
64
67
|
*/
|
|
@@ -139,6 +142,9 @@ async function readTemplatePackageJsonAsync(sourceDir) {
|
|
|
139
142
|
/**
|
|
140
143
|
* Read `wpTypia.projectType` from a rendered or source template package
|
|
141
144
|
* manifest and return it when present.
|
|
145
|
+
*
|
|
146
|
+
* @deprecated Use `getTemplateProjectTypeAsync()` from async template-source
|
|
147
|
+
* paths. This synchronous helper remains only for compatibility callers.
|
|
142
148
|
*/
|
|
143
149
|
export function getTemplateProjectType(sourceDir) {
|
|
144
150
|
const packageJsonEntry = readTemplatePackageJson(sourceDir);
|
|
@@ -185,11 +191,11 @@ export async function normalizeWpTypiaTemplateSeed(seed) {
|
|
|
185
191
|
const normalizedDir = path.join(tempRoot, 'template');
|
|
186
192
|
try {
|
|
187
193
|
await copyRawDirectory(seed.blockDir, normalizedDir, {
|
|
188
|
-
filter: (sourcePath, _targetPath, entry) => {
|
|
194
|
+
filter: async (sourcePath, _targetPath, entry) => {
|
|
189
195
|
const mustacheVariantPath = path.join(path.dirname(sourcePath), `${entry.name}.mustache`);
|
|
190
196
|
return !(entry.isFile() &&
|
|
191
197
|
(entry.name === 'package.json' || entry.name === 'README.md') &&
|
|
192
|
-
|
|
198
|
+
(await pathExists(mustacheVariantPath)));
|
|
193
199
|
},
|
|
194
200
|
});
|
|
195
201
|
if (seed.assetsDir && (await pathExists(seed.assetsDir))) {
|
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
import type { RemoteTemplateLocator, SeedSource } from './template-source-contracts.js';
|
|
2
|
+
/**
|
|
3
|
+
* Synchronously identify the bundled workspace template seed.
|
|
4
|
+
*
|
|
5
|
+
* This remains sync-only for compatibility callers. Async template resolution
|
|
6
|
+
* paths should use `isOfficialWorkspaceTemplateSeedAsync()`.
|
|
7
|
+
*/
|
|
2
8
|
export declare function isOfficialWorkspaceTemplateSeed(seed: SeedSource): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Asynchronously identify the bundled workspace template seed.
|
|
11
|
+
*
|
|
12
|
+
* @param seed Seed source to inspect.
|
|
13
|
+
* @returns Whether the seed resolves to the official workspace template package.
|
|
14
|
+
*/
|
|
15
|
+
export declare function isOfficialWorkspaceTemplateSeedAsync(seed: SeedSource): Promise<boolean>;
|
|
3
16
|
export declare function assertNoSymlinks(sourceDir: string): Promise<void>;
|
|
4
17
|
/**
|
|
5
18
|
* Resolves a template locator into a local seed source directory.
|
|
@@ -8,6 +8,7 @@ import { x as extractTarball } from 'tar';
|
|
|
8
8
|
import { createExternalTemplateTimeoutError, fetchWithExternalTemplateTimeout, getExternalTemplateMetadataMaxBytes, getExternalTemplateTarballMaxBytes, getExternalTemplateTimeoutMs, readBufferResponseWithLimit, readJsonResponseWithLimit, } from './external-template-guards.js';
|
|
9
9
|
import { findReusableExternalTemplateSourceCache, isExternalTemplateCacheEnabled, resolveExternalTemplateSourceCache, } from './template-source-cache.js';
|
|
10
10
|
import { CLI_DIAGNOSTIC_CODES, createCliDiagnosticCodeError, } from './cli-diagnostics.js';
|
|
11
|
+
import { pathExists } from './fs-async.js';
|
|
11
12
|
import { OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE, OFFICIAL_WORKSPACE_TEMPLATE_ALIAS, PROJECT_TOOLS_PACKAGE_ROOT, TEMPLATE_IDS, } from './template-registry.js';
|
|
12
13
|
import { isPlainObject } from './object-utils.js';
|
|
13
14
|
import { createManagedTempRoot } from './temp-roots.js';
|
|
@@ -184,7 +185,9 @@ async function fetchNpmTemplateSource(locator) {
|
|
|
184
185
|
* Resolve a locally installed npm template package from the caller workspace.
|
|
185
186
|
*
|
|
186
187
|
* Bare package ids are preferred here so monorepo and offline workflows can
|
|
187
|
-
* use an already-installed template without forcing a registry fetch.
|
|
188
|
+
* use an already-installed template without forcing a registry fetch. This
|
|
189
|
+
* path intentionally keeps Node's synchronous `require.resolve` semantics
|
|
190
|
+
* because the module resolver itself has no async equivalent.
|
|
188
191
|
*/
|
|
189
192
|
function resolveInstalledNpmTemplateSource(locator, cwd) {
|
|
190
193
|
if (locator.rawSpec !== '' && locator.rawSpec !== '*') {
|
|
@@ -244,6 +247,12 @@ function resolveInstalledNpmTemplateSource(locator, cwd) {
|
|
|
244
247
|
throw error;
|
|
245
248
|
}
|
|
246
249
|
}
|
|
250
|
+
/**
|
|
251
|
+
* Synchronously identify the bundled workspace template seed.
|
|
252
|
+
*
|
|
253
|
+
* This remains sync-only for compatibility callers. Async template resolution
|
|
254
|
+
* paths should use `isOfficialWorkspaceTemplateSeedAsync()`.
|
|
255
|
+
*/
|
|
247
256
|
export function isOfficialWorkspaceTemplateSeed(seed) {
|
|
248
257
|
const packageJsonPath = path.join(seed.rootDir, 'package.json');
|
|
249
258
|
if (!fs.existsSync(packageJsonPath)) {
|
|
@@ -257,6 +266,25 @@ export function isOfficialWorkspaceTemplateSeed(seed) {
|
|
|
257
266
|
return false;
|
|
258
267
|
}
|
|
259
268
|
}
|
|
269
|
+
/**
|
|
270
|
+
* Asynchronously identify the bundled workspace template seed.
|
|
271
|
+
*
|
|
272
|
+
* @param seed Seed source to inspect.
|
|
273
|
+
* @returns Whether the seed resolves to the official workspace template package.
|
|
274
|
+
*/
|
|
275
|
+
export async function isOfficialWorkspaceTemplateSeedAsync(seed) {
|
|
276
|
+
const packageJsonPath = path.join(seed.rootDir, 'package.json');
|
|
277
|
+
if (!(await pathExists(packageJsonPath))) {
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
try {
|
|
281
|
+
const packageJson = JSON.parse(await fsp.readFile(packageJsonPath, 'utf8'));
|
|
282
|
+
return packageJson.name === OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE;
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
260
288
|
export async function assertNoSymlinks(sourceDir) {
|
|
261
289
|
const stats = await fsp.lstat(sourceDir);
|
|
262
290
|
if (stats.isSymbolicLink()) {
|
|
@@ -300,13 +328,13 @@ function runGitTemplateCommand(args, label, options = {}) {
|
|
|
300
328
|
function getGitHubTemplateRepositoryUrl(locator) {
|
|
301
329
|
return `https://github.com/${locator.owner}/${locator.repo}.git`;
|
|
302
330
|
}
|
|
303
|
-
function resolveGitHubTemplateDirectory(checkoutDir, locator) {
|
|
331
|
+
async function resolveGitHubTemplateDirectory(checkoutDir, locator) {
|
|
304
332
|
const sourceDir = path.resolve(checkoutDir, locator.sourcePath);
|
|
305
333
|
const relativeSourceDir = path.relative(checkoutDir, sourceDir);
|
|
306
334
|
if (relativeSourceDir.startsWith('..') || path.isAbsolute(relativeSourceDir)) {
|
|
307
335
|
throw new Error('GitHub template path must stay within the cloned repository.');
|
|
308
336
|
}
|
|
309
|
-
if (!
|
|
337
|
+
if (!(await pathExists(sourceDir))) {
|
|
310
338
|
throw new Error(`GitHub template path does not exist: ${locator.sourcePath}`);
|
|
311
339
|
}
|
|
312
340
|
return sourceDir;
|
|
@@ -438,7 +466,7 @@ async function reuseGitHubTemplateCacheByMetadata(locator) {
|
|
|
438
466
|
if (!cachedSource) {
|
|
439
467
|
return null;
|
|
440
468
|
}
|
|
441
|
-
const sourceDir = resolveGitHubTemplateDirectory(cachedSource.sourceDir, locator);
|
|
469
|
+
const sourceDir = await resolveGitHubTemplateDirectory(cachedSource.sourceDir, locator);
|
|
442
470
|
await assertNoSymlinks(sourceDir);
|
|
443
471
|
return {
|
|
444
472
|
blockDir: sourceDir,
|
|
@@ -482,12 +510,12 @@ async function resolveGitHubTemplateSource(locator) {
|
|
|
482
510
|
namespace: 'github',
|
|
483
511
|
}, async (checkoutDir) => {
|
|
484
512
|
cloneGitHubTemplateSource(locator, checkoutDir);
|
|
485
|
-
const sourceDir = resolveGitHubTemplateDirectory(checkoutDir, locator);
|
|
513
|
+
const sourceDir = await resolveGitHubTemplateDirectory(checkoutDir, locator);
|
|
486
514
|
await assertNoSymlinks(sourceDir);
|
|
487
515
|
pinGitHubTemplateCacheRevision(locator, checkoutDir, resolvedCacheRevision);
|
|
488
516
|
});
|
|
489
517
|
if (cachedSource) {
|
|
490
|
-
const sourceDir = resolveGitHubTemplateDirectory(cachedSource.sourceDir, locator);
|
|
518
|
+
const sourceDir = await resolveGitHubTemplateDirectory(cachedSource.sourceDir, locator);
|
|
491
519
|
await assertNoSymlinks(sourceDir);
|
|
492
520
|
return {
|
|
493
521
|
blockDir: sourceDir,
|
|
@@ -506,7 +534,7 @@ async function resolveGitHubTemplateSource(locator) {
|
|
|
506
534
|
const checkoutDir = path.join(remoteRoot, 'source');
|
|
507
535
|
try {
|
|
508
536
|
cloneGitHubTemplateSource(locator, checkoutDir);
|
|
509
|
-
const sourceDir = resolveGitHubTemplateDirectory(checkoutDir, locator);
|
|
537
|
+
const sourceDir = await resolveGitHubTemplateDirectory(checkoutDir, locator);
|
|
510
538
|
await assertNoSymlinks(sourceDir);
|
|
511
539
|
return {
|
|
512
540
|
blockDir: sourceDir,
|
|
@@ -530,7 +558,7 @@ async function resolveGitHubTemplateSource(locator) {
|
|
|
530
558
|
export async function resolveTemplateSeed(locator, cwd) {
|
|
531
559
|
if (locator.kind === 'path') {
|
|
532
560
|
const sourceDir = path.resolve(cwd, locator.templatePath);
|
|
533
|
-
if (!
|
|
561
|
+
if (!(await pathExists(sourceDir))) {
|
|
534
562
|
throw new Error(`Template path does not exist: ${sourceDir}`);
|
|
535
563
|
}
|
|
536
564
|
await assertNoSymlinks(sourceDir);
|
|
@@ -2,7 +2,7 @@ import { OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE, isBuiltInTemplateId, } from './tem
|
|
|
2
2
|
import { resolveBuiltInTemplateSource } from './template-builtins.js';
|
|
3
3
|
import { parseTemplateLocator, } from './template-source-locators.js';
|
|
4
4
|
import { detectTemplateSourceFormat, getTemplateProjectTypeAsync, getDefaultCategoryAsync, getTemplateVariableContext, normalizeCreateBlockSubset, normalizeWpTypiaTemplateSeed, renderCreateBlockExternalTemplate, } from './template-source-normalization.js';
|
|
5
|
-
import {
|
|
5
|
+
import { isOfficialWorkspaceTemplateSeedAsync, resolveTemplateSeed, } from './template-source-seeds.js';
|
|
6
6
|
import { assertBuiltInTemplateVariantAllowed, } from './cli-validation.js';
|
|
7
7
|
import { getScaffoldTemplateVariableGroups } from './scaffold-template-variable-groups.js';
|
|
8
8
|
export { parseGitHubTemplateLocator, parseNpmTemplateLocator, parseTemplateLocator, } from './template-source-locators.js';
|
|
@@ -27,7 +27,7 @@ export async function resolveTemplateSource(templateId, cwd, variables, variant)
|
|
|
27
27
|
const context = getTemplateVariableContext(variables);
|
|
28
28
|
const seed = await resolveTemplateSeed(locator, cwd);
|
|
29
29
|
const isOfficialWorkspaceTemplate = templateId === OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE ||
|
|
30
|
-
|
|
30
|
+
(await isOfficialWorkspaceTemplateSeedAsync(seed));
|
|
31
31
|
let normalizedSeed = null;
|
|
32
32
|
try {
|
|
33
33
|
const format = await detectTemplateSourceFormat(seed.blockDir);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { getOptionalNodeErrorCode, isFileNotFoundError, } from './fs-async.js';
|
|
3
4
|
import { cloneJsonValue } from './json-utils.js';
|
|
4
5
|
import { normalizeEndpointAuthDefinition, } from './schema-core.js';
|
|
5
6
|
const TYPESCRIPT_RESERVED_WORDS = new Set([
|
|
@@ -433,13 +434,11 @@ async function reconcileGeneratedTypiaLlmArtifacts(artifacts, check) {
|
|
|
433
434
|
}
|
|
434
435
|
}
|
|
435
436
|
catch (error) {
|
|
436
|
-
|
|
437
|
-
? error.code
|
|
438
|
-
: undefined;
|
|
439
|
-
if (code === 'ENOENT') {
|
|
437
|
+
if (isFileNotFoundError(error)) {
|
|
440
438
|
issues.push(`- ${artifact.filePath} (missing)`);
|
|
441
439
|
continue;
|
|
442
440
|
}
|
|
441
|
+
const code = getOptionalNodeErrorCode(error);
|
|
443
442
|
issues.push(`- ${artifact.filePath} (unreadable: ${error instanceof Error ? error.message : code ?? 'unknown'})`);
|
|
444
443
|
}
|
|
445
444
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { WorkspaceInventoryUpdateOptions } from "./workspace-inventory-types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Update `scripts/block-config.ts` source text with additional inventory entries.
|
|
4
|
+
*
|
|
5
|
+
* Missing inventory sections for variations, patterns, binding sources, REST
|
|
6
|
+
* resources, workflow abilities, AI features, editor plugins, block styles, and
|
|
7
|
+
* block transforms are created
|
|
8
|
+
* automatically before new entries are appended at their marker comments.
|
|
9
|
+
* When provided, `transformSource` runs before any entries are inserted.
|
|
10
|
+
*
|
|
11
|
+
* @param source Existing `scripts/block-config.ts` source.
|
|
12
|
+
* @param options Entry lists plus an optional source transformer.
|
|
13
|
+
* @returns Updated source text with all requested inventory entries appended.
|
|
14
|
+
*/
|
|
15
|
+
export declare function updateWorkspaceInventorySource(source: string, options?: WorkspaceInventoryUpdateOptions): string;
|
|
16
|
+
/**
|
|
17
|
+
* Append new entries to the canonical workspace inventory file on disk.
|
|
18
|
+
*
|
|
19
|
+
* @param projectDir Workspace root directory.
|
|
20
|
+
* @param options Entry lists and optional source transform passed through to
|
|
21
|
+
* `updateWorkspaceInventorySource`.
|
|
22
|
+
* @returns Resolves once `scripts/block-config.ts` has been updated if needed.
|
|
23
|
+
*/
|
|
24
|
+
export declare function appendWorkspaceInventoryEntries(projectDir: string, options: Parameters<typeof updateWorkspaceInventorySource>[1]): Promise<void>;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { escapeRegex } from "./php-utils.js";
|
|
4
|
+
import { BLOCK_INVENTORY_SECTION, INVENTORY_SECTIONS, } from "./workspace-inventory-parser.js";
|
|
5
|
+
import { WORKSPACE_COMPATIBILITY_CONFIG_FIELD } from "./workspace-inventory-templates.js";
|
|
6
|
+
function ensureWorkspaceInventorySections(source) {
|
|
7
|
+
let nextSource = source.trimEnd();
|
|
8
|
+
for (const section of INVENTORY_SECTIONS) {
|
|
9
|
+
if (section.interface &&
|
|
10
|
+
!hasExportedInterface(nextSource, section.interface.name)) {
|
|
11
|
+
nextSource += section.interface.section;
|
|
12
|
+
}
|
|
13
|
+
if (section.value && !hasExportedConst(nextSource, section.value.name)) {
|
|
14
|
+
nextSource += section.value.section;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return `${nextSource}\n`;
|
|
18
|
+
}
|
|
19
|
+
function hasExportedInterface(source, interfaceName) {
|
|
20
|
+
return new RegExp(`export\\s+interface\\s+${escapeRegex(interfaceName)}\\b`, "u").test(source);
|
|
21
|
+
}
|
|
22
|
+
function hasExportedConst(source, constName) {
|
|
23
|
+
return new RegExp(`export\\s+const\\s+${escapeRegex(constName)}\\b`, "u").test(source);
|
|
24
|
+
}
|
|
25
|
+
function appendEntriesAtMarker(source, marker, entries) {
|
|
26
|
+
if (entries.length === 0) {
|
|
27
|
+
return source;
|
|
28
|
+
}
|
|
29
|
+
if (!source.includes(marker)) {
|
|
30
|
+
throw new Error(`Workspace inventory marker "${marker}" is missing in scripts/block-config.ts.`);
|
|
31
|
+
}
|
|
32
|
+
const replacement = `${entries.join("\n")}\n${marker}`;
|
|
33
|
+
return source.replace(marker, () => replacement);
|
|
34
|
+
}
|
|
35
|
+
function appendInventorySectionEntries(source, options) {
|
|
36
|
+
let nextSource = source;
|
|
37
|
+
for (const section of [BLOCK_INVENTORY_SECTION, ...INVENTORY_SECTIONS]) {
|
|
38
|
+
if (!section.append) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
nextSource = appendEntriesAtMarker(nextSource, section.append.marker, options[section.append.optionKey] ?? []);
|
|
42
|
+
}
|
|
43
|
+
return nextSource;
|
|
44
|
+
}
|
|
45
|
+
function ensureInterfaceField(source, interfaceName, fieldName, fieldSource) {
|
|
46
|
+
const interfacePattern = new RegExp(`(export\\s+interface\\s+${escapeRegex(interfaceName)}\\s*\\{\\r?\\n)([\\s\\S]*?)(\\r?\\n\\})`, "u");
|
|
47
|
+
return source.replace(interfacePattern, (match, start, body, end) => {
|
|
48
|
+
if (new RegExp(`^[ \t]*${escapeRegex(fieldName)}\\??:`, "mu").test(body)) {
|
|
49
|
+
return match;
|
|
50
|
+
}
|
|
51
|
+
const lineEnding = start.endsWith("\r\n") ? "\r\n" : "\n";
|
|
52
|
+
const formattedFieldSource = `${fieldSource
|
|
53
|
+
.replace(/\r?\n$/u, "")
|
|
54
|
+
.split("\n")
|
|
55
|
+
.join(lineEnding)}${lineEnding}`;
|
|
56
|
+
const memberPattern = /^[ \t]*([A-Za-z_$][\w$]*)\??:/gmu;
|
|
57
|
+
for (const member of body.matchAll(memberPattern)) {
|
|
58
|
+
const memberIndex = member.index;
|
|
59
|
+
const memberName = member[1];
|
|
60
|
+
if (memberIndex === undefined || !memberName) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (memberName.localeCompare(fieldName) > 0) {
|
|
64
|
+
return `${start}${body.slice(0, memberIndex)}${formattedFieldSource}${body.slice(memberIndex)}${end}`;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return `${start}${body}${body.length > 0 && !body.endsWith(lineEnding) ? lineEnding : ""}${formattedFieldSource}${end}`;
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
function normalizeInterfaceFieldBlock(source, interfaceName, fieldName, fieldSource, requiredFragments) {
|
|
71
|
+
const interfacePattern = new RegExp(`(export\\s+interface\\s+${escapeRegex(interfaceName)}\\s*\\{\\r?\\n)([\\s\\S]*?)(\\r?\\n\\})`, "u");
|
|
72
|
+
return source.replace(interfacePattern, (match, start, body, end) => {
|
|
73
|
+
const fieldPattern = new RegExp(`(^([ \\t]*)${escapeRegex(fieldName)}\\??:\\s*\\{[ \\t]*\\r?\\n)([\\s\\S]*?)(^\\2\\};\\r?\\n?)`, "mu");
|
|
74
|
+
const fieldMatch = fieldPattern.exec(body);
|
|
75
|
+
if (!fieldMatch) {
|
|
76
|
+
return match;
|
|
77
|
+
}
|
|
78
|
+
const existingFieldSource = fieldMatch[0];
|
|
79
|
+
if (requiredFragments.every((fragment) => existingFieldSource.includes(fragment))) {
|
|
80
|
+
return match;
|
|
81
|
+
}
|
|
82
|
+
const lineEnding = start.endsWith("\r\n") ? "\r\n" : "\n";
|
|
83
|
+
const formattedFieldSource = `${fieldSource
|
|
84
|
+
.replace(/\r?\n$/u, "")
|
|
85
|
+
.split("\n")
|
|
86
|
+
.join(lineEnding)}${lineEnding}`;
|
|
87
|
+
return `${start}${body.slice(0, fieldMatch.index)}${formattedFieldSource}${body.slice(fieldMatch.index + existingFieldSource.length)}${end}`;
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Update `scripts/block-config.ts` source text with additional inventory entries.
|
|
92
|
+
*
|
|
93
|
+
* Missing inventory sections for variations, patterns, binding sources, REST
|
|
94
|
+
* resources, workflow abilities, AI features, editor plugins, block styles, and
|
|
95
|
+
* block transforms are created
|
|
96
|
+
* automatically before new entries are appended at their marker comments.
|
|
97
|
+
* When provided, `transformSource` runs before any entries are inserted.
|
|
98
|
+
*
|
|
99
|
+
* @param source Existing `scripts/block-config.ts` source.
|
|
100
|
+
* @param options Entry lists plus an optional source transformer.
|
|
101
|
+
* @returns Updated source text with all requested inventory entries appended.
|
|
102
|
+
*/
|
|
103
|
+
export function updateWorkspaceInventorySource(source, options = {}) {
|
|
104
|
+
let nextSource = ensureWorkspaceInventorySections(source);
|
|
105
|
+
if (options.transformSource) {
|
|
106
|
+
nextSource = options.transformSource(nextSource);
|
|
107
|
+
}
|
|
108
|
+
nextSource = appendInventorySectionEntries(nextSource, options);
|
|
109
|
+
nextSource = ensureInterfaceField(nextSource, "WorkspaceBindingSourceConfig", "attribute", "\tattribute?: string;");
|
|
110
|
+
nextSource = ensureInterfaceField(nextSource, "WorkspaceBindingSourceConfig", "block", "\tblock?: string;");
|
|
111
|
+
nextSource = ensureInterfaceField(nextSource, "WorkspaceAbilityConfig", "compatibility", WORKSPACE_COMPATIBILITY_CONFIG_FIELD);
|
|
112
|
+
nextSource = normalizeInterfaceFieldBlock(nextSource, "WorkspaceAbilityConfig", "compatibility", WORKSPACE_COMPATIBILITY_CONFIG_FIELD, ["optionalFeatureIds: string[];", "requiredFeatureIds: string[];"]);
|
|
113
|
+
nextSource = ensureInterfaceField(nextSource, "WorkspaceAiFeatureConfig", "compatibility", WORKSPACE_COMPATIBILITY_CONFIG_FIELD);
|
|
114
|
+
nextSource = normalizeInterfaceFieldBlock(nextSource, "WorkspaceAiFeatureConfig", "compatibility", WORKSPACE_COMPATIBILITY_CONFIG_FIELD, ["optionalFeatureIds: string[];", "requiredFeatureIds: string[];"]);
|
|
115
|
+
return nextSource;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Append new entries to the canonical workspace inventory file on disk.
|
|
119
|
+
*
|
|
120
|
+
* @param projectDir Workspace root directory.
|
|
121
|
+
* @param options Entry lists and optional source transform passed through to
|
|
122
|
+
* `updateWorkspaceInventorySource`.
|
|
123
|
+
* @returns Resolves once `scripts/block-config.ts` has been updated if needed.
|
|
124
|
+
*/
|
|
125
|
+
export async function appendWorkspaceInventoryEntries(projectDir, options) {
|
|
126
|
+
const blockConfigPath = path.join(projectDir, "scripts", "block-config.ts");
|
|
127
|
+
const source = await readFile(blockConfigPath, "utf8");
|
|
128
|
+
const nextSource = updateWorkspaceInventorySource(source, options);
|
|
129
|
+
if (nextSource !== source) {
|
|
130
|
+
await writeFile(blockConfigPath, nextSource, "utf8");
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { WorkspaceInventory, WorkspaceInventoryAppendOptionKey, WorkspaceInventoryEntriesKey, WorkspaceInventorySectionFlagKey } from "./workspace-inventory-types.js";
|
|
2
|
+
type InventoryEntryFieldValidationContext = {
|
|
3
|
+
elementIndex: number;
|
|
4
|
+
entryName: string;
|
|
5
|
+
key: string;
|
|
6
|
+
};
|
|
7
|
+
type InventoryEntryFieldDescriptor = {
|
|
8
|
+
key: string;
|
|
9
|
+
kind?: "string" | "stringArray";
|
|
10
|
+
required?: boolean;
|
|
11
|
+
validate?: (value: string | string[] | undefined, context: InventoryEntryFieldValidationContext) => void;
|
|
12
|
+
};
|
|
13
|
+
type InventoryEntryParserDescriptor = {
|
|
14
|
+
entryName: string;
|
|
15
|
+
fields: readonly InventoryEntryFieldDescriptor[];
|
|
16
|
+
};
|
|
17
|
+
export type InventorySectionDescriptor = {
|
|
18
|
+
/** Optional marker metadata used when appending generated entries. */
|
|
19
|
+
append?: {
|
|
20
|
+
marker: string;
|
|
21
|
+
optionKey: WorkspaceInventoryAppendOptionKey;
|
|
22
|
+
};
|
|
23
|
+
/** Optional exported interface that backs the inventory section entries. */
|
|
24
|
+
interface?: {
|
|
25
|
+
name: string;
|
|
26
|
+
section: string;
|
|
27
|
+
};
|
|
28
|
+
/** Optional parser metadata for descriptor-driven inventory reads. */
|
|
29
|
+
parse?: {
|
|
30
|
+
entriesKey: WorkspaceInventoryEntriesKey;
|
|
31
|
+
entry: InventoryEntryParserDescriptor;
|
|
32
|
+
exportName?: string;
|
|
33
|
+
hasSectionKey?: WorkspaceInventorySectionFlagKey;
|
|
34
|
+
required?: boolean;
|
|
35
|
+
};
|
|
36
|
+
/** Optional exported const array that stores the inventory section entries. */
|
|
37
|
+
value?: {
|
|
38
|
+
name: string;
|
|
39
|
+
section: string;
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
export declare const BLOCK_INVENTORY_SECTION: InventorySectionDescriptor;
|
|
43
|
+
export declare const INVENTORY_SECTIONS: readonly InventorySectionDescriptor[];
|
|
44
|
+
/**
|
|
45
|
+
* Parse workspace inventory entries from the source of `scripts/block-config.ts`.
|
|
46
|
+
*
|
|
47
|
+
* @param source Raw TypeScript source from `scripts/block-config.ts`.
|
|
48
|
+
* @returns Parsed inventory sections without the resolved `blockConfigPath`.
|
|
49
|
+
* @throws {Error} When `BLOCKS` is missing or any inventory entry is malformed.
|
|
50
|
+
*/
|
|
51
|
+
export declare function parseWorkspaceInventorySource(source: string): Omit<WorkspaceInventory, "blockConfigPath">;
|
|
52
|
+
export {};
|