@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 CHANGED
@@ -1,4 +1,4 @@
1
- import { l as FormatConfig } from './config-C0067l3c.cjs';
1
+ import { l as FormatConfig } from './config-DBARTF0g.cjs';
2
2
 
3
3
  type AstMode = 'strict' | 'loose';
4
4
  type DiagnosticSeverity = 'info' | 'warning' | 'error';
package/dist/ast.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { l as FormatConfig } from './config-C0067l3c.js';
1
+ import { l as FormatConfig } from './config-DBARTF0g.js';
2
2
 
3
3
  type AstMode = 'strict' | 'loose';
4
4
  type DiagnosticSeverity = 'info' | 'warning' | 'error';
package/dist/cli.cjs CHANGED
@@ -2,7 +2,7 @@
2
2
  'use strict';
3
3
 
4
4
  var readline = require('readline');
5
- var path2 = require('path');
5
+ var path5 = require('path');
6
6
  var fs8 = require('fs');
7
7
  var commander = require('commander');
8
8
  var os = require('os');
@@ -10,15 +10,17 @@ var crypto = require('crypto');
10
10
  var url = require('url');
11
11
  var esbuild = require('esbuild');
12
12
  var minimatch = require('minimatch');
13
+ var pluralize = require('pluralize');
13
14
  var chokidar = require('chokidar');
14
15
 
15
16
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
16
17
 
17
18
  var readline__default = /*#__PURE__*/_interopDefault(readline);
18
- var path2__default = /*#__PURE__*/_interopDefault(path2);
19
+ var path5__default = /*#__PURE__*/_interopDefault(path5);
19
20
  var fs8__default = /*#__PURE__*/_interopDefault(fs8);
20
21
  var os__default = /*#__PURE__*/_interopDefault(os);
21
22
  var crypto__default = /*#__PURE__*/_interopDefault(crypto);
23
+ var pluralize__default = /*#__PURE__*/_interopDefault(pluralize);
22
24
  var chokidar__default = /*#__PURE__*/_interopDefault(chokidar);
23
25
 
24
26
  // src/schema/index.ts
@@ -131,31 +133,31 @@ function statSafeSync(targetPath) {
131
133
  }
132
134
  }
133
135
  function toProjectRelativePath(projectRoot, absolutePath) {
134
- const absRoot = path2__default.default.resolve(projectRoot);
135
- const absTarget = path2__default.default.resolve(absolutePath);
136
- const rootWithSep = absRoot.endsWith(path2__default.default.sep) ? absRoot : absRoot + path2__default.default.sep;
136
+ const absRoot = path5__default.default.resolve(projectRoot);
137
+ const absTarget = path5__default.default.resolve(absolutePath);
138
+ const rootWithSep = absRoot.endsWith(path5__default.default.sep) ? absRoot : absRoot + path5__default.default.sep;
137
139
  if (!absTarget.startsWith(rootWithSep) && absTarget !== absRoot) {
138
140
  throw new Error(
139
141
  `Path "${absTarget}" is not inside project root "${absRoot}".`
140
142
  );
141
143
  }
142
- const rel = path2__default.default.relative(absRoot, absTarget);
144
+ const rel = path5__default.default.relative(absRoot, absTarget);
143
145
  return toPosixPath(rel);
144
146
  }
145
147
 
146
148
  // src/core/config-loader.ts
147
149
  var logger = defaultLogger.child("[config]");
