@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.
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { S as ScaffoldConfig, a as ScanStructureOptions, b as ScanFromConfigOptions, c as StructureEntry } from './config-C0067l3c.cjs';
2
- export { B as BaseEntryOptions, D as DirEntry, F as FileEntry, l as FormatConfig, k as FormatMode, H as HookContext, e as HookFilter, h as RegularHookConfig, f as RegularHookFn, R as RegularHookKind, m as StructureGroupConfig, j as StubConfig, i as StubHookConfig, g as StubHookFn, d as StubHookKind } from './config-C0067l3c.cjs';
1
+ import { S as ScaffoldConfig, a as ScanStructureOptions, b as ScanFromConfigOptions, c as StructureEntry } from './config-DBARTF0g.cjs';
2
+ export { B as BaseEntryOptions, D as DirEntry, F as FileEntry, l as FormatConfig, k as FormatMode, H as HookContext, e as HookFilter, h as RegularHookConfig, f as RegularHookFn, R as RegularHookKind, m as StructureGroupConfig, j as StubConfig, i as StubHookConfig, g as StubHookFn, d as StubHookKind } from './config-DBARTF0g.cjs';
3
3
 
4
4
  declare const SCAFFOLD_ROOT_DIR = ".scaffold";
5
5
 
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { S as ScaffoldConfig, a as ScanStructureOptions, b as ScanFromConfigOptions, c as StructureEntry } from './config-C0067l3c.js';
2
- export { B as BaseEntryOptions, D as DirEntry, F as FileEntry, l as FormatConfig, k as FormatMode, H as HookContext, e as HookFilter, h as RegularHookConfig, f as RegularHookFn, R as RegularHookKind, m as StructureGroupConfig, j as StubConfig, i as StubHookConfig, g as StubHookFn, d as StubHookKind } from './config-C0067l3c.js';
1
+ import { S as ScaffoldConfig, a as ScanStructureOptions, b as ScanFromConfigOptions, c as StructureEntry } from './config-DBARTF0g.js';
2
+ export { B as BaseEntryOptions, D as DirEntry, F as FileEntry, l as FormatConfig, k as FormatMode, H as HookContext, e as HookFilter, h as RegularHookConfig, f as RegularHookFn, R as RegularHookKind, m as StructureGroupConfig, j as StubConfig, i as StubHookConfig, g as StubHookFn, d as StubHookKind } from './config-DBARTF0g.js';
3
3
 
4
4
  declare const SCAFFOLD_ROOT_DIR = ".scaffold";
5
5
 
package/dist/index.mjs CHANGED
@@ -1,10 +1,11 @@
1
- import path2 from 'path';
1
+ import path5 from 'path';
2
2
  import fs2 from 'fs';
3
3
  import os from 'os';
4
4
  import crypto from 'crypto';
5
5
  import { pathToFileURL } from 'url';
6
6
  import { transform } from 'esbuild';
7
7
  import { minimatch } from 'minimatch';
8
+ import pluralize from 'pluralize';
8
9
 
9
10
  // src/schema/index.ts
10
11
  var SCAFFOLD_ROOT_DIR = ".scaffold";
@@ -116,31 +117,31 @@ function statSafeSync(targetPath) {
116
117
  }
117
118
  }
118
119
  function toProjectRelativePath(projectRoot, absolutePath) {
119
- const absRoot = path2.resolve(projectRoot);
120
- const absTarget = path2.resolve(absolutePath);
121
- const rootWithSep = absRoot.endsWith(path2.sep) ? absRoot : absRoot + path2.sep;
120
+ const absRoot = path5.resolve(projectRoot);
121
+ const absTarget = path5.resolve(absolutePath);
122
+ const rootWithSep = absRoot.endsWith(path5.sep) ? absRoot : absRoot + path5.sep;
122
123
  if (!absTarget.startsWith(rootWithSep) && absTarget !== absRoot) {
123
124
  throw new Error(
124
125
  `Path "${absTarget}" is not inside project root "${absRoot}".`
125
126
  );
126
127
  }
127
- const rel = path2.relative(absRoot, absTarget);
128
+ const rel = path5.relative(absRoot, absTarget);
128
129
  return toPosixPath(rel);
129
130
  }
