simple-scaffold 3.0.0 → 3.1.0

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/README.md CHANGED
@@ -40,6 +40,21 @@ See full documentation [here](https://chenasraf.github.io/simple-scaffold).
40
40
 
41
41
  ## Getting Started
42
42
 
43
+ ### Quick Start
44
+
45
+ The fastest way to get started is to run `init` in your project directory:
46
+
47
+ ```sh
48
+ npx simple-scaffold init
49
+ ```
50
+
51
+ This creates a `scaffold.config.js` and an example template in `templates/default/`. Then generate
52
+ files with:
53
+
54
+ ```sh
55
+ npx simple-scaffold MyProject
56
+ ```
57
+
43
58
  ### Cheat Sheet
44
59
 
45
60
  A quick rundown of common usage scenarios:
package/cmd.js CHANGED
@@ -1,20 +1,110 @@
1
1
  #!/usr/bin/env node
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const require_scaffold = require("./scaffold-Ce-rIwy9.js");
3
+ const require_scaffold = require("./scaffold-DOzCgpZT.js");
4
4
  let node_path = require("node:path");
5
5
  node_path = require_scaffold.__toESM(node_path);
6
6
  let node_fs_promises = require("node:fs/promises");
7
7
  node_fs_promises = require_scaffold.__toESM(node_fs_promises);
8
+ let _inquirer_select = require("@inquirer/select");
9
+ _inquirer_select = require_scaffold.__toESM(_inquirer_select);
8
10
  let massarg = require("massarg");
9
11
  let massarg_command = require("massarg/command");
12
+ //#region src/init.ts
13
+ var CONFIG_EXTENSIONS = {
14
+ js: "scaffold.config.js",
15
+ mjs: "scaffold.config.mjs",
16
+ json: "scaffold.config.json"
17
+ };
18
+ var CONFIG_TEMPLATES = {
19
+ js: `/** @type {import('simple-scaffold').ScaffoldConfigMap} */
20
+ module.exports = {
21
+ default: {
22
+ templates: ["templates/default"],
23
+ output: ".",
24
+ // inputs: {
25
+ // author: { message: "Author name", required: true },
26
+ // license: { message: "License", default: "MIT" },
27
+ // },
28
+ },
29
+ }
30
+ `,
31
+ mjs: `/** @type {import('simple-scaffold').ScaffoldConfigMap} */
32
+ export default {
33
+ default: {
34
+ templates: ["templates/default"],
35
+ output: ".",
36
+ // inputs: {
37
+ // author: { message: "Author name", required: true },
38
+ // license: { message: "License", default: "MIT" },
39
+ // },
40
+ },
41
+ }
42
+ `,
43
+ json: `{
44
+ "default": {
45
+ "templates": ["templates/default"],
46
+ "output": "."
47
+ }
48
+ }
49
+ `
50
+ };
51
+ var EXAMPLE_TEMPLATE_CONTENT = `# {{ name }}
52
+
53
+ Created by Simple Scaffold.
54
+
55
+ {{#if description}}{{ description }}{{/if}}
56
+ `;
57
+ /**
58
+ * Initializes a new Simple Scaffold project by creating a config file
59
+ * and an optional example template directory.
60
+ */
61
+ async function initScaffold(options = {}) {
62
+ const dir = options.dir ?? process.cwd();
63
+ const format = options.format ?? await (0, _inquirer_select.default)({
64
+ message: require_scaffold.colorize.cyan("Config file format:"),
65
+ choices: [
66
+ {
67
+ name: "JavaScript (CommonJS)",
68
+ value: "js"
69
+ },
70
+ {
71
+ name: "JavaScript (ESM)",
72
+ value: "mjs"
73
+ },
74
+ {
75
+ name: "JSON",
76
+ value: "json"
77
+ }
78
+ ]
79
+ });
80
+ const filename = CONFIG_EXTENSIONS[format];
81
+ const configPath = node_path.default.resolve(dir, filename);
82
+ if (await require_scaffold.pathExists(configPath)) console.log(require_scaffold.colorize.yellow(`${filename} already exists, skipping config creation.`));
83
+ else {
84
+ await node_fs_promises.default.writeFile(configPath, CONFIG_TEMPLATES[format]);
85
+ console.log(require_scaffold.colorize.green(`Created ${filename}`));
86
+ }
87
+ if (options.createExample ?? true) {
88
+ const templateDir = node_path.default.resolve(dir, "templates", "default");
89
+ const templateFile = node_path.default.join(templateDir, "{{name}}.md");
90
+ if (await require_scaffold.pathExists(templateDir)) console.log(require_scaffold.colorize.yellow("templates/default/ already exists, skipping example template."));
91
+ else {
92
+ await node_fs_promises.default.mkdir(templateDir, { recursive: true });
93
+ await node_fs_promises.default.writeFile(templateFile, EXAMPLE_TEMPLATE_CONTENT);
94
+ console.log(require_scaffold.colorize.green("Created templates/default/{{name}}.md"));
95
+ }
96
+ }
97
+ console.log();
98
+ console.log(require_scaffold.colorize.dim("Get started:"));
99
+ console.log(require_scaffold.colorize.dim(` npx simple-scaffold MyProject`));
100
+ console.log();
101
+ }
102
+ //#endregion
10
103
  //#region src/cmd.ts
11
104
  async function parseCliArgs(args = process.argv.slice(2)) {
12
105
  const isProjectRoot = Boolean(await node_fs_promises.default.stat(node_path.default.join(__dirname, "package.json")).catch(() => false));
13
106
  const pkgFile = await node_fs_promises.default.readFile(node_path.default.resolve(__dirname, isProjectRoot ? "." : "..", "package.json"));
14
107
  const pkg = JSON.parse(pkgFile.toString());
15
- args.includes("--version") || args.includes("-v");
16
- args.includes("--config") || args.includes("-c");
17
- args.includes("--git") || args.includes("-g");
18
108
  return (0, massarg.massarg)({
19
109
  name: pkg.name,
20
110
  description: pkg.description
@@ -23,19 +113,20 @@ async function parseCliArgs(args = process.argv.slice(2)) {
23
113
  console.log(pkg.version);
24
114
  return;
25
115
  }
26
- require_scaffold.log(config, require_scaffold.LogLevel.info, `Simple Scaffold v${pkg.version}`);
116
+ require_scaffold.log(config, require_scaffold.LogLevel.debug, `Simple Scaffold v${pkg.version}`);
27
117
  config.tmpDir = require_scaffold.getUniqueTmpPath();
28
118
  try {
29
- if (!config.config && !config.git) try {
119
+ const isOneTimeRun = config.templates?.length > 0 || config.output;
120
+ if (!config.config && !config.git && !isOneTimeRun) try {
30
121
  config.config = await require_scaffold.findConfigFile(process.cwd());
31
122
  require_scaffold.log(config, require_scaffold.LogLevel.debug, `Auto-detected config file: ${config.config}`);
32
123
  } catch {}
33
124
  const hasConfigSource = Boolean(config.config || config.git);
34
125
  let configMap;
35
126
  if (hasConfigSource) configMap = await require_scaffold.getConfigFile(config);
36
- config = await require_scaffold.promptForMissingConfig(config, configMap);
127
+ config = await require_scaffold.promptBeforeConfig(config, configMap);
37
128
  require_scaffold.log(config, require_scaffold.LogLevel.debug, "Parsing config file...", config);
38
- await require_scaffold.Scaffold(await require_scaffold.resolveInputs(await require_scaffold.parseConfigFile(config)));
129
+ await require_scaffold.Scaffold(await require_scaffold.resolveInputs(await require_scaffold.promptAfterConfig(await require_scaffold.parseConfigFile(config))));
39
130
  } catch (e) {
40
131
  const message = "message" in e ? e.message : e?.toString();
41
132
  require_scaffold.log(config, require_scaffold.LogLevel.error, message);
@@ -118,6 +209,10 @@ async function parseCliArgs(args = process.argv.slice(2)) {
118
209
  name: "before-write",
119
210
  aliases: ["B"],
120
211
  description: "Run a script before writing the files. This can be a command or a path to a file. A temporary file path will be passed to the given command and the command should return a string for the final output."
212
+ }).option({
213
+ name: "after-scaffold",
214
+ aliases: ["A"],
215
+ description: "Run a shell command after all files have been written. The command is executed in the output directory. For example: `--after-scaffold 'npm install'`"
121
216
  }).flag({
122
217
  name: "dry-run",
123
218
  aliases: ["dr"],
@@ -177,6 +272,29 @@ async function parseCliArgs(args = process.argv.slice(2)) {
177
272
  if (!(val in require_scaffold.LogLevel)) throw new Error(`Invalid log level: ${val}, must be one of: ${Object.keys(require_scaffold.LogLevel).join(", ")}`);
178
273
  return val;
179
274
  }
275
+ }).help({ bindOption: true })).command(new massarg_command.MassargCommand({
276
+ name: "init",
277
+ aliases: [],
278
+ description: "Initialize a new scaffold config file and example template in the current directory.",
279
+ run: async (config) => {
280
+ try {
281
+ await initScaffold({
282
+ dir: config.dir,
283
+ format: config.format
284
+ });
285
+ } catch (e) {
286
+ const message = "message" in e ? e.message : e?.toString();
287
+ console.error(require_scaffold.colorize.red(message ?? "Unknown error"));
288
+ }
289
+ }
290
+ }).option({
291
+ name: "dir",
292
+ aliases: ["d"],
293
+ description: "Directory to create the config in. Defaults to current working directory."
294
+ }).option({
295
+ name: "format",
296
+ aliases: ["f"],
297
+ description: "Config file format: js, mjs, or json. If omitted, you will be prompted."
180
298
  }).help({ bindOption: true })).example({
181
299
  description: "Usage with config file",
182
300
  input: "simple-scaffold -c scaffold.cmd.js --key component"
package/cmd.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cmd.js","names":[],"sources":["../src/cmd.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport path from \"node:path\"\nimport fs from \"node:fs/promises\"\nimport { massarg } from \"massarg\"\nimport { ListCommandCliOptions, LogLevel, ScaffoldCmdConfig, ScaffoldConfigMap } from \"./types\"\nimport { Scaffold } from \"./scaffold\"\nimport { findConfigFile, getConfigFile, parseAppendData, parseConfigFile } from \"./config\"\nimport { log } from \"./logger\"\nimport { MassargCommand } from \"massarg/command\"\nimport { getUniqueTmpPath as generateUniqueTmpPath } from \"./file\"\nimport { colorize } from \"./colors\"\nimport { promptForMissingConfig, resolveInputs } from \"./prompts\"\n\nexport async function parseCliArgs(args = process.argv.slice(2)) {\n const isProjectRoot = Boolean(await fs.stat(path.join(__dirname, \"package.json\")).catch(() => false))\n const pkgFile = await fs.readFile(path.resolve(__dirname, isProjectRoot ? \".\" : \"..\", \"package.json\"))\n const pkg = JSON.parse(pkgFile.toString())\n const isVersionFlag = args.includes(\"--version\") || args.includes(\"-v\")\n const isConfigFileProvided = args.includes(\"--config\") || args.includes(\"-c\")\n const isGitProvided = args.includes(\"--git\") || args.includes(\"-g\")\n const isConfigProvided = isConfigFileProvided || isGitProvided || isVersionFlag\n\n return massarg<ScaffoldCmdConfig>({\n name: pkg.name,\n description: pkg.description,\n })\n .main(async (config) => {\n if (config.version) {\n console.log(pkg.version)\n return\n }\n log(config, LogLevel.info, `Simple Scaffold v${pkg.version}`)\n config.tmpDir = generateUniqueTmpPath()\n try {\n // Auto-detect config file in cwd if not explicitly provided\n if (!config.config && !config.git) {\n try {\n config.config = await findConfigFile(process.cwd())\n log(config, LogLevel.debug, `Auto-detected config file: ${config.config}`)\n } catch {\n // No config file found — that's fine, continue without one\n }\n }\n\n // Load config early so we can prompt for template key\n const hasConfigSource = Boolean(config.config || config.git)\n let configMap: ScaffoldConfigMap | undefined\n if (hasConfigSource) {\n configMap = await getConfigFile(config)\n }\n\n // Prompt for missing values interactively\n config = await promptForMissingConfig(config, configMap)\n\n log(config, LogLevel.debug, \"Parsing config file...\", config)\n const parsed = await parseConfigFile(config)\n const resolved = await resolveInputs(parsed)\n await Scaffold(resolved)\n } catch (e) {\n const message = \"message\" in (e as object) ? (e as Error).message : e?.toString()\n log(config, LogLevel.error, message)\n } finally {\n log(config, LogLevel.debug, \"Cleaning up temporary files...\", config.tmpDir)\n if (config.tmpDir) await fs.rm(config.tmpDir, { recursive: true, force: true })\n }\n })\n .option({\n name: \"name\",\n aliases: [\"n\"],\n description:\n \"Name to be passed to the generated files. `{{name}}` and other data parameters inside \" +\n \"contents and file names will be replaced accordingly. You may omit the `--name` or `-n` \" +\n \"for this specific option. If omitted in an interactive terminal, you will be prompted.\",\n isDefault: true,\n })\n .option({\n name: \"config\",\n aliases: [\"c\"],\n description: \"Filename or directory to load config from\",\n })\n .option({\n name: \"git\",\n aliases: [\"g\"],\n description: \"Git URL or GitHub path to load a template from.\",\n })\n .option({\n name: \"key\",\n aliases: [\"k\"],\n description:\n \"Key to load inside the config file. This overwrites the config key provided after the colon in `--config` \" +\n \"(e.g. `--config scaffold.cmd.js:component)`. If omitted and multiple templates are available, \" +\n \"you will be prompted to select one.\",\n })\n .option({\n name: \"output\",\n aliases: [\"o\"],\n description:\n \"Path to output to. If `--subdir` is enabled, the subdir will be created inside \" +\n \"this path. If omitted in an interactive terminal, you will be prompted.\",\n })\n .option({\n name: \"templates\",\n aliases: [\"t\"],\n array: true,\n description:\n \"Template files to use as input. You may provide multiple files, each of which can be a relative or \" +\n \"absolute path, \" +\n \"or a glob pattern for multiple file matching easily. If omitted in an interactive terminal, \" +\n \"you will be prompted for a comma-separated list.\",\n })\n .flag({\n name: \"overwrite\",\n aliases: [\"w\"],\n defaultValue: false,\n description: \"Enable to override output files, even if they already exist.\",\n negatable: true,\n })\n .option({\n name: \"data\",\n aliases: [\"d\"],\n description: \"Add custom data to the templates. By default, only your app name is included.\",\n parse: (v) => JSON.parse(v),\n })\n .option({\n name: \"append-data\",\n aliases: [\"D\"],\n description:\n \"Append additional custom data to the templates, which will overwrite `--data`, using an alternate syntax, \" +\n \"which is easier to use with CLI: `-D key1=string -D key2:=raw`\",\n parse: parseAppendData,\n })\n .flag({\n name: \"subdir\",\n aliases: [\"s\"],\n defaultValue: false,\n description: \"Create a parent directory with the input name (and possibly `--subdir-helper`\",\n negatable: true,\n negationName: \"no-subdir\",\n })\n .option({\n name: \"subdir-helper\",\n aliases: [\"H\"],\n description: \"Default helper to apply to subdir name when using `--subdir`.\",\n })\n .flag({\n name: \"quiet\",\n aliases: [\"q\"],\n defaultValue: false,\n description: \"Suppress output logs (Same as `--log-level none`)\",\n })\n .option({\n name: \"log-level\",\n aliases: [\"l\"],\n defaultValue: LogLevel.info,\n description:\n \"Determine amount of logs to display. The values are: \" +\n `${colorize.bold`\\`none | debug | info | warn | error\\``}. ` +\n \"The provided level will display messages of the same level or higher.\",\n parse: (v) => {\n const val = v.toLowerCase()\n if (!(val in LogLevel)) {\n throw new Error(`Invalid log level: ${val}, must be one of: ${Object.keys(LogLevel).join(\", \")}`)\n }\n return val\n },\n })\n .option({\n name: \"before-write\",\n aliases: [\"B\"],\n description:\n \"Run a script before writing the files. This can be a command or a path to a\" +\n \" file. A temporary file path will be passed to the given command and the command should \" +\n \"return a string for the final output.\",\n })\n .flag({\n name: \"dry-run\",\n aliases: [\"dr\"],\n defaultValue: false,\n description:\n \"Don't emit files. This is good for testing your scaffolds and making sure they \" +\n \"don't fail, without having to write actual file contents or create directories.\",\n })\n .flag({\n name: \"version\",\n aliases: [\"v\"],\n description: \"Display version.\",\n })\n .command(\n new MassargCommand<ListCommandCliOptions>({\n name: \"list\",\n aliases: [\"ls\"],\n description: \"List all available templates for a given config. See `list -h` for more information.\",\n run: async (_config) => {\n const config = {\n templates: [],\n name: \"\",\n version: false,\n output: \"\",\n subdir: false,\n overwrite: false,\n dryRun: false,\n tmpDir: generateUniqueTmpPath(),\n ..._config,\n config: _config.config ?? (!_config.git ? process.cwd() : undefined),\n }\n try {\n const file = await getConfigFile(config)\n console.log(colorize.underline`Available templates:\\n`)\n console.log(Object.keys(file).join(\"\\n\"))\n } catch (e) {\n const message = \"message\" in (e as object) ? (e as Error).message : e?.toString()\n log(config, LogLevel.error, message)\n } finally {\n log(config, LogLevel.debug, \"Cleaning up temporary files...\", config.tmpDir)\n if (config.tmpDir) await fs.rm(config.tmpDir, { recursive: true, force: true })\n }\n },\n })\n .option({\n name: \"config\",\n aliases: [\"c\"],\n description: \"Filename or directory to load config from. Defaults to current working directory.\",\n })\n .option({\n name: \"git\",\n aliases: [\"g\"],\n description: \"Git URL or GitHub path to load a template from.\",\n })\n .option({\n name: \"log-level\",\n aliases: [\"l\"],\n defaultValue: LogLevel.none,\n description:\n \"Determine amount of logs to display. The values are: \" +\n `${colorize.bold`\\`none | debug | info | warn | error\\``}. ` +\n \"The provided level will display messages of the same level or higher.\",\n parse: (v) => {\n const val = v.toLowerCase()\n if (!(val in LogLevel)) {\n throw new Error(`Invalid log level: ${val}, must be one of: ${Object.keys(LogLevel).join(\", \")}`)\n }\n return val\n },\n })\n .help({\n bindOption: true,\n }),\n )\n .example({\n description: \"Usage with config file\",\n input: \"simple-scaffold -c scaffold.cmd.js --key component\",\n })\n .example({\n description: \"Usage with GitHub config file\",\n input: \"simple-scaffold -g chenasraf/simple-scaffold --key component\",\n })\n .example({\n description: \"Usage with https git URL (for non-GitHub)\",\n input: \"simple-scaffold -g https://example.com/user/template.git -c scaffold.cmd.js --key component\",\n })\n .example({\n description: \"Excluded template key, assumes 'default' key\",\n input: \"simple-scaffold -c scaffold.cmd.js MyComponent\",\n })\n .example({\n description:\n \"Shortest syntax for GitHub, searches for config file automaticlly, assumes and template key 'default'\",\n input: \"simple-scaffold -g chenasraf/simple-scaffold MyComponent\",\n })\n .help({\n bindOption: true,\n lineLength: 100,\n useGlobalTableColumns: true,\n usageText: [colorize.yellow`simple-scaffold`, colorize.gray`[options]`, colorize.cyan`<name>`].join(\" \"),\n optionOptions: {\n displayNegations: true,\n },\n footerText: [\n `Version: ${pkg.version}`,\n `Copyright © Chen Asraf 2017-${new Date().getFullYear()}`,\n ``,\n `Documentation: ${colorize.underline`https://chenasraf.github.io/simple-scaffold`}`,\n `NPM: ${colorize.underline`https://npmjs.com/package/simple-scaffold`}`,\n `GitHub: ${colorize.underline`https://github.com/chenasraf/simple-scaffold`}`,\n ].join(\"\\n\"),\n })\n .parse(args)\n}\n\nparseCliArgs()\n"],"mappings":"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"}
1
+ {"version":3,"file":"cmd.js","names":[],"sources":["../src/init.ts","../src/cmd.ts"],"sourcesContent":["import path from \"node:path\"\nimport fs from \"node:fs/promises\"\nimport select from \"@inquirer/select\"\nimport { colorize } from \"./colors\"\nimport { pathExists } from \"./fs-utils\"\n\nconst CONFIG_EXTENSIONS = {\n js: \"scaffold.config.js\",\n mjs: \"scaffold.config.mjs\",\n json: \"scaffold.config.json\",\n} as const\n\ntype ConfigFormat = keyof typeof CONFIG_EXTENSIONS\n\nconst CONFIG_TEMPLATES: Record<ConfigFormat, string> = {\n js: `/** @type {import('simple-scaffold').ScaffoldConfigMap} */\nmodule.exports = {\n default: {\n templates: [\"templates/default\"],\n output: \".\",\n // inputs: {\n // author: { message: \"Author name\", required: true },\n // license: { message: \"License\", default: \"MIT\" },\n // },\n },\n}\n`,\n mjs: `/** @type {import('simple-scaffold').ScaffoldConfigMap} */\nexport default {\n default: {\n templates: [\"templates/default\"],\n output: \".\",\n // inputs: {\n // author: { message: \"Author name\", required: true },\n // license: { message: \"License\", default: \"MIT\" },\n // },\n },\n}\n`,\n json: `{\n \"default\": {\n \"templates\": [\"templates/default\"],\n \"output\": \".\"\n }\n}\n`,\n}\n\nconst EXAMPLE_TEMPLATE_CONTENT = `# {{ name }}\n\nCreated by Simple Scaffold.\n\n{{#if description}}{{ description }}{{/if}}\n`\n\nexport interface InitOptions {\n /** Working directory to create the config in. Defaults to cwd. */\n dir?: string\n /** Config format to use. If not provided, the user is prompted. */\n format?: ConfigFormat\n /** Whether to create an example template directory. Defaults to true. */\n createExample?: boolean\n}\n\n/**\n * Initializes a new Simple Scaffold project by creating a config file\n * and an optional example template directory.\n */\nexport async function initScaffold(options: InitOptions = {}): Promise<void> {\n const dir = options.dir ?? process.cwd()\n\n const format =\n options.format ??\n (await select<ConfigFormat>({\n message: colorize.cyan(\"Config file format:\"),\n choices: [\n { name: \"JavaScript (CommonJS)\", value: \"js\" },\n { name: \"JavaScript (ESM)\", value: \"mjs\" },\n { name: \"JSON\", value: \"json\" },\n ],\n }))\n\n const filename = CONFIG_EXTENSIONS[format]\n const configPath = path.resolve(dir, filename)\n\n if (await pathExists(configPath)) {\n console.log(colorize.yellow(`${filename} already exists, skipping config creation.`))\n } else {\n await fs.writeFile(configPath, CONFIG_TEMPLATES[format])\n console.log(colorize.green(`Created ${filename}`))\n }\n\n const createExample = options.createExample ?? true\n if (createExample) {\n const templateDir = path.resolve(dir, \"templates\", \"default\")\n const templateFile = path.join(templateDir, \"{{name}}.md\")\n\n if (await pathExists(templateDir)) {\n console.log(colorize.yellow(\"templates/default/ already exists, skipping example template.\"))\n } else {\n await fs.mkdir(templateDir, { recursive: true })\n await fs.writeFile(templateFile, EXAMPLE_TEMPLATE_CONTENT)\n console.log(colorize.green(\"Created templates/default/{{name}}.md\"))\n }\n }\n\n console.log()\n console.log(colorize.dim(\"Get started:\"))\n console.log(colorize.dim(` npx simple-scaffold MyProject`))\n console.log()\n}\n","#!/usr/bin/env node\n\nimport path from \"node:path\"\nimport fs from \"node:fs/promises\"\nimport { massarg } from \"massarg\"\nimport { ListCommandCliOptions, LogLevel, ScaffoldCmdConfig, ScaffoldConfigMap } from \"./types\"\nimport { Scaffold } from \"./scaffold\"\nimport { findConfigFile, getConfigFile, parseAppendData, parseConfigFile } from \"./config\"\nimport { log } from \"./logger\"\nimport { MassargCommand } from \"massarg/command\"\nimport { getUniqueTmpPath as generateUniqueTmpPath } from \"./file\"\nimport { colorize } from \"./colors\"\nimport { promptBeforeConfig, promptAfterConfig, resolveInputs } from \"./prompts\"\nimport { initScaffold } from \"./init\"\n\nexport async function parseCliArgs(args = process.argv.slice(2)) {\n const isProjectRoot = Boolean(\n await fs.stat(path.join(__dirname, \"package.json\")).catch(() => false),\n )\n const pkgFile = await fs.readFile(\n path.resolve(__dirname, isProjectRoot ? \".\" : \"..\", \"package.json\"),\n )\n const pkg = JSON.parse(pkgFile.toString())\n return massarg<ScaffoldCmdConfig>({\n name: pkg.name,\n description: pkg.description,\n })\n .main(async (config) => {\n if (config.version) {\n console.log(pkg.version)\n return\n }\n log(config, LogLevel.debug, `Simple Scaffold v${pkg.version}`)\n config.tmpDir = generateUniqueTmpPath()\n try {\n // Auto-detect config file in cwd — but only if the user didn't\n // explicitly provide templates/output (which signals a one-time run)\n const isOneTimeRun = config.templates?.length > 0 || config.output\n if (!config.config && !config.git && !isOneTimeRun) {\n try {\n config.config = await findConfigFile(process.cwd())\n log(config, LogLevel.debug, `Auto-detected config file: ${config.config}`)\n } catch {\n // No config file found — that's fine, continue without one\n }\n }\n\n // Load config map early so we can prompt for name and template key\n const hasConfigSource = Boolean(config.config || config.git)\n let configMap: ScaffoldConfigMap | undefined\n if (hasConfigSource) {\n configMap = await getConfigFile(config)\n }\n\n // Phase 1: prompt for name + key (needed before parseConfigFile)\n config = await promptBeforeConfig(config, configMap)\n\n // Parse and merge the config file\n log(config, LogLevel.debug, \"Parsing config file...\", config)\n const parsed = await parseConfigFile(config)\n\n // Phase 2: prompt for anything still missing after config merge\n const prompted = await promptAfterConfig(parsed)\n\n const resolved = await resolveInputs(prompted)\n await Scaffold(resolved)\n } catch (e) {\n const message = \"message\" in (e as object) ? (e as Error).message : e?.toString()\n log(config, LogLevel.error, message)\n } finally {\n log(config, LogLevel.debug, \"Cleaning up temporary files...\", config.tmpDir)\n if (config.tmpDir) await fs.rm(config.tmpDir, { recursive: true, force: true })\n }\n })\n .option({\n name: \"name\",\n aliases: [\"n\"],\n description:\n \"Name to be passed to the generated files. `{{name}}` and other data parameters inside \" +\n \"contents and file names will be replaced accordingly. You may omit the `--name` or `-n` \" +\n \"for this specific option. If omitted in an interactive terminal, you will be prompted.\",\n isDefault: true,\n })\n .option({\n name: \"config\",\n aliases: [\"c\"],\n description: \"Filename or directory to load config from\",\n })\n .option({\n name: \"git\",\n aliases: [\"g\"],\n description: \"Git URL or GitHub path to load a template from.\",\n })\n .option({\n name: \"key\",\n aliases: [\"k\"],\n description:\n \"Key to load inside the config file. This overwrites the config key provided after the colon in `--config` \" +\n \"(e.g. `--config scaffold.cmd.js:component)`. If omitted and multiple templates are available, \" +\n \"you will be prompted to select one.\",\n })\n .option({\n name: \"output\",\n aliases: [\"o\"],\n description:\n \"Path to output to. If `--subdir` is enabled, the subdir will be created inside \" +\n \"this path. If omitted in an interactive terminal, you will be prompted.\",\n })\n .option({\n name: \"templates\",\n aliases: [\"t\"],\n array: true,\n description:\n \"Template files to use as input. You may provide multiple files, each of which can be a relative or \" +\n \"absolute path, \" +\n \"or a glob pattern for multiple file matching easily. If omitted in an interactive terminal, \" +\n \"you will be prompted for a comma-separated list.\",\n })\n .flag({\n name: \"overwrite\",\n aliases: [\"w\"],\n defaultValue: false,\n description: \"Enable to override output files, even if they already exist.\",\n negatable: true,\n })\n .option({\n name: \"data\",\n aliases: [\"d\"],\n description: \"Add custom data to the templates. By default, only your app name is included.\",\n parse: (v) => JSON.parse(v),\n })\n .option({\n name: \"append-data\",\n aliases: [\"D\"],\n description:\n \"Append additional custom data to the templates, which will overwrite `--data`, using an alternate syntax, \" +\n \"which is easier to use with CLI: `-D key1=string -D key2:=raw`\",\n parse: parseAppendData,\n })\n .flag({\n name: \"subdir\",\n aliases: [\"s\"],\n defaultValue: false,\n description: \"Create a parent directory with the input name (and possibly `--subdir-helper`\",\n negatable: true,\n negationName: \"no-subdir\",\n })\n .option({\n name: \"subdir-helper\",\n aliases: [\"H\"],\n description: \"Default helper to apply to subdir name when using `--subdir`.\",\n })\n .flag({\n name: \"quiet\",\n aliases: [\"q\"],\n defaultValue: false,\n description: \"Suppress output logs (Same as `--log-level none`)\",\n })\n .option({\n name: \"log-level\",\n aliases: [\"l\"],\n defaultValue: LogLevel.info,\n description:\n \"Determine amount of logs to display. The values are: \" +\n `${colorize.bold`\\`none | debug | info | warn | error\\``}. ` +\n \"The provided level will display messages of the same level or higher.\",\n parse: (v) => {\n const val = v.toLowerCase()\n if (!(val in LogLevel)) {\n throw new Error(\n `Invalid log level: ${val}, must be one of: ${Object.keys(LogLevel).join(\", \")}`,\n )\n }\n return val\n },\n })\n .option({\n name: \"before-write\",\n aliases: [\"B\"],\n description:\n \"Run a script before writing the files. This can be a command or a path to a\" +\n \" file. A temporary file path will be passed to the given command and the command should \" +\n \"return a string for the final output.\",\n })\n .option({\n name: \"after-scaffold\",\n aliases: [\"A\"],\n description:\n \"Run a shell command after all files have been written. \" +\n \"The command is executed in the output directory. \" +\n \"For example: `--after-scaffold 'npm install'`\",\n })\n .flag({\n name: \"dry-run\",\n aliases: [\"dr\"],\n defaultValue: false,\n description:\n \"Don't emit files. This is good for testing your scaffolds and making sure they \" +\n \"don't fail, without having to write actual file contents or create directories.\",\n })\n .flag({\n name: \"version\",\n aliases: [\"v\"],\n description: \"Display version.\",\n })\n .command(\n new MassargCommand<ListCommandCliOptions>({\n name: \"list\",\n aliases: [\"ls\"],\n description:\n \"List all available templates for a given config. See `list -h` for more information.\",\n run: async (_config) => {\n const config = {\n templates: [],\n name: \"\",\n version: false,\n output: \"\",\n subdir: false,\n overwrite: false,\n dryRun: false,\n tmpDir: generateUniqueTmpPath(),\n ..._config,\n config: _config.config ?? (!_config.git ? process.cwd() : undefined),\n }\n try {\n const file = await getConfigFile(config)\n console.log(colorize.underline`Available templates:\\n`)\n console.log(Object.keys(file).join(\"\\n\"))\n } catch (e) {\n const message = \"message\" in (e as object) ? (e as Error).message : e?.toString()\n log(config, LogLevel.error, message)\n } finally {\n log(config, LogLevel.debug, \"Cleaning up temporary files...\", config.tmpDir)\n if (config.tmpDir) await fs.rm(config.tmpDir, { recursive: true, force: true })\n }\n },\n })\n .option({\n name: \"config\",\n aliases: [\"c\"],\n description:\n \"Filename or directory to load config from. Defaults to current working directory.\",\n })\n .option({\n name: \"git\",\n aliases: [\"g\"],\n description: \"Git URL or GitHub path to load a template from.\",\n })\n .option({\n name: \"log-level\",\n aliases: [\"l\"],\n defaultValue: LogLevel.none,\n description:\n \"Determine amount of logs to display. The values are: \" +\n `${colorize.bold`\\`none | debug | info | warn | error\\``}. ` +\n \"The provided level will display messages of the same level or higher.\",\n parse: (v) => {\n const val = v.toLowerCase()\n if (!(val in LogLevel)) {\n throw new Error(\n `Invalid log level: ${val}, must be one of: ${Object.keys(LogLevel).join(\", \")}`,\n )\n }\n return val\n },\n })\n .help({\n bindOption: true,\n }),\n )\n .command(\n new MassargCommand<{ dir?: string; format?: string }>({\n name: \"init\",\n aliases: [],\n description:\n \"Initialize a new scaffold config file and example template in the current directory.\",\n run: async (config) => {\n try {\n await initScaffold({\n dir: config.dir,\n format: config.format as \"js\" | \"mjs\" | \"json\" | undefined,\n })\n } catch (e) {\n const message = \"message\" in (e as object) ? (e as Error).message : e?.toString()\n console.error(colorize.red(message ?? \"Unknown error\"))\n }\n },\n })\n .option({\n name: \"dir\",\n aliases: [\"d\"],\n description: \"Directory to create the config in. Defaults to current working directory.\",\n })\n .option({\n name: \"format\",\n aliases: [\"f\"],\n description: \"Config file format: js, mjs, or json. If omitted, you will be prompted.\",\n })\n .help({\n bindOption: true,\n }),\n )\n .example({\n description: \"Usage with config file\",\n input: \"simple-scaffold -c scaffold.cmd.js --key component\",\n })\n .example({\n description: \"Usage with GitHub config file\",\n input: \"simple-scaffold -g chenasraf/simple-scaffold --key component\",\n })\n .example({\n description: \"Usage with https git URL (for non-GitHub)\",\n input:\n \"simple-scaffold -g https://example.com/user/template.git -c scaffold.cmd.js --key component\",\n })\n .example({\n description: \"Excluded template key, assumes 'default' key\",\n input: \"simple-scaffold -c scaffold.cmd.js MyComponent\",\n })\n .example({\n description:\n \"Shortest syntax for GitHub, searches for config file automaticlly, assumes and template key 'default'\",\n input: \"simple-scaffold -g chenasraf/simple-scaffold MyComponent\",\n })\n .help({\n bindOption: true,\n lineLength: 100,\n useGlobalTableColumns: true,\n usageText: [\n colorize.yellow`simple-scaffold`,\n colorize.gray`[options]`,\n colorize.cyan`<name>`,\n ].join(\" \"),\n optionOptions: {\n displayNegations: true,\n },\n footerText: [\n `Version: ${pkg.version}`,\n `Copyright © Chen Asraf 2017-${new Date().getFullYear()}`,\n ``,\n `Documentation: ${colorize.underline`https://chenasraf.github.io/simple-scaffold`}`,\n `NPM: ${colorize.underline`https://npmjs.com/package/simple-scaffold`}`,\n `GitHub: ${colorize.underline`https://github.com/chenasraf/simple-scaffold`}`,\n ].join(\"\\n\"),\n })\n .parse(args)\n}\n\nparseCliArgs()\n"],"mappings":"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"}
package/file.d.ts CHANGED
@@ -52,8 +52,9 @@ export declare function getOutputDir(config: ScaffoldConfig, outputPathOpt: stri
52
52
  /**
53
53
  * Processes a single template file: resolves output paths, creates directories,
54
54
  * and writes the transformed output.
55
+ * Returns the output path if the file was written, or null if skipped.
55
56
  */
56
57
  export declare function handleTemplateFile(config: ScaffoldConfig, { templatePath, basePath }: {
57
58
  templatePath: string;
58
59
  basePath: string;
59
- }): Promise<void>;
60
+ }): Promise<string | null>;
package/ignore.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Reads a `.scaffoldignore` file from the given directory and returns
3
+ * the parsed patterns for filtering.
4
+ *
5
+ * Lines starting with `#` are comments. Empty lines are skipped.
6
+ *
7
+ * @param dir The directory to search for `.scaffoldignore`
8
+ * @returns Array of glob patterns to ignore
9
+ */
10
+ export declare function loadIgnorePatterns(dir: string): Promise<string[]>;
11
+ /**
12
+ * Parses the contents of a `.scaffoldignore` file into glob patterns.
13
+ * @internal
14
+ */
15
+ export declare function parseIgnoreFile(content: string): string[];
16
+ /**
17
+ * Filters a list of file paths, removing any that match the ignore patterns.
18
+ * Patterns are matched against the relative path from baseDir.
19
+ * Also always excludes `.scaffoldignore` itself.
20
+ */
21
+ export declare function filterIgnoredFiles(files: string[], ignorePatterns: string[], baseDir: string): string[];
package/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./scaffold";
2
2
  export * from "./types";
3
+ export { validateConfig, assertConfigValid, scaffoldConfigSchema } from "./validate";
3
4
  import Scaffold from "./scaffold";
4
5
  export default Scaffold;
package/index.js CHANGED
@@ -2,12 +2,15 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_scaffold = require("./scaffold-Ce-rIwy9.js");
5
+ const require_scaffold = require("./scaffold-DOzCgpZT.js");
6
6
  //#region src/index.ts
7
7
  var src_default = require_scaffold.Scaffold;
8
8
  //#endregion
9
9
  exports.LogLevel = require_scaffold.LogLevel;
10
10
  exports.Scaffold = require_scaffold.Scaffold;
11
+ exports.assertConfigValid = require_scaffold.assertConfigValid;
11
12
  exports.default = src_default;
13
+ exports.scaffoldConfigSchema = require_scaffold.scaffoldConfigSchema;
14
+ exports.validateConfig = require_scaffold.validateConfig;
12
15
 
13
16
  //# sourceMappingURL=index.js.map
package/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["export * from \"./scaffold\"\nexport * from \"./types\"\nimport Scaffold from \"./scaffold\"\n\nexport default Scaffold\n"],"mappings":";;;;;;AAIA,IAAA,cAAe,iBAAA"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["export * from \"./scaffold\"\nexport * from \"./types\"\nexport { validateConfig, assertConfigValid, scaffoldConfigSchema } from \"./validate\"\nimport Scaffold from \"./scaffold\"\n\nexport default Scaffold\n"],"mappings":";;;;;;AAKA,IAAA,cAAe,iBAAA"}
package/init.d.ts ADDED
@@ -0,0 +1,20 @@
1
+ declare const CONFIG_EXTENSIONS: {
2
+ readonly js: "scaffold.config.js";
3
+ readonly mjs: "scaffold.config.mjs";
4
+ readonly json: "scaffold.config.json";
5
+ };
6
+ type ConfigFormat = keyof typeof CONFIG_EXTENSIONS;
7
+ export interface InitOptions {
8
+ /** Working directory to create the config in. Defaults to cwd. */
9
+ dir?: string;
10
+ /** Config format to use. If not provided, the user is prompted. */
11
+ format?: ConfigFormat;
12
+ /** Whether to create an example template directory. Defaults to true. */
13
+ createExample?: boolean;
14
+ }
15
+ /**
16
+ * Initializes a new Simple Scaffold project by creating a config file
17
+ * and an optional example template directory.
18
+ */
19
+ export declare function initScaffold(options?: InitOptions): Promise<void>;
20
+ export {};
package/logger.d.ts CHANGED
@@ -15,5 +15,13 @@ export declare function logInputFile(config: ScaffoldConfig, data: {
15
15
  isDirOrGlob: boolean;
16
16
  isGlob: boolean;
17
17
  }): void;
18
- /** Logs the full scaffold configuration at debug level, with a data summary at info level. */
18
+ /** Logs the full scaffold configuration at debug level. */
19
19
  export declare function logInitStep(config: ScaffoldConfig): void;
20
+ /**
21
+ * Logs a tree of created files, grouped by directory.
22
+ */
23
+ export declare function logFileTree(config: LogConfig, files: string[]): void;
24
+ /**
25
+ * Logs a final summary line with file count and elapsed time.
26
+ */
27
+ export declare function logSummary(config: LogConfig, fileCount: number, elapsedMs: number, dryRun?: boolean): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "simple-scaffold",
3
- "version": "3.0.0",
3
+ "version": "3.1.0",
4
4
  "description": "Generate any file structure - from single components to entire app boilerplates, with a single command.",
5
5
  "homepage": "https://chenasraf.github.io/simple-scaffold",
6
6
  "repository": {
@@ -37,21 +37,41 @@
37
37
  "docs:build": "cd docs && pnpm build",
38
38
  "docs:watch": "cd docs && pnpm start",
39
39
  "audit-fix": "pnpm audit --fix",
40
- "ci": "pnpm install --frozen-lockfile"
40
+ "ci": "pnpm install --frozen-lockfile",
41
+ "lint": "eslint src/ tests/",
42
+ "format": "prettier --write .",
43
+ "lint-staged": "lint-staged",
44
+ "prepare": "husky"
45
+ },
46
+ "lint-staged": {
47
+ "*.{ts,mts,js,mjs}": [
48
+ "eslint --fix",
49
+ "prettier --write"
50
+ ],
51
+ "*.{json,md,yml,yaml,css}": [
52
+ "prettier --write"
53
+ ]
41
54
  },
42
55
  "dependencies": {
56
+ "@inquirer/confirm": "^6.0.10",
43
57
  "@inquirer/input": "^5.0.10",
58
+ "@inquirer/number": "^4.0.10",
44
59
  "@inquirer/select": "^5.1.2",
45
60
  "date-fns": "^4.1.0",
46
61
  "glob": "^13.0.6",
47
62
  "handlebars": "^4.7.8",
48
- "massarg": "2.1.1"
63
+ "massarg": "2.1.1",
64
+ "minimatch": "^10.2.4",
65
+ "zod": "^4.3.6"
49
66
  },
50
67
  "devDependencies": {
51
68
  "@eslint/js": "^10.0.1",
52
69
  "@types/node": "^25.5.0",
53
70
  "@vitest/coverage-v8": "^4.1.0",
71
+ "husky": "^9.1.7",
72
+ "lint-staged": "^16.4.0",
54
73
  "mock-fs": "^5.5.0",
74
+ "prettier": "^3.8.1",
55
75
  "rimraf": "^6.1.3",
56
76
  "typescript": "^5.9.3",
57
77
  "typescript-eslint": "^8.57.1",
package/prompts.d.ts CHANGED
@@ -16,8 +16,17 @@ export declare function promptForInputs(inputs: Record<string, ScaffoldInput>, e
16
16
  /** Returns true if the process is running in an interactive terminal. */
17
17
  export declare function isInteractive(): boolean;
18
18
  /**
19
- * Fills in missing config values by prompting the user interactively.
20
- * Only prompts when running in a TTY in non-interactive mode, returns config as-is.
19
+ * Prompts for name and template key before the config file is parsed.
20
+ * These are needed by parseConfigFile to know which template to load.
21
+ */
22
+ export declare function promptBeforeConfig(config: ScaffoldCmdConfig, configMap?: ScaffoldConfigMap): Promise<ScaffoldCmdConfig>;
23
+ /**
24
+ * Prompts for any values still missing after the config file has been parsed.
25
+ * Only prompts in interactive mode.
26
+ */
27
+ export declare function promptAfterConfig(config: ScaffoldConfig): Promise<ScaffoldConfig>;
28
+ /**
29
+ * @deprecated Use {@link promptBeforeConfig} and {@link promptAfterConfig} instead.
21
30
  */
22
31
  export declare function promptForMissingConfig(config: ScaffoldCmdConfig, configMap?: ScaffoldConfigMap): Promise<ScaffoldCmdConfig>;
23
32
  /**