sku 16.2.0 → 16.3.0-replace-pnpm-plugin-20260907064258

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/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # sku
2
2
 
3
+ ## 16.3.0-replace-pnpm-plugin-20260907064258
4
+
5
+ ### Minor Changes
6
+
7
+ - Migrate from `pnpm-plugin-sku` to static `pnpm-workspace.yaml` configuration. ([#1718](https://github.com/seek-oss/sku/pull/1718))
8
+
9
+ ### Summary of Changes
10
+ - **Static Workspace Configuration**: Sku's recommended pnpm settings are now written directly into `pnpm-workspace.yaml` with `# sku_managed` markers instead of being injected at runtime via `pnpm-plugin-sku`.
11
+ - **Automatic Migration**: On the first run of a configuration-enabled sku command or postinstall, `pnpm-plugin-sku` is removed from `configDependencies` in `pnpm-workspace.yaml` (and the `configDependencies` field is deleted if empty).
12
+ - **Two Sync Modes**:
13
+ - **Additive Sync (configuration-enabled sku commands & postinstall)**: Adds missing managed single-value settings and `allowBuilds` keys, unions and deduplicates array settings (`publicHoistPattern`, `minimumReleaseAgeExclude`, `trustPolicyExclude`), and never overwrites existing config values or removes user-owned entries.
14
+ - **Enforce Mode (`sku configure`)**: Overwrites managed single-value settings and marked `allowBuilds` keys to match sku defaults in both directions. Unmarked `allowBuilds` overrides are preserved. Removes retired sku defaults that still carry a `# sku_managed` marker.
15
+ - **Managed Comments**: Comments on entries adopted or overwritten by sku are replaced with the `# sku_managed` marker.
16
+ - **Mutation Logging**: File creation, additions, adoptions, duplicate removal, migrations, updates, and retirements are logged; aligned files remain silent.
17
+ - **Drift Warnings**: Regular sku commands warn when managed values differ from sku defaults or when retired entries still have a `# sku_managed` marker, suggesting `sku configure` to align.
18
+ - **Opt-outs and Escape Hatches**:
19
+ - `skuSkipConfigure: true` in `package.json` disables the sync on regular sku commands (`skuSkipPostInstall: true` disables it on postinstall). Manual `sku configure` always syncs.
20
+ - To keep an entry that sku has retired, delete its `# sku_managed` marker comment (or re-add it without a marker) to designate it as user-managed.
21
+ - **New Project Scaffolding**: `@sku-lib/create` now uses the shared sync engine to emit `pnpm-workspace.yaml` on project creation, without installing `pnpm-plugin-sku` or gating on pnpm versions.
22
+
3
23
  ## 16.2.0
4
24
 
5
25
  ### Minor Changes
@@ -158,7 +158,8 @@ var package_default = {
158
158
  "webpack-bundle-analyzer": "^5.1.1",
159
159
  "webpack-dev-server": "catalog:",
160
160
  "webpack-merge": "^6.0.1",
161
- "webpack-node-externals": "^3.0.0"
161
+ "webpack-node-externals": "^3.0.0",
162
+ "yaml": "^2.8.3"
162
163
  },
163
164
  devDependencies: {
164
165
  "@sku-private/tsdown": "workspace:*",
@@ -4,7 +4,7 @@ import "./private/utils/src/index.mjs";
4
4
  import { banner } from "./private/utils/src/console/banner.mjs";
5
5
  import "./private/utils/src/console/index.mjs";
6
6
  import { createSkuContext } from "./context/createSkuContext.mjs";
7
- import configureApp_default from "./utils/configureApp.mjs";
7
+ import { configureApp } from "./utils/configureApp.mjs";
8
8
  import { createDebug } from "obug";
9
9
  //#region src/postinstall.ts
10
10
  /**
@@ -17,7 +17,7 @@ const postinstall = async ({ localCwd, packageJson, hasSkuDep }) => {
17
17
  try {
18
18
  log("postinstall", "running configure");
19
19
  const skuContext = await createSkuContext({});
20
- configureApp_default(skuContext);
20
+ await configureApp(skuContext);
21
21
  } catch (error) {
22
22
  console.error("An error occurred running postinstall script. Please check that sku.config.js is correct and try again.");
23
23
  console.error(error);
@@ -1,7 +1,9 @@
1
1
  import "./path/toPosixPath.mjs";
2
2
  import { cwd, getPathFromCwd, requireFromCwd, setCwd, writeFileToCWD } from "./filesystem/cwd.mjs";
3
3
  import "./filesystem/index.mjs";
4
- import { getAddCommand, getExecuteCommand, getRunCommand, getWhyCommand, isAtLeastPnpmV10, isAtLeastRecommendedPnpmVersion, isPnpm, packageManager, rootDir } from "./packageManager/packageManager.mjs";
4
+ import { getAddCommand, getExecuteCommand, getRunCommand, getWhyCommand, isPnpm, packageManager, rootDir } from "./packageManager/packageManager.mjs";
5
+ import { MANAGED_BY_SKU_COMMENT, MANAGED_BY_SKU_MARKER, arraySettings, defaultPnpmWorkspaceConfig, objectSettings, pnpmWorkspaceSettings, singleValueSettings } from "./packageManager/pnpmWorkspaceDefaults.mjs";
6
+ import { ensurePnpmWorkspaceConfig } from "./packageManager/ensurePnpmWorkspaceConfig.mjs";
5
7
  import "./packageManager/index.mjs";
6
8
  import { makeUrl, serverUrls } from "./serverUrls.mjs";
7
- export { cwd, getAddCommand, getExecuteCommand, getPathFromCwd, getRunCommand, getWhyCommand, isAtLeastPnpmV10, isAtLeastRecommendedPnpmVersion, isPnpm, makeUrl, packageManager, requireFromCwd, rootDir, serverUrls, setCwd, writeFileToCWD };
9
+ export { MANAGED_BY_SKU_COMMENT, MANAGED_BY_SKU_MARKER, arraySettings, cwd, defaultPnpmWorkspaceConfig, ensurePnpmWorkspaceConfig, getAddCommand, getExecuteCommand, getPathFromCwd, getRunCommand, getWhyCommand, isPnpm, makeUrl, objectSettings, packageManager, pnpmWorkspaceSettings, requireFromCwd, rootDir, serverUrls, setCwd, singleValueSettings, writeFileToCWD };
@@ -0,0 +1,50 @@
1
+ import { caution } from "../console/styles.mjs";
2
+ import { rootDir } from "./packageManager.mjs";
3
+ import { defaultPnpmWorkspaceConfig } from "./pnpmWorkspaceDefaults.mjs";
4
+ import { removePnpmPluginSku } from "./removePnpmPluginSku.mjs";
5
+ import { syncArraySettings } from "./syncArraySettings.mjs";
6
+ import { syncObjectSettings } from "./syncObjectSettings.mjs";
7
+ import { syncSingleValueSettings } from "./syncSingleValueSettings.mjs";
8
+ import { join } from "node:path";
9
+ import { existsSync } from "node:fs";
10
+ import { readFile, writeFile } from "node:fs/promises";
11
+ import { Document, isMap, parseDocument } from "yaml";
12
+ //#region ../../private/utils/src/packageManager/ensurePnpmWorkspaceConfig.ts
13
+ async function ensurePnpmWorkspaceConfig({ mode = "additive", create = false, targetDir = rootDir ?? process.cwd() } = {}) {
14
+ const filePath = join(targetDir, "pnpm-workspace.yaml");
15
+ const fileExisted = existsSync(filePath);
16
+ if (!fileExisted && !create) return;
17
+ let originalContent = "";
18
+ let doc;
19
+ if (fileExisted) {
20
+ originalContent = await readFile(filePath, "utf-8");
21
+ doc = parseDocument(originalContent);
22
+ if (!doc.contents) doc.contents = doc.createNode({});
23
+ else if (!isMap(doc.contents)) throw new Error(`Cannot sync ${filePath}: the document must contain a YAML mapping`);
24
+ } else doc = new Document(structuredClone(defaultPnpmWorkspaceConfig));
25
+ let modified = false;
26
+ const recordMutation = (message) => {
27
+ modified = true;
28
+ console.log(message);
29
+ };
30
+ if (!fileExisted) recordMutation("created pnpm-workspace.yaml");
31
+ const warn = (message) => {
32
+ console.warn(caution(message));
33
+ };
34
+ const context = {
35
+ doc,
36
+ mode,
37
+ recordMutation,
38
+ warn
39
+ };
40
+ removePnpmPluginSku(doc, recordMutation);
41
+ syncSingleValueSettings(context);
42
+ syncObjectSettings(context);
43
+ syncArraySettings(context);
44
+ if (modified) {
45
+ const newContent = doc.toString();
46
+ if (newContent !== originalContent) await writeFile(filePath, newContent, "utf-8");
47
+ }
48
+ }
49
+ //#endregion
50
+ export { ensurePnpmWorkspaceConfig };
@@ -1,2 +1,4 @@
1
- import { getAddCommand, getExecuteCommand, getRunCommand, getWhyCommand, isAtLeastPnpmV10, isAtLeastRecommendedPnpmVersion, isPnpm, packageManager, rootDir } from "./packageManager.mjs";
2
- export { getAddCommand, getExecuteCommand, getRunCommand, getWhyCommand, isAtLeastPnpmV10, isAtLeastRecommendedPnpmVersion, isPnpm, packageManager, rootDir };
1
+ import { getAddCommand, getExecuteCommand, getRunCommand, getWhyCommand, isPnpm, packageManager, rootDir } from "./packageManager.mjs";
2
+ import { MANAGED_BY_SKU_COMMENT, MANAGED_BY_SKU_MARKER, arraySettings, defaultPnpmWorkspaceConfig, objectSettings, pnpmWorkspaceSettings, singleValueSettings } from "./pnpmWorkspaceDefaults.mjs";
3
+ import { ensurePnpmWorkspaceConfig } from "./ensurePnpmWorkspaceConfig.mjs";
4
+ export { MANAGED_BY_SKU_COMMENT, MANAGED_BY_SKU_MARKER, arraySettings, defaultPnpmWorkspaceConfig, ensurePnpmWorkspaceConfig, getAddCommand, getExecuteCommand, getRunCommand, getWhyCommand, isPnpm, objectSettings, packageManager, pnpmWorkspaceSettings, rootDir, singleValueSettings };
@@ -4,7 +4,6 @@ import { detectSync } from "../../../../node_modules/.pnpm/package-manager-detec
4
4
  import { caution, strong } from "../console/styles.mjs";
5
5
  import { dirname } from "node:path";
6
6
  import * as find from "empathic/find";
7
- import semver from "semver";
8
7
  //#region ../../private/utils/src/packageManager/packageManager.ts
9
8
  const lockfileByPackageManager = {
10
9
  pnpm: "pnpm-lock.yaml",
@@ -72,9 +71,6 @@ const resolvePackageManager = () => {
72
71
  };
73
72
  };
74
73
  const { rootDir, packageManager, packageManagerVersion } = resolvePackageManager();
75
- const isAtLeastPnpmV10 = () => packageManager === "pnpm" && packageManagerVersion && semver.satisfies(packageManagerVersion, ">=10.0.0");
76
- const recommendedPnpmVersion = "10.13.0";
77
- const isAtLeastRecommendedPnpmVersion = () => Boolean(packageManager === "pnpm" && packageManagerVersion && semver.satisfies(packageManagerVersion, `>=${recommendedPnpmVersion}`));
78
74
  const getCommand = (agent, command, args) => {
79
75
  const resolvedCommand = resolveCommand(agent, command, args);
80
76
  if (!resolvedCommand) throw new Error(`Unable to resolve command: ${agent} ${command} ${args}`);
@@ -116,4 +112,4 @@ const getWhyCommand = () => {
116
112
  return `${packageManager} ${isPnpm ? "why -r" : "why"}`;
117
113
  };
118
114
  //#endregion
119
- export { getAddCommand, getCommand, getExecuteCommand, getRunCommand, getWhyCommand, isAtLeastPnpmV10, isAtLeastRecommendedPnpmVersion, isPnpm, isYarn, packageManager, packageManagerVersion, rootDir };
115
+ export { getAddCommand, getCommand, getExecuteCommand, getRunCommand, getWhyCommand, isPnpm, isYarn, packageManager, rootDir };
@@ -0,0 +1,74 @@
1
+ //#region ../../private/utils/src/packageManager/pnpmWorkspaceDefaults.ts
2
+ const MANAGED_BY_SKU_MARKER = "sku_managed";
3
+ const MANAGED_BY_SKU_COMMENT = `# ${MANAGED_BY_SKU_MARKER}`;
4
+ /** Overwritten wholesale by `sku configure`: sku's value always wins. */
5
+ const singleValueSetting = (value, comment) => ({
6
+ kind: "value",
7
+ value,
8
+ comment
9
+ });
10
+ /** A flat map merged per key. Marked keys are sku-owned, the rest are the user's. */
11
+ const objectSetting = (entries) => ({
12
+ kind: "object",
13
+ entries
14
+ });
15
+ /** Unioned and deduped. Marked entries are sku-owned, the rest are the user's. */
16
+ const arraySetting = (entries) => ({
17
+ kind: "array",
18
+ entries: entries.map((entry) => typeof entry === "string" ? { value: entry } : entry)
19
+ });
20
+ const minutesPerDay = 1440;
21
+ const minimumReleaseAgeInDays = 3;
22
+ /**
23
+ * Sku's recommended pnpm settings. Each entry declares its own value, merge
24
+ * policy and comments, and everything the sync needs is derived from here.
25
+ */
26
+ const pnpmWorkspaceSettings = {
27
+ allowBuilds: objectSetting({
28
+ "@parcel/watcher": true,
29
+ "@swc/core": true,
30
+ "core-js-pure": false,
31
+ esbuild: true,
32
+ sku: true,
33
+ "unrs-resolver": true
34
+ }),
35
+ blockExoticSubdeps: singleValueSetting(true),
36
+ minimumReleaseAge: singleValueSetting(minimumReleaseAgeInDays * minutesPerDay, `${minimumReleaseAgeInDays} days`),
37
+ minimumReleaseAgeExclude: arraySetting([
38
+ "@braid-design-system/*",
39
+ "@capsizecss/*",
40
+ "@seek/*",
41
+ "@sku-lib/*",
42
+ "@vanilla-extract/*",
43
+ "@vocab/*",
44
+ "braid-design-system",
45
+ "browserslist-config-seek",
46
+ "eslint-config-seek",
47
+ "sku"
48
+ ]),
49
+ publicHoistPattern: arraySetting(["eslint", "prettier"]),
50
+ strictDepBuilds: singleValueSetting(false),
51
+ trustPolicy: singleValueSetting("off"),
52
+ trustPolicyExclude: arraySetting([{
53
+ value: "semver@6.3.1",
54
+ comment: "dependency of eslint-plugin-react"
55
+ }])
56
+ };
57
+ const keyedSettings = Object.keys(pnpmWorkspaceSettings).map((key) => ({
58
+ key,
59
+ ...pnpmWorkspaceSettings[key]
60
+ }));
61
+ const singleValueSettings = keyedSettings.filter((setting) => setting.kind === "value");
62
+ const objectSettings = keyedSettings.filter((setting) => setting.kind === "object");
63
+ const arraySettings = keyedSettings.filter((setting) => setting.kind === "array");
64
+ const settingValue = (setting) => {
65
+ switch (setting.kind) {
66
+ case "value": return setting.value;
67
+ case "object": return { ...setting.entries };
68
+ case "array": return setting.entries.map(({ value }) => value);
69
+ }
70
+ };
71
+ /** The settings as plain values, for scaffolding a `pnpm-workspace.yaml` from scratch. */
72
+ const defaultPnpmWorkspaceConfig = Object.fromEntries(keyedSettings.map((setting) => [setting.key, settingValue(setting)]));
73
+ //#endregion
74
+ export { MANAGED_BY_SKU_COMMENT, MANAGED_BY_SKU_MARKER, arraySettings, defaultPnpmWorkspaceConfig, objectSettings, pnpmWorkspaceSettings, singleValueSettings };
@@ -0,0 +1,19 @@
1
+ import { getNodeKey } from "./syncShared.mjs";
2
+ import { isMap, isPair, isSeq } from "yaml";
3
+ //#region ../../private/utils/src/packageManager/removePnpmPluginSku.ts
4
+ /**
5
+ * Removes the pnpm-plugin-sku from the configDependencies in pnpm-workspace.yaml
6
+ */
7
+ const removePnpmPluginSku = (doc, recordMutation) => {
8
+ if (!doc.has("configDependencies")) return;
9
+ const cd = doc.get("configDependencies", true);
10
+ if (!isMap(cd) && !isSeq(cd)) return;
11
+ const items = cd.items;
12
+ const remainingItems = items.filter((item) => getNodeKey(isPair(item) ? item.key : item) !== "pnpm-plugin-sku");
13
+ if (remainingItems.length === items.length) return;
14
+ cd.items = remainingItems;
15
+ recordMutation("removed pnpm-plugin-sku from configDependencies in pnpm-workspace.yaml");
16
+ if (remainingItems.length === 0) doc.delete("configDependencies");
17
+ };
18
+ //#endregion
19
+ export { removePnpmPluginSku };
@@ -0,0 +1,75 @@
1
+ import { MANAGED_BY_SKU_COMMENT, arraySettings } from "./pnpmWorkspaceDefaults.mjs";
2
+ import { hasManagedMarker, setManagedComment } from "./syncShared.mjs";
3
+ import { isScalar, isSeq } from "yaml";
4
+ //#region ../../private/utils/src/packageManager/syncArraySettings.ts
5
+ const deduplicateArrayItems = (seqNode, key, context) => {
6
+ const seenValues = /* @__PURE__ */ new Map();
7
+ const deduplicatedItems = [];
8
+ for (const item of seqNode.items) if (isScalar(item) && typeof item.value === "string") {
9
+ const existingIndex = seenValues.get(item.value);
10
+ if (existingIndex !== void 0) {
11
+ const existingItem = deduplicatedItems[existingIndex];
12
+ if (isScalar(existingItem) && hasManagedMarker(existingItem.comment) && !hasManagedMarker(item.comment)) deduplicatedItems[existingIndex] = item;
13
+ context.recordMutation(`removed duplicate ${item.value} from ${key} in pnpm-workspace.yaml`);
14
+ continue;
15
+ }
16
+ seenValues.set(item.value, deduplicatedItems.length);
17
+ deduplicatedItems.push(item);
18
+ } else deduplicatedItems.push(item);
19
+ seqNode.items = deduplicatedItems;
20
+ };
21
+ /**
22
+ * Adopts default entries and flags retired ones in an existing array.
23
+ *
24
+ * @returns Whether the item should be removed from the sequence.
25
+ */
26
+ const processSingleArrayItem = (item, defaultComments, key, context) => {
27
+ if (!isScalar(item) || typeof item.value !== "string") return false;
28
+ const val = item.value;
29
+ if (defaultComments.has(val)) {
30
+ if (setManagedComment(item, defaultComments.get(val))) context.recordMutation(`adopted ${val} in ${key} in pnpm-workspace.yaml`);
31
+ return false;
32
+ }
33
+ if (hasManagedMarker(item.comment)) {
34
+ if (context.mode === "enforce") return true;
35
+ context.warn(`pnpm-workspace.yaml: "${val}" in ${key} is marked with "${MANAGED_BY_SKU_COMMENT}", but is no longer a sku default. Run "sku configure" to remove it, or delete its "${MANAGED_BY_SKU_COMMENT}" marker to keep it as a user-managed entry.`);
36
+ }
37
+ return false;
38
+ };
39
+ const processExistingArrayItems = (seqNode, defaultComments, key, context) => {
40
+ const indicesToRemove = [];
41
+ for (const [i, item] of seqNode.items.entries()) if (processSingleArrayItem(item, defaultComments, key, context)) indicesToRemove.push(i);
42
+ for (const idx of indicesToRemove.toReversed()) {
43
+ const item = seqNode.items[idx];
44
+ const val = isScalar(item) ? String(item.value) : "";
45
+ seqNode.items.splice(idx, 1);
46
+ context.recordMutation(`removed retired entry ${val} from ${key} in pnpm-workspace.yaml`);
47
+ }
48
+ };
49
+ const appendMissingArrayDefaults = (seqNode, entries, key, context) => {
50
+ const existingValues = /* @__PURE__ */ new Set();
51
+ for (const item of seqNode.items) if (isScalar(item) && typeof item.value === "string") existingValues.add(item.value);
52
+ for (const { value, comment } of entries) if (!existingValues.has(value)) {
53
+ const newItem = context.doc.createNode(value);
54
+ setManagedComment(newItem, comment);
55
+ seqNode.items.push(newItem);
56
+ existingValues.add(value);
57
+ context.recordMutation(`added ${value} to ${key} in pnpm-workspace.yaml`);
58
+ }
59
+ };
60
+ const syncArraySettings = (context) => {
61
+ for (const { key, entries } of arraySettings) {
62
+ const defaultComments = new Map(entries.map(({ value, comment }) => [value, comment]));
63
+ if (!context.doc.has(key)) {
64
+ context.doc.set(key, context.doc.createNode([]));
65
+ context.recordMutation(`added ${key} to pnpm-workspace.yaml`);
66
+ }
67
+ const seqNode = context.doc.get(key, true);
68
+ if (!isSeq(seqNode)) continue;
69
+ deduplicateArrayItems(seqNode, key, context);
70
+ processExistingArrayItems(seqNode, defaultComments, key, context);
71
+ appendMissingArrayDefaults(seqNode, entries, key, context);
72
+ }
73
+ };
74
+ //#endregion
75
+ export { syncArraySettings };
@@ -0,0 +1,67 @@
1
+ import { MANAGED_BY_SKU_COMMENT, objectSettings } from "./pnpmWorkspaceDefaults.mjs";
2
+ import { clearCommentBefore, getNodeKey, hasManagedMarker, setManagedComment } from "./syncShared.mjs";
3
+ import { isMap, isScalar } from "yaml";
4
+ //#region ../../private/utils/src/packageManager/syncObjectSettings.ts
5
+ const markPairAsManaged = (pair, explanatory) => {
6
+ const modified = setManagedComment(pair.value, explanatory);
7
+ return clearCommentBefore(pair.key) || modified;
8
+ };
9
+ const syncExistingObjectPair = (pair, key, subKey, defaultVal, context) => {
10
+ const { doc, mode, recordMutation, warn } = context;
11
+ if (!isScalar(pair.value)) return;
12
+ const currentVal = pair.value.value;
13
+ if (currentVal === defaultVal) {
14
+ if (markPairAsManaged(pair)) recordMutation(`adopted ${key}.${subKey}: ${String(currentVal)} in pnpm-workspace.yaml`);
15
+ return;
16
+ }
17
+ if (!hasManagedMarker(pair.value.comment)) return;
18
+ if (mode === "enforce") {
19
+ pair.value = doc.createNode(defaultVal);
20
+ setManagedComment(pair.value);
21
+ clearCommentBefore(pair.key);
22
+ recordMutation(`updated ${key}.${subKey}: ${String(currentVal)} → ${defaultVal} in pnpm-workspace.yaml`);
23
+ return;
24
+ }
25
+ warn(`pnpm-workspace.yaml: "${key}.${subKey}" has value ${String(currentVal)}, recommended is ${defaultVal}. Run "sku configure" to align.`);
26
+ };
27
+ const syncObjectPair = (mapNode, key, subKey, defaultVal, context) => {
28
+ const pair = mapNode.items.find((item) => getNodeKey(item.key) === subKey);
29
+ if (!pair) {
30
+ const valNode = context.doc.createNode(defaultVal);
31
+ setManagedComment(valNode);
32
+ mapNode.set(subKey, valNode);
33
+ context.recordMutation(`added ${key}.${subKey}: ${defaultVal} to pnpm-workspace.yaml`);
34
+ return;
35
+ }
36
+ syncExistingObjectPair(pair, key, subKey, defaultVal, context);
37
+ };
38
+ const cleanRetiredObjectKeys = (mapNode, key, defaultObj, context) => {
39
+ const { mode, recordMutation, warn } = context;
40
+ const itemsToRemove = [];
41
+ for (const pair of mapNode.items) {
42
+ const subKey = getNodeKey(pair.key);
43
+ if (!(subKey in defaultObj)) {
44
+ if (isScalar(pair.value) && hasManagedMarker(pair.value.comment)) if (mode === "enforce") itemsToRemove.push(subKey);
45
+ else warn(`pnpm-workspace.yaml: "${subKey}" in ${key} is marked with "${MANAGED_BY_SKU_COMMENT}", but is no longer a sku default. Run "sku configure" to remove it, or delete its "${MANAGED_BY_SKU_COMMENT}" marker to keep it as a user-managed entry.`);
46
+ }
47
+ }
48
+ for (const subKey of itemsToRemove) {
49
+ mapNode.delete(subKey);
50
+ recordMutation(`removed retired entry ${key}.${subKey} from pnpm-workspace.yaml`);
51
+ }
52
+ };
53
+ const syncObjectSettings = (context) => {
54
+ const { doc } = context;
55
+ for (const { key, entries } of objectSettings) {
56
+ if (!doc.has(key)) {
57
+ doc.set(key, doc.createNode({}));
58
+ context.recordMutation(`added ${key} to pnpm-workspace.yaml`);
59
+ }
60
+ const mapNode = doc.get(key, true);
61
+ if (!isMap(mapNode)) continue;
62
+ for (const [subKey, defaultVal] of Object.entries(entries)) syncObjectPair(mapNode, key, subKey, defaultVal, context);
63
+ cleanRetiredObjectKeys(mapNode, key, entries, context);
64
+ }
65
+ };
66
+ //#endregion
67
+ export { syncObjectSettings };
@@ -0,0 +1,25 @@
1
+ import { MANAGED_BY_SKU_MARKER } from "./pnpmWorkspaceDefaults.mjs";
2
+ import { isScalar } from "yaml";
3
+ //#region ../../private/utils/src/packageManager/syncShared.ts
4
+ const getNodeKey = (node) => isScalar(node) ? String(node.value) : String(node);
5
+ const hasManagedMarker = (comment) => {
6
+ const trimmed = comment?.trim();
7
+ if (!trimmed) return false;
8
+ return trimmed === "sku_managed" || trimmed.endsWith(`# sku_managed`);
9
+ };
10
+ const formatComment = (explanatory) => explanatory ? ` ${explanatory} # ${MANAGED_BY_SKU_MARKER}` : ` ${MANAGED_BY_SKU_MARKER}`;
11
+ const clearCommentBefore = (node) => {
12
+ if (!isScalar(node) || !node.commentBefore) return false;
13
+ node.commentBefore = void 0;
14
+ return true;
15
+ };
16
+ const setManagedComment = (node, explanatory) => {
17
+ if (!isScalar(node)) return false;
18
+ const comment = formatComment(explanatory);
19
+ const modified = node.comment !== comment || Boolean(node.commentBefore);
20
+ node.comment = comment;
21
+ node.commentBefore = void 0;
22
+ return modified;
23
+ };
24
+ //#endregion
25
+ export { clearCommentBefore, getNodeKey, hasManagedMarker, setManagedComment };
@@ -0,0 +1,58 @@
1
+ import { singleValueSettings } from "./pnpmWorkspaceDefaults.mjs";
2
+ import { clearCommentBefore, getNodeKey, setManagedComment } from "./syncShared.mjs";
3
+ import { isMap, isScalar } from "yaml";
4
+ //#region ../../private/utils/src/packageManager/syncSingleValueSettings.ts
5
+ const findMapPair = (doc, key) => {
6
+ if (!isMap(doc.contents)) return;
7
+ return doc.contents.items.find((pair) => getNodeKey(pair.key) === key);
8
+ };
9
+ const markSingleValueAsManaged = (node, explanatory, doc, key) => {
10
+ const modified = setManagedComment(node, explanatory);
11
+ const pair = findMapPair(doc, key);
12
+ return (pair ? clearCommentBefore(pair.key) : false) || modified;
13
+ };
14
+ const handleMatchingSingleValue = (node, explanatory, currentValue, doc, key, context) => {
15
+ if (isScalar(node)) {
16
+ if (markSingleValueAsManaged(node, explanatory, doc, key)) context.recordMutation(`adopted ${key}: ${String(currentValue)} in pnpm-workspace.yaml`);
17
+ return;
18
+ }
19
+ const wrapped = doc.createNode(currentValue);
20
+ setManagedComment(wrapped, explanatory);
21
+ doc.set(key, wrapped);
22
+ context.recordMutation(`adopted ${key}: ${String(currentValue)} in pnpm-workspace.yaml`);
23
+ };
24
+ const updateExistingSingleValue = (key, defaultValue, explanatory, context) => {
25
+ const { doc, mode, recordMutation, warn } = context;
26
+ const node = doc.get(key, true);
27
+ const currentValue = isScalar(node) ? node.value : doc.get(key);
28
+ if (currentValue === defaultValue) {
29
+ handleMatchingSingleValue(node, explanatory, currentValue, doc, key, context);
30
+ return;
31
+ }
32
+ if (mode === "enforce") {
33
+ const updatedNode = doc.createNode(defaultValue);
34
+ setManagedComment(updatedNode, explanatory);
35
+ doc.set(key, updatedNode);
36
+ const pair = findMapPair(doc, key);
37
+ if (pair) clearCommentBefore(pair.key);
38
+ recordMutation(`updated ${key}: ${String(currentValue)} → ${defaultValue} in pnpm-workspace.yaml`);
39
+ return;
40
+ }
41
+ warn(`pnpm-workspace.yaml: "${key}" has value ${String(currentValue)}, recommended is ${defaultValue}. Run "sku configure" to align.`);
42
+ };
43
+ const syncSingleValue = ({ key, value: defaultValue, comment }, context) => {
44
+ const { doc, recordMutation } = context;
45
+ if (!doc.has(key)) {
46
+ const node = doc.createNode(defaultValue);
47
+ setManagedComment(node, comment);
48
+ doc.set(key, node);
49
+ recordMutation(`added ${key}: ${defaultValue} to pnpm-workspace.yaml`);
50
+ return;
51
+ }
52
+ updateExistingSingleValue(key, defaultValue, comment, context);
53
+ };
54
+ const syncSingleValueSettings = (context) => {
55
+ for (const setting of singleValueSettings) syncSingleValue(setting, context);
56
+ };
57
+ //#endregion
58
+ export { syncSingleValueSettings };
@@ -1,7 +1,7 @@
1
- import configureApp_default from "../../../utils/configureApp.mjs";
1
+ import { configureApp } from "../../../utils/configureApp.mjs";
2
2
  //#region src/program/commands/configure/configure.action.ts
3
- const configureAction = ({ skuContext }) => {
4
- configureApp_default(skuContext);
3
+ const configureAction = async ({ skuContext }) => {
4
+ await configureApp(skuContext, { mode: "enforce" });
5
5
  };
6
6
  //#endregion
7
7
  export { configureAction };
@@ -2,7 +2,7 @@ import { Command } from "commander";
2
2
  //#region src/program/commands/configure/configure.command.ts
3
3
  const configureCommand = new Command("configure").description("Emit and update configuration files for your project.").action(async (options) => {
4
4
  const { configureAction } = await import("./configure.action.mjs");
5
- configureAction(options);
5
+ await configureAction(options);
6
6
  });
7
7
  //#endregion
8
8
  export { configureCommand };
@@ -6,7 +6,7 @@ import { Command } from "commander";
6
6
  const serveCommand = new Command("serve").description("Serve a production build of a statically-rendered application from your local machine.").addOption(siteOption).addOption(portOption).addOption(strictPortOption).addOption(listUrlsOption).action(async ({ site, skuContext }, command) => {
7
7
  const { serveAction } = await import("./serve.action.mjs");
8
8
  const { environment } = command.optsWithGlobals();
9
- serveAction({
9
+ await serveAction({
10
10
  site,
11
11
  environment,
12
12
  skuContext
@@ -1,6 +1,6 @@
1
1
  import { getVocabConfig } from "../../../../services/vocab/config.mjs";
2
- import { execSync } from "node:child_process";
3
2
  import { resolveConfig } from "@vocab/core";
3
+ import { execSync } from "node:child_process";
4
4
  //#region src/program/commands/translations/helpers/translation-helpers.ts
5
5
  const ensureBranch = async () => {
6
6
  const branch = getGitBranch();
@@ -17,8 +17,8 @@ const configureProject = async (skuContext) => {
17
17
  log(`"skuSkipConfigure" set in ${packageJson}, skipping sku configuration`);
18
18
  return;
19
19
  }
20
- const { default: configure } = await import("./configureApp.mjs");
21
- await configure(skuContext);
20
+ const { configureApp } = await import("./configureApp.mjs");
21
+ await configureApp(skuContext);
22
22
  };
23
23
  const validatePeerDeps = (skuContext) => {
24
24
  if (skipValidatePeerDeps) {
@@ -1,5 +1,6 @@
1
1
  import { getPathFromCwd, writeFileToCWD } from "../private/utils/src/filesystem/cwd.mjs";
2
- import { isAtLeastPnpmV10, isAtLeastRecommendedPnpmVersion, rootDir } from "../private/utils/src/packageManager/packageManager.mjs";
2
+ import { isPnpm, rootDir } from "../private/utils/src/packageManager/packageManager.mjs";
3
+ import { ensurePnpmWorkspaceConfig } from "../private/utils/src/packageManager/ensurePnpmWorkspaceConfig.mjs";
3
4
  import "../private/utils/src/index.mjs";
4
5
  import options from "../config/prettier.mjs";
5
6
  import { bundleReportFolder } from "../services/webpack/config/plugins/bundleAnalyzer.mjs";
@@ -7,8 +8,6 @@ import { createTSConfig } from "../services/typescript/tsconfig.mjs";
7
8
  import getCertificate from "./certificate.mjs";
8
9
  import { syncPathAliasImports } from "./pathAliasImports.mjs";
9
10
  import { validateSkuConfigFormat } from "./validateSkuConfigFormat.mjs";
10
- import { getPnpmConfigDependencies } from "../services/packageManager/getPnpmConfigDependencies.mjs";
11
- import { validatePnpmConfig } from "../services/packageManager/pnpmConfig.mjs";
12
11
  import { warnOnLegacyReact } from "./warnOnLegacyReact.mjs";
13
12
  import path from "node:path";
14
13
  import { rm } from "node:fs/promises";
@@ -18,7 +17,7 @@ import ensureGitignore from "ensure-gitignore";
18
17
  const coverageFolder = "coverage";
19
18
  const convertToForwardSlashPaths = (pathStr) => pathStr.replace(/\\/g, "/");
20
19
  const addSep = (p) => `${p}${path.sep}`;
21
- var configureApp_default = async (skuContext) => {
20
+ async function configureApp(skuContext, options$1) {
22
21
  const { paths, httpsDevServer, languages, hosts } = skuContext;
23
22
  validateSkuConfigFormat(paths.appSkuConfigPath);
24
23
  const targetDirectories = [.../* @__PURE__ */ new Set([addSep(paths.relativeTarget)])];
@@ -65,16 +64,11 @@ var configureApp_default = async (skuContext) => {
65
64
  comment: "managed by sku",
66
65
  patterns: gitIgnorePatterns.map(convertToForwardSlashPaths)
67
66
  });
68
- if (rootDir && isAtLeastPnpmV10()) {
69
- const pnpmConfigDependencies = await getPnpmConfigDependencies();
70
- const hasRecommendedPnpmVersionInstalled = isAtLeastRecommendedPnpmVersion();
71
- const pnpmPluginSkuInstalled = pnpmConfigDependencies.includes("pnpm-plugin-sku");
72
- await validatePnpmConfig({
73
- hasRecommendedPnpmVersionInstalled,
74
- pnpmPluginSkuInstalled
75
- });
76
- }
67
+ if (rootDir && isPnpm) await ensurePnpmWorkspaceConfig({
68
+ targetDir: rootDir,
69
+ mode: options$1?.mode ?? "additive"
70
+ });
77
71
  warnOnLegacyReact();
78
- };
72
+ }
79
73
  //#endregion
80
- export { configureApp_default as default };
74
+ export { configureApp };
@@ -7,8 +7,8 @@ import "../private/utils/src/console/index.mjs";
7
7
  import telemetry_default from "../services/telemetry/index.mjs";
8
8
  import { readFileSync } from "node:fs";
9
9
  import { readFile } from "node:fs/promises";
10
- import semver from "semver";
11
10
  import { fdir } from "fdir";
11
+ import semver from "semver";
12
12
  //#region src/utils/validatePeerDeps.ts
13
13
  const asyncMap = (list, fn) => Promise.all(list.map((item) => fn(item)));
14
14
  const singletonPackages = ["@vanilla-extract/css"];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sku",
3
- "version": "16.2.0",
3
+ "version": "16.3.0-replace-pnpm-plugin-20260907064258",
4
4
  "description": "Front-end development toolkit, powered by Webpack, Babel, Vanilla Extract and Jest",
5
5
  "types": "./dist/index.d.mts",
6
6
  "bin": {
@@ -169,7 +169,8 @@
169
169
  "webpack-bundle-analyzer": "^5.1.1",
170
170
  "webpack-dev-server": "^5.2.4",
171
171
  "webpack-merge": "^6.0.1",
172
- "webpack-node-externals": "^3.0.0"
172
+ "webpack-node-externals": "^3.0.0",
173
+ "yaml": "^2.8.3"
173
174
  },
174
175
  "devDependencies": {
175
176
  "@sku-private/tsdown": "0.0.0",
@@ -1,12 +0,0 @@
1
- import { execAsync } from "../../utils/execAsync.mjs";
2
- //#region src/services/packageManager/getPnpmConfigDependencies.ts
3
- const getPnpmConfigDependencies = async () => {
4
- const rawResult = await execAsync(`pnpm config get --json`);
5
- if (!rawResult) return [];
6
- const result = JSON.parse(rawResult);
7
- const configDependencies = result["config-dependencies"] ?? result.configDependencies;
8
- if (!configDependencies) return [];
9
- return Object.keys(configDependencies);
10
- };
11
- //#endregion
12
- export { getPnpmConfigDependencies };
@@ -1,14 +0,0 @@
1
- import { accent } from "../../private/utils/src/console/styles.mjs";
2
- import { banner } from "../../private/utils/src/console/banner.mjs";
3
- import "../../private/utils/src/console/index.mjs";
4
- //#region src/services/packageManager/pnpmConfig.ts
5
- const validatePnpmConfig = async ({ hasRecommendedPnpmVersionInstalled, pnpmPluginSkuInstalled }) => {
6
- if (!pnpmPluginSkuInstalled || !hasRecommendedPnpmVersionInstalled) {
7
- const messages = [];
8
- if (!hasRecommendedPnpmVersionInstalled) messages.push(`Please ensure you are on ${accent("PNPM v10.13.0")} or later.`);
9
- messages.push(`Please delete your top-level ${accent("node_modules")} and run "${accent("pnpm add --config pnpm-plugin-sku && pnpm install")}".`);
10
- banner("caution", "pnpm-plugin-sku is not configured correctly", messages);
11
- }
12
- };
13
- //#endregion
14
- export { validatePnpmConfig };