@platformos/platformos-graph 0.0.19 → 0.1.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/CHANGELOG.md +516 -0
- package/bin/platformos-graph +21 -81
- package/dist/cache-probes.spec.d.ts +1 -0
- package/dist/cache-probes.spec.js +62 -0
- package/dist/cache-probes.spec.js.map +1 -0
- package/dist/cli.d.ts +46 -0
- package/dist/cli.js +131 -0
- package/dist/cli.js.map +1 -0
- package/dist/cli.spec.d.ts +1 -0
- package/dist/cli.spec.js +235 -0
- package/dist/cli.spec.js.map +1 -0
- package/dist/graph/app-index-resolution.spec.d.ts +1 -0
- package/dist/graph/app-index-resolution.spec.js +167 -0
- package/dist/graph/app-index-resolution.spec.js.map +1 -0
- package/dist/graph/asset-resolution.spec.d.ts +1 -0
- package/dist/graph/asset-resolution.spec.js +64 -0
- package/dist/graph/asset-resolution.spec.js.map +1 -0
- package/dist/graph/augment.js +3 -1
- package/dist/graph/augment.js.map +1 -1
- package/dist/graph/build.d.ts +15 -2
- package/dist/graph/build.js +54 -12
- package/dist/graph/build.js.map +1 -1
- package/dist/graph/build.spec.d.ts +1 -0
- package/dist/graph/build.spec.js +138 -0
- package/dist/graph/build.spec.js.map +1 -0
- package/dist/graph/edge-sources.d.ts +28 -0
- package/dist/graph/edge-sources.js +43 -0
- package/dist/graph/edge-sources.js.map +1 -0
- package/dist/graph/edge-sources.spec.d.ts +1 -0
- package/dist/graph/edge-sources.spec.js +125 -0
- package/dist/graph/edge-sources.spec.js.map +1 -0
- package/dist/graph/extract.spec.d.ts +1 -0
- package/dist/graph/extract.spec.js +163 -0
- package/dist/graph/extract.spec.js.map +1 -0
- package/dist/graph/module.d.ts +70 -2
- package/dist/graph/module.js +164 -29
- package/dist/graph/module.js.map +1 -1
- package/dist/graph/module.spec.d.ts +1 -0
- package/dist/graph/module.spec.js +74 -0
- package/dist/graph/module.spec.js.map +1 -0
- package/dist/graph/query.d.ts +72 -0
- package/dist/graph/query.js +140 -0
- package/dist/graph/query.js.map +1 -0
- package/dist/graph/query.spec.d.ts +1 -0
- package/dist/graph/query.spec.js +209 -0
- package/dist/graph/query.spec.js.map +1 -0
- package/dist/graph/serialize.spec.d.ts +1 -0
- package/dist/graph/serialize.spec.js +50 -0
- package/dist/graph/serialize.spec.js.map +1 -0
- package/dist/graph/structural.spec.d.ts +1 -0
- package/dist/graph/structural.spec.js +146 -0
- package/dist/graph/structural.spec.js.map +1 -0
- package/dist/graph/test-helpers.d.ts +2 -3
- package/dist/graph/test-helpers.js +5 -9
- package/dist/graph/test-helpers.js.map +1 -1
- package/dist/graph/traverse-edges.spec.d.ts +1 -0
- package/dist/graph/traverse-edges.spec.js +337 -0
- package/dist/graph/traverse-edges.spec.js.map +1 -0
- package/dist/graph/traverse.d.ts +63 -4
- package/dist/graph/traverse.js +364 -49
- package/dist/graph/traverse.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +21 -4
- package/dist/index.js.map +1 -1
- package/dist/parsers.d.ts +35 -0
- package/dist/parsers.js +72 -0
- package/dist/parsers.js.map +1 -0
- package/dist/parsers.spec.d.ts +1 -0
- package/dist/parsers.spec.js +70 -0
- package/dist/parsers.spec.js.map +1 -0
- package/dist/toSourceCode.d.ts +14 -2
- package/dist/toSourceCode.js +23 -19
- package/dist/toSourceCode.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types.d.ts +114 -20
- package/dist/types.js.map +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/fixtures/asset-edges/app/assets/logo.css +1 -0
- package/fixtures/asset-edges/app/assets/site/app.js +1 -0
- package/fixtures/asset-edges/app/views/pages/index.liquid +3 -0
- package/fixtures/background-edges/app/views/pages/broken.liquid +3 -0
- package/fixtures/background-edges/app/views/pages/index.liquid +3 -0
- package/fixtures/background-edges/app/views/partials/jobs/notify.liquid +3 -0
- package/fixtures/function-edges/app/lib/queries/list.liquid +3 -0
- package/fixtures/function-edges/app/views/pages/broken.liquid +3 -0
- package/fixtures/function-edges/app/views/pages/index.liquid +3 -0
- package/fixtures/graphql-edges/app/graphql/blog_posts/find.graphql +10 -0
- package/fixtures/graphql-edges/app/views/pages/broken.liquid +3 -0
- package/fixtures/graphql-edges/app/views/pages/index.liquid +3 -0
- package/fixtures/graphql-table/app/graphql/with_table.graphql +5 -0
- package/fixtures/graphql-table/app/graphql/without_table.graphql +3 -0
- package/fixtures/graphql-table/app/views/pages/index.liquid +4 -0
- package/fixtures/include-edges/app/views/pages/index.liquid +1 -0
- package/fixtures/include-edges/app/views/partials/shared/header.liquid +1 -0
- package/fixtures/layout-edges/app/views/layouts/theme.liquid +4 -0
- package/fixtures/layout-edges/app/views/pages/broken.liquid +5 -0
- package/fixtures/layout-edges/app/views/pages/index.liquid +5 -0
- package/fixtures/module-edges/app/views/pages/index.liquid +4 -0
- package/fixtures/module-edges/modules/my_module/public/lib/queries/get.liquid +3 -0
- package/fixtures/module-edges/modules/my_module/public/views/partials/card.liquid +1 -0
- package/fixtures/schema-nodes/app/schema/blog_post.yml +6 -0
- package/fixtures/schema-nodes/app/schema/no_name.yml +3 -0
- package/fixtures/schema-nodes/app/views/pages/index.liquid +1 -0
- package/fixtures/skeleton/app/assets/app.js +1 -0
- package/fixtures/skeleton/app/views/partials/child.liquid +2 -4
- package/fixtures/skeleton/app/views/partials/parent.liquid +2 -4
- package/fixtures/structural/app/views/pages/about.liquid +1 -0
- package/fixtures/structural/app/views/pages/blog/show.liquid +4 -0
- package/fixtures/structural/app/views/pages/index.liquid +6 -0
- package/fixtures/structural/app/views/pages/rich.liquid +8 -0
- package/fixtures/structural/app/views/partials/card.liquid +1 -0
- package/fixtures/structural/app/views/partials/documented.liquid +5 -0
- package/package.json +7 -5
- package/src/cli.spec.ts +271 -0
- package/src/cli.ts +167 -0
- package/src/graph/augment.ts +4 -2
- package/src/graph/build.spec.ts +888 -9
- package/src/graph/build.ts +68 -17
- package/src/graph/edge-sources.spec.ts +125 -0
- package/src/graph/edge-sources.ts +49 -0
- package/src/graph/module.spec.ts +168 -0
- package/src/graph/module.ts +167 -33
- package/src/graph/query.spec.ts +260 -0
- package/src/graph/query.ts +154 -0
- package/src/graph/serialize.spec.ts +4 -4
- package/src/graph/test-helpers.ts +5 -12
- package/src/graph/traverse.spec.ts +278 -0
- package/src/graph/traverse.ts +423 -54
- package/src/index.ts +16 -1
- package/src/parsers.spec.ts +85 -0
- package/src/parsers.ts +75 -0
- package/src/toSourceCode.ts +26 -26
- package/src/types.ts +120 -23
- package/src/utils/index.ts +1 -1
- package/bin/jsconfig.json +0 -18
- package/dist/getWebComponentMap.d.ts +0 -10
- package/dist/getWebComponentMap.js +0 -66
- package/dist/getWebComponentMap.js.map +0 -1
- package/fixtures/skeleton/assets/app.js +0 -7
- package/src/getWebComponentMap.ts +0 -81
- /package/fixtures/skeleton/{assets → app/assets}/app.css +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,521 @@
|
|
|
1
1
|
# shopify/theme-graph
|
|
2
2
|
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- a8f4da9: The graph answers questions, updates in place, and survives a restart.
|
|
8
|
+
|
|
9
|
+
**Query API** (`query.ts`) — `dependentsOf` / `dependenciesOf`, `reachableFrom`, `orphans` /
|
|
10
|
+
`isOrphan`, `isEntryPoint`, `exists`, `missingDependencies` / `missingTargets`, and
|
|
11
|
+
`nearestModules` (edit-distance "did you mean" over real module names). `dependentsOf` is
|
|
12
|
+
what the MCP supervisor's blast radius reads: the incoming references a per-file lint cannot
|
|
13
|
+
see, because lint is forward-looking and per-file.
|
|
14
|
+
|
|
15
|
+
**Incremental update** (`incremental.ts`) — `applyFileChange(graph, uri, kind, fs)` applies
|
|
16
|
+
one file's add/modify/delete in time proportional to that file rather than to the project.
|
|
17
|
+
A consumer that reconciles per change no longer pays a whole-project parse to learn that one
|
|
18
|
+
partial gained a caller.
|
|
19
|
+
|
|
20
|
+
**Persistence** (`deserialize.ts`) — a graph can be serialized and reloaded, so a fresh
|
|
21
|
+
process starts from a persisted graph plus the on-disk delta instead of a full build.
|
|
22
|
+
|
|
23
|
+
**`enumerateEdgeSources`** (`edge-sources.ts`) — the graph now owns the definition of which
|
|
24
|
+
files are edge sources, beside the classifier that decides it. It was previously re-derived
|
|
25
|
+
by the supervisor's cache, which is two answers to one question that must agree; the consumer
|
|
26
|
+
is now pure. The enumeration is ANCHORED on the platformOS source subtrees rather than
|
|
27
|
+
walking the whole tree and skipping directories by name — a bundled `react-app/` is never
|
|
28
|
+
descended into, and `app/views/pages/vendor/**` is a real site section that any `vendor`
|
|
29
|
+
blacklist would have dropped.
|
|
30
|
+
|
|
31
|
+
- f644a30: The graph resolves reference names through the `App` index its caller already holds.
|
|
32
|
+
|
|
33
|
+
`IDependencies` gains `app`, the companion to `getSourceCode`: a caller that has an
|
|
34
|
+
`App` hands it over for BOTH, so a graph build reuses the project model's parses AND its
|
|
35
|
+
name index. `traverse.ts` passes it to `DocumentsLocator`, and the language server's
|
|
36
|
+
`AppGraphManager` supplies the same `App` it already reads sources through.
|
|
37
|
+
|
|
38
|
+
It cannot change the ANSWER, only its cost. `App.findOrLocate` is index-first and falls
|
|
39
|
+
through to the very candidate walk the walk-only stand-in performs, in
|
|
40
|
+
`getAppPaths`/`getModulePaths` order either way — so an index that is empty, partial or
|
|
41
|
+
stale resolves exactly as before. What it removes is the I/O: without an app, every
|
|
42
|
+
`{% render %}`, `{% include %}`, `{% function %}`, `{% background %}`, `{% graphql %}`
|
|
43
|
+
and `layout:` in the project costs one `readDirectory` per candidate DIRECTORY, on a
|
|
44
|
+
project the language server had just walked and indexed. Measured over a whole-project
|
|
45
|
+
build on three real projects — directory listings, then median wall clock of three runs,
|
|
46
|
+
with the graph compared node-for-node:
|
|
47
|
+
|
|
48
|
+
| project | files | listings | build |
|
|
49
|
+
| -------------------- | ----- | --------------- | ------------- |
|
|
50
|
+
| project-a | 3450 | 6993 → **590** | 12.1 → 11.7 s |
|
|
51
|
+
| project-b | 3623 | 9911 → **185** | 7.3 → 7.1 s |
|
|
52
|
+
| pos-module-community | 1622 | 11787 → **762** | 4.8 → 4.0 s |
|
|
53
|
+
|
|
54
|
+
Identical graphs on all three. **The wall-clock win is the small half** (3-15%): a full
|
|
55
|
+
build is dominated by parsing every reachable file, and no index changes that. What goes
|
|
56
|
+
away is 92-98% of the I/O, which is the part that scales with how many references a
|
|
57
|
+
project has rather than how much source it has. The listings left are the two that must
|
|
58
|
+
remain: an asset lookup (never indexed — nothing reads an asset, so the only question is
|
|
59
|
+
whether it still exists on disk) and a name the index genuinely cannot answer.
|
|
60
|
+
|
|
61
|
+
Graphs are also pinned identical across three arms in the suite — a whole app, no app,
|
|
62
|
+
and an app holding only the entry points, so that every target is an index miss — on both
|
|
63
|
+
the plain and the module-prefixed fixtures.
|
|
64
|
+
|
|
65
|
+
Two comments corrected while measuring this, both of which pointed the next reader the
|
|
66
|
+
wrong way:
|
|
67
|
+
|
|
68
|
+
- `graphParsers` claimed the MCP supervisor was a "consumer-to-be" of
|
|
69
|
+
`appBackedGetSourceCode`. It is not, and the reason is now written down: its full
|
|
70
|
+
builds run on a worker thread (a second heap on purpose) which cannot share `AppFile`
|
|
71
|
+
objects at all, and its incremental apply must not read through check-node's shared
|
|
72
|
+
`App`, which carries unsaved editor buffers and is mutated by concurrent lints while
|
|
73
|
+
the graph cache is an authority on DISK state. There is also nothing to win —
|
|
74
|
+
`lintBuffers` parses the content it was handed and drops the app's entry for that file
|
|
75
|
+
on the way out, so the file a reconcile parses is precisely the one whose parse no lint
|
|
76
|
+
would have reused.
|
|
77
|
+
- The same doc implied a graph build needs the `.js`/image parsers. It does not:
|
|
78
|
+
`traverseModule` returns immediately for an Asset node and the only fact the graph
|
|
79
|
+
wants about an asset is whether it exists (`fs.stat`). Every file a build READS is
|
|
80
|
+
Liquid, GraphQL or YAML, so `sourceParsers` alone is enough to back one. The entries
|
|
81
|
+
exist for `toSourceCode`'s total contract and for an `App` that an asset URI is put
|
|
82
|
+
into. Pinned by a test asserting the build asks for no asset source code, with the
|
|
83
|
+
Liquid files it does read as the control.
|
|
84
|
+
|
|
85
|
+
- cf80cfa: Lazy `App` object model, one anchored project walk, and a `lintBuffer` that says
|
|
86
|
+
whether it checked the file.
|
|
87
|
+
|
|
88
|
+
`@platformos/platformos-common` now owns an `App` / `AppFile` model: it classifies a
|
|
89
|
+
project's paths and reads or parses nothing until a consumer asks for a specific
|
|
90
|
+
file. Parsers are injected, so the package stays below the parser stack and every
|
|
91
|
+
consumer can share one set of file objects. Names (`{% render 'ui/card' %}`) resolve
|
|
92
|
+
through an O(1) per-type index whose precedence is `getAppPaths`/`getModulePaths`
|
|
93
|
+
order, i.e. the same rule the candidate-path walk uses.
|
|
94
|
+
|
|
95
|
+
`check-node`'s `getApp` builds that model instead of reading and eagerly parsing
|
|
96
|
+
every file on every call, so a `validate_code`-style single-file lint parses the file
|
|
97
|
+
it visits plus the handful of render targets that file resolves — not the project.
|
|
98
|
+
`check-node` also now holds one `RouteTable` per process, reconciled per run against
|
|
99
|
+
each page's `mtime`/`size`, so an unchanged project costs zero page reads while an
|
|
100
|
+
added, changed or deleted page is still reflected.
|
|
101
|
+
|
|
102
|
+
That table is now also resolved LAZILY. `Dependencies.routeTable` accepts a provider as
|
|
103
|
+
well as a table, `MissingPage` asks for it at the first URL it actually has to resolve
|
|
104
|
+
rather than in `onCodePathStart`, and check-node passes its reconciler as the provider.
|
|
105
|
+
Knowing a route means reading every page in the project, while 87-97% of the Liquid in a
|
|
106
|
+
real project contains no `<a href>`/`<form action>` pointing at an internal route — so a
|
|
107
|
+
lint of one of those files now touches no page at all, where before it fingerprinted
|
|
108
|
+
every one of them. Warm `lintBuffer` on such a file, on a 3.1k-file project: 405 → 8
|
|
109
|
+
`stat`s and 71-79 → 54-77 ms; a file that does resolve a route pays exactly what it did.
|
|
110
|
+
Whole-project offenses are identical, field for field, on three real projects (9623,
|
|
111
|
+
256 and 43 offenses).
|
|
112
|
+
|
|
113
|
+
`getApp` also stops classifying. It walks the subtrees an app file can live in —
|
|
114
|
+
`APP_SOURCE_SUBTREES` (`app/`, `marketplace_builder/`, `modules/*/public`,
|
|
115
|
+
`modules/*/private`) with the `SOURCE_FILE_EXTENSIONS` extensions, both derived in
|
|
116
|
+
`platformos-common` from the file-type model — and lets `App.fromPaths` decide what
|
|
117
|
+
the app contains. One answer to "is this a platformOS file", in the package that owns
|
|
118
|
+
the question: whether a file belongs to the app is its position relative to the
|
|
119
|
+
project ROOT, so `tmp/app/views/partials/x.liquid` is not a partial and
|
|
120
|
+
`app/views/pages/vendor/x.liquid` is a page.
|
|
121
|
+
|
|
122
|
+
The walk therefore never enters `node_modules` and the rest of the repository at all.
|
|
123
|
+
Walk time on two real projects: 903 → 33 ms and 177 → 30 ms; whole `getApp` on the same
|
|
124
|
+
two, 1289 → 152 ms and 381 → 154 ms, with a file-by-file identical app on four real
|
|
125
|
+
projects. `getAppFilesPathPattern` is REMOVED: it had no consumer left once the lint
|
|
126
|
+
stopped globbing, and a watcher that wants patterns can build them from
|
|
127
|
+
`APP_SOURCE_SUBTREES` and `SOURCE_FILE_GLOB`, which is all it did.
|
|
128
|
+
|
|
129
|
+
`check-node` also holds one `App` per project per process, reconciled per call rather
|
|
130
|
+
than rebuilt. The project walk is NOT cached — a process that gets no filesystem
|
|
131
|
+
events has no honest way to invalidate one, and an agent editing files out of band is
|
|
132
|
+
exactly the case this has to be right for — so the candidate paths are walked on
|
|
133
|
+
every call and the app is brought in line with them: files the walk no longer sees are
|
|
134
|
+
dropped, files it did not know are added, and files whose source is in memory are
|
|
135
|
+
`stat`ed and dropped if they changed. Everything else — classification, both name
|
|
136
|
+
indexes, and the handful of sources and ASTs the previous calls loaded — is reused.
|
|
137
|
+
Warm `lintBuffer`, shared vs rebuilt per call, on two real projects: 104-116 ms vs
|
|
138
|
+
177-195 ms and 77-107 ms vs 123-160 ms, with diagnostics identical file by file over 40
|
|
139
|
+
files per project. At most 200 files keep their source between calls, so a long-lived
|
|
140
|
+
process stops accumulating the project (300 calls on the larger: 574 MB and climbing →
|
|
141
|
+
497 MB, flat). `resetSharedApp()` discards it. `lintBuffer` now reverts its buffer
|
|
142
|
+
overlay when the call ends, since the app outlives it.
|
|
143
|
+
|
|
144
|
+
`isIgnored` compiles each `ignore` pattern once per config instead of rewriting and
|
|
145
|
+
recompiling a `Minimatch` on every path it is asked about — it is asked once per
|
|
146
|
+
candidate path in `getApp` and again per file per check in `check()`. Same patterns,
|
|
147
|
+
same answers, file-by-file identical on three real projects; the filter itself is
|
|
148
|
+
5-6× faster (on one of them, 1511 candidates against 13 patterns: 76-98 → 14-16
|
|
149
|
+
ms), and `getApp` there is 207-267 → 45-69 ms.
|
|
150
|
+
|
|
151
|
+
**The dependency graph and the language server see the whole app now.** Both walked
|
|
152
|
+
the project by starting at the root and skipping any directory whose name ended in
|
|
153
|
+
`.git`, `node_modules`, `dist`, `build`, `tmp` or `vendor`. The last four match at any
|
|
154
|
+
depth, so `app/views/pages/vendor/**` — an entire section of a live site — was invisible
|
|
155
|
+
to both: every reference to those pages looked orphaned in the graph, and the language
|
|
156
|
+
server managed none of them (no diagnostics, completions or rename), with nothing to
|
|
157
|
+
indicate why. Measured over the projects on hand, one loses 137 app files that way and
|
|
158
|
+
another 3 (`app/lib/commands/v2/projects/update/build/*`).
|
|
159
|
+
|
|
160
|
+
They now share `walkAppSourceFiles` in `platformos-common`, which walks
|
|
161
|
+
`APP_SOURCE_SUBTREES` — the same anchoring `getApp` adopted, and the same rule
|
|
162
|
+
`parseAppPath` has always enforced. `recursiveReadDirectory` and its directory-name
|
|
163
|
+
blacklist are gone. `getApp` walks with it too, instead of globbing the equivalent
|
|
164
|
+
patterns: same paths file for file on four real projects, and 9-15% faster (median
|
|
165
|
+
39 → 33 ms on the largest, 33 → 28 on another), because
|
|
166
|
+
a walk filters by extension as it enumerates instead of matching a pattern per path.
|
|
167
|
+
Two edge cases the glob decided and the walk now decides
|
|
168
|
+
deliberately: hidden entries (`.#card.liquid`, `._card.liquid`, `.old/`) are still
|
|
169
|
+
skipped, and an unreadable directory now FAILS the run rather than being skipped in
|
|
170
|
+
silence.
|
|
171
|
+
|
|
172
|
+
That failure is an `UnreadableDirectoryError`, which names the directory relative to
|
|
173
|
+
the project root and says what to do about it, rather than a bare `EACCES … scandir`.
|
|
174
|
+
`pos-cli check` prints it as a message and exits 1; an unexpected error still gets its
|
|
175
|
+
stack. In the language server it also fixed two things that were never specific to
|
|
176
|
+
permissions, and broke a session for any cause — a dropped network mount, `EMFILE` on
|
|
177
|
+
a large project, a directory another process has locked. `preload` is `memoize`d, and
|
|
178
|
+
`memoize` caches the REJECTED promise, so one failure replayed for every later preload
|
|
179
|
+
of that root, including after the cause was gone; and `progress.end()` was on the
|
|
180
|
+
success path only, so "Initializing Liquid LSP" stayed on screen for the rest of the
|
|
181
|
+
session. `preload` now ends its progress, drops the memo so a retry can succeed, shows
|
|
182
|
+
the user the reason (once per distinct failure per root — the graph rebuild preloads on
|
|
183
|
+
every file event) and logs the error with its stack. `AppGraphManager` likewise stops
|
|
184
|
+
caching a rejected graph build.
|
|
185
|
+
|
|
186
|
+
`NodeFileSystem.readDirectory` builds each entry's URI by appending to the directory's
|
|
187
|
+
(`path.childUri`) instead of parsing and re-serializing it (`path.join`). It runs once
|
|
188
|
+
per entry of every directory any walk opens — tens of thousands of times per project,
|
|
189
|
+
mostly for entries the caller discards — and the round trip was about a third of the
|
|
190
|
+
walk. `childUri` is pinned against `join` itself for every name shape a listing can
|
|
191
|
+
produce, including `#`, `?` and Windows separators.
|
|
192
|
+
|
|
193
|
+
Anchoring is also faster than either alternative, because the walk
|
|
194
|
+
never opens the directories in question: on a project with 20 000 files under
|
|
195
|
+
root-level `dist`/`build`/`vendor`/`coverage`, the walk is 6 ms, against 19-23 ms for
|
|
196
|
+
the blacklist as it was (which loses the vendor page) and 63-69 ms for a blacklist
|
|
197
|
+
shortened to the safe names (which keeps it). On four real projects: 71-78 → 31-34 ms,
|
|
198
|
+
35-38 → 30-34 ms, 20-21 → 18-19 ms, and one unchanged at 23-28 ms.
|
|
199
|
+
|
|
200
|
+
One consequence: files outside the app subtrees — `seed/post_import/**`,
|
|
201
|
+
`tests/post_import/**` — are no longer preloaded by the language server, matching what
|
|
202
|
+
the linter's app has contained since `getApp` was anchored. Opening one still manages
|
|
203
|
+
it, as opening any supported file always has.
|
|
204
|
+
|
|
205
|
+
**`lintBuffer` now says whether it checked the file at all.** It returns
|
|
206
|
+
`{ status, offenses }` instead of `Offense[]`. Three kinds of path are never linted —
|
|
207
|
+
one the config's `ignore` list covers, one outside `app/`/`marketplace_builder/`/
|
|
208
|
+
`modules/<name>/(public|private)/`, and an asset with no parser or checks — and each
|
|
209
|
+
used to come back as an empty `Offense[]`, which is exactly what a clean file returns.
|
|
210
|
+
For `pos-cli check` that is harmless; for an agent asking "is this file OK before I
|
|
211
|
+
write it?" it is the wrong answer given confidently. `status` is `checked`,
|
|
212
|
+
`excluded-by-config`, `not-an-app-file` or `not-a-source-file`, and `offenses` is empty
|
|
213
|
+
for all but the first. `pos-cli check`'s own behaviour is unchanged — it goes through
|
|
214
|
+
`appCheckRun`, not this seam. The MCP supervisor carries the reason into
|
|
215
|
+
`next_step` until its result contract grows a status of its own.
|
|
216
|
+
|
|
217
|
+
**VS Code now sends YAML buffers to the language server.** `documentSelectors` had no
|
|
218
|
+
`yaml` entry, so translations, tables, user profile types and transactable types got no
|
|
219
|
+
diagnostics, completions or go-to-definition — the server has handled them all along.
|
|
220
|
+
The selectors are now derived from `SOURCE_FILE_EXTENSIONS`, with the `.yml` pattern
|
|
221
|
+
anchored to `app/`, `marketplace_builder/` and `modules/` so that a
|
|
222
|
+
`.github/workflows/ci.yml` is not handed to the language server. The dead `json`/`jsonc`
|
|
223
|
+
selectors are gone: they matched Shopify's `{config,locales,sections,templates}` layout,
|
|
224
|
+
and `JSONLanguageService` has nothing to serve, since platformOS publishes no JSON
|
|
225
|
+
schemas.
|
|
226
|
+
|
|
227
|
+
**Classification is anchored at the project root, and the extension is part of it.**
|
|
228
|
+
Three changes that together make `platformos-common` the single answer to "is this a
|
|
229
|
+
platformOS file, and if so what kind".
|
|
230
|
+
|
|
231
|
+
`getFileType(uri, rootUri)` now REQUIRES a root, as does every `isPage` / `isPartial` /
|
|
232
|
+
`isLayout` / `isAsset` / `isKnown*File` / `isSupportedSourceFile` predicate. A platformOS
|
|
233
|
+
file is one whose position RELATIVE TO THE PROJECT ROOT matches the directory structure,
|
|
234
|
+
so a classifier without a root cannot answer the question — it can only test whether a
|
|
235
|
+
known directory name appears somewhere in the string. That is how
|
|
236
|
+
`seed/post_import/app/migrations/20220517145452_index_rebuild.liquid` came to be a
|
|
237
|
+
Migration to the language server, the graph and the VS Code extension while being
|
|
238
|
+
correctly absent from the lint's app: it contains `app/migrations/` and is not deployed,
|
|
239
|
+
so nothing it renders or queries exists to resolve, and every diagnostic on it was noise
|
|
240
|
+
about a file the platform will never run. Callers get their root from what they already
|
|
241
|
+
hold — checks from a new `context.fileType(uri?)` (which reads `AppFile.fileType` off the
|
|
242
|
+
run's App, so the common path re-derives nothing), the graph from `AppGraph.rootUri`, the
|
|
243
|
+
language server from `findAppRootURI`. **This is a breaking API change** for anything
|
|
244
|
+
calling those exports directly.
|
|
245
|
+
|
|
246
|
+
Classification also consults the EXTENSION now, mirroring each backend model's
|
|
247
|
+
`PHYSICAL_PATH`: `app/graphql/x.yml` and `app/translations/en.json` are no longer a
|
|
248
|
+
GraphQL file and a Translation. Page, Layout, Partial and Asset stay permissive, because
|
|
249
|
+
`page.rb` and `instance_view.rb` are `(.+)` with no extension anchor — `app/views/pages/home.html`
|
|
250
|
+
is a Page the platform deploys and the linter cannot read. **`.yaml` is no longer a
|
|
251
|
+
platformOS extension**: every YAML model anchors `\.yml\z`, so `SOURCE_FILE_EXTENSIONS`
|
|
252
|
+
is now `.liquid`, `.yml`, `.graphql`, and a project with `app/translations/en.yaml` stops
|
|
253
|
+
getting diagnostics for it — that file was never deployed. `ActivityStreamsHandler` and
|
|
254
|
+
`ActivityStreamsGroupingHandler` are new file types.
|
|
255
|
+
|
|
256
|
+
**There is no ignore list left.** `isSupportedSourceFile` is the intersection of two
|
|
257
|
+
whitelists — the platform deploys it, and we have a parser for it — and nothing else. It
|
|
258
|
+
used to open with a `/\.(s?css|js)\.liquid$/` test, and an ignore list is only consulted
|
|
259
|
+
by the callers of whoever holds it: the language server refused `theme.css.liquid` while
|
|
260
|
+
the lint put it in the app with the Liquid+HTML parser and reported
|
|
261
|
+
`LiquidHTMLSyntaxError` on it. A file we cannot parse is now one with no row in the
|
|
262
|
+
parser table, which is keyed on the RESPONSE FORMAT for `.liquid` files, because the
|
|
263
|
+
format is the body language. `users.json.liquid` is parsed; `theme.css.liquid` and
|
|
264
|
+
`run.js.liquid` are not, by either tool. `.scss.liquid` changes the other way: `scss` is
|
|
265
|
+
not in the platform's FORMAT_ENUM, so that file is a partial named `x.scss` and is now
|
|
266
|
+
linted.
|
|
267
|
+
|
|
268
|
+
App file sets and per-check offense totals are identical before and after on four real
|
|
269
|
+
projects (946 files / 43 offenses, 3139 / 9623, 2789 and 2895).
|
|
270
|
+
|
|
271
|
+
Behaviour changes worth knowing about:
|
|
272
|
+
|
|
273
|
+
- **`OrphanedPartial` is REMOVED**, and with it the `singleFileOnly` check partition
|
|
274
|
+
it was the only member of. It asks "is any file rendering this partial?", which no
|
|
275
|
+
index answers without every Liquid file parsed — and, once wired up and measured, it
|
|
276
|
+
answered wrongly too often to ship: 231 hits on a module project, every one of
|
|
277
|
+
them a module's `public/` API whose callers live in other repositories; and on a
|
|
278
|
+
large site a large share of the 465 hits were partials invoked BY NAME, either through
|
|
279
|
+
a dispatcher (`mutation_name: 'authentications/delete'`) or as a callback
|
|
280
|
+
(`access_callback: 'lib/can/theme_manage'`), which static analysis cannot see. A
|
|
281
|
+
warning that is wrong that often is one nobody reads.
|
|
282
|
+
|
|
283
|
+
Removing it removes the reason for the partition: every remaining check answers for
|
|
284
|
+
one file, resolving against the project through indexes that are already cached
|
|
285
|
+
(`MissingPage` through the route table, `MissingPartial` through the name index).
|
|
286
|
+
So `CheckOptions.singleFileOnly`, `meta.singleFile` and `Dependencies.getReferences`
|
|
287
|
+
are gone, and the editor, `pos-cli check` and `validate_code` now run exactly the
|
|
288
|
+
same set of checks. `validate_code`'s `mode: full | quick` input is REMOVED with it:
|
|
289
|
+
the partition was the only thing it could have selected. Unknown arguments are
|
|
290
|
+
dropped by the MCP SDK, so a caller that still sends `mode` gets the same result it
|
|
291
|
+
always did.
|
|
292
|
+
|
|
293
|
+
- Translation lookups now treat "an open editor buffer" as a file with a defined
|
|
294
|
+
`version`, rather than any file present in the app object. Contents are otherwise
|
|
295
|
+
read from the filesystem.
|
|
296
|
+
- Six packages that imported `@platformos/*` siblings without declaring them now do
|
|
297
|
+
(they had resolved only through workspace hoisting).
|
|
298
|
+
|
|
299
|
+
**The language server holds the same `App`, so opening a workspace no longer parses
|
|
300
|
+
it.** `DocumentManager` was a second, LSP-shaped store of source codes beside the
|
|
301
|
+
model — its own `Map<uri, AugmentedSourceCode>`, and a `preload` that read AND eagerly
|
|
302
|
+
parsed every file in the project. It now holds one `App` per project root and delegates:
|
|
303
|
+
`open`/`change` are `setSource`, `delete`/`rename` go through the App's own index (so
|
|
304
|
+
deleting an `app/modules/X` overwrite promotes the `modules/X` original back), and
|
|
305
|
+
`app(root, includeFilesFromDisk)` is `App.sourceCodes()` — the same intersection
|
|
306
|
+
`isSupportedSourceFile` names, asked of a file that classified its own path once
|
|
307
|
+
instead of re-derived per call per predicate. `preload` classifies the walk's paths
|
|
308
|
+
(no I/O), reads the ones with a parser, and parses nothing; an `AppFile` parses on the
|
|
309
|
+
first `ast`.
|
|
310
|
+
|
|
311
|
+
Measured through the real language server on a 2735-liquid-file project —
|
|
312
|
+
`initialize` → `didOpen` → the first `publishDiagnostics` — median of five runs: first
|
|
313
|
+
diagnostic 17,742 → **771 ms**, first completion 191 → 187 ms, RSS 705-720 → **333-347
|
|
314
|
+
MB**, with byte-identical diagnostics. One cost MOVES rather than disappearing: a
|
|
315
|
+
whole-project graph build now pays for the parses `preload` used to
|
|
316
|
+
(`appGraph/dependencies` 198 ms → 11.5 s there, one-time — the ASTs stay on the
|
|
317
|
+
files, so the next request is 1 ms). Total time to a graph still falls, 18.0 → 12.4 s,
|
|
318
|
+
and it is off the startup path.
|
|
319
|
+
|
|
320
|
+
`set()` also classifies WITH a root now, like every other consumer: the app a URI falls
|
|
321
|
+
under supplies one, so "is this part of an app" is asked the way `getFileType`, the
|
|
322
|
+
checks and the graph ask it. A readable file the platform does not deploy
|
|
323
|
+
(`scripts/build.liquid`) is still an editor document — formatting, hover, completions —
|
|
324
|
+
and is in no `App`, so it gets no diagnostics and no graph node.
|
|
325
|
+
|
|
326
|
+
**The graph and the checks now parse each file once, not once each.** `AppGraphManager`
|
|
327
|
+
builds its `getSourceCode` with `appBackedGetSourceCode(app, fallback)`, and the app is
|
|
328
|
+
built with check-common's `sourceParsers` merged with the graph's `.js`/image entries,
|
|
329
|
+
so both halves of the process hold the SAME `AppFile` instances. `sourceParsers` is new
|
|
330
|
+
and is the single definition of how a file becomes an AST; check-node's `nodeParsers` is
|
|
331
|
+
now an alias for it. The graph's `toSourceCode` remains as the path for a caller holding
|
|
332
|
+
a buffer and no app — an in-flight `validate_code` buffer, a URI outside the project —
|
|
333
|
+
which by definition has no `AppFile`.
|
|
334
|
+
|
|
335
|
+
Two bugs fixed on the way, both of which cost cross-file diagnostics silently:
|
|
336
|
+
|
|
337
|
+
- `runChecks` never waited for `preload`, so the first check of a session could run
|
|
338
|
+
against a project that had not been read and quietly miss every diagnostic that
|
|
339
|
+
depends on another file's `{% doc %}`. It got away with it only because `preload` was
|
|
340
|
+
slow enough to monopolise the event loop.
|
|
341
|
+
- A file the workspace could not READ stayed in the app with no contents, and
|
|
342
|
+
`AppFile.source` throws rather than returning `''`. One unreadable file therefore cost
|
|
343
|
+
the whole run. A file is a document here exactly when its contents are in memory,
|
|
344
|
+
which is the set the old `Map` held.
|
|
345
|
+
|
|
346
|
+
**A malformed translation file no longer costs a project its document links.**
|
|
347
|
+
`TranslationProvider` let `yaml.load` throw, and a duplicated mapping key — two people
|
|
348
|
+
adding the same key, which real projects have — made `textDocument/documentLink` reject
|
|
349
|
+
for the whole file: the `render` links disappeared along with the translation ones, while
|
|
350
|
+
hover and go-to-definition kept working because they are separate requests. Parse
|
|
351
|
+
failures are values here now, the same contract `AppFile.ast` and `toYAMLAST` keep; an
|
|
352
|
+
unparseable translation file contributes no translations, exactly as a missing one does,
|
|
353
|
+
and the linter still reports the syntax error against the file that has it.
|
|
354
|
+
|
|
355
|
+
### Patch Changes
|
|
356
|
+
|
|
357
|
+
- 8f1beea: `check` refuses a path that is not a project root, instead of reporting it clean.
|
|
358
|
+
|
|
359
|
+
`appCheckRun` handed its argument to `getAppAndConfig` as the project root without checking that
|
|
360
|
+
it was one. A directory carrying no marker — true of `app/`, and of any single module directory —
|
|
361
|
+
loaded zero files, so the run returned zero offenses and every caller printed "No offenses found".
|
|
362
|
+
That is indistinguishable from a clean project, and it is the dangerous direction: a developer, a
|
|
363
|
+
CI job or an agent gating on that message concludes the code is clean when nothing was inspected.
|
|
364
|
+
|
|
365
|
+
Measured on a real app: `pos-cli check run` reported 1036 offenses across 191 files, while
|
|
366
|
+
`pos-cli check run app` on the same project reported none — with a partial containing an unclosed
|
|
367
|
+
`{% if %}`, an unclosed `<div>` and an undefined filter sitting inside `app/`.
|
|
368
|
+
|
|
369
|
+
It now names what happened and where the root is — and says only as much as the evidence supports.
|
|
370
|
+
|
|
371
|
+
A root DECLARED by a `.pos` or `.platformos-check.yml` file is asserted:
|
|
372
|
+
|
|
373
|
+
```
|
|
374
|
+
Nothing was checked: /project/app is not the root of a platformOS project.
|
|
375
|
+
Re-run the check against the project root: /project
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
A root INFERRED from a directory NAME is not. `app`, `modules` and `marketplace_builder` are
|
|
379
|
+
ordinary names — a checkout of module repositories under `~/Work/modules` makes `~/Work` resolve as
|
|
380
|
+
a project root, and a Windows machine shipping `C:\Modules` makes the drive root resolve as one —
|
|
381
|
+
so stating it as fact sends people to re-run against a tree that is not their project:
|
|
382
|
+
|
|
383
|
+
```
|
|
384
|
+
Nothing was checked: /home/me/Work/multiproj is not a platformOS project root.
|
|
385
|
+
A project root contains one of: app/, marketplace_builder/, modules/, .pos, .platformos-check.yml.
|
|
386
|
+
The nearest above it is /home/me/Work, matched on modules/ alone — that may not be your project.
|
|
387
|
+
Re-run the check against your project root.
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
`resolveProjectRoot` reports which marker matched, and `isDeclaredRoot` distinguishes the two.
|
|
391
|
+
The message names no tool: the same text reaches pos-cli, the editor through the VS Code extension,
|
|
392
|
+
and any embedder, so naming one of them would be wrong for the other two.
|
|
393
|
+
|
|
394
|
+
The refusal is a typed `ProjectRootError` carrying a stable `PROJECT_ROOT_ERROR_CODE`, so a consumer
|
|
395
|
+
can present it as a message rather than a crash without matching on prose or on `instanceof` — the
|
|
396
|
+
latter throws outright when the loaded copy of this package predates the class.
|
|
397
|
+
|
|
398
|
+
**It reports rather than resolving.** Widening the run to the enclosing root would check MORE than
|
|
399
|
+
was asked — `check run app` would pull in `modules/`, so a run meant for one app reports offenses
|
|
400
|
+
from vendored code its caller does not own, and a CI job scoped to `app/` starts failing on its
|
|
401
|
+
dependencies. `platformos-graph` can resolve-and-proceed because the graph of a project is the same
|
|
402
|
+
answer wherever you point at it inside the project; "check this directory" is not. Linting an
|
|
403
|
+
arbitrary subtree remains unsupported and is a separate feature: it would have to load the whole
|
|
404
|
+
project anyway, since partials, pages and config all resolve project-wide, and then filter what it
|
|
405
|
+
reports.
|
|
406
|
+
|
|
407
|
+
`findRoot`, `makeFileExists` and the new `resolveProjectRoot` / `PROJECT_ROOT_MARKERS` moved from
|
|
408
|
+
`platformos-check-common` to `platformos-common`. They are project-LAYOUT knowledge with nothing
|
|
409
|
+
linter-specific in them, and every constant they run on — `APP_ROOTS`, `STANDALONE_MODULE_ROOTS`,
|
|
410
|
+
`APP_SOURCE_SUBTREES` — already lived in `platformos-common`, alongside `AbstractFileSystem` and
|
|
411
|
+
the URI helpers. They are re-exported from `platformos-check-common`, so existing imports keep
|
|
412
|
+
working. `platformos-graph` drops its own copy of the resolution and calls the shared one; its
|
|
413
|
+
error message is unchanged, which its existing assertion on that exact string proves.
|
|
414
|
+
|
|
415
|
+
- a8f4da9: How a platformOS GraphQL document is READ now lives in `platformos-common`, and a
|
|
416
|
+
`.graphql` file is parsed exactly once until its source changes.
|
|
417
|
+
|
|
418
|
+
`parseGraphql`, `extractGraphqlTables` and `extractGraphqlVariables` are platformOS
|
|
419
|
+
domain knowledge — what a table filter looks like, what a `{% graphql %}` call site may
|
|
420
|
+
and must pass — so they sit beside `extractSchemaTable`, the schema `name:` those tables
|
|
421
|
+
join to, rather than inside the linter. `platformos-check-common` no longer defines
|
|
422
|
+
`graphql-table.ts` and re-exports none of them; it re-exports only the
|
|
423
|
+
`GraphQLDocumentNode` TYPE, exactly as it re-exports `SourceCodeType`.
|
|
424
|
+
|
|
425
|
+
**The placement is what makes the parse shareable.** The GraphQL "AST" an `AppFile` held
|
|
426
|
+
was `{ type: 'Document', content }` — the source string, unparsed — so every consumer
|
|
427
|
+
parsed it again for itself:
|
|
428
|
+
|
|
429
|
+
- `GraphQLCheck`, once per file per run;
|
|
430
|
+
- `GraphQLVariablesCheck`, one `fs.readFile` **plus** one parse per `{% graphql %}` call
|
|
431
|
+
site — which also meant it read the disk while the editor held an unsaved buffer;
|
|
432
|
+
- `UnknownProperty` and the language server's type system, one parse per call site;
|
|
433
|
+
- the graph build, one more.
|
|
434
|
+
|
|
435
|
+
`sourceParsers` now injects `parseGraphql`, so the `AppFile` memoizes the real document
|
|
436
|
+
and all four read that one. Measured on a real project (`project-c`, 453 `.graphql` files)
|
|
437
|
+
by counting every call into `graphql`'s `parse` during a full CLI run: **2442 parses
|
|
438
|
+
before, 505 after**, for the same 15511 offenses. Wall clock is unchanged within noise
|
|
439
|
+
(115.9 s → 114.1 s) — a full lint of that project is dominated by Liquid parsing, so this
|
|
440
|
+
is a cost removed rather than a speed-up to advertise. It matters most where the same
|
|
441
|
+
document is read over and over against a warm app: the language server's per-keystroke
|
|
442
|
+
type inference, and the MCP supervisor's per-write `validate_code`.
|
|
443
|
+
|
|
444
|
+
`GraphQLDocumentNode` gains `document?` and `syntaxError?` alongside `content`, which is
|
|
445
|
+
unchanged — a document that does not compile is a normal state `GraphQLCheck` reports
|
|
446
|
+
on, so the error is a value on the node rather than the `Error` a parser may return,
|
|
447
|
+
which would take the file out of the very pipeline that reports it.
|
|
448
|
+
|
|
449
|
+
**`GraphQLCheck` now reports a syntax error even when the docset has no schema.** It used
|
|
450
|
+
to gate the whole check on `platformosDocset.graphQL()`, so a document that did not
|
|
451
|
+
compile drew nothing — and the test pinning that silence used a syntactically VALID
|
|
452
|
+
fixture, so nothing distinguished "no schema to compare against" from "says nothing at
|
|
453
|
+
all". A syntax error needs no schema; only `validate()` does. This changes no Node run:
|
|
454
|
+
the docs manager downloads the schema to its cache and falls back to the copy committed
|
|
455
|
+
in `platformos-check-docs-updater/data/graphql.graphql`, so `null` reaches the check only
|
|
456
|
+
from a caller that injects its own docset — a browser embedder, or a test. The silence
|
|
457
|
+
that remains (an unknown FIELD, without a schema) now has that syntax test beside it as
|
|
458
|
+
its control.
|
|
459
|
+
|
|
460
|
+
Callers with a buffer and no file — an inline `{% graphql res %}…{% endgraphql %}` body
|
|
461
|
+
— call `parseGraphql` directly. There is no cache inside it: the `AppFile` is the cache,
|
|
462
|
+
and a second content-keyed one behind it would be a different answer to what a file's
|
|
463
|
+
parse is.
|
|
464
|
+
|
|
465
|
+
`inferShapeFromGraphQL(node, schema?, args?)` and `ShapeAnalyzerDeps.readGraphQL` now
|
|
466
|
+
take and return the parsed node instead of a string.
|
|
467
|
+
|
|
468
|
+
The language server's `TypeSystem` takes an optional App resolver (`CompletionsProvider`
|
|
469
|
+
passes `DocumentManager.appModel`) and reads through the `AppFile` its own diagnostics
|
|
470
|
+
already parsed — for `.graphql` documents and, by the same `readLiquidFile`, for the
|
|
471
|
+
LIQUID PARTIALS behind `{% function %}`, which were read and re-parsed by BOTH the shape
|
|
472
|
+
analyzer's `readPartial` and `inferFunctionReturnType`, once per call site, per request.
|
|
473
|
+
So completion and hover cost a lookup instead of a read and a parse, and cannot disagree
|
|
474
|
+
with the diagnostic beside them about what a partial says. A host with no app (the hover
|
|
475
|
+
provider builds a type system without even a filesystem) still reads and parses, but once
|
|
476
|
+
per symbols table now rather than once per call site.
|
|
477
|
+
|
|
478
|
+
Pinned by `graphql-parse-once.spec.ts`, whose fixture makes the app's parse
|
|
479
|
+
unreproducible — the source carries a token that is not GraphQL and the injected parser
|
|
480
|
+
strips it — so a consumer that parses the file, the buffer or `ast.content` again is
|
|
481
|
+
visible in the offenses, not merely in a counter. The language server's path is pinned the
|
|
482
|
+
same way: its fixture puts a document in the App that the filesystem does not have, so a
|
|
483
|
+
read that went to disk infers the wrong shape. `platformos-common`'s
|
|
484
|
+
`package-boundaries.spec.ts` now states the invariant it always meant: no workspace
|
|
485
|
+
parser package and no Node import, rather than "no parser" — `js-yaml` was already there
|
|
486
|
+
for the same reason `graphql` is now.
|
|
487
|
+
|
|
488
|
+
- Updated dependencies [a8f4da9]
|
|
489
|
+
- Updated dependencies [a8f4da9]
|
|
490
|
+
- Updated dependencies [cf80cfa]
|
|
491
|
+
- Updated dependencies [8f1beea]
|
|
492
|
+
- Updated dependencies [cf80cfa]
|
|
493
|
+
- Updated dependencies [cf80cfa]
|
|
494
|
+
- Updated dependencies [7e7f1cd]
|
|
495
|
+
- Updated dependencies [a8f4da9]
|
|
496
|
+
- Updated dependencies [4567a07]
|
|
497
|
+
- Updated dependencies [a8f4da9]
|
|
498
|
+
- Updated dependencies [cf80cfa]
|
|
499
|
+
- Updated dependencies [f15573d]
|
|
500
|
+
- Updated dependencies [cf80cfa]
|
|
501
|
+
- Updated dependencies [d7374a8]
|
|
502
|
+
- Updated dependencies [cf80cfa]
|
|
503
|
+
- Updated dependencies [cf80cfa]
|
|
504
|
+
- Updated dependencies [280a66f]
|
|
505
|
+
- Updated dependencies [e3a7fb0]
|
|
506
|
+
- Updated dependencies [cf80cfa]
|
|
507
|
+
- Updated dependencies [a8f4da9]
|
|
508
|
+
- Updated dependencies [280a66f]
|
|
509
|
+
- Updated dependencies [4b6e0aa]
|
|
510
|
+
- Updated dependencies [c0907ab]
|
|
511
|
+
- Updated dependencies [f15573d]
|
|
512
|
+
- Updated dependencies [cf80cfa]
|
|
513
|
+
- Updated dependencies [cf80cfa]
|
|
514
|
+
- Updated dependencies [cf80cfa]
|
|
515
|
+
- @platformos/platformos-common@0.1.0
|
|
516
|
+
- @platformos/platformos-check-common@1.0.0
|
|
517
|
+
- @platformos/liquid-html-parser@0.1.0
|
|
518
|
+
|
|
3
519
|
## 0.0.19
|
|
4
520
|
|
|
5
521
|
### Patch Changes
|
package/bin/platformos-graph
CHANGED
|
@@ -1,107 +1,47 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
const {
|
|
4
|
-
const {
|
|
5
|
-
NodeFileSystem,
|
|
6
|
-
memoize,
|
|
7
|
-
toSchema,
|
|
8
|
-
path: pathUtils,
|
|
9
|
-
recursiveReadDirectory,
|
|
10
|
-
} = require('@platformos/theme-check-node');
|
|
11
|
-
const path = require('path');
|
|
12
|
-
const { URI } = require('vscode-uri');
|
|
3
|
+
const { buildSerializedGraph, buildSerializedFileDependencies } = require('../dist/cli');
|
|
13
4
|
|
|
14
5
|
const usage = `
|
|
15
6
|
Usage:
|
|
16
|
-
|
|
7
|
+
platformos-graph <path-to-app-directory> [file]
|
|
17
8
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
9
|
+
Without [file], prints the whole app graph (nodes + edges).
|
|
10
|
+
With [file], prints that single file's dependencies and references.
|
|
11
|
+
A relative [file] is resolved against the project directory.
|
|
21
12
|
|
|
22
|
-
|
|
13
|
+
Examples:
|
|
14
|
+
platformos-graph ./app > graph.json
|
|
15
|
+
platformos-graph ./app app/views/partials/header.liquid
|
|
16
|
+
`;
|
|
23
17
|
|
|
24
|
-
|
|
25
|
-
* Outputs the JSON representing the theme graph for a Shopify theme directory.
|
|
26
|
-
*
|
|
27
|
-
* @param {string} root
|
|
28
|
-
*/
|
|
29
|
-
async function main(root) {
|
|
18
|
+
async function main(root, file) {
|
|
30
19
|
if (!root) {
|
|
31
20
|
console.error(usage);
|
|
32
21
|
process.exit(1);
|
|
33
22
|
}
|
|
34
23
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const getSourceCode = makeGetSourceCode();
|
|
40
|
-
|
|
41
|
-
// Preload files to get a sense of what is slow
|
|
42
|
-
const fs = NodeFileSystem;
|
|
43
|
-
const rootUri = URI.file(root).toString(true);
|
|
44
|
-
const filesToPreload = await recursiveReadDirectory(fs, rootUri, ([uri]) =>
|
|
45
|
-
['.json', '.liquid'].some((ext) => uri.endsWith(ext)),
|
|
46
|
-
);
|
|
47
|
-
await bench('Preloading files', async () => {
|
|
48
|
-
await Promise.all(
|
|
49
|
-
filesToPreload.map(async (uri) => {
|
|
50
|
-
await getSourceCode(uri);
|
|
51
|
-
}),
|
|
52
|
-
);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
/** @type {import('@platformos/theme-graph').WebComponentMap} */
|
|
56
|
-
const webComponentDefs = new Map();
|
|
57
|
-
|
|
58
|
-
/** @type {import('@platformos/theme-graph').Dependencies} */
|
|
59
|
-
const dependencies = {
|
|
60
|
-
fs,
|
|
61
|
-
// @ts-ignore
|
|
62
|
-
getSectionSchema: memoize(async (name) => {
|
|
63
|
-
const uri = pathUtils.join(rootUri, 'sections', `${name}.liquid`);
|
|
64
|
-
const sourceCode = await getSourceCode(uri);
|
|
65
|
-
return toSchema('theme', uri, /** @type {any} */ (sourceCode), async () => true);
|
|
66
|
-
}, identity),
|
|
67
|
-
// @ts-ignore
|
|
68
|
-
getBlockSchema: memoize(async (name) => {
|
|
69
|
-
const uri = pathUtils.join(rootUri, 'blocks', `${name}.liquid`);
|
|
70
|
-
const sourceCode = await getSourceCode(uri);
|
|
71
|
-
return toSchema('theme', uri, /** @type {any} */ (sourceCode), async () => true);
|
|
72
|
-
}, identity),
|
|
73
|
-
getSourceCode,
|
|
74
|
-
getWebComponentDefinitionReference: (customElementName) =>
|
|
75
|
-
webComponentDefs.get(customElementName),
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
// Build graph
|
|
79
|
-
const graph = await bench('Build graph', () => buildThemeGraph(rootUri, dependencies));
|
|
80
|
-
|
|
81
|
-
// Serialize into JSON
|
|
82
|
-
const serializedGraph = serializeThemeGraph(graph);
|
|
83
|
-
|
|
84
|
-
// Print result to STDOUT
|
|
85
|
-
console.log(JSON.stringify(serializedGraph));
|
|
86
|
-
}
|
|
24
|
+
const result = file
|
|
25
|
+
? await bench('Build graph', () => buildSerializedFileDependencies(root, file))
|
|
26
|
+
: await bench('Build graph', () => buildSerializedGraph(root));
|
|
87
27
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const source = await NodeFileSystem.readFile(uri);
|
|
91
|
-
return await toSourceCode(URI.file(uri).toString(), source);
|
|
92
|
-
}, identity);
|
|
28
|
+
// Print result to STDOUT (diagnostics go to STDERR), so it can be piped.
|
|
29
|
+
console.log(JSON.stringify(result));
|
|
93
30
|
}
|
|
94
31
|
|
|
95
32
|
/**
|
|
96
33
|
* @param {string} name
|
|
97
|
-
* @param {(
|
|
34
|
+
* @param {() => Promise<any>} fn
|
|
98
35
|
*/
|
|
99
36
|
async function bench(name, fn) {
|
|
100
37
|
const start = performance.now();
|
|
101
38
|
const result = await fn();
|
|
102
39
|
const end = performance.now();
|
|
103
|
-
console.error(`${name} took ${end - start}ms`);
|
|
40
|
+
console.error(`${name} took ${(end - start).toFixed(1)}ms`);
|
|
104
41
|
return result;
|
|
105
42
|
}
|
|
106
43
|
|
|
107
|
-
main(process.argv[2])
|
|
44
|
+
main(process.argv[2], process.argv[3]).catch((error) => {
|
|
45
|
+
console.error(error);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|