@nci-gis/js-tmpl 0.1.1 → 0.1.2
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/package.json
CHANGED
|
@@ -24,7 +24,7 @@ function placeInTree(tree, chain, value) {
|
|
|
24
24
|
let cur = tree;
|
|
25
25
|
for (let i = 0; i < chain.length - 1; i++) {
|
|
26
26
|
const seg = chain[i];
|
|
27
|
-
if (!(seg
|
|
27
|
+
if (!Object.hasOwn(cur, seg)) {
|
|
28
28
|
cur[seg] = {};
|
|
29
29
|
}
|
|
30
30
|
cur = /** @type {Record<string, unknown>} */ (cur[seg]);
|
package/src/config/view.js
CHANGED
|
@@ -48,7 +48,7 @@ export function buildView(args = {}) {
|
|
|
48
48
|
* @param {string} valuesDir
|
|
49
49
|
*/
|
|
50
50
|
function assertReservedEnvNotInPartials(partials, valuesDir) {
|
|
51
|
-
if (RESERVED_ENV
|
|
51
|
+
if (Object.hasOwn(partials, RESERVED_ENV)) {
|
|
52
52
|
throw new Error(
|
|
53
53
|
`Value partial conflicts with reserved 'env' namespace.\n` +
|
|
54
54
|
` Source: ${valuesDir} produced a top-level 'env' namespace.\n` +
|
|
@@ -73,7 +73,7 @@ function assertNoRootNamespaceCollision(
|
|
|
73
73
|
if (key === RESERVED_ENV) {
|
|
74
74
|
continue;
|
|
75
75
|
}
|
|
76
|
-
if (key
|
|
76
|
+
if (Object.hasOwn(partials, key)) {
|
|
77
77
|
throw new Error(
|
|
78
78
|
`Duplicate view key '${key}' — registered by both:\n` +
|
|
79
79
|
` - ${valuesFile} top-level key\n` +
|
|
@@ -87,7 +87,7 @@ function assertNoRootNamespaceCollision(
|
|
|
87
87
|
* @param {Record<string, unknown>} values
|
|
88
88
|
*/
|
|
89
89
|
function warnOnReservedEnvInValuesFile(values) {
|
|
90
|
-
if (RESERVED_ENV
|
|
90
|
+
if (Object.hasOwn(values, RESERVED_ENV)) {
|
|
91
91
|
console.warn(
|
|
92
92
|
'Warning: "env" is a reserved key in js-tmpl and will be overwritten.\n' +
|
|
93
93
|
'Rename the "env" key in your values file to avoid this.',
|
|
@@ -109,7 +109,7 @@ export function pickEnv({ keys = [], prefix = '' }, source = process.env) {
|
|
|
109
109
|
const result = {};
|
|
110
110
|
|
|
111
111
|
for (const k of keys) {
|
|
112
|
-
if (k
|
|
112
|
+
if (Object.hasOwn(source, k)) {
|
|
113
113
|
result[k] = /** @type {string} */ (source[k]);
|
|
114
114
|
}
|
|
115
115
|
}
|
package/src/engine/treeWalker.js
CHANGED
|
@@ -27,6 +27,10 @@ function shouldSkipSubtree(rel, view) {
|
|
|
27
27
|
* prune the subtree before any filesystem descent (early-exit — no
|
|
28
28
|
* `stat`/`readdir` on skipped paths).
|
|
29
29
|
*
|
|
30
|
+
* Symbolic links are followed. A directory that resolves to one of its own
|
|
31
|
+
* ancestors throws instead of looping; the same directory linked from two
|
|
32
|
+
* places (no cycle) is walked twice.
|
|
33
|
+
*
|
|
30
34
|
* @param {string} rootDir
|
|
31
35
|
* @param {(string | { ext?: string, view?: Record<string, unknown> })} [optsOrExt]
|
|
32
36
|
* Options object, or a bare `ext` string for back-compat.
|
|
@@ -40,10 +44,13 @@ export async function walkTemplateTree(rootDir, optsOrExt) {
|
|
|
40
44
|
|
|
41
45
|
/** @type {import('../types.js').TemplateFile[]} */
|
|
42
46
|
const results = [];
|
|
43
|
-
|
|
47
|
+
/** @type {Array<{ rel: string, ancestors: Array<{ rel: string, real: string }> }>} */
|
|
48
|
+
const queue = [{ rel: '', ancestors: [] }];
|
|
44
49
|
|
|
45
50
|
while (queue.length) {
|
|
46
|
-
const rel = /** @type {
|
|
51
|
+
const { rel, ancestors } = /** @type {(typeof queue)[number]} */ (
|
|
52
|
+
queue.shift()
|
|
53
|
+
);
|
|
47
54
|
const abs = path.join(rootDir, rel);
|
|
48
55
|
const stat = await fs.stat(abs);
|
|
49
56
|
|
|
@@ -51,9 +58,21 @@ export async function walkTemplateTree(rootDir, optsOrExt) {
|
|
|
51
58
|
if (shouldSkipSubtree(rel, view)) {
|
|
52
59
|
continue;
|
|
53
60
|
}
|
|
61
|
+
const real = await fs.realpath(abs);
|
|
62
|
+
const loop = ancestors.find((a) => a.real === real);
|
|
63
|
+
if (loop) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
`Template directory '${rel}' links back to '${loop.rel || '.'}' (a symbolic link cycle in '${rootDir}').\n` +
|
|
66
|
+
'Remove the link, or point it outside its own parent directories.',
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
const chain = [...ancestors, { rel, real }];
|
|
54
70
|
const items = (await fs.readdir(abs)).sort();
|
|
55
71
|
for (const name of items) {
|
|
56
|
-
queue.push(
|
|
72
|
+
queue.push({
|
|
73
|
+
rel: rel ? path.join(rel, name) : name,
|
|
74
|
+
ancestors: chain,
|
|
75
|
+
});
|
|
57
76
|
}
|
|
58
77
|
} else if (path.extname(abs) === ext) {
|
|
59
78
|
results.push({ absPath: abs, relPath: rel });
|
package/src/utils/namespacing.js
CHANGED
|
@@ -21,6 +21,11 @@ const FLATTEN_SEGMENT_RE = /^@\w+$/;
|
|
|
21
21
|
* @param {string} [label='namespace'] - Noun used in the error message (e.g. "partial name").
|
|
22
22
|
*/
|
|
23
23
|
export function assertValidSegments(segments, filePath, label = 'namespace') {
|
|
24
|
+
if (segments.includes('__proto__')) {
|
|
25
|
+
throw new Error(
|
|
26
|
+
`Invalid ${label} segment '__proto__' in ${filePath} — reserved name`,
|
|
27
|
+
);
|
|
28
|
+
}
|
|
24
29
|
if (!SEGMENT_RE.test(segments.join(''))) {
|
|
25
30
|
throw new Error(
|
|
26
31
|
`Invalid ${label} segment '${segments.join('>')}' in ${filePath} — only alphanumeric and underscore allowed`,
|