@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.cjs CHANGED
@@ -21,6 +21,9 @@ var os__default = /*#__PURE__*/_interopDefault(os);
21
21
  var crypto__default = /*#__PURE__*/_interopDefault(crypto);
22
22
  var chokidar__default = /*#__PURE__*/_interopDefault(chokidar);
23
23
 
24
+ // src/schema/index.ts
25
+ var SCAFFOLD_ROOT_DIR = ".scaffold";
26
+
24
27
  // src/util/logger.ts
25
28
  var supportsColor = typeof process !== "undefined" && process.stdout && process.stdout.isTTY && process.env.NO_COLOR !== "1";
26
29
  function wrap(code) {
@@ -144,14 +147,14 @@ function toProjectRelativePath(projectRoot, absolutePath) {
144
147
  var logger = defaultLogger.child("[config]");
145
148
  async function loadScaffoldConfig(cwd, options = {}) {
146
149
  const absCwd = path2__default.default.resolve(cwd);
147
- const initialScaffoldDir = options.scaffoldDir ? path2__default.default.resolve(absCwd, options.scaffoldDir) : path2__default.default.join(absCwd, "scaffold");
150
+ const initialScaffoldDir = options.scaffoldDir ? path2__default.default.resolve(absCwd, options.scaffoldDir) : path2__default.default.join(absCwd, SCAFFOLD_ROOT_DIR);
148
151
  const configPath = options.configPath ?? resolveConfigPath(initialScaffoldDir);
149
152
  const config = await importConfig(configPath);
150
153
  let configRoot = absCwd;
151
154
  if (config.root) {
152
155
  configRoot = path2__default.default.resolve(absCwd, config.root);
153
156
  }
154
- const scaffoldDir = options.scaffoldDir ? path2__default.default.resolve(absCwd, options.scaffoldDir) : path2__default.default.join(configRoot, "scaffold");
157
+ const scaffoldDir = options.scaffoldDir ? path2__default.default.resolve(absCwd, options.scaffoldDir) : path2__default.default.join(configRoot, SCAFFOLD_ROOT_DIR);
155
158
  const baseRoot = config.base ? path2__default.default.resolve(configRoot, config.base) : configRoot;
156
159
  logger.debug(
157
160
  `Loaded config: configRoot=${configRoot}, baseRoot=${baseRoot}, scaffoldDir=${scaffoldDir}`
@@ -216,14 +219,50 @@ async function importTsConfig(configPath) {
216
219
  }
217
220
 
218
221
  // src/core/structure-txt.ts
222
+ function stripInlineComment(content) {
223
+ let cutIndex = -1;
224
+ const len = content.length;
225
+ for (let i = 0; i < len; i++) {
226
+ const ch = content[i];
227
+ const prev = i > 0 ? content[i - 1] : "";
228
+ if (ch === "#") {
229
+ if (i === 0) continue;
230
+ if (prev === " " || prev === " ") {
231
+ cutIndex = i;
232
+ break;
233
+ }
234
+ }
235
+ if (ch === "/" && i + 1 < len && content[i + 1] === "/" && (prev === " " || prev === " ")) {
236
+ cutIndex = i;
237
+ break;
238
+ }
239
+ }
240
+ if (cutIndex === -1) {
241
+ return content.trimEnd();
242
+ }
243
+ return content.slice(0, cutIndex).trimEnd();
244
+ }
219
245
  function parseLine(line, lineNo) {
220
- const match = line.match(/^(\s*)(.+)$/);
246
+ const match = line.match(/^(\s*)(.*)$/);
221
247
  if (!match) return null;
222
248
  const indentSpaces = match[1].length;
223
- const rest = match[2].trim();
224
- if (!rest || rest.startsWith("#")) return null;
225
- const parts = rest.split(/\s+/);
249
+ let rest = match[2];
250
+ if (!rest.trim()) return null;
251
+ const trimmedRest = rest.trimStart();
252
+ if (trimmedRest.startsWith("#") || trimmedRest.startsWith("//")) {
253
+ return null;
254
+ }
255
+ const stripped = stripInlineComment(rest);
256
+ const trimmed = stripped.trim();
257
+ if (!trimmed) return null;
258
+ const parts = trimmed.split(/\s+/);
259
+ if (!parts.length) return null;
226
260
  const pathToken = parts[0];
261
+ if (pathToken.includes(":")) {
262
+ throw new Error(
263
+ `structure.txt: ":" is reserved for annotations (@stub:, @include:, etc). Invalid path "${pathToken}" on line ${lineNo}.`
264
+ );
265
+ }
227
266
  let stub;
228
267
  const include = [];
229
268
  const exclude = [];
@@ -255,7 +294,7 @@ function parseLine(line, lineNo) {
255
294
  exclude: exclude.length ? exclude : void 0
256
295
  };
257
296
  }
258
- function parseStructureText(text) {
297
+ function parseStructureText(text, indentStep = 2) {
259
298
  const lines = text.split(/\r?\n/);
260
299
  const parsed = [];
261
300
  for (let i = 0; i < lines.length; i++) {
@@ -265,15 +304,14 @@ function parseStructureText(text) {
265
304
  }
266
305
  const rootEntries = [];
267
306
  const stack = [];
268
- const INDENT_STEP = 2;
269
307
  for (const p of parsed) {
270
308
  const { indentSpaces, lineNo } = p;
271
- if (indentSpaces % INDENT_STEP !== 0) {
309
+ if (indentSpaces % indentStep !== 0) {
272
310
  throw new Error(
273
- `structure.txt: Invalid indent on line ${lineNo}. Indent must be multiples of ${INDENT_STEP} spaces.`
311
+ `structure.txt: Invalid indent on line ${lineNo}. Indent must be multiples of ${indentStep} spaces.`
274
312
  );
275
313
  }
276
- const level = indentSpaces / INDENT_STEP;
314
+ const level = indentSpaces / indentStep;
277
315
  if (level > stack.length) {
278
316
  if (level !== stack.length + 1) {
279
317
  throw new Error(
@@ -839,6 +877,45 @@ async function writeScannedStructuresFromConfig(cwd, options = {}) {
839
877
  );
840
878
  }
841
879
  }
880
+ async function ensureStructureFilesFromConfig(cwd, options = {}) {
881
+ const { config, scaffoldDir } = await loadScaffoldConfig(cwd, {
882
+ scaffoldDir: options.scaffoldDirOverride
883
+ });
884
+ ensureDirSync(scaffoldDir);
885
+ const created = [];
886
+ const existing = [];
887
+ const seen = /* @__PURE__ */ new Set();
888
+ const ensureFile = (fileName) => {
889
+ if (!fileName) return;
890
+ const filePath = path2__default.default.join(scaffoldDir, fileName);
891
+ const key = path2__default.default.resolve(filePath);
892
+ if (seen.has(key)) return;
893
+ seen.add(key);
894
+ if (fs7__default.default.existsSync(filePath)) {
895
+ existing.push(filePath);
896
+ return;
897
+ }
898
+ const header = `# ${fileName}
899
+ # Structure file for @timeax/scaffold
900
+ # Define your desired folders/files here.
901
+ `;
902
+ fs7__default.default.writeFileSync(filePath, header, "utf8");
903
+ created.push(filePath);
904
+ };
905
+ if (config.groups && config.groups.length > 0) {
906
+ for (const group of config.groups) {
907
+ const fileName = group.structureFile ?? `${group.name}.txt`;
908
+ ensureFile(fileName);
909
+ }
910
+ } else {
911
+ const fileName = config.structureFile ?? "structure.txt";
912
+ ensureFile(fileName);
913
+ }
914
+ logger4.debug(
915
+ `ensureStructureFilesFromConfig: created=${created.length}, existing=${existing.length}`
916
+ );
917
+ return { created, existing };
918
+ }
842
919
  var logger5 = defaultLogger.child("[init]");
843
920
  var DEFAULT_CONFIG_TS = `import type { ScaffoldConfig } from '@timeax/scaffold';
844
921
 
@@ -857,7 +934,10 @@ const config: ScaffoldConfig = {
857
934
  // base: 'src', // apply to <root>/src
858
935
  // base: '..', // apply to parent of <root>
859
936
  // base: '.',
860
-
937
+
938
+ // Number of spaces per indent level in structure files (default: 2).
939
+ // indentStep: 2,
940
+
861
941
  // Cache file path, relative to base.
862
942
  // cacheFile: '.scaffold-cache.json',
863
943
 
@@ -889,7 +969,7 @@ const config: ScaffoldConfig = {
889
969
 
890
970
  export default config;
891
971
  `;
892
- var DEFAULT_STRUCTURE_TXT = `# scaffold/structure.txt
972
+ var DEFAULT_STRUCTURE_TXT = `# ${SCAFFOLD_ROOT_DIR}/structure.txt
893
973
  # Example structure definition.
894
974
  # - Indent with 2 spaces per level
895
975
  # - Directories must end with "/"
@@ -901,7 +981,7 @@ var DEFAULT_STRUCTURE_TXT = `# scaffold/structure.txt
901
981
  # index.ts
902
982
  `;
903
983
  async function initScaffold(cwd, options = {}) {
904
- const scaffoldDirRel = options.scaffoldDir ?? "scaffold";
984
+ const scaffoldDirRel = options.scaffoldDir ?? SCAFFOLD_ROOT_DIR;
905
985
  const scaffoldDirAbs = path2__default.default.resolve(cwd, scaffoldDirRel);
906
986
  const configFileName = options.configFileName ?? "config.ts";
907
987
  const structureFileName = options.structureFileName ?? "structure.txt";
@@ -1036,6 +1116,21 @@ async function handleInitCommand(cwd, initOpts, baseOpts) {
1036
1116
  `Done. Config: ${result.configPath}, Structure: ${result.structurePath}`
1037
1117
  );
1038
1118
  }
1119
+ async function handleStructuresCommand(cwd, baseOpts) {
1120
+ const logger6 = createCliLogger(baseOpts);
1121
+ logger6.info("Ensuring structure files declared in config exist...");
1122
+ const { created, existing } = await ensureStructureFilesFromConfig(cwd, {
1123
+ scaffoldDirOverride: baseOpts.dir
1124
+ });
1125
+ if (created.length === 0) {
1126
+ logger6.info("All structure files already exist. Nothing to do.");
1127
+ } else {
1128
+ for (const filePath of created) {
1129
+ logger6.info(`Created structure file: ${filePath}`);
1130
+ }
1131
+ }
1132
+ existing.forEach((p) => logger6.debug(`Structure file already exists: ${p}`));
1133
+ }
1039
1134
  async function main() {
1040
1135
  const cwd = process.cwd();
1041
1136
  const program = new commander.Command();
@@ -1071,6 +1166,12 @@ async function main() {
1071
1166
  program.action(async (opts) => {
1072
1167
  await handleRunCommand(cwd, opts);
1073
1168
  });
1169
+ program.command("structures").description(
1170
+ "Create missing structure files specified in the config (does not overwrite existing files)"
1171
+ ).action(async (_opts, cmd) => {
1172
+ const baseOpts = cmd.parent?.opts() ?? {};
1173
+ await handleStructuresCommand(cwd, baseOpts);
1174
+ });
1074
1175
  await program.parseAsync(process.argv);
1075
1176
  }
1076
1177
  main().catch((err) => {