@wp-typia/project-tools 0.19.0 → 0.19.1

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.
@@ -1,11 +1,10 @@
1
1
  /// <reference path="./external-template-modules.d.ts" />
2
2
  import fs from 'node:fs';
3
- import { promises as fsp } from 'node:fs';
4
- import os from 'node:os';
5
3
  import path from 'node:path';
6
4
  import { pathToFileURL } from 'node:url';
7
5
  import { isPlainObject } from './object-utils.js';
8
6
  import { toSegmentPascalCase } from './string-case.js';
7
+ import { createManagedTempRoot } from './temp-roots.js';
9
8
  import { copyRawDirectory, copyRenderedDirectory } from './template-render.js';
10
9
  /**
11
10
  * Candidate filenames for official external template config entrypoints.
@@ -15,6 +14,7 @@ export const EXTERNAL_TEMPLATE_ENTRY_CANDIDATES = [
15
14
  'index.cjs',
16
15
  'index.mjs',
17
16
  ];
17
+ export const EXTERNAL_TEMPLATE_TRUST_WARNING = 'External template configs execute trusted JavaScript during scaffolding. Review the template source before using local paths, GitHub repos, or npm packages you do not already trust.';
18
18
  const TEMPLATE_WARNING_MESSAGE = 'wp-typia owns package/tooling/sync setup for generated projects, so this external template setting is ignored.';
19
19
  function getTemplateWarning(key) {
20
20
  return `Ignoring external template config key "${key}": ${TEMPLATE_WARNING_MESSAGE}`;
@@ -53,7 +53,7 @@ async function loadExternalTemplateConfig(sourceDir) {
53
53
  if (!isPlainObject(loadedConfig)) {
54
54
  throw new Error(`External template config must export an object: ${entryPath}`);
55
55
  }
56
- const warnings = [];
56
+ const warnings = [EXTERNAL_TEMPLATE_TRUST_WARNING];
57
57
  for (const ignoredKey of [
58
58
  'wpScripts',
59
59
  'wpEnv',
@@ -174,10 +174,7 @@ export async function renderCreateBlockExternalTemplate(sourceDir, context, requ
174
174
  const { config, warnings } = await loadExternalTemplateConfig(sourceDir);
175
175
  const { selectedVariant, variantConfig } = getVariantConfig(config, requestedVariant);
176
176
  const { folderName, formatHint, templatePath } = resolveConfiguredTemplatePath(config, variantConfig);
177
- const tempRoot = await fsp.mkdtemp(path.join(os.tmpdir(), 'wp-typia-create-block-external-'));
178
- const cleanup = async () => {
179
- await fsp.rm(tempRoot, { force: true, recursive: true });
180
- };
177
+ const { path: tempRoot, cleanup } = await createManagedTempRoot('wp-typia-create-block-external-');
181
178
  try {
182
179
  const renderedRoot = path.join(tempRoot, 'rendered');
183
180
  const blockDir = resolveSourceSubpath(renderedRoot, folderName);
@@ -1,9 +1,27 @@
1
1
  import fs from 'node:fs';
2
2
  import { promises as fsp } from 'node:fs';
3
- import os from 'node:os';
4
3
  import path from 'node:path';
5
4
  import { getBuiltInTemplateLayerDirs, isOmittableBuiltInTemplateLayerDir, } from './template-builtins.js';
6
5
  import { copyRawDirectory } from './template-render.js';
6
+ import { createManagedTempRoot } from './temp-roots.js';
7
+ async function cleanupSeedRootPair(cleanup, seedCleanup) {
8
+ let cleanupError;
9
+ try {
10
+ await cleanup();
11
+ }
12
+ catch (error) {
13
+ cleanupError = error;
14
+ }
15
+ try {
16
+ await seedCleanup?.();
17
+ }
18
+ catch (error) {
19
+ cleanupError ?? (cleanupError = error);
20
+ }
21
+ if (cleanupError !== undefined) {
22
+ throw cleanupError;
23
+ }
24
+ }
7
25
  function getDefaultCategoryFromBlockJson(blockJson) {
8
26
  return typeof blockJson.category === 'string' &&
9
27
  blockJson.category.trim().length > 0
@@ -48,10 +66,14 @@ function readTemplatePackageJson(sourceDir) {
48
66
  continue;
49
67
  }
50
68
  try {
51
- return JSON.parse(fs.readFileSync(candidate, 'utf8'));
69
+ return {
70
+ packageJson: JSON.parse(fs.readFileSync(candidate, 'utf8')),
71
+ sourcePath: candidate,
72
+ };
52
73
  }
53
- catch {
54
- continue;
74
+ catch (error) {
75
+ const message = error instanceof Error ? error.message : 'Unknown parse failure';
76
+ throw new Error(`Failed to parse template metadata file "${candidate}": ${message}`);
55
77
  }
56
78
  }
57
79
  return null;
@@ -61,10 +83,18 @@ function readTemplatePackageJson(sourceDir) {
61
83
  * manifest and return it when present.
62
84
  */