130
131
 
131
132
  // src/core/config-loader.ts
132
133
  var logger = defaultLogger.child("[config]");
133
134
  async function loadScaffoldConfig(cwd, options = {}) {
134
- const absCwd = path2.resolve(cwd);
135
- const initialScaffoldDir = options.scaffoldDir ? path2.resolve(absCwd, options.scaffoldDir) : path2.join(absCwd, SCAFFOLD_ROOT_DIR);
135
+ const absCwd = path5.resolve(cwd);
136
+ const initialScaffoldDir = options.scaffoldDir ? path5.resolve(absCwd, options.scaffoldDir) : path5.join(absCwd, SCAFFOLD_ROOT_DIR);
136
137
  const configPath = options.configPath ?? resolveConfigPath(initialScaffoldDir);
137
138
  const config = await importConfig(configPath);
138
139
  let configRoot = absCwd;
139
140
  if (config.root) {
140
- configRoot = path2.resolve(absCwd, config.root);
141
+ configRoot = path5.resolve(absCwd, config.root);
141
142
  }
142
- const scaffoldDir = options.scaffoldDir ? path2.resolve(absCwd, options.scaffoldDir) : path2.join(configRoot, SCAFFOLD_ROOT_DIR);
143
- const baseRoot = config.base ? path2.resolve(configRoot, config.base) : configRoot;
143
+ const scaffoldDir = options.scaffoldDir ? path5.resolve(absCwd, options.scaffoldDir) : path5.join(configRoot, SCAFFOLD_ROOT_DIR);
144
+ const baseRoot = config.base ? path5.resolve(configRoot, config.base) : configRoot;
144
145
  logger.debug(
145
146
  `Loaded config: configRoot=${configRoot}, baseRoot=${baseRoot}, scaffoldDir=${scaffoldDir}`
146
147
  );
@@ -159,7 +160,7 @@ function resolveConfigPath(scaffoldDir) {
159
160
  "config.cjs"
160
161
  ];
161
162
  for (const file of candidates) {
162
- const full = path2.join(scaffoldDir, file);
163
+ const full = path5.join(scaffoldDir, file);
163
164
  if (fs2.existsSync(full)) {
164
165
  return full;
165
166
  }
@@ -171,7 +172,7 @@ function resolveConfigPath(scaffoldDir) {
171
172
  );
172
173
  }
173
174
  async function importConfig(configPath) {
174
- const ext = path2.extname(configPath).toLowerCase();
175
+ const ext = path5.extname(configPath).toLowerCase();
175
176
  if (ext === ".ts" || ext === ".tsx") {
176
177
  return importTsConfig(configPath);
177
178
  }
@@ -183,9 +184,9 @@ async function importTsConfig(configPath) {
183
184
  const source = fs2.readFileSync(configPath, "utf8");
184
185
  const stat = fs2.statSync(configPath);
185
186
  const hash = crypto.createHash("sha1").update(configPath).update(String(stat.mtimeMs)).digest("hex");
186
- const tmpDir = path2.join(os.tmpdir(), "timeax-scaffold-config");
187
+ const tmpDir = path5.join(os.tmpdir(), "timeax-scaffold-config");
187
188
  ensureDirSync(tmpDir);
188
- const tmpFile = path2.join(tmpDir, `${hash}.mjs`);
189
+ const tmpFile = path5.join(tmpDir, `${hash}.mjs`);
189
190
  if (!fs2.existsSync(tmpFile)) {
190
191
  const result = await transform(source, {
191
192
  loader: "ts",
@@ -779,7 +780,7 @@ function resolveGroupStructure(scaffoldDir, group, config) {
779
780
  return group.structure;
780
781
  }
781
782
  const fileName = group.structureFile ?? `${group.name}.txt`;
782
- const filePath = path2.join(scaffoldDir, fileName);
783
+ const filePath = path5.join(scaffoldDir, fileName);
783
784
  if (!fs2.existsSync(filePath)) {
784
785
  throw new Error(
785
786
  `@timeax/scaffold: Group "${group.name}" has no structure. Expected file "${fileName}" in "${scaffoldDir}".`
@@ -795,7 +796,7 @@ function resolveSingleStructure(scaffoldDir, config) {
795
796
  return config.structure;
796
797
  }
797
798
  const fileName = config.structureFile ?? "structure.txt";
798
- const filePath = path2.join(scaffoldDir, fileName);
799
+ const filePath = path5.join(scaffoldDir, fileName);
799
800
  if (!fs2.existsSync(filePath)) {
800
801
  throw new Error(
801
802
  `@timeax/scaffold: No structure defined. Expected "${fileName}" in "${scaffoldDir}".`
@@ -817,7 +818,7 @@ var CacheManager = class {
817
818
  }
818
819
  cache = DEFAULT_CACHE;
819
820
  get cachePathAbs() {
820
- return path2.resolve(this.projectRoot, this.cacheFileRelPath);
821
+ return path5.resolve(this.projectRoot, this.cacheFileRelPath);
821
822
  }
822
823
  load() {
823
824
  const cachePath = this.cachePathAbs;
@@ -841,7 +842,7 @@ var CacheManager = class {
841
842
  }
842
843
  save() {
843
844
  const cachePath = this.cachePathAbs;
844
- const dir = path2.dirname(cachePath);
845
+ const dir = path5.dirname(cachePath);
845
846
  ensureDirSync(dir);
846
847
  fs2.writeFileSync(cachePath, JSON.stringify(this.cache, null, 2), "utf8");
847
848
  }
@@ -926,9 +927,9 @@ async function applyStructure(opts) {
926
927
  interactiveDelete
927
928
  } = opts;
928
929
  const logger5 = opts.logger ?? defaultLogger.child(groupName ? `[apply:${groupName}]` : "[apply]");
929
- const projectRootAbs = path2.resolve(projectRoot);
930
- const baseDirAbs = path2.resolve(baseDir);
931
- baseDirAbs.endsWith(path2.sep) ? baseDirAbs : baseDirAbs + path2.sep;
930
+ const projectRootAbs = path5.resolve(projectRoot);
931
+ const baseDirAbs = path5.resolve(baseDir);
932
+ baseDirAbs.endsWith(path5.sep) ? baseDirAbs : baseDirAbs + path5.sep;
932
933
  const desiredPaths = /* @__PURE__ */ new Set();
933
934
  const threshold = sizePromptThreshold ?? config.sizePromptThreshold ?? 128 * 1024;
934
935
  async function walk(entry, inheritedStub) {
@@ -941,7 +942,7 @@ async function applyStructure(opts) {
941
942
  }
942
943
  async function handleDir(entry, inheritedStub) {
943
944
  const relFromBase = entry.path.replace(/^[./]+/, "");
944
- const absDir = path2.resolve(baseDirAbs, relFromBase);
945
+ const absDir = path5.resolve(baseDirAbs, relFromBase);
945
946
  const relFromRoot = toPosixPath(
946
947
  toProjectRelativePath(projectRootAbs, absDir)
947
948
  );
@@ -956,24 +957,30 @@ async function applyStructure(opts) {
956
957
  }
957
958
  async function handleFile(entry, inheritedStub) {
958
959
  const relFromBase = entry.path.replace(/^[./]+/, "");
959
- const absFile = path2.resolve(baseDirAbs, relFromBase);
960
+ const absFile = path5.resolve(baseDirAbs, relFromBase);
960
961
  const relFromRoot = toPosixPath(
961
962
  toProjectRelativePath(projectRootAbs, absFile)
962
963
  );
963
964
  desiredPaths.add(relFromRoot);
964
965
  const stubName = entry.stub ?? inheritedStub;
966
+ const extension = path5.extname(relFromRoot);
967
+ const fileName = path5.basename(relFromRoot, extension);
965
968
  const ctx = {
966
969
  projectRoot: projectRootAbs,
967
970
  targetPath: relFromRoot,
968
971
  absolutePath: absFile,
969
972
  isDirectory: false,
973
+ fileName,
974
+ dirName: path5.dirname(relFromRoot),
975
+ extension,
976
+ pluralFileName: pluralize.plural(fileName),
970
977
  stubName
971
978
  };
972
979
  if (fs2.existsSync(absFile)) {
973
980
  return;
974
981
  }
975
982
  await hooks.runRegular("preCreateFile", ctx);
976
- const dir = path2.dirname(absFile);
983
+ const dir = path5.dirname(absFile);
977
984
  ensureDirSync(dir);
978
985
  if (stubName) {
979
986
  await hooks.runStub("preStub", ctx);
@@ -1016,7 +1023,7 @@ async function applyStructure(opts) {
1016
1023
  if (desiredPaths.has(cachedPath)) {
1017
1024
  continue;
1018
1025
  }
1019
- const abs = path2.resolve(projectRoot, cachedPath);
1026
+ const abs = path5.resolve(projectRoot, cachedPath);
1020
1027
  const stats = statSafeSync(abs);
1021
1028
  if (!stats) {
1022
1029
  cache.delete(cachedPath);
@@ -1026,11 +1033,17 @@ async function applyStructure(opts) {
1026
1033
  cache.delete(cachedPath);
1027
1034
  continue;
1028
1035
  }
1036
+ const extension = path5.extname(abs);
1037
+ const fileName = path5.basename(abs, extension);
1029
1038
  const ctx = {
1030
1039
  projectRoot,
1031
1040
  targetPath: cachedPath,
1032
1041
  absolutePath: abs,
1033
1042
  isDirectory: false,
1043
+ fileName,
1044
+ dirName: path5.dirname(cachedPath),
1045
+ extension,
1046
+ pluralFileName: pluralize.plural(fileName),
1034
1047
  stubName: entry?.createdByStub
1035
1048
  };
1036
1049
  await hooks.runRegular("preDeleteFile", ctx);
@@ -1062,16 +1075,16 @@ async function applyStructure(opts) {
1062
1075
  }
1063
1076
  }
1064
1077
  function getStructureFilesFromConfig(projectRoot, scaffoldDir, config) {
1065
- const baseDir = path2.resolve(projectRoot, scaffoldDir || SCAFFOLD_ROOT_DIR);
1078
+ const baseDir = path5.resolve(projectRoot, scaffoldDir || SCAFFOLD_ROOT_DIR);
1066
1079
  const files = [];
1067
1080
  if (config.groups && config.groups.length > 0) {
1068
1081
  for (const group of config.groups) {
1069
1082
  const structureFile = group.structureFile && group.structureFile.trim().length ? group.structureFile : `${group.name}.txt`;
1070
- files.push(path2.join(baseDir, structureFile));
1083
+ files.push(path5.join(baseDir, structureFile));
1071
1084
  }
1072
1085
  } else {
1073
1086
  const structureFile = config.structureFile || "structure.txt";
1074
- files.push(path2.join(baseDir, structureFile));
1087
+ files.push(path5.join(baseDir, structureFile));
1075
1088
  }
1076
1089
  return files;
1077
1090
  }
@@ -1113,7 +1126,7 @@ async function runOnce(cwd, options = {}) {
1113
1126
  const hooks = new HookRunner(config);
1114
1127
  if (config.groups && config.groups.length > 0) {
1115
1128
  for (const group of config.groups) {
1116
- const groupRootAbs = path2.resolve(projectRoot, group.root);
1129
+ const groupRootAbs = path5.resolve(projectRoot, group.root);
1117
1130
  const structure = resolveGroupStructure(scaffoldDir, group, config);
1118
1131
  const groupLogger = logger5.child(`[group:${group.name}]`);
1119
1132
  await applyStructure({
@@ -1158,12 +1171,12 @@ var DEFAULT_IGNORE = [
1158
1171
  "coverage/**"
1159
1172
  ];
1160
1173
  function scanDirectoryToStructureText(rootDir, options = {}) {
1161
- const absRoot = path2.resolve(rootDir);
1174
+ const absRoot = path5.resolve(rootDir);
1162
1175
  const lines = [];
1163
1176
  const ignorePatterns = options.ignore ?? DEFAULT_IGNORE;
1164
1177
  const maxDepth = options.maxDepth ?? Infinity;
1165
1178
  function isIgnored(absPath) {
1166
- const rel = toPosixPath(path2.relative(absRoot, absPath));
1179
+ const rel = toPosixPath(path5.relative(absRoot, absPath));
1167
1180
  if (!rel || rel === ".") return false;
1168
1181
  return ignorePatterns.some(
1169
1182
  (pattern) => minimatch(rel, pattern, { dot: true })
@@ -1184,7 +1197,7 @@ function scanDirectoryToStructureText(rootDir, options = {}) {
1184
1197
  });
1185
1198
  for (const dirent of dirents) {
1186
1199
  const name = dirent.name;
1187
- const absPath = path2.join(currentAbs, name);
1200
+ const absPath = path5.join(currentAbs, name);
1188
1201
  if (isIgnored(absPath)) continue;
1189
1202
  const indent = " ".repeat(depth);
1190
1203
  if (dirent.isDirectory()) {
@@ -1207,13 +1220,13 @@ async function scanProjectFromConfig(cwd, options = {}) {
1207
1220
  const onlyGroups = options.groups;
1208
1221
  const results = [];
1209
1222
  function scanGroup(cfg, group) {
1210
- const rootAbs = path2.resolve(projectRoot, group.root);
1223
+ const rootAbs = path5.resolve(projectRoot, group.root);
1211
1224
  const text = scanDirectoryToStructureText(rootAbs, {
1212
1225
  ignore: ignorePatterns,
1213
1226
  maxDepth
1214
1227
  });
1215
1228
  const structureFileName = group.structureFile ?? `${group.name}.txt`;
1216
- const structureFilePath = path2.join(scaffoldDir, structureFileName);
1229
+ const structureFilePath = path5.join(scaffoldDir, structureFileName);
1217
1230
  return {
1218
1231
  groupName: group.name,
1219
1232
  groupRoot: group.root,
@@ -1240,7 +1253,7 @@ async function scanProjectFromConfig(cwd, options = {}) {
1240
1253
  maxDepth
1241
1254
  });
1242
1255
  const structureFileName = config.structureFile ?? "structure.txt";
1243
- const structureFilePath = path2.join(scaffoldDir, structureFileName);
1256
+ const structureFilePath = path5.join(scaffoldDir, structureFileName);
1244
1257
  results.push({
1245
1258
  groupName: "default",
1246
1259
  groupRoot: ".",
@@ -1274,8 +1287,8 @@ async function ensureStructureFilesFromConfig(cwd, options = {}) {
1274
1287
  const seen = /* @__PURE__ */ new Set();
1275
1288
  const ensureFile = (fileName) => {
1276
1289
  if (!fileName) return;
1277
- const filePath = path2.join(scaffoldDir, fileName);
1278
- const key = path2.resolve(filePath);
1290
+ const filePath = path5.join(scaffoldDir, fileName);
1291
+ const key = path5.resolve(filePath);
1279
1292
  if (seen.has(key)) return;
1280
1293
  seen.add(key);
1281
1294
  if (fs2.existsSync(filePath)) {