@pgpmjs/core 3.0.0
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/LICENSE +23 -0
- package/README.md +99 -0
- package/core/boilerplate-scanner.d.ts +41 -0
- package/core/boilerplate-scanner.js +106 -0
- package/core/boilerplate-types.d.ts +52 -0
- package/core/boilerplate-types.js +6 -0
- package/core/class/pgpm.d.ts +150 -0
- package/core/class/pgpm.js +1470 -0
- package/core/template-scaffold.d.ts +29 -0
- package/core/template-scaffold.js +168 -0
- package/esm/core/boilerplate-scanner.js +96 -0
- package/esm/core/boilerplate-types.js +5 -0
- package/esm/core/class/pgpm.js +1430 -0
- package/esm/core/template-scaffold.js +161 -0
- package/esm/export/export-meta.js +240 -0
- package/esm/export/export-migrations.js +180 -0
- package/esm/extensions/extensions.js +31 -0
- package/esm/files/extension/index.js +3 -0
- package/esm/files/extension/reader.js +79 -0
- package/esm/files/extension/writer.js +63 -0
- package/esm/files/index.js +6 -0
- package/esm/files/plan/generator.js +49 -0
- package/esm/files/plan/index.js +5 -0
- package/esm/files/plan/parser.js +296 -0
- package/esm/files/plan/validators.js +181 -0
- package/esm/files/plan/writer.js +114 -0
- package/esm/files/sql/index.js +1 -0
- package/esm/files/sql/writer.js +107 -0
- package/esm/files/sql-scripts/index.js +2 -0
- package/esm/files/sql-scripts/reader.js +19 -0
- package/esm/files/types/index.js +1 -0
- package/esm/files/types/package.js +1 -0
- package/esm/index.js +21 -0
- package/esm/init/client.js +144 -0
- package/esm/init/sql/bootstrap-roles.sql +55 -0
- package/esm/init/sql/bootstrap-test-roles.sql +72 -0
- package/esm/migrate/clean.js +23 -0
- package/esm/migrate/client.js +551 -0
- package/esm/migrate/index.js +5 -0
- package/esm/migrate/sql/procedures.sql +258 -0
- package/esm/migrate/sql/schema.sql +37 -0
- package/esm/migrate/types.js +1 -0
- package/esm/migrate/utils/event-logger.js +28 -0
- package/esm/migrate/utils/hash.js +27 -0
- package/esm/migrate/utils/transaction.js +125 -0
- package/esm/modules/modules.js +49 -0
- package/esm/packaging/package.js +96 -0
- package/esm/packaging/transform.js +70 -0
- package/esm/projects/deploy.js +123 -0
- package/esm/projects/revert.js +75 -0
- package/esm/projects/verify.js +61 -0
- package/esm/resolution/deps.js +526 -0
- package/esm/resolution/resolve.js +101 -0
- package/esm/utils/debug.js +147 -0
- package/esm/utils/target-utils.js +37 -0
- package/esm/workspace/paths.js +43 -0
- package/esm/workspace/utils.js +31 -0
- package/export/export-meta.d.ts +8 -0
- package/export/export-meta.js +244 -0
- package/export/export-migrations.d.ts +17 -0
- package/export/export-migrations.js +187 -0
- package/extensions/extensions.d.ts +5 -0
- package/extensions/extensions.js +35 -0
- package/files/extension/index.d.ts +2 -0
- package/files/extension/index.js +19 -0
- package/files/extension/reader.d.ts +24 -0
- package/files/extension/reader.js +86 -0
- package/files/extension/writer.d.ts +39 -0
- package/files/extension/writer.js +70 -0
- package/files/index.d.ts +5 -0
- package/files/index.js +22 -0
- package/files/plan/generator.d.ts +22 -0
- package/files/plan/generator.js +57 -0
- package/files/plan/index.d.ts +4 -0
- package/files/plan/index.js +21 -0
- package/files/plan/parser.d.ts +27 -0
- package/files/plan/parser.js +303 -0
- package/files/plan/validators.d.ts +52 -0
- package/files/plan/validators.js +187 -0
- package/files/plan/writer.d.ts +27 -0
- package/files/plan/writer.js +124 -0
- package/files/sql/index.d.ts +1 -0
- package/files/sql/index.js +17 -0
- package/files/sql/writer.d.ts +12 -0
- package/files/sql/writer.js +114 -0
- package/files/sql-scripts/index.d.ts +1 -0
- package/files/sql-scripts/index.js +18 -0
- package/files/sql-scripts/reader.d.ts +8 -0
- package/files/sql-scripts/reader.js +23 -0
- package/files/types/index.d.ts +46 -0
- package/files/types/index.js +17 -0
- package/files/types/package.d.ts +20 -0
- package/files/types/package.js +2 -0
- package/index.d.ts +21 -0
- package/index.js +45 -0
- package/init/client.d.ts +26 -0
- package/init/client.js +148 -0
- package/init/sql/bootstrap-roles.sql +55 -0
- package/init/sql/bootstrap-test-roles.sql +72 -0
- package/migrate/clean.d.ts +1 -0
- package/migrate/clean.js +27 -0
- package/migrate/client.d.ts +80 -0
- package/migrate/client.js +555 -0
- package/migrate/index.d.ts +5 -0
- package/migrate/index.js +21 -0
- package/migrate/sql/procedures.sql +258 -0
- package/migrate/sql/schema.sql +37 -0
- package/migrate/types.d.ts +67 -0
- package/migrate/types.js +2 -0
- package/migrate/utils/event-logger.d.ts +13 -0
- package/migrate/utils/event-logger.js +32 -0
- package/migrate/utils/hash.d.ts +12 -0
- package/migrate/utils/hash.js +32 -0
- package/migrate/utils/transaction.d.ts +27 -0
- package/migrate/utils/transaction.js +129 -0
- package/modules/modules.d.ts +31 -0
- package/modules/modules.js +56 -0
- package/package.json +70 -0
- package/packaging/package.d.ts +19 -0
- package/packaging/package.js +102 -0
- package/packaging/transform.d.ts +22 -0
- package/packaging/transform.js +75 -0
- package/projects/deploy.d.ts +8 -0
- package/projects/deploy.js +160 -0
- package/projects/revert.d.ts +15 -0
- package/projects/revert.js +112 -0
- package/projects/verify.d.ts +8 -0
- package/projects/verify.js +98 -0
- package/resolution/deps.d.ts +57 -0
- package/resolution/deps.js +531 -0
- package/resolution/resolve.d.ts +37 -0
- package/resolution/resolve.js +107 -0
- package/utils/debug.d.ts +21 -0
- package/utils/debug.js +153 -0
- package/utils/target-utils.d.ts +5 -0
- package/utils/target-utils.js +40 -0
- package/workspace/paths.d.ts +14 -0
- package/workspace/paths.js +50 -0
- package/workspace/utils.d.ts +8 -0
- package/workspace/utils.js +36 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { BoilerplateQuestion } from './boilerplate-types';
|
|
2
|
+
export type TemplateKind = 'workspace' | 'module';
|
|
3
|
+
export interface ScaffoldTemplateOptions {
|
|
4
|
+
type: TemplateKind;
|
|
5
|
+
outputDir: string;
|
|
6
|
+
templateRepo?: string;
|
|
7
|
+
branch?: string;
|
|
8
|
+
templatePath?: string;
|
|
9
|
+
answers: Record<string, any>;
|
|
10
|
+
noTty?: boolean;
|
|
11
|
+
cacheTtlMs?: number;
|
|
12
|
+
toolName?: string;
|
|
13
|
+
cwd?: string;
|
|
14
|
+
cacheBaseDir?: string;
|
|
15
|
+
/** Override the boilerplate directory (e.g., "default", "supabase") */
|
|
16
|
+
dir?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface ScaffoldTemplateResult {
|
|
19
|
+
cacheUsed: boolean;
|
|
20
|
+
cacheExpired: boolean;
|
|
21
|
+
cachePath?: string;
|
|
22
|
+
templateDir: string;
|
|
23
|
+
/** Questions loaded from .boilerplate.json, if any */
|
|
24
|
+
questions?: BoilerplateQuestion[];
|
|
25
|
+
}
|
|
26
|
+
export declare const DEFAULT_TEMPLATE_REPO = "https://github.com/constructive-io/pgpm-boilerplates.git";
|
|
27
|
+
export declare const DEFAULT_TEMPLATE_TTL_MS: number;
|
|
28
|
+
export declare const DEFAULT_TEMPLATE_TOOL_NAME = "pgpm";
|
|
29
|
+
export declare function scaffoldTemplate(options: ScaffoldTemplateOptions): Promise<ScaffoldTemplateResult>;
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.DEFAULT_TEMPLATE_TOOL_NAME = exports.DEFAULT_TEMPLATE_TTL_MS = exports.DEFAULT_TEMPLATE_REPO = void 0;
|
|
7
|
+
exports.scaffoldTemplate = scaffoldTemplate;
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const os_1 = __importDefault(require("os"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const create_gen_app_1 = require("create-gen-app");
|
|
12
|
+
const boilerplate_scanner_1 = require("./boilerplate-scanner");
|
|
13
|
+
exports.DEFAULT_TEMPLATE_REPO = 'https://github.com/constructive-io/pgpm-boilerplates.git';
|
|
14
|
+
exports.DEFAULT_TEMPLATE_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 1 week
|
|
15
|
+
exports.DEFAULT_TEMPLATE_TOOL_NAME = 'pgpm';
|
|
16
|
+
const templatizer = new create_gen_app_1.Templatizer();
|
|
17
|
+
const looksLikePath = (value) => {
|
|
18
|
+
return (value.startsWith('.') || value.startsWith('/') || value.startsWith('~'));
|
|
19
|
+
};
|
|
20
|
+
const normalizeQuestions = (questions) => questions?.map((q) => ({
|
|
21
|
+
...q,
|
|
22
|
+
type: q.type || 'text',
|
|
23
|
+
}));
|
|
24
|
+
const attachQuestionsToTemplatizer = (templ, questions) => {
|
|
25
|
+
if (!questions?.length || typeof templ?.extract !== 'function')
|
|
26
|
+
return;
|
|
27
|
+
const originalExtract = templ.extract.bind(templ);
|
|
28
|
+
templ.extract = async (templateDir) => {
|
|
29
|
+
const extracted = await originalExtract(templateDir);
|
|
30
|
+
extracted.projectQuestions = {
|
|
31
|
+
questions: normalizeQuestions(questions),
|
|
32
|
+
};
|
|
33
|
+
return extracted;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Resolve the template path using the new metadata-driven resolution.
|
|
38
|
+
*
|
|
39
|
+
* Resolution order:
|
|
40
|
+
* 1. If explicit `templatePath` is provided, use it directly
|
|
41
|
+
* 2. If `.boilerplates.json` exists, use its `dir` field to find the base directory
|
|
42
|
+
* 3. Look for `{baseDir}/{type}` (e.g., "default/module")
|
|
43
|
+
* 4. Fallback to legacy structure: `{type}` directly in root
|
|
44
|
+
*/
|
|
45
|
+
const resolveFromPath = (templateDir, templatePath, type, dirOverride) => {
|
|
46
|
+
// If explicit templatePath is provided, use it directly
|
|
47
|
+
if (templatePath) {
|
|
48
|
+
const candidateDir = path_1.default.isAbsolute(templatePath)
|
|
49
|
+
? templatePath
|
|
50
|
+
: path_1.default.join(templateDir, templatePath);
|
|
51
|
+
if (fs_1.default.existsSync(candidateDir) &&
|
|
52
|
+
fs_1.default.statSync(candidateDir).isDirectory()) {
|
|
53
|
+
return {
|
|
54
|
+
fromPath: path_1.default.relative(templateDir, candidateDir) || '.',
|
|
55
|
+
resolvedTemplatePath: candidateDir,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
fromPath: templatePath,
|
|
60
|
+
resolvedTemplatePath: path_1.default.join(templateDir, templatePath),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
// Try new metadata-driven resolution
|
|
64
|
+
const rootConfig = (0, boilerplate_scanner_1.readBoilerplatesConfig)(templateDir);
|
|
65
|
+
const baseDir = dirOverride ?? rootConfig?.dir;
|
|
66
|
+
if (baseDir) {
|
|
67
|
+
// New structure: {templateDir}/{baseDir}/{type}
|
|
68
|
+
const newStructurePath = path_1.default.join(templateDir, baseDir, type);
|
|
69
|
+
if (fs_1.default.existsSync(newStructurePath) &&
|
|
70
|
+
fs_1.default.statSync(newStructurePath).isDirectory()) {
|
|
71
|
+
return {
|
|
72
|
+
fromPath: path_1.default.join(baseDir, type),
|
|
73
|
+
resolvedTemplatePath: newStructurePath,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// Fallback to legacy structure: {templateDir}/{type}
|
|
78
|
+
const legacyPath = path_1.default.join(templateDir, type);
|
|
79
|
+
if (fs_1.default.existsSync(legacyPath) && fs_1.default.statSync(legacyPath).isDirectory()) {
|
|
80
|
+
return {
|
|
81
|
+
fromPath: type,
|
|
82
|
+
resolvedTemplatePath: legacyPath,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
// Default fallback
|
|
86
|
+
return {
|
|
87
|
+
fromPath: type,
|
|
88
|
+
resolvedTemplatePath: path_1.default.join(templateDir, type),
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
async function scaffoldTemplate(options) {
|
|
92
|
+
const { type, outputDir, templateRepo = exports.DEFAULT_TEMPLATE_REPO, branch, templatePath, answers, noTty = false, cacheTtlMs = exports.DEFAULT_TEMPLATE_TTL_MS, toolName = exports.DEFAULT_TEMPLATE_TOOL_NAME, cwd, cacheBaseDir, dir, } = options;
|
|
93
|
+
const resolvedRepo = looksLikePath(templateRepo)
|
|
94
|
+
? path_1.default.resolve(cwd ?? process.cwd(), templateRepo)
|
|
95
|
+
: templateRepo;
|
|
96
|
+
// Handle local template directories without caching
|
|
97
|
+
if (looksLikePath(templateRepo) &&
|
|
98
|
+
fs_1.default.existsSync(resolvedRepo) &&
|
|
99
|
+
fs_1.default.statSync(resolvedRepo).isDirectory()) {
|
|
100
|
+
const { fromPath, resolvedTemplatePath } = resolveFromPath(resolvedRepo, templatePath, type, dir);
|
|
101
|
+
// Read boilerplate config for questions
|
|
102
|
+
const boilerplateConfig = (0, boilerplate_scanner_1.readBoilerplateConfig)(resolvedTemplatePath);
|
|
103
|
+
// Inject questions into the templatizer pipeline so prompt types and defaults are applied
|
|
104
|
+
attachQuestionsToTemplatizer(templatizer, boilerplateConfig?.questions);
|
|
105
|
+
await templatizer.process(resolvedRepo, outputDir, {
|
|
106
|
+
argv: answers,
|
|
107
|
+
noTty,
|
|
108
|
+
fromPath,
|
|
109
|
+
});
|
|
110
|
+
return {
|
|
111
|
+
cacheUsed: false,
|
|
112
|
+
cacheExpired: false,
|
|
113
|
+
templateDir: resolvedRepo,
|
|
114
|
+
questions: boilerplateConfig?.questions,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
// Remote repo with caching
|
|
118
|
+
const cacheManager = new create_gen_app_1.CacheManager({
|
|
119
|
+
toolName,
|
|
120
|
+
ttl: cacheTtlMs,
|
|
121
|
+
baseDir: cacheBaseDir ??
|
|
122
|
+
process.env.PGPM_CACHE_BASE_DIR ??
|
|
123
|
+
(process.env.JEST_WORKER_ID
|
|
124
|
+
? path_1.default.join(os_1.default.tmpdir(), `pgpm-cache-${process.env.JEST_WORKER_ID}`)
|
|
125
|
+
: undefined),
|
|
126
|
+
});
|
|
127
|
+
const gitCloner = new create_gen_app_1.GitCloner();
|
|
128
|
+
const normalizedUrl = gitCloner.normalizeUrl(resolvedRepo);
|
|
129
|
+
const cacheKey = cacheManager.createKey(normalizedUrl, branch);
|
|
130
|
+
const expiredMetadata = cacheManager.checkExpiration(cacheKey);
|
|
131
|
+
if (expiredMetadata) {
|
|
132
|
+
cacheManager.clear(cacheKey);
|
|
133
|
+
}
|
|
134
|
+
let templateDir;
|
|
135
|
+
let cacheUsed = false;
|
|
136
|
+
const cachedPath = cacheManager.get(cacheKey);
|
|
137
|
+
if (cachedPath && !expiredMetadata) {
|
|
138
|
+
templateDir = cachedPath;
|
|
139
|
+
cacheUsed = true;
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
const tempDest = path_1.default.join(cacheManager.getReposDir(), cacheKey);
|
|
143
|
+
gitCloner.clone(normalizedUrl, tempDest, {
|
|
144
|
+
branch,
|
|
145
|
+
depth: 1,
|
|
146
|
+
singleBranch: true,
|
|
147
|
+
});
|
|
148
|
+
cacheManager.set(cacheKey, tempDest);
|
|
149
|
+
templateDir = tempDest;
|
|
150
|
+
}
|
|
151
|
+
const { fromPath, resolvedTemplatePath } = resolveFromPath(templateDir, templatePath, type, dir);
|
|
152
|
+
// Read boilerplate config for questions
|
|
153
|
+
const boilerplateConfig = (0, boilerplate_scanner_1.readBoilerplateConfig)(resolvedTemplatePath);
|
|
154
|
+
// Inject questions into the templatizer pipeline so prompt types and defaults are applied
|
|
155
|
+
attachQuestionsToTemplatizer(templatizer, boilerplateConfig?.questions);
|
|
156
|
+
await templatizer.process(templateDir, outputDir, {
|
|
157
|
+
argv: answers,
|
|
158
|
+
noTty,
|
|
159
|
+
fromPath,
|
|
160
|
+
});
|
|
161
|
+
return {
|
|
162
|
+
cacheUsed,
|
|
163
|
+
cacheExpired: Boolean(expiredMetadata),
|
|
164
|
+
cachePath: templateDir,
|
|
165
|
+
templateDir,
|
|
166
|
+
questions: boilerplateConfig?.questions,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
/**
|
|
4
|
+
* Read the root `.boilerplates.json` configuration from a template repository.
|
|
5
|
+
* This file specifies the default directory containing boilerplate templates.
|
|
6
|
+
*
|
|
7
|
+
* @param templateDir - The root directory of the template repository
|
|
8
|
+
* @returns The root config or null if not found
|
|
9
|
+
*/
|
|
10
|
+
export function readBoilerplatesConfig(templateDir) {
|
|
11
|
+
const configPath = path.join(templateDir, '.boilerplates.json');
|
|
12
|
+
if (fs.existsSync(configPath)) {
|
|
13
|
+
try {
|
|
14
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
15
|
+
return JSON.parse(content);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Read the `.boilerplate.json` configuration from a boilerplate directory.
|
|
25
|
+
* This file specifies the boilerplate type and questions.
|
|
26
|
+
*
|
|
27
|
+
* @param boilerplatePath - The path to the boilerplate directory
|
|
28
|
+
* @returns The boilerplate config or null if not found
|
|
29
|
+
*/
|
|
30
|
+
export function readBoilerplateConfig(boilerplatePath) {
|
|
31
|
+
const jsonPath = path.join(boilerplatePath, '.boilerplate.json');
|
|
32
|
+
if (fs.existsSync(jsonPath)) {
|
|
33
|
+
try {
|
|
34
|
+
const content = fs.readFileSync(jsonPath, 'utf-8');
|
|
35
|
+
return JSON.parse(content);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Scan a base directory for boilerplate templates.
|
|
45
|
+
* Each subdirectory with a `.boilerplate.json` file is considered a boilerplate.
|
|
46
|
+
*
|
|
47
|
+
* @param baseDir - The directory to scan (e.g., "default/")
|
|
48
|
+
* @returns Array of scanned boilerplates with their configurations
|
|
49
|
+
*/
|
|
50
|
+
export function scanBoilerplates(baseDir) {
|
|
51
|
+
const boilerplates = [];
|
|
52
|
+
if (!fs.existsSync(baseDir)) {
|
|
53
|
+
return boilerplates;
|
|
54
|
+
}
|
|
55
|
+
const entries = fs.readdirSync(baseDir, { withFileTypes: true });
|
|
56
|
+
for (const entry of entries) {
|
|
57
|
+
if (!entry.isDirectory()) {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const boilerplatePath = path.join(baseDir, entry.name);
|
|
61
|
+
const config = readBoilerplateConfig(boilerplatePath);
|
|
62
|
+
if (config) {
|
|
63
|
+
boilerplates.push({
|
|
64
|
+
name: entry.name,
|
|
65
|
+
path: boilerplatePath,
|
|
66
|
+
type: config.type ?? 'module',
|
|
67
|
+
questions: config.questions
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return boilerplates;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Find a boilerplate by type within a scanned list.
|
|
75
|
+
*
|
|
76
|
+
* @param boilerplates - Array of scanned boilerplates
|
|
77
|
+
* @param type - The type to find ('workspace' or 'module')
|
|
78
|
+
* @returns The matching boilerplate or undefined
|
|
79
|
+
*/
|
|
80
|
+
export function findBoilerplateByType(boilerplates, type) {
|
|
81
|
+
return boilerplates.find((bp) => bp.type === type);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Resolve the base directory for boilerplates in a template repository.
|
|
85
|
+
* Uses `.boilerplates.json` if present, otherwise returns empty string.
|
|
86
|
+
*
|
|
87
|
+
* @param templateDir - The root directory of the template repository
|
|
88
|
+
* @returns The resolved base directory path
|
|
89
|
+
*/
|
|
90
|
+
export function resolveBoilerplateBaseDir(templateDir) {
|
|
91
|
+
const rootConfig = readBoilerplatesConfig(templateDir);
|
|
92
|
+
if (rootConfig?.dir) {
|
|
93
|
+
return path.join(templateDir, rootConfig.dir);
|
|
94
|
+
}
|
|
95
|
+
return templateDir;
|
|
96
|
+
}
|