@joshuafolkken/kit 1.881.0 → 1.883.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/.github/workflows/ci.yml +3 -3
- package/dist/version/index.js +9 -1
- package/package.json +2 -2
- package/pnpm-workspace.yaml +3 -6
- package/scripts/init/init.ts +2 -0
- package/scripts/safe-chain/project-config.ts +104 -0
- package/scripts/sync/sync.ts +2 -0
- package/scripts/version/publishable-range.ts +3 -3
- package/scripts/version/version-commands.ts +10 -1
- package/templates/workflows/ci.yml +2 -2
package/.github/workflows/ci.yml
CHANGED
|
@@ -307,7 +307,7 @@ jobs:
|
|
|
307
307
|
${{ runner.os }}-cspell-cache-
|
|
308
308
|
|
|
309
309
|
- name: Setup safe-chain
|
|
310
|
-
run: pnpm dlx @aikidosec/safe-chain@1.5.
|
|
310
|
+
run: pnpm dlx @aikidosec/safe-chain@1.5.20 setup-ci
|
|
311
311
|
|
|
312
312
|
- name: Install dependencies
|
|
313
313
|
env:
|
|
@@ -406,7 +406,7 @@ jobs:
|
|
|
406
406
|
${{ runner.os }}-pnpm-store-
|
|
407
407
|
|
|
408
408
|
- name: Setup safe-chain
|
|
409
|
-
run: pnpm dlx @aikidosec/safe-chain@1.5.
|
|
409
|
+
run: pnpm dlx @aikidosec/safe-chain@1.5.20 setup-ci
|
|
410
410
|
|
|
411
411
|
- name: Install dependencies
|
|
412
412
|
env:
|
|
@@ -615,7 +615,7 @@ jobs:
|
|
|
615
615
|
${{ runner.os }}-build-cache-e2e-
|
|
616
616
|
|
|
617
617
|
- name: Setup safe-chain
|
|
618
|
-
run: pnpm dlx @aikidosec/safe-chain@1.5.
|
|
618
|
+
run: pnpm dlx @aikidosec/safe-chain@1.5.20 setup-ci
|
|
619
619
|
|
|
620
620
|
- name: Install dependencies
|
|
621
621
|
env:
|
package/dist/version/index.js
CHANGED
|
@@ -962,9 +962,17 @@ function run_upgrade_command(command) {
|
|
|
962
962
|
}
|
|
963
963
|
function run_all_upgrade_commands(commands) {
|
|
964
964
|
let exit_code = 0;
|
|
965
|
+
const failed_commands = [];
|
|
965
966
|
for (const command of commands) {
|
|
966
967
|
const code = run_upgrade_command(command);
|
|
967
|
-
if (code
|
|
968
|
+
if (code === 0) continue;
|
|
969
|
+
exit_code = code;
|
|
970
|
+
failed_commands.push(command);
|
|
971
|
+
}
|
|
972
|
+
if (failed_commands.length > 0) {
|
|
973
|
+
console.error(
|
|
974
|
+
`Upgrade incomplete; successful steps remain applied. Failed: ${failed_commands.join(", ")}`
|
|
975
|
+
);
|
|
968
976
|
}
|
|
969
977
|
return exit_code;
|
|
970
978
|
}
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.
|
|
2
|
+
"version": "1.883.0",
|
|
3
3
|
"name": "@joshuafolkken/kit",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
@@ -166,7 +166,7 @@
|
|
|
166
166
|
}
|
|
167
167
|
},
|
|
168
168
|
"scripts": {
|
|
169
|
-
"preinstall": "pnpm dlx @aikidosec/safe-chain@1.5.
|
|
169
|
+
"preinstall": "pnpm dlx @aikidosec/safe-chain@1.5.20 setup-ci",
|
|
170
170
|
"build": "tsx scripts/build/build-bin.ts && tsx scripts/build/build-hooks.ts && tsx scripts/build/build-version.ts && tsx scripts/build/build-config-merge.ts && tsx scripts/build/build-managed-marker.ts && tsx scripts/build/build-self-sync-guard.ts && tsx scripts/build/build-claude-md.ts",
|
|
171
171
|
"josh": "tsx scripts/josh/josh.ts"
|
|
172
172
|
}
|
package/pnpm-workspace.yaml
CHANGED
|
@@ -18,12 +18,9 @@ trustLockfile: true
|
|
|
18
18
|
# `@types/node` and `tsx` are also excluded so routine Dependabot-style bumps are not
|
|
19
19
|
# delayed by the minimum-release-age window.
|
|
20
20
|
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
# a release that safe-chain still withholds publishes a range no consumer can resolve (kit #742), so
|
|
25
|
-
# `josh ranges` checks every published range against that filtered view — from `josh latest` right
|
|
26
|
-
# after the ranges are rewritten, and again from `prepack` as the last gate before a publish.
|
|
21
|
+
# `josh init` / `josh sync` mirror this list to Safe Chain's .aikido project config (kit #2618).
|
|
22
|
+
# Older Safe Chain shims ignore .aikido and can still hide an excluded release. `josh ranges`
|
|
23
|
+
# checks published dependency ranges through that filtered view before a release (kit #742).
|
|
27
24
|
minimumReleaseAgeExclude:
|
|
28
25
|
- vite
|
|
29
26
|
- '@types/node'
|
package/scripts/init/init.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { resolve_local_bin } from '#scripts/build/local-bin'
|
|
|
6
6
|
import { doctor_io } from '#scripts/doctor/doctor-io'
|
|
7
7
|
import { package_version_schema, with_package_manager_schema } from '#scripts/lib/schemas'
|
|
8
8
|
import { auto_merge_setting } from '#scripts/repo/auto-merge-setting'
|
|
9
|
+
import { project_config } from '#scripts/safe-chain/project-config'
|
|
9
10
|
import { security_updates } from '#scripts/security/security-updates'
|
|
10
11
|
import { did_refuse_self_run } from '#scripts/self-sync-guard/self-sync-refusal'
|
|
11
12
|
import { sync } from '#scripts/sync/sync'
|
|
@@ -203,6 +204,7 @@ function main(): void {
|
|
|
203
204
|
merge_project_package_json()
|
|
204
205
|
|
|
205
206
|
run_ai_file_actions()
|
|
207
|
+
project_config.sync_project_config(PROJECT_ROOT)
|
|
206
208
|
console.info('\n✅ Done.\n')
|
|
207
209
|
}
|
|
208
210
|
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { yaml_document } from '#scripts/yaml/yaml-document'
|
|
4
|
+
import { dump } from 'js-yaml'
|
|
5
|
+
import { z } from 'zod'
|
|
6
|
+
|
|
7
|
+
const WORKSPACE_FILE = 'pnpm-workspace.yaml'
|
|
8
|
+
const PROJECT_CONFIG_FILE = '.aikido'
|
|
9
|
+
const EXCLUSIONS_FIELD = 'minimumPackageAgeExclusions'
|
|
10
|
+
const workspace_schema = z.object({ minimumReleaseAgeExclude: z.array(z.string()).optional() })
|
|
11
|
+
|
|
12
|
+
function read_mapping(value: unknown, label: string): Record<string, unknown> {
|
|
13
|
+
if (value === undefined) return {}
|
|
14
|
+
if (yaml_document.is_mapping_document(value)) return value
|
|
15
|
+
throw new Error(`${label} must be a YAML mapping`)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function is_block_line(line: string): boolean {
|
|
19
|
+
return line.trim() === '' || line.startsWith(' ') || line.startsWith('\t')
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function trim_block_end(lines: ReadonlyArray<string>, start: number, end: number): number {
|
|
23
|
+
let trimmed = end
|
|
24
|
+
while (trimmed > start + 1 && lines[trimmed - 1]?.trim() === '') trimmed -= 1
|
|
25
|
+
|
|
26
|
+
return trimmed
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function find_block_end(lines: ReadonlyArray<string>, start: number): number {
|
|
30
|
+
const remaining = lines.slice(start + 1)
|
|
31
|
+
const relative = remaining.findIndex((line, index) => {
|
|
32
|
+
if (is_block_line(line)) return false
|
|
33
|
+
if (!line.startsWith('#')) return true
|
|
34
|
+
const next = remaining
|
|
35
|
+
.slice(index + 1)
|
|
36
|
+
.find((candidate) => candidate.trim() !== '' && !candidate.startsWith('#'))
|
|
37
|
+
|
|
38
|
+
return next === undefined || !is_block_line(next)
|
|
39
|
+
})
|
|
40
|
+
const end = relative === -1 ? lines.length : start + 1 + relative
|
|
41
|
+
|
|
42
|
+
return trim_block_end(lines, start, end)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function append_block(existing: string, block: string): string {
|
|
46
|
+
if (existing.length === 0) return block
|
|
47
|
+
const prefix = existing.endsWith('\n') ? existing : `${existing}\n`
|
|
48
|
+
|
|
49
|
+
return `${prefix}\n${block}`
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function preserve_anchor(block: string, key_line: string): string {
|
|
53
|
+
const anchor = /(?:^|\s)(&[^\s#]+)/u.exec(key_line)?.[1]
|
|
54
|
+
if (!anchor) return block
|
|
55
|
+
const header = 'safe-chain:'
|
|
56
|
+
|
|
57
|
+
return `${header} ${anchor}${block.slice(header.length)}`
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function replace_safe_chain_block(existing: string, safe_chain: Record<string, unknown>): string {
|
|
61
|
+
const block = dump({ 'safe-chain': safe_chain }, { lineWidth: -1 })
|
|
62
|
+
const lines = existing.split('\n')
|
|
63
|
+
const start = lines.findIndex((line) =>
|
|
64
|
+
/^(?:safe-chain|'safe-chain'|"safe-chain")\s*:/u.test(line),
|
|
65
|
+
)
|
|
66
|
+
if (start === -1) return append_block(existing, block)
|
|
67
|
+
const replacement = preserve_anchor(block, lines[start] ?? '')
|
|
68
|
+
const end = find_block_end(lines, start)
|
|
69
|
+
const prefix = lines.slice(0, start).join('\n')
|
|
70
|
+
const suffix = lines.slice(end).join('\n')
|
|
71
|
+
|
|
72
|
+
return `${prefix}${prefix ? '\n' : ''}${replacement}${suffix}`
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function merge_project_config(existing: string, workspace: string): string {
|
|
76
|
+
const source = workspace_schema.parse(yaml_document.parse_yaml(workspace))
|
|
77
|
+
const exclusions = source.minimumReleaseAgeExclude ?? []
|
|
78
|
+
const aikido = yaml_document.parse_yaml(existing)
|
|
79
|
+
const safe_chain = read_mapping(aikido['safe-chain'], 'safe-chain')
|
|
80
|
+
const npm = read_mapping(safe_chain['npm'], 'safe-chain.npm')
|
|
81
|
+
if (JSON.stringify(npm[EXCLUSIONS_FIELD]) === JSON.stringify(exclusions)) return existing
|
|
82
|
+
if (exclusions.length === 0 && existing.length === 0) return existing
|
|
83
|
+
|
|
84
|
+
const merged = { ...safe_chain, npm: { ...npm, [EXCLUSIONS_FIELD]: exclusions } }
|
|
85
|
+
|
|
86
|
+
return replace_safe_chain_block(existing, merged)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function sync_project_config(root: string): boolean {
|
|
90
|
+
const workspace_path = path.join(root, WORKSPACE_FILE)
|
|
91
|
+
if (!existsSync(workspace_path)) return false
|
|
92
|
+
const config_path = path.join(root, PROJECT_CONFIG_FILE)
|
|
93
|
+
const existing = existsSync(config_path) ? readFileSync(config_path, 'utf8') : ''
|
|
94
|
+
const workspace = readFileSync(workspace_path, 'utf8')
|
|
95
|
+
const merged = merge_project_config(existing, workspace)
|
|
96
|
+
if (merged === existing) return false
|
|
97
|
+
writeFileSync(config_path, merged)
|
|
98
|
+
|
|
99
|
+
return true
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const project_config = { merge_project_config, sync_project_config }
|
|
103
|
+
|
|
104
|
+
export { project_config }
|
package/scripts/sync/sync.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { init_logic } from '#scripts/init/init-logic'
|
|
|
8
8
|
import { PACKAGE_DIR, PROJECT_ROOT } from '#scripts/init/init-paths'
|
|
9
9
|
import { plugin_install_hint_module } from '#scripts/init/plugin-install-hint'
|
|
10
10
|
import { auto_merge_setting } from '#scripts/repo/auto-merge-setting'
|
|
11
|
+
import { project_config } from '#scripts/safe-chain/project-config'
|
|
11
12
|
import { security_updates } from '#scripts/security/security-updates'
|
|
12
13
|
import { sonar_file } from '#scripts/security/sonar-file'
|
|
13
14
|
import { did_refuse_self_run } from '#scripts/self-sync-guard/self-sync-refusal'
|
|
@@ -367,6 +368,7 @@ function report_repository_settings(name_with_owner: string | undefined): void {
|
|
|
367
368
|
|
|
368
369
|
function sync_project_artifacts(is_force: boolean): void {
|
|
369
370
|
sync_ai_copy_all(is_force)
|
|
371
|
+
project_config.sync_project_config(PROJECT_ROOT)
|
|
370
372
|
sync_prettier_config(path.join(PROJECT_ROOT, 'prettier.config.js'))
|
|
371
373
|
sync_playwright_config(path.join(PROJECT_ROOT, 'playwright.config.ts'))
|
|
372
374
|
sync_deploy_vps(path.join(PROJECT_ROOT, '.github/workflows/deploy-vps.yml'))
|
|
@@ -9,9 +9,9 @@ import semver from 'semver'
|
|
|
9
9
|
// The check asks the registry through that same filtered view instead of modelling the policy:
|
|
10
10
|
// `pnpm view <name>@<range> version` prints no version and exits non-zero
|
|
11
11
|
// (ERR_PNPM_PACKAGE_NOT_FOUND) exactly when nothing visible satisfies the range. Modelling would
|
|
12
|
-
// mean guessing safe-chain's threshold
|
|
13
|
-
//
|
|
14
|
-
//
|
|
12
|
+
// mean guessing safe-chain's threshold. `josh init` / `josh sync` now mirror
|
|
13
|
+
// `minimumReleaseAgeExclude` into Safe Chain's .aikido project config (kit #2618), but older
|
|
14
|
+
// installed shims ignore it and published ranges must still be checked against their filtered view.
|
|
15
15
|
|
|
16
16
|
interface PublishedRange {
|
|
17
17
|
name: string
|
|
@@ -204,10 +204,19 @@ function run_upgrade_command(command: string): number {
|
|
|
204
204
|
// succeed) so a failure on either target is surfaced without aborting the remaining upgrades.
|
|
205
205
|
function run_all_upgrade_commands(commands: ReadonlyArray<string>): number {
|
|
206
206
|
let exit_code = 0
|
|
207
|
+
const failed_commands: Array<string> = []
|
|
207
208
|
|
|
208
209
|
for (const command of commands) {
|
|
209
210
|
const code = run_upgrade_command(command)
|
|
210
|
-
if (code
|
|
211
|
+
if (code === 0) continue
|
|
212
|
+
exit_code = code
|
|
213
|
+
failed_commands.push(command)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (failed_commands.length > 0) {
|
|
217
|
+
console.error(
|
|
218
|
+
`Upgrade incomplete; successful steps remain applied. Failed: ${failed_commands.join(', ')}`,
|
|
219
|
+
)
|
|
211
220
|
}
|
|
212
221
|
|
|
213
222
|
return exit_code
|
|
@@ -178,7 +178,7 @@ jobs:
|
|
|
178
178
|
${{ runner.os }}-cspell-cache-
|
|
179
179
|
|
|
180
180
|
- name: Setup safe-chain
|
|
181
|
-
run: pnpm dlx @aikidosec/safe-chain@1.5.
|
|
181
|
+
run: pnpm dlx @aikidosec/safe-chain@1.5.20 setup-ci
|
|
182
182
|
|
|
183
183
|
- name: Install dependencies
|
|
184
184
|
env:
|
|
@@ -400,7 +400,7 @@ jobs:
|
|
|
400
400
|
${{ runner.os }}-build-cache-e2e-
|
|
401
401
|
|
|
402
402
|
- name: Setup safe-chain
|
|
403
|
-
run: pnpm dlx @aikidosec/safe-chain@1.5.
|
|
403
|
+
run: pnpm dlx @aikidosec/safe-chain@1.5.20 setup-ci
|
|
404
404
|
|
|
405
405
|
- name: Install dependencies
|
|
406
406
|
env:
|