@cardstack/boxel-cli 0.2.0-unstable.298 → 0.2.0-unstable.425
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/dist/index.js +156 -97
- package/package.json +5 -3
- package/src/build-program.ts +6 -0
- package/src/commands/lint.ts +285 -0
- package/src/commands/parse.ts +741 -0
- package/src/commands/realm/index.ts +4 -0
- package/src/commands/realm/publish.ts +291 -0
- package/src/commands/realm/unpublish.ts +150 -0
- package/src/commands/test.ts +728 -0
- package/src/lib/find-package-root.ts +34 -0
- package/src/lib/realm-relative-path.ts +46 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Walk up from `__dirname` until we find the `@cardstack/boxel-cli`
|
|
6
|
+
* package.json. The single-file esbuild bundle places `__dirname` at
|
|
7
|
+
* `boxel-cli/dist`; the ts-node fallback places it inside `src/...`.
|
|
8
|
+
* Anchoring to the package.json keeps every downstream path stable
|
|
9
|
+
* regardless of which entry mode is active.
|
|
10
|
+
*/
|
|
11
|
+
export function findBoxelCliRoot(startDir: string): string {
|
|
12
|
+
let dir = startDir;
|
|
13
|
+
for (;;) {
|
|
14
|
+
let candidate = join(dir, 'package.json');
|
|
15
|
+
if (existsSync(candidate)) {
|
|
16
|
+
try {
|
|
17
|
+
let parsed = JSON.parse(readFileSync(candidate, 'utf8'));
|
|
18
|
+
if (parsed?.name === '@cardstack/boxel-cli') {
|
|
19
|
+
return dir;
|
|
20
|
+
}
|
|
21
|
+
} catch {
|
|
22
|
+
// ignore unparseable package.json and keep walking
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
let parent = dirname(dir);
|
|
26
|
+
if (parent === dir) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
'Could not locate the @cardstack/boxel-cli package root walking up from ' +
|
|
29
|
+
startDir,
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
dir = parent;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validate that an agent- or user-supplied `path` is a safe
|
|
3
|
+
* realm-relative path. Returns an error message if rejected, or null
|
|
4
|
+
* if the path is acceptable.
|
|
5
|
+
*
|
|
6
|
+
* Rejects:
|
|
7
|
+
* - empty / whitespace-only paths
|
|
8
|
+
* - absolute URLs with a scheme (`http:`, `file:`, etc.)
|
|
9
|
+
* - paths starting with `/`
|
|
10
|
+
* - backslash characters (ambiguous across URL/path handling layers;
|
|
11
|
+
* e.g. `foo\..\bar` never splits on `/` so the `..` check below
|
|
12
|
+
* misses it)
|
|
13
|
+
* - percent-encoded traversal segments — decodes once and rejects any
|
|
14
|
+
* `..` segment after decoding, so `%2e%2e`, `%2E%2E`, `%2e.`, etc.
|
|
15
|
+
* all collapse to a `..` and fail
|
|
16
|
+
* - malformed percent-encoded escapes
|
|
17
|
+
*
|
|
18
|
+
* Mirrors `packages/software-factory/src/realm-relative-path.ts` so
|
|
19
|
+
* both the SDK orchestrator and the boxel-cli validators apply the
|
|
20
|
+
* same gate before a path reaches realm-server URL handling.
|
|
21
|
+
*/
|
|
22
|
+
export function validateRealmRelativePath(path: string): string | null {
|
|
23
|
+
if (path.trim() === '') {
|
|
24
|
+
return `Path must be a non-empty realm-relative file path.`;
|
|
25
|
+
}
|
|
26
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(path)) {
|
|
27
|
+
return `Path "${path}" must be realm-relative — absolute URLs (with a scheme) are not accepted.`;
|
|
28
|
+
}
|
|
29
|
+
if (path.startsWith('/')) {
|
|
30
|
+
return `Path "${path}" must be realm-relative — paths starting with "/" are not accepted.`;
|
|
31
|
+
}
|
|
32
|
+
if (path.includes('\\')) {
|
|
33
|
+
return `Path "${path}" must not contain backslash characters.`;
|
|
34
|
+
}
|
|
35
|
+
let decoded: string;
|
|
36
|
+
try {
|
|
37
|
+
decoded = decodeURIComponent(path);
|
|
38
|
+
} catch {
|
|
39
|
+
return `Path "${path}" contains an invalid percent-encoded escape.`;
|
|
40
|
+
}
|
|
41
|
+
let segments = decoded.split('/');
|
|
42
|
+
if (segments.some((seg) => seg === '..')) {
|
|
43
|
+
return `Path "${path}" must not contain ".." segments — the path must stay inside the target realm.`;
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|