@_linked/cli 1.46.2 → 1.47.1
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 +113 -0
- package/README.md +44 -36
- package/hooks/drift.cjs +169 -0
- package/hooks/git-hook.cjs +170 -0
- package/hooks/hook-shim.cjs +63 -0
- package/hooks/lock-at-rest.cjs +268 -0
- package/hooks/manifests.cjs +216 -0
- package/hooks/platform.cjs +25 -0
- package/lib/esm/app-release/build-backend.d.ts.map +1 -1
- package/lib/esm/app-release/build-backend.js +2 -1
- package/lib/esm/app-release/build-backend.js.map +1 -1
- package/lib/esm/cli-methods.d.ts.map +1 -1
- package/lib/esm/cli-methods.js +166 -150
- package/lib/esm/cli-methods.js.map +1 -1
- package/lib/esm/cli.js +20 -22
- package/lib/esm/cli.js.map +1 -1
- package/lib/esm/commands/build-app.d.ts.map +1 -1
- package/lib/esm/commands/build-app.js +2 -1
- package/lib/esm/commands/build-app.js.map +1 -1
- package/lib/esm/commands/build-package.d.ts.map +1 -1
- package/lib/esm/commands/build-package.js +6 -2
- package/lib/esm/commands/build-package.js.map +1 -1
- package/lib/esm/commands/localize.d.ts +9 -15
- package/lib/esm/commands/localize.d.ts.map +1 -1
- package/lib/esm/commands/localize.js +43 -74
- package/lib/esm/commands/localize.js.map +1 -1
- package/lib/esm/commands/setup-publish.d.ts.map +1 -1
- package/lib/esm/commands/setup-publish.js +22 -15
- package/lib/esm/commands/setup-publish.js.map +1 -1
- package/lib/esm/commands/start.d.ts +8 -0
- package/lib/esm/commands/start.d.ts.map +1 -1
- package/lib/esm/commands/start.js +20 -8
- package/lib/esm/commands/start.js.map +1 -1
- package/lib/esm/installed-packages.d.ts +5 -13
- package/lib/esm/installed-packages.d.ts.map +1 -1
- package/lib/esm/installed-packages.js +5 -38
- package/lib/esm/installed-packages.js.map +1 -1
- package/lib/esm/lifecycle.d.ts +35 -22
- package/lib/esm/lifecycle.d.ts.map +1 -1
- package/lib/esm/lifecycle.js +62 -36
- package/lib/esm/lifecycle.js.map +1 -1
- package/lib/esm/localize/adopt.d.ts +5 -5
- package/lib/esm/localize/adopt.d.ts.map +1 -1
- package/lib/esm/localize/adopt.js +46 -55
- package/lib/esm/localize/adopt.js.map +1 -1
- package/lib/esm/localize/app-root.d.ts +16 -0
- package/lib/esm/localize/app-root.d.ts.map +1 -0
- package/lib/esm/localize/app-root.js +94 -0
- package/lib/esm/localize/app-root.js.map +1 -0
- package/lib/esm/localize/app-setup.d.ts +21 -0
- package/lib/esm/localize/app-setup.d.ts.map +1 -0
- package/lib/esm/localize/app-setup.js +178 -0
- package/lib/esm/localize/app-setup.js.map +1 -0
- package/lib/esm/localize/delocalize.d.ts.map +1 -1
- package/lib/esm/localize/delocalize.js +168 -104
- package/lib/esm/localize/delocalize.js.map +1 -1
- package/lib/esm/localize/drift-hook.d.ts +18 -0
- package/lib/esm/localize/drift-hook.d.ts.map +1 -0
- package/lib/esm/localize/drift-hook.js +37 -0
- package/lib/esm/localize/drift-hook.js.map +1 -0
- package/lib/esm/localize/drift.d.ts +36 -0
- package/lib/esm/localize/drift.d.ts.map +1 -0
- package/lib/esm/localize/drift.js +34 -0
- package/lib/esm/localize/drift.js.map +1 -0
- package/lib/esm/localize/errors.d.ts +14 -8
- package/lib/esm/localize/errors.d.ts.map +1 -1
- package/lib/esm/localize/errors.js +14 -8
- package/lib/esm/localize/errors.js.map +1 -1
- package/lib/esm/localize/fsops.d.ts +11 -27
- package/lib/esm/localize/fsops.d.ts.map +1 -1
- package/lib/esm/localize/fsops.js +40 -115
- package/lib/esm/localize/fsops.js.map +1 -1
- package/lib/esm/localize/git-hooks.d.ts +33 -0
- package/lib/esm/localize/git-hooks.d.ts.map +1 -0
- package/lib/esm/localize/git-hooks.js +146 -0
- package/lib/esm/localize/git-hooks.js.map +1 -0
- package/lib/esm/localize/hook-module.d.ts +6 -0
- package/lib/esm/localize/hook-module.d.ts.map +1 -0
- package/lib/esm/localize/hook-module.js +37 -0
- package/lib/esm/localize/hook-module.js.map +1 -0
- package/lib/esm/localize/index.d.ts +14 -15
- package/lib/esm/localize/index.d.ts.map +1 -1
- package/lib/esm/localize/index.js +8 -9
- package/lib/esm/localize/index.js.map +1 -1
- package/lib/esm/localize/list.d.ts +6 -10
- package/lib/esm/localize/list.d.ts.map +1 -1
- package/lib/esm/localize/list.js +48 -189
- package/lib/esm/localize/list.js.map +1 -1
- package/lib/esm/localize/localize.d.ts +41 -62
- package/lib/esm/localize/localize.d.ts.map +1 -1
- package/lib/esm/localize/localize.js +398 -285
- package/lib/esm/localize/localize.js.map +1 -1
- package/lib/esm/localize/members.d.ts +13 -0
- package/lib/esm/localize/members.d.ts.map +1 -0
- package/lib/esm/localize/members.js +46 -0
- package/lib/esm/localize/members.js.map +1 -0
- package/lib/esm/localize/resolve.d.ts +1 -2
- package/lib/esm/localize/resolve.d.ts.map +1 -1
- package/lib/esm/localize/resolve.js +3 -5
- package/lib/esm/localize/resolve.js.map +1 -1
- package/lib/esm/localize/run.d.ts +1 -0
- package/lib/esm/localize/run.d.ts.map +1 -1
- package/lib/esm/localize/run.js +11 -6
- package/lib/esm/localize/run.js.map +1 -1
- package/lib/esm/localize/setup-lockfile.d.ts +13 -0
- package/lib/esm/localize/setup-lockfile.d.ts.map +1 -0
- package/lib/esm/localize/setup-lockfile.js +80 -0
- package/lib/esm/localize/setup-lockfile.js.map +1 -0
- package/lib/esm/utils/createPackageLocation.d.ts +4 -13
- package/lib/esm/utils/createPackageLocation.d.ts.map +1 -1
- package/lib/esm/utils/createPackageLocation.js +12 -21
- package/lib/esm/utils/createPackageLocation.js.map +1 -1
- package/lib/esm/utils/packageSetup.d.ts +5 -2
- package/lib/esm/utils/packageSetup.d.ts.map +1 -1
- package/lib/esm/utils/packageSetup.js +9 -3
- package/lib/esm/utils/packageSetup.js.map +1 -1
- package/lib/esm/utils/platform.d.ts +24 -0
- package/lib/esm/utils/platform.d.ts.map +1 -0
- package/lib/esm/utils/platform.js +62 -0
- package/lib/esm/utils/platform.js.map +1 -0
- package/lib/esm/utils/shapeReferences.d.ts.map +1 -1
- package/lib/esm/utils/shapeReferences.js +3 -2
- package/lib/esm/utils/shapeReferences.js.map +1 -1
- package/lib/esm/utils/spawn.d.ts +65 -0
- package/lib/esm/utils/spawn.d.ts.map +1 -0
- package/lib/esm/utils/spawn.js +181 -0
- package/lib/esm/utils/spawn.js.map +1 -0
- package/lib/esm/utils.d.ts.map +1 -1
- package/lib/esm/utils.js +19 -5
- package/lib/esm/utils.js.map +1 -1
- package/lib/esm/vite-config.d.ts +5 -12
- package/lib/esm/vite-config.d.ts.map +1 -1
- package/lib/esm/vite-config.js +41 -33
- package/lib/esm/vite-config.js.map +1 -1
- package/package.json +4 -3
- package/lib/esm/localize/ensure.d.ts +0 -44
- package/lib/esm/localize/ensure.d.ts.map +0 -1
- package/lib/esm/localize/ensure.js +0 -151
- package/lib/esm/localize/ensure.js.map +0 -1
- package/lib/esm/localize/manifest.d.ts +0 -63
- package/lib/esm/localize/manifest.d.ts.map +0 -1
- package/lib/esm/localize/manifest.js +0 -133
- package/lib/esm/localize/manifest.js.map +0 -1
- package/lib/esm/localize/one-copy-hook.d.ts +0 -16
- package/lib/esm/localize/one-copy-hook.d.ts.map +0 -1
- package/lib/esm/localize/one-copy-hook.js +0 -20
- package/lib/esm/localize/one-copy-hook.js.map +0 -1
- package/lib/esm/localize/provided.d.ts +0 -28
- package/lib/esm/localize/provided.d.ts.map +0 -1
- package/lib/esm/localize/provided.js +0 -89
- package/lib/esm/localize/provided.js.map +0 -1
- package/lib/esm/localize/prune.d.ts +0 -182
- package/lib/esm/localize/prune.d.ts.map +0 -1
- package/lib/esm/localize/prune.js +0 -703
- package/lib/esm/localize/prune.js.map +0 -1
- package/lib/esm/localize/relink.d.ts +0 -13
- package/lib/esm/localize/relink.d.ts.map +0 -1
- package/lib/esm/localize/relink.js +0 -181
- package/lib/esm/localize/relink.js.map +0 -1
- package/lib/esm/localized-dedupe.d.ts +0 -26
- package/lib/esm/localized-dedupe.d.ts.map +0 -1
- package/lib/esm/localized-dedupe.js +0 -139
- package/lib/esm/localized-dedupe.js.map +0 -1
- package/lib/esm/utils/relinkHooks.d.ts +0 -19
- package/lib/esm/utils/relinkHooks.d.ts.map +0 -1
- package/lib/esm/utils/relinkHooks.js +0 -74
- package/lib/esm/utils/relinkHooks.js.map +0 -1
|
@@ -1,345 +1,461 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `localize` -- turn an npm-installed dependency into an editable git checkout
|
|
2
|
+
* `localize` -- turn an npm-installed dependency into an editable git checkout
|
|
3
|
+
* that npm itself links.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
+
* A checkout in `packages-local/` is a workspace member: the app's
|
|
6
|
+
* `workspaces` holds `"packages-local/*"`, so `npm install` links
|
|
7
|
+
* `node_modules/<name>` to it and dedupes everything below it like any other
|
|
8
|
+
* member. The committed `package-lock.json` is put back after every install by
|
|
9
|
+
* `hooks/lock-at-rest.cjs`, so the working tree stays clean.
|
|
5
10
|
*
|
|
6
|
-
*
|
|
7
|
-
* 2. `git clone` into `<dir>/<name>`
|
|
8
|
-
* 3. **`npm install` INSIDE the checkout**, then prune it (`ensure`)
|
|
9
|
-
* 4. `fs.symlink` node_modules/<name> -> the checkout
|
|
10
|
-
* 5. record it in a gitignored manifest
|
|
11
|
+
* One run, for every name at once:
|
|
11
12
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
13
|
+
* 1. refuse an `overrides` key naming a package being localized or already
|
|
14
|
+
* in `packages-local/` (npm fails with `EOVERRIDE` on a member's name);
|
|
15
|
+
* nothing is changed then;
|
|
16
|
+
* 2. the one-time app setup (app-setup.ts), reported line by line so the
|
|
17
|
+
* developer commits it; when it changed `package.json`, the lockfile
|
|
18
|
+
* that goes with it is made now, before anything is cloned, and written
|
|
19
|
+
* back after the install (`captureSetupLockfile`);
|
|
20
|
+
* 3. per name: a parked checkout (`linked delocalize`) moves back; an
|
|
21
|
+
* existing directory is refused with an `--adopt` hint; anything else is
|
|
22
|
+
* resolved through the registry's `repository` (or `--repo`) and cloned;
|
|
23
|
+
* 4. the git hooks (git-hooks.ts), or the lines to add under
|
|
24
|
+
* `core.hooksPath`;
|
|
25
|
+
* 5. ONE `npm install` at the app root -- a checkout's dependencies arrive
|
|
26
|
+
* with it, so it comes before any build;
|
|
27
|
+
* 6. build each new checkout: `--build`, else `linked build`, else for the
|
|
28
|
+
* cli its own `npm run build` (the cli is what `linked build` runs, and a
|
|
29
|
+
* fresh cli checkout has no `lib/` yet). The cli builds first and then
|
|
30
|
+
* gets its bin linked (`npm rebuild`), since the others' `linked build`
|
|
31
|
+
* runs it. A failing build warns: a package whose build is broken is
|
|
32
|
+
* usually what you localized it to fix.
|
|
15
33
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* app's boot on a missing peer dependency, because module resolution happens
|
|
19
|
-
* from the symlink's REAL path, whose ancestors have no `node_modules`. The
|
|
20
|
-
* checkout needs its own installed tree. `tests/unit/localize/real-localize.test.ts` runs a
|
|
21
|
-
* real clone and a real install and asserts the dependency landed inside the
|
|
22
|
-
* checkout, so a reimplementation that moves the install back out fails there.
|
|
34
|
+
* `adopt` (adopt.ts) replaces step 3 with "the checkout is already there", and
|
|
35
|
+
* `--parked` with "every parked checkout". Steps 1, 2 and 4-6 are `runLocalize`.
|
|
23
36
|
*
|
|
24
|
-
* A
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* `package-lock.json`, `.gitignore`, `workspaces`, dependencies or any
|
|
29
|
-
* bundler config. It asserts the work did not touch `package.json` or
|
|
30
|
-
* `package-lock.json` before exiting 0.
|
|
31
|
-
*
|
|
32
|
-
* The one deliberate edit, made after that assertion: when a run records a
|
|
33
|
-
* package, the app's `postinstall` and `dependencies` scripts get
|
|
34
|
-
* `linked localize --relink` if they lack it (`../utils/relinkHooks.ts`
|
|
35
|
-
* explains why npm needs both). Without them the next npm command silently
|
|
36
|
-
* undoes the link this run just made.
|
|
37
|
+
* A package inside a monorepo (`--subdir`, or a registry `repository.directory`)
|
|
38
|
+
* is cloned whole into `packages-local/.repos/<checkout>` -- a dot directory,
|
|
39
|
+
* which the `packages-local/*` glob skips -- and `packages-local/<checkout>` is
|
|
40
|
+
* a link to the package inside it. npm follows the link like a directory.
|
|
37
41
|
*/
|
|
38
42
|
import fs from 'node:fs';
|
|
39
43
|
import path from 'node:path';
|
|
40
|
-
import {
|
|
41
|
-
import {
|
|
44
|
+
import { conflictingOverrides, ensureAppSetup } from './app-setup.js';
|
|
45
|
+
import { describeDrift, driftProblems } from './drift.js';
|
|
46
|
+
import { EXIT_INSTALL_FAILED, EXIT_REFUSED, EXIT_USAGE, EXIT_WARNED, LocalizeError, } from './errors.js';
|
|
47
|
+
import { existsOrLink, moveEntry, readJson } from './fsops.js';
|
|
48
|
+
import { installGitHooks } from './git-hooks.js';
|
|
49
|
+
import { LOCAL_DIR, localMembers, parkedMembers } from './members.js';
|
|
50
|
+
import { setupLockfileByEdit } from './setup-lockfile.js';
|
|
42
51
|
import { checkoutNameFor, resolvePackage } from './resolve.js';
|
|
43
|
-
import {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
52
|
+
import { linkDirectory } from '../utils/platform.js';
|
|
53
|
+
/** What a new checkout is built with when `--build` does not say. */
|
|
54
|
+
export const DEFAULT_BUILD_COMMAND = 'linked build';
|
|
55
|
+
/** The cli builds itself: `linked build` IS the cli, and a fresh checkout of it has no `lib/`. */
|
|
56
|
+
const CLI_PACKAGE = '@_linked/cli';
|
|
57
|
+
const CLI_BUILD_COMMAND = 'npm run build';
|
|
58
|
+
/** The one install a run makes, at the app root. */
|
|
59
|
+
export const INSTALL_ARGS = ['install', '--no-audit', '--no-fund'];
|
|
60
|
+
/** Where a cloned monorepo lives, inside `packages-local/` but outside its glob. */
|
|
61
|
+
const REPOS_DIR = `${LOCAL_DIR}/.repos`;
|
|
62
|
+
/**
|
|
63
|
+
* Where the set-up's lockfile run puts the checkouts while npm makes it:
|
|
64
|
+
* inside the gitignored `packages-local/`, outside its glob (npm skips dot
|
|
65
|
+
* directories). A run interrupted there leaves them in it; the next
|
|
66
|
+
* `localize`, `delocalize` or `--list` moves them back (`restoreSetAside`).
|
|
67
|
+
*/
|
|
68
|
+
const SETUP_ASIDE_DIR = `${LOCAL_DIR}/.setup-aside`;
|
|
69
|
+
/** The app's lockfile, kept clean of checkouts. */
|
|
70
|
+
export const LOCKFILE = 'package-lock.json';
|
|
71
|
+
/**
|
|
72
|
+
* @param names full npm package names
|
|
73
|
+
* @returns the process exit code: the highest of the per-name codes and the
|
|
74
|
+
* install's, since a failure on name 2 does not undo name 1.
|
|
75
|
+
*/
|
|
47
76
|
export function localize(names, opts, deps) {
|
|
48
|
-
return
|
|
77
|
+
return runLocalize(names, opts, deps, prepareClone);
|
|
49
78
|
}
|
|
50
79
|
/**
|
|
51
|
-
* The
|
|
52
|
-
* name
|
|
53
|
-
* consumer's untouched manifests.
|
|
54
|
-
*
|
|
55
|
-
* @param {(name: string, dir: string, recorded: object|undefined, opts: object, deps: object)
|
|
56
|
-
* => {code: number, entry?: {name: string, value: object}}} one
|
|
80
|
+
* The run `localize`, `adopt` and `--parked` share: everything except how a
|
|
81
|
+
* name becomes a checkout. See the module header for the steps.
|
|
57
82
|
*/
|
|
58
|
-
export function
|
|
83
|
+
export function runLocalize(names, opts, deps, prepare) {
|
|
59
84
|
var _a, _b;
|
|
60
|
-
|
|
61
|
-
// changed, not about what the tree looked like at HEAD.
|
|
62
|
-
const before = snapshotManifests(deps.appRoot);
|
|
63
|
-
let manifest;
|
|
85
|
+
let setupLock;
|
|
64
86
|
try {
|
|
65
|
-
|
|
87
|
+
restoreSetAside(deps);
|
|
88
|
+
checkDir(opts.dir);
|
|
89
|
+
// The checkouts already there count too: the one install below covers them all.
|
|
90
|
+
const localized = new Set([
|
|
91
|
+
...names,
|
|
92
|
+
...localMembers(deps.appRoot).map((m) => m.name),
|
|
93
|
+
]);
|
|
94
|
+
const conflicts = conflictingOverrides(deps.appRoot, [...localized]);
|
|
95
|
+
if (conflicts.length) {
|
|
96
|
+
throw new LocalizeError(`package.json "overrides" names a localized package: ${conflicts
|
|
97
|
+
.map((k) => `"${k}"`)
|
|
98
|
+
.join(', ')}.\n` +
|
|
99
|
+
`npm refuses an override on a workspace member's name (EOVERRIDE), and a nested one ` +
|
|
100
|
+
`would replace the checkout. Remove ${conflicts.length === 1 ? 'that key' : 'those keys'} ` +
|
|
101
|
+
`from "overrides", then run this again. Nothing was changed.`, EXIT_REFUSED);
|
|
102
|
+
}
|
|
103
|
+
const workspacesBefore = (_a = readJson(path.join(deps.appRoot, 'package.json'))) === null || _a === void 0 ? void 0 : _a.workspaces;
|
|
104
|
+
const changes = ensureAppSetup(deps.appRoot);
|
|
105
|
+
reportSetup(changes, deps);
|
|
106
|
+
if (changes.some((c) => c.file === 'package.json'))
|
|
107
|
+
setupLock =
|
|
108
|
+
(_b = captureSetupLockfile(changes, workspacesBefore, deps)) !== null && _b !== void 0 ? _b : undefined;
|
|
66
109
|
}
|
|
67
110
|
catch (e) {
|
|
68
111
|
return reportError(e, deps);
|
|
69
112
|
}
|
|
70
|
-
const dir = (_b = (_a = opts.dir) !== null && _a !== void 0 ? _a : manifest.dir) !== null && _b !== void 0 ? _b : DEFAULT_DIR;
|
|
71
|
-
const entries = Object.assign({}, manifest.entries);
|
|
72
113
|
let code = 0;
|
|
73
|
-
|
|
114
|
+
const prepared = [];
|
|
74
115
|
for (const name of names) {
|
|
75
116
|
try {
|
|
76
|
-
const
|
|
77
|
-
if (
|
|
78
|
-
|
|
79
|
-
recorded = true;
|
|
80
|
-
}
|
|
81
|
-
code = Math.max(code, result.code);
|
|
82
|
-
// Written after EACH name, so an interruption keeps what already worked.
|
|
83
|
-
writeManifest(deps.appRoot, {
|
|
84
|
-
version: SCHEMA_VERSION,
|
|
85
|
-
dir,
|
|
86
|
-
packages: entries,
|
|
87
|
-
});
|
|
117
|
+
const p = prepare(name, opts, deps);
|
|
118
|
+
if (p)
|
|
119
|
+
prepared.push(p);
|
|
88
120
|
}
|
|
89
121
|
catch (e) {
|
|
90
122
|
code = Math.max(code, reportError(e, deps));
|
|
91
123
|
}
|
|
92
124
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
125
|
+
if (!prepared.length) {
|
|
126
|
+
// Nothing was installed: the work tree holds the set-up's lockfile as captured.
|
|
127
|
+
if (setupLock !== undefined)
|
|
128
|
+
commitHint(deps);
|
|
129
|
+
return code;
|
|
130
|
+
}
|
|
131
|
+
return Math.max(code, installAndBuild(prepared, opts, deps, setupLock));
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Steps 4-6: git hooks, one root install, then the builds. `setupLock` is the
|
|
135
|
+
* lockfile the set-up captured (see `captureSetupLockfile`), written back
|
|
136
|
+
* over what the install left.
|
|
137
|
+
*/
|
|
138
|
+
export function installAndBuild(prepared, opts, deps, setupLock) {
|
|
139
|
+
reportHooks(installGitHooks(deps.appRoot), deps);
|
|
140
|
+
deps.log(`[localize] npm ${INSTALL_ARGS.join(' ')} (app root)`);
|
|
141
|
+
const install = deps.run('npm', INSTALL_ARGS, { cwd: deps.appRoot });
|
|
142
|
+
if (setupLock !== undefined)
|
|
143
|
+
restoreSetupLockfile(setupLock, deps);
|
|
144
|
+
if (install.status !== 0) {
|
|
145
|
+
deps.error(`[localize] npm install failed at the app root. The checkout${prepared.length === 1 ? ' stays' : 's stay'} ` +
|
|
146
|
+
`in ${LOCAL_DIR}/; fix the cause and run \`npm install\`.\n` +
|
|
147
|
+
tail(install.stderr || install.stdout));
|
|
148
|
+
return EXIT_INSTALL_FAILED;
|
|
103
149
|
}
|
|
104
|
-
code =
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
150
|
+
let code = 0;
|
|
151
|
+
// The cli first: the other builds run `linked build`, which is the cli.
|
|
152
|
+
const ordered = [
|
|
153
|
+
...prepared.filter((p) => p.name === CLI_PACKAGE),
|
|
154
|
+
...prepared.filter((p) => p.name !== CLI_PACKAGE),
|
|
155
|
+
];
|
|
156
|
+
for (const p of ordered) {
|
|
157
|
+
const pkgDir = path.join(deps.appRoot, p.dir);
|
|
158
|
+
const built = p.build ? runBuild(p, pkgDir, opts, deps) : 'skipped';
|
|
159
|
+
if (built === 'failed')
|
|
160
|
+
code = EXIT_WARNED;
|
|
161
|
+
if (built === 'ok' && p.name === CLI_PACKAGE)
|
|
162
|
+
linkCliBin(deps);
|
|
163
|
+
deps.log(`[localize] ${p.name} -> ${p.dir} (branch ${currentBranch(pkgDir, deps)})`);
|
|
164
|
+
}
|
|
165
|
+
for (const d of driftProblems(deps.appRoot))
|
|
166
|
+
deps.warn(`[localize] drift: ${describeDrift(d)}`);
|
|
109
167
|
return code;
|
|
110
168
|
}
|
|
111
|
-
/**
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
169
|
+
/**
|
|
170
|
+
* npm links a package's bin only when its target exists, and a fresh cli
|
|
171
|
+
* checkout has no `lib/` at install time: without this, `node_modules/.bin/linked`
|
|
172
|
+
* is missing after the build, and `npx linked` would fetch the unrelated npm
|
|
173
|
+
* package named `linked`. `--ignore-scripts`: only the bin links are wanted.
|
|
174
|
+
*/
|
|
175
|
+
function linkCliBin(deps) {
|
|
176
|
+
const r = deps.run('npm', ['rebuild', CLI_PACKAGE, '--ignore-scripts'], {
|
|
177
|
+
cwd: deps.appRoot,
|
|
178
|
+
});
|
|
179
|
+
if (r.status !== 0) {
|
|
180
|
+
deps.warn(`[localize] npm rebuild ${CLI_PACKAGE} failed, so \`linked\` may not be on the app's ` +
|
|
181
|
+
`bin path. Run it yourself.\n` +
|
|
182
|
+
tail(r.stderr || r.stdout));
|
|
183
|
+
}
|
|
118
184
|
}
|
|
119
|
-
|
|
185
|
+
/** Step 3 for `localize`: take back a parked checkout, else clone. */
|
|
186
|
+
function prepareClone(name, opts, deps) {
|
|
120
187
|
var _a;
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const resolved = resolveOrSuggestAdopt(name, dir, override, deps);
|
|
129
|
-
let code = 0;
|
|
130
|
-
const relClone = path.join(dir, resolved.checkoutName);
|
|
131
|
-
const clone = path.join(deps.appRoot, relClone);
|
|
132
|
-
// When the repository holds more than this package, the CHECKOUT is the
|
|
133
|
-
// clone but the PACKAGE is a directory inside it: that is where the install
|
|
134
|
-
// runs and that is what the symlink points at.
|
|
135
|
-
const relPkg = resolved.subdir
|
|
136
|
-
? path.join(relClone, resolved.subdir)
|
|
137
|
-
: relClone;
|
|
138
|
-
const pkgDir = path.join(deps.appRoot, relPkg);
|
|
139
|
-
guardForeignLink(resolved.npmName, dir, opts, deps);
|
|
140
|
-
if (isGitCheckout(clone)) {
|
|
141
|
-
code = Math.max(code, refresh(relClone, clone, deps));
|
|
142
|
-
if (code === EXIT_WARNED)
|
|
143
|
-
return { code }; // a non-fast-forward is reported, never forced
|
|
188
|
+
const parked = parkedMembers(deps.appRoot).find((m) => m.name === name);
|
|
189
|
+
if (parked)
|
|
190
|
+
return unpark(parked, deps);
|
|
191
|
+
const member = localMembers(deps.appRoot).find((m) => m.name === name);
|
|
192
|
+
if (member) {
|
|
193
|
+
throw new LocalizeError(`${name} is already in ${member.dir}. To install and build it as it is:\n` +
|
|
194
|
+
` linked localize ${name} --adopt`, EXIT_REFUSED);
|
|
144
195
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
throw new LocalizeError(`git clone ${resolved.repo} failed:\n${(r.stderr || r.stdout).trim()}`, EXIT_REFUSED);
|
|
151
|
-
}
|
|
196
|
+
const relDir = `${LOCAL_DIR}/${checkoutNameFor(name)}`;
|
|
197
|
+
const target = path.join(deps.appRoot, relDir);
|
|
198
|
+
if (existsOrLink(target)) {
|
|
199
|
+
throw new LocalizeError(`${relDir} already exists. localize never clones over a directory. If it is ${name}'s ` +
|
|
200
|
+
`checkout, install and build it as it is:\n linked localize ${name} --adopt`, EXIT_REFUSED);
|
|
152
201
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
202
|
+
const resolved = resolvePackage(name, deps, opts.repo ? { repo: opts.repo, subdir: opts.subdir } : undefined);
|
|
203
|
+
// A monorepo is cloned whole outside the glob; the member is a link to the package.
|
|
204
|
+
const relClone = resolved.subdir
|
|
205
|
+
? `${REPOS_DIR}/${checkoutNameFor(name)}`
|
|
206
|
+
: relDir;
|
|
207
|
+
const clone = path.join(deps.appRoot, relClone);
|
|
208
|
+
if (existsOrLink(clone)) {
|
|
209
|
+
throw new LocalizeError(`${relClone} already exists. Remove it, or move it out of the way, and run this again.`, EXIT_REFUSED);
|
|
159
210
|
}
|
|
160
|
-
|
|
161
|
-
|
|
211
|
+
fs.mkdirSync(path.dirname(clone), { recursive: true });
|
|
212
|
+
deps.log(`[localize] cloning ${resolved.repo} -> ${relClone}`);
|
|
213
|
+
const r = deps.run('git', ['clone', '--', resolved.repo, clone]);
|
|
214
|
+
if (r.status !== 0) {
|
|
215
|
+
throw new LocalizeError(`git clone ${resolved.repo} failed:\n${(r.stderr || r.stdout).trim()}`, EXIT_REFUSED);
|
|
216
|
+
}
|
|
217
|
+
const pkgDir = resolved.subdir ? path.join(clone, resolved.subdir) : clone;
|
|
218
|
+
const relPkg = resolved.subdir ? `${relClone}/${resolved.subdir}` : relClone;
|
|
219
|
+
// A clone that is not the package must not stay: in packages-local/ it
|
|
220
|
+
// would be a workspace member under the wrong name.
|
|
162
221
|
const declared = (_a = readJson(path.join(pkgDir, 'package.json'))) === null || _a === void 0 ? void 0 : _a.name;
|
|
163
|
-
if (declared
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
222
|
+
if (declared !== name) {
|
|
223
|
+
fs.rmSync(clone, { recursive: true, force: true });
|
|
224
|
+
const where = resolved.subdir
|
|
225
|
+
? `The registry's repository.directory for "${name}" points at the wrong place.`
|
|
226
|
+
: `${resolved.repo} looks like a monorepo. Say where the package lives:\n` +
|
|
227
|
+
` linked localize ${name} --repo ${resolved.repo} --subdir <path-inside-the-repo>`;
|
|
228
|
+
throw new LocalizeError((declared
|
|
229
|
+
? `${relPkg} declares itself as "${declared}", not "${name}".\n`
|
|
230
|
+
: `${relPkg} has no package.json naming it, so it is not the package "${name}".\n`) +
|
|
231
|
+
`${where}\nThe clone was removed.`, EXIT_REFUSED);
|
|
232
|
+
}
|
|
233
|
+
if (resolved.subdir) {
|
|
234
|
+
linkDirectory(pkgDir, target);
|
|
235
|
+
deps.log(`[localize] ${relDir} -> ${relPkg}`);
|
|
169
236
|
}
|
|
170
|
-
return
|
|
171
|
-
npmName: resolved.npmName,
|
|
172
|
-
clone,
|
|
173
|
-
relPkg,
|
|
174
|
-
pkgDir,
|
|
175
|
-
repo: resolved.repo,
|
|
176
|
-
subdir: resolved.subdir,
|
|
177
|
-
}, opts, deps, code);
|
|
237
|
+
return { name, dir: relDir, build: true };
|
|
178
238
|
}
|
|
239
|
+
/** Move a parked checkout back into `packages-local/`. No clone, no build: it is as it was. */
|
|
240
|
+
export function unpark(member, deps) {
|
|
241
|
+
const entry = path.basename(member.dir);
|
|
242
|
+
const relDir = `${LOCAL_DIR}/${entry}`;
|
|
243
|
+
const to = path.join(deps.appRoot, relDir);
|
|
244
|
+
if (existsOrLink(to)) {
|
|
245
|
+
throw new LocalizeError(`${member.name} is parked in ${member.dir}, but ${relDir} already exists. Move one of ` +
|
|
246
|
+
`them out of the way and run this again.`, EXIT_REFUSED);
|
|
247
|
+
}
|
|
248
|
+
// npm refuses two workspace members of one name, so the install would fail.
|
|
249
|
+
const same = localMembers(deps.appRoot).find((m) => m.name === member.name);
|
|
250
|
+
if (same) {
|
|
251
|
+
throw new LocalizeError(`${member.name} is parked in ${member.dir}, but ${same.dir} is ${member.name} already. ` +
|
|
252
|
+
`Keep one: delete or move the other out of the way, and run this again.`, EXIT_REFUSED);
|
|
253
|
+
}
|
|
254
|
+
moveEntry(path.join(deps.appRoot, member.dir), to);
|
|
255
|
+
deps.log(`[localize] ${member.dir} -> ${relDir}`);
|
|
256
|
+
return { name: member.name, dir: relDir, build: false };
|
|
257
|
+
}
|
|
258
|
+
/** `--dir` is kept for scripts that pass the default; anything else is not a workspace member. */
|
|
259
|
+
function checkDir(dir) {
|
|
260
|
+
if (dir === undefined || dir.replace(/\/+$/, '') === LOCAL_DIR)
|
|
261
|
+
return;
|
|
262
|
+
throw new LocalizeError(`--dir ${dir}: checkouts live in ${LOCAL_DIR}/, the directory the app's ` +
|
|
263
|
+
`"workspaces" glob and the lock-at-rest hook name. Move them there.`, EXIT_USAGE);
|
|
264
|
+
}
|
|
265
|
+
function reportSetup(changes, deps) {
|
|
266
|
+
if (!changes.length)
|
|
267
|
+
return;
|
|
268
|
+
deps.log(`[localize] set up this app for checkouts (commit these):`);
|
|
269
|
+
for (const c of changes)
|
|
270
|
+
deps.log(` ${c.file}: ${c.what}`);
|
|
271
|
+
}
|
|
272
|
+
/** What `captureSetupLockfile` runs: the lockfile for the new `package.json`, nothing installed. */
|
|
273
|
+
const SETUP_LOCK_ARGS = [
|
|
274
|
+
'install',
|
|
275
|
+
'--package-lock-only',
|
|
276
|
+
'--ignore-scripts',
|
|
277
|
+
'--no-audit',
|
|
278
|
+
'--no-fund',
|
|
279
|
+
];
|
|
179
280
|
/**
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
281
|
+
* The set-up changed `package.json`, so the lockfile must change with it
|
|
282
|
+
* (the root entry's `workspaces`) -- and it must be a lockfile with no
|
|
283
|
+
* checkout in it, which the install below cannot give: it lists them, and
|
|
284
|
+
* lock-at-rest leaves it so while `package.json` differs from HEAD. So,
|
|
285
|
+
* before anything is cloned, make that lockfile, keep it, and write it back
|
|
286
|
+
* after the install. When the root entry already has `workspaces` and
|
|
287
|
+
* `hasInstallScript`, it is an edit of that entry (setup-lockfile.ts: exact,
|
|
288
|
+
* offline); otherwise npm makes the lockfile alone, with checkouts already in
|
|
289
|
+
* `packages-local/` (an adopted one) moved into `SETUP_ASIDE_DIR` for that
|
|
290
|
+
* one call.
|
|
183
291
|
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
292
|
+
* @returns the lockfile to write back, or null when it could not be made (the
|
|
293
|
+
* manual steps are printed instead).
|
|
186
294
|
*/
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
295
|
+
function captureSetupLockfile(changes, workspacesBefore, deps) {
|
|
296
|
+
const lockfile = path.join(deps.appRoot, LOCKFILE);
|
|
297
|
+
const edited = editedSetupLockfile(lockfile, workspacesBefore, deps);
|
|
298
|
+
if (edited !== null)
|
|
299
|
+
return edited;
|
|
300
|
+
const moveAside = localMembers(deps.appRoot).length > 0;
|
|
301
|
+
deps.log(`[localize] npm ${SETUP_LOCK_ARGS.join(' ')} (the set-up's lockfile)`);
|
|
302
|
+
if (moveAside)
|
|
303
|
+
setAside(deps);
|
|
304
|
+
let r;
|
|
190
305
|
try {
|
|
191
|
-
|
|
306
|
+
r = deps.run('npm', SETUP_LOCK_ARGS, { cwd: deps.appRoot });
|
|
192
307
|
}
|
|
193
|
-
|
|
194
|
-
if (
|
|
195
|
-
|
|
196
|
-
throw e;
|
|
308
|
+
finally {
|
|
309
|
+
if (moveAside)
|
|
310
|
+
moveBack(deps);
|
|
197
311
|
}
|
|
198
|
-
|
|
199
|
-
// configuration rather than behaviour. A failure is a WARNING: a package
|
|
200
|
-
// whose build is broken is exactly what you are about to fix, and refusing
|
|
201
|
-
// to link it would be perverse.
|
|
202
|
-
if (runBuild(relPkg, pkgDir, opts, deps) === 'failed')
|
|
203
|
-
code = Math.max(code, EXIT_WARNED);
|
|
204
|
-
writeLink(deps.appRoot, npmName, pkgDir);
|
|
205
|
-
const branch = currentBranch(clone, deps);
|
|
206
|
-
deps.log(`[localize] linked ${npmName} -> ${relPkg} (branch ${branch})`);
|
|
207
|
-
const range = declaredRange(npmName, deps);
|
|
208
|
-
return {
|
|
209
|
-
code,
|
|
210
|
-
entry: {
|
|
211
|
-
name: npmName,
|
|
212
|
-
value: Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (repo ? { repo } : {})), { path: relPkg, branch }), (subdir ? { subdir } : {})), (range ? { range } : {})), (shouldPrune(opts) ? {} : { prune: false })),
|
|
213
|
-
},
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
/**
|
|
217
|
-
* A registry lookup that fails while a git checkout already sits where the
|
|
218
|
-
* clone would go is almost always a package that was never published -- one
|
|
219
|
-
* made locally. Say how to link it as it is rather than leave the developer
|
|
220
|
-
* guessing at a `--repo`.
|
|
221
|
-
*/
|
|
222
|
-
function resolveOrSuggestAdopt(name, dir, override, deps) {
|
|
312
|
+
let text = null;
|
|
223
313
|
try {
|
|
224
|
-
|
|
314
|
+
text = fs.readFileSync(lockfile, 'utf8');
|
|
225
315
|
}
|
|
226
|
-
catch (
|
|
227
|
-
|
|
228
|
-
if (e instanceof LocalizeError &&
|
|
229
|
-
e.code === EXIT_NOT_FOUND &&
|
|
230
|
-
isGitCheckout(path.join(deps.appRoot, relClone))) {
|
|
231
|
-
e.message +=
|
|
232
|
-
`\n${relClone} is already a git checkout. To link it as it is, with no registry lookup:\n` +
|
|
233
|
-
` linked localize ${name} --adopt`;
|
|
234
|
-
}
|
|
235
|
-
throw e;
|
|
316
|
+
catch (_a) {
|
|
317
|
+
/* none written */
|
|
236
318
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
319
|
+
if (r.status !== 0 || text === null) {
|
|
320
|
+
manualSetupSteps(changes, r.status !== 0
|
|
321
|
+
? `npm could not write it:\n${tail(r.stderr || r.stdout)}`
|
|
322
|
+
: `npm wrote no ${LOCKFILE}`, deps);
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
return text;
|
|
241
326
|
}
|
|
242
327
|
/**
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
* rather than forced, because the developer's commits are the entire point.
|
|
328
|
+
* Every entry of `packages-local/` but the dot directories, moved into
|
|
329
|
+
* `SETUP_ASIDE_DIR` as it is (a link keeps its text and works again once back).
|
|
246
330
|
*/
|
|
247
|
-
function
|
|
248
|
-
const
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
if (pull.status !== 0) {
|
|
256
|
-
deps.warn(`[localize] ${relPath}: \`git pull --ff-only\` failed (not a fast-forward) — skipped, not ` +
|
|
257
|
-
`forced. Reconcile it by hand:\n${(pull.stderr || pull.stdout).trim()}`);
|
|
258
|
-
return EXIT_WARNED;
|
|
331
|
+
function setAside(deps) {
|
|
332
|
+
const local = path.join(deps.appRoot, LOCAL_DIR);
|
|
333
|
+
const aside = path.join(deps.appRoot, SETUP_ASIDE_DIR);
|
|
334
|
+
fs.mkdirSync(aside, { recursive: true });
|
|
335
|
+
for (const entry of fs.readdirSync(local)) {
|
|
336
|
+
if (entry.startsWith('.'))
|
|
337
|
+
continue;
|
|
338
|
+
fs.renameSync(path.join(local, entry), path.join(aside, entry));
|
|
259
339
|
}
|
|
260
|
-
|
|
340
|
+
}
|
|
341
|
+
/** Everything in `SETUP_ASIDE_DIR` back into `packages-local/`; the directory goes. */
|
|
342
|
+
function moveBack(deps) {
|
|
343
|
+
const local = path.join(deps.appRoot, LOCAL_DIR);
|
|
344
|
+
const aside = path.join(deps.appRoot, SETUP_ASIDE_DIR);
|
|
345
|
+
const entries = fs.readdirSync(aside);
|
|
346
|
+
for (const entry of entries)
|
|
347
|
+
fs.renameSync(path.join(aside, entry), path.join(local, entry));
|
|
348
|
+
fs.rmdirSync(aside);
|
|
349
|
+
return entries.length;
|
|
261
350
|
}
|
|
262
351
|
/**
|
|
263
|
-
*
|
|
264
|
-
*
|
|
265
|
-
*
|
|
266
|
-
*
|
|
267
|
-
* `{build}` in the programmatic options. Absent -- the common case, and the
|
|
268
|
-
* case for anything whose dev loop reads source directly -- nothing runs and
|
|
269
|
-
* nothing is said.
|
|
352
|
+
* A set-up run that was interrupted while the checkouts were in
|
|
353
|
+
* `SETUP_ASIDE_DIR` left them there, where npm does not see them: move them
|
|
354
|
+
* back, saying so. Refuses, moving nothing, when `packages-local/` already
|
|
355
|
+
* has an entry of the same name.
|
|
270
356
|
*/
|
|
271
|
-
function
|
|
272
|
-
const
|
|
273
|
-
if (!
|
|
274
|
-
return 'skipped';
|
|
275
|
-
deps.log(`[localize] ${relPath}: ${cmd}`);
|
|
276
|
-
const r = deps.run(cmd, [], { cwd: pkgDir, shell: true });
|
|
277
|
-
if (r.status !== 0) {
|
|
278
|
-
deps.warn(`[localize] ${relPath}: \`${cmd}\` failed. The package is linked and recorded anyway — ` +
|
|
279
|
-
`a package whose build is broken is exactly what you are about to fix.\n` +
|
|
280
|
-
`${(r.stderr || r.stdout).trim()}`);
|
|
281
|
-
return 'failed';
|
|
282
|
-
}
|
|
283
|
-
return 'ok';
|
|
284
|
-
}
|
|
285
|
-
/** Refuse when `node_modules/<name>` is already a symlink pointing outside `<dir>`. */
|
|
286
|
-
export function guardForeignLink(npmName, dir, opts, deps) {
|
|
287
|
-
const link = path.join(deps.appRoot, 'node_modules', npmName);
|
|
288
|
-
if (!isSymlink(link))
|
|
357
|
+
export function restoreSetAside(deps) {
|
|
358
|
+
const aside = path.join(deps.appRoot, SETUP_ASIDE_DIR);
|
|
359
|
+
if (!existsOrLink(aside))
|
|
289
360
|
return;
|
|
290
|
-
let
|
|
361
|
+
let entries;
|
|
291
362
|
try {
|
|
292
|
-
|
|
363
|
+
entries = fs.readdirSync(aside);
|
|
293
364
|
}
|
|
294
365
|
catch (_a) {
|
|
295
|
-
|
|
366
|
+
throw new LocalizeError(`${SETUP_ASIDE_DIR} is not a directory. Remove it and run this again.`, EXIT_REFUSED);
|
|
296
367
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
368
|
+
const clashes = entries.filter((e) => existsOrLink(path.join(deps.appRoot, LOCAL_DIR, e)));
|
|
369
|
+
if (clashes.length) {
|
|
370
|
+
throw new LocalizeError(`${SETUP_ASIDE_DIR}/ holds checkouts an interrupted set-up left there, but ` +
|
|
371
|
+
`${clashes.map((e) => `${LOCAL_DIR}/${e}`).join(', ')} now exist${clashes.length === 1 ? 's' : ''} ` +
|
|
372
|
+
`too. Keep one of each, move the other out of ${LOCAL_DIR}/, and run this again. ` +
|
|
373
|
+
`Nothing was moved.`, EXIT_REFUSED);
|
|
302
374
|
}
|
|
303
|
-
|
|
304
|
-
|
|
375
|
+
const n = moveBack(deps);
|
|
376
|
+
if (n)
|
|
377
|
+
deps.log(`[localize] moved ${n} checkout${n === 1 ? '' : 's'} back from ${SETUP_ASIDE_DIR}/ into ` +
|
|
378
|
+
`${LOCAL_DIR}/, where an interrupted set-up left ${n === 1 ? 'it' : 'them'}.`);
|
|
305
379
|
}
|
|
306
|
-
/**
|
|
307
|
-
function
|
|
308
|
-
let
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
380
|
+
/** The set-up's lockfile as an edit of the root entry, written now; null when npm must make it. */
|
|
381
|
+
function editedSetupLockfile(lockfile, workspacesBefore, deps) {
|
|
382
|
+
let current;
|
|
383
|
+
try {
|
|
384
|
+
current = fs.readFileSync(lockfile, 'utf8');
|
|
385
|
+
}
|
|
386
|
+
catch (_a) {
|
|
387
|
+
return null;
|
|
388
|
+
}
|
|
389
|
+
const text = setupLockfileByEdit(current, readJson(path.join(deps.appRoot, 'package.json')), workspacesBefore);
|
|
390
|
+
if (text === null)
|
|
391
|
+
return null;
|
|
392
|
+
fs.writeFileSync(lockfile, text);
|
|
393
|
+
deps.log(`[localize] ${LOCKFILE}: the root entry's "workspaces" now matches package.json (the set-up's lockfile)`);
|
|
394
|
+
return text;
|
|
395
|
+
}
|
|
396
|
+
/** Put the captured set-up lockfile back after the install rewrote it. */
|
|
397
|
+
function restoreSetupLockfile(text, deps) {
|
|
398
|
+
fs.writeFileSync(path.join(deps.appRoot, LOCKFILE), text);
|
|
399
|
+
deps.log(`[localize] ${LOCKFILE}: put back the set-up's lockfile, which lists no checkout (any ` +
|
|
400
|
+
`lock-at-rest warning above was about the one the install wrote).`);
|
|
401
|
+
commitHint(deps);
|
|
402
|
+
}
|
|
403
|
+
function commitHint(deps) {
|
|
404
|
+
deps.log(`[localize] commit the set-up now -- package.json, ${LOCKFILE}, .gitignore:\n` +
|
|
405
|
+
` git add package.json ${LOCKFILE} .gitignore && git commit -m "chore: set up linked localize"`);
|
|
406
|
+
}
|
|
407
|
+
/** The set-up's lockfile could not be made in one step: how to make it by hand. */
|
|
408
|
+
function manualSetupSteps(changes, why, deps) {
|
|
409
|
+
const files = [...new Set([...changes.map((c) => c.file), LOCKFILE])];
|
|
410
|
+
deps.warn(`[localize] could not make the set-up's ${LOCKFILE} in one step: ${why}\n` +
|
|
411
|
+
`Commit the set-up with a lockfile from an install with no checkout present:\n` +
|
|
412
|
+
` npx linked delocalize\n` +
|
|
413
|
+
` npm install\n` +
|
|
414
|
+
` git add ${files.join(' ')} && git commit -m "chore: set up linked localize"\n` +
|
|
415
|
+
` npx linked localize --parked`);
|
|
416
|
+
}
|
|
417
|
+
/** Tell the developer what `installGitHooks` did not do. */
|
|
418
|
+
function reportHooks(hooks, deps) {
|
|
419
|
+
for (const file of hooks.skipped) {
|
|
420
|
+
deps.warn(`[localize] ${path.relative(deps.appRoot, file)} is not ours, so it was left alone. Without ` +
|
|
421
|
+
`the localize hook there, run \`npm install\` yourself after a pull or branch switch that ` +
|
|
422
|
+
`changes dependencies.`);
|
|
423
|
+
}
|
|
424
|
+
if (hooks.hooksPath !== null) {
|
|
425
|
+
deps.warn(`[localize] core.hooksPath is set (${hooks.hooksPath}), so no git hooks were written. Add ` +
|
|
426
|
+
`each line below to the hook named before it, an \`sh\` script (not, say, a Python hook ` +
|
|
427
|
+
`the pre-commit framework writes); they do nothing where the cli is not installed:`);
|
|
428
|
+
for (const m of hooks.manual)
|
|
429
|
+
deps.warn(` ${m.file}:\n ${m.line}`);
|
|
430
|
+
}
|
|
431
|
+
if (hooks.sharedWithWorktrees) {
|
|
432
|
+
deps.warn(`[localize] this is a linked worktree: git hooks are shared by every worktree of the ` +
|
|
433
|
+
`repository. They act only in a work tree whose ${LOCAL_DIR}/ has checkouts.`);
|
|
323
434
|
}
|
|
324
|
-
return code;
|
|
325
435
|
}
|
|
326
436
|
/**
|
|
327
|
-
*
|
|
328
|
-
*
|
|
329
|
-
*
|
|
330
|
-
* any tree with an uncommitted `package.json` it reports a modification the
|
|
331
|
-
* command did not make -- an earlier tool shipped exactly that and reported
|
|
332
|
-
* correct runs as failures.
|
|
437
|
+
* Run the build for one new checkout. `opts.build` wins, including `''` for
|
|
438
|
+
* no build; otherwise the cli builds itself and everything else gets
|
|
439
|
+
* `linked build`.
|
|
333
440
|
*/
|
|
334
|
-
|
|
335
|
-
const
|
|
336
|
-
if (!
|
|
337
|
-
return
|
|
338
|
-
deps.
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
`localize is
|
|
342
|
-
|
|
441
|
+
function runBuild(p, pkgDir, opts, deps) {
|
|
442
|
+
const cmd = buildCommandFor(p.name, opts);
|
|
443
|
+
if (!cmd)
|
|
444
|
+
return 'skipped';
|
|
445
|
+
deps.log(`[localize] ${p.dir}: ${cmd}`);
|
|
446
|
+
const r = deps.run(cmd, [], { cwd: pkgDir, shell: true });
|
|
447
|
+
if (r.status !== 0) {
|
|
448
|
+
deps.warn(`[localize] ${p.dir}: \`${cmd}\` failed. It is installed and linked anyway -- a package ` +
|
|
449
|
+
`whose build is broken is usually what you localized it to fix.\n` +
|
|
450
|
+
tail(r.stderr || r.stdout));
|
|
451
|
+
return 'failed';
|
|
452
|
+
}
|
|
453
|
+
return 'ok';
|
|
454
|
+
}
|
|
455
|
+
function buildCommandFor(name, opts) {
|
|
456
|
+
if (opts.build !== undefined)
|
|
457
|
+
return opts.build;
|
|
458
|
+
return name === CLI_PACKAGE ? CLI_BUILD_COMMAND : DEFAULT_BUILD_COMMAND;
|
|
343
459
|
}
|
|
344
460
|
export function reportError(e, deps) {
|
|
345
461
|
if (e instanceof LocalizeError) {
|
|
@@ -354,23 +470,20 @@ export function reportError(e, deps) {
|
|
|
354
470
|
* `rev-parse --abbrev-ref` fails there, and `create-package` adopts a fresh
|
|
355
471
|
* `git init` before its first commit.
|
|
356
472
|
*/
|
|
357
|
-
export function currentBranch(
|
|
473
|
+
export function currentBranch(checkout, deps) {
|
|
358
474
|
const sym = deps.run('git', ['symbolic-ref', '--short', '-q', 'HEAD'], {
|
|
359
|
-
cwd:
|
|
475
|
+
cwd: checkout,
|
|
360
476
|
});
|
|
361
477
|
if (sym.status === 0 && sym.stdout.trim())
|
|
362
478
|
return sym.stdout.trim();
|
|
363
479
|
const r = deps.run('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {
|
|
364
|
-
cwd:
|
|
480
|
+
cwd: checkout,
|
|
365
481
|
});
|
|
366
482
|
const b = r.stdout.trim();
|
|
367
483
|
return r.status === 0 && b ? b : 'HEAD';
|
|
368
484
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
if (!pkg)
|
|
373
|
-
return undefined;
|
|
374
|
-
return ((_d = (_b = (_a = pkg.dependencies) === null || _a === void 0 ? void 0 : _a[name]) !== null && _b !== void 0 ? _b : (_c = pkg.devDependencies) === null || _c === void 0 ? void 0 : _c[name]) !== null && _d !== void 0 ? _d : (_e = pkg.optionalDependencies) === null || _e === void 0 ? void 0 : _e[name]);
|
|
485
|
+
/** The last lines of a subprocess's output: enough to see the error, not the whole log. */
|
|
486
|
+
export function tail(output, lines = 20) {
|
|
487
|
+
return output.trim().split('\n').slice(-lines).join('\n');
|
|
375
488
|
}
|
|
376
489
|
//# sourceMappingURL=localize.js.map
|