dsh-as-aistudio 0.2.2 → 0.2.5
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 +8 -6
- package/src/client.js +30 -7
- package/src/components.js +1 -1
- package/src/studio.js +1 -1
- package/src/vendor/dsh-delete-turn.js +29 -6
- package/src/vendor/dsh-edit-turn.js +2 -2
- package/tools/bump.mjs +82 -0
- package/tools/verify-manifest.mjs +77 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-as-aistudio",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "Google-AI-Studio-style turn controls for DeepSeek Harness, in one install: edit a prompt and re-run it, re-run a reply in place, delete a turn from the model context, and render sent prompts as Markdown. Self-contained: the plugin list shows this one row; the four components are dependencies mounted by it. Each still installs alone and works alone.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -41,13 +41,15 @@
|
|
|
41
41
|
"vendor": "node tools/vendor.mjs",
|
|
42
42
|
"build": "node tools/vendor.mjs && node tools/build.mjs",
|
|
43
43
|
"test": "node --test test/*.test.js",
|
|
44
|
-
"check": "npm run build && npm test",
|
|
44
|
+
"check": "npm run verify:manifest && npm run build && npm test",
|
|
45
|
+
"bump": "node tools/bump.mjs",
|
|
45
46
|
"verify:vendor": "node tools/vendor.mjs --check",
|
|
46
|
-
"verify:build": "node tools/build.mjs --check"
|
|
47
|
+
"verify:build": "node tools/build.mjs --check",
|
|
48
|
+
"verify:manifest": "node tools/verify-manifest.mjs"
|
|
47
49
|
},
|
|
48
50
|
"dependencies": {
|
|
49
|
-
"dsh-delete-turn": "^0.1.
|
|
50
|
-
"dsh-edit-turn": "^0.2.
|
|
51
|
+
"dsh-delete-turn": "^0.1.8",
|
|
52
|
+
"dsh-edit-turn": "^0.2.16",
|
|
51
53
|
"dsh-markdown-bubble": "^0.1.4",
|
|
52
54
|
"dsh-rerun-turn": "^0.1.24"
|
|
53
55
|
},
|
|
@@ -71,7 +73,7 @@
|
|
|
71
73
|
"compatibility": {
|
|
72
74
|
"dshReleases": {
|
|
73
75
|
"0.1.6-alpha.2": "compatible",
|
|
74
|
-
"0.2.
|
|
76
|
+
"0.2.0-rc.2": "compatible"
|
|
75
77
|
}
|
|
76
78
|
}
|
|
77
79
|
}
|
package/src/client.js
CHANGED
|
@@ -32,7 +32,7 @@ window.__ModuleLoader__.load({
|
|
|
32
32
|
const RERUN_PREFIX = '/dsh-rerun-turn'
|
|
33
33
|
|
|
34
34
|
/** Keep in sync with package.json and lib/index.js. */
|
|
35
|
-
const PLUGIN_VERSION = '0.2.
|
|
35
|
+
const PLUGIN_VERSION = '0.2.16'
|
|
36
36
|
|
|
37
37
|
// --- copy -----------------------------------------------------------------
|
|
38
38
|
|
|
@@ -3131,6 +3131,8 @@ window.__ModuleLoader__.load({
|
|
|
3131
3131
|
this.animateOnce = false
|
|
3132
3132
|
this.view = Object.freeze({
|
|
3133
3133
|
hidden: new Map(),
|
|
3134
|
+
// replacement seq -> the seq it was recorded as shadowing. See hiddenViaOf.
|
|
3135
|
+
hiddenVia: new Map(),
|
|
3134
3136
|
surface: new Set(),
|
|
3135
3137
|
replyTurns: new Set(),
|
|
3136
3138
|
edits: new Map(),
|
|
@@ -3195,6 +3197,7 @@ window.__ModuleLoader__.load({
|
|
|
3195
3197
|
for (const item of Array.isArray(data.hidden) ? data.hidden : []) {
|
|
3196
3198
|
if (item && typeof item.seq === 'number') hidden.set(item.seq, typeof item.mode === 'string' ? item.mode : 'message')
|
|
3197
3199
|
}
|
|
3200
|
+
const hiddenVia = hiddenViaOf(data.hidden)
|
|
3198
3201
|
const surface = new Set()
|
|
3199
3202
|
for (const seq of Array.isArray(data.surface) ? data.surface : []) surface.add(seq)
|
|
3200
3203
|
const replyTurns = new Set()
|
|
@@ -3208,7 +3211,7 @@ window.__ModuleLoader__.load({
|
|
|
3208
3211
|
if (typeof turn === 'number') markerTurns.add(turn)
|
|
3209
3212
|
}
|
|
3210
3213
|
const surfaceThrough = typeof data.lastSeq === 'number' ? data.lastSeq : -1
|
|
3211
|
-
this.publish({ hidden, surface, replyTurns, edits, markerTurns, surfaceReady: true, surfaceThrough, loaded: true, loadError: false })
|
|
3214
|
+
this.publish({ hidden, hiddenVia, surface, replyTurns, edits, markerTurns, surfaceReady: true, surfaceThrough, loaded: true, loadError: false })
|
|
3212
3215
|
})
|
|
3213
3216
|
.catch(() => {
|
|
3214
3217
|
this.publish({ loadError: true })
|
|
@@ -3260,8 +3263,10 @@ window.__ModuleLoader__.load({
|
|
|
3260
3263
|
for (const item of Array.isArray(data.hidden) ? data.hidden : []) {
|
|
3261
3264
|
if (item && typeof item.seq === 'number') hidden.set(item.seq, typeof item.mode === 'string' ? item.mode : target.mode)
|
|
3262
3265
|
}
|
|
3266
|
+
const hiddenVia = new Map(this.view.hiddenVia)
|
|
3267
|
+
for (const [via, seq] of hiddenViaOf(data.hidden)) hiddenVia.set(via, seq)
|
|
3263
3268
|
this.animateOnce = true
|
|
3264
|
-
this.publish({ pending: false, dialog: null, hidden, loaded: true, loadError: false })
|
|
3269
|
+
this.publish({ pending: false, dialog: null, hidden, hiddenVia, loaded: true, loadError: false })
|
|
3265
3270
|
} catch {
|
|
3266
3271
|
this.publish({ pending: false, failure: 'generic' })
|
|
3267
3272
|
}
|
|
@@ -3419,9 +3424,27 @@ window.__ModuleLoader__.load({
|
|
|
3419
3424
|
return seq === target.seq ? target : { ...target, seq }
|
|
3420
3425
|
}
|
|
3421
3426
|
|
|
3422
|
-
|
|
3427
|
+
// Index a deletion by the REPLACEMENT it landed, not only by the seq it
|
|
3428
|
+
// shadowed. A deleted message leaves a carrier standing in its row, and the
|
|
3429
|
+
// transcript row names the live node (the carrier), so a row checked against
|
|
3430
|
+
// 'hidden' alone never matches: the deletion succeeds, the model context
|
|
3431
|
+
// loses the message, and the row is simply never hidden - an empty bubble
|
|
3432
|
+
// left behind. The link is already on every hidden entry ('replacement'), so
|
|
3433
|
+
// it is indexed here rather than derived from 'edits': that chain
|
|
3434
|
+
// deliberately excludes this plugin's own carriers (they rewrite context,
|
|
3435
|
+
// not content) and so cannot bridge the gap.
|
|
3436
|
+
function hiddenViaOf(items) {
|
|
3437
|
+
const via = new Map()
|
|
3438
|
+
for (const item of Array.isArray(items) ? items : []) {
|
|
3439
|
+
if (item && typeof item.seq === 'number' && typeof item.replacement === 'number') via.set(item.replacement, item.seq)
|
|
3440
|
+
}
|
|
3441
|
+
return via
|
|
3442
|
+
}
|
|
3443
|
+
|
|
3444
|
+
function isRowHidden(hidden, hiddenVia, seqs) {
|
|
3423
3445
|
for (const seq of seqs) {
|
|
3424
3446
|
if (hidden.has(seq)) return true
|
|
3447
|
+
if (hiddenVia !== undefined && hiddenVia.has(seq)) return true
|
|
3425
3448
|
}
|
|
3426
3449
|
return false
|
|
3427
3450
|
}
|
|
@@ -3684,13 +3707,13 @@ window.__ModuleLoader__.load({
|
|
|
3684
3707
|
const anchor = anchorKey === undefined ? undefined : snapshot.nodes.get(anchorKey)
|
|
3685
3708
|
if (anchor !== undefined) {
|
|
3686
3709
|
const seqs = expandSeqs(seqsFor(anchor), view.edits)
|
|
3687
|
-
setRowHidden(row, isRowHidden(view.hidden, seqs), animate)
|
|
3710
|
+
setRowHidden(row, isRowHidden(view.hidden, view.hiddenVia, seqs), animate)
|
|
3688
3711
|
}
|
|
3689
3712
|
continue
|
|
3690
3713
|
}
|
|
3691
3714
|
const seqs = expandSeqs(seqsFor(node), view.edits)
|
|
3692
3715
|
for (const seq of seqs) if (typeof seq === 'number' && seq > maxSeq) maxSeq = seq
|
|
3693
|
-
const hidden = isRowHidden(view.hidden, seqs)
|
|
3716
|
+
const hidden = isRowHidden(view.hidden, view.hiddenVia, seqs)
|
|
3694
3717
|
setRowHidden(row, hidden, animate)
|
|
3695
3718
|
const target = hidden ? null : liveTarget(targetFor(node), view.edits)
|
|
3696
3719
|
const covered = target !== null || slotCoversRow(node)
|
|
@@ -4795,7 +4818,7 @@ window.__ModuleLoader__.load({
|
|
|
4795
4818
|
const NS = 'dsh-as-aistudio'
|
|
4796
4819
|
|
|
4797
4820
|
/** Keep in sync with package.json and src/components.js. */
|
|
4798
|
-
const PLUGIN_VERSION = '0.2.
|
|
4821
|
+
const PLUGIN_VERSION = '0.2.5'
|
|
4799
4822
|
|
|
4800
4823
|
/** Presence marker the live verifier reads. */
|
|
4801
4824
|
const DEBUG_KEY = '__DSH_AS_AISTUDIO__'
|
package/src/components.js
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
export const STUDIO_ID = 'dsh-as-aistudio'
|
|
20
20
|
|
|
21
21
|
/** Keep in sync with package.json and src/client.js. */
|
|
22
|
-
export const STUDIO_VERSION = '0.2.
|
|
22
|
+
export const STUDIO_VERSION = '0.2.5'
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* The AI Studio action strip this studio composes, in the order the user meets
|
package/src/studio.js
CHANGED
|
@@ -49,7 +49,7 @@ window.__ModuleLoader__.load({
|
|
|
49
49
|
const NS = 'dsh-as-aistudio'
|
|
50
50
|
|
|
51
51
|
/** Keep in sync with package.json and src/components.js. */
|
|
52
|
-
const PLUGIN_VERSION = '0.2.
|
|
52
|
+
const PLUGIN_VERSION = '0.2.5'
|
|
53
53
|
|
|
54
54
|
/** Presence marker the live verifier reads. */
|
|
55
55
|
const DEBUG_KEY = '__DSH_AS_AISTUDIO__'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// dsh-delete-turn browser half, VENDORED by tools/vendor.mjs - DO NOT EDIT.
|
|
2
2
|
//
|
|
3
|
-
// Source: ../dsh-delete-turn/src/client.js (v0.1.
|
|
3
|
+
// Source: ../dsh-delete-turn/src/client.js (v0.1.8, sha256 09de24fd97ecd4ec)
|
|
4
4
|
// Regenerate with `npm run vendor`; test/vendor-sync.test.js fails when this is stale.
|
|
5
5
|
//
|
|
6
6
|
// The component registers its factory with the host module loader; inside this
|
|
@@ -154,6 +154,8 @@ export default (require) => {
|
|
|
154
154
|
this.animateOnce = false
|
|
155
155
|
this.view = Object.freeze({
|
|
156
156
|
hidden: new Map(),
|
|
157
|
+
// replacement seq -> the seq it was recorded as shadowing. See hiddenViaOf.
|
|
158
|
+
hiddenVia: new Map(),
|
|
157
159
|
surface: new Set(),
|
|
158
160
|
replyTurns: new Set(),
|
|
159
161
|
edits: new Map(),
|
|
@@ -218,6 +220,7 @@ export default (require) => {
|
|
|
218
220
|
for (const item of Array.isArray(data.hidden) ? data.hidden : []) {
|
|
219
221
|
if (item && typeof item.seq === 'number') hidden.set(item.seq, typeof item.mode === 'string' ? item.mode : 'message')
|
|
220
222
|
}
|
|
223
|
+
const hiddenVia = hiddenViaOf(data.hidden)
|
|
221
224
|
const surface = new Set()
|
|
222
225
|
for (const seq of Array.isArray(data.surface) ? data.surface : []) surface.add(seq)
|
|
223
226
|
const replyTurns = new Set()
|
|
@@ -231,7 +234,7 @@ export default (require) => {
|
|
|
231
234
|
if (typeof turn === 'number') markerTurns.add(turn)
|
|
232
235
|
}
|
|
233
236
|
const surfaceThrough = typeof data.lastSeq === 'number' ? data.lastSeq : -1
|
|
234
|
-
this.publish({ hidden, surface, replyTurns, edits, markerTurns, surfaceReady: true, surfaceThrough, loaded: true, loadError: false })
|
|
237
|
+
this.publish({ hidden, hiddenVia, surface, replyTurns, edits, markerTurns, surfaceReady: true, surfaceThrough, loaded: true, loadError: false })
|
|
235
238
|
})
|
|
236
239
|
.catch(() => {
|
|
237
240
|
this.publish({ loadError: true })
|
|
@@ -283,8 +286,10 @@ export default (require) => {
|
|
|
283
286
|
for (const item of Array.isArray(data.hidden) ? data.hidden : []) {
|
|
284
287
|
if (item && typeof item.seq === 'number') hidden.set(item.seq, typeof item.mode === 'string' ? item.mode : target.mode)
|
|
285
288
|
}
|
|
289
|
+
const hiddenVia = new Map(this.view.hiddenVia)
|
|
290
|
+
for (const [via, seq] of hiddenViaOf(data.hidden)) hiddenVia.set(via, seq)
|
|
286
291
|
this.animateOnce = true
|
|
287
|
-
this.publish({ pending: false, dialog: null, hidden, loaded: true, loadError: false })
|
|
292
|
+
this.publish({ pending: false, dialog: null, hidden, hiddenVia, loaded: true, loadError: false })
|
|
288
293
|
} catch {
|
|
289
294
|
this.publish({ pending: false, failure: 'generic' })
|
|
290
295
|
}
|
|
@@ -442,9 +447,27 @@ export default (require) => {
|
|
|
442
447
|
return seq === target.seq ? target : { ...target, seq }
|
|
443
448
|
}
|
|
444
449
|
|
|
445
|
-
|
|
450
|
+
// Index a deletion by the REPLACEMENT it landed, not only by the seq it
|
|
451
|
+
// shadowed. A deleted message leaves a carrier standing in its row, and the
|
|
452
|
+
// transcript row names the live node (the carrier), so a row checked against
|
|
453
|
+
// 'hidden' alone never matches: the deletion succeeds, the model context
|
|
454
|
+
// loses the message, and the row is simply never hidden - an empty bubble
|
|
455
|
+
// left behind. The link is already on every hidden entry ('replacement'), so
|
|
456
|
+
// it is indexed here rather than derived from 'edits': that chain
|
|
457
|
+
// deliberately excludes this plugin's own carriers (they rewrite context,
|
|
458
|
+
// not content) and so cannot bridge the gap.
|
|
459
|
+
function hiddenViaOf(items) {
|
|
460
|
+
const via = new Map()
|
|
461
|
+
for (const item of Array.isArray(items) ? items : []) {
|
|
462
|
+
if (item && typeof item.seq === 'number' && typeof item.replacement === 'number') via.set(item.replacement, item.seq)
|
|
463
|
+
}
|
|
464
|
+
return via
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function isRowHidden(hidden, hiddenVia, seqs) {
|
|
446
468
|
for (const seq of seqs) {
|
|
447
469
|
if (hidden.has(seq)) return true
|
|
470
|
+
if (hiddenVia !== undefined && hiddenVia.has(seq)) return true
|
|
448
471
|
}
|
|
449
472
|
return false
|
|
450
473
|
}
|
|
@@ -707,13 +730,13 @@ export default (require) => {
|
|
|
707
730
|
const anchor = anchorKey === undefined ? undefined : snapshot.nodes.get(anchorKey)
|
|
708
731
|
if (anchor !== undefined) {
|
|
709
732
|
const seqs = expandSeqs(seqsFor(anchor), view.edits)
|
|
710
|
-
setRowHidden(row, isRowHidden(view.hidden, seqs), animate)
|
|
733
|
+
setRowHidden(row, isRowHidden(view.hidden, view.hiddenVia, seqs), animate)
|
|
711
734
|
}
|
|
712
735
|
continue
|
|
713
736
|
}
|
|
714
737
|
const seqs = expandSeqs(seqsFor(node), view.edits)
|
|
715
738
|
for (const seq of seqs) if (typeof seq === 'number' && seq > maxSeq) maxSeq = seq
|
|
716
|
-
const hidden = isRowHidden(view.hidden, seqs)
|
|
739
|
+
const hidden = isRowHidden(view.hidden, view.hiddenVia, seqs)
|
|
717
740
|
setRowHidden(row, hidden, animate)
|
|
718
741
|
const target = hidden ? null : liveTarget(targetFor(node), view.edits)
|
|
719
742
|
const covered = target !== null || slotCoversRow(node)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// dsh-edit-turn browser half, VENDORED by tools/vendor.mjs - DO NOT EDIT.
|
|
2
2
|
//
|
|
3
|
-
// Source: ../dsh-edit-turn/lib/client.js (v0.2.
|
|
3
|
+
// Source: ../dsh-edit-turn/lib/client.js (v0.2.16, sha256 3ccc92a337c0541e)
|
|
4
4
|
// Regenerate with `npm run vendor`; test/vendor-sync.test.js fails when this is stale.
|
|
5
5
|
//
|
|
6
6
|
// The component registers its factory with the host module loader; inside this
|
|
@@ -24,7 +24,7 @@ export default (require) => {
|
|
|
24
24
|
const RERUN_PREFIX = '/dsh-rerun-turn'
|
|
25
25
|
|
|
26
26
|
/** Keep in sync with package.json and lib/index.js. */
|
|
27
|
-
const PLUGIN_VERSION = '0.2.
|
|
27
|
+
const PLUGIN_VERSION = '0.2.16'
|
|
28
28
|
|
|
29
29
|
// --- copy -----------------------------------------------------------------
|
|
30
30
|
|
package/tools/bump.mjs
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Bump this package's version in the three places that legitimately carry it -
|
|
2
|
+
// and assert that NOTHING ELSE in those files changed.
|
|
3
|
+
//
|
|
4
|
+
// The rule this tool exists to enforce. Do not bump with a blind replace:
|
|
5
|
+
//
|
|
6
|
+
// s.split('0.2.1').join('0.2.2') // WRONG
|
|
7
|
+
//
|
|
8
|
+
// That also rewrites every other string containing the old version. '^0.2.15'
|
|
9
|
+
// contains '0.2.1', so a dependency range silently became '^0.2.25' - a version
|
|
10
|
+
// that has never existed - and the package shipped unable to install at all.
|
|
11
|
+
// It also rewrote a DSH release key ('0.2.0-rc.1' -> '0.2.1-rc.1'), which has
|
|
12
|
+
// nothing to do with this package's version.
|
|
13
|
+
//
|
|
14
|
+
// node tools/bump.mjs 0.2.3 # write
|
|
15
|
+
// node tools/bump.mjs --check # verify the three sites agree, write nothing
|
|
16
|
+
import { readFileSync, writeFileSync } from 'node:fs'
|
|
17
|
+
import { dirname, join } from 'node:path'
|
|
18
|
+
import { fileURLToPath } from 'node:url'
|
|
19
|
+
|
|
20
|
+
const root = join(dirname(fileURLToPath(import.meta.url)), '..')
|
|
21
|
+
|
|
22
|
+
/** file, the one regex that matches its version literal, and a human name. */
|
|
23
|
+
const SITES = [
|
|
24
|
+
{ file: 'package.json', pattern: /(^\s*"version":\s*")([^"]+)(")/m, label: 'package.json version' },
|
|
25
|
+
{ file: 'src/components.js', pattern: /(export const STUDIO_VERSION = ')([^']+)(')/, label: 'STUDIO_VERSION' },
|
|
26
|
+
{ file: 'src/studio.js', pattern: /(const PLUGIN_VERSION = ')([^']+)(')/, label: 'PLUGIN_VERSION' },
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
function currentOf(site) {
|
|
30
|
+
const text = readFileSync(join(root, site.file), 'utf8')
|
|
31
|
+
const match = site.pattern.exec(text)
|
|
32
|
+
if (match === null) throw new Error(site.label + ': the version literal is not in ' + site.file)
|
|
33
|
+
return match[2]
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const versions = SITES.map((site) => ({ ...site, current: currentOf(site) }))
|
|
37
|
+
const distinct = [...new Set(versions.map((v) => v.current))]
|
|
38
|
+
|
|
39
|
+
if (process.argv.includes('--check')) {
|
|
40
|
+
if (distinct.length !== 1) {
|
|
41
|
+
console.error('the three version sites disagree: ' + versions.map((v) => v.label + '=' + v.current).join(', '))
|
|
42
|
+
process.exit(1)
|
|
43
|
+
}
|
|
44
|
+
console.log('the version is one number in three places: ' + distinct[0])
|
|
45
|
+
process.exit(0)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const next = process.argv[2]
|
|
49
|
+
if (!/^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$/.test(next || '')) {
|
|
50
|
+
console.error('usage: node tools/bump.mjs <semver> (for example 0.2.3)')
|
|
51
|
+
process.exit(2)
|
|
52
|
+
}
|
|
53
|
+
if (distinct.length !== 1) {
|
|
54
|
+
console.error('refusing to bump: the three sites already disagree (' + distinct.join(', ') + '); fix them first')
|
|
55
|
+
process.exit(2)
|
|
56
|
+
}
|
|
57
|
+
const from = distinct[0]
|
|
58
|
+
if (from === next) {
|
|
59
|
+
console.error('already at ' + next)
|
|
60
|
+
process.exit(2)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
for (const site of versions) {
|
|
64
|
+
const path = join(root, site.file)
|
|
65
|
+
const before = readFileSync(path, 'utf8')
|
|
66
|
+
const after = before.replace(site.pattern, (_m, head, _old, tail) => head + next + tail)
|
|
67
|
+
if (before === after) throw new Error(site.label + ': nothing changed in ' + site.file)
|
|
68
|
+
// The whole point: exactly one line may differ, and it must be the version line.
|
|
69
|
+
const beforeLines = before.split('\n')
|
|
70
|
+
const afterLines = after.split('\n')
|
|
71
|
+
if (beforeLines.length !== afterLines.length) throw new Error(site.file + ': the line count changed')
|
|
72
|
+
const changed = beforeLines.map((line, index) => (line === afterLines[index] ? null : index)).filter((index) => index !== null)
|
|
73
|
+
if (changed.length !== 1) {
|
|
74
|
+
throw new Error(site.file + ': expected exactly 1 changed line, got ' + changed.length + '; refusing to write')
|
|
75
|
+
}
|
|
76
|
+
writeFileSync(path, after)
|
|
77
|
+
console.log(' ' + site.file + ':' + (changed[0] + 1) + ' ' + site.label + ' ' + from + ' -> ' + next)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
console.log('')
|
|
81
|
+
console.log('bumped ' + from + ' -> ' + next + ' in ' + versions.length + ' places, nothing else touched.')
|
|
82
|
+
console.log('Now: node tools/verify-manifest.mjs, npm run build, npm test.')
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// Gate the manifest against the registry before publishing.
|
|
2
|
+
//
|
|
3
|
+
// This is the check whose absence shipped a broken release: dsh-as-aistudio@0.2.2
|
|
4
|
+
// declared 'dsh-edit-turn': '^0.2.25'. No such version exists (the latest is
|
|
5
|
+
// 0.2.15), so `pnpm add dsh-as-aistudio@0.2.2` failed with
|
|
6
|
+
// ERR_PNPM_NO_MATCHING_VERSION and the package could not be installed at all -
|
|
7
|
+
// while every test that exercised the bundle inside it passed, because unpacking
|
|
8
|
+
// a tarball never resolves its dependencies.
|
|
9
|
+
//
|
|
10
|
+
// node tools/verify-manifest.mjs # exit 1 on any unresolvable range
|
|
11
|
+
import { execFileSync } from 'node:child_process'
|
|
12
|
+
import { readFileSync } from 'node:fs'
|
|
13
|
+
import { dirname, join } from 'node:path'
|
|
14
|
+
import { fileURLToPath } from 'node:url'
|
|
15
|
+
|
|
16
|
+
const root = join(dirname(fileURLToPath(import.meta.url)), '..')
|
|
17
|
+
const manifest = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'))
|
|
18
|
+
let failed = 0
|
|
19
|
+
|
|
20
|
+
// 1. every dependency range must resolve to a published version
|
|
21
|
+
const deps = Object.entries(manifest.dependencies || {})
|
|
22
|
+
if (deps.length === 0) {
|
|
23
|
+
console.error('no dependencies declared; that is not the shape of this package')
|
|
24
|
+
failed += 1
|
|
25
|
+
}
|
|
26
|
+
for (const [name, range] of deps) {
|
|
27
|
+
let resolved = ''
|
|
28
|
+
try {
|
|
29
|
+
resolved = execFileSync('npm', ['view', name + '@' + range, 'version'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim().split('\n').pop().trim()
|
|
30
|
+
} catch (error) {
|
|
31
|
+
resolved = ''
|
|
32
|
+
}
|
|
33
|
+
if (resolved === '') {
|
|
34
|
+
console.error(' UNRESOLVABLE ' + name + '@' + range + ' <- no published version satisfies this')
|
|
35
|
+
failed += 1
|
|
36
|
+
} else {
|
|
37
|
+
console.log(' ok ' + name + '@' + range + ' -> ' + resolved)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 2. the version must be one number in the three places that carry it
|
|
42
|
+
const sites = [
|
|
43
|
+
['package.json', /(^\s*"version":\s*")([^"]+)(")/m],
|
|
44
|
+
['src/components.js', /(export const STUDIO_VERSION = ')([^']+)(')/],
|
|
45
|
+
['src/studio.js', /(const PLUGIN_VERSION = ')([^']+)(')/],
|
|
46
|
+
]
|
|
47
|
+
const found = sites.map(([file, pattern]) => {
|
|
48
|
+
const match = pattern.exec(readFileSync(join(root, file), 'utf8'))
|
|
49
|
+
return { file, version: match === null ? null : match[2] }
|
|
50
|
+
})
|
|
51
|
+
const distinct = [...new Set(found.map((s) => s.version))]
|
|
52
|
+
if (distinct.length !== 1 || distinct[0] === null) {
|
|
53
|
+
console.error(' VERSION DRIFT ' + found.map((s) => s.file + '=' + s.version).join(', '))
|
|
54
|
+
failed += 1
|
|
55
|
+
} else {
|
|
56
|
+
console.log(' ok version ' + distinct[0] + ' in all three places')
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 3. DSH compatibility keys must look like DSH releases, not like this package
|
|
60
|
+
const releases = Object.keys(manifest.dsh?.compatibility?.dshReleases || {})
|
|
61
|
+
if (releases.length === 0) {
|
|
62
|
+
console.error(' NO DSH RELEASES declared in dsh.compatibility.dshReleases')
|
|
63
|
+
failed += 1
|
|
64
|
+
}
|
|
65
|
+
for (const release of releases) {
|
|
66
|
+
if (!/^0\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$/.test(release)) {
|
|
67
|
+
console.error(' BAD DSH KEY ' + release + ' does not look like a DSH release')
|
|
68
|
+
failed += 1
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
console.log('')
|
|
73
|
+
if (failed > 0) {
|
|
74
|
+
console.error(failed + ' manifest problem(s); do not publish')
|
|
75
|
+
process.exit(1)
|
|
76
|
+
}
|
|
77
|
+
console.log('manifest ok: ' + deps.length + ' dependency ranges resolve, version ' + distinct[0] + ' consistent')
|