@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.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,
|
|
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,
|
|
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
|
-
|
|
213
|
-
if (!rest
|
|
214
|
-
const
|
|
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 %
|
|
298
|
+
if (indentSpaces % indentStep !== 0) {
|
|
261
299
|
throw new Error(
|
|
262
|
-
`structure.txt: Invalid indent on line ${lineNo}. Indent must be multiples of ${
|
|
300
|
+
`structure.txt: Invalid indent on line ${lineNo}. Indent must be multiples of ${indentStep} spaces.`
|
|
263
301
|
);
|
|
264
302
|
}
|
|
265
|
-
const level = indentSpaces /
|
|
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 = `#
|
|
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 ??
|
|
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) => {
|