@awiki/cli 0.0.1-beta.2
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/release.yml +44 -0
- package/.goreleaser.yml +44 -0
- package/AGENTS.md +60 -0
- package/CLAUDE.md +192 -0
- package/README.md +2 -0
- package/docs/architecture/awiki-command-v2.md +955 -0
- package/docs/architecture/awiki-skill-architecture.md +475 -0
- package/docs/architecture/awiki-v2-architecture.md +1063 -0
- package/docs/architecture//345/217/202/350/200/203/346/226/207/346/241/243/cli-init.md +1008 -0
- package/docs/architecture//345/217/202/350/200/203/346/226/207/346/241/243/output-format.md +407 -0
- package/docs/architecture//345/217/202/350/200/203/346/226/207/346/241/243/overall-init.md +741 -0
- package/docs/harness/review-spec.md +474 -0
- package/docs/installation.md +372 -0
- package/docs/plan/awiki-v2-implementation-plan.md +903 -0
- package/docs/plan/phase-0/adr-index.md +56 -0
- package/docs/plan/phase-0/audit-findings.md +251 -0
- package/docs/plan/phase-0/capability-mapping.md +108 -0
- package/docs/plan/phase-0/implementation-constraints.md +363 -0
- package/docs/publish.md +169 -0
- package/go.mod +29 -0
- package/go.sum +73 -0
- package/internal/anpsdk/registry.go +63 -0
- package/internal/authsdk/session.go +351 -0
- package/internal/buildinfo/buildinfo.go +34 -0
- package/internal/cli/app.go +136 -0
- package/internal/cli/app_test.go +88 -0
- package/internal/cli/debug.go +104 -0
- package/internal/cli/group.go +263 -0
- package/internal/cli/id.go +473 -0
- package/internal/cli/init.go +134 -0
- package/internal/cli/msg.go +228 -0
- package/internal/cli/page.go +267 -0
- package/internal/cli/root.go +499 -0
- package/internal/cli/runtime.go +232 -0
- package/internal/cli/upgrade.go +60 -0
- package/internal/cmdmeta/catalog.go +203 -0
- package/internal/cmdmeta/catalog_test.go +21 -0
- package/internal/config/config.go +399 -0
- package/internal/config/config_test.go +104 -0
- package/internal/config/write.go +37 -0
- package/internal/content/service.go +314 -0
- package/internal/content/service_test.go +165 -0
- package/internal/content/types.go +44 -0
- package/internal/docs/topics.go +110 -0
- package/internal/doctor/doctor.go +306 -0
- package/internal/identity/client.go +267 -0
- package/internal/identity/did.go +85 -0
- package/internal/identity/did_test.go +50 -0
- package/internal/identity/layout.go +206 -0
- package/internal/identity/legacy.go +378 -0
- package/internal/identity/public.go +70 -0
- package/internal/identity/public_test.go +73 -0
- package/internal/identity/readiness.go +74 -0
- package/internal/identity/service.go +826 -0
- package/internal/identity/store.go +385 -0
- package/internal/identity/store_test.go +180 -0
- package/internal/identity/types.go +204 -0
- package/internal/message/auth.go +167 -0
- package/internal/message/group_service.go +838 -0
- package/internal/message/group_wire.go +350 -0
- package/internal/message/group_wire_test.go +67 -0
- package/internal/message/helpers.go +61 -0
- package/internal/message/http_client.go +334 -0
- package/internal/message/proof.go +156 -0
- package/internal/message/proof_test.go +61 -0
- package/internal/message/service.go +696 -0
- package/internal/message/service_test.go +97 -0
- package/internal/message/types.go +155 -0
- package/internal/message/wire.go +100 -0
- package/internal/message/wire_test.go +49 -0
- package/internal/message/ws_proxy_client.go +151 -0
- package/internal/output/output.go +350 -0
- package/internal/output/output_test.go +48 -0
- package/internal/runtime/config.go +117 -0
- package/internal/runtime/config_test.go +46 -0
- package/internal/runtime/listener/files.go +65 -0
- package/internal/runtime/listener/manager.go +142 -0
- package/internal/runtime/listener/server.go +983 -0
- package/internal/runtime/listener/server_test.go +319 -0
- package/internal/runtime/listener/sysproc_unix.go +17 -0
- package/internal/runtime/listener/sysproc_windows.go +13 -0
- package/internal/runtime/listener/types.go +21 -0
- package/internal/runtime/listener/wsclient.go +299 -0
- package/internal/runtime/listener/wsclient_test.go +41 -0
- package/internal/store/dao.go +632 -0
- package/internal/store/dao_test.go +87 -0
- package/internal/store/helpers.go +197 -0
- package/internal/store/import.go +499 -0
- package/internal/store/import_test.go +103 -0
- package/internal/store/open.go +71 -0
- package/internal/store/query.go +151 -0
- package/internal/store/schema.go +277 -0
- package/internal/store/schema_test.go +56 -0
- package/internal/store/types.go +177 -0
- package/internal/update/update.go +368 -0
- package/package.json +17 -0
- package/scripts/install.js +171 -0
- package/scripts/release/release-prerelease.sh +86 -0
- package/scripts/release/tag-release.sh +66 -0
- package/scripts/release/withdraw-release.sh +78 -0
- package/scripts/run.js +69 -0
- package/skills/README.md +32 -0
- package/skills/awiki-bundle/SKILL.md +76 -0
- package/skills/awiki-debug/SKILL.md +80 -0
- package/skills/awiki-group/SKILL.md +111 -0
- package/skills/awiki-id/SKILL.md +123 -0
- package/skills/awiki-msg/SKILL.md +131 -0
- package/skills/awiki-page/SKILL.md +93 -0
- package/skills/awiki-people/SKILL.md +66 -0
- package/skills/awiki-runtime/SKILL.md +137 -0
- package/skills/awiki-shared/SKILL.md +124 -0
- package/skills/awiki-workflow-discovery/SKILL.md +93 -0
- package/skills/awiki-workflow-onboarding/SKILL.md +119 -0
- package/skills/manifests/skills.yaml +260 -0
- package/skills/templates/bundle-skill-template.md +42 -0
- package/skills/templates/debug-skill-template.md +44 -0
- package/skills/templates/domain-skill-template.md +56 -0
- package/skills/templates/shared-skill-template.md +46 -0
- package/skills/templates/workflow-skill-template.md +46 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
package identity
|
|
2
|
+
|
|
3
|
+
import "errors"
|
|
4
|
+
|
|
5
|
+
const (
|
|
6
|
+
IndexSchemaVersion = 3
|
|
7
|
+
IndexFileName = "index.json"
|
|
8
|
+
LegacyBackupDirName = ".legacy-backup"
|
|
9
|
+
IdentityFileName = "identity.json"
|
|
10
|
+
AuthFileName = "auth.json"
|
|
11
|
+
DIDDocumentFileName = "did_document.json"
|
|
12
|
+
Key1PrivateFileName = "key-1-private.pem"
|
|
13
|
+
Key1PublicFileName = "key-1-public.pem"
|
|
14
|
+
E2EESigningPrivateFileName = "e2ee-signing-private.pem"
|
|
15
|
+
E2EEAgreementPrivateFileName = "e2ee-agreement-private.pem"
|
|
16
|
+
E2EEStateFileName = "e2ee-state.json"
|
|
17
|
+
LegacyE2EEPrefix = "e2ee_"
|
|
18
|
+
LegacyLayoutHint = "Legacy credential layout detected. Import it before relying on the v2 identity store."
|
|
19
|
+
DefaultEmailVerificationSecs = 300
|
|
20
|
+
DefaultEmailPollIntervalSecs = 5
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
var (
|
|
24
|
+
ErrIdentityNotFound = errors.New("identity not found")
|
|
25
|
+
ErrIdentityConflict = errors.New("identity conflict")
|
|
26
|
+
ErrNoDefaultIdentity = errors.New("no default identity")
|
|
27
|
+
ErrLegacyNotFound = errors.New("legacy identity not found")
|
|
28
|
+
ErrInvalidInput = errors.New("invalid input")
|
|
29
|
+
ErrAuthRequired = errors.New("authentication required")
|
|
30
|
+
ErrUserRegistrationRequired = errors.New("registered handle user is required")
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
type IndexEntry struct {
|
|
34
|
+
CredentialName string `json:"credential_name"`
|
|
35
|
+
DirName string `json:"dir_name"`
|
|
36
|
+
DID string `json:"did"`
|
|
37
|
+
UniqueID string `json:"unique_id"`
|
|
38
|
+
UserID string `json:"user_id,omitempty"`
|
|
39
|
+
Name string `json:"name,omitempty"`
|
|
40
|
+
Handle string `json:"handle,omitempty"`
|
|
41
|
+
CreatedAt string `json:"created_at,omitempty"`
|
|
42
|
+
IsDefault bool `json:"is_default,omitempty"`
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
type IndexPayload struct {
|
|
46
|
+
SchemaVersion int `json:"schema_version"`
|
|
47
|
+
DefaultCredentialName string `json:"default_credential_name,omitempty"`
|
|
48
|
+
Credentials map[string]IndexEntry `json:"credentials"`
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
type Paths struct {
|
|
52
|
+
RootDir string `json:"root_dir"`
|
|
53
|
+
DirName string `json:"dir_name"`
|
|
54
|
+
IdentityDir string `json:"identity_dir"`
|
|
55
|
+
IdentityPath string `json:"identity_path"`
|
|
56
|
+
AuthPath string `json:"auth_path"`
|
|
57
|
+
DIDDocumentPath string `json:"did_document_path"`
|
|
58
|
+
Key1PrivatePath string `json:"key1_private_path"`
|
|
59
|
+
Key1PublicPath string `json:"key1_public_path"`
|
|
60
|
+
E2EESigningPrivatePath string `json:"e2ee_signing_private_path"`
|
|
61
|
+
E2EEAgreementPrivatePath string `json:"e2ee_agreement_private_path"`
|
|
62
|
+
E2EEStatePath string `json:"e2ee_state_path"`
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
type StoredIdentity struct {
|
|
66
|
+
IdentityName string `json:"identity_name"`
|
|
67
|
+
DirName string `json:"dir_name"`
|
|
68
|
+
DID string `json:"did"`
|
|
69
|
+
UniqueID string `json:"unique_id"`
|
|
70
|
+
UserID string `json:"user_id,omitempty"`
|
|
71
|
+
DisplayName string `json:"display_name,omitempty"`
|
|
72
|
+
Handle string `json:"handle,omitempty"`
|
|
73
|
+
CreatedAt string `json:"created_at,omitempty"`
|
|
74
|
+
JWTToken string `json:"jwt_token,omitempty"`
|
|
75
|
+
DIDDocument map[string]any `json:"did_document,omitempty"`
|
|
76
|
+
Key1PrivatePEM string `json:"key1_private_pem,omitempty"`
|
|
77
|
+
Key1PublicPEM string `json:"key1_public_pem,omitempty"`
|
|
78
|
+
E2EESigningPrivatePEM string `json:"e2ee_signing_private_pem,omitempty"`
|
|
79
|
+
E2EEAgreementPrivatePEM string `json:"e2ee_agreement_private_pem,omitempty"`
|
|
80
|
+
IsDefault bool `json:"is_default,omitempty"`
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
type IdentitySummary struct {
|
|
84
|
+
IdentityName string `json:"identity_name"`
|
|
85
|
+
DID string `json:"did"`
|
|
86
|
+
UniqueID string `json:"unique_id"`
|
|
87
|
+
UserID string `json:"user_id,omitempty"`
|
|
88
|
+
DisplayName string `json:"display_name,omitempty"`
|
|
89
|
+
Handle string `json:"handle,omitempty"`
|
|
90
|
+
CreatedAt string `json:"created_at,omitempty"`
|
|
91
|
+
DirName string `json:"dir_name"`
|
|
92
|
+
IsDefault bool `json:"is_default"`
|
|
93
|
+
HasJWT bool `json:"has_jwt"`
|
|
94
|
+
HasDIDDocument bool `json:"has_did_document"`
|
|
95
|
+
HasKey1Private bool `json:"has_key1_private"`
|
|
96
|
+
HasKey1Public bool `json:"has_key1_public"`
|
|
97
|
+
HasE2EESigningPrivate bool `json:"has_e2ee_signing_private"`
|
|
98
|
+
HasE2EEAgreementPrivate bool `json:"has_e2ee_agreement_private"`
|
|
99
|
+
UserState UserState `json:"user_state"`
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
type UserState struct {
|
|
103
|
+
RegistrationState string `json:"registration_state"`
|
|
104
|
+
ReadyForMessaging bool `json:"ready_for_messaging"`
|
|
105
|
+
Missing []string `json:"missing,omitempty"`
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
type LegacyFlatIdentity struct {
|
|
109
|
+
CredentialName string `json:"credential_name"`
|
|
110
|
+
Path string `json:"path"`
|
|
111
|
+
DID string `json:"did"`
|
|
112
|
+
UniqueID string `json:"unique_id"`
|
|
113
|
+
Handle string `json:"handle,omitempty"`
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
type LegacyScan struct {
|
|
117
|
+
RootDir string `json:"root_dir"`
|
|
118
|
+
IndexedLayout bool `json:"indexed_layout"`
|
|
119
|
+
IndexedEntries map[string]IndexEntry `json:"indexed_entries,omitempty"`
|
|
120
|
+
LegacyCredentials []LegacyFlatIdentity `json:"legacy_credentials,omitempty"`
|
|
121
|
+
InvalidJSONFiles []map[string]string `json:"invalid_json_files,omitempty"`
|
|
122
|
+
OrphanE2EEFiles []map[string]string `json:"orphan_e2ee_files,omitempty"`
|
|
123
|
+
HasLegacy bool `json:"has_legacy"`
|
|
124
|
+
Hint string `json:"hint,omitempty"`
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
type SaveInput struct {
|
|
128
|
+
IdentityName string
|
|
129
|
+
DID string
|
|
130
|
+
UniqueID string
|
|
131
|
+
UserID string
|
|
132
|
+
DisplayName string
|
|
133
|
+
Handle string
|
|
134
|
+
JWTToken string
|
|
135
|
+
DIDDocument map[string]any
|
|
136
|
+
Key1PrivatePEM string
|
|
137
|
+
Key1PublicPEM string
|
|
138
|
+
E2EESigningPrivatePEM string
|
|
139
|
+
E2EEAgreementPrivatePEM string
|
|
140
|
+
ReplaceExisting bool
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
type GenerateOptions struct {
|
|
144
|
+
Hostname string
|
|
145
|
+
PathPrefix []string
|
|
146
|
+
ProofDomain string
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
type GeneratedIdentity struct {
|
|
150
|
+
DID string
|
|
151
|
+
UniqueID string
|
|
152
|
+
DIDDocument map[string]any
|
|
153
|
+
Key1PrivatePEM string
|
|
154
|
+
Key1PublicPEM string
|
|
155
|
+
E2EESigningPrivatePEM string
|
|
156
|
+
E2EEAgreementPrivatePEM string
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
type CommandResult struct {
|
|
160
|
+
Data map[string]any
|
|
161
|
+
Summary string
|
|
162
|
+
Warnings []string
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
type RegisterParams struct {
|
|
166
|
+
IdentityName string
|
|
167
|
+
Handle string
|
|
168
|
+
Phone string
|
|
169
|
+
Email string
|
|
170
|
+
OTP string
|
|
171
|
+
InviteCode string
|
|
172
|
+
Wait bool
|
|
173
|
+
VerificationTimeout int
|
|
174
|
+
PollIntervalSeconds float64
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
type BindParams struct {
|
|
178
|
+
Phone string
|
|
179
|
+
Email string
|
|
180
|
+
OTP string
|
|
181
|
+
Wait bool
|
|
182
|
+
VerificationTimeout int
|
|
183
|
+
PollIntervalSeconds float64
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
type RecoverParams struct {
|
|
187
|
+
IdentityName string
|
|
188
|
+
Handle string
|
|
189
|
+
Phone string
|
|
190
|
+
OTP string
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
type UpdateProfileParams struct {
|
|
194
|
+
DisplayName string
|
|
195
|
+
Bio string
|
|
196
|
+
TagsCSV string
|
|
197
|
+
Markdown string
|
|
198
|
+
MarkdownFile string
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
type ImportResult struct {
|
|
202
|
+
Imported []IdentitySummary `json:"imported,omitempty"`
|
|
203
|
+
Skipped []string `json:"skipped,omitempty"`
|
|
204
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
package message
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"fmt"
|
|
6
|
+
"sort"
|
|
7
|
+
"strings"
|
|
8
|
+
|
|
9
|
+
"github.com/agentconnect/awiki-cli/internal/anpsdk"
|
|
10
|
+
"github.com/agentconnect/awiki-cli/internal/authsdk"
|
|
11
|
+
"github.com/agentconnect/awiki-cli/internal/identity"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
type authContext struct {
|
|
15
|
+
record *identity.StoredIdentity
|
|
16
|
+
session *authsdk.Session
|
|
17
|
+
privateKey anpsdk.PrivateKeyMaterial
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
func newAuthContext(record *identity.StoredIdentity, manager *identity.Manager) (*authContext, error) {
|
|
21
|
+
if record == nil {
|
|
22
|
+
return nil, fmt.Errorf("identity record is required")
|
|
23
|
+
}
|
|
24
|
+
privateKey, err := loadPrivateKeyMaterial(record.Key1PrivatePEM)
|
|
25
|
+
if err != nil {
|
|
26
|
+
return nil, err
|
|
27
|
+
}
|
|
28
|
+
var session *authsdk.Session
|
|
29
|
+
if manager != nil {
|
|
30
|
+
paths, err := manager.PathsForIdentity(record.IdentityName)
|
|
31
|
+
if err != nil {
|
|
32
|
+
return nil, err
|
|
33
|
+
}
|
|
34
|
+
session = authsdk.NewSession(paths.DIDDocumentPath, paths.Key1PrivatePath, record.IdentityName, record.DID, record.JWTToken, func(token string) error { return manager.UpdateJWT(record.IdentityName, token) })
|
|
35
|
+
}
|
|
36
|
+
return &authContext{
|
|
37
|
+
record: record,
|
|
38
|
+
session: session,
|
|
39
|
+
privateKey: privateKey,
|
|
40
|
+
}, nil
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
func loadPrivateKeyMaterial(pemText string) (anpsdk.PrivateKeyMaterial, error) {
|
|
44
|
+
_, scalar, err := authsdk.NormalizeSecp256k1PrivatePEM(pemText)
|
|
45
|
+
if err != nil {
|
|
46
|
+
return anpsdk.PrivateKeyMaterial{}, err
|
|
47
|
+
}
|
|
48
|
+
return anpsdk.PrivateKeyMaterial{
|
|
49
|
+
Type: anpsdk.KeyTypeSecp256k1,
|
|
50
|
+
Bytes: scalar,
|
|
51
|
+
}, nil
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
func verificationMethodID(didDocument map[string]any) string {
|
|
55
|
+
if didDocument == nil {
|
|
56
|
+
return ""
|
|
57
|
+
}
|
|
58
|
+
if authenticationMethods, ok := didDocument["authentication"].([]any); ok && len(authenticationMethods) > 0 {
|
|
59
|
+
if methodID, ok := authenticationMethods[0].(string); ok {
|
|
60
|
+
return methodID
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if verificationMethods, ok := didDocument["verificationMethod"].([]any); ok && len(verificationMethods) > 0 {
|
|
64
|
+
if method, ok := verificationMethods[0].(map[string]any); ok {
|
|
65
|
+
if methodID, ok := method["id"].(string); ok {
|
|
66
|
+
return methodID
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return ""
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
func cloneMap(value map[string]any) map[string]any {
|
|
74
|
+
cloned := make(map[string]any, len(value))
|
|
75
|
+
for key, item := range value {
|
|
76
|
+
cloned[key] = item
|
|
77
|
+
}
|
|
78
|
+
return cloned
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
func canonicalJSON(value any) ([]byte, error) {
|
|
82
|
+
raw, err := json.Marshal(value)
|
|
83
|
+
if err != nil {
|
|
84
|
+
return nil, err
|
|
85
|
+
}
|
|
86
|
+
var normalized any
|
|
87
|
+
if err := json.Unmarshal(raw, &normalized); err != nil {
|
|
88
|
+
return nil, err
|
|
89
|
+
}
|
|
90
|
+
buffer := &strings.Builder{}
|
|
91
|
+
if err := writeCanonicalJSON(buffer, normalized); err != nil {
|
|
92
|
+
return nil, err
|
|
93
|
+
}
|
|
94
|
+
return []byte(buffer.String()), nil
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
func writeCanonicalJSON(buffer *strings.Builder, value any) error {
|
|
98
|
+
switch typed := value.(type) {
|
|
99
|
+
case nil:
|
|
100
|
+
buffer.WriteString("null")
|
|
101
|
+
case string:
|
|
102
|
+
raw, err := json.Marshal(typed)
|
|
103
|
+
if err != nil {
|
|
104
|
+
return err
|
|
105
|
+
}
|
|
106
|
+
buffer.Write(raw)
|
|
107
|
+
case bool:
|
|
108
|
+
if typed {
|
|
109
|
+
buffer.WriteString("true")
|
|
110
|
+
} else {
|
|
111
|
+
buffer.WriteString("false")
|
|
112
|
+
}
|
|
113
|
+
case []any:
|
|
114
|
+
buffer.WriteByte('[')
|
|
115
|
+
for index, item := range typed {
|
|
116
|
+
if index > 0 {
|
|
117
|
+
buffer.WriteByte(',')
|
|
118
|
+
}
|
|
119
|
+
if err := writeCanonicalJSON(buffer, item); err != nil {
|
|
120
|
+
return err
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
buffer.WriteByte(']')
|
|
124
|
+
case []string:
|
|
125
|
+
buffer.WriteByte('[')
|
|
126
|
+
for index, item := range typed {
|
|
127
|
+
if index > 0 {
|
|
128
|
+
buffer.WriteByte(',')
|
|
129
|
+
}
|
|
130
|
+
if err := writeCanonicalJSON(buffer, item); err != nil {
|
|
131
|
+
return err
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
buffer.WriteByte(']')
|
|
135
|
+
case map[string]any:
|
|
136
|
+
keys := make([]string, 0, len(typed))
|
|
137
|
+
for key := range typed {
|
|
138
|
+
keys = append(keys, key)
|
|
139
|
+
}
|
|
140
|
+
sort.Strings(keys)
|
|
141
|
+
buffer.WriteByte('{')
|
|
142
|
+
for index, key := range keys {
|
|
143
|
+
if index > 0 {
|
|
144
|
+
buffer.WriteByte(',')
|
|
145
|
+
}
|
|
146
|
+
if err := writeCanonicalJSON(buffer, key); err != nil {
|
|
147
|
+
return err
|
|
148
|
+
}
|
|
149
|
+
buffer.WriteByte(':')
|
|
150
|
+
if err := writeCanonicalJSON(buffer, typed[key]); err != nil {
|
|
151
|
+
return err
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
buffer.WriteByte('}')
|
|
155
|
+
default:
|
|
156
|
+
raw, err := json.Marshal(typed)
|
|
157
|
+
if err != nil {
|
|
158
|
+
return err
|
|
159
|
+
}
|
|
160
|
+
var normalized any
|
|
161
|
+
if err := json.Unmarshal(raw, &normalized); err != nil {
|
|
162
|
+
return err
|
|
163
|
+
}
|
|
164
|
+
return writeCanonicalJSON(buffer, normalized)
|
|
165
|
+
}
|
|
166
|
+
return nil
|
|
167
|
+
}
|