create-contractor-site 2.3.0 → 2.3.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/README.md +145 -146
- package/bin/create-contractor-site.mjs +321 -321
- package/package.json +1 -1
- package/scripts/smoke-test.mjs +1819 -1756
- package/src/copy-template.mjs +296 -296
- package/src/prompts.mjs +461 -461
- package/src/replace-data.mjs +504 -504
- package/src/run-command.mjs +399 -399
- package/src/validate-target.mjs +96 -96
package/src/validate-target.mjs
CHANGED
|
@@ -1,96 +1,96 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Normalize a path for stable prefix comparisons on Windows and POSIX.
|
|
6
|
-
*
|
|
7
|
-
* @param {string} value
|
|
8
|
-
* @returns {string}
|
|
9
|
-
*/
|
|
10
|
-
export function normalizePathForCompare(value) {
|
|
11
|
-
const resolved = path.resolve(value);
|
|
12
|
-
// path.resolve already drops trailing separators except root; lower-case on win32.
|
|
13
|
-
const normalized =
|
|
14
|
-
process.platform === 'win32' ? resolved.toLowerCase() : resolved;
|
|
15
|
-
// Ensure directory prefix checks use a trailing separator boundary.
|
|
16
|
-
return normalized;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* True when `candidate` is the same path as `root` or a path inside `root`.
|
|
21
|
-
*
|
|
22
|
-
* @param {string} candidate
|
|
23
|
-
* @param {string} root
|
|
24
|
-
* @returns {boolean}
|
|
25
|
-
*/
|
|
26
|
-
export function isSameOrInside(candidate, root) {
|
|
27
|
-
const child = normalizePathForCompare(candidate);
|
|
28
|
-
const parent = normalizePathForCompare(root);
|
|
29
|
-
|
|
30
|
-
if (child === parent) return true;
|
|
31
|
-
|
|
32
|
-
const prefix = parent.endsWith(path.sep) ? parent : parent + path.sep;
|
|
33
|
-
return child.startsWith(prefix);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Resolve and validate the scaffold target directory.
|
|
38
|
-
* Refuses existing non-empty directories.
|
|
39
|
-
* When `templateRoot` is provided, refuses targets equal to or inside the template.
|
|
40
|
-
*
|
|
41
|
-
* @param {string} rawTarget
|
|
42
|
-
* @param {{ templateRoot?: string }} [opts]
|
|
43
|
-
* @returns {string} absolute target path
|
|
44
|
-
*/
|
|
45
|
-
export function validateTarget(rawTarget, opts = {}) {
|
|
46
|
-
if (!rawTarget || !String(rawTarget).trim()) {
|
|
47
|
-
throw new TargetValidationError(
|
|
48
|
-
'Missing target directory.\n\nUsage: create-contractor-site <target-dir>',
|
|
49
|
-
);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const targetDir = path.resolve(process.cwd(), String(rawTarget).trim());
|
|
53
|
-
const { templateRoot } = opts;
|
|
54
|
-
|
|
55
|
-
if (templateRoot) {
|
|
56
|
-
const root = path.resolve(templateRoot);
|
|
57
|
-
if (isSameOrInside(targetDir, root)) {
|
|
58
|
-
throw new TargetValidationError(
|
|
59
|
-
`Target directory must not be the template root or inside it.\n` +
|
|
60
|
-
` Template: ${root}\n` +
|
|
61
|
-
` Target: ${targetDir}\n` +
|
|
62
|
-
`Choose a directory outside the template repository.`,
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (!fs.existsSync(targetDir)) {
|
|
68
|
-
return targetDir;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const stat = fs.statSync(targetDir);
|
|
72
|
-
if (!stat.isDirectory()) {
|
|
73
|
-
throw new TargetValidationError(
|
|
74
|
-
`Target path exists and is not a directory: ${targetDir}`,
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const entries = fs.readdirSync(targetDir);
|
|
79
|
-
if (entries.length > 0) {
|
|
80
|
-
throw new TargetValidationError(
|
|
81
|
-
`Target directory is not empty: ${targetDir}\nChoose a different directory or remove existing files first.`,
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return targetDir;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export class TargetValidationError extends Error {
|
|
89
|
-
/**
|
|
90
|
-
* @param {string} message
|
|
91
|
-
*/
|
|
92
|
-
constructor(message) {
|
|
93
|
-
super(message);
|
|
94
|
-
this.name = 'TargetValidationError';
|
|
95
|
-
}
|
|
96
|
-
}
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Normalize a path for stable prefix comparisons on Windows and POSIX.
|
|
6
|
+
*
|
|
7
|
+
* @param {string} value
|
|
8
|
+
* @returns {string}
|
|
9
|
+
*/
|
|
10
|
+
export function normalizePathForCompare(value) {
|
|
11
|
+
const resolved = path.resolve(value);
|
|
12
|
+
// path.resolve already drops trailing separators except root; lower-case on win32.
|
|
13
|
+
const normalized =
|
|
14
|
+
process.platform === 'win32' ? resolved.toLowerCase() : resolved;
|
|
15
|
+
// Ensure directory prefix checks use a trailing separator boundary.
|
|
16
|
+
return normalized;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* True when `candidate` is the same path as `root` or a path inside `root`.
|
|
21
|
+
*
|
|
22
|
+
* @param {string} candidate
|
|
23
|
+
* @param {string} root
|
|
24
|
+
* @returns {boolean}
|
|
25
|
+
*/
|
|
26
|
+
export function isSameOrInside(candidate, root) {
|
|
27
|
+
const child = normalizePathForCompare(candidate);
|
|
28
|
+
const parent = normalizePathForCompare(root);
|
|
29
|
+
|
|
30
|
+
if (child === parent) return true;
|
|
31
|
+
|
|
32
|
+
const prefix = parent.endsWith(path.sep) ? parent : parent + path.sep;
|
|
33
|
+
return child.startsWith(prefix);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Resolve and validate the scaffold target directory.
|
|
38
|
+
* Refuses existing non-empty directories.
|
|
39
|
+
* When `templateRoot` is provided, refuses targets equal to or inside the template.
|
|
40
|
+
*
|
|
41
|
+
* @param {string} rawTarget
|
|
42
|
+
* @param {{ templateRoot?: string }} [opts]
|
|
43
|
+
* @returns {string} absolute target path
|
|
44
|
+
*/
|
|
45
|
+
export function validateTarget(rawTarget, opts = {}) {
|
|
46
|
+
if (!rawTarget || !String(rawTarget).trim()) {
|
|
47
|
+
throw new TargetValidationError(
|
|
48
|
+
'Missing target directory.\n\nUsage: create-contractor-site <target-dir>',
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const targetDir = path.resolve(process.cwd(), String(rawTarget).trim());
|
|
53
|
+
const { templateRoot } = opts;
|
|
54
|
+
|
|
55
|
+
if (templateRoot) {
|
|
56
|
+
const root = path.resolve(templateRoot);
|
|
57
|
+
if (isSameOrInside(targetDir, root)) {
|
|
58
|
+
throw new TargetValidationError(
|
|
59
|
+
`Target directory must not be the template root or inside it.\n` +
|
|
60
|
+
` Template: ${root}\n` +
|
|
61
|
+
` Target: ${targetDir}\n` +
|
|
62
|
+
`Choose a directory outside the template repository.`,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!fs.existsSync(targetDir)) {
|
|
68
|
+
return targetDir;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const stat = fs.statSync(targetDir);
|
|
72
|
+
if (!stat.isDirectory()) {
|
|
73
|
+
throw new TargetValidationError(
|
|
74
|
+
`Target path exists and is not a directory: ${targetDir}`,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const entries = fs.readdirSync(targetDir);
|
|
79
|
+
if (entries.length > 0) {
|
|
80
|
+
throw new TargetValidationError(
|
|
81
|
+
`Target directory is not empty: ${targetDir}\nChoose a different directory or remove existing files first.`,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return targetDir;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export class TargetValidationError extends Error {
|
|
89
|
+
/**
|
|
90
|
+
* @param {string} message
|
|
91
|
+
*/
|
|
92
|
+
constructor(message) {
|
|
93
|
+
super(message);
|
|
94
|
+
this.name = 'TargetValidationError';
|
|
95
|
+
}
|
|
96
|
+
}
|