@notis_ai/cli 0.2.0-beta.57.1 → 0.2.0-beta.92.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.
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"javascript": [
|
|
3
|
+
{
|
|
4
|
+
"pattern": "window\\.__NOTIS_RUNTIME__",
|
|
5
|
+
"message": "window.__NOTIS_RUNTIME__ is forbidden; rely on the portal-injected NotisProvider runtime."
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
"pattern": "document\\.(?:body|head|documentElement)\\b",
|
|
9
|
+
"message": "document.body, document.head, and document.documentElement are forbidden; apps must stay inside the app surface."
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"pattern": "document\\.(?:querySelector|querySelectorAll|getElementById|getElementsByClassName|getElementsByTagName)\\s*\\(\\s*(['\"`])[^\\n]*\\[data-notis-",
|
|
13
|
+
"message": "querying portal-owned [data-notis-*] hooks is forbidden."
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"pattern": "createPortal\\s*\\(",
|
|
17
|
+
"message": "React portals are unsupported in Notis apps until a shadow-safe overlay contract exists."
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"pattern": "@radix-ui/react-portal",
|
|
21
|
+
"message": "@radix-ui/react-portal is unsupported in Notis apps until a shadow-safe overlay contract exists."
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"pattern": "PortalPrimitive",
|
|
25
|
+
"message": "Radix portal primitives are unsupported in Notis apps until a shadow-safe overlay contract exists."
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
"css": [
|
|
29
|
+
{
|
|
30
|
+
"pattern": ":root\\b",
|
|
31
|
+
"message": ":root selectors are forbidden; styles must stay inside the app surface."
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"pattern": "(^|[\\s,>+~])html(?=[\\s.#[:{>+~,]|$)",
|
|
35
|
+
"message": "html selectors are forbidden; styles must stay inside the app surface."
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"pattern": "(^|[\\s,>+~])body(?=[\\s.#[:{>+~,]|$)",
|
|
39
|
+
"message": "body selectors are forbidden; styles must stay inside the app surface."
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"pattern": "\\[data-notis-(?!app-root\\b)[^\\]]*\\]",
|
|
43
|
+
"message": "portal-owned [data-notis-*] selectors are forbidden. Use [data-notis-app-root] only for the app surface itself."
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"pattern": "\\[data-sidebar(?:=[^\\]]+)?\\]",
|
|
47
|
+
"message": "portal sidebar selectors are forbidden."
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@notis_ai/cli",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.92.1",
|
|
4
4
|
"description": "Agent-first Notis CLI for apps and generic tool execution",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"bin/",
|
|
11
|
+
"config/",
|
|
11
12
|
"dist/",
|
|
12
13
|
"src/",
|
|
13
14
|
"skills/",
|
|
@@ -21,6 +22,7 @@
|
|
|
21
22
|
"prepack": "npm run build",
|
|
22
23
|
"release:prepare": "npm run build && node ./scripts/prepare-publish.js --apply",
|
|
23
24
|
"cli:set-mode": "node ./scripts/set-cli-mode.js",
|
|
25
|
+
"smoke": "node ./scripts/smoke-package.js",
|
|
24
26
|
"test": "node --test"
|
|
25
27
|
},
|
|
26
28
|
"engines": {
|
package/src/cli.js
CHANGED
|
@@ -1,10 +1,29 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
1
4
|
import { Command } from 'commander';
|
|
2
5
|
import { COMMAND_SPECS, GROUP_SUMMARIES } from './command-specs/index.js';
|
|
3
6
|
import { OutputManager } from './runtime/output.js';
|
|
4
7
|
import { asCliError } from './runtime/errors.js';
|
|
5
8
|
import { resolveRuntimeProfile, workspacePath } from './runtime/profiles.js';
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
// Read the version from package.json: the publish pipeline bumps the manifest
|
|
11
|
+
// (scripts/release-utils.js applyVersion), so a hardcoded string here goes
|
|
12
|
+
// stale on every release.
|
|
13
|
+
function readCliVersion() {
|
|
14
|
+
try {
|
|
15
|
+
const manifestPath = join(dirname(fileURLToPath(import.meta.url)), '..', 'package.json');
|
|
16
|
+
const version = JSON.parse(readFileSync(manifestPath, 'utf-8')).version;
|
|
17
|
+
if (typeof version === 'string' && version.trim()) {
|
|
18
|
+
return version.trim();
|
|
19
|
+
}
|
|
20
|
+
} catch {
|
|
21
|
+
// Fall through to the placeholder below.
|
|
22
|
+
}
|
|
23
|
+
return '0.0.0';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const CLI_VERSION = readCliVersion();
|
|
8
27
|
|
|
9
28
|
function buildHelpFooter(spec) {
|
|
10
29
|
const lines = [
|
|
@@ -4,10 +4,30 @@ import { fileURLToPath } from 'node:url';
|
|
|
4
4
|
|
|
5
5
|
import { usageError } from './errors.js';
|
|
6
6
|
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
)
|
|
7
|
+
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
|
|
9
|
+
// Candidate locations for the app boundary rules, in priority order:
|
|
10
|
+
// 1. The monorepo server source (always fresh during local development).
|
|
11
|
+
// 2. The copy bundled into the published package at build time (see
|
|
12
|
+
// scripts/copy-boundary-rules.js + package.json "files": config/). When the
|
|
13
|
+
// CLI is installed via npm, candidate 1 escapes the package and is absent,
|
|
14
|
+
// so the bundled copy is used. This is what was missing before: the package
|
|
15
|
+
// shipped only the escaping path and crashed with ENOENT on any command.
|
|
16
|
+
export const RULES_PATH_CANDIDATES = [
|
|
17
|
+
resolve(moduleDir, '../../../../server/config/notis_app_boundary_rules.json'),
|
|
18
|
+
resolve(moduleDir, '../../config/notis_app_boundary_rules.json'),
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
export function resolveRulesPath() {
|
|
22
|
+
for (const candidate of RULES_PATH_CANDIDATES) {
|
|
23
|
+
if (existsSync(candidate)) {
|
|
24
|
+
return candidate;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
// Fall back to the bundled-copy path so any error message points inside the
|
|
28
|
+
// package rather than at an escaping monorepo path.
|
|
29
|
+
return RULES_PATH_CANDIDATES[RULES_PATH_CANDIDATES.length - 1];
|
|
30
|
+
}
|
|
11
31
|
|
|
12
32
|
const SOURCE_FILE_EXTENSIONS = new Set(['.js', '.jsx', '.ts', '.tsx', '.css', '.scss', '.sass', '.pcss']);
|
|
13
33
|
const IGNORED_SOURCE_DIRS = new Set([
|
|
@@ -20,20 +40,50 @@ const IGNORED_SOURCE_DIRS = new Set([
|
|
|
20
40
|
'coverage',
|
|
21
41
|
]);
|
|
22
42
|
|
|
23
|
-
function loadBoundaryRules() {
|
|
24
|
-
return JSON.parse(readFileSync(RULES_PATH, 'utf-8'));
|
|
25
|
-
}
|
|
26
|
-
|
|
27
43
|
function compileRules(entries) {
|
|
28
|
-
|
|
44
|
+
// Tolerate a rules object whose javascript/css field is a non-array (a corrupt
|
|
45
|
+
// file can parse to {"javascript":"x"}); coerce anything non-array to [] so
|
|
46
|
+
// getCompiledRules degrades to an empty rule set instead of throwing on .map.
|
|
47
|
+
return (Array.isArray(entries) ? entries : []).map((entry) => ({
|
|
29
48
|
regex: new RegExp(entry.pattern, 'm'),
|
|
30
49
|
message: entry.message,
|
|
31
50
|
}));
|
|
32
51
|
}
|
|
33
52
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
53
|
+
// Loaded lazily on first validation rather than at module import time: a
|
|
54
|
+
// missing rules file must not prevent the whole CLI from booting (every
|
|
55
|
+
// command imports this module transitively). The server re-validates on
|
|
56
|
+
// save/deploy, so an empty client-side rule set degrades gracefully.
|
|
57
|
+
let compiledRules = null;
|
|
58
|
+
|
|
59
|
+
function getCompiledRules() {
|
|
60
|
+
if (compiledRules) {
|
|
61
|
+
return compiledRules;
|
|
62
|
+
}
|
|
63
|
+
let boundaryRules = { javascript: [], css: [] };
|
|
64
|
+
try {
|
|
65
|
+
const parsed = JSON.parse(readFileSync(resolveRulesPath(), 'utf-8'));
|
|
66
|
+
// Keep the safe default unless the file is a real rules object: a valid but
|
|
67
|
+
// non-object payload (null, a number, an array) would otherwise throw on the
|
|
68
|
+
// `.javascript`/`.css` deref below, reintroducing the boot crash this guards.
|
|
69
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
70
|
+
boundaryRules = parsed;
|
|
71
|
+
} else {
|
|
72
|
+
process.stderr.write(
|
|
73
|
+
'Warning: Notis app boundary rules are not a rules object; skipping local boundary checks.\n',
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
} catch (error) {
|
|
77
|
+
process.stderr.write(
|
|
78
|
+
`Warning: could not load Notis app boundary rules (${error.message}); skipping local boundary checks.\n`,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
compiledRules = {
|
|
82
|
+
javascript: compileRules(boundaryRules.javascript),
|
|
83
|
+
css: compileRules(boundaryRules.css),
|
|
84
|
+
};
|
|
85
|
+
return compiledRules;
|
|
86
|
+
}
|
|
37
87
|
|
|
38
88
|
function collectProjectFiles(projectDir, dir, results) {
|
|
39
89
|
if (!existsSync(dir)) {
|
|
@@ -81,11 +131,12 @@ function applyRules(content, rules, relPath) {
|
|
|
81
131
|
|
|
82
132
|
function validateTextFile(relPath, content) {
|
|
83
133
|
const extension = extname(relPath);
|
|
134
|
+
const rules = getCompiledRules();
|
|
84
135
|
if (extension === '.css' || extension === '.scss' || extension === '.sass' || extension === '.pcss') {
|
|
85
|
-
return applyRules(content,
|
|
136
|
+
return applyRules(content, rules.css, relPath);
|
|
86
137
|
}
|
|
87
138
|
if (extension === '.js' || extension === '.jsx' || extension === '.ts' || extension === '.tsx') {
|
|
88
|
-
return applyRules(content,
|
|
139
|
+
return applyRules(content, rules.javascript, relPath);
|
|
89
140
|
}
|
|
90
141
|
return [];
|
|
91
142
|
}
|