@timeax/scaffold 0.0.1 → 0.0.3

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/cli.mjs CHANGED
@@ -10,6 +10,9 @@ import { transform } from 'esbuild';
10
10
  import { minimatch } from 'minimatch';
11
11
  import chokidar from 'chokidar';
12
12
 
13
+ // src/schema/index.ts
14
+ var SCAFFOLD_ROOT_DIR = ".scaffold";
15
+
13
16
  // src/util/logger.ts
14
17
  var supportsColor = typeof process !== "undefined" && process.stdout && process.stdout.isTTY && process.env.NO_COLOR !== "1";
15
18
  function wrap(code) {
@@ -133,14 +136,14 @@ function toProjectRelativePath(projectRoot, absolutePath) {
133
136
  var logger = defaultLogger.child("[config]");
134
137
  async function loadScaffoldConfig(cwd, options = {}) {
135
138
  const absCwd = path2.resolve(cwd);
136
- const initialScaffoldDir = options.scaffoldDir ? path2.resolve(absCwd, options.scaffoldDir) : path2.join(absCwd, "scaffold");
139
+ const initialScaffoldDir = options.scaffoldDir ? path2.resolve(absCwd, options.scaffoldDir) : path2.join(absCwd, SCAFFOLD_ROOT_DIR);
137
140
  const configPath = options.configPath ?? resolveConfigPath(initialScaffoldDir);
138
141
  const config = await importConfig(configPath);
139
142
  let configRoot = absCwd;
140
143
  if (config.root) {
141
144
  configRoot = path2.resolve(absCwd, config.root);
142
145
  }
143
- const scaffoldDir = options.scaffoldDir ? path2.resolve(absCwd, options.scaffoldDir) : path2.join(configRoot, "scaffold");
146
+ const scaffoldDir = options.scaffoldDir ? path2.resolve(absCwd, options.scaffoldDir) : path2.join(configRoot, SCAFFOLD_ROOT_DIR);
144
147
  const baseRoot = config.base ? path2.resolve(configRoot, config.base) : configRoot;
145
148
  logger.debug(
146
149
  `Loaded config: configRoot=${configRoot}, baseRoot=${baseRoot}, scaffoldDir=${scaffoldDir}`
@@ -205,14 +208,50 @@ async function importTsConfig(configPath) {
205
208
  }
206
209
 
207
210
  // src/core/structure-txt.ts
211
+ function stripInlineComment(content) {
212
+ let cutIndex = -1;
213
+ const len = content.length;
214
+ for (let i = 0; i < len; i++) {
215
+ const ch = content[i];
216
+ const prev = i > 0 ? content[i - 1] : "";
217
+ if (ch === "#") {
218
+ if (i === 0) continue;
219
+ if (prev === " " || prev === " ") {
220
+ cutIndex = i;
221
+ break;
222
+ }
223
+ }
224
+ if (ch === "/" && i + 1 < len && content[i + 1] === "/" && (prev === " " || prev === " ")) {
225
+ cutIndex = i;
226
+ break;
227
+ }
228
+ }
229
+ if (cutIndex === -1) {
230
+ return content.trimEnd();
231
+ }
232
+ return content.slice(0, cutIndex).trimEnd();
233
+ }
208
234
  function parseLine(line, lineNo) {
209
- const match = line.match(/^(\s*)(.+)$/);
235
+ const match = line.match(/^(\s*)(.*)$/);
210
236
  if (!match) return null;
211
237
  const indentSpaces = match[1].length;
212
- const rest = match[2].trim();
213
- if (!rest || rest.startsWith("#")) return null;
214
- const parts = rest.split(/\s+/);
238
+ let rest = match[2];
239
+ if (!rest.trim()) return null;
240
+ const trimmedRest = rest.trimStart();
241
+ if (trimmedRest.startsWith("#") || trimmedRest.startsWith("//")) {
242
+ return null;
243
+ }
244
+ const stripped = stripInlineComment(rest);
245
+ const trimmed = stripped.trim();
246
+ if (!trimmed) return null;
247
+ const parts = trimmed.split(/\s+/);
248
+ if (!parts.length) return null;
215
249
  const pathToken = parts[0];
250
+ if (pathToken.includes(":")) {
251
+ throw new Error(
252
+ `structure.txt: ":" is reserved for annotations (@stub:, @include:, etc). Invalid path "${pathToken}" on line ${lineNo}.`
253
+ );
254
+ }
216
255
  let stub;
217
256
  const include = [];
218
257
  const exclude = [];
@@ -244,7 +283,7 @@ function parseLine(line, lineNo) {
244
283
  exclude: exclude.length ? exclude : void 0
245
284
  };
246
285
  }
247
- function parseStructureText(text) {
286
+ function parseStructureText(text, indentStep = 2) {
248
287
  const lines = text.split(/\r?\n/);
249
288
  const parsed = [];
250
289
  for (let i = 0; i < lines.length; i++) {
@@ -254,15 +293,14 @@ function parseStructureText(text) {
254
293
  }
255
294
  const rootEntries = [];
256
295
  const stack = [];
257
- const INDENT_STEP = 2;
258
296
  for (const p of parsed) {
259
297
  const { indentSpaces, lineNo } = p;
260
- if (indentSpaces % INDENT_STEP !== 0) {
298
+ if (indentSpaces % indentStep !== 0) {
261
299
  throw new Error(
262
- `structure.txt: Invalid indent on line ${lineNo}. Indent must be multiples of ${INDENT_STEP} spaces.`
300
+ `structure.txt: Invalid indent on line ${lineNo}. Indent must be multiples of ${indentStep} spaces.`
263
301
  );
264
302
  }
265
- const level = indentSpaces / INDENT_STEP;
303
+ const level = indentSpaces / indentStep;
266
304
  if (level > stack.length) {
267
305
  if (level !== stack.length + 1) {
268
306
  throw new Error(
@@ -828,6 +866,45 @@ async function writeScannedStructuresFromConfig(cwd, options = {}) {
828
866
  );
829
867
  }
830
868
  }
869
+ async function ensureStructureFilesFromConfig(cwd, options = {}) {
870
+ const { config, scaffoldDir } = await loadScaffoldConfig(cwd, {
871
+ scaffoldDir: options.scaffoldDirOverride
872
+ });
873
+ ensureDirSync(scaffoldDir);
874
+ const created = [];
875
+ const existing = [];
876
+ const seen = /* @__PURE__ */ new Set();
877
+ const ensureFile = (fileName) => {
878
+ if (!fileName) return;
879
+ const filePath = path2.join(scaffoldDir, fileName);
880
+ const key = path2.resolve(filePath);
881
+ if (seen.has(key)) return;
882
+ seen.add(key);
883
+ if (fs7.existsSync(filePath)) {
884
+ existing.push(filePath);
885
+ return;
886
+ }
887
+ const header = `# ${fileName}
888
+ # Structure file for @timeax/scaffold
889
+ # Define your desired folders/files here.
890
+ `;
891
+ fs7.writeFileSync(filePath, header, "utf8");
892
+ created.push(filePath);
893
+ };
894
+ if (config.groups && config.groups.length > 0) {
895
+ for (const group of config.groups) {
896
+ const fileName = group.structureFile ?? `${group.name}.txt`;
897
+ ensureFile(fileName);
898
+ }
899
+ } else {
900
+ const fileName = config.structureFile ?? "structure.txt";
901
+ ensureFile(fileName);
902
+ }
903
+ logger4.debug(
904
+ `ensureStructureFilesFromConfig: created=${created.length}, existing=${existing.length}`
905
+ );
906
+ return { created, existing };
907
+ }
831
908
  var logger5 = defaultLogger.child("[init]");
832
909
  var DEFAULT_CONFIG_TS = `import type { ScaffoldConfig } from '@timeax/scaffold';
833
910
 
@@ -846,7 +923,10 @@ const config: ScaffoldConfig = {
846
923
  // base: 'src', // apply to <root>/src
847
924
  // base: '..', // apply to parent of <root>
848
925
  // base: '.',
849
-
926
+
927
+ // Number of spaces per indent level in structure files (default: 2).
928
+ // indentStep: 2,
929
+
850
930
  // Cache file path, relative to base.
851
931
  // cacheFile: '.scaffold-cache.json',
852
932
 
@@ -878,7 +958,7 @@ const config: ScaffoldConfig = {
878
958
 
879
959
  export default config;
880
960
  `;
881
- var DEFAULT_STRUCTURE_TXT = `# scaffold/structure.txt
961
+ var DEFAULT_STRUCTURE_TXT = `# ${SCAFFOLD_ROOT_DIR}/structure.txt
882
962
  # Example structure definition.
883
963
  # - Indent with 2 spaces per level
884
964
  # - Directories must end with "/"
@@ -890,7 +970,7 @@ var DEFAULT_STRUCTURE_TXT = `# scaffold/structure.txt
890
970
  # index.ts
891
971
  `;
892
972
  async function initScaffold(cwd, options = {}) {
893
- const scaffoldDirRel = options.scaffoldDir ?? "scaffold";
973
+ const scaffoldDirRel = options.scaffoldDir ?? SCAFFOLD_ROOT_DIR;
894
974
  const scaffoldDirAbs = path2.resolve(cwd, scaffoldDirRel);
895
975
  const configFileName = options.configFileName ?? "config.ts";
896
976
  const structureFileName = options.structureFileName ?? "structure.txt";
@@ -1025,6 +1105,21 @@ async function handleInitCommand(cwd, initOpts, baseOpts) {
1025
1105
  `Done. Config: ${result.configPath}, Structure: ${result.structurePath}`
1026
1106
  );
1027
1107
  }
1108
+ async function handleStructuresCommand(cwd, baseOpts) {
1109
+ const logger6 = createCliLogger(baseOpts);
1110
+ logger6.info("Ensuring structure files declared in config exist...");
1111
+ const { created, existing } = await ensureStructureFilesFromConfig(cwd, {
1112
+ scaffoldDirOverride: baseOpts.dir
1113
+ });
1114
+ if (created.length === 0) {
1115
+ logger6.info("All structure files already exist. Nothing to do.");
1116
+ } else {
1117
+ for (const filePath of created) {
1118
+ logger6.info(`Created structure file: ${filePath}`);
1119
+ }
1120
+ }
1121
+ existing.forEach((p) => logger6.debug(`Structure file already exists: ${p}`));
1122
+ }
1028
1123
  async function main() {
1029
1124
  const cwd = process.cwd();
1030
1125
  const program = new Command();
@@ -1060,6 +1155,12 @@ async function main() {
1060
1155
  program.action(async (opts) => {
1061
1156
  await handleRunCommand(cwd, opts);
1062
1157
  });
1158
+ program.command("structures").description(
1159
+ "Create missing structure files specified in the config (does not overwrite existing files)"
1160
+ ).action(async (_opts, cmd) => {
1161
+ const baseOpts = cmd.parent?.opts() ?? {};
1162
+ await handleStructuresCommand(cwd, baseOpts);
1163
+ });
1063
1164
  await program.parseAsync(process.argv);
1064
1165
  }
1065
1166
  main().catch((err) => {