{"id":"553658021d055bb6","repo":"gohugoio/hugo","slug":"npm-pack-unsupported-workspaces-object-missing","errorCode":null,"errorMessage":"npm pack: unsupported workspaces object; missing \"packages\" field","messagePattern":"npm pack: unsupported workspaces object; missing \"packages\" field","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"modules/npm/package_builder.go","lineNumber":272,"sourceCode":"\tquoted := fmt.Sprintf(\"%q\", workspacePath)\n\n\twsVal, hasWS := pkg[\"workspaces\"]\n\n\tswitch v := wsVal.(type) {\n\tcase []any:\n\t\t// Array form: [\"pkg-a\", \"pkg-b\", ...]\n\t\tif slices.Contains(toStringSlice(v), workspacePath) {\n\t\t\tif runtime.GOOS == \"windows\" {\n\t\t\t\treturn afero.WriteFile(fsys, packageJSONName, data, 0o666)\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t\t// Fall through to byte-based insertion into the existing array below.\n\tcase map[string]any:\n\t\t// Object form: { \"workspaces\": { \"packages\": [...] } }\n\t\tpackagesVal, ok := v[\"packages\"]\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"npm pack: unsupported workspaces object; missing \\\"packages\\\" field\")\n\t\t}\n\t\tpackagesSlice := toStringSlice(packagesVal)\n\t\tif slices.Contains(packagesSlice, workspacePath) {\n\t\t\tif runtime.GOOS == \"windows\" {\n\t\t\t\treturn afero.WriteFile(fsys, packageJSONName, data, 0o666)\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\n\t\t// Append the new workspace path to the packages slice.\n\t\tnewPkgs := make([]any, 0, len(packagesSlice)+1)\n\t\tfor _, s := range packagesSlice {\n\t\t\tnewPkgs = append(newPkgs, s)\n\t\t}\n\t\tnewPkgs = append(newPkgs, workspacePath)\n\t\tv[\"packages\"] = newPkgs\n\n\t\tupdated, err := json.MarshalIndent(pkg, \"\", indent)","sourceCodeStart":254,"sourceCodeEnd":290,"githubUrl":"https://github.com/gohugoio/hugo/blob/8a468df065a75c1c7cf9f6850f32148746590ea5/modules/npm/package_builder.go#L254-L290","documentation":"When ensuring the root package.json references Hugo's autogenerated workspace, Hugo supports `workspaces` either as an array or as the yarn-classic object form `{ \"packages\": [...] }`. If `workspaces` is an object but has no `packages` key, Hugo cannot know where to insert its workspace path and refuses to guess, failing fast per its no-silent-fallback policy.","triggerScenarios":"`hugo mod npm pack` on a project whose root package.json has `\"workspaces\": {}` or an object containing only yarn-specific fields like `nohoist` without `packages`.","commonSituations":"Yarn workspaces configs that use `nohoist` but omitted `packages`; hand-migrated monorepo configs; tooling that wrote an empty workspaces object placeholder.","solutions":["Add a `packages` array to the workspaces object: `\"workspaces\": { \"packages\": [] }` — Hugo will then append its workspace path.","Or simplify to the array form: `\"workspaces\": []`.","Re-run `hugo mod npm pack` after the change."],"exampleFix":"// before\n{ \"workspaces\": { \"nohoist\": [\"**/foo\"] } }\n// after\n{ \"workspaces\": { \"packages\": [], \"nohoist\": [\"**/foo\"] } }","handlingStrategy":"validation","validationCode":"var pkg struct{ Workspaces any `json:\"workspaces\"` }\njson.Unmarshal(data, &pkg)\nif m, ok := pkg.Workspaces.(map[string]any); ok {\n    if _, ok := m[\"packages\"]; !ok {\n        return errors.New(\"workspaces object must contain a \\\"packages\\\" array\")\n    }\n}","typeGuard":"func hasPackagesField(ws any) bool {\n    m, ok := ws.(map[string]any)\n    if !ok { return false }\n    _, ok = m[\"packages\"].([]any)\n    return ok\n}","tryCatchPattern":null,"preventionTips":["Prefer the simple array form: \"workspaces\": [\"pkg-a\", \"pkg-b\"]","If using the object form (yarn style), always include a \"packages\" array field","Validate package.json workspaces shape in CI before running hugo mod npm pack"],"tags":["hugo","npm-pack","workspaces","package-json"],"analyzedSha":"8a468df065a75c1c7cf9f6850f32148746590ea5","analyzedAt":"2026-07-31T21:23:07.045Z","schemaVersion":2}