@motionstudies/data 0.1.0-alpha.32 → 0.1.0-alpha.34
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/edition-boundary.d.mts +9 -0
- package/edition-boundary.mjs +48 -0
- package/module-references.d.mts +1 -0
- package/module-references.mjs +31 -0
- package/package.json +12 -3
package/README.md
CHANGED
|
@@ -25,6 +25,28 @@ Build release candidates with `npm run build:packages`. Distribution manifests a
|
|
|
25
25
|
|
|
26
26
|
Source workspace manifests always stay private. `npm run check:release` builds public candidates, tests their packed consumer and records the tested tarball hashes; `npm run release:dry-run` inspects the publication without writing to npm. The manual main-branch `release.yml` workflow publishes those same tarballs with public access and provenance. See [release instructions](https://github.com/emmettl/motionstudies/blob/main/docs/RELEASING.md) for bootstrap-token and trusted-publisher setup. All four shared packages are MIT-licensed; each distribution includes `LICENSE`.
|
|
27
27
|
|
|
28
|
+
## Edition maintenance and layout observation
|
|
29
|
+
|
|
30
|
+
These exports are introduced in coordinated release `0.1.0-alpha.33`; earlier releases do not expose them.
|
|
31
|
+
|
|
32
|
+
`checkEditionBoundary` from `@motionstudies/data/edition-boundary` verifies the four
|
|
33
|
+
shared registry pins, lockfile metadata, installed package identity, public exports
|
|
34
|
+
and declared source imports. Pass `directories` for the edition's required source
|
|
35
|
+
and worker roots, and `additionalPackages` for other packages needing the same
|
|
36
|
+
exact-pin/public-export checks. `root` defaults to the working directory. The
|
|
37
|
+
checker performs no installation, mutation or network access. It rejects missing
|
|
38
|
+
configured directories, workspaces, shared source copies and installed links.
|
|
39
|
+
`moduleReferences` from `@motionstudies/data/module-references` supplies the same
|
|
40
|
+
AST-based import analysis to local policy tests; it reports nonliteral imports as
|
|
41
|
+
unverifiable. Dataset, worker and publication policy remain edition-owned.
|
|
42
|
+
|
|
43
|
+
`observeLayout` from `@motionstudies/web/observe-layout` measures once immediately,
|
|
44
|
+
then batches resize notifications into one animation frame. Call its returned
|
|
45
|
+
cleanup before removing the observed elements; it disconnects the observer and
|
|
46
|
+
cancels pending work. Without ResizeObserver it still performs the initial
|
|
47
|
+
measurement. The caller owns the elements, geometry, CSS variables and their
|
|
48
|
+
cleanup; the helper does not choose clearance values or depend on React.
|
|
49
|
+
|
|
28
50
|
## Timetable patterns, station calls and observation windows
|
|
29
51
|
|
|
30
52
|
`@motionstudies/core/domain/network-patterns` provides a transport-neutral repeated-schedule codec. `@motionstudies/web/use-pattern-network-day` lazily decodes current/adjacent chunks after verifying byte length and SHA-256; it exposes retry and drops old-source state when the manifest URL changes. Supply stable options and a stable asset resolver. Format/category options support existing edition wire contracts.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface EditionBoundaryOptions {
|
|
2
|
+
/** Repository root; defaults to the working directory. */
|
|
3
|
+
root?: string
|
|
4
|
+
/** Required source directories, relative to root. Defaults to src and scripts. */
|
|
5
|
+
directories?: readonly string[]
|
|
6
|
+
/** Extra registry packages whose exact pins and public exports must be checked. */
|
|
7
|
+
additionalPackages?: readonly string[]
|
|
8
|
+
}
|
|
9
|
+
export function checkEditionBoundary(options?: EditionBoundaryOptions): Promise<void>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { access, lstat, readFile, readdir, realpath } from 'node:fs/promises'
|
|
2
|
+
import { builtinModules } from 'node:module'
|
|
3
|
+
import { dirname, extname, relative, resolve, sep } from 'node:path'
|
|
4
|
+
import { moduleReferences } from './module-references.mjs'
|
|
5
|
+
|
|
6
|
+
export async function checkEditionBoundary({ root = '.', directories = ['src', 'scripts'], additionalPackages = [] } = {}) {
|
|
7
|
+
root = resolve(root)
|
|
8
|
+
const manifest = JSON.parse(await readFile(resolve(root, 'package.json'), 'utf8'))
|
|
9
|
+
const lock = JSON.parse(await readFile(resolve(root, 'package-lock.json'), 'utf8'))
|
|
10
|
+
const declared = { ...manifest.dependencies, ...manifest.devDependencies }
|
|
11
|
+
if (manifest.workspaces) throw new Error('An edition must consume installed packages, not workspaces')
|
|
12
|
+
try { await access(resolve(root, 'packages')); throw new Error('Shared package source belongs in Motion Studies') }
|
|
13
|
+
catch (error) { if (error.code !== 'ENOENT') throw error }
|
|
14
|
+
const exported = new Map()
|
|
15
|
+
for (const name of [...['core', 'data', 'three', 'web'].map(name => `@motionstudies/${name}`), ...additionalPackages]) {
|
|
16
|
+
const version = declared[name]
|
|
17
|
+
if (!/^\d+\.\d+\.\d+(?:-(?:alpha|beta|rc)\.\d+)?$/.test(version ?? '')) throw new Error(`Expected an exact registry version: ${name}`)
|
|
18
|
+
const locked = lock.packages[`node_modules/${name}`]
|
|
19
|
+
if (locked?.version !== version || !locked.resolved?.startsWith('https://registry.npmjs.org/') || !locked.integrity?.startsWith('sha512-') || locked.link) throw new Error(`Registry lock mismatch: ${name}`)
|
|
20
|
+
const installed = resolve(root, 'node_modules', name)
|
|
21
|
+
if ((await lstat(installed)).isSymbolicLink() || !(await realpath(installed)).startsWith(`${await realpath(resolve(root, 'node_modules'))}${sep}`)) throw new Error(`Shared source link: ${name}`)
|
|
22
|
+
const pkg = JSON.parse(await readFile(`${installed}/package.json`, 'utf8'))
|
|
23
|
+
if (pkg.version !== version || pkg.private === true || (name.startsWith('@motionstudies/') && pkg.private !== false)) throw new Error(`Installed release mismatch: ${name}`)
|
|
24
|
+
exported.set(name, pkg.exports)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function visit(directory) {
|
|
28
|
+
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
|
29
|
+
const file = resolve(directory, entry.name)
|
|
30
|
+
if (entry.isDirectory()) { await visit(file); continue }
|
|
31
|
+
if (!/\.(?:[cm]?[jt]sx?|css)$/.test(file)) continue
|
|
32
|
+
for (const reference of moduleReferences(await readFile(file, 'utf8'), file)) {
|
|
33
|
+
if (reference === undefined) throw new Error(`Unverifiable module reference in ${file}`)
|
|
34
|
+
if (/^https?:/.test(reference) && extname(file) === '.css') continue
|
|
35
|
+
if (reference.startsWith('.')) {
|
|
36
|
+
const target = relative(root, resolve(dirname(file), reference))
|
|
37
|
+
if (target === '..' || target.startsWith(`..${sep}`)) throw new Error(`${file} imports outside this repository`)
|
|
38
|
+
} else {
|
|
39
|
+
if (reference.startsWith('node:') || builtinModules.includes(reference)) continue
|
|
40
|
+
const name = reference.startsWith('@') ? reference.split('/').slice(0, 2).join('/') : reference.split('/')[0]
|
|
41
|
+
if (!declared[name]) throw new Error(`Undeclared dependency ${name} in ${file}`)
|
|
42
|
+
if (exported.has(name) && !Object.hasOwn(exported.get(name), `.${reference.slice(name.length)}`)) throw new Error(`Private package import: ${reference}`)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
for (const directory of directories) await visit(resolve(root, directory))
|
|
48
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function moduleReferences(source: string, fileName: string): (string | undefined)[]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { extname } from 'node:path'
|
|
2
|
+
import { parse } from '@babel/parser'
|
|
3
|
+
|
|
4
|
+
/** Parse imports, re-exports, import types, side effects and dynamic imports. */
|
|
5
|
+
export function moduleReferences(source, fileName) {
|
|
6
|
+
if (extname(fileName) === '.css') {
|
|
7
|
+
return [...source.matchAll(/@import\s+(?:url\(\s*)?['"]([^'"]+)['"]/g)].map((match) => match[1])
|
|
8
|
+
}
|
|
9
|
+
const references = []
|
|
10
|
+
const ast = parse(source, {
|
|
11
|
+
sourceType: 'module',
|
|
12
|
+
plugins: [['typescript', { dts: /\.d\.[cm]?ts$/.test(fileName) }], 'jsx'],
|
|
13
|
+
createImportExpressions: true,
|
|
14
|
+
})
|
|
15
|
+
function add(node) {
|
|
16
|
+
references.push(node?.type === 'StringLiteral' ? node.value : undefined)
|
|
17
|
+
}
|
|
18
|
+
function visit(node) {
|
|
19
|
+
if (!node || typeof node !== 'object') return
|
|
20
|
+
if (['ImportDeclaration', 'ExportNamedDeclaration', 'ExportAllDeclaration'].includes(node.type) && node.source) add(node.source)
|
|
21
|
+
else if (node.type === 'ImportExpression') add(node.source)
|
|
22
|
+
else if (node.type === 'TSImportType') add(node.argument)
|
|
23
|
+
else if (node.type === 'CallExpression' && node.callee.type === 'Identifier' && node.callee.name === 'require') add(node.arguments[0])
|
|
24
|
+
for (const value of Object.values(node)) {
|
|
25
|
+
if (Array.isArray(value)) value.forEach(visit)
|
|
26
|
+
else if (value && typeof value === 'object') visit(value)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
visit(ast)
|
|
30
|
+
return references
|
|
31
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@motionstudies/data",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.34",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Offline data preparation primitives for Motion Studies.",
|
|
@@ -118,6 +118,14 @@
|
|
|
118
118
|
"./feed-expectations": {
|
|
119
119
|
"types": "./feed-expectations.d.mts",
|
|
120
120
|
"import": "./feed-expectations.mjs"
|
|
121
|
+
},
|
|
122
|
+
"./module-references": {
|
|
123
|
+
"types": "./module-references.d.mts",
|
|
124
|
+
"import": "./module-references.mjs"
|
|
125
|
+
},
|
|
126
|
+
"./edition-boundary": {
|
|
127
|
+
"types": "./edition-boundary.d.mts",
|
|
128
|
+
"import": "./edition-boundary.mjs"
|
|
121
129
|
}
|
|
122
130
|
},
|
|
123
131
|
"files": [
|
|
@@ -133,7 +141,8 @@
|
|
|
133
141
|
],
|
|
134
142
|
"sideEffects": false,
|
|
135
143
|
"dependencies": {
|
|
136
|
-
"@motionstudies/core": "0.1.0-alpha.
|
|
137
|
-
"fast-xml-parser": "5.11.2"
|
|
144
|
+
"@motionstudies/core": "0.1.0-alpha.34",
|
|
145
|
+
"fast-xml-parser": "5.11.2",
|
|
146
|
+
"@babel/parser": "7.29.9"
|
|
138
147
|
}
|
|
139
148
|
}
|