filemaker-cli 0.1.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 filemaker contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,73 @@
1
+ # filemakerx
2
+
3
+ A cross-platform CLI tool that scaffolds files and folders instantly,
4
+ either from a structure.txt blueprint or an interactive prompt.
5
+
6
+ Installs as `filemaker-cli`, runs as `filemaker`.
7
+
8
+ ## Install
9
+
10
+ ```
11
+ npm install -g filemaker-cli
12
+ ```
13
+
14
+ ## Usage
15
+
16
+ ```
17
+ cd any/project/folder
18
+ filemaker
19
+ ```
20
+
21
+ If a structure.txt file exists in the current directory, filemaker reads
22
+ it and builds everything listed, line by line. Otherwise it asks what
23
+ you want to build.
24
+
25
+ ```
26
+ filemaker --dry-run show what would be created without touching disk
27
+ filemaker --version print the installed version
28
+ filemaker --help show usage info
29
+ ```
30
+
31
+ ## structure.txt format
32
+
33
+ ```
34
+ src/
35
+ src/index.js
36
+ src/utils/helpers.js
37
+ .env
38
+ .gitignore
39
+ LICENSE
40
+ README.md
41
+ ```
42
+
43
+ One entry per line. Lines ending in / are always folders. Anything
44
+ starting with a dot, like .env or .gitignore, is always a file. Names
45
+ with a real extension are files too. A handful of common extension-less
46
+ filenames (LICENSE, Dockerfile, Makefile, README, CHANGELOG, and similar)
47
+ are recognized as files as well. Everything else is treated as a folder.
48
+ Blank lines and lines starting with # are ignored.
49
+
50
+ ## Comma mode example
51
+
52
+ ```
53
+ What do you want to build? (Separate items with commas)
54
+ > src/, src/index.js, .env, .gitignore, README.md
55
+ ```
56
+
57
+ ## Safety
58
+
59
+ filemaker only ever creates things inside the folder you ran it from.
60
+ Any entry that tries to escape that folder (using ../ or an absolute
61
+ path) gets skipped and reported, not silently followed.
62
+
63
+ Running filemaker again over an existing structure won't overwrite
64
+ anything. Items that already exist are left alone and marked
65
+ "[already there]".
66
+
67
+ ## Exiting
68
+
69
+ Press Ctrl+C at any time to exit cleanly:
70
+
71
+ ```
72
+ Closing safely... Take care!
73
+ ```
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env node
2
+
3
+ 'use strict';
4
+
5
+ const inquirer = require('inquirer');
6
+
7
+ const { clearScreen, printMainBanner, printSectionHeader, printExitMessage } = require('../lib/banner');
8
+ const { parseCommaList, parseLines } = require('../lib/parser');
9
+ const { buildAll } = require('../lib/builder');
10
+ const { structureFileExists, readStructureFileLines, STRUCTURE_FILENAME } = require('../lib/structureFile');
11
+ const { parseArgs, printHelp, printVersion } = require('../lib/cli');
12
+ const { checkNodeVersion, filterSafeEntries } = require('../lib/safety');
13
+
14
+ let exiting = false;
15
+ function handleSigint() {
16
+ if (exiting) return;
17
+ exiting = true;
18
+ printExitMessage();
19
+ process.exit(0);
20
+ }
21
+ process.on('SIGINT', handleSigint);
22
+
23
+ async function run() {
24
+ const flags = parseArgs(process.argv.slice(2));
25
+
26
+ if (flags.help) {
27
+ printHelp();
28
+ return;
29
+ }
30
+
31
+ if (flags.version) {
32
+ printVersion();
33
+ return;
34
+ }
35
+
36
+ if (!checkNodeVersion()) {
37
+ process.exitCode = 1;
38
+ return;
39
+ }
40
+
41
+ const cwd = process.cwd();
42
+
43
+ clearScreen();
44
+ printMainBanner();
45
+
46
+ if (flags.dryRun) {
47
+ printSectionHeader('Dry run -- nothing will actually be created');
48
+ console.log('');
49
+ }
50
+
51
+ let entries = [];
52
+
53
+ if (structureFileExists(cwd)) {
54
+ printSectionHeader(`Found ${STRUCTURE_FILENAME} -- building automatically`);
55
+ const lines = readStructureFileLines(cwd);
56
+ entries = parseLines(lines);
57
+
58
+ if (entries.length === 0) {
59
+ console.log(`[!] ${STRUCTURE_FILENAME} was found but contained no valid entries.`);
60
+ console.log('[!] Nothing to build. Exiting.');
61
+ return;
62
+ }
63
+ } else {
64
+ printSectionHeader('No structure.txt found -- manual mode');
65
+
66
+ const answer = await inquirer.prompt([
67
+ {
68
+ type: 'input',
69
+ name: 'items',
70
+ message: 'What do you want to build? (Separate items with commas)',
71
+ validate: (input) => {
72
+ if (!input || input.trim().length === 0) {
73
+ return 'Please enter at least one file or folder name.';
74
+ }
75
+ return true;
76
+ }
77
+ }
78
+ ]);
79
+
80
+ entries = parseCommaList(answer.items);
81
+
82
+ if (entries.length === 0) {
83
+ console.log('[!] No valid items were parsed from your input. Exiting.');
84
+ return;
85
+ }
86
+ }
87
+
88
+ const { safe, blocked } = filterSafeEntries(entries, cwd);
89
+
90
+ if (blocked.length > 0) {
91
+ console.log('');
92
+ console.log(`[!] Skipped ${blocked.length} unsafe entr${blocked.length === 1 ? 'y' : 'ies'} (outside the current folder):`);
93
+ blocked.forEach((entry) => console.log(`[!] ${entry.name}`));
94
+ }
95
+
96
+ if (safe.length === 0) {
97
+ console.log('');
98
+ console.log('[!] Nothing safe left to build. Exiting.');
99
+ return;
100
+ }
101
+
102
+ console.log('');
103
+ printSectionHeader(`Building ${safe.length} item(s) in: ${cwd}`);
104
+ console.log('');
105
+
106
+ const results = buildAll(safe, cwd, { dryRun: flags.dryRun });
107
+
108
+ const successCount = results.filter((r) => r.success).length;
109
+ const failCount = results.length - successCount;
110
+
111
+ console.log('');
112
+ printSectionHeader('Summary');
113
+ console.log(`[+] ${flags.dryRun ? 'Would create' : 'Created'}: ${successCount}`);
114
+ if (failCount > 0) {
115
+ console.log(`[!] Failed: ${failCount}`);
116
+ }
117
+ console.log('');
118
+ console.log('Done. Take care!');
119
+
120
+ const noFlagsUsed = !flags.dryRun && !flags.help && !flags.version;
121
+ if (noFlagsUsed) {
122
+ console.log('');
123
+ console.log('Tip: run "filemaker --help" to see all available options.');
124
+ }
125
+ }
126
+
127
+ run().catch((err) => {
128
+ console.log('');
129
+ console.log('[!] Something went wrong:');
130
+ console.log(`[!] ${err.message}`);
131
+ process.exit(1);
132
+ });
package/lib/banner.js ADDED
@@ -0,0 +1,39 @@
1
+ 'use strict';
2
+
3
+ function clearScreen() {
4
+ process.stdout.write(process.platform === 'win32' ? '\x1Bc' : '\x1B[2J\x1B[3J\x1B[H');
5
+ }
6
+
7
+ function printMainBanner() {
8
+ const banner = [
9
+ '=========================================',
10
+ '|| ||',
11
+ '|| F I L E M A K E R ||',
12
+ '|| Cross-Platform Scaffold Tool ||',
13
+ '|| ||',
14
+ '========================================='
15
+ ];
16
+ console.log(banner.join('\n'));
17
+ console.log('');
18
+ }
19
+
20
+ function printSectionHeader(title) {
21
+ const line = '-----------------------------------------';
22
+ console.log(line);
23
+ console.log(` ${title}`);
24
+ console.log(line);
25
+ }
26
+
27
+ function printExitMessage() {
28
+ console.log('');
29
+ console.log('-----------------------------------------');
30
+ console.log(' Closing safely... Take care!');
31
+ console.log('-----------------------------------------');
32
+ }
33
+
34
+ module.exports = {
35
+ clearScreen,
36
+ printMainBanner,
37
+ printSectionHeader,
38
+ printExitMessage
39
+ };
package/lib/builder.js ADDED
@@ -0,0 +1,73 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ function ensureDirSync(targetPath) {
7
+ fs.mkdirSync(targetPath, { recursive: true });
8
+ }
9
+
10
+ function ensureFileSync(targetPath) {
11
+ const dir = path.dirname(targetPath);
12
+ if (dir && dir !== '.') {
13
+ ensureDirSync(dir);
14
+ }
15
+ if (!fs.existsSync(targetPath)) {
16
+ fs.writeFileSync(targetPath, '');
17
+ }
18
+ }
19
+
20
+ function buildEntry(entry, baseDir, dryRun) {
21
+ const targetPath = path.join(baseDir, entry.name);
22
+ const alreadyExists = fs.existsSync(targetPath);
23
+
24
+ if (dryRun) {
25
+ return { ...entry, success: true, alreadyExists, dryRun: true };
26
+ }
27
+
28
+ try {
29
+ if (entry.type === 'DIR') {
30
+ ensureDirSync(targetPath);
31
+ } else {
32
+ ensureFileSync(targetPath);
33
+ }
34
+ return { ...entry, success: true, alreadyExists };
35
+ } catch (err) {
36
+ return { ...entry, success: false, error: err.message };
37
+ }
38
+ }
39
+
40
+ function buildAll(entries, baseDir, options = {}) {
41
+ const dryRun = Boolean(options.dryRun);
42
+ const results = [];
43
+
44
+ for (const entry of entries) {
45
+ const result = buildEntry(entry, baseDir, dryRun);
46
+ results.push(result);
47
+
48
+ const label = result.type === 'DIR' ? '[DIR]' : '[FILE]';
49
+
50
+ if (!result.success) {
51
+ console.log(`[!] FAILED: ${result.name} -- ${result.error}`);
52
+ continue;
53
+ }
54
+
55
+ if (dryRun) {
56
+ const tag = result.alreadyExists ? '[exists]' : '[would create]';
57
+ console.log(`[+] ${label} ${result.name} ${tag}`);
58
+ continue;
59
+ }
60
+
61
+ const tag = result.alreadyExists ? '[already there]' : '';
62
+ console.log(`[+] ${label} ${result.name} ${tag}`.trim());
63
+ }
64
+
65
+ return results;
66
+ }
67
+
68
+ module.exports = {
69
+ ensureDirSync,
70
+ ensureFileSync,
71
+ buildEntry,
72
+ buildAll
73
+ };
package/lib/cli.js ADDED
@@ -0,0 +1,65 @@
1
+ 'use strict';
2
+
3
+ const pkg = require('../package.json');
4
+
5
+ const HELP_TEXT = `
6
+ filemaker v${pkg.version}
7
+
8
+ Scaffold files and folders in seconds, right from your terminal.
9
+
10
+ USAGE
11
+ filemaker Build from structure.txt if present, otherwise prompt
12
+ filemaker --dry-run Show what would be built without touching disk
13
+ filemaker --version Print the installed version
14
+ filemaker --help Show this help message
15
+
16
+ HOW IT WORKS
17
+ Run filemaker inside any folder. If a structure.txt file exists there,
18
+ it gets read line by line and everything in it gets built automatically.
19
+ No structure.txt? You'll be asked what to build, comma separated.
20
+
21
+ STRUCTURE.TXT FORMAT
22
+ src/
23
+ src/index.js
24
+ .env
25
+ .gitignore
26
+ README.md
27
+
28
+ One entry per line. A trailing slash always means a folder. Anything
29
+ starting with a dot is always a file. Anything else with a dot in the
30
+ name is treated as a file too. Everything else is a folder.
31
+
32
+ EXAMPLES
33
+ filemaker
34
+ filemaker --dry-run
35
+ `;
36
+
37
+ function parseArgs(argv) {
38
+ const flags = {
39
+ help: false,
40
+ version: false,
41
+ dryRun: false
42
+ };
43
+
44
+ for (const arg of argv) {
45
+ if (arg === '--help' || arg === '-h') flags.help = true;
46
+ if (arg === '--version' || arg === '-v') flags.version = true;
47
+ if (arg === '--dry-run' || arg === '-d') flags.dryRun = true;
48
+ }
49
+
50
+ return flags;
51
+ }
52
+
53
+ function printHelp() {
54
+ console.log(HELP_TEXT);
55
+ }
56
+
57
+ function printVersion() {
58
+ console.log(pkg.version);
59
+ }
60
+
61
+ module.exports = {
62
+ parseArgs,
63
+ printHelp,
64
+ printVersion
65
+ };
package/lib/parser.js ADDED
@@ -0,0 +1,74 @@
1
+ 'use strict';
2
+
3
+ const KNOWN_FILES_NO_EXTENSION = new Set([
4
+ 'license',
5
+ 'licence',
6
+ 'dockerfile',
7
+ 'makefile',
8
+ 'readme',
9
+ 'changelog',
10
+ 'authors',
11
+ 'contributing',
12
+ 'procfile',
13
+ 'vagrantfile',
14
+ 'jenkinsfile',
15
+ 'rakefile',
16
+ 'gemfile'
17
+ ]);
18
+
19
+ function classifyEntry(rawEntry) {
20
+ const trimmed = rawEntry.trim();
21
+
22
+ if (!trimmed) {
23
+ return null;
24
+ }
25
+
26
+ if (trimmed.endsWith('/') || trimmed.endsWith('\\')) {
27
+ return {
28
+ name: trimmed.replace(/[/\\]+$/, ''),
29
+ type: 'DIR'
30
+ };
31
+ }
32
+
33
+ const isLeadingDot = trimmed.startsWith('.');
34
+ const nameForCheck = isLeadingDot ? trimmed.slice(1) : trimmed;
35
+ const hasExtension = /\.[^.]+$/.test(nameForCheck);
36
+
37
+ if (isLeadingDot) {
38
+ return { name: trimmed, type: 'FILE' };
39
+ }
40
+
41
+ if (hasExtension) {
42
+ return { name: trimmed, type: 'FILE' };
43
+ }
44
+
45
+ const baseName = trimmed.split(/[/\\]/).pop().toLowerCase();
46
+ if (KNOWN_FILES_NO_EXTENSION.has(baseName)) {
47
+ return { name: trimmed, type: 'FILE' };
48
+ }
49
+
50
+ return { name: trimmed, type: 'DIR' };
51
+ }
52
+
53
+ function parseCommaList(input) {
54
+ return input
55
+ .split(',')
56
+ .map((item) => item.trim())
57
+ .filter((item) => item.length > 0)
58
+ .map(classifyEntry)
59
+ .filter(Boolean);
60
+ }
61
+
62
+ function parseLines(lines) {
63
+ return lines
64
+ .map((line) => line.trim())
65
+ .filter((line) => line.length > 0 && !line.startsWith('#'))
66
+ .map(classifyEntry)
67
+ .filter(Boolean);
68
+ }
69
+
70
+ module.exports = {
71
+ classifyEntry,
72
+ parseCommaList,
73
+ parseLines
74
+ };
package/lib/safety.js ADDED
@@ -0,0 +1,51 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+
5
+ const MIN_NODE_MAJOR = 14;
6
+
7
+ function checkNodeVersion() {
8
+ const major = parseInt(process.versions.node.split('.')[0], 10);
9
+ if (major < MIN_NODE_MAJOR) {
10
+ console.log(`[!] filemaker needs Node ${MIN_NODE_MAJOR} or newer. You're running Node ${process.versions.node}.`);
11
+ console.log('[!] Please update Node and try again.');
12
+ return false;
13
+ }
14
+ return true;
15
+ }
16
+
17
+ function isPathSafe(baseDir, entryName) {
18
+ const resolvedBase = path.resolve(baseDir);
19
+ const resolvedTarget = path.resolve(baseDir, entryName);
20
+
21
+ if (path.isAbsolute(entryName)) {
22
+ return false;
23
+ }
24
+
25
+ if (entryName.split(/[/\\]/).includes('..')) {
26
+ return false;
27
+ }
28
+
29
+ return resolvedTarget.startsWith(resolvedBase + path.sep) || resolvedTarget === resolvedBase;
30
+ }
31
+
32
+ function filterSafeEntries(entries, baseDir) {
33
+ const safe = [];
34
+ const blocked = [];
35
+
36
+ for (const entry of entries) {
37
+ if (isPathSafe(baseDir, entry.name)) {
38
+ safe.push(entry);
39
+ } else {
40
+ blocked.push(entry);
41
+ }
42
+ }
43
+
44
+ return { safe, blocked };
45
+ }
46
+
47
+ module.exports = {
48
+ checkNodeVersion,
49
+ isPathSafe,
50
+ filterSafeEntries
51
+ };
@@ -0,0 +1,28 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ const STRUCTURE_FILENAME = 'structure.txt';
7
+
8
+ function getStructureFilePath(baseDir) {
9
+ return path.join(baseDir, STRUCTURE_FILENAME);
10
+ }
11
+
12
+ function structureFileExists(baseDir) {
13
+ const filePath = getStructureFilePath(baseDir);
14
+ return fs.existsSync(filePath) && fs.statSync(filePath).isFile();
15
+ }
16
+
17
+ function readStructureFileLines(baseDir) {
18
+ const filePath = getStructureFilePath(baseDir);
19
+ const raw = fs.readFileSync(filePath, 'utf8');
20
+ return raw.split(/\r?\n/);
21
+ }
22
+
23
+ module.exports = {
24
+ STRUCTURE_FILENAME,
25
+ getStructureFilePath,
26
+ structureFileExists,
27
+ readStructureFileLines
28
+ };
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "filemaker-cli",
3
+ "version": "0.1.1",
4
+ "description": "A cross-platform CLI tool to scaffold files and folders instantly from a list or a structure.txt blueprint.",
5
+ "main": "bin/filemaker.js",
6
+ "bin": {
7
+ "filemaker": "bin/filemaker.js"
8
+ },
9
+ "preferGlobal": true,
10
+ "engines": {
11
+ "node": ">=14.0.0"
12
+ },
13
+ "files": [
14
+ "bin/",
15
+ "lib/"
16
+ ],
17
+ "scripts": {
18
+ "start": "node bin/filemaker.js"
19
+ },
20
+ "keywords": [
21
+ "cli",
22
+ "scaffold",
23
+ "file-generator",
24
+ "folder-structure",
25
+ "boilerplate",
26
+ "project-setup"
27
+ ],
28
+ "author": "",
29
+ "license": "MIT",
30
+ "dependencies": {
31
+ "inquirer": "^8.2.6"
32
+ }
33
+ }