@joshuafolkken/kit 1.879.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/hooks/batch-guard.js +4 -4
- package/dist/hooks/{chunk-KABG6AEW.js → chunk-264QY6KS.js} +7 -0
- package/dist/hooks/{chunk-B4YNCUGB.js → chunk-36UE53LS.js} +2 -2
- package/dist/hooks/{chunk-WCYPBPJC.js → chunk-4OXFU3MK.js} +8 -8
- package/dist/hooks/{chunk-BIWLABFK.js → chunk-6MQCXEEA.js} +1 -1
- package/dist/hooks/{chunk-GXKKK23T.js → chunk-FNFOLFFV.js} +1 -1
- package/dist/hooks/{chunk-B2LIXYFL.js → chunk-FRJKVSK3.js} +2 -2
- package/dist/hooks/{chunk-2RUPMGGV.js → chunk-PRDCNGA6.js} +4 -4
- package/dist/hooks/{chunk-2XIGZQMQ.js → chunk-VGYSA2NT.js} +2 -2
- package/dist/hooks/{chunk-QNNWYQ2R.js → chunk-Y5OYCU2F.js} +3 -3
- package/dist/hooks/{chunk-E55NKX5F.js → chunk-ZBK4XRAS.js} +3 -3
- package/dist/hooks/codex-hook-adapter.js +10 -10
- package/dist/hooks/duplicate-read-guard.js +5 -5
- package/dist/hooks/format-edited.js +2 -2
- package/dist/hooks/investigation-guard.js +5 -5
- package/dist/hooks/pretool-guard.js +9 -9
- package/dist/hooks/stop-guard.js +3 -3
- package/dist/josh.js +7 -0
- 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/josh/__snapshots__/josh-command-map.test.ts.snap +10 -0
- package/scripts/josh/josh-command-map.ts +1 -0
- package/scripts/josh/josh-commands-project.ts +6 -0
- package/scripts/registry-migration/migrate-logic.ts +229 -0
- package/scripts/registry-migration/migrate.ts +280 -0
- package/scripts/registry-migration/user-npmrc.ts +15 -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:
|
|
@@ -9,10 +9,10 @@ import {
|
|
|
9
9
|
load_environment_file,
|
|
10
10
|
notice_refusal_path,
|
|
11
11
|
refusal_path
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import "./chunk-
|
|
14
|
-
import "./chunk-
|
|
15
|
-
import "./chunk-
|
|
12
|
+
} from "./chunk-FRJKVSK3.js";
|
|
13
|
+
import "./chunk-36UE53LS.js";
|
|
14
|
+
import "./chunk-FNFOLFFV.js";
|
|
15
|
+
import "./chunk-264QY6KS.js";
|
|
16
16
|
import "./chunk-FXAKJKTB.js";
|
|
17
17
|
import "./chunk-SSX23SJ2.js";
|
|
18
18
|
export {
|
|
@@ -1016,6 +1016,12 @@ var PROJECT_COMMANDS = {
|
|
|
1016
1016
|
category: "Project",
|
|
1017
1017
|
reference: ["", "developer", ["files"]]
|
|
1018
1018
|
},
|
|
1019
|
+
"registry:migrate": {
|
|
1020
|
+
script: "scripts/registry-migration/migrate.ts",
|
|
1021
|
+
description: "Migrate a kit-only project from GitHub Packages to public npm",
|
|
1022
|
+
category: "Project",
|
|
1023
|
+
reference: ["", "developer", ["files", "network", "processes"]]
|
|
1024
|
+
},
|
|
1019
1025
|
"sync:scope": {
|
|
1020
1026
|
script: "scripts/sync/managed-config-scope-cli.ts",
|
|
1021
1027
|
description: "Say whether this change touches a file josh sync distributes",
|
|
@@ -1237,6 +1243,7 @@ var ALIASES = {
|
|
|
1237
1243
|
pt: "port",
|
|
1238
1244
|
i: "init",
|
|
1239
1245
|
sy: "sync",
|
|
1246
|
+
rmi: "registry:migrate",
|
|
1240
1247
|
sys: "sync:scope",
|
|
1241
1248
|
g: "git",
|
|
1242
1249
|
gp: "pr",
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
lane_child_marker
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-FNFOLFFV.js";
|
|
4
4
|
import {
|
|
5
5
|
time_bundle_call,
|
|
6
6
|
time_format,
|
|
7
7
|
time_round_trips,
|
|
8
8
|
time_spans,
|
|
9
9
|
time_transcript_line
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-264QY6KS.js";
|
|
11
11
|
|
|
12
12
|
// scripts/lane/lane-guard-policy.ts
|
|
13
13
|
var DEFAULT_MODE = "refuse";
|
|
@@ -29,28 +29,28 @@ import {
|
|
|
29
29
|
run_watcher_guard,
|
|
30
30
|
shell_segments,
|
|
31
31
|
tail_commands
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-VGYSA2NT.js";
|
|
33
33
|
import {
|
|
34
34
|
batch_outcome
|
|
35
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-FRJKVSK3.js";
|
|
36
36
|
import {
|
|
37
37
|
investigation_refusal
|
|
38
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-ZBK4XRAS.js";
|
|
39
39
|
import {
|
|
40
40
|
duplicate_read_outcome
|
|
41
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-PRDCNGA6.js";
|
|
42
42
|
import {
|
|
43
43
|
investigation_reads
|
|
44
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-Y5OYCU2F.js";
|
|
45
45
|
import {
|
|
46
46
|
lane_guard_policy,
|
|
47
47
|
time_batch_guard
|
|
48
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-36UE53LS.js";
|
|
49
49
|
import {
|
|
50
50
|
hook_decision,
|
|
51
51
|
lane_child_marker,
|
|
52
52
|
run_issue_number
|
|
53
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-FNFOLFFV.js";
|
|
54
54
|
import {
|
|
55
55
|
PLATFORM_TEMP_ROOT,
|
|
56
56
|
cost_attribute,
|
|
@@ -61,7 +61,7 @@ import {
|
|
|
61
61
|
lane_paths,
|
|
62
62
|
stamp_file,
|
|
63
63
|
time_shell
|
|
64
|
-
} from "./chunk-
|
|
64
|
+
} from "./chunk-264QY6KS.js";
|
|
65
65
|
|
|
66
66
|
// scripts/hooks/pretool-guard.ts
|
|
67
67
|
import { text } from "node:stream/consumers";
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
lane_guard_policy,
|
|
3
3
|
time_batch_guard
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-36UE53LS.js";
|
|
5
5
|
import {
|
|
6
6
|
hook_decision
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-FNFOLFFV.js";
|
|
8
8
|
|
|
9
9
|
// scripts/hooks/batch-guard.ts
|
|
10
10
|
import { text } from "node:stream/consumers";
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
investigation_reads
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-Y5OYCU2F.js";
|
|
4
4
|
import {
|
|
5
5
|
lane_guard_policy
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-36UE53LS.js";
|
|
7
7
|
import {
|
|
8
8
|
hook_decision
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-FNFOLFFV.js";
|
|
10
10
|
import {
|
|
11
11
|
json_value,
|
|
12
12
|
time_spans
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-264QY6KS.js";
|
|
14
14
|
|
|
15
15
|
// scripts/delegation/duplicate-read-guard.ts
|
|
16
16
|
import { statSync } from "node:fs";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
lane_child_marker,
|
|
3
3
|
run_issue_number
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-FNFOLFFV.js";
|
|
5
5
|
import {
|
|
6
6
|
PLATFORM_TEMP_ROOT,
|
|
7
7
|
cost_blocks,
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
time_density_hook,
|
|
12
12
|
time_shell,
|
|
13
13
|
time_transcript_line
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-264QY6KS.js";
|
|
15
15
|
import {
|
|
16
16
|
ENV_FILE_NAME,
|
|
17
17
|
LANE_SEAT_KEY,
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
time_batch_guard
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-36UE53LS.js";
|
|
4
4
|
import {
|
|
5
5
|
hook_decision
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-FNFOLFFV.js";
|
|
7
7
|
import {
|
|
8
8
|
cost_blocks,
|
|
9
9
|
cost_transcript,
|
|
10
10
|
time_bundle_call,
|
|
11
11
|
time_shell,
|
|
12
12
|
time_spans
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-264QY6KS.js";
|
|
14
14
|
|
|
15
15
|
// scripts/delegation/investigation-reads.ts
|
|
16
16
|
import path from "node:path";
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
investigation_reads
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-Y5OYCU2F.js";
|
|
4
4
|
import {
|
|
5
5
|
lane_guard_policy
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-36UE53LS.js";
|
|
7
7
|
import {
|
|
8
8
|
hook_decision
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-FNFOLFFV.js";
|
|
10
10
|
|
|
11
11
|
// scripts/delegation/investigation-guard.ts
|
|
12
12
|
import { text } from "node:stream/consumers";
|
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env tsx
|
|
2
2
|
import {
|
|
3
3
|
pretool_guard
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-4OXFU3MK.js";
|
|
5
|
+
import "./chunk-VGYSA2NT.js";
|
|
6
6
|
import {
|
|
7
7
|
build_envelope,
|
|
8
8
|
compose_context,
|
|
9
9
|
format_edited_payload
|
|
10
|
-
} from "./chunk-
|
|
11
|
-
import "./chunk-
|
|
12
|
-
import "./chunk-
|
|
13
|
-
import "./chunk-
|
|
14
|
-
import "./chunk-
|
|
15
|
-
import "./chunk-
|
|
10
|
+
} from "./chunk-6MQCXEEA.js";
|
|
11
|
+
import "./chunk-FRJKVSK3.js";
|
|
12
|
+
import "./chunk-ZBK4XRAS.js";
|
|
13
|
+
import "./chunk-PRDCNGA6.js";
|
|
14
|
+
import "./chunk-Y5OYCU2F.js";
|
|
15
|
+
import "./chunk-36UE53LS.js";
|
|
16
16
|
import {
|
|
17
17
|
hook_decision
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-FNFOLFFV.js";
|
|
19
19
|
import {
|
|
20
20
|
time_density_hook
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-264QY6KS.js";
|
|
22
22
|
import "./chunk-FXAKJKTB.js";
|
|
23
23
|
import "./chunk-SSX23SJ2.js";
|
|
24
24
|
|
|
@@ -9,11 +9,11 @@ import {
|
|
|
9
9
|
is_enabled,
|
|
10
10
|
notice_refusal_path,
|
|
11
11
|
refusal_path
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import "./chunk-
|
|
14
|
-
import "./chunk-
|
|
15
|
-
import "./chunk-
|
|
16
|
-
import "./chunk-
|
|
12
|
+
} from "./chunk-PRDCNGA6.js";
|
|
13
|
+
import "./chunk-Y5OYCU2F.js";
|
|
14
|
+
import "./chunk-36UE53LS.js";
|
|
15
|
+
import "./chunk-FNFOLFFV.js";
|
|
16
|
+
import "./chunk-264QY6KS.js";
|
|
17
17
|
import "./chunk-FXAKJKTB.js";
|
|
18
18
|
import "./chunk-SSX23SJ2.js";
|
|
19
19
|
export {
|
|
@@ -15,8 +15,8 @@ import {
|
|
|
15
15
|
relative_to_root,
|
|
16
16
|
resolve_invocations,
|
|
17
17
|
select_invocation
|
|
18
|
-
} from "./chunk-
|
|
19
|
-
import "./chunk-
|
|
18
|
+
} from "./chunk-6MQCXEEA.js";
|
|
19
|
+
import "./chunk-264QY6KS.js";
|
|
20
20
|
import "./chunk-SSX23SJ2.js";
|
|
21
21
|
export {
|
|
22
22
|
ESLINT_DAEMON,
|
|
@@ -10,11 +10,11 @@ import {
|
|
|
10
10
|
is_enabled,
|
|
11
11
|
load_environment_file,
|
|
12
12
|
refusal_path
|
|
13
|
-
} from "./chunk-
|
|
14
|
-
import "./chunk-
|
|
15
|
-
import "./chunk-
|
|
16
|
-
import "./chunk-
|
|
17
|
-
import "./chunk-
|
|
13
|
+
} from "./chunk-ZBK4XRAS.js";
|
|
14
|
+
import "./chunk-Y5OYCU2F.js";
|
|
15
|
+
import "./chunk-36UE53LS.js";
|
|
16
|
+
import "./chunk-FNFOLFFV.js";
|
|
17
|
+
import "./chunk-264QY6KS.js";
|
|
18
18
|
import "./chunk-FXAKJKTB.js";
|
|
19
19
|
import "./chunk-SSX23SJ2.js";
|
|
20
20
|
export {
|
|
@@ -3,15 +3,15 @@ import {
|
|
|
3
3
|
pretool_guard,
|
|
4
4
|
pretool_outcome,
|
|
5
5
|
pretool_outcome_async
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
9
|
-
import "./chunk-
|
|
10
|
-
import "./chunk-
|
|
11
|
-
import "./chunk-
|
|
12
|
-
import "./chunk-
|
|
13
|
-
import "./chunk-
|
|
14
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-4OXFU3MK.js";
|
|
7
|
+
import "./chunk-VGYSA2NT.js";
|
|
8
|
+
import "./chunk-FRJKVSK3.js";
|
|
9
|
+
import "./chunk-ZBK4XRAS.js";
|
|
10
|
+
import "./chunk-PRDCNGA6.js";
|
|
11
|
+
import "./chunk-Y5OYCU2F.js";
|
|
12
|
+
import "./chunk-36UE53LS.js";
|
|
13
|
+
import "./chunk-FNFOLFFV.js";
|
|
14
|
+
import "./chunk-264QY6KS.js";
|
|
15
15
|
import "./chunk-FXAKJKTB.js";
|
|
16
16
|
import "./chunk-SSX23SJ2.js";
|
|
17
17
|
export {
|
package/dist/hooks/stop-guard.js
CHANGED
|
@@ -15,20 +15,20 @@ import {
|
|
|
15
15
|
run_hold,
|
|
16
16
|
run_invocation,
|
|
17
17
|
run_watcher_guard
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-VGYSA2NT.js";
|
|
19
19
|
import {
|
|
20
20
|
session_language
|
|
21
21
|
} from "./chunk-6V5OPIUU.js";
|
|
22
22
|
import {
|
|
23
23
|
hook_decision,
|
|
24
24
|
lane_child_marker
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-FNFOLFFV.js";
|
|
26
26
|
import {
|
|
27
27
|
PROJECT_ROOT,
|
|
28
28
|
stamp_file,
|
|
29
29
|
time_density_hook,
|
|
30
30
|
time_hook_transcript
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-264QY6KS.js";
|
|
32
32
|
import "./chunk-FXAKJKTB.js";
|
|
33
33
|
import "./chunk-SSX23SJ2.js";
|
|
34
34
|
|
package/dist/josh.js
CHANGED
|
@@ -1173,6 +1173,12 @@ var PROJECT_COMMANDS = {
|
|
|
1173
1173
|
category: "Project",
|
|
1174
1174
|
reference: ["", "developer", ["files"]]
|
|
1175
1175
|
},
|
|
1176
|
+
"registry:migrate": {
|
|
1177
|
+
script: "scripts/registry-migration/migrate.ts",
|
|
1178
|
+
description: "Migrate a kit-only project from GitHub Packages to public npm",
|
|
1179
|
+
category: "Project",
|
|
1180
|
+
reference: ["", "developer", ["files", "network", "processes"]]
|
|
1181
|
+
},
|
|
1176
1182
|
"sync:scope": {
|
|
1177
1183
|
script: "scripts/sync/managed-config-scope-cli.ts",
|
|
1178
1184
|
description: "Say whether this change touches a file josh sync distributes",
|
|
@@ -1403,6 +1409,7 @@ var ALIASES = {
|
|
|
1403
1409
|
pt: "port",
|
|
1404
1410
|
i: "init",
|
|
1405
1411
|
sy: "sync",
|
|
1412
|
+
rmi: "registry:migrate",
|
|
1406
1413
|
sys: "sync:scope",
|
|
1407
1414
|
g: "git",
|
|
1408
1415
|
gp: "pr",
|
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
|
|
|
@@ -102,6 +102,7 @@ exports[`COMMAND_MAP — reference metadata > keeps every canonical command and
|
|
|
102
102
|
"rh": "run:hold",
|
|
103
103
|
"rl": "report:lint",
|
|
104
104
|
"rmg": "run:merge",
|
|
105
|
+
"rmi": "registry:migrate",
|
|
105
106
|
"rn": "run:next",
|
|
106
107
|
"rp": "run:prep",
|
|
107
108
|
"rpy": "repo:party",
|
|
@@ -787,6 +788,15 @@ exports[`COMMAND_MAP — reference metadata > keeps every canonical command and
|
|
|
787
788
|
"processes",
|
|
788
789
|
],
|
|
789
790
|
],
|
|
791
|
+
"registry:migrate": [
|
|
792
|
+
"",
|
|
793
|
+
"developer",
|
|
794
|
+
[
|
|
795
|
+
"files",
|
|
796
|
+
"network",
|
|
797
|
+
"processes",
|
|
798
|
+
],
|
|
799
|
+
],
|
|
790
800
|
"release": [
|
|
791
801
|
"[version]",
|
|
792
802
|
"maintainer",
|
|
@@ -17,6 +17,12 @@ const PROJECT_COMMANDS: Record<string, CommandEntry> = {
|
|
|
17
17
|
category: 'Project',
|
|
18
18
|
reference: ['', 'developer', ['files']],
|
|
19
19
|
},
|
|
20
|
+
'registry:migrate': {
|
|
21
|
+
script: 'scripts/registry-migration/migrate.ts',
|
|
22
|
+
description: 'Migrate a kit-only project from GitHub Packages to public npm',
|
|
23
|
+
category: 'Project',
|
|
24
|
+
reference: ['', 'developer', ['files', 'network', 'processes']],
|
|
25
|
+
},
|
|
20
26
|
'sync:scope': {
|
|
21
27
|
script: 'scripts/sync/managed-config-scope-cli.ts',
|
|
22
28
|
description: 'Say whether this change touches a file josh sync distributes',
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { fix_gh_packages_logic } from '#scripts/gh/fix-gh-packages-logic'
|
|
2
|
+
|
|
3
|
+
const SCOPE = '@joshuafolkken'
|
|
4
|
+
const REGISTRY_KEY = `${SCOPE}:registry`
|
|
5
|
+
const PUBLIC_REGISTRY = 'https://registry.npmjs.org/'
|
|
6
|
+
const GITHUB_REGISTRY = 'https://npm.pkg.github.com/'
|
|
7
|
+
const MAPPING = `${REGISTRY_KEY}=${PUBLIC_REGISTRY}`
|
|
8
|
+
const REGISTRY_VALUE_INDEX = 1
|
|
9
|
+
const SPLIT_LIMIT = 2
|
|
10
|
+
|
|
11
|
+
interface MigrationPlan {
|
|
12
|
+
content: string
|
|
13
|
+
packages: ReadonlyArray<string>
|
|
14
|
+
blocked: ReadonlyArray<string>
|
|
15
|
+
change: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type IntegrityPair = readonly [string, string]
|
|
19
|
+
|
|
20
|
+
function normalized_registry(value: string): string {
|
|
21
|
+
let normalized = value.trim()
|
|
22
|
+
while (normalized.endsWith('/')) normalized = normalized.slice(0, -1)
|
|
23
|
+
|
|
24
|
+
return `${normalized}/`
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function mapping_lines(content: string): ReadonlyArray<string> {
|
|
28
|
+
return content.split(/\r?\n/u).filter((line) => line.trimStart().startsWith(`${REGISTRY_KEY}=`))
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function registry_of(content: string): string | undefined {
|
|
32
|
+
const lines = mapping_lines(content)
|
|
33
|
+
if (lines.length !== 1) return undefined
|
|
34
|
+
|
|
35
|
+
return normalized_registry(lines[0]?.split('=', SPLIT_LIMIT)[REGISTRY_VALUE_INDEX] ?? '')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function scoped_packages(lockfile: string): ReadonlyArray<string> {
|
|
39
|
+
const packages = fix_gh_packages_logic.parse_lockfile_packages(lockfile)
|
|
40
|
+
|
|
41
|
+
return Object.keys(packages)
|
|
42
|
+
.filter((key) => key.startsWith(`${SCOPE}/`))
|
|
43
|
+
.map((key) => fix_gh_packages_logic.package_path_from_key(key))
|
|
44
|
+
.filter((name, index, names) => names.indexOf(name) === index)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function scoped_versions(lockfile: string): ReadonlyArray<readonly [string, string]> {
|
|
48
|
+
const packages = fix_gh_packages_logic.parse_lockfile_packages(lockfile)
|
|
49
|
+
|
|
50
|
+
return Object.keys(packages)
|
|
51
|
+
.filter((key) => key.startsWith(`${SCOPE}/`))
|
|
52
|
+
.map(
|
|
53
|
+
(key) =>
|
|
54
|
+
[
|
|
55
|
+
fix_gh_packages_logic.package_path_from_key(key),
|
|
56
|
+
fix_gh_packages_logic.package_version_from_key(key),
|
|
57
|
+
] as const,
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function is_github_tarball(value: string): boolean {
|
|
62
|
+
try {
|
|
63
|
+
const url = new URL(value)
|
|
64
|
+
|
|
65
|
+
return url.protocol === 'https:' && url.hostname === 'npm.pkg.github.com'
|
|
66
|
+
} catch {
|
|
67
|
+
return false
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function github_tarballs(lockfile: string): ReadonlyArray<string> {
|
|
72
|
+
const packages = fix_gh_packages_logic.parse_lockfile_packages(lockfile)
|
|
73
|
+
|
|
74
|
+
return Object.entries(packages)
|
|
75
|
+
.filter(
|
|
76
|
+
([key, entry]) =>
|
|
77
|
+
key.startsWith(`${SCOPE}/`) &&
|
|
78
|
+
entry.resolution?.tarball !== undefined &&
|
|
79
|
+
is_github_tarball(entry.resolution.tarball),
|
|
80
|
+
)
|
|
81
|
+
.map(([key]) => key)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function remove_flow_tarball(line: string): string {
|
|
85
|
+
const marker = ', tarball: https://npm.pkg.github.com/'
|
|
86
|
+
const start = line.indexOf(marker)
|
|
87
|
+
if (start === -1) return line
|
|
88
|
+
const end = line.indexOf('}', start)
|
|
89
|
+
if (end === -1) return line
|
|
90
|
+
|
|
91
|
+
return `${line.slice(0, start)}${line.slice(end)}`
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function is_package_entry(line: string): boolean {
|
|
95
|
+
return /^ {2}[^ ]/u.test(line)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function is_github_tarball_line(line: string): boolean {
|
|
99
|
+
return line.trimStart().startsWith('tarball: https://npm.pkg.github.com/')
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function clean_kit_line(
|
|
103
|
+
line: string,
|
|
104
|
+
version: string,
|
|
105
|
+
integrities: ReadonlyMap<string, IntegrityPair>,
|
|
106
|
+
): string | undefined {
|
|
107
|
+
if (!version) return line
|
|
108
|
+
if (is_github_tarball_line(line)) return undefined
|
|
109
|
+
const cleaned = remove_flow_tarball(line)
|
|
110
|
+
const pair = integrities.get(version)
|
|
111
|
+
if (pair === undefined) return cleaned
|
|
112
|
+
|
|
113
|
+
return cleaned.split(`integrity: ${pair[0]}`).join(`integrity: ${pair[1]}`)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function next_kit_version(line: string, version: string): string {
|
|
117
|
+
if (!is_package_entry(line)) return version
|
|
118
|
+
const prefix = " '@joshuafolkken/kit@"
|
|
119
|
+
|
|
120
|
+
return line.startsWith(prefix) ? (line.slice(prefix.length).split("'", 1)[0] ?? '') : ''
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function integrity_pair(
|
|
124
|
+
old_integrity: string | undefined,
|
|
125
|
+
npm_integrity: string | undefined,
|
|
126
|
+
): IntegrityPair | undefined {
|
|
127
|
+
if (old_integrity === undefined || npm_integrity === undefined) return undefined
|
|
128
|
+
|
|
129
|
+
return [old_integrity, npm_integrity]
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function lock_integrities(
|
|
133
|
+
lockfile: string,
|
|
134
|
+
npm_integrities: ReadonlyMap<string, string>,
|
|
135
|
+
): Map<string, IntegrityPair> {
|
|
136
|
+
const packages = fix_gh_packages_logic.parse_lockfile_packages(lockfile)
|
|
137
|
+
const pairs = new Map<string, IntegrityPair>()
|
|
138
|
+
const kit_entries = Object.entries(packages).filter(([key]) => key.startsWith(`${SCOPE}/kit@`))
|
|
139
|
+
|
|
140
|
+
for (const [key, entry] of kit_entries) {
|
|
141
|
+
const version = fix_gh_packages_logic.package_version_from_key(key)
|
|
142
|
+
const pair = integrity_pair(entry.resolution?.integrity, npm_integrities.get(version))
|
|
143
|
+
if (pair !== undefined) pairs.set(version, pair)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return pairs
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function push_if_defined(lines: Array<string>, cleaned: string | undefined): void {
|
|
150
|
+
if (cleaned !== undefined) lines.push(cleaned)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function rewrite_kit_lockfile(
|
|
154
|
+
lockfile: string,
|
|
155
|
+
npm_integrities: ReadonlyMap<string, string>,
|
|
156
|
+
): string {
|
|
157
|
+
let kit_version = ''
|
|
158
|
+
const lines: Array<string> = []
|
|
159
|
+
const integrities = lock_integrities(lockfile, npm_integrities)
|
|
160
|
+
|
|
161
|
+
for (const line of lockfile.split('\n')) {
|
|
162
|
+
kit_version = next_kit_version(line, kit_version)
|
|
163
|
+
const cleaned = clean_kit_line(line, kit_version, integrities)
|
|
164
|
+
|
|
165
|
+
push_if_defined(lines, cleaned)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return lines.join('\n')
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function append_mapping(content: string, eol: string): string {
|
|
172
|
+
const separator = content && !content.endsWith('\n') ? eol : ''
|
|
173
|
+
|
|
174
|
+
return `${content}${separator}${MAPPING}${eol}`
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function replace_mapping(content: string): string {
|
|
178
|
+
const lines = mapping_lines(content)
|
|
179
|
+
const eol = content.includes('\r\n') ? '\r\n' : '\n'
|
|
180
|
+
|
|
181
|
+
if (lines.length === 0) return append_mapping(content, eol)
|
|
182
|
+
|
|
183
|
+
return content
|
|
184
|
+
.split(/\r?\n/u)
|
|
185
|
+
.map((line) => (line === lines[0] ? MAPPING : line))
|
|
186
|
+
.join(eol)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function unsupported_registry(project_npmrc: string): string | undefined {
|
|
190
|
+
const registry = registry_of(project_npmrc)
|
|
191
|
+
|
|
192
|
+
if (registry === undefined || [GITHUB_REGISTRY, PUBLIC_REGISTRY].includes(registry)) {
|
|
193
|
+
return undefined
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return `unsupported project registry: ${registry}`
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function has_duplicate_mapping(project_npmrc: string, user_npmrc: string): boolean {
|
|
200
|
+
return mapping_lines(project_npmrc).length > 1 || mapping_lines(user_npmrc).length > 1
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function previous_registry(project_npmrc: string, user_npmrc: string): string {
|
|
204
|
+
return registry_of(project_npmrc) ?? registry_of(user_npmrc) ?? 'default npm'
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function plan(project_npmrc: string, user_npmrc: string, lockfile: string): MigrationPlan {
|
|
208
|
+
const packages = scoped_packages(lockfile)
|
|
209
|
+
const blocked = packages.filter((name) => name !== `${SCOPE}/kit`)
|
|
210
|
+
if (has_duplicate_mapping(project_npmrc, user_npmrc)) blocked.push('duplicate registry mappings')
|
|
211
|
+
const unsupported = unsupported_registry(project_npmrc)
|
|
212
|
+
if (unsupported !== undefined) blocked.push(unsupported)
|
|
213
|
+
const before = previous_registry(project_npmrc, user_npmrc)
|
|
214
|
+
const change = `${before} → ${PUBLIC_REGISTRY}`
|
|
215
|
+
|
|
216
|
+
return { content: replace_mapping(project_npmrc), packages, blocked, change }
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const migrate_logic = {
|
|
220
|
+
plan,
|
|
221
|
+
github_tarballs,
|
|
222
|
+
rewrite_kit_lockfile,
|
|
223
|
+
registry_of,
|
|
224
|
+
scoped_packages,
|
|
225
|
+
scoped_versions,
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export { migrate_logic }
|
|
229
|
+
export type { MigrationPlan }
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import { existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
import { execa } from 'execa'
|
|
5
|
+
import { z } from 'zod'
|
|
6
|
+
import { migrate_logic, type MigrationPlan } from './migrate-logic'
|
|
7
|
+
import { user_npmrc } from './user-npmrc'
|
|
8
|
+
|
|
9
|
+
const NPMRC = '.npmrc'
|
|
10
|
+
const LOCKFILE = 'pnpm-lock.yaml'
|
|
11
|
+
const WORKSPACE = 'pnpm-workspace.yaml'
|
|
12
|
+
const PUBLIC_REGISTRY = 'https://registry.npmjs.org/'
|
|
13
|
+
const FETCH_TIMEOUT_MS = 10_000
|
|
14
|
+
const SCOPE = '@joshuafolkken/'
|
|
15
|
+
const manifest_schema = z.looseObject({
|
|
16
|
+
dependencies: z.record(z.string(), z.string()).optional(),
|
|
17
|
+
devDependencies: z.record(z.string(), z.string()).optional(),
|
|
18
|
+
optionalDependencies: z.record(z.string(), z.string()).optional(),
|
|
19
|
+
peerDependencies: z.record(z.string(), z.string()).optional(),
|
|
20
|
+
})
|
|
21
|
+
const version_schema = z.looseObject({
|
|
22
|
+
dist: z.looseObject({ integrity: z.string(), tarball: z.string() }),
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
function is_public_npm_url(value: string): boolean {
|
|
26
|
+
try {
|
|
27
|
+
const url = new URL(value)
|
|
28
|
+
|
|
29
|
+
return url.origin === 'https://registry.npmjs.org' && url.username === '' && url.password === ''
|
|
30
|
+
} catch {
|
|
31
|
+
return false
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface VersionCheck {
|
|
36
|
+
missing: ReadonlyArray<string>
|
|
37
|
+
integrities: ReadonlyMap<string, string>
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function probe_integrity(
|
|
41
|
+
name: string,
|
|
42
|
+
version: string,
|
|
43
|
+
probe_version: MigrationDependencies['fetch_version'],
|
|
44
|
+
): Promise<string | undefined> {
|
|
45
|
+
if (!version) return undefined
|
|
46
|
+
|
|
47
|
+
return await probe_version(name, version)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface MigrationDependencies {
|
|
51
|
+
fetch_version: (name: string, version: string) => Promise<string | undefined>
|
|
52
|
+
install: (cwd: string) => Promise<void>
|
|
53
|
+
effective_registry: (cwd: string) => Promise<string>
|
|
54
|
+
user_npmrc: string
|
|
55
|
+
environment: NodeJS.ProcessEnv
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
interface MigrationContext {
|
|
59
|
+
cwd: string
|
|
60
|
+
npmrc_path: string
|
|
61
|
+
lockfile_path: string
|
|
62
|
+
workspace_path: string
|
|
63
|
+
original_npmrc: string
|
|
64
|
+
original_lockfile: string
|
|
65
|
+
original_workspace: string
|
|
66
|
+
has_npmrc: boolean
|
|
67
|
+
has_lockfile: boolean
|
|
68
|
+
has_workspace: boolean
|
|
69
|
+
plan: MigrationPlan
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function read_file(file_path: string): string {
|
|
73
|
+
return existsSync(file_path) ? readFileSync(file_path, 'utf8') : ''
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function has_registry_override(environment: NodeJS.ProcessEnv): boolean {
|
|
77
|
+
return Object.keys(environment).some((key) =>
|
|
78
|
+
/^(?:npm|pnpm)_config_.*(?:joshuafolkken.*registry|userconfig)/iu.test(key),
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function manifest_packages(content: string): ReadonlyArray<string> {
|
|
83
|
+
const manifest = manifest_schema.parse(JSON.parse(content))
|
|
84
|
+
const sections = ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies']
|
|
85
|
+
|
|
86
|
+
return sections.flatMap((section) => {
|
|
87
|
+
const value: unknown = Reflect.get(manifest, section)
|
|
88
|
+
if (typeof value !== 'object' || value === null) return []
|
|
89
|
+
|
|
90
|
+
return Object.keys(value).filter((name) => name.startsWith(SCOPE))
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function restore(context: MigrationContext): void {
|
|
95
|
+
if (context.has_npmrc) writeFileSync(context.npmrc_path, context.original_npmrc)
|
|
96
|
+
else rmSync(context.npmrc_path, { force: true })
|
|
97
|
+
writeFileSync(context.lockfile_path, context.original_lockfile)
|
|
98
|
+
if (context.has_workspace) writeFileSync(context.workspace_path, context.original_workspace)
|
|
99
|
+
else rmSync(context.workspace_path, { force: true })
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function verify_public_versions(
|
|
103
|
+
lockfile: string,
|
|
104
|
+
probe_version: MigrationDependencies['fetch_version'],
|
|
105
|
+
): Promise<VersionCheck> {
|
|
106
|
+
const missing: Array<string> = []
|
|
107
|
+
const integrities = new Map<string, string>()
|
|
108
|
+
|
|
109
|
+
for (const [name, version] of migrate_logic.scoped_versions(lockfile)) {
|
|
110
|
+
const integrity = await probe_integrity(name, version, probe_version)
|
|
111
|
+
if (integrity === undefined) missing.push(`${name}@${version}`)
|
|
112
|
+
else integrities.set(version, integrity)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return { missing, integrities }
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function report_context(context: MigrationContext): void {
|
|
119
|
+
console.info(`Current registry and planned change: ${context.plan.change}`)
|
|
120
|
+
console.info(`Installed scoped packages: ${context.plan.packages.join(', ') || '(none)'}`)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function create_context(cwd: string, dependencies: MigrationDependencies): MigrationContext {
|
|
124
|
+
const npmrc_path = path.join(cwd, NPMRC)
|
|
125
|
+
const lockfile_path = path.join(cwd, LOCKFILE)
|
|
126
|
+
const workspace_path = path.join(cwd, WORKSPACE)
|
|
127
|
+
const original_npmrc = read_file(npmrc_path)
|
|
128
|
+
const original_lockfile = read_file(lockfile_path)
|
|
129
|
+
const original_workspace = read_file(workspace_path)
|
|
130
|
+
const plan = migrate_logic.plan(original_npmrc, dependencies.user_npmrc, original_lockfile)
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
cwd,
|
|
134
|
+
npmrc_path,
|
|
135
|
+
lockfile_path,
|
|
136
|
+
workspace_path,
|
|
137
|
+
original_npmrc,
|
|
138
|
+
original_lockfile,
|
|
139
|
+
original_workspace,
|
|
140
|
+
plan,
|
|
141
|
+
has_npmrc: existsSync(npmrc_path),
|
|
142
|
+
has_lockfile: existsSync(lockfile_path),
|
|
143
|
+
has_workspace: existsSync(workspace_path),
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function initial_block(
|
|
148
|
+
context: MigrationContext,
|
|
149
|
+
dependencies: MigrationDependencies,
|
|
150
|
+
): string | undefined {
|
|
151
|
+
if (!context.has_lockfile) return 'No lockfile; run pnpm install before migrating.'
|
|
152
|
+
const manifest = manifest_packages(read_file(path.join(context.cwd, 'package.json')))
|
|
153
|
+
const missing_lock = manifest.filter((name) => !context.plan.packages.includes(name))
|
|
154
|
+
const blocked = [...context.plan.blocked, ...missing_lock]
|
|
155
|
+
if (!context.plan.packages.includes(`${SCOPE}kit`)) blocked.push('kit is absent from lockfile')
|
|
156
|
+
if (has_registry_override(dependencies.environment)) blocked.push('registry environment override')
|
|
157
|
+
if (blocked.length === 0) return undefined
|
|
158
|
+
|
|
159
|
+
return `Migration blocked: ${blocked.join(', ')}. Keep GitHub Packages until every package is available on npm.`
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async function verify_resolution(
|
|
163
|
+
context: MigrationContext,
|
|
164
|
+
dependencies: MigrationDependencies,
|
|
165
|
+
integrities: ReadonlyMap<string, string>,
|
|
166
|
+
): Promise<void> {
|
|
167
|
+
await dependencies.install(context.cwd)
|
|
168
|
+
|
|
169
|
+
if (read_file(context.workspace_path) !== context.original_workspace) {
|
|
170
|
+
throw new Error('pnpm changed pnpm-workspace.yaml outside the migration')
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const actual = await dependencies.effective_registry(context.cwd)
|
|
174
|
+
if (!is_public_npm_url(actual)) throw new Error(`effective registry is ${actual}`)
|
|
175
|
+
const lockfile = read_file(context.lockfile_path)
|
|
176
|
+
const old_tarballs = migrate_logic.github_tarballs(lockfile)
|
|
177
|
+
if (old_tarballs.length > 0) throw new Error(`GitHub tarballs remain: ${old_tarballs.join(', ')}`)
|
|
178
|
+
|
|
179
|
+
if (migrate_logic.rewrite_kit_lockfile(lockfile, integrities) !== lockfile) {
|
|
180
|
+
throw new Error('lockfile integrity does not match public npm')
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async function apply(
|
|
185
|
+
context: MigrationContext,
|
|
186
|
+
dependencies: MigrationDependencies,
|
|
187
|
+
integrities: ReadonlyMap<string, string>,
|
|
188
|
+
): Promise<string> {
|
|
189
|
+
try {
|
|
190
|
+
writeFileSync(context.npmrc_path, context.plan.content)
|
|
191
|
+
writeFileSync(
|
|
192
|
+
context.lockfile_path,
|
|
193
|
+
migrate_logic.rewrite_kit_lockfile(context.original_lockfile, integrities),
|
|
194
|
+
)
|
|
195
|
+
await verify_resolution(context, dependencies, integrities)
|
|
196
|
+
} catch (error) {
|
|
197
|
+
restore(context)
|
|
198
|
+
|
|
199
|
+
return `Migration blocked; original settings restored: ${String(error)}`
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return 'Migrated to public npm. The project registry and lockfile now resolve from npm.'
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function is_already_migrated(
|
|
206
|
+
context: MigrationContext,
|
|
207
|
+
integrities: ReadonlyMap<string, string>,
|
|
208
|
+
): boolean {
|
|
209
|
+
return (
|
|
210
|
+
context.plan.content === context.original_npmrc &&
|
|
211
|
+
migrate_logic.rewrite_kit_lockfile(context.original_lockfile, integrities) ===
|
|
212
|
+
context.original_lockfile
|
|
213
|
+
)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async function migrate(cwd: string, dependencies: MigrationDependencies): Promise<string> {
|
|
217
|
+
const context = create_context(cwd, dependencies)
|
|
218
|
+
|
|
219
|
+
report_context(context)
|
|
220
|
+
const block = initial_block(context, dependencies)
|
|
221
|
+
if (block !== undefined) return block
|
|
222
|
+
const version_check = await verify_public_versions(
|
|
223
|
+
context.original_lockfile,
|
|
224
|
+
dependencies.fetch_version,
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
if (version_check.missing.length > 0) {
|
|
228
|
+
return `Migration blocked: unpublished on npm: ${version_check.missing.join(', ')}.`
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (is_already_migrated(context, version_check.integrities)) {
|
|
232
|
+
return 'Already using public npm; no changes made.'
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return await apply(context, dependencies, version_check.integrities)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async function fetch_version(name: string, version: string): Promise<string | undefined> {
|
|
239
|
+
const encoded = encodeURIComponent(name)
|
|
240
|
+
const response = await fetch(`${PUBLIC_REGISTRY}${encoded}/${encodeURIComponent(version)}`, {
|
|
241
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
if (!response.ok) return undefined
|
|
245
|
+
const parsed = version_schema.safeParse(await response.json())
|
|
246
|
+
if (!parsed.success || !is_public_npm_url(parsed.data.dist.tarball)) return undefined
|
|
247
|
+
|
|
248
|
+
return parsed.data.dist.integrity
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async function install(cwd: string): Promise<void> {
|
|
252
|
+
await execa('pnpm', ['install', '--lockfile-only', '--ignore-scripts', '--force'], { cwd })
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async function effective_registry(cwd: string): Promise<string> {
|
|
256
|
+
const result = await execa('pnpm', ['config', 'get', '@joshuafolkken:registry'], { cwd })
|
|
257
|
+
|
|
258
|
+
return result.stdout.trim()
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async function main(): Promise<void> {
|
|
262
|
+
const result = await migrate(process.cwd(), {
|
|
263
|
+
fetch_version,
|
|
264
|
+
install,
|
|
265
|
+
effective_registry,
|
|
266
|
+
user_npmrc: user_npmrc.read(),
|
|
267
|
+
environment: process.env,
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
console.info(result)
|
|
271
|
+
|
|
272
|
+
if (result.startsWith('Migration blocked') || result.startsWith('No lockfile')) process.exit(1)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) await main()
|
|
276
|
+
|
|
277
|
+
const registry_migration = { migrate }
|
|
278
|
+
|
|
279
|
+
export { registry_migration }
|
|
280
|
+
export type { MigrationDependencies }
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
2
|
+
import os from 'node:os'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
|
|
5
|
+
const NPMRC = '.npmrc'
|
|
6
|
+
|
|
7
|
+
function read(home: string = os.homedir()): string {
|
|
8
|
+
const file = path.join(home, NPMRC)
|
|
9
|
+
|
|
10
|
+
return existsSync(file) ? readFileSync(file, 'utf8') : ''
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const user_npmrc = { read }
|
|
14
|
+
|
|
15
|
+
export { user_npmrc }
|
|
@@ -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:
|