zyzz 0.0.6 → 0.0.8
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 +26 -1
- package/dist/Style.d.ts +3 -3
- package/dist/Style.d.ts.map +1 -1
- package/dist/Style.js +12 -1
- package/dist/Style.js.map +1 -1
- package/dist/babel/index.d.ts +4 -1
- package/dist/babel/index.d.ts.map +1 -1
- package/dist/babel/index.js +6 -3
- package/dist/babel/index.js.map +1 -1
- package/dist/babel/internal/Compilation.d.ts +12 -0
- package/dist/babel/internal/Compilation.d.ts.map +1 -0
- package/dist/babel/internal/Compilation.js +3 -0
- package/dist/babel/internal/Compilation.js.map +1 -0
- package/dist/compiler/Graph.d.ts +6 -1
- package/dist/compiler/Graph.d.ts.map +1 -1
- package/dist/compiler/Graph.js +160 -36
- package/dist/compiler/Graph.js.map +1 -1
- package/dist/compiler/Transform.d.ts.map +1 -1
- package/dist/compiler/Transform.js +2 -1
- package/dist/compiler/Transform.js.map +1 -1
- package/dist/compiler/internal/Stylesheets.d.ts +8 -0
- package/dist/compiler/internal/Stylesheets.d.ts.map +1 -1
- package/dist/compiler/internal/Stylesheets.js +4 -0
- package/dist/compiler/internal/Stylesheets.js.map +1 -1
- package/dist/internal/Authoring.d.ts.map +1 -1
- package/dist/internal/Authoring.js +14 -0
- package/dist/internal/Authoring.js.map +1 -1
- package/dist/internal/Binding.d.ts +7 -0
- package/dist/internal/Binding.d.ts.map +1 -1
- package/dist/internal/Binding.js.map +1 -1
- package/dist/internal/Configuration.d.ts +1 -1
- package/dist/internal/Configuration.d.ts.map +1 -1
- package/dist/internal/Configuration.js +7 -6
- package/dist/internal/Configuration.js.map +1 -1
- package/dist/internal/Theme.d.ts +1 -1
- package/dist/internal/Theme.d.ts.map +1 -1
- package/dist/internal/Theme.js.map +1 -1
- package/dist/internal/Token.d.ts +3 -1
- package/dist/internal/Token.d.ts.map +1 -1
- package/dist/internal/Token.js +28 -3
- package/dist/internal/Token.js.map +1 -1
- package/dist/internal/Value.d.ts +22 -0
- package/dist/internal/Value.d.ts.map +1 -1
- package/dist/internal/Value.js +76 -9
- package/dist/internal/Value.js.map +1 -1
- package/dist/metro/Graph.d.ts +9 -1
- package/dist/metro/Graph.d.ts.map +1 -1
- package/dist/metro/Graph.js +5 -4
- package/dist/metro/Graph.js.map +1 -1
- package/dist/metro/transformer.d.ts +0 -1
- package/dist/metro/transformer.d.ts.map +1 -1
- package/dist/metro/transformer.js +19 -1
- package/dist/metro/transformer.js.map +1 -1
- package/dist/next/index.d.ts +3 -3
- package/dist/next/index.d.ts.map +1 -1
- package/dist/next/index.js +25 -30
- package/dist/next/index.js.map +1 -1
- package/dist/next/internal/Project.d.ts +28 -0
- package/dist/next/internal/Project.d.ts.map +1 -0
- package/dist/next/internal/Project.js +295 -0
- package/dist/next/internal/Project.js.map +1 -0
- package/dist/next/loader.d.ts +5 -6
- package/dist/next/loader.d.ts.map +1 -1
- package/dist/next/loader.js +161 -247
- package/dist/next/loader.js.map +1 -1
- package/dist/node/Host.d.ts.map +1 -1
- package/dist/node/Host.js +11 -3
- package/dist/node/Host.js.map +1 -1
- package/dist/node/internal/Snapshot.d.ts +12 -0
- package/dist/node/internal/Snapshot.d.ts.map +1 -0
- package/dist/node/internal/Snapshot.js +74 -0
- package/dist/node/internal/Snapshot.js.map +1 -0
- package/dist/unplugin/index.d.ts.map +1 -1
- package/dist/unplugin/index.js +10 -5
- package/dist/unplugin/index.js.map +1 -1
- package/dist/variants.d.ts +1 -1
- package/dist/variants.d.ts.map +1 -1
- package/dist/variants.js.map +1 -1
- package/dist/vite/index.d.ts.map +1 -1
- package/dist/vite/index.js +19 -4
- package/dist/vite/index.js.map +1 -1
- package/package.json +1 -1
- package/src/Style.ts +42 -8
- package/src/babel/index.ts +10 -4
- package/src/babel/internal/Compilation.ts +13 -0
- package/src/compiler/Graph.ts +213 -47
- package/src/compiler/Transform.ts +2 -1
- package/src/compiler/internal/Stylesheets.ts +9 -0
- package/src/internal/Authoring.ts +21 -0
- package/src/internal/Binding.ts +30 -0
- package/src/internal/Configuration.ts +19 -13
- package/src/internal/Theme.ts +5 -1
- package/src/internal/Token.ts +34 -3
- package/src/internal/Value.ts +180 -10
- package/src/metro/Graph.ts +5 -3
- package/src/metro/transformer.ts +26 -1
- package/src/next/index.ts +26 -31
- package/src/next/internal/Project.ts +378 -0
- package/src/next/loader.ts +195 -315
- package/src/node/Host.ts +15 -3
- package/src/node/internal/Snapshot.ts +79 -0
- package/src/unplugin/index.ts +10 -5
- package/src/variants.ts +1 -0
- package/src/vite/index.ts +24 -4
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
/** Retains filesystem snapshots and an incremental graph within a Next.js loader worker. @module */
|
|
2
|
+
import * as Contract from '../../compiler/internal/Contract.js'
|
|
3
|
+
import * as Fs from 'node:fs/promises'
|
|
4
|
+
import * as Graph from '../../compiler/Graph.js'
|
|
5
|
+
import * as Path from 'node:path'
|
|
6
|
+
import * as Reset from '../../node/Reset.js'
|
|
7
|
+
import * as Stylesheets from '../../compiler/internal/Stylesheets.js'
|
|
8
|
+
import * as Syntax from '../../compiler/internal/Syntax.js'
|
|
9
|
+
|
|
10
|
+
/** Resolution and dependency notifications supplied by the active loader invocation. */
|
|
11
|
+
export type Context = {
|
|
12
|
+
/** Track source and contract edits. */
|
|
13
|
+
addDependency(file: string): void
|
|
14
|
+
/** Resolve with the active bundler's aliases and conditions. */
|
|
15
|
+
getResolve(
|
|
16
|
+
options: object,
|
|
17
|
+
): (
|
|
18
|
+
directory: string,
|
|
19
|
+
specifier: string,
|
|
20
|
+
callback: (error: Error | null, file?: string | false) => void,
|
|
21
|
+
) => void
|
|
22
|
+
/** Physical loader resource. */
|
|
23
|
+
resourcePath: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Creates project state owned by a loader configuration in one worker process. */
|
|
27
|
+
export function create(options: create.Options) {
|
|
28
|
+
let compiler = Graph.create()
|
|
29
|
+
const files = new Map<string, { source: string; version: string }>()
|
|
30
|
+
const libraries = new Map<
|
|
31
|
+
string,
|
|
32
|
+
{ dependencies: readonly (readonly string[])[]; source: string }
|
|
33
|
+
>()
|
|
34
|
+
const reset = options.reset ? Reset.read() : undefined
|
|
35
|
+
const syntax = new Map<
|
|
36
|
+
string,
|
|
37
|
+
{ parsed: ReturnType<typeof Syntax.parse>; source: string }
|
|
38
|
+
>()
|
|
39
|
+
let pending = Promise.resolve()
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
/** Compiles a fresh snapshot while retaining unchanged syntax and graph results. */
|
|
43
|
+
compile(context: Context, source: string) {
|
|
44
|
+
// Loader calls may overlap, but only one immutable snapshot updates the compiler at a time.
|
|
45
|
+
const result = pending.then(async () => {
|
|
46
|
+
const tracked = new Set<string>()
|
|
47
|
+
try {
|
|
48
|
+
return await compile(context, source, tracked)
|
|
49
|
+
} catch (error) {
|
|
50
|
+
// Failed resolution or extraction may leave a partial filesystem snapshot.
|
|
51
|
+
compiler = Graph.create()
|
|
52
|
+
files.clear()
|
|
53
|
+
libraries.clear()
|
|
54
|
+
syntax.clear()
|
|
55
|
+
for (const file of tracked) context.addDependency(file)
|
|
56
|
+
throw error
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
// A failed generation must not prevent recovery after the source is corrected.
|
|
61
|
+
pending = result.then(
|
|
62
|
+
() => {},
|
|
63
|
+
() => {},
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
return result
|
|
67
|
+
},
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function compile(
|
|
71
|
+
context: Context,
|
|
72
|
+
source: string,
|
|
73
|
+
tracked: Set<string>,
|
|
74
|
+
) {
|
|
75
|
+
const root = options.root
|
|
76
|
+
const runtime = new Set<string>()
|
|
77
|
+
function track(file: string) {
|
|
78
|
+
if (tracked.has(file)) return
|
|
79
|
+
tracked.add(file)
|
|
80
|
+
}
|
|
81
|
+
const programs = new Map<string, ReturnType<typeof Syntax.parse>>()
|
|
82
|
+
const reads = new Map<string, Promise<string>>()
|
|
83
|
+
function read(file: string) {
|
|
84
|
+
let pending = reads.get(file)
|
|
85
|
+
if (!pending) {
|
|
86
|
+
pending = load(file)
|
|
87
|
+
reads.set(file, pending)
|
|
88
|
+
}
|
|
89
|
+
return pending
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function load(file: string) {
|
|
93
|
+
const version = await stamp(file)
|
|
94
|
+
let entry = files.get(file)
|
|
95
|
+
if (entry?.version !== version) {
|
|
96
|
+
entry = { source: await Fs.readFile(file, 'utf8'), version }
|
|
97
|
+
}
|
|
98
|
+
files.delete(file)
|
|
99
|
+
files.set(file, entry)
|
|
100
|
+
if (files.size > 256) files.delete(files.keys().next().value!)
|
|
101
|
+
return entry.source
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const modules: Record<string, string> = Object.create(null)
|
|
105
|
+
const imports: Record<
|
|
106
|
+
string,
|
|
107
|
+
Record<string, string | null>
|
|
108
|
+
> = Object.create(null)
|
|
109
|
+
const contracts: Record<string, string> = Object.create(null)
|
|
110
|
+
const resolver = context.getResolve({})
|
|
111
|
+
const resolutions = new Map<string, Promise<string | false | undefined>>()
|
|
112
|
+
function resolve(directory: string, specifier: string) {
|
|
113
|
+
const key = JSON.stringify([directory, specifier])
|
|
114
|
+
let pending = resolutions.get(key)
|
|
115
|
+
if (!pending) {
|
|
116
|
+
pending = new Promise<string | false | undefined>((accept, reject) => {
|
|
117
|
+
resolver(directory, specifier, (error, value) =>
|
|
118
|
+
error ? reject(error) : accept(value),
|
|
119
|
+
)
|
|
120
|
+
})
|
|
121
|
+
resolutions.set(key, pending)
|
|
122
|
+
}
|
|
123
|
+
return pending
|
|
124
|
+
}
|
|
125
|
+
const id = (file: string) =>
|
|
126
|
+
`app/${Path.relative(root, file).split(Path.sep).join('/')}`
|
|
127
|
+
const eligible = (file: string) =>
|
|
128
|
+
/\.[cm]?[jt]sx?$/.test(file) &&
|
|
129
|
+
!/\.(?:d|test|test-d|bench|bench-d)\.[cm]?[jt]sx?$/.test(file) &&
|
|
130
|
+
!Path.relative(root, file)
|
|
131
|
+
.split(Path.sep)
|
|
132
|
+
.some(
|
|
133
|
+
(part) =>
|
|
134
|
+
part === '..' || part === 'node_modules' || part.startsWith('.'),
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
function parse(name: string, source: string) {
|
|
138
|
+
let entry = syntax.get(name)
|
|
139
|
+
if (entry?.source !== source)
|
|
140
|
+
entry = { source, parsed: Syntax.parse({ moduleId: name, source }) }
|
|
141
|
+
syntax.delete(name)
|
|
142
|
+
syntax.set(name, entry)
|
|
143
|
+
if (syntax.size > 256) syntax.delete(syntax.keys().next().value!)
|
|
144
|
+
return entry.parsed
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function visit(file: string, text?: string): Promise<void> {
|
|
148
|
+
const name = id(file)
|
|
149
|
+
if (Object.hasOwn(modules, name)) return
|
|
150
|
+
|
|
151
|
+
track(file)
|
|
152
|
+
modules[name] = text ?? (await read(file))
|
|
153
|
+
const links: Record<string, string | null> = Object.create(null)
|
|
154
|
+
imports[name] = links
|
|
155
|
+
const parsed = parse(name, modules[name]!)
|
|
156
|
+
programs.set(name, parsed)
|
|
157
|
+
|
|
158
|
+
const resolutions = await Promise.all(
|
|
159
|
+
parsed.program.body.map(async (node) => {
|
|
160
|
+
if (
|
|
161
|
+
(node.type !== 'ImportDeclaration' &&
|
|
162
|
+
node.type !== 'ExportNamedDeclaration' &&
|
|
163
|
+
node.type !== 'ExportAllDeclaration') ||
|
|
164
|
+
!node.source
|
|
165
|
+
)
|
|
166
|
+
return undefined
|
|
167
|
+
if (
|
|
168
|
+
node.type === 'ImportDeclaration'
|
|
169
|
+
? node.importKind === 'type'
|
|
170
|
+
: node.exportKind === 'type'
|
|
171
|
+
)
|
|
172
|
+
return undefined
|
|
173
|
+
|
|
174
|
+
const specifier = node.source.value
|
|
175
|
+
links[specifier] = null
|
|
176
|
+
// Next owns this virtual module, which has no physical stylesheet contract.
|
|
177
|
+
if (specifier === 'next/root-params') return undefined
|
|
178
|
+
if (
|
|
179
|
+
specifier === 'zyzz' ||
|
|
180
|
+
(specifier.startsWith('zyzz/') && specifier !== 'zyzz/default') ||
|
|
181
|
+
specifier.startsWith('node:')
|
|
182
|
+
)
|
|
183
|
+
return undefined
|
|
184
|
+
|
|
185
|
+
const resolved = await resolve(Path.dirname(file), specifier)
|
|
186
|
+
return { node, specifier, resolved }
|
|
187
|
+
}),
|
|
188
|
+
)
|
|
189
|
+
for (const resolution of resolutions) {
|
|
190
|
+
if (!resolution) continue
|
|
191
|
+
const { node, specifier, resolved } = resolution
|
|
192
|
+
if (!resolved || !/\.[cm]?[jt]sx?$/.test(resolved)) continue
|
|
193
|
+
if (eligible(resolved)) {
|
|
194
|
+
if (node.type === 'ImportDeclaration' && node.specifiers.length) {
|
|
195
|
+
track(resolved)
|
|
196
|
+
const parsed = parse(id(resolved), await read(resolved))
|
|
197
|
+
const functions = new Set<string>()
|
|
198
|
+
let defaultBinding: string | undefined
|
|
199
|
+
for (const statement of parsed.program.body) {
|
|
200
|
+
if (
|
|
201
|
+
statement.type === 'ExportDefaultDeclaration' &&
|
|
202
|
+
[
|
|
203
|
+
'FunctionDeclaration',
|
|
204
|
+
'ClassDeclaration',
|
|
205
|
+
'ArrowFunctionExpression',
|
|
206
|
+
'FunctionExpression',
|
|
207
|
+
].includes(statement.declaration.type)
|
|
208
|
+
) {
|
|
209
|
+
functions.add('default')
|
|
210
|
+
if (
|
|
211
|
+
statement.declaration.type === 'FunctionDeclaration' ||
|
|
212
|
+
statement.declaration.type === 'ClassDeclaration'
|
|
213
|
+
)
|
|
214
|
+
defaultBinding = statement.declaration.id?.name
|
|
215
|
+
}
|
|
216
|
+
if (statement.type !== 'ExportNamedDeclaration') continue
|
|
217
|
+
const declaration = statement.declaration
|
|
218
|
+
if (
|
|
219
|
+
(declaration?.type === 'FunctionDeclaration' ||
|
|
220
|
+
declaration?.type === 'ClassDeclaration') &&
|
|
221
|
+
declaration.id
|
|
222
|
+
)
|
|
223
|
+
functions.add(declaration.id.name)
|
|
224
|
+
if (declaration?.type === 'VariableDeclaration')
|
|
225
|
+
for (const item of declaration.declarations) {
|
|
226
|
+
if (
|
|
227
|
+
item.id.type === 'Identifier' &&
|
|
228
|
+
item.init &&
|
|
229
|
+
[
|
|
230
|
+
'ArrowFunctionExpression',
|
|
231
|
+
'FunctionExpression',
|
|
232
|
+
'ClassExpression',
|
|
233
|
+
].includes(item.init.type)
|
|
234
|
+
)
|
|
235
|
+
functions.add(item.id.name)
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
for (const statement of parsed.program.body) {
|
|
239
|
+
const declaration =
|
|
240
|
+
statement.type === 'ExportNamedDeclaration'
|
|
241
|
+
? statement.declaration
|
|
242
|
+
: statement
|
|
243
|
+
if (
|
|
244
|
+
declaration?.type === 'TSModuleDeclaration' &&
|
|
245
|
+
declaration.id.type === 'Identifier'
|
|
246
|
+
) {
|
|
247
|
+
functions.delete(declaration.id.name)
|
|
248
|
+
if (declaration.id.name === defaultBinding)
|
|
249
|
+
functions.delete('default')
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (
|
|
253
|
+
!parsed.errors.length &&
|
|
254
|
+
node.specifiers.every((specifier) =>
|
|
255
|
+
specifier.type === 'ImportDefaultSpecifier'
|
|
256
|
+
? functions.has('default')
|
|
257
|
+
: specifier.type === 'ImportSpecifier' &&
|
|
258
|
+
(specifier.importKind === 'type' ||
|
|
259
|
+
functions.has(
|
|
260
|
+
specifier.imported.type === 'Identifier'
|
|
261
|
+
? specifier.imported.name
|
|
262
|
+
: specifier.imported.value,
|
|
263
|
+
)),
|
|
264
|
+
)
|
|
265
|
+
) {
|
|
266
|
+
runtime.add(resolved)
|
|
267
|
+
track(resolved)
|
|
268
|
+
continue
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
links[specifier] = id(resolved)
|
|
272
|
+
await visit(resolved)
|
|
273
|
+
continue
|
|
274
|
+
}
|
|
275
|
+
const sidecar = `${resolved}.zyzz.json`
|
|
276
|
+
try {
|
|
277
|
+
contracts[resolved] = await read(sidecar)
|
|
278
|
+
track(sidecar)
|
|
279
|
+
links[specifier] = resolved
|
|
280
|
+
} catch (error) {
|
|
281
|
+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
await visit(context.resourcePath, source)
|
|
287
|
+
const loaded = new Set<string>()
|
|
288
|
+
async function dependencies(file: string): Promise<void> {
|
|
289
|
+
if (loaded.has(file)) return
|
|
290
|
+
loaded.add(file)
|
|
291
|
+
|
|
292
|
+
let library = libraries.get(file)
|
|
293
|
+
if (!library || library.source !== contracts[file]) {
|
|
294
|
+
library = {
|
|
295
|
+
source: contracts[file]!,
|
|
296
|
+
dependencies: Contract.read(
|
|
297
|
+
contracts[file]!,
|
|
298
|
+
new Map(),
|
|
299
|
+
file,
|
|
300
|
+
).stylesheets.map((section) => section.dependency ?? []),
|
|
301
|
+
}
|
|
302
|
+
libraries.set(file, library)
|
|
303
|
+
}
|
|
304
|
+
for (const dependency of library.dependencies) {
|
|
305
|
+
let owner = file
|
|
306
|
+
for (const specifier of dependency) {
|
|
307
|
+
const target = await resolve(Path.dirname(owner), specifier)
|
|
308
|
+
if (!target || !Path.isAbsolute(target))
|
|
309
|
+
throw new Error('Unable to resolve packed stylesheet dependency.')
|
|
310
|
+
|
|
311
|
+
const links = (imports[owner] ??= Object.create(null))
|
|
312
|
+
links[specifier] = target
|
|
313
|
+
if (!Object.hasOwn(contracts, target)) {
|
|
314
|
+
const sidecar = `${target}.zyzz.json`
|
|
315
|
+
contracts[target] = await read(sidecar)
|
|
316
|
+
track(sidecar)
|
|
317
|
+
}
|
|
318
|
+
await dependencies(target)
|
|
319
|
+
owner = target
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
for (const file of Object.keys(contracts)) await dependencies(file)
|
|
324
|
+
|
|
325
|
+
const graph = compiler.compile({
|
|
326
|
+
[Syntax.cache]: programs,
|
|
327
|
+
[Stylesheets.entry]: id(context.resourcePath),
|
|
328
|
+
contracts,
|
|
329
|
+
development: options.development,
|
|
330
|
+
imports,
|
|
331
|
+
modules,
|
|
332
|
+
reset,
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
const entry = id(context.resourcePath)
|
|
336
|
+
const output = graph.modules[entry]!
|
|
337
|
+
// Unchanged runtime modules rely on Next's import graph, not transitive style inputs.
|
|
338
|
+
if (output.code === source && !output.css) {
|
|
339
|
+
context.addDependency(context.resourcePath)
|
|
340
|
+
for (const file of runtime) context.addDependency(file)
|
|
341
|
+
for (const file of tracked)
|
|
342
|
+
if (file.endsWith('.zyzz.json')) context.addDependency(file)
|
|
343
|
+
for (const target of Object.values(imports[entry] ?? {})) {
|
|
344
|
+
if (!target) continue
|
|
345
|
+
context.addDependency(
|
|
346
|
+
target.startsWith('app/')
|
|
347
|
+
? Path.join(root, target.slice(4))
|
|
348
|
+
: `${target}.zyzz.json`,
|
|
349
|
+
)
|
|
350
|
+
}
|
|
351
|
+
} else {
|
|
352
|
+
for (const file of tracked) context.addDependency(file)
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
for (const file of libraries.keys())
|
|
356
|
+
if (!Object.hasOwn(contracts, file)) libraries.delete(file)
|
|
357
|
+
|
|
358
|
+
return graph
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/** Project cache settings. */
|
|
363
|
+
export declare namespace create {
|
|
364
|
+
/** Isolates imported source graphs and shared stylesheet options. */
|
|
365
|
+
type Options = {
|
|
366
|
+
/** Keep development class names stable across value edits. */
|
|
367
|
+
readonly development?: boolean | undefined
|
|
368
|
+
/** Include the packaged reset in shared CSS. */
|
|
369
|
+
readonly reset?: boolean | undefined
|
|
370
|
+
/** Absolute application directory. */
|
|
371
|
+
readonly root: string
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
async function stamp(file: string) {
|
|
376
|
+
const stat = await Fs.stat(file, { bigint: true })
|
|
377
|
+
return `${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}`
|
|
378
|
+
}
|