@homeflare/alchemy 0.37.8 → 0.39.0
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.md +22 -0
- package/dist/caddy/admin.d.ts +42 -8
- package/dist/caddy/admin.d.ts.map +1 -1
- package/dist/caddy/config.d.ts +9 -3
- package/dist/caddy/config.d.ts.map +1 -1
- package/dist/caddy/fake-caddy.d.ts +5 -0
- package/dist/caddy/fake-caddy.d.ts.map +1 -1
- package/dist/caddy/index.js +10 -6
- package/dist/caddy/index.js.map +5 -5
- package/dist/caddy/providers.d.ts +19 -12
- package/dist/caddy/providers.d.ts.map +1 -1
- package/dist/forgejo/index.js +4 -2
- package/dist/forgejo/index.js.map +3 -3
- package/dist/forgejo/team-member.d.ts +25 -0
- package/dist/forgejo/team-member.d.ts.map +1 -1
- package/dist/proxmox/generated/constraints/index.d.ts +1 -1
- package/dist/proxmox/generated/constraints/index.d.ts.map +1 -1
- package/dist/proxmox/generated/constraints/pve-access.d.ts +1 -1
- package/dist/proxmox/generated/constraints/pve-cluster-backup.d.ts +1 -1
- package/dist/proxmox/generated/constraints/pve-cluster-ceph.d.ts +1 -1
- package/dist/proxmox/generated/constraints/pve-cluster-firewall.d.ts +1 -1
- package/dist/proxmox/generated/constraints/pve-cluster-ha.d.ts +1 -1
- package/dist/proxmox/generated/constraints/pve-cluster-metrics.d.ts +1 -1
- package/dist/proxmox/generated/constraints/pve-cluster-notifications.d.ts +1 -1
- package/dist/proxmox/generated/constraints/pve-cluster-replication.d.ts +1 -1
- package/dist/proxmox/generated/constraints/pve-cluster-sdn.d.ts +1 -1
- package/dist/proxmox/generated/constraints/pve-nodes-ceph.d.ts +1 -1
- package/dist/proxmox/generated/constraints/pve-nodes-disks.d.ts +1 -1
- package/dist/proxmox/generated/constraints/pve-nodes-lxc.d.ts +1 -1
- package/dist/proxmox/generated/constraints/pve-nodes-network.d.ts +1 -1
- package/dist/proxmox/generated/constraints/pve-nodes-qemu.d.ts +1 -1
- package/dist/proxmox/generated/constraints/pve-nodes-storage.d.ts +23 -0
- package/dist/proxmox/generated/constraints/pve-nodes-storage.d.ts.map +1 -0
- package/dist/proxmox/generated/constraints/pve-pools.d.ts +1 -1
- package/dist/proxmox/generated/constraints/pve-storage.d.ts +1 -1
- package/dist/proxmox/index.d.ts +14 -3
- package/dist/proxmox/index.d.ts.map +1 -1
- package/dist/proxmox/index.js +565 -133
- package/dist/proxmox/index.js.map +33 -23
- package/dist/proxmox/qemu-form.d.ts +27 -18
- package/dist/proxmox/qemu-form.d.ts.map +1 -1
- package/dist/proxmox/qemu-judge.d.ts +30 -0
- package/dist/proxmox/qemu-judge.d.ts.map +1 -0
- package/dist/proxmox/qemu-lifecycle.d.ts +16 -2
- package/dist/proxmox/qemu-lifecycle.d.ts.map +1 -1
- package/dist/proxmox/qemu-net.d.ts +9 -0
- package/dist/proxmox/qemu-net.d.ts.map +1 -0
- package/dist/proxmox/qemu-props.d.ts +112 -0
- package/dist/proxmox/qemu-props.d.ts.map +1 -0
- package/dist/proxmox/qemu-volume.d.ts +17 -0
- package/dist/proxmox/qemu-volume.d.ts.map +1 -0
- package/dist/proxmox/qemu.d.ts +14 -27
- package/dist/proxmox/qemu.d.ts.map +1 -1
- package/dist/proxmox/storage-download-errors.d.ts +5 -0
- package/dist/proxmox/storage-download-errors.d.ts.map +1 -0
- package/dist/proxmox/storage-download-identity.d.ts +27 -0
- package/dist/proxmox/storage-download-identity.d.ts.map +1 -0
- package/dist/proxmox/storage-download-lifecycle.d.ts +97 -0
- package/dist/proxmox/storage-download-lifecycle.d.ts.map +1 -0
- package/dist/proxmox/storage-download-props.d.ts +36 -0
- package/dist/proxmox/storage-download-props.d.ts.map +1 -0
- package/dist/proxmox/storage-download-task.d.ts +17 -0
- package/dist/proxmox/storage-download-task.d.ts.map +1 -0
- package/dist/proxmox/storage-download.d.ts +44 -0
- package/dist/proxmox/storage-download.d.ts.map +1 -0
- package/dist/telemetry/fake-collector.d.ts +36 -0
- package/dist/telemetry/fake-collector.d.ts.map +1 -0
- package/dist/telemetry/index.d.ts +11 -0
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/index.js +223 -0
- package/dist/telemetry/index.js.map +12 -0
- package/dist/telemetry/layer.d.ts +86 -0
- package/dist/telemetry/layer.d.ts.map +1 -0
- package/dist/telemetry/redact-serialization.d.ts +21 -0
- package/dist/telemetry/redact-serialization.d.ts.map +1 -0
- package/dist/telemetry/redact.d.ts +46 -0
- package/dist/telemetry/redact.d.ts.map +1 -0
- package/dist/unifi/drift.d.ts +27 -0
- package/dist/unifi/drift.d.ts.map +1 -0
- package/dist/unifi/fake-unifi.d.ts +5 -1
- package/dist/unifi/fake-unifi.d.ts.map +1 -1
- package/dist/unifi/firewall-zone-form.d.ts +6 -0
- package/dist/unifi/firewall-zone-form.d.ts.map +1 -1
- package/dist/unifi/firewall-zone.d.ts +1 -1
- package/dist/unifi/firewall-zone.d.ts.map +1 -1
- package/dist/unifi/index.d.ts +4 -3
- package/dist/unifi/index.d.ts.map +1 -1
- package/dist/unifi/index.js +138 -58
- package/dist/unifi/index.js.map +10 -8
- package/dist/unifi/network-drift.d.ts +26 -0
- package/dist/unifi/network-drift.d.ts.map +1 -0
- package/dist/unifi/network-form.d.ts +40 -7
- package/dist/unifi/network-form.d.ts.map +1 -1
- package/dist/unifi/network.d.ts +1 -0
- package/dist/unifi/network.d.ts.map +1 -1
- package/dist/unifi/paginate.d.ts +36 -0
- package/dist/unifi/paginate.d.ts.map +1 -0
- package/dist/unifi/resource.d.ts +36 -1
- package/dist/unifi/resource.d.ts.map +1 -1
- package/docs/telemetry-spike.md +94 -0
- package/docs/telemetry.md +109 -0
- package/docs/unifi.md +56 -0
- package/package.json +6 -2
- package/src/caddy/admin-transport-scope.test.ts +127 -0
- package/src/caddy/admin.ts +45 -9
- package/src/caddy/config-adopt-provider.test.ts +2 -2
- package/src/caddy/config-format-warning.test.ts +9 -3
- package/src/caddy/config.ts +20 -4
- package/src/caddy/fake-caddy.ts +10 -1
- package/src/caddy/providers.test.ts +3 -2
- package/src/caddy/providers.ts +18 -11
- package/src/forgejo/team-member.test.ts +14 -0
- package/src/forgejo/team-member.ts +28 -1
- package/src/proxmox/constraints-required.test.ts +1 -0
- package/src/proxmox/generated/constraints/index.ts +3 -1
- package/src/proxmox/generated/constraints/pve-access.ts +1 -1
- package/src/proxmox/generated/constraints/pve-cluster-backup.ts +1 -1
- package/src/proxmox/generated/constraints/pve-cluster-ceph.ts +1 -1
- package/src/proxmox/generated/constraints/pve-cluster-firewall.ts +1 -1
- package/src/proxmox/generated/constraints/pve-cluster-ha.ts +1 -1
- package/src/proxmox/generated/constraints/pve-cluster-metrics.ts +1 -1
- package/src/proxmox/generated/constraints/pve-cluster-notifications.ts +1 -1
- package/src/proxmox/generated/constraints/pve-cluster-replication.ts +1 -1
- package/src/proxmox/generated/constraints/pve-cluster-sdn.ts +1 -1
- package/src/proxmox/generated/constraints/pve-nodes-ceph.ts +1 -1
- package/src/proxmox/generated/constraints/pve-nodes-disks.ts +1 -1
- package/src/proxmox/generated/constraints/pve-nodes-lxc.ts +1 -1
- package/src/proxmox/generated/constraints/pve-nodes-network.ts +1 -1
- package/src/proxmox/generated/constraints/pve-nodes-qemu.ts +1 -1
- package/src/proxmox/generated/constraints/pve-nodes-storage.ts +30 -0
- package/src/proxmox/generated/constraints/pve-pools.ts +1 -1
- package/src/proxmox/generated/constraints/pve-storage.ts +1 -1
- package/src/proxmox/index.test.ts +11 -6
- package/src/proxmox/index.ts +19 -3
- package/src/proxmox/qemu-form.ts +49 -13
- package/src/proxmox/qemu-identity.test.ts +30 -0
- package/src/proxmox/qemu-judge.test.ts +65 -0
- package/src/proxmox/qemu-judge.ts +84 -0
- package/src/proxmox/qemu-lifecycle.test.ts +78 -19
- package/src/proxmox/qemu-lifecycle.ts +131 -32
- package/src/proxmox/qemu-net.test.ts +48 -0
- package/src/proxmox/qemu-net.ts +78 -0
- package/src/proxmox/qemu-ownership.test.ts +44 -0
- package/src/proxmox/qemu-props.test.ts +87 -0
- package/src/proxmox/qemu-props.ts +157 -0
- package/src/proxmox/qemu-volume.test.ts +80 -0
- package/src/proxmox/qemu-volume.ts +121 -0
- package/src/proxmox/qemu.ts +15 -29
- package/src/proxmox/storage-download-errors.ts +7 -0
- package/src/proxmox/storage-download-identity.test.ts +54 -0
- package/src/proxmox/storage-download-identity.ts +50 -0
- package/src/proxmox/storage-download-lifecycle.ts +226 -0
- package/src/proxmox/storage-download-ownership.test.ts +50 -0
- package/src/proxmox/storage-download-props.ts +38 -0
- package/src/proxmox/storage-download-task.ts +72 -0
- package/src/proxmox/storage-download.test.ts +216 -0
- package/src/proxmox/storage-download.ts +58 -0
- package/src/telemetry/fake-collector.ts +59 -0
- package/src/telemetry/http-client-sealing.test.ts +82 -0
- package/src/telemetry/index.ts +10 -0
- package/src/telemetry/layer.test.ts +204 -0
- package/src/telemetry/layer.ts +152 -0
- package/src/telemetry/redact-serialization.ts +178 -0
- package/src/telemetry/redact.test.ts +124 -0
- package/src/telemetry/redact.ts +147 -0
- package/src/unifi/drift.test.ts +78 -0
- package/src/unifi/drift.ts +56 -0
- package/src/unifi/errors-and-secrets.test.ts +127 -0
- package/src/unifi/fake-unifi.ts +5 -1
- package/src/unifi/firewall-zone-form.ts +31 -2
- package/src/unifi/firewall-zone.test.ts +21 -1
- package/src/unifi/firewall-zone.ts +1 -1
- package/src/unifi/get-only-guard.test.ts +165 -0
- package/src/unifi/index.ts +15 -1
- package/src/unifi/network-drift.test.ts +207 -0
- package/src/unifi/network-drift.ts +80 -0
- package/src/unifi/network-form.ts +11 -39
- package/src/unifi/network.test.ts +7 -82
- package/src/unifi/network.ts +6 -6
- package/src/unifi/paginate.test.ts +228 -0
- package/src/unifi/paginate.ts +129 -0
- package/src/unifi/resource.ts +73 -3
- package/src/unifi/write-op-reference.test.ts +236 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* B0a / T12 static guard: no file under `src/unifi` (recursively) may reference an SDK write
|
|
3
|
+
* operation — a check that travels with the DIRECTORY, not with today's two-family knowledge of
|
|
4
|
+
* what those operations are named. `resource.ts`'s engine and `policy.ts`'s refusal never call one
|
|
5
|
+
* today; nothing in the TYPE SYSTEM stops a future family file from importing, say,
|
|
6
|
+
* `unifi_devices.adoptDevice` and calling it directly, bypassing `unifiOperations` altogether.
|
|
7
|
+
* `GetOnlyHttpClient` (`resource.ts`) is the runtime backstop if one ever did; this is the static
|
|
8
|
+
* one, so the mistake is caught at test time instead of by a wire-level defect.
|
|
9
|
+
*
|
|
10
|
+
* ⚠️ REWRITTEN (2026-09-26, red team, IMPORTANT-2). The previous version parsed import statements
|
|
11
|
+
* structurally (namespace alias + `.op`, or a named import's LOCAL name) and missed: the SDK's
|
|
12
|
+
* own documented bare-specifier root import, an `as`-aliased named import (checked the wrong
|
|
13
|
+
* side), destructuring, bracket access, re-export, and dynamic `import()` — five different
|
|
14
|
+
* syntax shapes a write op reference could hide behind, all invisible to that parser. Trying to
|
|
15
|
+
* special-case every one of those forms is an unbounded list; the fix here is a different
|
|
16
|
+
* design, not a patch: harvest the SDK's REAL write-op export names straight from its own service
|
|
17
|
+
* modules (below), then fail any file that mentions the SDK's package specifier anywhere AND
|
|
18
|
+
* contains one of those exact names as a token anywhere in its code — an identifier, a bracket
|
|
19
|
+
* key, a destructured binding, a re-export, or a dynamic-import property access all show up as
|
|
20
|
+
* the same literal text, so one scan catches all of them without parsing which syntax form it is.
|
|
21
|
+
* ⚠️ COMMENTS ARE STRIPPED WITH A SINGLE LEFT-TO-RIGHT SCAN, NOT TWO INDEPENDENT REGEXES. An
|
|
22
|
+
* earlier "mask strings, then regex the comments" attempt at fixing this had its own bug: an
|
|
23
|
+
* apostrophe in ordinary prose ("doesn't", "it's") reads as an unterminated string open to any
|
|
24
|
+
* regex that does not already know it is inside a comment, and can swallow real code up to the
|
|
25
|
+
* next quote anywhere later in the file. `stripComments` below tracks ONE state (code / line
|
|
26
|
+
* comment / block comment / string) char by char left to right, so a comment can't be misread as
|
|
27
|
+
* a string and a string's contents (needed below — a write op hidden in a string token is still
|
|
28
|
+
* an offense) are never mistaken for a comment either. See its own test for the specific
|
|
29
|
+
* "glob-like string" bug (LOW-4) this replaces.
|
|
30
|
+
*/
|
|
31
|
+
import { describe, expect, test } from 'bun:test';
|
|
32
|
+
import { mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
33
|
+
import { dirname, join, resolve } from 'node:path';
|
|
34
|
+
import { fileURLToPath } from 'node:url';
|
|
35
|
+
|
|
36
|
+
const SELF = fileURLToPath(import.meta.url);
|
|
37
|
+
const DIR = dirname(SELF);
|
|
38
|
+
const SDK_MARKER = '@distilled.cloud/unifi-network';
|
|
39
|
+
const SDK_SERVICES_DIR = resolve(DIR, '../../../distilled-unifi-network/src/services');
|
|
40
|
+
const BANNED_VERBS = ['create', 'update', 'delete', 'patch', 'execute', 'remove', 'adopt'];
|
|
41
|
+
// A bare verb or a verb-led camelCase op name (`deleteFirewallZone`, `adoptDevice`) — never a
|
|
42
|
+
// longer English word that merely starts the same way (`removes`, `updated`, `deletion`).
|
|
43
|
+
const WRITE_OP_NAME = new RegExp(`^(?:${BANNED_VERBS.join('|')})(?:[A-Z][A-Za-z0-9]*)?$`);
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* One left-to-right scan tracking a single state (code / `//` / `/* *\/` / string) — see the file
|
|
47
|
+
* header for why two independent regex passes cannot do this correctly. Comments are blanked to
|
|
48
|
+
* spaces (newlines kept, so line numbers in a caller's own diagnostics would still line up); string
|
|
49
|
+
* and template literal CONTENTS are left untouched, because a write-op reference hidden inside one
|
|
50
|
+
* (`alias['deleteNetwork']`) is still an offense this scan needs to see.
|
|
51
|
+
*/
|
|
52
|
+
const stripComments = (src: string): string => {
|
|
53
|
+
let out = '';
|
|
54
|
+
for (let i = 0; i < src.length;) {
|
|
55
|
+
const two = src.slice(i, i + 2);
|
|
56
|
+
if (two === '//') {
|
|
57
|
+
const end = src.indexOf('\n', i);
|
|
58
|
+
const stop = end === -1 ? src.length : end;
|
|
59
|
+
out += ' '.repeat(stop - i);
|
|
60
|
+
i = stop;
|
|
61
|
+
} else if (two === '/*') {
|
|
62
|
+
const end = src.indexOf('*/', i + 2);
|
|
63
|
+
const stop = end === -1 ? src.length : end + 2;
|
|
64
|
+
out += src.slice(i, stop).replace(/[^\n]/g, ' ');
|
|
65
|
+
i = stop;
|
|
66
|
+
} else if (src[i] === '"' || src[i] === "'" || src[i] === '`') {
|
|
67
|
+
const quote = src[i];
|
|
68
|
+
let j = i + 1;
|
|
69
|
+
while (j < src.length && src[j] !== quote) j += src[j] === '\\' ? 2 : 1;
|
|
70
|
+
const stop = Math.min(j + 1, src.length);
|
|
71
|
+
out += src.slice(i, stop);
|
|
72
|
+
i = stop;
|
|
73
|
+
} else {
|
|
74
|
+
out += src[i];
|
|
75
|
+
i += 1;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return out;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/** Every real write-op export name the SDK has TODAY, read from its own service modules — not a
|
|
82
|
+
* hand-maintained list, so a renamed operation or a new service file is caught here for free
|
|
83
|
+
* instead of silently falling outside a list this test forgot to update. */
|
|
84
|
+
const sdkWriteOpNames = (): ReadonlySet<string> => {
|
|
85
|
+
const names = new Set<string>();
|
|
86
|
+
for (const file of readdirSync(SDK_SERVICES_DIR).filter((f) => f.endsWith('.ts'))) {
|
|
87
|
+
const src = stripComments(readFileSync(join(SDK_SERVICES_DIR, file), 'utf8'));
|
|
88
|
+
for (const m of src.matchAll(/export (?:const|function) ([A-Za-z_$][\w$]*)/g)) {
|
|
89
|
+
if (WRITE_OP_NAME.test(m[1] ?? '')) names.add(m[1] as string);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return names;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const REAL_WRITE_OP_NAMES = sdkWriteOpNames();
|
|
96
|
+
|
|
97
|
+
const walk = (dir: string): string[] =>
|
|
98
|
+
readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
|
|
99
|
+
const full = join(dir, entry.name);
|
|
100
|
+
return entry.isDirectory() ? walk(full) : entry.name.endsWith('.ts') ? [full] : [];
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
/** Recursive by construction (IMPORTANT-2): a future `src/unifi/<subdir>/*.ts` is walked too. */
|
|
104
|
+
const sourceFiles = () => walk(DIR).filter((path) => path !== SELF);
|
|
105
|
+
|
|
106
|
+
/** Every offense in one file: any real SDK write-op name appearing as a token anywhere in its
|
|
107
|
+
* comment-stripped text, GATED on the file mentioning the SDK's package specifier at all (a file
|
|
108
|
+
* that never imports it — by any syntax — cannot meaningfully reference one of its exports). */
|
|
109
|
+
const offensesIn = (path: string, rawSrc: string): string[] => {
|
|
110
|
+
if (!rawSrc.includes(SDK_MARKER)) return [];
|
|
111
|
+
const codeSrc = stripComments(rawSrc);
|
|
112
|
+
const offenses: string[] = [];
|
|
113
|
+
for (const name of REAL_WRITE_OP_NAMES) {
|
|
114
|
+
if (new RegExp(`\\b${name}\\b`).test(codeSrc)) {
|
|
115
|
+
offenses.push(`${path}: references SDK write op '${name}'`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return offenses;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
describe('src/unifi never references an SDK write operation (T12)', () => {
|
|
122
|
+
test('every source file is clean of every write op the SDK exports today', () => {
|
|
123
|
+
const offenders = sourceFiles().flatMap((path) => offensesIn(path, readFileSync(path, 'utf8')));
|
|
124
|
+
expect(offenders).toEqual([]);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test('the SDK write-op harvest still finds every op this sentinel names', () => {
|
|
128
|
+
// Sentinel, not exhaustive: if the harvest ever stops finding one of these, the SDK's export
|
|
129
|
+
// shape moved in a way this scan no longer understands -- fail HERE, not by silently scanning
|
|
130
|
+
// for nothing. See docs/unifi.md for the excluded families and firewall-policy ordering's PR.
|
|
131
|
+
const knownWriteOps = [
|
|
132
|
+
'createNetwork',
|
|
133
|
+
'updateNetwork',
|
|
134
|
+
'deleteNetwork',
|
|
135
|
+
'createFirewallZone',
|
|
136
|
+
'updateFirewallZone',
|
|
137
|
+
'deleteFirewallZone',
|
|
138
|
+
'createFirewallPolicy',
|
|
139
|
+
'patchFirewallPolicy',
|
|
140
|
+
'updateFirewallPolicyOrdering',
|
|
141
|
+
'adoptDevice',
|
|
142
|
+
'removeDevice',
|
|
143
|
+
'executeAdoptedDeviceAction',
|
|
144
|
+
];
|
|
145
|
+
for (const op of knownWriteOps) expect(REAL_WRITE_OP_NAMES.has(op)).toBe(true);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("the scan ignores Alchemy's own adopt() and the refusal vocabulary, unscoped", () => {
|
|
149
|
+
// A realistic slice of network.ts's/policy.ts's actual shape: Alchemy's `adopt()` (a different
|
|
150
|
+
// import specifier entirely) and the bare-word string literals `refuseWrite` sends — neither
|
|
151
|
+
// is a reference TO an SDK op, and neither should ever be flagged.
|
|
152
|
+
const safe = `
|
|
153
|
+
import { adopt } from 'alchemy/AdoptPolicy';
|
|
154
|
+
import * as networks from '${SDK_MARKER}/networks';
|
|
155
|
+
export type UnifiWriteAction = 'create' | 'update' | 'delete';
|
|
156
|
+
export const network = (id: string, props: NetworkProps) =>
|
|
157
|
+
UnifiNetwork(id, props).pipe(adopt(true));
|
|
158
|
+
export const read = (props: NetworkProps) => networks.getNetworkDetails(props);
|
|
159
|
+
`;
|
|
160
|
+
expect(offensesIn('safe.ts', safe)).toEqual([]);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test('comments quoting a write op name in prose, even with contractions, never trigger it', () => {
|
|
164
|
+
const documented = `
|
|
165
|
+
/**
|
|
166
|
+
* network.ts's own doc: never calls updateNetwork or createNetwork directly. It doesn't,
|
|
167
|
+
* and it's not going to -- an apostrophe here must not make the scanner treat this comment
|
|
168
|
+
* as an unterminated string and swallow the real code below (LOW-4).
|
|
169
|
+
*/
|
|
170
|
+
import * as networks from '${SDK_MARKER}/networks';
|
|
171
|
+
// deleteNetwork is refused by policy.ts, never called from here.
|
|
172
|
+
export const read = (props: unknown) => networks.getNetworkDetails(props);
|
|
173
|
+
`;
|
|
174
|
+
expect(offensesIn('documented.ts', documented)).toEqual([]);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test('a glob-like string does not make the comment stripper swallow the next real line (LOW-4)', () => {
|
|
178
|
+
const globTrap = `
|
|
179
|
+
import * as networks from '${SDK_MARKER}/networks';
|
|
180
|
+
const pattern = "src/*.ts";
|
|
181
|
+
export const bad = () => networks.deleteNetwork({});
|
|
182
|
+
`;
|
|
183
|
+
expect(offensesIn('glob-trap.ts', globTrap)).toEqual([
|
|
184
|
+
"glob-trap.ts: references SDK write op 'deleteNetwork'",
|
|
185
|
+
]);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test('the scan flags a real SDK write reference across every syntax form IMPORTANT-2 named', () => {
|
|
189
|
+
const cases: Record<string, string> = {
|
|
190
|
+
'bare-root-import.ts': `
|
|
191
|
+
import * as UnifiNetwork from '${SDK_MARKER}';
|
|
192
|
+
export const bad = () => UnifiNetwork.Services.networks.deleteNetwork({});
|
|
193
|
+
`,
|
|
194
|
+
'aliased-named-import.ts': `
|
|
195
|
+
import { deleteNetwork as readNetwork } from '${SDK_MARKER}/networks';
|
|
196
|
+
export const bad = () => readNetwork({});
|
|
197
|
+
`,
|
|
198
|
+
'destructured.ts': `
|
|
199
|
+
import * as networks from '${SDK_MARKER}/networks';
|
|
200
|
+
const { deleteNetwork } = networks;
|
|
201
|
+
export const bad = () => deleteNetwork({});
|
|
202
|
+
`,
|
|
203
|
+
'bracket-access.ts': `
|
|
204
|
+
import * as networks from '${SDK_MARKER}/networks';
|
|
205
|
+
export const bad = () => (networks as Record<string, unknown>)['deleteNetwork'];
|
|
206
|
+
`,
|
|
207
|
+
're-export.ts': `export { deleteNetwork } from '${SDK_MARKER}/networks';`,
|
|
208
|
+
'dynamic-import.ts': `
|
|
209
|
+
export const bad = async () =>
|
|
210
|
+
(await import('${SDK_MARKER}/networks')).deleteNetwork({});
|
|
211
|
+
`,
|
|
212
|
+
};
|
|
213
|
+
for (const [path, src] of Object.entries(cases)) {
|
|
214
|
+
expect(offensesIn(path, src)).toEqual([`${path}: references SDK write op 'deleteNetwork'`]);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test('the walk is recursive -- a future src/unifi/<subdir>/*.ts is not silently unscanned', () => {
|
|
219
|
+
const probeDir = join(DIR, '.write-op-reference-recursion-probe');
|
|
220
|
+
const probeFile = join(probeDir, 'nested.ts');
|
|
221
|
+
mkdirSync(probeDir, { recursive: true });
|
|
222
|
+
writeFileSync(
|
|
223
|
+
probeFile,
|
|
224
|
+
`import * as networks from '${SDK_MARKER}/networks';\n` +
|
|
225
|
+
'export const bad = () => networks.deleteNetwork({});\n',
|
|
226
|
+
);
|
|
227
|
+
try {
|
|
228
|
+
expect(sourceFiles()).toContain(probeFile);
|
|
229
|
+
expect(offensesIn(probeFile, readFileSync(probeFile, 'utf8'))).toEqual([
|
|
230
|
+
`${probeFile}: references SDK write op 'deleteNetwork'`,
|
|
231
|
+
]);
|
|
232
|
+
} finally {
|
|
233
|
+
rmSync(probeDir, { recursive: true, force: true });
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
});
|