diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8aeecfd..4037e64 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,6 +19,12 @@ jobs: node-version: 20 cache: npm + # Before `npm ci`, deliberately: the check needs no dependencies, and a leak + # should be reported in seconds rather than behind a five-minute build. It is + # also the one check whose failure cannot be fixed by retrying. + - name: Leak check + run: npm run check:leak + - name: Install dependencies run: npm ci diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..96c771f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,341 @@ +# Change Log + +All notable changes to the Simulink Data Explorer extension. Versions follow the +`vX.Y.Z` tags in this repository; each tag builds the `.vsix` attached to its +GitHub Release. + +Entries before this file existed were reconstructed from those release tags. + +## [1.24.3] — 2026-09-18 + +An array inside a cell in a binary .sldd + +## [1.24.2] — 2026-09-18 + +Read a string in a cell as a string in the binary dictionary + +## [1.24.1] — 2026-09-18 + +The search box works on read-only views (.slx, .mdl, .mat, .prj) + +## [1.24.0] — 2026-09-17 + +A filter condition that contains whitespace is now one condition + +## [1.23.0] — 2026-09-16 + +Schema parity gaps from the MATLAB Data Explorer app + +## [1.22.0] — 2026-09-16 + +Read each file once: a dictionary scanned once per version, a model's structure off a parse already held + +## [1.21.0] — 2026-09-16 + +Parse a file once per content change, not once per consumer + +## [1.20.1] — 2026-09-15 + +A Data Type link jumps to the definition, not to a same-named child row + +## [1.20.0] — 2026-09-15 + +Data Type links to its type definition + +## [1.19.1] — 2026-09-13 + +The same extension, with its rules where they can be reused + +## [1.19.0] — 2026-09-13 + +A folder's indexes read only what they need + +## [1.18.2] — 2026-09-11 + +Configurations refuses what it cannot hold + +## [1.18.1] — 2026-09-11 + +A paste selects every entry it added + +## [1.18.0] — 2026-09-11 + +Multi-select row actions + +## [1.17.1] — 2026-09-10 + +Say how many entries each section holds + +## [1.17.0] — 2026-09-10 + +Draw a link only where there is somewhere to go + +## [1.16.0] — 2026-09-10 + +The Usage column now follows a mask. A `Gain = g1` inside a masked subsystem + +## [1.15.2] — 2026-09-10 + +Keep the string class when a Simulink.Parameter Value is retyped + +## [1.15.1] — 2026-09-10 + +No user-visible change. The two file-name reductions (refModelExt, projectNameOf) + +## [1.15.0] — 2026-09-10 + +A shaped value inside a cell keeps its shape, a multi-row cell or string + +## [1.14.1] — 2026-09-09 + +A table edit writes only the bytes it changes and repaints only the entry it touched; the copied row keeps its ring across the frozen Name column + +## [1.13.1] — 2026-09-09 + +A rename carries the System Composer catalog; the cells the format cannot keep are refused + +## [1.13.0] — 2026-09-09 + +Let a table edit repaint the entry it just wrote + +## [1.12.1] — 2026-09-09 + +Keep the search bar still while the table is still loading + +## [1.12.0] — 2026-09-09 + +Freeze the Name column, and scroll the table sideways under it + +## [1.11.0] — 2026-09-09 + +Entry-scoped model updates and repaints + +## [1.10.4] — 2026-09-08 + +Entry-scoped table repaint for binary .sldd edits + +## [1.10.3] — 2026-09-08 + +Bounded workspace scans: opening a folder of large dictionaries no longer kills the extension host + +## [1.10.2] — 2026-09-08 + +A block name's qualifier now truncates before the name does. In a Name column + +## [1.10.1] — 2026-09-08 + +Two Usage-column fixes + +## [1.10.0] — 2026-09-08 + +Every block is searchable, and each one is somewhere + +## [1.9.2] — 2026-09-08 + +A block is its SID: nameless blocks render , same-named blocks stay separate + +## [1.9.1] — 2026-09-08 + +A shadowed dictionary entry no longer shows a Usage link + +## [1.9.0] — 2026-09-08 + +One answer for the Usage column + +## [1.8.3] — 2026-09-06 + +An object array reads as the class it is an array of + +## [1.8.2] — 2026-09-06 + +Objects carry the object icon, and the tree's icons ship in the VSIX + +## [1.8.1] — 2026-09-05 + +A file that opens short says so + +## [1.8.0] — 2026-09-04 + +.mdl model support + +## [1.7.0] — 2026-09-04 + +Variable Editor: open a matrix value as a floating grid with a (:,:,k) page selector + +## [1.6.5] — 2026-09-02 + +Pick up data-explorer-core 0.1.7: an array's element rows now carry the + +## [1.6.4] — 2026-09-02 + +Fix the table not tracking its panel width after a column resize. Column + +## [1.6.3] — 2026-09-02 + +Bump the data-explorer-core pin to v0.1.5 and fix the defects found + +## [1.6.2] — 2026-08-31 + +Maintenance release. + +## [1.6.1] — 2026-08-31 + +- Delete the src/dex tree; tests now import data-explorer-core only + +## [1.6.0] — 2026-08-31 + +Adopt data-explorer-core package + native webview UI + +## [1.5.10] — 2026-08-19 + +- Remove Marketplace auto-publish from release workflow + +## [1.5.9] — 2026-08-19 + +- Expand object arrays across all formats; fix nested-array truncation +- Fix singleFileUsage integration test for empty workspace source label + +## [1.5.8] — 2026-08-19 + +- Broaden block-param capture (issue #9) + Usage display cleanups; bump to 1.5.8 +- Resolve intra-model Usage for single-file (no-folder) opens + +## [1.5.7] — 2026-08-19 + +- Schema-driven PI layout: common 'General' group across all node types + +## [1.5.6] — 2026-08-14 + +- Publish to VS Code Marketplace on release +- Add Ctrl+F shortcut to focus the table search bar + +## [1.5.5] — 2026-08-14 + +Fix scroll-to-selected on large virtualized tables; center the selected row when scrolling it into view. + +## [1.5.4] — 2026-08-14 + +Expand custom MATLAB class objects; class property names read-only (issue #3) + +## [1.5.3] — 2026-08-14 + +Expand custom MATLAB class objects in the tree (issue #3) + +## [1.5.2] — 2026-08-14 + +- Fix Dependabot devDependency vulnerabilities + +## [1.5.1] — 2026-08-14 + +- Add systematic MATLAB fidelity docs, hardening, and round-trip tests +- Mirror MATLAB element property behavior for Element-level nodes +- Mirror MATLAB setPropValue constraints for editable value/codegen props + +## [1.5.0] — 2026-08-12 + +Constant node for Architectural Data + +## [1.4.1] — 2026-08-12 + +- Docs: describe .sldd as editable regardless of format; bump to 1.4.1 +- Move binary-sldd design spec and plan to local-only docs/deep-work + +## [1.4.0] — 2026-08-12 + +Editable compressed-binary .sldd + +## [1.3.1] — 2026-08-12 + +- Refactor: extract shared common/ modules, reduce duplication +- Remove internal deep-work doc from the public tree + +## [1.3.0] — 2026-08-11 + +- Cover structuralIndex .prj branch and error path +- Add integration test for the lazy-cut single-undo contract +- Add cut/paste end-to-end tests (lazy-cut composition) + +## [1.2.13] — 2026-08-11 + +- Fix CI: repoint arch-paste tests to a committed fixture +- Fix copy/paste + Kind for arch data; add keyboard shortcuts; bump to 1.2.13 + +## [1.2.12] — 2026-08-11 + +- Generate a fresh uuid when pasting an entry; bump to 1.2.12 + +## [1.2.11] — 2026-08-11 + +- Add Child on a ServiceInterface creates a FunctionElement; bump to 1.2.11 + +## [1.2.10] — 2026-08-11 + +- Fix Add Child/Remove Child correctness for text sldd; bump to 1.2.10 + +## [1.2.9] — 2026-08-10 + +- Drop the Jump-to-Reference Navigation section from README; bump to 1.2.9 + +## [1.2.8] — 2026-08-10 + +- Improve README for the Marketplace listing; bump to 1.2.8 + +## [1.2.7] — 2026-08-10 + +- Add Marketplace keywords and Data Science category; bump to 1.2.7 + +## [1.2.6] — 2026-08-10 + +- Rename extension id to simulink-data-explorer; bump to 1.2.6 + +## [1.2.5] — 2026-08-10 + +- Open Model Reference / External Data links on click; bump to 1.2.5 + +## [1.2.4] — 2026-08-10 + +- Add loading spinner; route >512MB JSON .sldd to text editor; bump to 1.2.4 + +## [1.2.3] — 2026-08-10 + +- Rename customer-visible "Simulink Project" to "MATLAB Project"; bump to 1.2.3 + +## [1.2.2] — 2026-08-10 + +- Add Marketplace icon; shorten displayName; bump to 1.2.2 + +## [1.2.1] — 2026-08-10 + +- Route oversized JSON .sldd to read-only view; refresh on save + +## [1.2.0] — 2026-08-10 + +Release v1.2.0: format-independent element-name coloring + +## [1.1.2] — 2026-08-07 + +- docs: add install instructions to README Getting Started +- docs: remove Release Notes section from README + +## [1.1.1] — 2026-08-07 + +Maintenance release. + +## [1.1.0] — 2026-08-07 + +Maintenance release. + +## [1.0.3] — 2026-08-07 + +- Route Simulink.VariantConfigurations to VariantConfiguration; empty ConfigSet Value +- Empty non-editable Value for value-less object nodes; close column menu on blur +- Separate Class/Kind/Data Type; add column customization menu + +## [1.0.2] — 2026-08-06 + +- test(integration): fix viewAsText active-editor race +- Refine Architectural Data presentation; release v1.0.2 + +## [1.0.1] — 2026-08-03 + +Maintenance release. diff --git a/package.json b/package.json index c270fe2..0a5e527 100644 --- a/package.json +++ b/package.json @@ -189,6 +189,8 @@ "typecheck": "tsc --noEmit", "test": "vitest run", "test:coverage": "vitest run --coverage", + "check:leak": "node scripts/leak-check.mjs", + "verify": "npm run typecheck && npm run build && npm run build:web && npm run test && npm run check:leak", "compile:test": "node esbuild.test.mjs", "test:integration": "npm run build && npm run compile:test && vscode-test", "package": "vsce package" diff --git a/scripts/leak-check.mjs b/scripts/leak-check.mjs new file mode 100644 index 0000000..d9ebb93 --- /dev/null +++ b/scripts/leak-check.mjs @@ -0,0 +1,144 @@ +// Copyright 2026 The MathWorks, Inc. +// Leak check: nothing internal to MathWorks may reach this tree. The repository is +// public, so this guards the boundary on every push rather than on release — by the +// time a leak is tagged it is already fetchable, and git history keeps it there. +// +// Three phases, each guarding a different way internal material has actually escaped +// or could: a needle in prose or code, an internal registry URL regressed into the +// lockfile, and a local-only notes file force-added past .gitignore. + +import { execFileSync } from 'node:child_process'; +import { readFileSync } from 'node:fs'; + +// `data-explorer-ts` is the internal codename for the subsystem this extension grew +// out of; the public name is "data explorer". The rest are internal infrastructure +// hostnames and the internal forge. +const NEEDLES = ['insidelabs', 'ipws', 'mw-npm-repository', 'gitlab', 'data-explorer-ts']; + +// The lockfile must resolve against the PUBLIC registry. The npm registry configured +// in the dev environment here is the internal Artifactory, and it is the public one +// that is unreachable — so every `npm install` pulls internal `resolved` URLs in and +// they have to be rewritten back out. That rewrite is manual, which means it is +// forgettable, which is why it is asserted. Content-based `integrity` hashes stay +// valid across the rewrite, so a correct lockfile is still an installable one. +const PUBLIC_REGISTRY = 'https://registry.npmjs.org/'; + +// The single legitimate exception: the upstream data model is a git dependency, not a +// registry one, so it resolves to a GitHub URL by design. +const ALLOWED_GIT_PREFIX = 'git+ssh://git@github.com/mathworks/'; + +// Local-only files, kept out of the tree by .gitignore. Ignore rules do not apply to +// files already in the index, so a single `git add -f` — or a tool that does the +// equivalent — makes one tracked permanently and .gitignore goes on looking correct. +// These are the internal notes, so that is the leak with the highest cost. +const LOCAL_ONLY = ['CLAUDE.md', 'docs/superpowers/', 'docs/deep-work/', '.superpowers/']; + +// Every phase runs and the exit code is decided at the end, so one failure does not +// mask what the later phases would have found. Reporting every leak at once matters +// when the caller is unattended and only gets one shot at the output. +let failed = false; +if (!checkNeedles()) failed = true; +if (!checkLockfileRegistry()) failed = true; +if (!checkLocalOnlyUntracked()) failed = true; +process.exit(failed ? 1 : 0); + +// `git grep` rather than a filesystem walk: it scans tracked files only, so it needs +// no exclusion list for node_modules, dist/, or the gitignored notes — and "tracked" +// is exactly the set that can reach the remote. This file is excluded from its own +// scan because it necessarily spells the needles out as literals. +// +// Case-SENSITIVE, deliberately. Adding -i looks strictly safer and is not: `ipws` is +// four characters and turns up inside ordinary camelCase, so -i flags every call to +// `skipWs()` in src/webview/rowFilter.ts. A check that cries wolf on correct code gets +// muted, and a muted check guards nothing. The cost is that a shouted `GITLAB` would +// pass; the needles are lowercase everywhere they legitimately occur. +function checkNeedles() { + let hits = ''; + try { + hits = execFileSync( + 'git', + ['grep', '-nI', '-E', NEEDLES.join('|'), '--', '.', ':(exclude)scripts/leak-check.mjs'], + { encoding: 'utf8' }, + ); + } catch (e) { + // git grep exits 1 when nothing matched — the success case. + if (e.status === 1) { + console.log(`OK: none of ${NEEDLES.length} internal needles appear in a tracked file`); + return true; + } + throw e; + } + + if (hits.trim()) { + console.error('LEAK FAIL — internal references found in tracked files:'); + console.error(hits); + console.error('Genericize the wording, or move the file out of the tree.'); + return false; + } + console.log(`OK: none of ${NEEDLES.length} internal needles appear in a tracked file`); + return true; +} + +// Walk the whole lockfile for `resolved` keys rather than reading the `packages` map +// directly: lockfileVersion 3 keeps them under `packages`, but v2 mirrors them under a +// legacy `dependencies` tree, and a walk covers a format change without noticing one. +function checkLockfileRegistry() { + let lock; + try { + lock = JSON.parse(readFileSync('package-lock.json', 'utf8')); + } catch (e) { + console.error(`LEAK CHECK INCONCLUSIVE — package-lock.json is unreadable: ${e.message}`); + return false; + } + + const bad = []; + let checked = 0; + const walk = (node, path) => { + if (!node || typeof node !== 'object') return; + for (const [key, value] of Object.entries(node)) { + if (key === 'resolved' && typeof value === 'string') { + checked += 1; + if (!value.startsWith(PUBLIC_REGISTRY) && !value.startsWith(ALLOWED_GIT_PREFIX)) { + bad.push(`${path || ''}: ${value}`); + } + } else if (value && typeof value === 'object') { + walk(value, `${path}/${key}`); + } + } + }; + walk(lock, ''); + + if (bad.length > 0) { + const subject = bad.length === 1 ? 'entry does not' : `entries do not`; + console.error(`LEAK FAIL — ${bad.length} lockfile ${subject} resolve to the public registry:`); + for (const entry of bad) console.error(` ${entry}`); + console.error( + `\nRewrite each \`resolved\` host to ${PUBLIC_REGISTRY} — the integrity hashes are\n` + + 'content-based and stay valid. An internal host here both names internal\n' + + 'infrastructure and breaks `npm ci` for everyone outside it.', + ); + return false; + } + console.log(`OK: all ${checked} lockfile resolutions point at the public registry`); + return true; +} + +// `git ls-files` asks the index, which is the question that matters: .gitignore says +// what WOULD be ignored, the index says what will actually be pushed. +function checkLocalOnlyUntracked() { + const tracked = execFileSync('git', ['ls-files', '--', ...LOCAL_ONLY], { encoding: 'utf8' }) + .split('\n') + .filter(Boolean); + + if (tracked.length > 0) { + console.error('LEAK FAIL — local-only files are tracked and would be pushed:'); + for (const file of tracked) console.error(` ${file}`); + console.error( + '\nThese hold internal notes. Run `git rm --cached ` — and if any commit\n' + + 'already containing one has been pushed, the history needs rewriting too.', + ); + return false; + } + console.log(`OK: none of ${LOCAL_ONLY.length} local-only paths are tracked`); + return true; +}