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 +21 -0
- package/README.md +73 -0
- package/bin/filemaker.js +132 -0
- package/lib/banner.js +39 -0
- package/lib/builder.js +73 -0
- package/lib/cli.js +65 -0
- package/lib/parser.js +74 -0
- package/lib/safety.js +51 -0
- package/lib/structureFile.js +28 -0
- package/package.json +33 -0
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
|
+
```
|
package/bin/filemaker.js
ADDED
|
@@ -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
|
+
}
|