@timeax/scaffold 0.1.0 → 0.1.2
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/ast.d.cts +1 -1
- package/dist/ast.d.ts +1 -1
- package/dist/cli.cjs +65 -51
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.mjs +63 -50
- package/dist/cli.mjs.map +1 -1
- package/dist/{config-C0067l3c.d.cts → config-DBARTF0g.d.cts} +7 -0
- package/dist/{config-C0067l3c.d.ts → config-DBARTF0g.d.ts} +7 -0
- package/dist/index.cjs +52 -38
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +50 -37
- package/dist/index.mjs.map +1 -1
- package/package.json +20 -8
- package/src/core/apply-structure.ts +14 -0
- package/src/schema/hooks.ts +8 -0
|
@@ -110,6 +110,13 @@ interface HookContext {
|
|
|
110
110
|
* produced a given file.
|
|
111
111
|
*/
|
|
112
112
|
stubName?: string;
|
|
113
|
+
fileName: string;
|
|
114
|
+
dirName: string;
|
|
115
|
+
extension?: string;
|
|
116
|
+
/**
|
|
117
|
+
* Plural form of the file name (without extension), if applicable.
|
|
118
|
+
*/
|
|
119
|
+
pluralFileName: string;
|
|
113
120
|
}
|
|
114
121
|
/**
|
|
115
122
|
* Common filter options used by both regular and stub hooks.
|
|
@@ -110,6 +110,13 @@ interface HookContext {
|
|
|
110
110
|
* produced a given file.
|
|
111
111
|
*/
|
|
112
112
|
stubName?: string;
|
|
113
|
+
fileName: string;
|
|
114
|
+
dirName: string;
|
|
115
|
+
extension?: string;
|
|
116
|
+
/**
|
|
117
|
+
* Plural form of the file name (without extension), if applicable.
|
|
118
|
+
*/
|
|
119
|
+
pluralFileName: string;
|
|
113
120
|
}
|
|
114
121
|
/**
|
|
115
122
|
* Common filter options used by both regular and stub hooks.
|
package/dist/index.cjs
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var path5 = require('path');
|
|
4
4
|
var fs2 = require('fs');
|
|
5
5
|
var os = require('os');
|
|
6
6
|
var crypto = require('crypto');
|
|
7
7
|
var url = require('url');
|
|
8
8
|
var esbuild = require('esbuild');
|
|
9
9
|
var minimatch = require('minimatch');
|
|
10
|
+
var pluralize = require('pluralize');
|
|
10
11
|
|
|
11
12
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
12
13
|
|
|
13
|
-
var
|
|
14
|
+
var path5__default = /*#__PURE__*/_interopDefault(path5);
|
|
14
15
|
var fs2__default = /*#__PURE__*/_interopDefault(fs2);
|
|
15
16
|
var os__default = /*#__PURE__*/_interopDefault(os);
|
|
16
17
|
var crypto__default = /*#__PURE__*/_interopDefault(crypto);
|
|
18
|
+
var pluralize__default = /*#__PURE__*/_interopDefault(pluralize);
|
|
17
19
|
|
|
18
20
|
// src/schema/index.ts
|
|
19
21
|
var SCAFFOLD_ROOT_DIR = ".scaffold";
|
|
@@ -125,31 +127,31 @@ function statSafeSync(targetPath) {
|
|
|
125
127
|
}
|
|
126
128
|
}
|
|
127
129
|
function toProjectRelativePath(projectRoot, absolutePath) {
|
|
128
|
-
const absRoot =
|
|
129
|
-
const absTarget =
|
|
130
|
-
const rootWithSep = absRoot.endsWith(
|
|
130
|
+
const absRoot = path5__default.default.resolve(projectRoot);
|
|
131
|
+
const absTarget = path5__default.default.resolve(absolutePath);
|
|
132
|
+
const rootWithSep = absRoot.endsWith(path5__default.default.sep) ? absRoot : absRoot + path5__default.default.sep;
|
|
131
133
|
if (!absTarget.startsWith(rootWithSep) && absTarget !== absRoot) {
|
|
132
134
|
throw new Error(
|
|
133
135
|
`Path "${absTarget}" is not inside project root "${absRoot}".`
|
|
134
136
|
);
|
|
135
137
|
}
|
|
136
|
-
const rel =
|
|
138
|
+
const rel = path5__default.default.relative(absRoot, absTarget);
|
|
137
139
|
return toPosixPath(rel);
|
|
138
140
|
}
|
|
139
141
|
|
|
140
142
|
// src/core/config-loader.ts
|
|
141
143
|
var logger = defaultLogger.child("[config]");
|
|
142
144
|
async function loadScaffoldConfig(cwd, options = {}) {
|
|
143
|
-
const absCwd =
|
|
144
|
-
const initialScaffoldDir = options.scaffoldDir ?
|
|
145
|
+
const absCwd = path5__default.default.resolve(cwd);
|
|
146
|
+
const initialScaffoldDir = options.scaffoldDir ? path5__default.default.resolve(absCwd, options.scaffoldDir) : path5__default.default.join(absCwd, SCAFFOLD_ROOT_DIR);
|
|
145
147
|
const configPath = options.configPath ?? resolveConfigPath(initialScaffoldDir);
|
|
146
148
|
const config = await importConfig(configPath);
|
|
147
149
|
let configRoot = absCwd;
|
|
148
150
|
if (config.root) {
|
|
149
|
-
configRoot =
|
|
151
|
+
configRoot = path5__default.default.resolve(absCwd, config.root);
|
|
150
152
|
}
|
|
151
|
-
const scaffoldDir = options.scaffoldDir ?
|
|
152
|
-
const baseRoot = config.base ?
|
|
153
|
+
const scaffoldDir = options.scaffoldDir ? path5__default.default.resolve(absCwd, options.scaffoldDir) : path5__default.default.join(configRoot, SCAFFOLD_ROOT_DIR);
|
|
154
|
+
const baseRoot = config.base ? path5__default.default.resolve(configRoot, config.base) : configRoot;
|
|
153
155
|
logger.debug(
|
|
154
156
|
`Loaded config: configRoot=${configRoot}, baseRoot=${baseRoot}, scaffoldDir=${scaffoldDir}`
|
|
155
157
|
);
|
|
@@ -168,7 +170,7 @@ function resolveConfigPath(scaffoldDir) {
|
|
|
168
170
|
"config.cjs"
|
|
169
171
|
];
|
|
170
172
|
for (const file of candidates) {
|
|
171
|
-
const full =
|
|
173
|
+
const full = path5__default.default.join(scaffoldDir, file);
|
|
172
174
|
if (fs2__default.default.existsSync(full)) {
|
|
173
175
|
return full;
|
|
174
176
|
}
|
|
@@ -180,7 +182,7 @@ function resolveConfigPath(scaffoldDir) {
|
|
|
180
182
|
);
|
|
181
183
|
}
|
|
182
184
|
async function importConfig(configPath) {
|
|
183
|
-
const ext =
|
|
185
|
+
const ext = path5__default.default.extname(configPath).toLowerCase();
|
|
184
186
|
if (ext === ".ts" || ext === ".tsx") {
|
|
185
187
|
return importTsConfig(configPath);
|
|
186
188
|
}
|
|
@@ -192,9 +194,9 @@ async function importTsConfig(configPath) {
|
|
|
192
194
|
const source = fs2__default.default.readFileSync(configPath, "utf8");
|
|
193
195
|
const stat = fs2__default.default.statSync(configPath);
|
|
194
196
|
const hash = crypto__default.default.createHash("sha1").update(configPath).update(String(stat.mtimeMs)).digest("hex");
|
|
195
|
-
const tmpDir =
|
|
197
|
+
const tmpDir = path5__default.default.join(os__default.default.tmpdir(), "timeax-scaffold-config");
|
|
196
198
|
ensureDirSync(tmpDir);
|
|
197
|
-
const tmpFile =
|
|
199
|
+
const tmpFile = path5__default.default.join(tmpDir, `${hash}.mjs`);
|
|
198
200
|
if (!fs2__default.default.existsSync(tmpFile)) {
|
|
199
201
|
const result = await esbuild.transform(source, {
|
|
200
202
|
loader: "ts",
|
|
@@ -788,7 +790,7 @@ function resolveGroupStructure(scaffoldDir, group, config) {
|
|
|
788
790
|
return group.structure;
|
|
789
791
|
}
|
|
790
792
|
const fileName = group.structureFile ?? `${group.name}.txt`;
|
|
791
|
-
const filePath =
|
|
793
|
+
const filePath = path5__default.default.join(scaffoldDir, fileName);
|
|
792
794
|
if (!fs2__default.default.existsSync(filePath)) {
|
|
793
795
|
throw new Error(
|
|
794
796
|
`@timeax/scaffold: Group "${group.name}" has no structure. Expected file "${fileName}" in "${scaffoldDir}".`
|
|
@@ -804,7 +806,7 @@ function resolveSingleStructure(scaffoldDir, config) {
|
|
|
804
806
|
return config.structure;
|
|
805
807
|
}
|
|
806
808
|
const fileName = config.structureFile ?? "structure.txt";
|
|
807
|
-
const filePath =
|
|
809
|
+
const filePath = path5__default.default.join(scaffoldDir, fileName);
|
|
808
810
|
if (!fs2__default.default.existsSync(filePath)) {
|
|
809
811
|
throw new Error(
|
|
810
812
|
`@timeax/scaffold: No structure defined. Expected "${fileName}" in "${scaffoldDir}".`
|
|
@@ -826,7 +828,7 @@ var CacheManager = class {
|
|
|
826
828
|
}
|
|
827
829
|
cache = DEFAULT_CACHE;
|
|
828
830
|
get cachePathAbs() {
|
|
829
|
-
return
|
|
831
|
+
return path5__default.default.resolve(this.projectRoot, this.cacheFileRelPath);
|
|
830
832
|
}
|
|
831
833
|
load() {
|
|
832
834
|
const cachePath = this.cachePathAbs;
|
|
@@ -850,7 +852,7 @@ var CacheManager = class {
|
|
|
850
852
|
}
|
|
851
853
|
save() {
|
|
852
854
|
const cachePath = this.cachePathAbs;
|
|
853
|
-
const dir =
|
|
855
|
+
const dir = path5__default.default.dirname(cachePath);
|
|
854
856
|
ensureDirSync(dir);
|
|
855
857
|
fs2__default.default.writeFileSync(cachePath, JSON.stringify(this.cache, null, 2), "utf8");
|
|
856
858
|
}
|
|
@@ -935,9 +937,9 @@ async function applyStructure(opts) {
|
|
|
935
937
|
interactiveDelete
|
|
936
938
|
} = opts;
|
|
937
939
|
const logger5 = opts.logger ?? defaultLogger.child(groupName ? `[apply:${groupName}]` : "[apply]");
|
|
938
|
-
const projectRootAbs =
|
|
939
|
-
const baseDirAbs =
|
|
940
|
-
baseDirAbs.endsWith(
|
|
940
|
+
const projectRootAbs = path5__default.default.resolve(projectRoot);
|
|
941
|
+
const baseDirAbs = path5__default.default.resolve(baseDir);
|
|
942
|
+
baseDirAbs.endsWith(path5__default.default.sep) ? baseDirAbs : baseDirAbs + path5__default.default.sep;
|
|
941
943
|
const desiredPaths = /* @__PURE__ */ new Set();
|
|
942
944
|
const threshold = sizePromptThreshold ?? config.sizePromptThreshold ?? 128 * 1024;
|
|
943
945
|
async function walk(entry, inheritedStub) {
|
|
@@ -950,7 +952,7 @@ async function applyStructure(opts) {
|
|
|
950
952
|
}
|
|
951
953
|
async function handleDir(entry, inheritedStub) {
|
|
952
954
|
const relFromBase = entry.path.replace(/^[./]+/, "");
|
|
953
|
-
const absDir =
|
|
955
|
+
const absDir = path5__default.default.resolve(baseDirAbs, relFromBase);
|
|
954
956
|
const relFromRoot = toPosixPath(
|
|
955
957
|
toProjectRelativePath(projectRootAbs, absDir)
|
|
956
958
|
);
|
|
@@ -965,24 +967,30 @@ async function applyStructure(opts) {
|
|
|
965
967
|
}
|
|
966
968
|
async function handleFile(entry, inheritedStub) {
|
|
967
969
|
const relFromBase = entry.path.replace(/^[./]+/, "");
|
|
968
|
-
const absFile =
|
|
970
|
+
const absFile = path5__default.default.resolve(baseDirAbs, relFromBase);
|
|
969
971
|
const relFromRoot = toPosixPath(
|
|
970
972
|
toProjectRelativePath(projectRootAbs, absFile)
|
|
971
973
|
);
|
|
972
974
|
desiredPaths.add(relFromRoot);
|
|
973
975
|
const stubName = entry.stub ?? inheritedStub;
|
|
976
|
+
const extension = path5__default.default.extname(relFromRoot);
|
|
977
|
+
const fileName = path5__default.default.basename(relFromRoot, extension);
|
|
974
978
|
const ctx = {
|
|
975
979
|
projectRoot: projectRootAbs,
|
|
976
980
|
targetPath: relFromRoot,
|
|
977
981
|
absolutePath: absFile,
|
|
978
982
|
isDirectory: false,
|
|
983
|
+
fileName,
|
|
984
|
+
dirName: path5__default.default.dirname(relFromRoot),
|
|
985
|
+
extension,
|
|
986
|
+
pluralFileName: pluralize__default.default.plural(fileName),
|
|
979
987
|
stubName
|
|
980
988
|
};
|
|
981
989
|
if (fs2__default.default.existsSync(absFile)) {
|
|
982
990
|
return;
|
|
983
991
|
}
|
|
984
992
|
await hooks.runRegular("preCreateFile", ctx);
|
|
985
|
-
const dir =
|
|
993
|
+
const dir = path5__default.default.dirname(absFile);
|
|
986
994
|
ensureDirSync(dir);
|
|
987
995
|
if (stubName) {
|
|
988
996
|
await hooks.runStub("preStub", ctx);
|
|
@@ -1025,7 +1033,7 @@ async function applyStructure(opts) {
|
|
|
1025
1033
|
if (desiredPaths.has(cachedPath)) {
|
|
1026
1034
|
continue;
|
|
1027
1035
|
}
|
|
1028
|
-
const abs =
|
|
1036
|
+
const abs = path5__default.default.resolve(projectRoot, cachedPath);
|
|
1029
1037
|
const stats = statSafeSync(abs);
|
|
1030
1038
|
if (!stats) {
|
|
1031
1039
|
cache.delete(cachedPath);
|
|
@@ -1035,11 +1043,17 @@ async function applyStructure(opts) {
|
|
|
1035
1043
|
cache.delete(cachedPath);
|
|
1036
1044
|
continue;
|
|
1037
1045
|
}
|
|
1046
|
+
const extension = path5__default.default.extname(abs);
|
|
1047
|
+
const fileName = path5__default.default.basename(abs, extension);
|
|
1038
1048
|
const ctx = {
|
|
1039
1049
|
projectRoot,
|
|
1040
1050
|
targetPath: cachedPath,
|
|
1041
1051
|
absolutePath: abs,
|
|
1042
1052
|
isDirectory: false,
|
|
1053
|
+
fileName,
|
|
1054
|
+
dirName: path5__default.default.dirname(cachedPath),
|
|
1055
|
+
extension,
|
|
1056
|
+
pluralFileName: pluralize__default.default.plural(fileName),
|
|
1043
1057
|
stubName: entry?.createdByStub
|
|
1044
1058
|
};
|
|
1045
1059
|
await hooks.runRegular("preDeleteFile", ctx);
|
|
@@ -1071,16 +1085,16 @@ async function applyStructure(opts) {
|
|
|
1071
1085
|
}
|
|
1072
1086
|
}
|
|
1073
1087
|
function getStructureFilesFromConfig(projectRoot, scaffoldDir, config) {
|
|
1074
|
-
const baseDir =
|
|
1088
|
+
const baseDir = path5__default.default.resolve(projectRoot, scaffoldDir || SCAFFOLD_ROOT_DIR);
|
|
1075
1089
|
const files = [];
|
|
1076
1090
|
if (config.groups && config.groups.length > 0) {
|
|
1077
1091
|
for (const group of config.groups) {
|
|
1078
1092
|
const structureFile = group.structureFile && group.structureFile.trim().length ? group.structureFile : `${group.name}.txt`;
|
|
1079
|
-
files.push(
|
|
1093
|
+
files.push(path5__default.default.join(baseDir, structureFile));
|
|
1080
1094
|
}
|
|
1081
1095
|
} else {
|
|
1082
1096
|
const structureFile = config.structureFile || "structure.txt";
|
|
1083
|
-
files.push(
|
|
1097
|
+
files.push(path5__default.default.join(baseDir, structureFile));
|
|
1084
1098
|
}
|
|
1085
1099
|
return files;
|
|
1086
1100
|
}
|
|
@@ -1122,7 +1136,7 @@ async function runOnce(cwd, options = {}) {
|
|
|
1122
1136
|
const hooks = new HookRunner(config);
|
|
1123
1137
|
if (config.groups && config.groups.length > 0) {
|
|
1124
1138
|
for (const group of config.groups) {
|
|
1125
|
-
const groupRootAbs =
|
|
1139
|
+
const groupRootAbs = path5__default.default.resolve(projectRoot, group.root);
|
|
1126
1140
|
const structure = resolveGroupStructure(scaffoldDir, group, config);
|
|
1127
1141
|
const groupLogger = logger5.child(`[group:${group.name}]`);
|
|
1128
1142
|
await applyStructure({
|
|
@@ -1167,12 +1181,12 @@ var DEFAULT_IGNORE = [
|
|
|
1167
1181
|
"coverage/**"
|
|
1168
1182
|
];
|
|
1169
1183
|
function scanDirectoryToStructureText(rootDir, options = {}) {
|
|
1170
|
-
const absRoot =
|
|
1184
|
+
const absRoot = path5__default.default.resolve(rootDir);
|
|
1171
1185
|
const lines = [];
|
|
1172
1186
|
const ignorePatterns = options.ignore ?? DEFAULT_IGNORE;
|
|
1173
1187
|
const maxDepth = options.maxDepth ?? Infinity;
|
|
1174
1188
|
function isIgnored(absPath) {
|
|
1175
|
-
const rel = toPosixPath(
|
|
1189
|
+
const rel = toPosixPath(path5__default.default.relative(absRoot, absPath));
|
|
1176
1190
|
if (!rel || rel === ".") return false;
|
|
1177
1191
|
return ignorePatterns.some(
|
|
1178
1192
|
(pattern) => minimatch.minimatch(rel, pattern, { dot: true })
|
|
@@ -1193,7 +1207,7 @@ function scanDirectoryToStructureText(rootDir, options = {}) {
|
|
|
1193
1207
|
});
|
|
1194
1208
|
for (const dirent of dirents) {
|
|
1195
1209
|
const name = dirent.name;
|
|
1196
|
-
const absPath =
|
|
1210
|
+
const absPath = path5__default.default.join(currentAbs, name);
|
|
1197
1211
|
if (isIgnored(absPath)) continue;
|
|
1198
1212
|
const indent = " ".repeat(depth);
|
|
1199
1213
|
if (dirent.isDirectory()) {
|
|
@@ -1216,13 +1230,13 @@ async function scanProjectFromConfig(cwd, options = {}) {
|
|
|
1216
1230
|
const onlyGroups = options.groups;
|
|
1217
1231
|
const results = [];
|
|
1218
1232
|
function scanGroup(cfg, group) {
|
|
1219
|
-
const rootAbs =
|
|
1233
|
+
const rootAbs = path5__default.default.resolve(projectRoot, group.root);
|
|
1220
1234
|
const text = scanDirectoryToStructureText(rootAbs, {
|
|
1221
1235
|
ignore: ignorePatterns,
|
|
1222
1236
|
maxDepth
|
|
1223
1237
|
});
|
|
1224
1238
|
const structureFileName = group.structureFile ?? `${group.name}.txt`;
|
|
1225
|
-
const structureFilePath =
|
|
1239
|
+
const structureFilePath = path5__default.default.join(scaffoldDir, structureFileName);
|
|
1226
1240
|
return {
|
|
1227
1241
|
groupName: group.name,
|
|
1228
1242
|
groupRoot: group.root,
|
|
@@ -1249,7 +1263,7 @@ async function scanProjectFromConfig(cwd, options = {}) {
|
|
|
1249
1263
|
maxDepth
|
|
1250
1264
|
});
|
|
1251
1265
|
const structureFileName = config.structureFile ?? "structure.txt";
|
|
1252
|
-
const structureFilePath =
|
|
1266
|
+
const structureFilePath = path5__default.default.join(scaffoldDir, structureFileName);
|
|
1253
1267
|
results.push({
|
|
1254
1268
|
groupName: "default",
|
|
1255
1269
|
groupRoot: ".",
|
|
@@ -1283,8 +1297,8 @@ async function ensureStructureFilesFromConfig(cwd, options = {}) {
|
|
|
1283
1297
|
const seen = /* @__PURE__ */ new Set();
|
|
1284
1298
|
const ensureFile = (fileName) => {
|
|
1285
1299
|
if (!fileName) return;
|
|
1286
|
-
const filePath =
|
|
1287
|
-
const key =
|
|
1300
|
+
const filePath = path5__default.default.join(scaffoldDir, fileName);
|
|
1301
|
+
const key = path5__default.default.resolve(filePath);
|
|
1288
1302
|
if (seen.has(key)) return;
|
|
1289
1303
|
seen.add(key);
|
|
1290
1304
|
if (fs2__default.default.existsSync(filePath)) {
|