ngx-locatorjs 0.4.0 → 0.5.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.ko.md +27 -10
- package/README.md +27 -9
- package/dist/node/cmp-scan.js +20 -16
- package/dist/node/config-setup.js +281 -15
- package/dist/node/file-opener.js +4 -4
- package/package.json +3 -1
package/README.ko.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# ngx-locatorjs (Open-in-Editor)
|
|
2
2
|
|
|
3
|
+
Angular 인기가 적어서인지 [LocatorJs](https://www.locatorjs.com/)와 같은 개발 도구가 없어 불편했습니다. 그래서 CODEX와 함께 만들어보았습니다.
|
|
3
4
|
브라우저에서 Alt+클릭으로 Angular 컴포넌트 파일을 에디터에서 바로 여는 개발용 도구입니다. Angular 프로젝트 어디에나 npm 패키지로 설치해 사용할 수 있습니다.
|
|
4
|
-
이 프로젝트는 [locatorjs.com](https://www.locatorjs.com/)에서 영감을 받았습니다.
|
|
5
5
|
|
|
6
6
|
**기능**
|
|
7
7
|
|
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
- Alt 키 홀드: 컴포넌트 하이라이트 + 툴팁 표시
|
|
11
11
|
- Antigravity IDE, Cursor, Zed, VS Code, WebStorm 지원
|
|
12
12
|
|
|
13
|
+
**Angular 버전 지원**
|
|
14
|
+
|
|
15
|
+
지원 범위: **Angular 15~22**
|
|
16
|
+
|
|
13
17
|
**필수 단계 (1~4 반드시 수행)**
|
|
14
18
|
|
|
15
19
|
1. 패키지 설치: `npm i -D ngx-locatorjs`
|
|
@@ -109,19 +113,19 @@ bootstrapApplication(AppComponent, appConfig)
|
|
|
109
113
|
"authToken": "locatorjs-config가 자동 생성",
|
|
110
114
|
"scan": {
|
|
111
115
|
"includeGlobs": [
|
|
112
|
-
"src/**/*.{ts,tsx}",
|
|
113
|
-
"projects/**/*.{ts,tsx}",
|
|
114
|
-
"apps/**/*.{ts,tsx}",
|
|
115
|
-
"libs/**/*.{ts,tsx}"
|
|
116
|
+
"src/**/*.{ts,tsx,js,jsx}",
|
|
117
|
+
"projects/**/*.{ts,tsx,js,jsx}",
|
|
118
|
+
"apps/**/*.{ts,tsx,js,jsx}",
|
|
119
|
+
"libs/**/*.{ts,tsx,js,jsx}"
|
|
116
120
|
],
|
|
117
121
|
"excludeGlobs": [
|
|
118
122
|
"**/node_modules/**",
|
|
119
123
|
"**/dist/**",
|
|
120
124
|
"**/.angular/**",
|
|
121
125
|
"**/coverage/**",
|
|
122
|
-
"**/*.spec.ts",
|
|
123
|
-
"**/*.test.ts",
|
|
124
|
-
"**/*.e2e.ts"
|
|
126
|
+
"**/*.spec.{ts,tsx,js,jsx}",
|
|
127
|
+
"**/*.test.{ts,tsx,js,jsx}",
|
|
128
|
+
"**/*.e2e.{ts,tsx,js,jsx}"
|
|
125
129
|
]
|
|
126
130
|
}
|
|
127
131
|
}
|
|
@@ -138,14 +142,27 @@ bootstrapApplication(AppComponent, appConfig)
|
|
|
138
142
|
- `scan.includeGlobs`: 컴포넌트 소스 파일 탐색 대상 glob 목록입니다.
|
|
139
143
|
- `scan.excludeGlobs`: 컴포넌트 스캔에서 제외할 glob 목록입니다.
|
|
140
144
|
|
|
145
|
+
**authToken 동작 방식 (0.5.0)**
|
|
146
|
+
|
|
147
|
+
- `locatorjs-config`가 프로젝트별 `authToken`을 `ngx-locatorjs.config.json`에 자동 생성합니다.
|
|
148
|
+
- `ngx-locatorjs.proxy.cjs`가 해당 토큰을 읽어 opener 라우트로 `x-locatorjs-token` 헤더를 전달합니다.
|
|
149
|
+
- Angular proxy 없이 opener 엔드포인트를 직접 호출할 경우 `installAngularLocator(...)`에 `authToken`을 전달해야 인증됩니다.
|
|
150
|
+
- `401 Unauthorized`가 발생하면 `npx locatorjs-config`를 다시 실행해 config/proxy를 재생성하세요.
|
|
151
|
+
|
|
152
|
+
**자동 스캔 범위 탐색 (0.5.0)**
|
|
153
|
+
|
|
154
|
+
- `locatorjs-config`가 스캔 루트를 자동으로 구성합니다.
|
|
155
|
+
- 기본 탐색 확장자는 `ts/tsx/js/jsx`를 포함합니다.
|
|
156
|
+
- 생성된 `scan.includeGlobs` / `scan.excludeGlobs`는 언제든 수동 수정 가능합니다.
|
|
157
|
+
|
|
141
158
|
**프로젝트 구조별 includeGlobs 예시**
|
|
142
159
|
|
|
143
160
|
1. 일반 Angular 앱
|
|
144
161
|
`["src/app/**/*.ts"]`
|
|
145
162
|
2. Angular Workspace (projects/)
|
|
146
|
-
`["projects/**/*.{ts,tsx}"]`
|
|
163
|
+
`["projects/**/*.{ts,tsx,js,jsx}"]`
|
|
147
164
|
3. Nx (apps/libs)
|
|
148
|
-
`["apps/**/*.{ts,tsx}", "libs/**/*.{ts,tsx}"]`
|
|
165
|
+
`["apps/**/*.{ts,tsx,js,jsx}", "libs/**/*.{ts,tsx,js,jsx}"]`
|
|
149
166
|
|
|
150
167
|
**환경변수 우선순위**
|
|
151
168
|
|
package/README.md
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
한국어 문서: [README.ko.md](README.ko.md)
|
|
6
6
|
|
|
7
|
+
Angular doesn’t have many developer tools like LocatorJs, likely due to lower popularity, which has been inconvenient. So I built one together with CODEX.
|
|
7
8
|
Open Angular component files directly from the browser with **Alt + Click** during development. This package provides:
|
|
8
9
|
|
|
9
10
|
- Browser runtime for Alt+click / hover UI
|
|
@@ -19,6 +20,10 @@ Inspired by [locatorjs.com](https://www.locatorjs.com/).
|
|
|
19
20
|
- **Hold Alt**: highlight component + tooltip
|
|
20
21
|
- Supports **Antigravity IDE**, **Cursor**, **Zed**, **VS Code**, **WebStorm**
|
|
21
22
|
|
|
23
|
+
## Angular Version Support
|
|
24
|
+
|
|
25
|
+
Tested support: **Angular 15–22**.
|
|
26
|
+
|
|
22
27
|
## Install
|
|
23
28
|
|
|
24
29
|
```bash
|
|
@@ -109,19 +114,19 @@ Example:
|
|
|
109
114
|
"authToken": "generated-by-locatorjs-config",
|
|
110
115
|
"scan": {
|
|
111
116
|
"includeGlobs": [
|
|
112
|
-
"src/**/*.{ts,tsx}",
|
|
113
|
-
"projects/**/*.{ts,tsx}",
|
|
114
|
-
"apps/**/*.{ts,tsx}",
|
|
115
|
-
"libs/**/*.{ts,tsx}"
|
|
117
|
+
"src/**/*.{ts,tsx,js,jsx}",
|
|
118
|
+
"projects/**/*.{ts,tsx,js,jsx}",
|
|
119
|
+
"apps/**/*.{ts,tsx,js,jsx}",
|
|
120
|
+
"libs/**/*.{ts,tsx,js,jsx}"
|
|
116
121
|
],
|
|
117
122
|
"excludeGlobs": [
|
|
118
123
|
"**/node_modules/**",
|
|
119
124
|
"**/dist/**",
|
|
120
125
|
"**/.angular/**",
|
|
121
126
|
"**/coverage/**",
|
|
122
|
-
"**/*.spec.ts",
|
|
123
|
-
"**/*.test.ts",
|
|
124
|
-
"**/*.e2e.ts"
|
|
127
|
+
"**/*.spec.{ts,tsx,js,jsx}",
|
|
128
|
+
"**/*.test.{ts,tsx,js,jsx}",
|
|
129
|
+
"**/*.e2e.{ts,tsx,js,jsx}"
|
|
125
130
|
]
|
|
126
131
|
}
|
|
127
132
|
}
|
|
@@ -138,11 +143,24 @@ Example:
|
|
|
138
143
|
- `scan.includeGlobs`: Globs used to find component source files.
|
|
139
144
|
- `scan.excludeGlobs`: Globs excluded from component scanning.
|
|
140
145
|
|
|
146
|
+
### authToken Behavior (0.5.0)
|
|
147
|
+
|
|
148
|
+
- `locatorjs-config` generates a per-project `authToken` in `ngx-locatorjs.config.json`.
|
|
149
|
+
- `ngx-locatorjs.proxy.cjs` reads that token and forwards it as `x-locatorjs-token` to opener routes.
|
|
150
|
+
- If you skip Angular proxy and call opener endpoints directly, pass `authToken` to `installAngularLocator(...)` so requests are authorized.
|
|
151
|
+
- If token mismatch occurs (`401 Unauthorized`), regenerate config/proxy: `npx locatorjs-config`.
|
|
152
|
+
|
|
153
|
+
### Auto Scan Scope Discovery (0.5.0)
|
|
154
|
+
|
|
155
|
+
- `locatorjs-config` now auto-detects scan roots.
|
|
156
|
+
- Generated defaults now include `ts/tsx/js/jsx`.
|
|
157
|
+
- You can always edit the generated `scan.includeGlobs` / `scan.excludeGlobs` manually.
|
|
158
|
+
|
|
141
159
|
### Example includeGlobs
|
|
142
160
|
|
|
143
161
|
- Simple app: `"src/app/**/*.ts"`
|
|
144
|
-
- Angular workspace: `"projects/**/*.{ts,tsx}"`
|
|
145
|
-
- Nx: `"apps/**/*.{ts,tsx}", "libs/**/*.{ts,tsx}"`
|
|
162
|
+
- Angular workspace: `"projects/**/*.{ts,tsx,js,jsx}"`
|
|
163
|
+
- Nx: `"apps/**/*.{ts,tsx,js,jsx}", "libs/**/*.{ts,tsx,js,jsx}"`
|
|
146
164
|
|
|
147
165
|
## Proxy (`ngx-locatorjs.proxy.cjs`)
|
|
148
166
|
|
package/dist/node/cmp-scan.js
CHANGED
|
@@ -4,19 +4,19 @@ import fs from 'fs';
|
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import { glob } from 'glob';
|
|
6
6
|
const DEFAULT_INCLUDE_GLOBS = [
|
|
7
|
-
'src/**/*.{ts,tsx}',
|
|
8
|
-
'projects/**/*.{ts,tsx}',
|
|
9
|
-
'apps/**/*.{ts,tsx}',
|
|
10
|
-
'libs/**/*.{ts,tsx}',
|
|
7
|
+
'src/**/*.{ts,tsx,js,jsx}',
|
|
8
|
+
'projects/**/*.{ts,tsx,js,jsx}',
|
|
9
|
+
'apps/**/*.{ts,tsx,js,jsx}',
|
|
10
|
+
'libs/**/*.{ts,tsx,js,jsx}',
|
|
11
11
|
];
|
|
12
12
|
const DEFAULT_EXCLUDE_GLOBS = [
|
|
13
13
|
'**/node_modules/**',
|
|
14
14
|
'**/dist/**',
|
|
15
15
|
'**/.angular/**',
|
|
16
16
|
'**/coverage/**',
|
|
17
|
-
'**/*.spec.ts',
|
|
18
|
-
'**/*.test.ts',
|
|
19
|
-
'**/*.e2e.ts',
|
|
17
|
+
'**/*.spec.{ts,tsx,js,jsx}',
|
|
18
|
+
'**/*.test.{ts,tsx,js,jsx}',
|
|
19
|
+
'**/*.e2e.{ts,tsx,js,jsx}',
|
|
20
20
|
];
|
|
21
21
|
const root = process.cwd();
|
|
22
22
|
const CONFIG_FILENAME = 'ngx-locatorjs.config.json';
|
|
@@ -56,16 +56,20 @@ function extractTemplateUrl(node) {
|
|
|
56
56
|
}
|
|
57
57
|
return undefined;
|
|
58
58
|
}
|
|
59
|
+
function getDecorators(node) {
|
|
60
|
+
const modernTs = ts;
|
|
61
|
+
if (modernTs.canHaveDecorators?.(node) && modernTs.getDecorators) {
|
|
62
|
+
return modernTs.getDecorators(node) ?? [];
|
|
63
|
+
}
|
|
64
|
+
const legacyNode = node;
|
|
65
|
+
return legacyNode.decorators ?? legacyNode.modifiers?.filter(ts.isDecorator) ?? [];
|
|
66
|
+
}
|
|
59
67
|
function findComponentDecorator(node) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (ts.isCallExpression(expr) && ts.isIdentifier(expr.expression)) {
|
|
66
|
-
if (expr.expression.text === 'Component') {
|
|
67
|
-
return modifier;
|
|
68
|
-
}
|
|
68
|
+
for (const decorator of getDecorators(node)) {
|
|
69
|
+
const expr = decorator.expression;
|
|
70
|
+
if (ts.isCallExpression(expr) && ts.isIdentifier(expr.expression)) {
|
|
71
|
+
if (expr.expression.text === 'Component') {
|
|
72
|
+
return decorator;
|
|
69
73
|
}
|
|
70
74
|
}
|
|
71
75
|
}
|
|
@@ -10,6 +10,20 @@ const __dirname = path.dirname(__filename);
|
|
|
10
10
|
const root = process.cwd();
|
|
11
11
|
const CONFIG_FILENAME = 'ngx-locatorjs.config.json';
|
|
12
12
|
const PROXY_FILENAME = 'ngx-locatorjs.proxy.cjs';
|
|
13
|
+
const ANSI_ENABLED = Boolean(process.stdout.isTTY);
|
|
14
|
+
const IGNORED_SCAN_DIR_NAMES = new Set([
|
|
15
|
+
'node_modules',
|
|
16
|
+
'dist',
|
|
17
|
+
'coverage',
|
|
18
|
+
'.angular',
|
|
19
|
+
'.git',
|
|
20
|
+
'.nx',
|
|
21
|
+
'.turbo',
|
|
22
|
+
'.cache',
|
|
23
|
+
'build',
|
|
24
|
+
'out',
|
|
25
|
+
'tmp',
|
|
26
|
+
]);
|
|
13
27
|
const configPath = path.resolve(root, CONFIG_FILENAME);
|
|
14
28
|
const proxyConfigPath = resolveProxyConfigPath();
|
|
15
29
|
console.log('🚀 LocatorJs (Open-in-Editor) Configuration Setup\n');
|
|
@@ -287,28 +301,280 @@ function selectEditor() {
|
|
|
287
301
|
process.stdin.on('data', handleKeypress);
|
|
288
302
|
});
|
|
289
303
|
}
|
|
290
|
-
function promptScanSettings() {
|
|
304
|
+
async function promptScanSettings() {
|
|
291
305
|
const defaultInclude = [
|
|
292
|
-
'src/**/*.{ts,tsx}',
|
|
293
|
-
'projects/**/*.{ts,tsx}',
|
|
294
|
-
'apps/**/*.{ts,tsx}',
|
|
295
|
-
'libs/**/*.{ts,tsx}',
|
|
306
|
+
'src/**/*.{ts,tsx,js,jsx}',
|
|
307
|
+
'projects/**/*.{ts,tsx,js,jsx}',
|
|
308
|
+
'apps/**/*.{ts,tsx,js,jsx}',
|
|
309
|
+
'libs/**/*.{ts,tsx,js,jsx}',
|
|
296
310
|
];
|
|
297
311
|
const defaultExclude = [
|
|
298
312
|
'**/node_modules/**',
|
|
299
313
|
'**/dist/**',
|
|
300
314
|
'**/.angular/**',
|
|
301
315
|
'**/coverage/**',
|
|
302
|
-
'**/*.spec.ts',
|
|
303
|
-
'**/*.test.ts',
|
|
304
|
-
'**/*.e2e.ts',
|
|
316
|
+
'**/*.spec.{ts,tsx,js,jsx}',
|
|
317
|
+
'**/*.test.{ts,tsx,js,jsx}',
|
|
318
|
+
'**/*.e2e.{ts,tsx,js,jsx}',
|
|
305
319
|
];
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
320
|
+
const includeGlobs = detectIncludeGlobs(defaultInclude);
|
|
321
|
+
const excludeGlobs = detectExcludeGlobs(defaultExclude);
|
|
322
|
+
printScanSettingsSummary(includeGlobs, excludeGlobs);
|
|
323
|
+
return {
|
|
324
|
+
includeGlobs: includeGlobs.values,
|
|
325
|
+
excludeGlobs: excludeGlobs.values,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
function detectIncludeGlobs(defaultInclude) {
|
|
329
|
+
const sources = [];
|
|
330
|
+
let detectedBaseDirs = detectIncludeBaseDirsFromAngularJson();
|
|
331
|
+
if (detectedBaseDirs.size > 0) {
|
|
332
|
+
sources.push('angular.json');
|
|
333
|
+
const supplemental = detectSupplementalWorkspaceBaseDirs(detectedBaseDirs);
|
|
334
|
+
if (supplemental.size > 0) {
|
|
335
|
+
supplemental.forEach((dir) => detectedBaseDirs.add(dir));
|
|
336
|
+
sources.push('workspace directories (not declared in angular.json)');
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
detectedBaseDirs = detectIncludeBaseDirsFromConventionalLayout();
|
|
341
|
+
if (detectedBaseDirs.size > 0) {
|
|
342
|
+
sources.push('existing directories');
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
if (detectedBaseDirs.size > 0) {
|
|
346
|
+
return {
|
|
347
|
+
values: toGlobPatterns(detectedBaseDirs),
|
|
348
|
+
autoDetected: true,
|
|
349
|
+
sources,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
return {
|
|
353
|
+
values: defaultInclude,
|
|
354
|
+
autoDetected: false,
|
|
355
|
+
sources: [],
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
function detectExcludeGlobs(defaultExclude) {
|
|
359
|
+
const extraExcludeCandidates = [
|
|
360
|
+
{ pattern: '**/.nx/**', reason: 'nx.json', when: fileExists('nx.json') },
|
|
361
|
+
{ pattern: '**/.turbo/**', reason: 'turbo.json', when: fileExists('turbo.json') },
|
|
362
|
+
{ pattern: '**/.cache/**', reason: '.cache directory', when: directoryExists('.cache') },
|
|
363
|
+
{ pattern: '**/build/**', reason: 'build directory', when: directoryExists('build') },
|
|
364
|
+
{ pattern: '**/out/**', reason: 'out directory', when: directoryExists('out') },
|
|
365
|
+
{ pattern: '**/tmp/**', reason: 'tmp directory', when: directoryExists('tmp') },
|
|
366
|
+
];
|
|
367
|
+
const values = [...defaultExclude];
|
|
368
|
+
const reasons = [];
|
|
369
|
+
for (const candidate of extraExcludeCandidates) {
|
|
370
|
+
if (!candidate.when)
|
|
371
|
+
continue;
|
|
372
|
+
if (!values.includes(candidate.pattern)) {
|
|
373
|
+
values.push(candidate.pattern);
|
|
374
|
+
reasons.push(`${candidate.pattern} (${candidate.reason})`);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return {
|
|
378
|
+
values,
|
|
379
|
+
autoDetected: reasons.length > 0,
|
|
380
|
+
sources: reasons,
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
function detectIncludeBaseDirsFromAngularJson() {
|
|
384
|
+
const angularJsonPath = path.resolve(root, 'angular.json');
|
|
385
|
+
if (!fs.existsSync(angularJsonPath))
|
|
386
|
+
return new Set();
|
|
387
|
+
try {
|
|
388
|
+
const angularJson = JSON.parse(fs.readFileSync(angularJsonPath, 'utf8'));
|
|
389
|
+
const projects = angularJson?.projects ?? {};
|
|
390
|
+
const detectedBaseDirs = new Set();
|
|
391
|
+
for (const project of Object.values(projects)) {
|
|
392
|
+
const sourceRoot = normalizeRelativePath(project.sourceRoot);
|
|
393
|
+
const projectRoot = normalizeRelativePath(project.root);
|
|
394
|
+
if (sourceRoot && directoryExists(sourceRoot)) {
|
|
395
|
+
detectedBaseDirs.add(sourceRoot);
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
if (!projectRoot)
|
|
399
|
+
continue;
|
|
400
|
+
const srcUnderProjectRoot = normalizeRelativePath(path.posix.join(projectRoot, 'src'));
|
|
401
|
+
if (srcUnderProjectRoot && directoryExists(srcUnderProjectRoot)) {
|
|
402
|
+
detectedBaseDirs.add(srcUnderProjectRoot);
|
|
403
|
+
}
|
|
404
|
+
else if (directoryExists(projectRoot)) {
|
|
405
|
+
detectedBaseDirs.add(projectRoot);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
return detectedBaseDirs;
|
|
409
|
+
}
|
|
410
|
+
catch {
|
|
411
|
+
return new Set();
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
function detectIncludeBaseDirsFromConventionalLayout() {
|
|
415
|
+
const conventionalDirs = ['src', 'projects', 'apps', 'libs'];
|
|
416
|
+
const detectedBaseDirs = new Set();
|
|
417
|
+
for (const dir of conventionalDirs) {
|
|
418
|
+
if (directoryExists(dir)) {
|
|
419
|
+
detectedBaseDirs.add(dir);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return detectedBaseDirs;
|
|
423
|
+
}
|
|
424
|
+
function detectSupplementalWorkspaceBaseDirs(knownBaseDirs) {
|
|
425
|
+
const roots = ['projects', 'apps', 'libs'];
|
|
426
|
+
const supplemental = new Set();
|
|
427
|
+
for (const rootDir of roots) {
|
|
428
|
+
const rootPath = path.resolve(root, rootDir);
|
|
429
|
+
if (!directoryExists(rootDir))
|
|
430
|
+
continue;
|
|
431
|
+
let entries;
|
|
432
|
+
try {
|
|
433
|
+
entries = fs.readdirSync(rootPath, { withFileTypes: true });
|
|
434
|
+
}
|
|
435
|
+
catch {
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
for (const entry of entries) {
|
|
439
|
+
if (!entry.isDirectory())
|
|
440
|
+
continue;
|
|
441
|
+
const candidate = normalizeRelativePath(path.posix.join(rootDir, entry.name));
|
|
442
|
+
if (!candidate)
|
|
443
|
+
continue;
|
|
444
|
+
if (isCoveredByKnownBaseDirs(candidate, knownBaseDirs))
|
|
445
|
+
continue;
|
|
446
|
+
if (!directoryContainsScanCandidateFiles(candidate))
|
|
447
|
+
continue;
|
|
448
|
+
supplemental.add(candidate);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
return supplemental;
|
|
452
|
+
}
|
|
453
|
+
function isCoveredByKnownBaseDirs(candidate, knownBaseDirs) {
|
|
454
|
+
for (const known of knownBaseDirs) {
|
|
455
|
+
if (candidate === known)
|
|
456
|
+
return true;
|
|
457
|
+
if (candidate.startsWith(`${known}/`))
|
|
458
|
+
return true;
|
|
459
|
+
if (known.startsWith(`${candidate}/`))
|
|
460
|
+
return true;
|
|
461
|
+
}
|
|
462
|
+
return false;
|
|
463
|
+
}
|
|
464
|
+
function directoryContainsScanCandidateFiles(relPath, maxDepth = 6) {
|
|
465
|
+
const absolute = path.resolve(root, relPath);
|
|
466
|
+
return directoryContainsScanCandidateFilesRecursive(absolute, 0, maxDepth);
|
|
467
|
+
}
|
|
468
|
+
function directoryContainsScanCandidateFilesRecursive(absolutePath, currentDepth, maxDepth) {
|
|
469
|
+
let entries;
|
|
470
|
+
try {
|
|
471
|
+
entries = fs.readdirSync(absolutePath, { withFileTypes: true });
|
|
472
|
+
}
|
|
473
|
+
catch {
|
|
474
|
+
return false;
|
|
475
|
+
}
|
|
476
|
+
for (const entry of entries) {
|
|
477
|
+
if (entry.isFile() && /\.(ts|tsx|js|jsx)$/.test(entry.name)) {
|
|
478
|
+
return true;
|
|
479
|
+
}
|
|
480
|
+
if (!entry.isDirectory())
|
|
481
|
+
continue;
|
|
482
|
+
if (currentDepth >= maxDepth)
|
|
483
|
+
continue;
|
|
484
|
+
if (IGNORED_SCAN_DIR_NAMES.has(entry.name))
|
|
485
|
+
continue;
|
|
486
|
+
const childPath = path.join(absolutePath, entry.name);
|
|
487
|
+
if (directoryContainsScanCandidateFilesRecursive(childPath, currentDepth + 1, maxDepth)) {
|
|
488
|
+
return true;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
return false;
|
|
492
|
+
}
|
|
493
|
+
function toGlobPatterns(baseDirs) {
|
|
494
|
+
if (baseDirs.size === 0)
|
|
495
|
+
return [];
|
|
496
|
+
const preferredOrder = ['src', 'projects', 'apps', 'libs'];
|
|
497
|
+
return Array.from(baseDirs)
|
|
498
|
+
.sort((a, b) => {
|
|
499
|
+
const aIdx = preferredOrder.indexOf(a);
|
|
500
|
+
const bIdx = preferredOrder.indexOf(b);
|
|
501
|
+
const aRank = aIdx === -1 ? Number.MAX_SAFE_INTEGER : aIdx;
|
|
502
|
+
const bRank = bIdx === -1 ? Number.MAX_SAFE_INTEGER : bIdx;
|
|
503
|
+
if (aRank !== bRank)
|
|
504
|
+
return aRank - bRank;
|
|
505
|
+
return a.localeCompare(b);
|
|
506
|
+
})
|
|
507
|
+
.map((baseDir) => `${baseDir}/**/*.{ts,tsx,js,jsx}`);
|
|
508
|
+
}
|
|
509
|
+
function normalizeRelativePath(value) {
|
|
510
|
+
if (!value)
|
|
511
|
+
return null;
|
|
512
|
+
const normalized = value
|
|
513
|
+
.trim()
|
|
514
|
+
.replace(/\\/g, '/')
|
|
515
|
+
.replace(/^\.\/+/, '')
|
|
516
|
+
.replace(/^\/+/, '')
|
|
517
|
+
.replace(/\/+$/, '');
|
|
518
|
+
return normalized.length > 0 ? normalized : null;
|
|
519
|
+
}
|
|
520
|
+
function directoryExists(relPath) {
|
|
521
|
+
try {
|
|
522
|
+
const stat = fs.statSync(path.resolve(root, relPath));
|
|
523
|
+
return stat.isDirectory();
|
|
524
|
+
}
|
|
525
|
+
catch {
|
|
526
|
+
return false;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
function fileExists(relPath) {
|
|
530
|
+
try {
|
|
531
|
+
const stat = fs.statSync(path.resolve(root, relPath));
|
|
532
|
+
return stat.isFile();
|
|
533
|
+
}
|
|
534
|
+
catch {
|
|
535
|
+
return false;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
function colorize(text, code) {
|
|
539
|
+
if (!ANSI_ENABLED)
|
|
540
|
+
return text;
|
|
541
|
+
return `\x1b[${code}m${text}\x1b[0m`;
|
|
542
|
+
}
|
|
543
|
+
function bold(text) {
|
|
544
|
+
return colorize(text, '1');
|
|
545
|
+
}
|
|
546
|
+
function cyan(text) {
|
|
547
|
+
return colorize(text, '36');
|
|
548
|
+
}
|
|
549
|
+
function green(text) {
|
|
550
|
+
return colorize(text, '32');
|
|
551
|
+
}
|
|
552
|
+
function yellow(text) {
|
|
553
|
+
return colorize(text, '33');
|
|
554
|
+
}
|
|
555
|
+
function dim(text) {
|
|
556
|
+
return colorize(text, '2');
|
|
557
|
+
}
|
|
558
|
+
function printScanSettingsSummary(includeGlobs, excludeGlobs) {
|
|
559
|
+
const mode = includeGlobs.autoDetected ? 'AUTO-DETECTED' : 'DEFAULTS';
|
|
560
|
+
console.log(`\n${bold(cyan('Scan Settings Applied'))} ${dim(`[${mode}]`)}`);
|
|
561
|
+
console.log(dim('────────────────────────────────────────────'));
|
|
562
|
+
console.log(`${bold(green('Include globs'))}:`);
|
|
563
|
+
includeGlobs.values.forEach((value) => {
|
|
564
|
+
console.log(` ${green('•')} ${value}`);
|
|
313
565
|
});
|
|
566
|
+
console.log(`\n${bold(yellow('Exclude globs'))}:`);
|
|
567
|
+
excludeGlobs.values.forEach((value) => {
|
|
568
|
+
console.log(` ${yellow('•')} ${value}`);
|
|
569
|
+
});
|
|
570
|
+
if (includeGlobs.autoDetected) {
|
|
571
|
+
console.log(`\n${bold('Detected from')}: ${includeGlobs.sources.join(', ')}`);
|
|
572
|
+
}
|
|
573
|
+
if (excludeGlobs.autoDetected) {
|
|
574
|
+
console.log(`${bold('Auto-added excludes')}:`);
|
|
575
|
+
excludeGlobs.sources.forEach((value) => {
|
|
576
|
+
console.log(` ${yellow('•')} ${value}`);
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
console.log(`\n${dim(`Edit scan globs anytime in ${CONFIG_FILENAME}`)}`);
|
|
314
580
|
}
|
package/dist/node/file-opener.js
CHANGED
|
@@ -21,10 +21,10 @@ function isErrnoException(error) {
|
|
|
21
21
|
return typeof error === 'object' && error !== null && 'code' in error;
|
|
22
22
|
}
|
|
23
23
|
const DEFAULT_INCLUDE_GLOBS = [
|
|
24
|
-
'src/**/*.{ts,tsx}',
|
|
25
|
-
'projects/**/*.{ts,tsx}',
|
|
26
|
-
'apps/**/*.{ts,tsx}',
|
|
27
|
-
'libs/**/*.{ts,tsx}',
|
|
24
|
+
'src/**/*.{ts,tsx,js,jsx}',
|
|
25
|
+
'projects/**/*.{ts,tsx,js,jsx}',
|
|
26
|
+
'apps/**/*.{ts,tsx,js,jsx}',
|
|
27
|
+
'libs/**/*.{ts,tsx,js,jsx}',
|
|
28
28
|
];
|
|
29
29
|
const cfg = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
30
30
|
const WATCH_ENABLED = process.argv.includes('--watch') || process.argv.includes('-w');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ngx-locatorjs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "LocatorJs open-in-editor tools for Angular projects",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"angular",
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
"prepare": "npm run build",
|
|
52
52
|
"test": "node --test",
|
|
53
53
|
"test:ci": "npm run build && npm test",
|
|
54
|
+
"test:angular:e2e": "node scripts/angular-e2e-matrix.mjs",
|
|
54
55
|
"format": "oxfmt",
|
|
55
56
|
"format:check": "oxfmt --check",
|
|
56
57
|
"publish:github": "node scripts/publish-github.mjs"
|
|
@@ -61,6 +62,7 @@
|
|
|
61
62
|
"devDependencies": {
|
|
62
63
|
"@types/node": "^18.18.0",
|
|
63
64
|
"oxfmt": "^0.35.0",
|
|
65
|
+
"playwright": "^1.61.0",
|
|
64
66
|
"typescript": "^5.5.4"
|
|
65
67
|
}
|
|
66
68
|
}
|