@marcusrbrown/infra 0.9.13 → 0.9.14
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/package.json
CHANGED
|
@@ -181,23 +181,73 @@ describe('keeweb status helpers', () => {
|
|
|
181
181
|
})
|
|
182
182
|
|
|
183
183
|
describe('checkContentHash', () => {
|
|
184
|
-
it('
|
|
185
|
-
//
|
|
186
|
-
|
|
187
|
-
//
|
|
188
|
-
|
|
189
|
-
fileSpy = spyOn(Bun, 'file').mockImplementation(
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
184
|
+
it('does not emit a path-noisy warning when running from a packaged install (bunx)', async () => {
|
|
185
|
+
// Simulate packaged layout: moduleDir is inside node_modules (bunx temp install)
|
|
186
|
+
const packagedDir = '/private/var/folders/xx/T/bunx-1234/node_modules/@marcusrbrown/infra/src/commands/keeweb'
|
|
187
|
+
// Bun.file should not be called for the dist path in packaged mode,
|
|
188
|
+
// but if it is, return false to avoid false positives
|
|
189
|
+
fileSpy = spyOn(Bun, 'file').mockImplementation(
|
|
190
|
+
() =>
|
|
191
|
+
({
|
|
192
|
+
exists: async () => false,
|
|
193
|
+
}) as ReturnType<typeof Bun.file>,
|
|
194
|
+
)
|
|
193
195
|
|
|
194
|
-
await checkContentHash(false)
|
|
196
|
+
const result = await checkContentHash(false, packagedDir)
|
|
197
|
+
|
|
198
|
+
// Must NOT contain the bogus node_modules path in the summary
|
|
199
|
+
expect(result.summary).not.toContain('node_modules/apps/keeweb')
|
|
200
|
+
expect(result.summary).not.toContain('/private/var/folders')
|
|
201
|
+
// Must clearly communicate that local dist is unavailable from packaged CLI
|
|
202
|
+
expect(result.summary.toLowerCase()).toMatch(/not available|unavailable|packaged|source checkout/)
|
|
203
|
+
// Level should be info (neutral — not a warning for packaged installs)
|
|
204
|
+
expect(result.level).toBe('info')
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
it('does not emit path-noisy warning for any node_modules install path', async () => {
|
|
208
|
+
// Simulate npm/npx global install: node_modules path but not bunx temp
|
|
209
|
+
const npmInstallDir = '/usr/local/lib/node_modules/@marcusrbrown/infra/src/commands/keeweb'
|
|
210
|
+
fileSpy = spyOn(Bun, 'file').mockImplementation(
|
|
211
|
+
() =>
|
|
212
|
+
({
|
|
213
|
+
exists: async () => false,
|
|
214
|
+
}) as ReturnType<typeof Bun.file>,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
const result = await checkContentHash(false, npmInstallDir)
|
|
218
|
+
|
|
219
|
+
expect(result.summary).not.toContain('node_modules/apps/keeweb')
|
|
220
|
+
expect(result.summary.toLowerCase()).toMatch(/not available|unavailable|packaged|source checkout/)
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
it('does not emit path-noisy warning when running from an unpacked tarball outside node_modules', async () => {
|
|
224
|
+
// Simulate unpacked tarball layout: e.g. `npm pack` + extract to /tmp/pkg/package/
|
|
225
|
+
// The path does NOT contain node_modules, but also has no repo apps/keeweb/src tree.
|
|
226
|
+
const unpackedTarballDir = '/tmp/pkg/package/src/commands/keeweb'
|
|
227
|
+
fileSpy = spyOn(Bun, 'file').mockImplementation(
|
|
228
|
+
() =>
|
|
229
|
+
({
|
|
230
|
+
exists: async () => false,
|
|
231
|
+
}) as ReturnType<typeof Bun.file>,
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
const result = await checkContentHash(false, unpackedTarballDir)
|
|
195
235
|
|
|
196
|
-
|
|
197
|
-
expect(
|
|
236
|
+
// Must NOT contain the raw temp path in the summary
|
|
237
|
+
expect(result.summary).not.toContain('/tmp/pkg/package')
|
|
238
|
+
// Must NOT contain the path-noisy "Local dist file not found: ..." message
|
|
239
|
+
expect(result.summary).not.toMatch(/Local dist file not found/)
|
|
240
|
+
// Must clearly communicate that local dist is unavailable from packaged CLI
|
|
241
|
+
expect(result.summary.toLowerCase()).toMatch(/not available|unavailable|packaged|source checkout/)
|
|
242
|
+
// Level should be info (neutral — not a warning for packaged installs)
|
|
243
|
+
expect(result.level).toBe('info')
|
|
198
244
|
})
|
|
199
245
|
|
|
200
|
-
it('
|
|
246
|
+
it('packaged install content hash level is info, not warning', async () => {
|
|
247
|
+
// Packaged/non-source installs should not degrade status with a warning.
|
|
248
|
+
// The content hash check is a source-only local comparison; when not in a
|
|
249
|
+
// source checkout, the result should be neutral (info), not warning.
|
|
250
|
+
const packagedDir = '/private/var/folders/xx/T/bunx-1234/node_modules/@marcusrbrown/infra/src/commands/keeweb'
|
|
201
251
|
fileSpy = spyOn(Bun, 'file').mockImplementation(
|
|
202
252
|
() =>
|
|
203
253
|
({
|
|
@@ -205,10 +255,111 @@ describe('keeweb status helpers', () => {
|
|
|
205
255
|
}) as ReturnType<typeof Bun.file>,
|
|
206
256
|
)
|
|
207
257
|
|
|
208
|
-
const result = await checkContentHash(false)
|
|
258
|
+
const result = await checkContentHash(false, packagedDir)
|
|
259
|
+
|
|
260
|
+
expect(result.level).toBe('info')
|
|
261
|
+
expect(result.summary.toLowerCase()).toMatch(/not available|unavailable|packaged|source checkout/)
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
it('source checkout with matching hashes returns ok', async () => {
|
|
265
|
+
// Simulate source checkout: marker file exists, dist file exists, hashes match.
|
|
266
|
+
const sourceCheckoutDir = '/home/user/projects/infra/packages/cli/src/commands/keeweb'
|
|
267
|
+
const htmlContent = '<html>KeeWeb</html>'
|
|
268
|
+
|
|
269
|
+
fileSpy = spyOn(Bun, 'file').mockImplementation((pathArg: unknown) => {
|
|
270
|
+
const p = String(pathArg)
|
|
271
|
+
if (p.endsWith('apps/keeweb/src/build.ts')) {
|
|
272
|
+
return {exists: async () => true} as ReturnType<typeof Bun.file>
|
|
273
|
+
}
|
|
274
|
+
if (p.endsWith('apps/keeweb/dist/index.html')) {
|
|
275
|
+
return {
|
|
276
|
+
exists: async () => true,
|
|
277
|
+
text: async () => htmlContent,
|
|
278
|
+
} as ReturnType<typeof Bun.file>
|
|
279
|
+
}
|
|
280
|
+
return {exists: async () => false} as ReturnType<typeof Bun.file>
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
fetchMock.mockImplementation(createFetchImplementation(async () => new Response(htmlContent, {status: 200})))
|
|
284
|
+
|
|
285
|
+
const result = await checkContentHash(false, sourceCheckoutDir)
|
|
286
|
+
|
|
287
|
+
expect(result.level).toBe('ok')
|
|
288
|
+
expect(result.summary).toContain('matches')
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
it('source checkout with mismatched hashes returns warning', async () => {
|
|
292
|
+
// Simulate source checkout: marker file exists, dist file exists, hashes differ.
|
|
293
|
+
const sourceCheckoutDir = '/home/user/projects/infra/packages/cli/src/commands/keeweb'
|
|
294
|
+
const localHtml = '<html>KeeWeb local</html>'
|
|
295
|
+
const remoteHtml = '<html>KeeWeb remote</html>'
|
|
296
|
+
|
|
297
|
+
fileSpy = spyOn(Bun, 'file').mockImplementation((pathArg: unknown) => {
|
|
298
|
+
const p = String(pathArg)
|
|
299
|
+
if (p.endsWith('apps/keeweb/src/build.ts')) {
|
|
300
|
+
return {exists: async () => true} as ReturnType<typeof Bun.file>
|
|
301
|
+
}
|
|
302
|
+
if (p.endsWith('apps/keeweb/dist/index.html')) {
|
|
303
|
+
return {
|
|
304
|
+
exists: async () => true,
|
|
305
|
+
text: async () => localHtml,
|
|
306
|
+
} as ReturnType<typeof Bun.file>
|
|
307
|
+
}
|
|
308
|
+
return {exists: async () => false} as ReturnType<typeof Bun.file>
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
fetchMock.mockImplementation(createFetchImplementation(async () => new Response(remoteHtml, {status: 200})))
|
|
312
|
+
|
|
313
|
+
const result = await checkContentHash(false, sourceCheckoutDir)
|
|
314
|
+
|
|
315
|
+
expect(result.level).toBe('warning')
|
|
316
|
+
expect(result.summary).toContain('differs')
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
it('resolves dist path at correct depth from restructured location (path-sensitive mock)', async () => {
|
|
320
|
+
// Regression test for P1 path depth bug: after restructure from
|
|
321
|
+
// commands/keeweb-status.ts to commands/keeweb/status.ts, the path
|
|
322
|
+
// needs 5 levels of ../ to reach apps/keeweb/dist/index.html.
|
|
323
|
+
//
|
|
324
|
+
// Uses path-sensitive mock: marker returns true, dist returns false.
|
|
325
|
+
// This exercises the dist path check and lets us assert the correct path.
|
|
326
|
+
const capturedPaths: string[] = []
|
|
327
|
+
fileSpy = spyOn(Bun, 'file').mockImplementation((pathArg: unknown) => {
|
|
328
|
+
const p = String(pathArg)
|
|
329
|
+
capturedPaths.push(p)
|
|
330
|
+
if (p.endsWith('apps/keeweb/src/build.ts')) {
|
|
331
|
+
return {exists: async () => true} as ReturnType<typeof Bun.file>
|
|
332
|
+
}
|
|
333
|
+
return {exists: async () => false} as ReturnType<typeof Bun.file>
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
await checkContentHash(false)
|
|
337
|
+
|
|
338
|
+
// The dist path must appear in the captured paths
|
|
339
|
+
const distPath = capturedPaths.find(p => p.endsWith('apps/keeweb/dist/index.html'))
|
|
340
|
+
expect(distPath).toBeDefined()
|
|
341
|
+
expect(distPath).not.toContain('commands/keeweb/apps')
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
it('returns warning when the local dist file does not exist in source checkout (path-sensitive mock)', async () => {
|
|
345
|
+
// Simulate source checkout: marker exists, dist file missing (needs a build).
|
|
346
|
+
// Uses path-sensitive mock instead of call-count ordering.
|
|
347
|
+
const sourceCheckoutDir = '/home/user/projects/infra/packages/cli/src/commands/keeweb'
|
|
348
|
+
fileSpy = spyOn(Bun, 'file').mockImplementation((pathArg: unknown) => {
|
|
349
|
+
const p = String(pathArg)
|
|
350
|
+
if (p.endsWith('apps/keeweb/src/build.ts')) {
|
|
351
|
+
return {exists: async () => true} as ReturnType<typeof Bun.file>
|
|
352
|
+
}
|
|
353
|
+
// dist/index.html → missing
|
|
354
|
+
return {exists: async () => false} as ReturnType<typeof Bun.file>
|
|
355
|
+
})
|
|
356
|
+
|
|
357
|
+
const result = await checkContentHash(false, sourceCheckoutDir)
|
|
209
358
|
|
|
210
359
|
expect(result.level).toBe('warning')
|
|
211
360
|
expect(result.summary).toContain('not found')
|
|
361
|
+
expect(result.details).toEqual(expect.arrayContaining(['Run: bun run --cwd apps/keeweb build']))
|
|
362
|
+
expect(result.summary).not.toContain('node_modules')
|
|
212
363
|
})
|
|
213
364
|
})
|
|
214
365
|
})
|
|
@@ -10,7 +10,7 @@ const SITE_URL = 'https://kw.igg.ms/'
|
|
|
10
10
|
const GH_REPO = 'marcusrbrown/infra'
|
|
11
11
|
const HTTP_TIMEOUT_MS = 10_000
|
|
12
12
|
|
|
13
|
-
type CheckLevel = 'ok' | 'warning' | 'error'
|
|
13
|
+
type CheckLevel = 'ok' | 'info' | 'warning' | 'error'
|
|
14
14
|
|
|
15
15
|
interface CheckResult {
|
|
16
16
|
title: string
|
|
@@ -31,6 +31,10 @@ function levelLabel(level: CheckLevel): string {
|
|
|
31
31
|
return 'OK'
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
if (level === 'info') {
|
|
35
|
+
return 'INFO'
|
|
36
|
+
}
|
|
37
|
+
|
|
34
38
|
if (level === 'warning') {
|
|
35
39
|
return 'WARN'
|
|
36
40
|
}
|
|
@@ -192,8 +196,44 @@ export async function checkLastDeploy(verbose: boolean): Promise<CheckResult> {
|
|
|
192
196
|
}
|
|
193
197
|
}
|
|
194
198
|
|
|
195
|
-
|
|
196
|
-
|
|
199
|
+
/**
|
|
200
|
+
* Detect whether moduleDir belongs to a source checkout of this monorepo.
|
|
201
|
+
*
|
|
202
|
+
* A source checkout has the repo root at 5 levels above the keeweb command
|
|
203
|
+
* directory (packages/cli/src/commands/keeweb → repo root). We confirm the
|
|
204
|
+
* layout by checking for a source-only marker file that is never shipped in
|
|
205
|
+
* the CLI package. This is more robust than checking for `node_modules` in
|
|
206
|
+
* the path, which misses unpacked tarballs and global-style installs.
|
|
207
|
+
*/
|
|
208
|
+
async function isSourceCheckout(resolvedModuleDir: string): Promise<boolean> {
|
|
209
|
+
// Candidate repo root is 5 levels up from packages/cli/src/commands/keeweb
|
|
210
|
+
const candidateRoot = path.resolve(resolvedModuleDir, '../../../../../')
|
|
211
|
+
// apps/keeweb/src/build.ts is a source-only file never included in the CLI package
|
|
212
|
+
const sourceMarker = path.join(candidateRoot, 'apps', 'keeweb', 'src', 'build.ts')
|
|
213
|
+
return Bun.file(sourceMarker).exists()
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export async function checkContentHash(verbose: boolean, moduleDir?: string): Promise<CheckResult> {
|
|
217
|
+
const resolvedModuleDir = moduleDir ?? import.meta.dir
|
|
218
|
+
const distIndexPath = path.resolve(resolvedModuleDir, '../../../../../apps/keeweb/dist/index.html')
|
|
219
|
+
|
|
220
|
+
// When running from a packaged install (bunx, npm global, unpacked tarball, etc.),
|
|
221
|
+
// the resolved path has no repo apps/keeweb source tree — the KeeWeb dist is not
|
|
222
|
+
// shipped with the CLI package. Emit a clean, non-path-noisy message instead of
|
|
223
|
+
// a scary temp path. We detect source checkout by looking for a source-only marker
|
|
224
|
+
// file rather than relying on `node_modules` in the path (which misses tarballs).
|
|
225
|
+
const sourceCheckout = await isSourceCheckout(resolvedModuleDir)
|
|
226
|
+
if (!sourceCheckout) {
|
|
227
|
+
return {
|
|
228
|
+
title: 'Content hash',
|
|
229
|
+
level: 'info',
|
|
230
|
+
summary: 'Content hash not available: local KeeWeb dist is only present in a source checkout',
|
|
231
|
+
details: verbose
|
|
232
|
+
? ['Clone the repo and run: bun run --cwd apps/keeweb build', 'Then re-run status from the source checkout']
|
|
233
|
+
: undefined,
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
197
237
|
const localFile = Bun.file(distIndexPath)
|
|
198
238
|
const localExists = await localFile.exists()
|
|
199
239
|
|