@gajae-code/utils 0.8.0 → 0.8.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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@gajae-code/utils",
4
- "version": "0.8.0",
4
+ "version": "0.8.2",
5
5
  "description": "Shared utilities for pi packages",
6
6
  "homepage": "https://gajae-code.com",
7
7
  "author": "Yeachan-Heo",
@@ -31,7 +31,7 @@
31
31
  "fmt": "biome format --write ."
32
32
  },
33
33
  "dependencies": {
34
- "@gajae-code/natives": "0.8.0",
34
+ "@gajae-code/natives": "0.8.2",
35
35
  "beautiful-mermaid": "^1.1.3",
36
36
  "handlebars": "^4.7.9",
37
37
  "winston": "^3.19.0",
package/src/env.ts CHANGED
@@ -272,7 +272,10 @@ export function isCompiledBinary(): boolean {
272
272
 
273
273
  const TRUTHY: Dict<boolean> = { "1": true, Y: true, TRUE: true, YES: true, ON: true };
274
274
  export function $flag(name: string, def: boolean = false): boolean {
275
- const value = $env[name];
275
+ const value = $env[name]?.trim();
276
276
  if (!value) return def;
277
- return TRUTHY[value] === true;
277
+ // Boolean-like env values are documented as case-insensitive (`1`/`true`/`yes`/`on`),
278
+ // so normalize before the lookup — otherwise `FOO=true` (the common lowercase spelling)
279
+ // would silently read as false while only `FOO=TRUE`/`FOO=1` worked.
280
+ return TRUTHY[value.toUpperCase()] === true;
278
281
  }
@@ -17,15 +17,27 @@ function stripLooseScalarTrailingCommas(metadata: string): string {
17
17
  .join("\n");
18
18
  }
19
19
 
20
+ function asFrontmatterRecord(parsed: unknown): Record<string, unknown> | null {
21
+ if (parsed === null) return null;
22
+ if (typeof parsed !== "object" || Array.isArray(parsed)) {
23
+ throw new Error("YAML frontmatter root must be an object");
24
+ }
25
+ const prototype = Object.getPrototypeOf(parsed);
26
+ if (prototype !== Object.prototype && prototype !== null) {
27
+ throw new Error("YAML frontmatter root must be an object");
28
+ }
29
+ return parsed as Record<string, unknown>;
30
+ }
31
+
20
32
  function parseYamlMetadata(metadata: string): Record<string, unknown> | null {
21
33
  const normalized = metadata.replaceAll("\t", " ");
22
34
  try {
23
- return YAML.parse(normalized) as Record<string, unknown> | null;
35
+ return asFrontmatterRecord(YAML.parse(normalized));
24
36
  } catch (strictError) {
25
37
  const loose = stripLooseScalarTrailingCommas(normalized);
26
38
  if (loose === normalized) throw strictError;
27
39
  try {
28
- return YAML.parse(loose) as Record<string, unknown> | null;
40
+ return asFrontmatterRecord(YAML.parse(loose));
29
41
  } catch {
30
42
  throw strictError;
31
43
  }
package/src/glob.ts CHANGED
@@ -70,20 +70,25 @@ function parseGitignorePatterns(content: string, gitignoreDir: string, baseDir:
70
70
  } else {
71
71
  patterns.push(pattern);
72
72
  }
73
+ } else if (pattern.includes("/") && !pattern.startsWith("**/")) {
74
+ // Separator in the middle: git anchors these to the .gitignore's
75
+ // directory, same as rooted patterns
76
+ const absolutePattern = path.join(gitignoreDir, pattern);
77
+ const relativeToBase = path.relative(baseDir, absolutePattern);
78
+ if (relativeToBase.startsWith("..")) {
79
+ // Pattern is outside the search directory, skip
80
+ continue;
81
+ }
82
+ pattern = relativeToBase.replace(/\\/g, "/");
83
+ patterns.push(pattern);
84
+ if (isDirectoryOnly) {
85
+ patterns.push(`${pattern}/**`);
86
+ }
73
87
  } else {
74
- // Unrooted pattern: match anywhere in the tree
75
- if (pattern.includes("/")) {
76
- // Contains slash: match from any directory level
77
- patterns.push(`**/${pattern}`);
78
- if (isDirectoryOnly) {
79
- patterns.push(`**/${pattern}/**`);
80
- }
81
- } else {
82
- // No slash: match file/dir name anywhere
83
- patterns.push(`**/${pattern}`);
84
- if (isDirectoryOnly) {
85
- patterns.push(`**/${pattern}/**`);
86
- }
88
+ // No middle separator: match file/dir name anywhere in the tree
89
+ patterns.push(`**/${pattern}`);
90
+ if (isDirectoryOnly) {
91
+ patterns.push(`**/${pattern}/**`);
87
92
  }
88
93
  }
89
94
  }
@@ -149,6 +154,9 @@ export async function globPaths(patterns: string | string[], options: GlobPathsO
149
154
 
150
155
  const base = cwd ?? getProjectDir();
151
156
  const allResults: string[] = [];
157
+ // Overlapping patterns (e.g. `["**/*.ts", "src/*.ts"]`) can both match the same
158
+ // file; dedupe so a path is returned at most once regardless of pattern overlap.
159
+ const seen = new Set<string>();
152
160
 
153
161
  // Combine timeout and abort signals
154
162
  const timeoutSignal = timeoutMs ? AbortSignal.timeout(timeoutMs) : undefined;
@@ -176,6 +184,10 @@ export async function globPaths(patterns: string | string[], options: GlobPathsO
176
184
  if (excludeGlobs.some(excludeGlob => excludeGlob.match(normalized))) {
177
185
  continue;
178
186
  }
187
+ if (seen.has(normalized)) {
188
+ continue;
189
+ }
190
+ seen.add(normalized);
179
191
  allResults.push(normalized);
180
192
  }
181
193
  }