@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.
- package/dist/runtime/block-generator-service-spec.js +206 -2
- package/dist/runtime/built-in-block-artifacts.js +3 -1
- package/dist/runtime/built-in-block-code-artifacts.js +3 -1
- package/dist/runtime/cli-add-block.js +11 -5
- package/dist/runtime/cli-diagnostics.d.ts +26 -0
- package/dist/runtime/cli-diagnostics.js +107 -0
- package/dist/runtime/cli-doctor-workspace.js +4 -4
- package/dist/runtime/cli-scaffold.js +3 -3
- package/dist/runtime/index.d.ts +3 -1
- package/dist/runtime/index.js +2 -1
- package/dist/runtime/scaffold-bootstrap.js +5 -1
- package/dist/runtime/scaffold-documents.js +11 -8
- package/dist/runtime/scaffold-template-variable-groups.d.ts +154 -0
- package/dist/runtime/scaffold-template-variable-groups.js +13 -0
- package/dist/runtime/scaffold-template-variables.js +58 -1
- package/dist/runtime/scaffold.d.ts +5 -1
- package/dist/runtime/scaffold.js +5 -1
- package/dist/runtime/temp-roots.d.ts +44 -0
- package/dist/runtime/temp-roots.js +129 -0
- package/dist/runtime/template-builtins.js +4 -6
- package/dist/runtime/template-registry.d.ts +8 -0
- package/dist/runtime/template-registry.js +34 -1
- package/dist/runtime/template-source-external.d.ts +1 -0
- package/dist/runtime/template-source-external.js +4 -7
- package/dist/runtime/template-source-remote.js +44 -23
- package/dist/runtime/template-source-seeds.js +3 -9
- package/dist/runtime/template-source.d.ts +2 -3
- package/dist/runtime/template-source.js +8 -2
- package/package.json +6 -1
|
@@ -2,6 +2,7 @@ import { getPrimaryDevelopmentScript } from './local-dev-presets.js';
|
|
|
2
2
|
import { getCompoundExtensionWorkflowSection, getInitialCommitCommands, getInitialCommitNote, getOptionalOnboardingNote, getOptionalOnboardingSteps, getQuickStartWorkflowNote, getPhpRestExtensionPointsSection, getTemplateSourceOfTruthNote, } from './scaffold-onboarding.js';
|
|
3
3
|
import { formatPackageExecCommand, formatInstallCommand, formatRunScript, } from './package-managers.js';
|
|
4
4
|
import { getPackageVersions } from './package-versions.js';
|
|
5
|
+
import { getScaffoldTemplateVariableGroups } from './scaffold-template-variable-groups.js';
|
|
5
6
|
/**
|
|
6
7
|
* Builds the generated README markdown for one scaffolded project.
|
|
7
8
|
*
|
|
@@ -12,26 +13,28 @@ import { getPackageVersions } from './package-versions.js';
|
|
|
12
13
|
* @returns Markdown README content for the generated project root.
|
|
13
14
|
*/
|
|
14
15
|
export function buildReadme(templateId, variables, packageManager, { withMigrationUi = false, withTestPreset = false, withWpEnv = false, } = {}) {
|
|
16
|
+
const variableGroups = getScaffoldTemplateVariableGroups(variables);
|
|
17
|
+
const compoundPersistenceEnabled = variableGroups.compound.enabled &&
|
|
18
|
+
variableGroups.compound.persistenceEnabled;
|
|
15
19
|
const optionalOnboardingSteps = getOptionalOnboardingSteps(packageManager, templateId, {
|
|
16
|
-
compoundPersistenceEnabled
|
|
20
|
+
compoundPersistenceEnabled,
|
|
17
21
|
});
|
|
18
22
|
const initialCommitCommands = getInitialCommitCommands();
|
|
19
23
|
const sourceOfTruthNote = getTemplateSourceOfTruthNote(templateId, {
|
|
20
|
-
compoundPersistenceEnabled
|
|
24
|
+
compoundPersistenceEnabled,
|
|
21
25
|
});
|
|
22
|
-
const
|
|
23
|
-
const publicPersistencePolicyNote = variables.isPublicPersistencePolicy === 'true'
|
|
26
|
+
const publicPersistencePolicyNote = variableGroups.persistence.enabled && variableGroups.persistence.auth.isPublic
|
|
24
27
|
? 'Public persistence writes use signed short-lived tokens, per-request ids, and coarse rate limiting by default. Add application-specific abuse controls before using the same pattern for high-value metrics or experiments.'
|
|
25
28
|
: null;
|
|
26
|
-
const alternateRenderTargetSection =
|
|
29
|
+
const alternateRenderTargetSection = variableGroups.alternateRenderTargets.enabled
|
|
27
30
|
? `## Alternate Render Targets\n\nThis scaffold keeps \`${templateId === 'compound' ? `src/blocks/${variables.slugKebabCase}/render.php` : 'src/render.php'}\` as the default web render boundary and also generates ${[
|
|
28
|
-
|
|
31
|
+
variableGroups.alternateRenderTargets.hasEmail
|
|
29
32
|
? `\`${templateId === 'compound' ? `src/blocks/${variables.slugKebabCase}/render-email.php` : 'src/render-email.php'}\``
|
|
30
33
|
: null,
|
|
31
|
-
|
|
34
|
+
variableGroups.alternateRenderTargets.hasMjml
|
|
32
35
|
? `\`${templateId === 'compound' ? `src/blocks/${variables.slugKebabCase}/render-mjml.php` : 'src/render-mjml.php'}\``
|
|
33
36
|
: null,
|
|
34
|
-
|
|
37
|
+
variableGroups.alternateRenderTargets.hasPlainText
|
|
35
38
|
? `\`${templateId === 'compound' ? `src/blocks/${variables.slugKebabCase}/render-text.php` : 'src/render-text.php'}\``
|
|
36
39
|
: null,
|
|
37
40
|
]
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
export declare const SCAFFOLD_TEMPLATE_VARIABLE_GROUPS: unique symbol;
|
|
2
|
+
export type ScaffoldTemplateFamily = "basic" | "interactivity" | "persistence" | "compound" | "query-loop" | "external";
|
|
3
|
+
export interface ScaffoldSharedTemplateVariableGroup {
|
|
4
|
+
author: string;
|
|
5
|
+
blockMetadataVersion: string;
|
|
6
|
+
category: string;
|
|
7
|
+
cssClassName: string;
|
|
8
|
+
description: string;
|
|
9
|
+
descriptionJson: string;
|
|
10
|
+
frontendCssClassName: string;
|
|
11
|
+
icon: string;
|
|
12
|
+
keyword: string;
|
|
13
|
+
namespace: string;
|
|
14
|
+
pascalCase: string;
|
|
15
|
+
phpPrefix: string;
|
|
16
|
+
phpPrefixUpper: string;
|
|
17
|
+
slug: string;
|
|
18
|
+
slugCamelCase: string;
|
|
19
|
+
slugKebabCase: string;
|
|
20
|
+
slugSnakeCase: string;
|
|
21
|
+
textDomain: string;
|
|
22
|
+
title: string;
|
|
23
|
+
titleCase: string;
|
|
24
|
+
titleJson: string;
|
|
25
|
+
versions: {
|
|
26
|
+
apiClient: string;
|
|
27
|
+
blockRuntime: string;
|
|
28
|
+
blockTypes: string;
|
|
29
|
+
projectTools: string;
|
|
30
|
+
rest: string;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export interface ScaffoldAlternateRenderTargetVariableGroup {
|
|
34
|
+
csv: string;
|
|
35
|
+
enabled: boolean;
|
|
36
|
+
hasEmail: boolean;
|
|
37
|
+
hasMjml: boolean;
|
|
38
|
+
hasPlainText: boolean;
|
|
39
|
+
json: string;
|
|
40
|
+
targets: readonly string[];
|
|
41
|
+
}
|
|
42
|
+
export interface DisabledScaffoldCompoundVariableGroup {
|
|
43
|
+
enabled: false;
|
|
44
|
+
persistenceEnabled: false;
|
|
45
|
+
}
|
|
46
|
+
export interface EnabledScaffoldCompoundVariableGroup {
|
|
47
|
+
child: {
|
|
48
|
+
category: string;
|
|
49
|
+
cssClassName: string;
|
|
50
|
+
icon: string;
|
|
51
|
+
title: string;
|
|
52
|
+
titleJson: string;
|
|
53
|
+
};
|
|
54
|
+
enabled: true;
|
|
55
|
+
innerBlocks: {
|
|
56
|
+
description: string;
|
|
57
|
+
directInsert: boolean;
|
|
58
|
+
label: string;
|
|
59
|
+
orientation: "" | "horizontal" | "vertical";
|
|
60
|
+
orientationExpression: string;
|
|
61
|
+
preset: string;
|
|
62
|
+
templateLockExpression: string;
|
|
63
|
+
};
|
|
64
|
+
persistenceEnabled: boolean;
|
|
65
|
+
}
|
|
66
|
+
export type ScaffoldCompoundVariableGroup = DisabledScaffoldCompoundVariableGroup | EnabledScaffoldCompoundVariableGroup;
|
|
67
|
+
export interface DisabledScaffoldPersistenceVariableGroup {
|
|
68
|
+
enabled: false;
|
|
69
|
+
scope: "none";
|
|
70
|
+
}
|
|
71
|
+
export interface EnabledScaffoldPersistenceVariableGroup {
|
|
72
|
+
auth: {
|
|
73
|
+
bootstrapCredentialDeclarations: string;
|
|
74
|
+
descriptionJson: string;
|
|
75
|
+
intent: "authenticated" | "public-write-protected";
|
|
76
|
+
isAuthenticated: boolean;
|
|
77
|
+
isPublic: boolean;
|
|
78
|
+
mechanism: "public-signed-token" | "rest-nonce";
|
|
79
|
+
mode: "authenticated-rest-nonce" | "public-signed-token";
|
|
80
|
+
publicWriteRequestIdDeclaration: string;
|
|
81
|
+
};
|
|
82
|
+
dataStorageMode: "custom-table" | "post-meta";
|
|
83
|
+
enabled: true;
|
|
84
|
+
policy: "authenticated" | "public";
|
|
85
|
+
scope: "compound-parent" | "single";
|
|
86
|
+
}
|
|
87
|
+
export type ScaffoldPersistenceVariableGroup = DisabledScaffoldPersistenceVariableGroup | EnabledScaffoldPersistenceVariableGroup;
|
|
88
|
+
export interface DisabledScaffoldQueryLoopVariableGroup {
|
|
89
|
+
enabled: false;
|
|
90
|
+
}
|
|
91
|
+
export interface EnabledScaffoldQueryLoopVariableGroup {
|
|
92
|
+
allowedControls: readonly string[];
|
|
93
|
+
allowedControlsJson: string;
|
|
94
|
+
enabled: true;
|
|
95
|
+
postType: string;
|
|
96
|
+
postTypeJson: string;
|
|
97
|
+
variationNamespace: string;
|
|
98
|
+
variationNamespaceJson: string;
|
|
99
|
+
}
|
|
100
|
+
export type ScaffoldQueryLoopVariableGroup = DisabledScaffoldQueryLoopVariableGroup | EnabledScaffoldQueryLoopVariableGroup;
|
|
101
|
+
interface ScaffoldTemplateVariableGroupsBase {
|
|
102
|
+
alternateRenderTargets: ScaffoldAlternateRenderTargetVariableGroup;
|
|
103
|
+
shared: ScaffoldSharedTemplateVariableGroup;
|
|
104
|
+
template: {
|
|
105
|
+
description: string;
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
type DisabledTemplateFamilyGroups<TFamily extends "basic" | "interactivity" | "external"> = ScaffoldTemplateVariableGroupsBase & {
|
|
109
|
+
compound: DisabledScaffoldCompoundVariableGroup;
|
|
110
|
+
persistence: DisabledScaffoldPersistenceVariableGroup;
|
|
111
|
+
queryLoop: DisabledScaffoldQueryLoopVariableGroup;
|
|
112
|
+
templateFamily: TFamily;
|
|
113
|
+
};
|
|
114
|
+
export type BasicScaffoldTemplateVariableGroups = DisabledTemplateFamilyGroups<"basic">;
|
|
115
|
+
export type InteractivityScaffoldTemplateVariableGroups = DisabledTemplateFamilyGroups<"interactivity">;
|
|
116
|
+
export interface PersistenceScaffoldTemplateVariableGroups extends ScaffoldTemplateVariableGroupsBase {
|
|
117
|
+
compound: DisabledScaffoldCompoundVariableGroup;
|
|
118
|
+
persistence: EnabledScaffoldPersistenceVariableGroup & {
|
|
119
|
+
scope: "single";
|
|
120
|
+
};
|
|
121
|
+
queryLoop: DisabledScaffoldQueryLoopVariableGroup;
|
|
122
|
+
templateFamily: "persistence";
|
|
123
|
+
}
|
|
124
|
+
export interface CompoundScaffoldTemplateVariableGroups extends ScaffoldTemplateVariableGroupsBase {
|
|
125
|
+
compound: EnabledScaffoldCompoundVariableGroup;
|
|
126
|
+
persistence: DisabledScaffoldPersistenceVariableGroup | (EnabledScaffoldPersistenceVariableGroup & {
|
|
127
|
+
scope: "compound-parent";
|
|
128
|
+
});
|
|
129
|
+
queryLoop: DisabledScaffoldQueryLoopVariableGroup;
|
|
130
|
+
templateFamily: "compound";
|
|
131
|
+
}
|
|
132
|
+
export interface QueryLoopScaffoldTemplateVariableGroups extends ScaffoldTemplateVariableGroupsBase {
|
|
133
|
+
compound: DisabledScaffoldCompoundVariableGroup;
|
|
134
|
+
persistence: DisabledScaffoldPersistenceVariableGroup;
|
|
135
|
+
queryLoop: EnabledScaffoldQueryLoopVariableGroup;
|
|
136
|
+
templateFamily: "query-loop";
|
|
137
|
+
}
|
|
138
|
+
export type ExternalScaffoldTemplateVariableGroups = DisabledTemplateFamilyGroups<"external">;
|
|
139
|
+
export type ScaffoldTemplateVariableGroups = BasicScaffoldTemplateVariableGroups | InteractivityScaffoldTemplateVariableGroups | PersistenceScaffoldTemplateVariableGroups | CompoundScaffoldTemplateVariableGroups | QueryLoopScaffoldTemplateVariableGroups | ExternalScaffoldTemplateVariableGroups;
|
|
140
|
+
export interface ScaffoldTemplateVariableGroupsCarrier {
|
|
141
|
+
readonly [SCAFFOLD_TEMPLATE_VARIABLE_GROUPS]: ScaffoldTemplateVariableGroups;
|
|
142
|
+
}
|
|
143
|
+
export type CompoundScaffoldTemplateVariablesLike = ScaffoldTemplateVariableGroupsCarrier & {
|
|
144
|
+
slugKebabCase: string;
|
|
145
|
+
};
|
|
146
|
+
export type PersistenceScaffoldTemplateVariablesLike = ScaffoldTemplateVariableGroupsCarrier & {
|
|
147
|
+
namespace: string;
|
|
148
|
+
pascalCase: string;
|
|
149
|
+
slugKebabCase: string;
|
|
150
|
+
title: string;
|
|
151
|
+
};
|
|
152
|
+
export declare function attachScaffoldTemplateVariableGroups<TVariables extends Record<string, string>>(variables: TVariables, groups: ScaffoldTemplateVariableGroups): TVariables & ScaffoldTemplateVariableGroupsCarrier;
|
|
153
|
+
export declare function getScaffoldTemplateVariableGroups(variables: ScaffoldTemplateVariableGroupsCarrier): ScaffoldTemplateVariableGroups;
|
|
154
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const SCAFFOLD_TEMPLATE_VARIABLE_GROUPS = Symbol("wp-typia.scaffold-template-variable-groups");
|
|
2
|
+
export function attachScaffoldTemplateVariableGroups(variables, groups) {
|
|
3
|
+
Object.defineProperty(variables, SCAFFOLD_TEMPLATE_VARIABLE_GROUPS, {
|
|
4
|
+
configurable: false,
|
|
5
|
+
enumerable: false,
|
|
6
|
+
value: groups,
|
|
7
|
+
writable: false,
|
|
8
|
+
});
|
|
9
|
+
return variables;
|
|
10
|
+
}
|
|
11
|
+
export function getScaffoldTemplateVariableGroups(variables) {
|
|
12
|
+
return variables[SCAFFOLD_TEMPLATE_VARIABLE_GROUPS];
|
|
13
|
+
}
|
|
@@ -5,6 +5,7 @@ import { BUILTIN_BLOCK_METADATA_VERSION, COMPOUND_CHILD_BLOCK_METADATA_DEFAULTS,
|
|
|
5
5
|
import { DEFAULT_COMPOUND_INNER_BLOCKS_PRESET_ID, getCompoundInnerBlocksPresetDefinition, } from './compound-inner-blocks.js';
|
|
6
6
|
import { getTemplateById, isBuiltInTemplateId, } from './template-registry.js';
|
|
7
7
|
import { toPascalCase, toSnakeCase, } from './string-case.js';
|
|
8
|
+
import { attachScaffoldTemplateVariableGroups } from "./scaffold-template-variable-groups.js";
|
|
8
9
|
/**
|
|
9
10
|
* Build the normalized template variables used by scaffold rendering.
|
|
10
11
|
*
|
|
@@ -57,7 +58,7 @@ export function getTemplateVariables(templateId, answers) {
|
|
|
57
58
|
const persistencePolicy = templateId === 'persistence' || compoundPersistenceEnabled
|
|
58
59
|
? answers.persistencePolicy ?? 'authenticated'
|
|
59
60
|
: 'authenticated';
|
|
60
|
-
|
|
61
|
+
const flatVariables = {
|
|
61
62
|
alternateRenderTargetsCsv: '',
|
|
62
63
|
alternateRenderTargetsJson: '[]',
|
|
63
64
|
apiClientPackageVersion,
|
|
@@ -136,4 +137,60 @@ export function getTemplateVariables(templateId, answers) {
|
|
|
136
137
|
titleCase: pascalCase,
|
|
137
138
|
persistencePolicy,
|
|
138
139
|
};
|
|
140
|
+
return attachScaffoldTemplateVariableGroups(flatVariables, {
|
|
141
|
+
alternateRenderTargets: {
|
|
142
|
+
csv: '',
|
|
143
|
+
enabled: false,
|
|
144
|
+
hasEmail: false,
|
|
145
|
+
hasMjml: false,
|
|
146
|
+
hasPlainText: false,
|
|
147
|
+
json: '[]',
|
|
148
|
+
targets: [],
|
|
149
|
+
},
|
|
150
|
+
compound: {
|
|
151
|
+
enabled: false,
|
|
152
|
+
persistenceEnabled: false,
|
|
153
|
+
},
|
|
154
|
+
persistence: {
|
|
155
|
+
enabled: false,
|
|
156
|
+
scope: 'none',
|
|
157
|
+
},
|
|
158
|
+
queryLoop: {
|
|
159
|
+
enabled: false,
|
|
160
|
+
},
|
|
161
|
+
shared: {
|
|
162
|
+
author: answers.author.trim(),
|
|
163
|
+
blockMetadataVersion: BUILTIN_BLOCK_METADATA_VERSION,
|
|
164
|
+
category: metadataDefaults?.category ?? template?.defaultCategory ?? 'widgets',
|
|
165
|
+
cssClassName,
|
|
166
|
+
description,
|
|
167
|
+
descriptionJson: JSON.stringify(description),
|
|
168
|
+
frontendCssClassName: buildFrontendCssClassName(cssClassName),
|
|
169
|
+
icon: metadataDefaults?.icon ?? 'smiley',
|
|
170
|
+
keyword: slug.replace(/-/g, ' '),
|
|
171
|
+
namespace,
|
|
172
|
+
pascalCase,
|
|
173
|
+
phpPrefix,
|
|
174
|
+
phpPrefixUpper,
|
|
175
|
+
slug,
|
|
176
|
+
slugCamelCase: pascalCase.charAt(0).toLowerCase() + pascalCase.slice(1),
|
|
177
|
+
slugKebabCase: slug,
|
|
178
|
+
slugSnakeCase,
|
|
179
|
+
textDomain,
|
|
180
|
+
title,
|
|
181
|
+
titleCase: pascalCase,
|
|
182
|
+
titleJson: JSON.stringify(title),
|
|
183
|
+
versions: {
|
|
184
|
+
apiClient: apiClientPackageVersion,
|
|
185
|
+
blockRuntime: blockRuntimePackageVersion,
|
|
186
|
+
blockTypes: blockTypesPackageVersion,
|
|
187
|
+
projectTools: projectToolsPackageVersion,
|
|
188
|
+
rest: restPackageVersion,
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
template: {
|
|
192
|
+
description: template?.description ?? 'External scaffold template variables',
|
|
193
|
+
},
|
|
194
|
+
templateFamily: 'external',
|
|
195
|
+
});
|
|
139
196
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { PackageManagerId } from "./package-managers.js";
|
|
2
|
+
import type { ScaffoldTemplateVariableGroupsCarrier } from "./scaffold-template-variable-groups.js";
|
|
2
3
|
/**
|
|
3
4
|
* User-facing scaffold answers before template rendering.
|
|
4
5
|
*
|
|
@@ -29,7 +30,7 @@ export type PersistencePolicy = (typeof PERSISTENCE_POLICIES)[number];
|
|
|
29
30
|
/**
|
|
30
31
|
* Normalized template variables shared by built-in and remote scaffold flows.
|
|
31
32
|
*/
|
|
32
|
-
export interface
|
|
33
|
+
export interface FlatScaffoldTemplateVariables extends Record<string, string> {
|
|
33
34
|
alternateRenderTargetsCsv: string;
|
|
34
35
|
alternateRenderTargetsJson: string;
|
|
35
36
|
apiClientPackageVersion: string;
|
|
@@ -94,6 +95,8 @@ export interface ScaffoldTemplateVariables extends Record<string, string> {
|
|
|
94
95
|
titleCase: string;
|
|
95
96
|
persistencePolicy: PersistencePolicy;
|
|
96
97
|
}
|
|
98
|
+
export interface ScaffoldTemplateVariables extends FlatScaffoldTemplateVariables, ScaffoldTemplateVariableGroupsCarrier {
|
|
99
|
+
}
|
|
97
100
|
/**
|
|
98
101
|
* Resolve scaffold template input from either built-in template ids or custom
|
|
99
102
|
* template identifiers such as local paths, GitHub refs, and npm packages.
|
|
@@ -176,6 +179,7 @@ export interface ScaffoldProjectResult {
|
|
|
176
179
|
export { buildBlockCssClassName } from "./scaffold-identifiers.js";
|
|
177
180
|
export { collectScaffoldAnswers, detectAuthor, getDefaultAnswers, resolvePackageManagerId, resolveTemplateId, } from "./scaffold-answer-resolution.js";
|
|
178
181
|
export { getTemplateVariables } from "./scaffold-template-variables.js";
|
|
182
|
+
export { getScaffoldTemplateVariableGroups, type BasicScaffoldTemplateVariableGroups, type CompoundScaffoldTemplateVariableGroups, type ExternalScaffoldTemplateVariableGroups, type InteractivityScaffoldTemplateVariableGroups, type PersistenceScaffoldTemplateVariableGroups, type QueryLoopScaffoldTemplateVariableGroups, type ScaffoldTemplateFamily, type ScaffoldTemplateVariableGroups, type ScaffoldTemplateVariableGroupsCarrier, } from "./scaffold-template-variable-groups.js";
|
|
179
183
|
export declare function isDataStorageMode(value: string): value is DataStorageMode;
|
|
180
184
|
export declare function isPersistencePolicy(value: string): value is PersistencePolicy;
|
|
181
185
|
export declare function scaffoldProject({ projectDir, templateId, answers, alternateRenderTargets, dataStorageMode, persistencePolicy, packageManager, externalLayerId, externalLayerSource, externalLayerSourceLabel, repositoryReference, cwd, allowExistingDir, noInstall, installDependencies, onProgress, variant, withMigrationUi, withTestPreset, withWpEnv, }: ScaffoldProjectOptions): Promise<ScaffoldProjectResult>;
|
package/dist/runtime/scaffold.js
CHANGED
|
@@ -12,6 +12,7 @@ import { OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE, isBuiltInTemplateId, } from "./tem
|
|
|
12
12
|
import { resolveTemplateSource } from "./template-source.js";
|
|
13
13
|
import { BlockGeneratorService, } from "./block-generator-service.js";
|
|
14
14
|
import { getTemplateVariables } from "./scaffold-template-variables.js";
|
|
15
|
+
import { getScaffoldTemplateVariableGroups } from "./scaffold-template-variable-groups.js";
|
|
15
16
|
import { assertExternalLayerCompositionOptions, } from "./cli-validation.js";
|
|
16
17
|
const WORKSPACE_TEMPLATE_ALIAS = "workspace";
|
|
17
18
|
export const DATA_STORAGE_MODES = ["post-meta", "custom-table"];
|
|
@@ -19,6 +20,7 @@ export const PERSISTENCE_POLICIES = ["authenticated", "public"];
|
|
|
19
20
|
export { buildBlockCssClassName } from "./scaffold-identifiers.js";
|
|
20
21
|
export { collectScaffoldAnswers, detectAuthor, getDefaultAnswers, resolvePackageManagerId, resolveTemplateId, } from "./scaffold-answer-resolution.js";
|
|
21
22
|
export { getTemplateVariables } from "./scaffold-template-variables.js";
|
|
23
|
+
export { getScaffoldTemplateVariableGroups, } from "./scaffold-template-variable-groups.js";
|
|
22
24
|
export function isDataStorageMode(value) {
|
|
23
25
|
return DATA_STORAGE_MODES.includes(value);
|
|
24
26
|
}
|
|
@@ -164,8 +166,10 @@ export async function scaffoldProject({ projectDir, templateId, answers, alterna
|
|
|
164
166
|
await fsp.writeFile(gitignorePath, mergeTextLines(buildGitignore(), existingGitignore), "utf8");
|
|
165
167
|
await normalizePackageJson(projectDir, resolvedPackageManager);
|
|
166
168
|
if (isBuiltInTemplate) {
|
|
169
|
+
const variableGroups = getScaffoldTemplateVariableGroups(variables);
|
|
167
170
|
await applyGeneratedProjectDxPackageJson({
|
|
168
|
-
compoundPersistenceEnabled:
|
|
171
|
+
compoundPersistenceEnabled: variableGroups.compound.enabled &&
|
|
172
|
+
variableGroups.compound.persistenceEnabled,
|
|
169
173
|
packageManager: resolvedPackageManager,
|
|
170
174
|
projectDir,
|
|
171
175
|
templateId: resolvedTemplateId,
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Required prefix for managed wp-typia temporary directories.
|
|
3
|
+
*/
|
|
4
|
+
export declare const WP_TYPIA_TEMP_ROOT_PREFIX = "wp-typia-";
|
|
5
|
+
/**
|
|
6
|
+
* Default age threshold for pruning stale wp-typia temp roots.
|
|
7
|
+
*/
|
|
8
|
+
export declare const STALE_TEMP_ROOT_MAX_AGE_MS: number;
|
|
9
|
+
type TempRootOptions = {
|
|
10
|
+
maxAgeMs?: number;
|
|
11
|
+
now?: number;
|
|
12
|
+
tmpDir?: string;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Remove a managed temp root and stop tracking it for process-level cleanup.
|
|
16
|
+
*
|
|
17
|
+
* @param tempRoot Absolute temporary directory path to remove.
|
|
18
|
+
*/
|
|
19
|
+
export declare function cleanupManagedTempRoot(tempRoot: string): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Remove stale `wp-typia-*` temp directories from the target temp root.
|
|
22
|
+
*
|
|
23
|
+
* @param options Optional temp directory, age threshold, and clock override.
|
|
24
|
+
* @returns Absolute temp-root paths removed during the cleanup pass.
|
|
25
|
+
*/
|
|
26
|
+
export declare function cleanupStaleTempRoots({ maxAgeMs, now, tmpDir, }?: TempRootOptions): Promise<string[]>;
|
|
27
|
+
/**
|
|
28
|
+
* Create a managed wp-typia temp root and install process cleanup handlers.
|
|
29
|
+
*
|
|
30
|
+
* @param prefix Temp directory prefix. Must start with `wp-typia-`.
|
|
31
|
+
* @param options Optional temp directory override.
|
|
32
|
+
* @returns The created temp-root path plus an async cleanup helper.
|
|
33
|
+
*/
|
|
34
|
+
export declare function createManagedTempRoot(prefix: string, options?: Pick<TempRootOptions, "tmpDir">): Promise<{
|
|
35
|
+
cleanup: () => Promise<void>;
|
|
36
|
+
path: string;
|
|
37
|
+
}>;
|
|
38
|
+
/**
|
|
39
|
+
* Snapshot the currently tracked temp roots for diagnostics and tests.
|
|
40
|
+
*
|
|
41
|
+
* @returns Absolute paths for temp roots currently registered for cleanup.
|
|
42
|
+
*/
|
|
43
|
+
export declare function getTrackedTempRoots(): string[];
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import * as fsp from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
/**
|
|
6
|
+
* Required prefix for managed wp-typia temporary directories.
|
|
7
|
+
*/
|
|
8
|
+
export const WP_TYPIA_TEMP_ROOT_PREFIX = "wp-typia-";
|
|
9
|
+
/**
|
|
10
|
+
* Default age threshold for pruning stale wp-typia temp roots.
|
|
11
|
+
*/
|
|
12
|
+
export const STALE_TEMP_ROOT_MAX_AGE_MS = 1000 * 60 * 60 * 24;
|
|
13
|
+
const trackedTempRoots = new Set();
|
|
14
|
+
let cleanupHandlersInstalled = false;
|
|
15
|
+
let staleCleanupRan = false;
|
|
16
|
+
let signalCleanupInProgress = false;
|
|
17
|
+
function getTempDir(tmpDir) {
|
|
18
|
+
return tmpDir ?? os.tmpdir();
|
|
19
|
+
}
|
|
20
|
+
function cleanupTrackedTempRootsSync() {
|
|
21
|
+
for (const tempRoot of [...trackedTempRoots]) {
|
|
22
|
+
trackedTempRoots.delete(tempRoot);
|
|
23
|
+
try {
|
|
24
|
+
fs.rmSync(tempRoot, { force: true, recursive: true });
|
|
25
|
+
}
|
|
26
|
+
catch { }
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function installCleanupHandlers() {
|
|
30
|
+
if (cleanupHandlersInstalled) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
cleanupHandlersInstalled = true;
|
|
34
|
+
process.on("exit", cleanupTrackedTempRootsSync);
|
|
35
|
+
for (const [signal, exitCode] of [
|
|
36
|
+
["SIGHUP", 129],
|
|
37
|
+
["SIGINT", 130],
|
|
38
|
+
["SIGTERM", 143],
|
|
39
|
+
]) {
|
|
40
|
+
process.once(signal, () => {
|
|
41
|
+
if (signalCleanupInProgress) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
signalCleanupInProgress = true;
|
|
45
|
+
cleanupTrackedTempRootsSync();
|
|
46
|
+
process.exitCode = exitCode;
|
|
47
|
+
process.exit();
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Remove a managed temp root and stop tracking it for process-level cleanup.
|
|
53
|
+
*
|
|
54
|
+
* @param tempRoot Absolute temporary directory path to remove.
|
|
55
|
+
*/
|
|
56
|
+
export async function cleanupManagedTempRoot(tempRoot) {
|
|
57
|
+
trackedTempRoots.delete(tempRoot);
|
|
58
|
+
await fsp.rm(tempRoot, { force: true, recursive: true });
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Remove stale `wp-typia-*` temp directories from the target temp root.
|
|
62
|
+
*
|
|
63
|
+
* @param options Optional temp directory, age threshold, and clock override.
|
|
64
|
+
* @returns Absolute temp-root paths removed during the cleanup pass.
|
|
65
|
+
*/
|
|
66
|
+
export async function cleanupStaleTempRoots({ maxAgeMs = STALE_TEMP_ROOT_MAX_AGE_MS, now = Date.now(), tmpDir, } = {}) {
|
|
67
|
+
const resolvedTmpDir = getTempDir(tmpDir);
|
|
68
|
+
const removedRoots = [];
|
|
69
|
+
const entries = await fsp.readdir(resolvedTmpDir, { withFileTypes: true });
|
|
70
|
+
for (const entry of entries) {
|
|
71
|
+
if (!entry.isDirectory() ||
|
|
72
|
+
!entry.name.startsWith(WP_TYPIA_TEMP_ROOT_PREFIX)) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const tempRoot = path.join(resolvedTmpDir, entry.name);
|
|
76
|
+
if (trackedTempRoots.has(tempRoot)) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
let stats;
|
|
80
|
+
try {
|
|
81
|
+
stats = await fsp.stat(tempRoot);
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (now - stats.mtimeMs < maxAgeMs) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
await fsp.rm(tempRoot, { force: true, recursive: true });
|
|
91
|
+
removedRoots.push(tempRoot);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return removedRoots;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Create a managed wp-typia temp root and install process cleanup handlers.
|
|
101
|
+
*
|
|
102
|
+
* @param prefix Temp directory prefix. Must start with `wp-typia-`.
|
|
103
|
+
* @param options Optional temp directory override.
|
|
104
|
+
* @returns The created temp-root path plus an async cleanup helper.
|
|
105
|
+
*/
|
|
106
|
+
export async function createManagedTempRoot(prefix, options = {}) {
|
|
107
|
+
if (!prefix.startsWith(WP_TYPIA_TEMP_ROOT_PREFIX)) {
|
|
108
|
+
throw new Error(`Managed wp-typia temp roots must use the "${WP_TYPIA_TEMP_ROOT_PREFIX}" prefix.`);
|
|
109
|
+
}
|
|
110
|
+
installCleanupHandlers();
|
|
111
|
+
if (!staleCleanupRan) {
|
|
112
|
+
staleCleanupRan = true;
|
|
113
|
+
await cleanupStaleTempRoots({ tmpDir: options.tmpDir });
|
|
114
|
+
}
|
|
115
|
+
const tempRoot = await fsp.mkdtemp(path.join(getTempDir(options.tmpDir), prefix));
|
|
116
|
+
trackedTempRoots.add(tempRoot);
|
|
117
|
+
return {
|
|
118
|
+
cleanup: async () => cleanupManagedTempRoot(tempRoot),
|
|
119
|
+
path: tempRoot,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Snapshot the currently tracked temp roots for diagnostics and tests.
|
|
124
|
+
*
|
|
125
|
+
* @returns Absolute paths for temp roots currently registered for cleanup.
|
|
126
|
+
*/
|
|
127
|
+
export function getTrackedTempRoots() {
|
|
128
|
+
return [...trackedTempRoots];
|
|
129
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
-
import os from "node:os";
|
|
3
2
|
import path from "node:path";
|
|
4
3
|
import { promises as fsp } from "node:fs";
|
|
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([
|
|
7
7
|
{
|
|
@@ -152,7 +152,7 @@ function resolveMaterializedTemplateLayerDirs(templateId, layerDirs) {
|
|
|
152
152
|
async function materializeBuiltInTemplateSource(templateId, layerDirs) {
|
|
153
153
|
const template = getTemplateById(templateId);
|
|
154
154
|
const materializedLayerDirs = resolveMaterializedTemplateLayerDirs(templateId, layerDirs);
|
|
155
|
-
const tempRoot = await
|
|
155
|
+
const { path: tempRoot, cleanup } = await createManagedTempRoot("wp-typia-template-");
|
|
156
156
|
const templateDir = path.join(tempRoot, templateId);
|
|
157
157
|
try {
|
|
158
158
|
await fsp.mkdir(templateDir, { recursive: true });
|
|
@@ -164,7 +164,7 @@ async function materializeBuiltInTemplateSource(templateId, layerDirs) {
|
|
|
164
164
|
}
|
|
165
165
|
}
|
|
166
166
|
catch (error) {
|
|
167
|
-
await
|
|
167
|
+
await cleanup();
|
|
168
168
|
throw error;
|
|
169
169
|
}
|
|
170
170
|
return {
|
|
@@ -174,9 +174,7 @@ async function materializeBuiltInTemplateSource(templateId, layerDirs) {
|
|
|
174
174
|
features: template.features,
|
|
175
175
|
format: "wp-typia",
|
|
176
176
|
templateDir,
|
|
177
|
-
cleanup
|
|
178
|
-
await fsp.rm(tempRoot, { force: true, recursive: true });
|
|
179
|
-
},
|
|
177
|
+
cleanup,
|
|
180
178
|
};
|
|
181
179
|
}
|
|
182
180
|
export async function resolveBuiltInTemplateSourceFromLayerDirs(templateId, layerDirs) {
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the canonical `@wp-typia/project-tools` package root.
|
|
3
|
+
*
|
|
4
|
+
* When `WP_TYPIA_PROJECT_TOOLS_PACKAGE_ROOT` is set, the override is only
|
|
5
|
+
* accepted if it points at a readable package manifest whose `name` matches
|
|
6
|
+
* `@wp-typia/project-tools`. Invalid or stale overrides are ignored and normal
|
|
7
|
+
* upward package-root discovery continues.
|
|
8
|
+
*/
|
|
1
9
|
export declare function resolvePackageRoot(startDir: string): string;
|
|
2
10
|
export declare const PROJECT_TOOLS_PACKAGE_ROOT: string;
|
|
3
11
|
export declare const TEMPLATE_ROOT: string;
|
|
@@ -3,14 +3,47 @@ import path from "node:path";
|
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { getBuiltInTemplateMetadataDefaults } from "./template-defaults.js";
|
|
5
5
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const PROJECT_TOOLS_PACKAGE_ROOT_ENV = "WP_TYPIA_PROJECT_TOOLS_PACKAGE_ROOT";
|
|
7
|
+
const PROJECT_TOOLS_PACKAGE_NAME = "@wp-typia/project-tools";
|
|
8
|
+
function resolveValidProjectToolsPackageRoot(candidateRoot) {
|
|
9
|
+
const packageJsonPath = path.join(candidateRoot, "package.json");
|
|
10
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
15
|
+
return packageJson.name === PROJECT_TOOLS_PACKAGE_NAME
|
|
16
|
+
? candidateRoot
|
|
17
|
+
: undefined;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Resolve the canonical `@wp-typia/project-tools` package root.
|
|
25
|
+
*
|
|
26
|
+
* When `WP_TYPIA_PROJECT_TOOLS_PACKAGE_ROOT` is set, the override is only
|
|
27
|
+
* accepted if it points at a readable package manifest whose `name` matches
|
|
28
|
+
* `@wp-typia/project-tools`. Invalid or stale overrides are ignored and normal
|
|
29
|
+
* upward package-root discovery continues.
|
|
30
|
+
*/
|
|
6
31
|
export function resolvePackageRoot(startDir) {
|
|
32
|
+
const overriddenPackageRoot = process.env[PROJECT_TOOLS_PACKAGE_ROOT_ENV]?.trim();
|
|
33
|
+
if (overriddenPackageRoot) {
|
|
34
|
+
const resolvedOverride = path.resolve(overriddenPackageRoot);
|
|
35
|
+
const validOverride = resolveValidProjectToolsPackageRoot(resolvedOverride);
|
|
36
|
+
if (validOverride) {
|
|
37
|
+
return validOverride;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
7
40
|
let currentDir = startDir;
|
|
8
41
|
while (true) {
|
|
9
42
|
const packageJsonPath = path.join(currentDir, "package.json");
|
|
10
43
|
if (fs.existsSync(packageJsonPath)) {
|
|
11
44
|
try {
|
|
12
45
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
13
|
-
if (packageJson.name ===
|
|
46
|
+
if (packageJson.name === PROJECT_TOOLS_PACKAGE_NAME) {
|
|
14
47
|
return currentDir;
|
|
15
48
|
}
|
|
16
49
|
}
|
|
@@ -3,6 +3,7 @@ import type { SeedSource, TemplateVariableContext } from './template-source-cont
|
|
|
3
3
|
* Candidate filenames for official external template config entrypoints.
|
|
4
4
|
*/
|
|
5
5
|
export declare const EXTERNAL_TEMPLATE_ENTRY_CANDIDATES: readonly ["index.js", "index.cjs", "index.mjs"];
|
|
6
|
+
export declare 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.";
|
|
6
7
|
/**
|
|
7
8
|
* Search a source directory for the first supported external template entry.
|
|
8
9
|
*
|