@timeax/scaffold 0.1.1 → 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.
@@ -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 path2 = require('path');
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 path2__default = /*#__PURE__*/_interopDefault(path2);
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 = path2__default.default.resolve(projectRoot);
129
- const absTarget = path2__default.default.resolve(absolutePath);
130
- const rootWithSep = absRoot.endsWith(path2__default.default.sep) ? absRoot : absRoot + path2__default.default.sep;
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 = path2__default.default.relative(absRoot, absTarget);
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 = path2__default.default.resolve(cwd);
144
- const initialScaffoldDir = options.scaffoldDir ? path2__default.default.resolve(absCwd, options.scaffoldDir) : path2__default.default.join(absCwd, SCAFFOLD_ROOT_DIR);
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 = path2__default.default.resolve(absCwd, config.root);
151
+ configRoot = path5__default.default.resolve(absCwd, config.root);
150
152
  }
151
- const scaffoldDir = options.scaffoldDir ? path2__default.default.resolve(absCwd, options.scaffoldDir) : path2__default.default.join(configRoot, SCAFFOLD_ROOT_DIR);
152
- const baseRoot = config.base ? path2__default.default.resolve(configRoot, config.base) : configRoot;
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 = path2__default.default.join(scaffoldDir, file);
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 = path2__default.default.extname(configPath).toLowerCase();
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 = path2__default.default.join(os__default.default.tmpdir(), "timeax-scaffold-config");
197
+ const tmpDir = path5__default.default.join(os__default.default.tmpdir(), "timeax-scaffold-config");
196
198
  ensureDirSync(tmpDir);
197
- const tmpFile = path2__default.default.join(tmpDir, `${hash}.mjs`);
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 = path2__default.default.join(scaffoldDir, fileName);
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 = path2__default.default.join(scaffoldDir, fileName);
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 path2__default.default.resolve(this.projectRoot, this.cacheFileRelPath);
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 = path2__default.default.dirname(cachePath);
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 = path2__default.default.resolve(projectRoot);
939
- const baseDirAbs = path2__default.default.resolve(baseDir);
940
- baseDirAbs.endsWith(path2__default.default.sep) ? baseDirAbs : baseDirAbs + path2__default.default.sep;
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 = path2__default.default.resolve(baseDirAbs, relFromBase);
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 = path2__default.default.resolve(baseDirAbs, relFromBase);
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 = path2__default.default.dirname(absFile);
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 = path2__default.default.resolve(projectRoot, cachedPath);
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 = path2__default.default.resolve(projectRoot, scaffoldDir || SCAFFOLD_ROOT_DIR);
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(path2__default.default.join(baseDir, structureFile));
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(path2__default.default.join(baseDir, structureFile));
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 = path2__default.default.resolve(projectRoot, group.root);
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 = path2__default.default.resolve(rootDir);
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(path2__default.default.relative(absRoot, absPath));
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 = path2__default.default.join(currentAbs, name);
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 = path2__default.default.resolve(projectRoot, group.root);
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 = path2__default.default.join(scaffoldDir, structureFileName);
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 = path2__default.default.join(scaffoldDir, structureFileName);
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 = path2__default.default.join(scaffoldDir, fileName);
1287
- const key = path2__default.default.resolve(filePath);
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)) {