148
150
  async function loadScaffoldConfig(cwd, options = {}) {
149
- const absCwd = path2__default.default.resolve(cwd);
150
- const initialScaffoldDir = options.scaffoldDir ? path2__default.default.resolve(absCwd, options.scaffoldDir) : path2__default.default.join(absCwd, SCAFFOLD_ROOT_DIR);
151
+ const absCwd = path5__default.default.resolve(cwd);
152
+ const initialScaffoldDir = options.scaffoldDir ? path5__default.default.resolve(absCwd, options.scaffoldDir) : path5__default.default.join(absCwd, SCAFFOLD_ROOT_DIR);
151
153
  const configPath = options.configPath ?? resolveConfigPath(initialScaffoldDir);
152
154
  const config = await importConfig(configPath);
153
155
  let configRoot = absCwd;
154
156
  if (config.root) {
155
- configRoot = path2__default.default.resolve(absCwd, config.root);
157
+ configRoot = path5__default.default.resolve(absCwd, config.root);
156
158
  }
157
- const scaffoldDir = options.scaffoldDir ? path2__default.default.resolve(absCwd, options.scaffoldDir) : path2__default.default.join(configRoot, SCAFFOLD_ROOT_DIR);
158
- const baseRoot = config.base ? path2__default.default.resolve(configRoot, config.base) : configRoot;
159
+ const scaffoldDir = options.scaffoldDir ? path5__default.default.resolve(absCwd, options.scaffoldDir) : path5__default.default.join(configRoot, SCAFFOLD_ROOT_DIR);
160
+ const baseRoot = config.base ? path5__default.default.resolve(configRoot, config.base) : configRoot;
159
161
  logger.debug(
160
162
  `Loaded config: configRoot=${configRoot}, baseRoot=${baseRoot}, scaffoldDir=${scaffoldDir}`
161
163
  );
@@ -174,7 +176,7 @@ function resolveConfigPath(scaffoldDir) {
174
176
  "config.cjs"
175
177
  ];
176
178
  for (const file of candidates) {
177
- const full = path2__default.default.join(scaffoldDir, file);
179
+ const full = path5__default.default.join(scaffoldDir, file);
178
180
  if (fs8__default.default.existsSync(full)) {
179
181
  return full;
180
182
  }
@@ -186,7 +188,7 @@ function resolveConfigPath(scaffoldDir) {
186
188
  );
187
189
  }
188
190
  async function importConfig(configPath) {
189
- const ext = path2__default.default.extname(configPath).toLowerCase();
191
+ const ext = path5__default.default.extname(configPath).toLowerCase();
190
192
  if (ext === ".ts" || ext === ".tsx") {
191
193
  return importTsConfig(configPath);
192
194
  }
@@ -198,9 +200,9 @@ async function importTsConfig(configPath) {
198
200
  const source = fs8__default.default.readFileSync(configPath, "utf8");
199
201
  const stat = fs8__default.default.statSync(configPath);
200
202
  const hash = crypto__default.default.createHash("sha1").update(configPath).update(String(stat.mtimeMs)).digest("hex");
201
- const tmpDir = path2__default.default.join(os__default.default.tmpdir(), "timeax-scaffold-config");
203
+ const tmpDir = path5__default.default.join(os__default.default.tmpdir(), "timeax-scaffold-config");
202
204
  ensureDirSync(tmpDir);
203
- const tmpFile = path2__default.default.join(tmpDir, `${hash}.mjs`);
205
+ const tmpFile = path5__default.default.join(tmpDir, `${hash}.mjs`);
204
206
  if (!fs8__default.default.existsSync(tmpFile)) {
205
207
  const result = await esbuild.transform(source, {
206
208
  loader: "ts",
@@ -794,7 +796,7 @@ function resolveGroupStructure(scaffoldDir, group, config) {
794
796
  return group.structure;
795
797
  }
796
798
  const fileName = group.structureFile ?? `${group.name}.txt`;
797
- const filePath = path2__default.default.join(scaffoldDir, fileName);
799
+ const filePath = path5__default.default.join(scaffoldDir, fileName);
798
800
  if (!fs8__default.default.existsSync(filePath)) {
799
801
  throw new Error(
800
802
  `@timeax/scaffold: Group "${group.name}" has no structure. Expected file "${fileName}" in "${scaffoldDir}".`
@@ -810,7 +812,7 @@ function resolveSingleStructure(scaffoldDir, config) {
810
812
  return config.structure;
811
813
  }
812
814
  const fileName = config.structureFile ?? "structure.txt";
813
- const filePath = path2__default.default.join(scaffoldDir, fileName);
815
+ const filePath = path5__default.default.join(scaffoldDir, fileName);
814
816
  if (!fs8__default.default.existsSync(filePath)) {
815
817
  throw new Error(
816
818
  `@timeax/scaffold: No structure defined. Expected "${fileName}" in "${scaffoldDir}".`
@@ -832,7 +834,7 @@ var CacheManager = class {
832
834
  }
833
835
  cache = DEFAULT_CACHE;
834
836
  get cachePathAbs() {
835
- return path2__default.default.resolve(this.projectRoot, this.cacheFileRelPath);
837
+ return path5__default.default.resolve(this.projectRoot, this.cacheFileRelPath);
836
838
  }
837
839
  load() {
838
840
  const cachePath = this.cachePathAbs;
@@ -856,7 +858,7 @@ var CacheManager = class {
856
858
  }
857
859
  save() {
858
860
  const cachePath = this.cachePathAbs;
859
- const dir = path2__default.default.dirname(cachePath);
861
+ const dir = path5__default.default.dirname(cachePath);
860
862
  ensureDirSync(dir);
861
863
  fs8__default.default.writeFileSync(cachePath, JSON.stringify(this.cache, null, 2), "utf8");
862
864
  }
@@ -941,9 +943,9 @@ async function applyStructure(opts) {
941
943
  interactiveDelete
942
944
  } = opts;
943
945
  const logger6 = opts.logger ?? defaultLogger.child(groupName ? `[apply:${groupName}]` : "[apply]");
944
- const projectRootAbs = path2__default.default.resolve(projectRoot);
945
- const baseDirAbs = path2__default.default.resolve(baseDir);
946
- baseDirAbs.endsWith(path2__default.default.sep) ? baseDirAbs : baseDirAbs + path2__default.default.sep;
946
+ const projectRootAbs = path5__default.default.resolve(projectRoot);
947
+ const baseDirAbs = path5__default.default.resolve(baseDir);
948
+ baseDirAbs.endsWith(path5__default.default.sep) ? baseDirAbs : baseDirAbs + path5__default.default.sep;
947
949
  const desiredPaths = /* @__PURE__ */ new Set();
948
950
  const threshold = sizePromptThreshold ?? config.sizePromptThreshold ?? 128 * 1024;
949
951
  async function walk(entry, inheritedStub) {
@@ -956,7 +958,7 @@ async function applyStructure(opts) {
956
958
  }
957
959
  async function handleDir(entry, inheritedStub) {
958
960
  const relFromBase = entry.path.replace(/^[./]+/, "");
959
- const absDir = path2__default.default.resolve(baseDirAbs, relFromBase);
961
+ const absDir = path5__default.default.resolve(baseDirAbs, relFromBase);
960
962
  const relFromRoot = toPosixPath(
961
963
  toProjectRelativePath(projectRootAbs, absDir)
962
964
  );
@@ -971,24 +973,30 @@ async function applyStructure(opts) {
971
973
  }
972
974
  async function handleFile(entry, inheritedStub) {
973
975
  const relFromBase = entry.path.replace(/^[./]+/, "");
974
- const absFile = path2__default.default.resolve(baseDirAbs, relFromBase);
976
+ const absFile = path5__default.default.resolve(baseDirAbs, relFromBase);
975
977
  const relFromRoot = toPosixPath(
976
978
  toProjectRelativePath(projectRootAbs, absFile)
977
979
  );
978
980
  desiredPaths.add(relFromRoot);
979
981
  const stubName = entry.stub ?? inheritedStub;
982
+ const extension = path5__default.default.extname(relFromRoot);
983
+ const fileName = path5__default.default.basename(relFromRoot, extension);
980
984
  const ctx = {
981
985
  projectRoot: projectRootAbs,
982
986
  targetPath: relFromRoot,
983
987
  absolutePath: absFile,
984
988
  isDirectory: false,
989
+ fileName,
990
+ dirName: path5__default.default.dirname(relFromRoot),
991
+ extension,
992
+ pluralFileName: pluralize__default.default.plural(fileName),
985
993
  stubName
986
994
  };
987
995
  if (fs8__default.default.existsSync(absFile)) {
988
996
  return;
989
997
  }
990
998
  await hooks.runRegular("preCreateFile", ctx);
991
- const dir = path2__default.default.dirname(absFile);
999
+ const dir = path5__default.default.dirname(absFile);
992
1000
  ensureDirSync(dir);
993
1001
  if (stubName) {
994
1002
  await hooks.runStub("preStub", ctx);
@@ -1031,7 +1039,7 @@ async function applyStructure(opts) {
1031
1039
  if (desiredPaths.has(cachedPath)) {
1032
1040
  continue;
1033
1041
  }
1034
- const abs = path2__default.default.resolve(projectRoot, cachedPath);
1042
+ const abs = path5__default.default.resolve(projectRoot, cachedPath);
1035
1043
  const stats = statSafeSync(abs);
1036
1044
  if (!stats) {
1037
1045
  cache.delete(cachedPath);
@@ -1041,11 +1049,17 @@ async function applyStructure(opts) {
1041
1049
  cache.delete(cachedPath);
1042
1050
  continue;
1043
1051
  }
1052
+ const extension = path5__default.default.extname(abs);
1053
+ const fileName = path5__default.default.basename(abs, extension);
1044
1054
  const ctx = {
1045
1055
  projectRoot,
1046
1056
  targetPath: cachedPath,
1047
1057
  absolutePath: abs,
1048
1058
  isDirectory: false,
1059
+ fileName,
1060
+ dirName: path5__default.default.dirname(cachedPath),
1061
+ extension,
1062
+ pluralFileName: pluralize__default.default.plural(fileName),
1049
1063
  stubName: entry?.createdByStub
1050
1064
  };
1051
1065
  await hooks.runRegular("preDeleteFile", ctx);
@@ -1087,12 +1101,12 @@ var DEFAULT_IGNORE = [
1087
1101
  "coverage/**"
1088
1102
  ];
1089
1103
  function scanDirectoryToStructureText(rootDir, options = {}) {
1090
- const absRoot = path2__default.default.resolve(rootDir);
1104
+ const absRoot = path5__default.default.resolve(rootDir);
1091
1105
  const lines = [];
1092
1106
  const ignorePatterns = options.ignore ?? DEFAULT_IGNORE;
1093
1107
  const maxDepth = options.maxDepth ?? Infinity;
1094
1108
  function isIgnored(absPath) {
1095
- const rel = toPosixPath(path2__default.default.relative(absRoot, absPath));
1109
+ const rel = toPosixPath(path5__default.default.relative(absRoot, absPath));
1096
1110
  if (!rel || rel === ".") return false;
1097
1111
  return ignorePatterns.some(
1098
1112
  (pattern) => minimatch.minimatch(rel, pattern, { dot: true })
@@ -1113,7 +1127,7 @@ function scanDirectoryToStructureText(rootDir, options = {}) {
1113
1127
  });
1114
1128
  for (const dirent of dirents) {
1115
1129
  const name = dirent.name;
1116
- const absPath = path2__default.default.join(currentAbs, name);
1130
+ const absPath = path5__default.default.join(currentAbs, name);
1117
1131
  if (isIgnored(absPath)) continue;
1118
1132
  const indent = " ".repeat(depth);
1119
1133
  if (dirent.isDirectory()) {
@@ -1136,13 +1150,13 @@ async function scanProjectFromConfig(cwd, options = {}) {
1136
1150
  const onlyGroups = options.groups;
1137
1151
  const results = [];
1138
1152
  function scanGroup(cfg, group) {
1139
- const rootAbs = path2__default.default.resolve(projectRoot, group.root);
1153
+ const rootAbs = path5__default.default.resolve(projectRoot, group.root);
1140
1154
  const text = scanDirectoryToStructureText(rootAbs, {
1141
1155
  ignore: ignorePatterns,
1142
1156
  maxDepth
1143
1157
  });
1144
1158
  const structureFileName = group.structureFile ?? `${group.name}.txt`;
1145
- const structureFilePath = path2__default.default.join(scaffoldDir, structureFileName);
1159
+ const structureFilePath = path5__default.default.join(scaffoldDir, structureFileName);
1146
1160
  return {
1147
1161
  groupName: group.name,
1148
1162
  groupRoot: group.root,
@@ -1169,7 +1183,7 @@ async function scanProjectFromConfig(cwd, options = {}) {
1169
1183
  maxDepth
1170
1184
  });
1171
1185
  const structureFileName = config.structureFile ?? "structure.txt";
1172
- const structureFilePath = path2__default.default.join(scaffoldDir, structureFileName);
1186
+ const structureFilePath = path5__default.default.join(scaffoldDir, structureFileName);
1173
1187
  results.push({
1174
1188
  groupName: "default",
1175
1189
  groupRoot: ".",
@@ -1203,8 +1217,8 @@ async function ensureStructureFilesFromConfig(cwd, options = {}) {
1203
1217
  const seen = /* @__PURE__ */ new Set();
1204
1218
  const ensureFile = (fileName) => {
1205
1219
  if (!fileName) return;
1206
- const filePath = path2__default.default.join(scaffoldDir, fileName);
1207
- const key = path2__default.default.resolve(filePath);
1220
+ const filePath = path5__default.default.join(scaffoldDir, fileName);
1221
+ const key = path5__default.default.resolve(filePath);
1208
1222
  if (seen.has(key)) return;
1209
1223
  seen.add(key);
1210
1224
  if (fs8__default.default.existsSync(filePath)) {
@@ -1235,16 +1249,16 @@ async function ensureStructureFilesFromConfig(cwd, options = {}) {
1235
1249
 
1236
1250
  // src/core/format.ts
1237
1251
  function getStructureFilesFromConfig(projectRoot, scaffoldDir, config) {
1238
- const baseDir = path2__default.default.resolve(projectRoot, scaffoldDir || SCAFFOLD_ROOT_DIR);
1252
+ const baseDir = path5__default.default.resolve(projectRoot, scaffoldDir || SCAFFOLD_ROOT_DIR);
1239
1253
  const files = [];
1240
1254
  if (config.groups && config.groups.length > 0) {
1241
1255
  for (const group of config.groups) {
1242
1256
  const structureFile = group.structureFile && group.structureFile.trim().length ? group.structureFile : `${group.name}.txt`;
1243
- files.push(path2__default.default.join(baseDir, structureFile));
1257
+ files.push(path5__default.default.join(baseDir, structureFile));
1244
1258
  }
1245
1259
  } else {
1246
1260
  const structureFile = config.structureFile || "structure.txt";
1247
- files.push(path2__default.default.join(baseDir, structureFile));
1261
+ files.push(path5__default.default.join(baseDir, structureFile));
1248
1262
  }
1249
1263
  return files;
1250
1264
  }
@@ -1286,7 +1300,7 @@ async function runOnce(cwd, options = {}) {
1286
1300
  const hooks = new HookRunner(config);
1287
1301
  if (config.groups && config.groups.length > 0) {
1288
1302
  for (const group of config.groups) {
1289
- const groupRootAbs = path2__default.default.resolve(projectRoot, group.root);
1303
+ const groupRootAbs = path5__default.default.resolve(projectRoot, group.root);
1290
1304
  const structure = resolveGroupStructure(scaffoldDir, group, config);
1291
1305
  const groupLogger = logger6.child(`[group:${group.name}]`);
1292
1306
  await applyStructure({
@@ -1322,7 +1336,7 @@ async function runOnce(cwd, options = {}) {
1322
1336
  }
1323
1337
  function watchScaffold(cwd, options = {}) {
1324
1338
  const logger6 = options.logger ?? defaultLogger.child("[watch]");
1325
- const scaffoldDir = options.scaffoldDir ? path2__default.default.resolve(cwd, options.scaffoldDir) : path2__default.default.resolve(cwd, SCAFFOLD_ROOT_DIR);
1339
+ const scaffoldDir = options.scaffoldDir ? path5__default.default.resolve(cwd, options.scaffoldDir) : path5__default.default.resolve(cwd, SCAFFOLD_ROOT_DIR);
1326
1340
  const debounceMs = options.debounceMs ?? 150;
1327
1341
  logger6.info(`Watching scaffold directory: ${scaffoldDir}`);
1328
1342
  let timer;
@@ -1357,11 +1371,11 @@ function watchScaffold(cwd, options = {}) {
1357
1371
  timer = setTimeout(run, debounceMs);
1358
1372
  }
1359
1373
  function isInteresting(filePath) {
1360
- const rel = path2__default.default.relative(scaffoldDir, filePath);
1374
+ const rel = path5__default.default.relative(scaffoldDir, filePath);
1361
1375
  if (rel.startsWith("..")) return false;
1362
- const base = path2__default.default.basename(filePath).toLowerCase();
1376
+ const base = path5__default.default.basename(filePath).toLowerCase();
1363
1377
  if (base.startsWith("config.")) return true;
1364
- const ext = path2__default.default.extname(base);
1378
+ const ext = path5__default.default.extname(base);
1365
1379
  return ext === ".txt" || ext === ".tss" || ext === ".stx";
1366
1380
  }
1367
1381
  const watcher = chokidar__default.default.watch(scaffoldDir, {
@@ -1478,12 +1492,12 @@ var DEFAULT_STRUCTURE_TXT = `# ${SCAFFOLD_ROOT_DIR}/structure.txt
1478
1492
  `;
1479
1493
  async function initScaffold(cwd, options = {}) {
1480
1494
  const scaffoldDirRel = options.scaffoldDir ?? SCAFFOLD_ROOT_DIR;
1481
- const scaffoldDirAbs = path2__default.default.resolve(cwd, scaffoldDirRel);
1495
+ const scaffoldDirAbs = path5__default.default.resolve(cwd, scaffoldDirRel);
1482
1496
  const configFileName = options.configFileName ?? "config.ts";
1483
1497
  const structureFileName = options.structureFileName ?? "structure.txt";
1484
1498
  ensureDirSync(scaffoldDirAbs);
1485
- const configPath = path2__default.default.join(scaffoldDirAbs, configFileName);
1486
- const structurePath = path2__default.default.join(scaffoldDirAbs, structureFileName);
1499
+ const configPath = path5__default.default.join(scaffoldDirAbs, configFileName);
1500
+ const structurePath = path5__default.default.join(scaffoldDirAbs, structureFileName);
1487
1501
  let createdConfig = false;
1488
1502
  let createdStructure = false;
1489
1503
  if (fs8__default.default.existsSync(configPath) && !options.force) {
@@ -1547,9 +1561,9 @@ function askYesNo(question) {
1547
1561
  }
1548
1562
  async function handleRunCommand(cwd, baseOpts) {
1549
1563
  const logger6 = createCliLogger(baseOpts);
1550
- const configPath = baseOpts.config ? path2__default.default.resolve(cwd, baseOpts.config) : void 0;
1551
- const scaffoldDir = baseOpts.dir ? path2__default.default.resolve(cwd, baseOpts.dir) : void 0;
1552
- const resolvedScaffoldDir = scaffoldDir ?? path2__default.default.resolve(cwd, SCAFFOLD_ROOT_DIR);
1564
+ const configPath = baseOpts.config ? path5__default.default.resolve(cwd, baseOpts.config) : void 0;
1565
+ const scaffoldDir = baseOpts.dir ? path5__default.default.resolve(cwd, baseOpts.dir) : void 0;
1566
+ const resolvedScaffoldDir = scaffoldDir ?? path5__default.default.resolve(cwd, SCAFFOLD_ROOT_DIR);
1553
1567
  logger6.debug(
1554
1568
  `Starting scaffold (cwd=${cwd}, config=${configPath ?? "auto"}, dir=${resolvedScaffoldDir}, watch=${baseOpts.watch ? "yes" : "no"})`
1555
1569
  );
@@ -1589,15 +1603,15 @@ async function handleScanCommand(cwd, scanOpts, baseOpts) {
1589
1603
  });
1590
1604
  return;
1591
1605
  }
1592
- const rootDir = path2__default.default.resolve(cwd, scanOpts.root ?? ".");
1606
+ const rootDir = path5__default.default.resolve(cwd, scanOpts.root ?? ".");
1593
1607
  const ignore = scanOpts.ignore ?? [];
1594
1608
  logger6.info(`Scanning directory for structure: ${rootDir}`);
1595
1609
  const text = scanDirectoryToStructureText(rootDir, {
1596
1610
  ignore
1597
1611
  });
1598
1612
  if (scanOpts.out) {
1599
- const outPath = path2__default.default.resolve(cwd, scanOpts.out);
1600
- const dir = path2__default.default.dirname(outPath);
1613
+ const outPath = path5__default.default.resolve(cwd, scanOpts.out);
1614
+ const dir = path5__default.default.dirname(outPath);
1601
1615
  ensureDirSync(dir);
1602
1616
  fs8__default.default.writeFileSync(outPath, text, "utf8");
1603
1617
  logger6.info(`Wrote structure to ${outPath}`);