@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 +115 -14
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.mjs +115 -14
- package/dist/cli.mjs.map +1 -1
- package/dist/index.cjs +91 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +84 -1
- package/dist/index.d.ts +84 -1
- package/dist/index.mjs +88 -13
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -2
- package/readme.md +184 -46
- package/scripts/postpublish.mjs +72 -0
- package/scripts/prepublish.mjs +95 -0
- package/src/cli/main.ts +38 -0
- package/src/core/config-loader.ts +4 -3
- package/src/core/init-scaffold.ts +8 -3
- package/src/core/scan-structure.ts +71 -0
- package/src/core/structure-txt.ts +88 -15
- package/src/index.ts +3 -2
- package/src/schema/config.ts +10 -1
- package/src/schema/index.ts +1 -0
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,
|
|
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,
|
|
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
|
-
|
|
224
|
-
if (!rest
|
|
225
|
-
const
|
|
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 %
|
|
309
|
+
if (indentSpaces % indentStep !== 0) {
|
|
272
310
|
throw new Error(
|
|
273
|
-
`structure.txt: Invalid indent on line ${lineNo}. Indent must be multiples of ${
|
|
311
|
+
`structure.txt: Invalid indent on line ${lineNo}. Indent must be multiples of ${indentStep} spaces.`
|
|
274
312
|
);
|
|
275
313
|
}
|
|
276
|
-
const level = indentSpaces /
|
|
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 = `#
|
|
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 ??
|
|
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) => {
|