@_linked/cli 1.46.2 → 1.47.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.
Files changed (167) hide show
  1. package/CHANGELOG.md +107 -0
  2. package/README.md +48 -36
  3. package/hooks/drift.cjs +169 -0
  4. package/hooks/git-hook.cjs +170 -0
  5. package/hooks/hook-shim.cjs +63 -0
  6. package/hooks/lock-at-rest.cjs +268 -0
  7. package/hooks/manifests.cjs +216 -0
  8. package/hooks/platform.cjs +25 -0
  9. package/lib/esm/app-release/build-backend.d.ts.map +1 -1
  10. package/lib/esm/app-release/build-backend.js +2 -1
  11. package/lib/esm/app-release/build-backend.js.map +1 -1
  12. package/lib/esm/cli-methods.d.ts.map +1 -1
  13. package/lib/esm/cli-methods.js +166 -150
  14. package/lib/esm/cli-methods.js.map +1 -1
  15. package/lib/esm/cli.js +20 -22
  16. package/lib/esm/cli.js.map +1 -1
  17. package/lib/esm/commands/build-app.d.ts.map +1 -1
  18. package/lib/esm/commands/build-app.js +2 -1
  19. package/lib/esm/commands/build-app.js.map +1 -1
  20. package/lib/esm/commands/build-package.d.ts.map +1 -1
  21. package/lib/esm/commands/build-package.js +6 -2
  22. package/lib/esm/commands/build-package.js.map +1 -1
  23. package/lib/esm/commands/localize.d.ts +9 -15
  24. package/lib/esm/commands/localize.d.ts.map +1 -1
  25. package/lib/esm/commands/localize.js +43 -74
  26. package/lib/esm/commands/localize.js.map +1 -1
  27. package/lib/esm/commands/setup-publish.d.ts.map +1 -1
  28. package/lib/esm/commands/setup-publish.js +22 -15
  29. package/lib/esm/commands/setup-publish.js.map +1 -1
  30. package/lib/esm/commands/start.d.ts +8 -0
  31. package/lib/esm/commands/start.d.ts.map +1 -1
  32. package/lib/esm/commands/start.js +20 -8
  33. package/lib/esm/commands/start.js.map +1 -1
  34. package/lib/esm/installed-packages.d.ts +5 -13
  35. package/lib/esm/installed-packages.d.ts.map +1 -1
  36. package/lib/esm/installed-packages.js +5 -38
  37. package/lib/esm/installed-packages.js.map +1 -1
  38. package/lib/esm/lifecycle.d.ts +35 -22
  39. package/lib/esm/lifecycle.d.ts.map +1 -1
  40. package/lib/esm/lifecycle.js +62 -36
  41. package/lib/esm/lifecycle.js.map +1 -1
  42. package/lib/esm/localize/adopt.d.ts +5 -5
  43. package/lib/esm/localize/adopt.d.ts.map +1 -1
  44. package/lib/esm/localize/adopt.js +46 -55
  45. package/lib/esm/localize/adopt.js.map +1 -1
  46. package/lib/esm/localize/app-root.d.ts +16 -0
  47. package/lib/esm/localize/app-root.d.ts.map +1 -0
  48. package/lib/esm/localize/app-root.js +94 -0
  49. package/lib/esm/localize/app-root.js.map +1 -0
  50. package/lib/esm/localize/app-setup.d.ts +21 -0
  51. package/lib/esm/localize/app-setup.d.ts.map +1 -0
  52. package/lib/esm/localize/app-setup.js +178 -0
  53. package/lib/esm/localize/app-setup.js.map +1 -0
  54. package/lib/esm/localize/delocalize.d.ts.map +1 -1
  55. package/lib/esm/localize/delocalize.js +169 -104
  56. package/lib/esm/localize/delocalize.js.map +1 -1
  57. package/lib/esm/localize/drift-hook.d.ts +18 -0
  58. package/lib/esm/localize/drift-hook.d.ts.map +1 -0
  59. package/lib/esm/localize/drift-hook.js +37 -0
  60. package/lib/esm/localize/drift-hook.js.map +1 -0
  61. package/lib/esm/localize/drift.d.ts +36 -0
  62. package/lib/esm/localize/drift.d.ts.map +1 -0
  63. package/lib/esm/localize/drift.js +34 -0
  64. package/lib/esm/localize/drift.js.map +1 -0
  65. package/lib/esm/localize/errors.d.ts +14 -8
  66. package/lib/esm/localize/errors.d.ts.map +1 -1
  67. package/lib/esm/localize/errors.js +14 -8
  68. package/lib/esm/localize/errors.js.map +1 -1
  69. package/lib/esm/localize/fsops.d.ts +11 -27
  70. package/lib/esm/localize/fsops.d.ts.map +1 -1
  71. package/lib/esm/localize/fsops.js +40 -115
  72. package/lib/esm/localize/fsops.js.map +1 -1
  73. package/lib/esm/localize/git-hooks.d.ts +33 -0
  74. package/lib/esm/localize/git-hooks.d.ts.map +1 -0
  75. package/lib/esm/localize/git-hooks.js +146 -0
  76. package/lib/esm/localize/git-hooks.js.map +1 -0
  77. package/lib/esm/localize/hook-module.d.ts +6 -0
  78. package/lib/esm/localize/hook-module.d.ts.map +1 -0
  79. package/lib/esm/localize/hook-module.js +37 -0
  80. package/lib/esm/localize/hook-module.js.map +1 -0
  81. package/lib/esm/localize/index.d.ts +14 -15
  82. package/lib/esm/localize/index.d.ts.map +1 -1
  83. package/lib/esm/localize/index.js +8 -9
  84. package/lib/esm/localize/index.js.map +1 -1
  85. package/lib/esm/localize/list.d.ts +6 -10
  86. package/lib/esm/localize/list.d.ts.map +1 -1
  87. package/lib/esm/localize/list.js +49 -189
  88. package/lib/esm/localize/list.js.map +1 -1
  89. package/lib/esm/localize/localize.d.ts +46 -62
  90. package/lib/esm/localize/localize.d.ts.map +1 -1
  91. package/lib/esm/localize/localize.js +411 -285
  92. package/lib/esm/localize/localize.js.map +1 -1
  93. package/lib/esm/localize/members.d.ts +13 -0
  94. package/lib/esm/localize/members.d.ts.map +1 -0
  95. package/lib/esm/localize/members.js +46 -0
  96. package/lib/esm/localize/members.js.map +1 -0
  97. package/lib/esm/localize/resolve.d.ts +1 -2
  98. package/lib/esm/localize/resolve.d.ts.map +1 -1
  99. package/lib/esm/localize/resolve.js +3 -5
  100. package/lib/esm/localize/resolve.js.map +1 -1
  101. package/lib/esm/localize/run.d.ts +1 -0
  102. package/lib/esm/localize/run.d.ts.map +1 -1
  103. package/lib/esm/localize/run.js +11 -6
  104. package/lib/esm/localize/run.js.map +1 -1
  105. package/lib/esm/localize/setup-lockfile.d.ts +13 -0
  106. package/lib/esm/localize/setup-lockfile.d.ts.map +1 -0
  107. package/lib/esm/localize/setup-lockfile.js +80 -0
  108. package/lib/esm/localize/setup-lockfile.js.map +1 -0
  109. package/lib/esm/utils/createPackageLocation.d.ts +4 -13
  110. package/lib/esm/utils/createPackageLocation.d.ts.map +1 -1
  111. package/lib/esm/utils/createPackageLocation.js +12 -21
  112. package/lib/esm/utils/createPackageLocation.js.map +1 -1
  113. package/lib/esm/utils/packageSetup.d.ts +5 -2
  114. package/lib/esm/utils/packageSetup.d.ts.map +1 -1
  115. package/lib/esm/utils/packageSetup.js +9 -3
  116. package/lib/esm/utils/packageSetup.js.map +1 -1
  117. package/lib/esm/utils/platform.d.ts +24 -0
  118. package/lib/esm/utils/platform.d.ts.map +1 -0
  119. package/lib/esm/utils/platform.js +62 -0
  120. package/lib/esm/utils/platform.js.map +1 -0
  121. package/lib/esm/utils/shapeReferences.d.ts.map +1 -1
  122. package/lib/esm/utils/shapeReferences.js +3 -2
  123. package/lib/esm/utils/shapeReferences.js.map +1 -1
  124. package/lib/esm/utils/spawn.d.ts +65 -0
  125. package/lib/esm/utils/spawn.d.ts.map +1 -0
  126. package/lib/esm/utils/spawn.js +181 -0
  127. package/lib/esm/utils/spawn.js.map +1 -0
  128. package/lib/esm/utils.d.ts.map +1 -1
  129. package/lib/esm/utils.js +19 -5
  130. package/lib/esm/utils.js.map +1 -1
  131. package/lib/esm/vite-config.d.ts +5 -12
  132. package/lib/esm/vite-config.d.ts.map +1 -1
  133. package/lib/esm/vite-config.js +41 -33
  134. package/lib/esm/vite-config.js.map +1 -1
  135. package/package.json +4 -3
  136. package/lib/esm/localize/ensure.d.ts +0 -44
  137. package/lib/esm/localize/ensure.d.ts.map +0 -1
  138. package/lib/esm/localize/ensure.js +0 -151
  139. package/lib/esm/localize/ensure.js.map +0 -1
  140. package/lib/esm/localize/manifest.d.ts +0 -63
  141. package/lib/esm/localize/manifest.d.ts.map +0 -1
  142. package/lib/esm/localize/manifest.js +0 -133
  143. package/lib/esm/localize/manifest.js.map +0 -1
  144. package/lib/esm/localize/one-copy-hook.d.ts +0 -16
  145. package/lib/esm/localize/one-copy-hook.d.ts.map +0 -1
  146. package/lib/esm/localize/one-copy-hook.js +0 -20
  147. package/lib/esm/localize/one-copy-hook.js.map +0 -1
  148. package/lib/esm/localize/provided.d.ts +0 -28
  149. package/lib/esm/localize/provided.d.ts.map +0 -1
  150. package/lib/esm/localize/provided.js +0 -89
  151. package/lib/esm/localize/provided.js.map +0 -1
  152. package/lib/esm/localize/prune.d.ts +0 -182
  153. package/lib/esm/localize/prune.d.ts.map +0 -1
  154. package/lib/esm/localize/prune.js +0 -703
  155. package/lib/esm/localize/prune.js.map +0 -1
  156. package/lib/esm/localize/relink.d.ts +0 -13
  157. package/lib/esm/localize/relink.d.ts.map +0 -1
  158. package/lib/esm/localize/relink.js +0 -181
  159. package/lib/esm/localize/relink.js.map +0 -1
  160. package/lib/esm/localized-dedupe.d.ts +0 -26
  161. package/lib/esm/localized-dedupe.d.ts.map +0 -1
  162. package/lib/esm/localized-dedupe.js +0 -139
  163. package/lib/esm/localized-dedupe.js.map +0 -1
  164. package/lib/esm/utils/relinkHooks.d.ts +0 -19
  165. package/lib/esm/utils/relinkHooks.d.ts.map +0 -1
  166. package/lib/esm/utils/relinkHooks.js +0 -74
  167. package/lib/esm/utils/relinkHooks.js.map +0 -1
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Which directory is the app: the one whose `package.json` lists the
3
+ * workspaces, wherever in it a command runs. `linked localize` run inside
4
+ * `packages/foo` must set up and install the app, not the member; `build-all`
5
+ * plans from the same root.
6
+ *
7
+ * Only a directory that is a workspace of the one above it belongs to it. A
8
+ * git worktree nested in an app (`<app>/.claude/worktrees/<wt>`) has its own
9
+ * `package.json` with `workspaces`, and none of the app's globs match it: it
10
+ * is its own app, and a command run there must not act on the main checkout.
11
+ */
12
+ import fs from 'node:fs';
13
+ import path from 'node:path';
14
+ import { readJson } from './fsops.js';
15
+ import { requireHook } from './hook-module.js';
16
+ let manifests;
17
+ /** `hooks/manifests.cjs`: the glob matching the hooks use, loaded on first use. */
18
+ function manifestsModule() {
19
+ manifests !== null && manifests !== void 0 ? manifests : (manifests = requireHook('manifests.cjs', (mod) => typeof mod.workspaceGlobs === 'function' &&
20
+ typeof mod.globsMatch === 'function'));
21
+ return manifests;
22
+ }
23
+ function manifestAt(dir) {
24
+ const file = path.join(dir, 'package.json');
25
+ const pkg = fs.existsSync(file) ? readJson(file) : null;
26
+ if (!pkg || typeof pkg !== 'object')
27
+ return null;
28
+ return {
29
+ workspaces: 'workspaces' in pkg && !!pkg.workspaces,
30
+ linkedPackage: 'linkedPackage' in pkg && pkg.linkedPackage === true,
31
+ };
32
+ }
33
+ /**
34
+ * The workspace root for `start`: the nearest ancestor (or `start` itself)
35
+ * whose `package.json` has `workspaces`, then each higher ancestor with
36
+ * `workspaces` whose globs match the root found so far -- the first one that
37
+ * does not ends the climb. Of that chain, the topmost that is not itself a
38
+ * linked package (`"linkedPackage": true`), else the topmost. Null when there
39
+ * is none.
40
+ */
41
+ export function findWorkspaceRoot(start) {
42
+ var _a;
43
+ const nearest = nearestWithWorkspaces(path.resolve(start));
44
+ if (!nearest)
45
+ return null;
46
+ let topmost = nearest.dir;
47
+ let topmostApp = nearest.linkedPackage ? null : nearest.dir;
48
+ for (let above = path.dirname(topmost);; above = path.dirname(above)) {
49
+ const m = manifestAt(above);
50
+ if (m === null || m === void 0 ? void 0 : m.workspaces) {
51
+ const { workspaceGlobs, globsMatch } = manifestsModule();
52
+ const rel = path.relative(above, topmost).split(path.sep).join('/');
53
+ const globs = (_a = workspaceGlobs(above)) !== null && _a !== void 0 ? _a : [];
54
+ if (!globsMatch(globs, rel) && !underPackagesLocal(globs, rel))
55
+ break;
56
+ topmost = above;
57
+ if (!m.linkedPackage)
58
+ topmostApp = above;
59
+ }
60
+ if (path.dirname(above) === above)
61
+ break;
62
+ }
63
+ return topmostApp !== null && topmostApp !== void 0 ? topmostApp : topmost;
64
+ }
65
+ /**
66
+ * A `--subdir` checkout is a symlink `packages-local/<name>` into
67
+ * `packages-local/.repos/<mono>/…`, and a command run in it sees the real
68
+ * path, which `packages-local/*` does not match: the climb would stop at the
69
+ * framework monorepo and localize would set it up. So anything under
70
+ * `packages-local/` (`.repos/`, `.setup-aside/` too) belongs to an app whose
71
+ * globs list `packages-local/*`.
72
+ */
73
+ function underPackagesLocal(globs, rel) {
74
+ return (rel.startsWith('packages-local/') && globs.includes('packages-local/*'));
75
+ }
76
+ function nearestWithWorkspaces(start) {
77
+ for (let dir = start;; dir = path.dirname(dir)) {
78
+ const m = manifestAt(dir);
79
+ if (m === null || m === void 0 ? void 0 : m.workspaces)
80
+ return { dir, linkedPackage: m.linkedPackage };
81
+ if (path.dirname(dir) === dir)
82
+ return null;
83
+ }
84
+ }
85
+ /**
86
+ * The app root for a command run in `start`: the workspace root above it, or
87
+ * `start` itself for an app that has no `workspaces` yet (localize's set-up
88
+ * adds them).
89
+ */
90
+ export function findAppRoot(start) {
91
+ var _a;
92
+ return (_a = findWorkspaceRoot(start)) !== null && _a !== void 0 ? _a : path.resolve(start);
93
+ }
94
+ //# sourceMappingURL=app-root.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-root.js","sourceRoot":"","sources":["../../../src/localize/app-root.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Which directory is the app: the one whose `package.json` lists the\n * workspaces, wherever in it a command runs. `linked localize` run inside\n * `packages/foo` must set up and install the app, not the member; `build-all`\n * plans from the same root.\n *\n * Only a directory that is a workspace of the one above it belongs to it. A\n * git worktree nested in an app (`<app>/.claude/worktrees/<wt>`) has its own\n * `package.json` with `workspaces`, and none of the app's globs match it: it\n * is its own app, and a command run there must not act on the main checkout.\n */\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nimport {readJson} from './fsops.js';\nimport {requireHook} from './hook-module.js';\n\ninterface ManifestsModule {\n workspaceGlobs: (appRoot: string) => string[] | null;\n globsMatch: (globs: string[], rel: string) => boolean;\n}\n\nlet manifests: ManifestsModule | undefined;\n\n/** `hooks/manifests.cjs`: the glob matching the hooks use, loaded on first use. */\nfunction manifestsModule(): ManifestsModule {\n manifests ??= requireHook<ManifestsModule>(\n 'manifests.cjs',\n (mod) =>\n typeof mod.workspaceGlobs === 'function' &&\n typeof mod.globsMatch === 'function',\n );\n return manifests;\n}\n\ninterface Manifest {\n workspaces: boolean;\n linkedPackage: boolean;\n}\n\nfunction manifestAt(dir: string): Manifest | null {\n const file = path.join(dir, 'package.json');\n const pkg: unknown = fs.existsSync(file) ? readJson(file) : null;\n if (!pkg || typeof pkg !== 'object') return null;\n return {\n workspaces: 'workspaces' in pkg && !!pkg.workspaces,\n linkedPackage: 'linkedPackage' in pkg && pkg.linkedPackage === true,\n };\n}\n\n/**\n * The workspace root for `start`: the nearest ancestor (or `start` itself)\n * whose `package.json` has `workspaces`, then each higher ancestor with\n * `workspaces` whose globs match the root found so far -- the first one that\n * does not ends the climb. Of that chain, the topmost that is not itself a\n * linked package (`\"linkedPackage\": true`), else the topmost. Null when there\n * is none.\n */\nexport function findWorkspaceRoot(start: string): string | null {\n const nearest = nearestWithWorkspaces(path.resolve(start));\n if (!nearest) return null;\n let topmost = nearest.dir;\n let topmostApp: string | null = nearest.linkedPackage ? null : nearest.dir;\n for (let above = path.dirname(topmost); ; above = path.dirname(above)) {\n const m = manifestAt(above);\n if (m?.workspaces) {\n const {workspaceGlobs, globsMatch} = manifestsModule();\n const rel = path.relative(above, topmost).split(path.sep).join('/');\n const globs = workspaceGlobs(above) ?? [];\n if (!globsMatch(globs, rel) && !underPackagesLocal(globs, rel)) break;\n topmost = above;\n if (!m.linkedPackage) topmostApp = above;\n }\n if (path.dirname(above) === above) break;\n }\n return topmostApp ?? topmost;\n}\n\n/**\n * A `--subdir` checkout is a symlink `packages-local/<name>` into\n * `packages-local/.repos/<mono>/…`, and a command run in it sees the real\n * path, which `packages-local/*` does not match: the climb would stop at the\n * framework monorepo and localize would set it up. So anything under\n * `packages-local/` (`.repos/`, `.setup-aside/` too) belongs to an app whose\n * globs list `packages-local/*`.\n */\nfunction underPackagesLocal(globs: string[], rel: string): boolean {\n return (\n rel.startsWith('packages-local/') && globs.includes('packages-local/*')\n );\n}\n\nfunction nearestWithWorkspaces(\n start: string,\n): {dir: string; linkedPackage: boolean} | null {\n for (let dir = start; ; dir = path.dirname(dir)) {\n const m = manifestAt(dir);\n if (m?.workspaces) return {dir, linkedPackage: m.linkedPackage};\n if (path.dirname(dir) === dir) return null;\n }\n}\n\n/**\n * The app root for a command run in `start`: the workspace root above it, or\n * `start` itself for an app that has no `workspaces` yet (localize's set-up\n * adds them).\n */\nexport function findAppRoot(start: string): string {\n return findWorkspaceRoot(start) ?? path.resolve(start);\n}\n"]}
@@ -0,0 +1,21 @@
1
+ export interface SetupChange {
2
+ file: 'package.json' | '.gitignore';
3
+ what: string;
4
+ }
5
+ /**
6
+ * What the app's `postinstall` and `dependencies` run: the hook by its path,
7
+ * not through `linked` (see hooks/lock-at-rest.cjs), and only when the cli is
8
+ * installed -- `npm ci --omit=dev` leaves it out, and the install must not
9
+ * fail for that.
10
+ */
11
+ export declare const LOCK_AT_REST_COMMAND: string;
12
+ /** Make the app ready for checkouts; returns what it changed. */
13
+ export declare function ensureAppSetup(appRoot: string): SetupChange[];
14
+ /**
15
+ * The `overrides` keys -- at any depth, as `a > b` -- that name one of
16
+ * `names`. A root one makes npm fail with `EOVERRIDE` once that package is a
17
+ * workspace member; a nested one installs, but overrides the checkout the
18
+ * developer localized it to use.
19
+ */
20
+ export declare function conflictingOverrides(appRoot: string, names: string[]): string[];
21
+ //# sourceMappingURL=app-setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-setup.d.ts","sourceRoot":"","sources":["../../../src/localize/app-setup.ts"],"names":[],"mappings":"AAwBA,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,cAAc,GAAG,YAAY,CAAC;IACpC,IAAI,EAAE,MAAM,CAAC;CACd;AAGD;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,QAEoC,CAAC;AAWtE,iEAAiE;AACjE,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,EAAE,CAE7D;AAoGD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EAAE,GACd,MAAM,EAAE,CAeV"}
@@ -0,0 +1,178 @@
1
+ /**
2
+ * The one-time setup an app needs before a checkout can be a workspace
3
+ * member, made by `linked localize` and committed by the developer:
4
+ *
5
+ * - `"packages-local/*"` in `workspaces`, so npm links and dedupes checkouts;
6
+ * - `postinstall` and `dependencies` run the lock-at-rest hook, which puts the
7
+ * committed `package-lock.json` back after npm rewrote it to list them;
8
+ * - `.gitignore` keeps checkouts and parked checkouts out of git.
9
+ *
10
+ * Idempotent: a second run changes nothing and returns `[]`. `package.json` is
11
+ * rewritten only when something changed, with its own indentation, key order,
12
+ * line endings and trailing newline, so the diff is the setup and nothing else.
13
+ *
14
+ * It never edits `overrides`. npm refuses (`EOVERRIDE`) a root override on a
15
+ * workspace member's name, and what to do about it -- drop the override or
16
+ * not localize -- is the developer's call; `conflictingOverrides` names them.
17
+ */
18
+ import fs from 'node:fs';
19
+ import path from 'node:path';
20
+ import { withEolOf } from '../utils/platform.js';
21
+ import { LocalizeError, EXIT_REFUSED } from './errors.js';
22
+ import { LOCAL_DIR, PARKED_DIR } from './members.js';
23
+ const WORKSPACE_GLOB = `${LOCAL_DIR}/*`;
24
+ /**
25
+ * What the app's `postinstall` and `dependencies` run: the hook by its path,
26
+ * not through `linked` (see hooks/lock-at-rest.cjs), and only when the cli is
27
+ * installed -- `npm ci --omit=dev` leaves it out, and the install must not
28
+ * fail for that.
29
+ */
30
+ export const LOCK_AT_REST_COMMAND = `node -e "const p='node_modules/@_linked/cli/hooks/lock-at-rest.cjs';` +
31
+ `require('fs').existsSync(p)&&require(require('path').resolve(p))"`;
32
+ const HOOK_SCRIPTS = ['postinstall', 'dependencies'];
33
+ /**
34
+ * Steps the hook replaces: what an app ran before checkouts were workspace
35
+ * members, and the hook's first, unguarded form. Matched as a whole step of
36
+ * an `&&` chain.
37
+ */
38
+ const REPLACED_STEP = /(^|&&)(\s*)(?:(?:npx )?linked localize --relink|node node_modules\/@_linked\/cli\/hooks\/lock-at-rest\.cjs)(\s*)(?=&&|$)/g;
39
+ const IGNORED = [`${LOCAL_DIR}/`, `${PARKED_DIR}/`];
40
+ /** Make the app ready for checkouts; returns what it changed. */
41
+ export function ensureAppSetup(appRoot) {
42
+ return [...setupPackageJson(appRoot), ...setupGitignore(appRoot)];
43
+ }
44
+ function setupPackageJson(appRoot) {
45
+ var _a, _b;
46
+ const file = path.join(appRoot, 'package.json');
47
+ const { text, pkg } = readPackageJson(file);
48
+ const changes = [];
49
+ const change = (what) => changes.push({ file: 'package.json', what });
50
+ // npm reads an array; the `{packages: [...]}` form is yarn's, and npm reads its `packages` too.
51
+ const ws = pkg.workspaces;
52
+ const globs = Array.isArray(ws)
53
+ ? ws
54
+ : ws && typeof ws === 'object' && Array.isArray(ws.packages)
55
+ ? ws.packages
56
+ : null;
57
+ if (!globs) {
58
+ if (ws !== undefined)
59
+ throw new LocalizeError(`package.json "workspaces" is neither a list nor {"packages": [...]}; ` +
60
+ `add "${WORKSPACE_GLOB}" to it by hand.`, EXIT_REFUSED);
61
+ pkg.workspaces = [WORKSPACE_GLOB];
62
+ change(`workspaces: ["${WORKSPACE_GLOB}"]`);
63
+ }
64
+ else if (!globs.includes(WORKSPACE_GLOB)) {
65
+ globs.push(WORKSPACE_GLOB);
66
+ change(`workspaces += "${WORKSPACE_GLOB}"`);
67
+ }
68
+ if (pkg.scripts === undefined)
69
+ pkg.scripts = {};
70
+ const scripts = pkg.scripts;
71
+ if (!scripts || typeof scripts !== 'object' || Array.isArray(scripts))
72
+ throw new LocalizeError('package.json "scripts" is not an object.', EXIT_REFUSED);
73
+ for (const name of HOOK_SCRIPTS) {
74
+ const before = scripts[name];
75
+ const next = withHook(typeof before === 'string' ? before : undefined);
76
+ if (next === before)
77
+ continue;
78
+ scripts[name] = next;
79
+ change(typeof before === 'string'
80
+ ? `scripts.${name}: "${before}" -> "${next}"`
81
+ : `scripts.${name}: "${next}"`);
82
+ }
83
+ if (changes.length) {
84
+ const indent = (_b = (_a = text.match(/^([ \t]+)"/m)) === null || _a === void 0 ? void 0 : _a[1]) !== null && _b !== void 0 ? _b : ' ';
85
+ const eol = text.endsWith('\n') ? '\n' : '';
86
+ fs.writeFileSync(file, withEolOf(JSON.stringify(pkg, null, indent) + eol, text));
87
+ }
88
+ return changes;
89
+ }
90
+ /**
91
+ * The script with the hook in it: the hook alone for none, an old
92
+ * `linked localize --relink` step or the unguarded hook replaced by it,
93
+ * anything else kept with the hook after it. A script that already runs the
94
+ * hook (or runs `hooks/lock-at-rest.cjs` some other way) is returned as it is.
95
+ */
96
+ function withHook(script) {
97
+ if (script === undefined || !script.trim())
98
+ return LOCK_AT_REST_COMMAND;
99
+ if (script.includes(LOCK_AT_REST_COMMAND))
100
+ return script;
101
+ const replaced = script.replace(REPLACED_STEP, (_step, chain, before, after) => `${chain}${before}${LOCK_AT_REST_COMMAND}${after}`);
102
+ if (replaced !== script)
103
+ return replaced;
104
+ if (script.includes('hooks/lock-at-rest.cjs'))
105
+ return script;
106
+ return `${script} && ${LOCK_AT_REST_COMMAND}`;
107
+ }
108
+ function setupGitignore(appRoot) {
109
+ const file = path.join(appRoot, '.gitignore');
110
+ let text = '';
111
+ try {
112
+ text = fs.readFileSync(file, 'utf8');
113
+ }
114
+ catch (_a) {
115
+ /* none yet */
116
+ }
117
+ // `packages-local`, `/packages-local` and `packages-local/` all ignore the directory.
118
+ const have = new Set(text
119
+ .split(/\r?\n/)
120
+ .map((l) => l.trim().replace(/^\//, '').replace(/\/$/, '')));
121
+ const missing = IGNORED.filter((line) => !have.has(line.replace(/\/$/, '')));
122
+ if (!missing.length)
123
+ return [];
124
+ const sep = text === '' ? '' : text.endsWith('\n') ? '\n' : '\n\n';
125
+ const added = `${sep}# linked localize\n${missing.join('\n')}\n`;
126
+ fs.writeFileSync(file, text + withEolOf(added, text));
127
+ return missing.map((line) => ({ file: '.gitignore', what: `ignore ${line}` }));
128
+ }
129
+ /**
130
+ * The `overrides` keys -- at any depth, as `a > b` -- that name one of
131
+ * `names`. A root one makes npm fail with `EOVERRIDE` once that package is a
132
+ * workspace member; a nested one installs, but overrides the checkout the
133
+ * developer localized it to use.
134
+ */
135
+ export function conflictingOverrides(appRoot, names) {
136
+ const { pkg } = readPackageJson(path.join(appRoot, 'package.json'));
137
+ const wanted = new Set(names);
138
+ const found = [];
139
+ const walk = (overrides, trail) => {
140
+ if (!overrides || typeof overrides !== 'object')
141
+ return;
142
+ for (const [key, value] of Object.entries(overrides)) {
143
+ if (key === '.')
144
+ continue;
145
+ const keyPath = [...trail, key];
146
+ if (wanted.has(overrideName(key)))
147
+ found.push(keyPath.join(' > '));
148
+ walk(value, keyPath);
149
+ }
150
+ };
151
+ walk(pkg.overrides, []);
152
+ return found;
153
+ }
154
+ /** `@scope/name@^2` -> `@scope/name`, `name@1` -> `name`. */
155
+ function overrideName(key) {
156
+ const at = key.indexOf('@', 1);
157
+ return at === -1 ? key : key.slice(0, at);
158
+ }
159
+ function readPackageJson(file) {
160
+ let text;
161
+ try {
162
+ text = fs.readFileSync(file, 'utf8');
163
+ }
164
+ catch (_a) {
165
+ throw new LocalizeError(`no package.json at ${path.dirname(file)}.`, EXIT_REFUSED);
166
+ }
167
+ let pkg;
168
+ try {
169
+ pkg = JSON.parse(text);
170
+ }
171
+ catch (e) {
172
+ throw new LocalizeError(`${file} is not valid JSON: ${e.message}`, EXIT_REFUSED);
173
+ }
174
+ if (!pkg || typeof pkg !== 'object' || Array.isArray(pkg))
175
+ throw new LocalizeError(`${file} is not a JSON object.`, EXIT_REFUSED);
176
+ return { text, pkg: pkg };
177
+ }
178
+ //# sourceMappingURL=app-setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-setup.js","sourceRoot":"","sources":["../../../src/localize/app-setup.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The one-time setup an app needs before a checkout can be a workspace\n * member, made by `linked localize` and committed by the developer:\n *\n * - `\"packages-local/*\"` in `workspaces`, so npm links and dedupes checkouts;\n * - `postinstall` and `dependencies` run the lock-at-rest hook, which puts the\n * committed `package-lock.json` back after npm rewrote it to list them;\n * - `.gitignore` keeps checkouts and parked checkouts out of git.\n *\n * Idempotent: a second run changes nothing and returns `[]`. `package.json` is\n * rewritten only when something changed, with its own indentation, key order,\n * line endings and trailing newline, so the diff is the setup and nothing else.\n *\n * It never edits `overrides`. npm refuses (`EOVERRIDE`) a root override on a\n * workspace member's name, and what to do about it -- drop the override or\n * not localize -- is the developer's call; `conflictingOverrides` names them.\n */\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nimport {withEolOf} from '../utils/platform.js';\nimport {LocalizeError, EXIT_REFUSED} from './errors.js';\nimport {LOCAL_DIR, PARKED_DIR} from './members.js';\n\nexport interface SetupChange {\n file: 'package.json' | '.gitignore';\n what: string;\n}\n\nconst WORKSPACE_GLOB = `${LOCAL_DIR}/*`;\n/**\n * What the app's `postinstall` and `dependencies` run: the hook by its path,\n * not through `linked` (see hooks/lock-at-rest.cjs), and only when the cli is\n * installed -- `npm ci --omit=dev` leaves it out, and the install must not\n * fail for that.\n */\nexport const LOCK_AT_REST_COMMAND =\n `node -e \"const p='node_modules/@_linked/cli/hooks/lock-at-rest.cjs';` +\n `require('fs').existsSync(p)&&require(require('path').resolve(p))\"`;\nconst HOOK_SCRIPTS = ['postinstall', 'dependencies'] as const;\n/**\n * Steps the hook replaces: what an app ran before checkouts were workspace\n * members, and the hook's first, unguarded form. Matched as a whole step of\n * an `&&` chain.\n */\nconst REPLACED_STEP =\n /(^|&&)(\\s*)(?:(?:npx )?linked localize --relink|node node_modules\\/@_linked\\/cli\\/hooks\\/lock-at-rest\\.cjs)(\\s*)(?=&&|$)/g;\nconst IGNORED = [`${LOCAL_DIR}/`, `${PARKED_DIR}/`];\n\n/** Make the app ready for checkouts; returns what it changed. */\nexport function ensureAppSetup(appRoot: string): SetupChange[] {\n return [...setupPackageJson(appRoot), ...setupGitignore(appRoot)];\n}\n\nfunction setupPackageJson(appRoot: string): SetupChange[] {\n const file = path.join(appRoot, 'package.json');\n const {text, pkg} = readPackageJson(file);\n const changes: SetupChange[] = [];\n const change = (what: string) => changes.push({file: 'package.json', what});\n\n // npm reads an array; the `{packages: [...]}` form is yarn's, and npm reads its `packages` too.\n const ws = pkg.workspaces;\n const globs: unknown[] | null = Array.isArray(ws)\n ? ws\n : ws && typeof ws === 'object' && Array.isArray(ws.packages)\n ? ws.packages\n : null;\n if (!globs) {\n if (ws !== undefined)\n throw new LocalizeError(\n `package.json \"workspaces\" is neither a list nor {\"packages\": [...]}; ` +\n `add \"${WORKSPACE_GLOB}\" to it by hand.`,\n EXIT_REFUSED,\n );\n pkg.workspaces = [WORKSPACE_GLOB];\n change(`workspaces: [\"${WORKSPACE_GLOB}\"]`);\n } else if (!globs.includes(WORKSPACE_GLOB)) {\n globs.push(WORKSPACE_GLOB);\n change(`workspaces += \"${WORKSPACE_GLOB}\"`);\n }\n\n if (pkg.scripts === undefined) pkg.scripts = {};\n const scripts = pkg.scripts;\n if (!scripts || typeof scripts !== 'object' || Array.isArray(scripts))\n throw new LocalizeError(\n 'package.json \"scripts\" is not an object.',\n EXIT_REFUSED,\n );\n for (const name of HOOK_SCRIPTS) {\n const before: unknown = scripts[name];\n const next = withHook(typeof before === 'string' ? before : undefined);\n if (next === before) continue;\n scripts[name] = next;\n change(\n typeof before === 'string'\n ? `scripts.${name}: \"${before}\" -> \"${next}\"`\n : `scripts.${name}: \"${next}\"`,\n );\n }\n\n if (changes.length) {\n const indent = text.match(/^([ \\t]+)\"/m)?.[1] ?? ' ';\n const eol = text.endsWith('\\n') ? '\\n' : '';\n fs.writeFileSync(\n file,\n withEolOf(JSON.stringify(pkg, null, indent) + eol, text),\n );\n }\n return changes;\n}\n\n/**\n * The script with the hook in it: the hook alone for none, an old\n * `linked localize --relink` step or the unguarded hook replaced by it,\n * anything else kept with the hook after it. A script that already runs the\n * hook (or runs `hooks/lock-at-rest.cjs` some other way) is returned as it is.\n */\nfunction withHook(script: string | undefined): string {\n if (script === undefined || !script.trim()) return LOCK_AT_REST_COMMAND;\n if (script.includes(LOCK_AT_REST_COMMAND)) return script;\n const replaced = script.replace(\n REPLACED_STEP,\n (_step, chain: string, before: string, after: string) =>\n `${chain}${before}${LOCK_AT_REST_COMMAND}${after}`,\n );\n if (replaced !== script) return replaced;\n if (script.includes('hooks/lock-at-rest.cjs')) return script;\n return `${script} && ${LOCK_AT_REST_COMMAND}`;\n}\n\nfunction setupGitignore(appRoot: string): SetupChange[] {\n const file = path.join(appRoot, '.gitignore');\n let text = '';\n try {\n text = fs.readFileSync(file, 'utf8');\n } catch {\n /* none yet */\n }\n // `packages-local`, `/packages-local` and `packages-local/` all ignore the directory.\n const have = new Set(\n text\n .split(/\\r?\\n/)\n .map((l) => l.trim().replace(/^\\//, '').replace(/\\/$/, '')),\n );\n const missing = IGNORED.filter((line) => !have.has(line.replace(/\\/$/, '')));\n if (!missing.length) return [];\n const sep = text === '' ? '' : text.endsWith('\\n') ? '\\n' : '\\n\\n';\n const added = `${sep}# linked localize\\n${missing.join('\\n')}\\n`;\n fs.writeFileSync(file, text + withEolOf(added, text));\n return missing.map((line) => ({file: '.gitignore', what: `ignore ${line}`}));\n}\n\n/**\n * The `overrides` keys -- at any depth, as `a > b` -- that name one of\n * `names`. A root one makes npm fail with `EOVERRIDE` once that package is a\n * workspace member; a nested one installs, but overrides the checkout the\n * developer localized it to use.\n */\nexport function conflictingOverrides(\n appRoot: string,\n names: string[],\n): string[] {\n const {pkg} = readPackageJson(path.join(appRoot, 'package.json'));\n const wanted = new Set(names);\n const found: string[] = [];\n const walk = (overrides: unknown, trail: string[]) => {\n if (!overrides || typeof overrides !== 'object') return;\n for (const [key, value] of Object.entries(overrides)) {\n if (key === '.') continue;\n const keyPath = [...trail, key];\n if (wanted.has(overrideName(key))) found.push(keyPath.join(' > '));\n walk(value, keyPath);\n }\n };\n walk(pkg.overrides, []);\n return found;\n}\n\n/** `@scope/name@^2` -> `@scope/name`, `name@1` -> `name`. */\nfunction overrideName(key: string): string {\n const at = key.indexOf('@', 1);\n return at === -1 ? key : key.slice(0, at);\n}\n\nfunction readPackageJson(file: string): {\n text: string;\n pkg: Record<string, any>;\n} {\n let text: string;\n try {\n text = fs.readFileSync(file, 'utf8');\n } catch {\n throw new LocalizeError(\n `no package.json at ${path.dirname(file)}.`,\n EXIT_REFUSED,\n );\n }\n let pkg: unknown;\n try {\n pkg = JSON.parse(text);\n } catch (e) {\n throw new LocalizeError(\n `${file} is not valid JSON: ${(e as Error).message}`,\n EXIT_REFUSED,\n );\n }\n if (!pkg || typeof pkg !== 'object' || Array.isArray(pkg))\n throw new LocalizeError(`${file} is not a JSON object.`, EXIT_REFUSED);\n return {text, pkg: pkg as Record<string, any>};\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"delocalize.d.ts","sourceRoot":"","sources":["../../../src/localize/delocalize.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,UAAU,CAAC;AAEnC,wBAAgB,UAAU,CACxB,KAAK,EAAE,MAAM,EAAE,EACf,IAAI,EAAE;IAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAC,EACxC,IAAI,EAAE,IAAI,GACT,MAAM,CAuER"}
1
+ {"version":3,"file":"delocalize.d.ts","sourceRoot":"","sources":["../../../src/localize/delocalize.ts"],"names":[],"mappings":"AA6CA,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,UAAU,CAAC;AAEnC,wBAAgB,UAAU,CACxB,KAAK,EAAE,MAAM,EAAE,EACf,IAAI,EAAE;IAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAC,EACxC,IAAI,EAAE,IAAI,GACT,MAAM,CAoCR"}