63
85
  export function getTemplateProjectType(sourceDir) {
64
- const packageJson = readTemplatePackageJson(sourceDir);
65
- return typeof packageJson?.wpTypia?.projectType === 'string'
66
- ? packageJson.wpTypia.projectType
67
- : null;
86
+ const packageJsonEntry = readTemplatePackageJson(sourceDir);
87
+ if (!packageJsonEntry) {
88
+ return null;
89
+ }
90
+ const projectType = packageJsonEntry.packageJson.wpTypia?.projectType;
91
+ if (projectType === undefined) {
92
+ return null;
93
+ }
94
+ if (typeof projectType !== 'string' || projectType.trim().length === 0) {
95
+ throw new Error(`Template metadata file "${packageJsonEntry.sourcePath}" defines wpTypia.projectType, but it must be a non-empty string.`);
96
+ }
97
+ return projectType;
68
98
  }
69
99
  /**
70
100
  * Copy a wp-typia seed into a normalized temporary template directory.
@@ -73,7 +103,7 @@ export function getTemplateProjectType(sourceDir) {
73
103
  * @returns A cloned seed whose cleanup removes the temp root and original seed.
74
104
  */
75
105
  export async function normalizeWpTypiaTemplateSeed(seed) {
76
- const tempRoot = await fsp.mkdtemp(path.join(os.tmpdir(), 'wp-typia-template-source-'));
106
+ const { path: tempRoot, cleanup } = await createManagedTempRoot('wp-typia-template-source-');
77
107
  const normalizedDir = path.join(tempRoot, 'template');
78
108
  try {
79
109
  await copyRawDirectory(seed.blockDir, normalizedDir, {
@@ -92,15 +122,12 @@ export async function normalizeWpTypiaTemplateSeed(seed) {
92
122
  }
93
123
  }
94
124
  catch (error) {
95
- await fsp.rm(tempRoot, { force: true, recursive: true });
125
+ await Promise.allSettled([cleanup(), seed.cleanup?.()]);
96
126
  throw error;
97
127
  }
98
128
  return {
99
129
  blockDir: normalizedDir,
100
- cleanup: async () => {
101
- await fsp.rm(tempRoot, { force: true, recursive: true });
102
- await seed.cleanup?.();
103
- },
130
+ cleanup: async () => cleanupSeedRootPair(cleanup, seed.cleanup),
104
131
  rootDir: normalizedDir,
105
132
  selectedVariant: seed.selectedVariant,
106
133
  warnings: seed.warnings,
@@ -296,7 +323,7 @@ async function removeSeedEntryConflicts(templateDir) {
296
323
  * @returns A normalized template source rooted in a temporary directory.
297
324
  */
298
325
  export async function normalizeCreateBlockSubset(seed, context) {
299
- const tempRoot = await fsp.mkdtemp(path.join(os.tmpdir(), 'wp-typia-remote-template-'));
326
+ const { path: tempRoot, cleanup } = await createManagedTempRoot('wp-typia-remote-template-');
300
327
  try {
301
328
  const templateDir = path.join(tempRoot, 'template');
302
329
  const blockJson = readRemoteBlockJson(seed.blockDir);
@@ -353,17 +380,11 @@ export async function normalizeCreateBlockSubset(seed, context) {
353
380
  selectedVariant: seed.selectedVariant ?? null,
354
381
  templateDir,
355
382
  warnings: seed.warnings ?? [],
356
- cleanup: async () => {
357
- await fsp.rm(tempRoot, { force: true, recursive: true });
358
- if (seed.cleanup) {
359
- await seed.cleanup();
360
- }
361
- },
383
+ cleanup: async () => cleanupSeedRootPair(cleanup, seed.cleanup),
362
384
  };
363
385
  }
364
386
  catch (error) {
365
- await fsp.rm(tempRoot, { force: true, recursive: true });
366
- await seed.cleanup?.();
387
+ await Promise.allSettled([cleanup(), seed.cleanup?.()]);
367
388
  throw error;
368
389
  }
369
390
  }
@@ -1,13 +1,13 @@
1
1
  import fs from 'node:fs';
2
2
  import { promises as fsp } from 'node:fs';
3
3
  import { createRequire } from 'node:module';
4
- import os from 'node:os';
5
4
  import path from 'node:path';
6
5
  import { execFileSync } from 'node:child_process';
7
6
  import semver from 'semver';
8
7
  import { x as extractTarball } from 'tar';
9
8
  import { OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE, PROJECT_TOOLS_PACKAGE_ROOT, } from './template-registry.js';
10
9
  import { isPlainObject } from './object-utils.js';
10
+ import { createManagedTempRoot } from './temp-roots.js';
11
11
  function selectRegistryVersion(metadata, locator) {
12
12
  const distTags = isPlainObject(metadata['dist-tags'])
13
13
  ? metadata['dist-tags']
@@ -58,10 +58,7 @@ async function fetchNpmTemplateSource(locator) {
58
58
  if (typeof tarballUrl !== 'string' || tarballUrl.length === 0) {
59
59
  throw new Error(`npm template metadata is missing tarball URL for ${locator.raw}@${resolvedVersion}.`);
60
60
  }
61
- const tempRoot = await fsp.mkdtemp(path.join(os.tmpdir(), 'wp-typia-template-source-'));
62
- const cleanup = async () => {
63
- await fsp.rm(tempRoot, { force: true, recursive: true });
64
- };
61
+ const { path: tempRoot, cleanup } = await createManagedTempRoot('wp-typia-template-source-');
65
62
  try {
66
63
  const tarballResponse = await fetch(tarballUrl);
67
64
  if (!tarballResponse.ok) {
@@ -178,10 +175,7 @@ export async function assertNoSymlinks(sourceDir) {
178
175
  }
179
176
  }
180
177
  async function resolveGitHubTemplateSource(locator) {
181
- const remoteRoot = await fsp.mkdtemp(path.join(os.tmpdir(), 'wp-typia-template-source-'));
182
- const cleanup = async () => {
183
- await fsp.rm(remoteRoot, { force: true, recursive: true });
184
- };
178
+ const { path: remoteRoot, cleanup } = await createManagedTempRoot('wp-typia-template-source-');
185
179
  const checkoutDir = path.join(remoteRoot, 'source');
186
180
  try {
187
181
  const args = ['clone', '--depth', '1'];
@@ -1,7 +1,6 @@
1
1
  import type { ResolvedTemplateSource } from './template-source-contracts.js';
2
+ import type { ScaffoldTemplateVariables } from './scaffold.js';
2
3
  export type { GitHubTemplateLocator, NpmTemplateLocator, RemoteTemplateLocator, ResolvedTemplateSource, TemplateSourceFormat, TemplateVariableContext, } from './template-source-contracts.js';
3
4
  export { parseGitHubTemplateLocator, parseNpmTemplateLocator, parseTemplateLocator, } from './template-source-locators.js';
4
5
  export { resolveTemplateSeed } from './template-source-seeds.js';
5
- export declare function resolveTemplateSource(templateId: string, cwd: string, variables: {
6
- [key: string]: string;
7
- }, variant?: string): Promise<ResolvedTemplateSource>;
6
+ export declare function resolveTemplateSource(templateId: string, cwd: string, variables: ScaffoldTemplateVariables, variant?: string): Promise<ResolvedTemplateSource>;
@@ -4,17 +4,23 @@ import { parseTemplateLocator, } from './template-source-locators.js';
4
4
  import { detectTemplateSourceFormat, getTemplateProjectType, getDefaultCategory, getTemplateVariableContext, normalizeCreateBlockSubset, normalizeWpTypiaTemplateSeed, renderCreateBlockExternalTemplate, } from './template-source-normalization.js';
5
5
  import { isOfficialWorkspaceTemplateSeed, resolveTemplateSeed, } from './template-source-seeds.js';
6
6
  import { assertBuiltInTemplateVariantAllowed, } from './cli-validation.js';
7
+ import { getScaffoldTemplateVariableGroups } from './scaffold-template-variable-groups.js';
7
8
  export { parseGitHubTemplateLocator, parseNpmTemplateLocator, parseTemplateLocator, } from './template-source-locators.js';
8
9
  export { resolveTemplateSeed } from './template-source-seeds.js';
9
10
  export async function resolveTemplateSource(templateId, cwd, variables, variant) {
10
11
  if (isBuiltInTemplateId(templateId)) {
12
+ const variableGroups = getScaffoldTemplateVariableGroups(variables);
11
13
  assertBuiltInTemplateVariantAllowed({
12
14
  templateId,
13
15
  variant,
14
16
  });
15
17
  return resolveBuiltInTemplateSource(templateId, {
16
- persistenceEnabled: variables.compoundPersistenceEnabled === 'true',
17
- persistencePolicy: variables.persistencePolicy === 'public' ? 'public' : 'authenticated',
18
+ persistenceEnabled: variableGroups.compound.enabled &&
19
+ variableGroups.compound.persistenceEnabled,
20
+ persistencePolicy: variableGroups.persistence.enabled &&
21
+ variableGroups.persistence.policy === 'public'
22
+ ? 'public'
23
+ : 'authenticated',
18
24
  });
19
25
  }
20
26
  const locator = parseTemplateLocator(templateId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wp-typia/project-tools",
3
- "version": "0.19.0",
3
+ "version": "0.19.1",
4
4
  "description": "Project orchestration and programmatic tooling for wp-typia",
5
5
  "packageManager": "bun@1.3.11",
6
6
  "type": "module",
@@ -67,6 +67,11 @@
67
67
  "import": "./dist/runtime/schema-core.js",
68
68
  "default": "./dist/runtime/schema-core.js"
69
69
  },
70
+ "./temp-roots": {
71
+ "types": "./dist/runtime/temp-roots.d.ts",
72
+ "import": "./dist/runtime/temp-roots.js",
73
+ "default": "./dist/runtime/temp-roots.js"
74
+ },
70
75
  "./workspace-project": {
71
76
  "types": "./dist/runtime/workspace-project.d.ts",
72
77
  "import": "./dist/runtime/workspace-project.js",