@_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.
- package/CHANGELOG.md +107 -0
- package/README.md +48 -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 +169 -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 +49 -189
- package/lib/esm/localize/list.js.map +1 -1
- package/lib/esm/localize/localize.d.ts +46 -62
- package/lib/esm/localize/localize.d.ts.map +1 -1
- package/lib/esm/localize/localize.js +411 -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,474 @@
|
|
|
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, PARKED_DIR } 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
|
+
/** What an app recorded checkouts in before they were workspace members. */
|
|
72
|
+
const LEGACY_MANIFEST = 'local-packages.json';
|
|
73
|
+
/**
|
|
74
|
+
* @param names full npm package names
|
|
75
|
+
* @returns the process exit code: the highest of the per-name codes and the
|
|
76
|
+
* install's, since a failure on name 2 does not undo name 1.
|
|
77
|
+
*/
|
|
47
78
|
export function localize(names, opts, deps) {
|
|
48
|
-
return
|
|
79
|
+
return runLocalize(names, opts, deps, prepareClone);
|
|
49
80
|
}
|
|
50
81
|
/**
|
|
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
|
|
82
|
+
* The run `localize`, `adopt` and `--parked` share: everything except how a
|
|
83
|
+
* name becomes a checkout. See the module header for the steps.
|
|
57
84
|
*/
|
|
58
|
-
export function
|
|
85
|
+
export function runLocalize(names, opts, deps, prepare) {
|
|
59
86
|
var _a, _b;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const before = snapshotManifests(deps.appRoot);
|
|
63
|
-
let manifest;
|
|
87
|
+
legacyManifestHint(deps);
|
|
88
|
+
let setupLock;
|
|
64
89
|
try {
|
|
65
|
-
|
|
90
|
+
restoreSetAside(deps);
|
|
91
|
+
checkDir(opts.dir);
|
|
92
|
+
// The checkouts already there count too: the one install below covers them all.
|
|
93
|
+
const localized = new Set([
|
|
94
|
+
...names,
|
|
95
|
+
...localMembers(deps.appRoot).map((m) => m.name),
|
|
96
|
+
]);
|
|
97
|
+
const conflicts = conflictingOverrides(deps.appRoot, [...localized]);
|
|
98
|
+
if (conflicts.length) {
|
|
99
|
+
throw new LocalizeError(`package.json "overrides" names a localized package: ${conflicts
|
|
100
|
+
.map((k) => `"${k}"`)
|
|
101
|
+
.join(', ')}.\n` +
|
|
102
|
+
`npm refuses an override on a workspace member's name (EOVERRIDE), and a nested one ` +
|
|
103
|
+
`would replace the checkout. Remove ${conflicts.length === 1 ? 'that key' : 'those keys'} ` +
|
|
104
|
+
`from "overrides", then run this again. Nothing was changed.`, EXIT_REFUSED);
|
|
105
|
+
}
|
|
106
|
+
const workspacesBefore = (_a = readJson(path.join(deps.appRoot, 'package.json'))) === null || _a === void 0 ? void 0 : _a.workspaces;
|
|
107
|
+
const changes = ensureAppSetup(deps.appRoot);
|
|
108
|
+
reportSetup(changes, deps);
|
|
109
|
+
if (changes.some((c) => c.file === 'package.json'))
|
|
110
|
+
setupLock =
|
|
111
|
+
(_b = captureSetupLockfile(changes, workspacesBefore, deps)) !== null && _b !== void 0 ? _b : undefined;
|
|
66
112
|
}
|
|
67
113
|
catch (e) {
|
|
68
114
|
return reportError(e, deps);
|
|
69
115
|
}
|
|
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
116
|
let code = 0;
|
|
73
|
-
|
|
117
|
+
const prepared = [];
|
|
74
118
|
for (const name of names) {
|
|
75
119
|
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
|
-
});
|
|
120
|
+
const p = prepare(name, opts, deps);
|
|
121
|
+
if (p)
|
|
122
|
+
prepared.push(p);
|
|
88
123
|
}
|
|
89
124
|
catch (e) {
|
|
90
125
|
code = Math.max(code, reportError(e, deps));
|
|
91
126
|
}
|
|
92
127
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
catch (e) {
|
|
100
|
-
deps.warn(`[localize] pruning failed, the checkouts' node_modules are as npm left them: ${e.message}`);
|
|
101
|
-
code = Math.max(code, EXIT_WARNED);
|
|
102
|
-
}
|
|
128
|
+
if (!prepared.length) {
|
|
129
|
+
// Nothing was installed: the work tree holds the set-up's lockfile as captured.
|
|
130
|
+
if (setupLock !== undefined)
|
|
131
|
+
commitHint(deps);
|
|
132
|
+
return code;
|
|
103
133
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
134
|
+
return Math.max(code, installAndBuild(prepared, opts, deps, setupLock));
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Steps 4-6: git hooks, one root install, then the builds. `setupLock` is the
|
|
138
|
+
* lockfile the set-up captured (see `captureSetupLockfile`), written back
|
|
139
|
+
* over what the install left.
|
|
140
|
+
*/
|
|
141
|
+
export function installAndBuild(prepared, opts, deps, setupLock) {
|
|
142
|
+
reportHooks(installGitHooks(deps.appRoot), deps);
|
|
143
|
+
deps.log(`[localize] npm ${INSTALL_ARGS.join(' ')} (app root)`);
|
|
144
|
+
const install = deps.run('npm', INSTALL_ARGS, { cwd: deps.appRoot });
|
|
145
|
+
if (setupLock !== undefined)
|
|
146
|
+
restoreSetupLockfile(setupLock, deps);
|
|
147
|
+
if (install.status !== 0) {
|
|
148
|
+
deps.error(`[localize] npm install failed at the app root. The checkout${prepared.length === 1 ? ' stays' : 's stay'} ` +
|
|
149
|
+
`in ${LOCAL_DIR}/; fix the cause and run \`npm install\`.\n` +
|
|
150
|
+
tail(install.stderr || install.stdout));
|
|
151
|
+
return EXIT_INSTALL_FAILED;
|
|
152
|
+
}
|
|
153
|
+
let code = 0;
|
|
154
|
+
// The cli first: the other builds run `linked build`, which is the cli.
|
|
155
|
+
const ordered = [
|
|
156
|
+
...prepared.filter((p) => p.name === CLI_PACKAGE),
|
|
157
|
+
...prepared.filter((p) => p.name !== CLI_PACKAGE),
|
|
158
|
+
];
|
|
159
|
+
for (const p of ordered) {
|
|
160
|
+
const pkgDir = path.join(deps.appRoot, p.dir);
|
|
161
|
+
const built = p.build ? runBuild(p, pkgDir, opts, deps) : 'skipped';
|
|
162
|
+
if (built === 'failed')
|
|
163
|
+
code = EXIT_WARNED;
|
|
164
|
+
if (built === 'ok' && p.name === CLI_PACKAGE)
|
|
165
|
+
linkCliBin(deps);
|
|
166
|
+
deps.log(`[localize] ${p.name} -> ${p.dir} (branch ${currentBranch(pkgDir, deps)})`);
|
|
167
|
+
}
|
|
168
|
+
for (const d of driftProblems(deps.appRoot))
|
|
169
|
+
deps.warn(`[localize] drift: ${describeDrift(d)}`);
|
|
109
170
|
return code;
|
|
110
171
|
}
|
|
111
|
-
/**
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
172
|
+
/**
|
|
173
|
+
* npm links a package's bin only when its target exists, and a fresh cli
|
|
174
|
+
* checkout has no `lib/` at install time: without this, `node_modules/.bin/linked`
|
|
175
|
+
* is missing after the build, and `npx linked` would fetch the unrelated npm
|
|
176
|
+
* package named `linked`. `--ignore-scripts`: only the bin links are wanted.
|
|
177
|
+
*/
|
|
178
|
+
function linkCliBin(deps) {
|
|
179
|
+
const r = deps.run('npm', ['rebuild', CLI_PACKAGE, '--ignore-scripts'], {
|
|
180
|
+
cwd: deps.appRoot,
|
|
181
|
+
});
|
|
182
|
+
if (r.status !== 0) {
|
|
183
|
+
deps.warn(`[localize] npm rebuild ${CLI_PACKAGE} failed, so \`linked\` may not be on the app's ` +
|
|
184
|
+
`bin path. Run it yourself.\n` +
|
|
185
|
+
tail(r.stderr || r.stdout));
|
|
186
|
+
}
|
|
118
187
|
}
|
|
119
|
-
|
|
188
|
+
/** Step 3 for `localize`: take back a parked checkout, else clone. */
|
|
189
|
+
function prepareClone(name, opts, deps) {
|
|
120
190
|
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
|
|
191
|
+
const parked = parkedMembers(deps.appRoot).find((m) => m.name === name);
|
|
192
|
+
if (parked)
|
|
193
|
+
return unpark(parked, deps);
|
|
194
|
+
const member = localMembers(deps.appRoot).find((m) => m.name === name);
|
|
195
|
+
if (member) {
|
|
196
|
+
throw new LocalizeError(`${name} is already in ${member.dir}. To install and build it as it is:\n` +
|
|
197
|
+
` linked localize ${name} --adopt`, EXIT_REFUSED);
|
|
144
198
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
throw new LocalizeError(`git clone ${resolved.repo} failed:\n${(r.stderr || r.stdout).trim()}`, EXIT_REFUSED);
|
|
151
|
-
}
|
|
199
|
+
const relDir = `${LOCAL_DIR}/${checkoutNameFor(name)}`;
|
|
200
|
+
const target = path.join(deps.appRoot, relDir);
|
|
201
|
+
if (existsOrLink(target)) {
|
|
202
|
+
throw new LocalizeError(`${relDir} already exists. localize never clones over a directory. If it is ${name}'s ` +
|
|
203
|
+
`checkout, install and build it as it is:\n linked localize ${name} --adopt`, EXIT_REFUSED);
|
|
152
204
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
205
|
+
const resolved = resolvePackage(name, deps, opts.repo ? { repo: opts.repo, subdir: opts.subdir } : undefined);
|
|
206
|
+
// A monorepo is cloned whole outside the glob; the member is a link to the package.
|
|
207
|
+
const relClone = resolved.subdir
|
|
208
|
+
? `${REPOS_DIR}/${checkoutNameFor(name)}`
|
|
209
|
+
: relDir;
|
|
210
|
+
const clone = path.join(deps.appRoot, relClone);
|
|
211
|
+
if (existsOrLink(clone)) {
|
|
212
|
+
throw new LocalizeError(`${relClone} already exists. Remove it, or move it out of the way, and run this again.`, EXIT_REFUSED);
|
|
159
213
|
}
|
|
160
|
-
|
|
161
|
-
|
|
214
|
+
fs.mkdirSync(path.dirname(clone), { recursive: true });
|
|
215
|
+
deps.log(`[localize] cloning ${resolved.repo} -> ${relClone}`);
|
|
216
|
+
const r = deps.run('git', ['clone', '--', resolved.repo, clone]);
|
|
217
|
+
if (r.status !== 0) {
|
|
218
|
+
throw new LocalizeError(`git clone ${resolved.repo} failed:\n${(r.stderr || r.stdout).trim()}`, EXIT_REFUSED);
|
|
219
|
+
}
|
|
220
|
+
const pkgDir = resolved.subdir ? path.join(clone, resolved.subdir) : clone;
|
|
221
|
+
const relPkg = resolved.subdir ? `${relClone}/${resolved.subdir}` : relClone;
|
|
222
|
+
// A clone that is not the package must not stay: in packages-local/ it
|
|
223
|
+
// would be a workspace member under the wrong name.
|
|
162
224
|
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
|
-
|
|
225
|
+
if (declared !== name) {
|
|
226
|
+
fs.rmSync(clone, { recursive: true, force: true });
|
|
227
|
+
const where = resolved.subdir
|
|
228
|
+
? `The registry's repository.directory for "${name}" points at the wrong place.`
|
|
229
|
+
: `${resolved.repo} looks like a monorepo. Say where the package lives:\n` +
|
|
230
|
+
` linked localize ${name} --repo ${resolved.repo} --subdir <path-inside-the-repo>`;
|
|
231
|
+
throw new LocalizeError((declared
|
|
232
|
+
? `${relPkg} declares itself as "${declared}", not "${name}".\n`
|
|
233
|
+
: `${relPkg} has no package.json naming it, so it is not the package "${name}".\n`) +
|
|
234
|
+
`${where}\nThe clone was removed.`, EXIT_REFUSED);
|
|
235
|
+
}
|
|
236
|
+
if (resolved.subdir) {
|
|
237
|
+
linkDirectory(pkgDir, target);
|
|
238
|
+
deps.log(`[localize] ${relDir} -> ${relPkg}`);
|
|
239
|
+
}
|
|
240
|
+
return { name, dir: relDir, build: true };
|
|
241
|
+
}
|
|
242
|
+
/** Move a parked checkout back into `packages-local/`. No clone, no build: it is as it was. */
|
|
243
|
+
export function unpark(member, deps) {
|
|
244
|
+
const entry = path.basename(member.dir);
|
|
245
|
+
const relDir = `${LOCAL_DIR}/${entry}`;
|
|
246
|
+
const to = path.join(deps.appRoot, relDir);
|
|
247
|
+
if (existsOrLink(to)) {
|
|
248
|
+
throw new LocalizeError(`${member.name} is parked in ${member.dir}, but ${relDir} already exists. Move one of ` +
|
|
249
|
+
`them out of the way and run this again.`, EXIT_REFUSED);
|
|
250
|
+
}
|
|
251
|
+
// npm refuses two workspace members of one name, so the install would fail.
|
|
252
|
+
const same = localMembers(deps.appRoot).find((m) => m.name === member.name);
|
|
253
|
+
if (same) {
|
|
254
|
+
throw new LocalizeError(`${member.name} is parked in ${member.dir}, but ${same.dir} is ${member.name} already. ` +
|
|
255
|
+
`Keep one: delete or move the other out of the way, and run this again.`, EXIT_REFUSED);
|
|
169
256
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
relPkg,
|
|
174
|
-
pkgDir,
|
|
175
|
-
repo: resolved.repo,
|
|
176
|
-
subdir: resolved.subdir,
|
|
177
|
-
}, opts, deps, code);
|
|
257
|
+
moveEntry(path.join(deps.appRoot, member.dir), to);
|
|
258
|
+
deps.log(`[localize] ${member.dir} -> ${relDir}`);
|
|
259
|
+
return { name: member.name, dir: relDir, build: false };
|
|
178
260
|
}
|
|
261
|
+
/** `--dir` is kept for scripts that pass the default; anything else is not a workspace member. */
|
|
262
|
+
function checkDir(dir) {
|
|
263
|
+
if (dir === undefined || dir.replace(/\/+$/, '') === LOCAL_DIR)
|
|
264
|
+
return;
|
|
265
|
+
throw new LocalizeError(`--dir ${dir}: checkouts live in ${LOCAL_DIR}/, the directory the app's ` +
|
|
266
|
+
`"workspaces" glob and the lock-at-rest hook name. Move them there.`, EXIT_USAGE);
|
|
267
|
+
}
|
|
268
|
+
function reportSetup(changes, deps) {
|
|
269
|
+
if (!changes.length)
|
|
270
|
+
return;
|
|
271
|
+
deps.log(`[localize] set up this app for checkouts (commit these):`);
|
|
272
|
+
for (const c of changes)
|
|
273
|
+
deps.log(` ${c.file}: ${c.what}`);
|
|
274
|
+
}
|
|
275
|
+
/** What `captureSetupLockfile` runs: the lockfile for the new `package.json`, nothing installed. */
|
|
276
|
+
const SETUP_LOCK_ARGS = [
|
|
277
|
+
'install',
|
|
278
|
+
'--package-lock-only',
|
|
279
|
+
'--ignore-scripts',
|
|
280
|
+
'--no-audit',
|
|
281
|
+
'--no-fund',
|
|
282
|
+
];
|
|
179
283
|
/**
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
284
|
+
* The set-up changed `package.json`, so the lockfile must change with it
|
|
285
|
+
* (the root entry's `workspaces`) -- and it must be a lockfile with no
|
|
286
|
+
* checkout in it, which the install below cannot give: it lists them, and
|
|
287
|
+
* lock-at-rest leaves it so while `package.json` differs from HEAD. So,
|
|
288
|
+
* before anything is cloned, make that lockfile, keep it, and write it back
|
|
289
|
+
* after the install. When the root entry already has `workspaces` and
|
|
290
|
+
* `hasInstallScript`, it is an edit of that entry (setup-lockfile.ts: exact,
|
|
291
|
+
* offline); otherwise npm makes the lockfile alone, with checkouts already in
|
|
292
|
+
* `packages-local/` (an adopted one) moved into `SETUP_ASIDE_DIR` for that
|
|
293
|
+
* one call.
|
|
183
294
|
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
295
|
+
* @returns the lockfile to write back, or null when it could not be made (the
|
|
296
|
+
* manual steps are printed instead).
|
|
186
297
|
*/
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
298
|
+
function captureSetupLockfile(changes, workspacesBefore, deps) {
|
|
299
|
+
const lockfile = path.join(deps.appRoot, LOCKFILE);
|
|
300
|
+
const edited = editedSetupLockfile(lockfile, workspacesBefore, deps);
|
|
301
|
+
if (edited !== null)
|
|
302
|
+
return edited;
|
|
303
|
+
const moveAside = localMembers(deps.appRoot).length > 0;
|
|
304
|
+
deps.log(`[localize] npm ${SETUP_LOCK_ARGS.join(' ')} (the set-up's lockfile)`);
|
|
305
|
+
if (moveAside)
|
|
306
|
+
setAside(deps);
|
|
307
|
+
let r;
|
|
190
308
|
try {
|
|
191
|
-
|
|
309
|
+
r = deps.run('npm', SETUP_LOCK_ARGS, { cwd: deps.appRoot });
|
|
192
310
|
}
|
|
193
|
-
|
|
194
|
-
if (
|
|
195
|
-
|
|
196
|
-
throw e;
|
|
311
|
+
finally {
|
|
312
|
+
if (moveAside)
|
|
313
|
+
moveBack(deps);
|
|
197
314
|
}
|
|
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) {
|
|
315
|
+
let text = null;
|
|
223
316
|
try {
|
|
224
|
-
|
|
317
|
+
text = fs.readFileSync(lockfile, 'utf8');
|
|
225
318
|
}
|
|
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;
|
|
319
|
+
catch (_a) {
|
|
320
|
+
/* none written */
|
|
236
321
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
322
|
+
if (r.status !== 0 || text === null) {
|
|
323
|
+
manualSetupSteps(changes, r.status !== 0
|
|
324
|
+
? `npm could not write it:\n${tail(r.stderr || r.stdout)}`
|
|
325
|
+
: `npm wrote no ${LOCKFILE}`, deps);
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
return text;
|
|
241
329
|
}
|
|
242
330
|
/**
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
* rather than forced, because the developer's commits are the entire point.
|
|
331
|
+
* Every entry of `packages-local/` but the dot directories, moved into
|
|
332
|
+
* `SETUP_ASIDE_DIR` as it is (a link keeps its text and works again once back).
|
|
246
333
|
*/
|
|
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;
|
|
334
|
+
function setAside(deps) {
|
|
335
|
+
const local = path.join(deps.appRoot, LOCAL_DIR);
|
|
336
|
+
const aside = path.join(deps.appRoot, SETUP_ASIDE_DIR);
|
|
337
|
+
fs.mkdirSync(aside, { recursive: true });
|
|
338
|
+
for (const entry of fs.readdirSync(local)) {
|
|
339
|
+
if (entry.startsWith('.'))
|
|
340
|
+
continue;
|
|
341
|
+
fs.renameSync(path.join(local, entry), path.join(aside, entry));
|
|
259
342
|
}
|
|
260
|
-
|
|
343
|
+
}
|
|
344
|
+
/** Everything in `SETUP_ASIDE_DIR` back into `packages-local/`; the directory goes. */
|
|
345
|
+
function moveBack(deps) {
|
|
346
|
+
const local = path.join(deps.appRoot, LOCAL_DIR);
|
|
347
|
+
const aside = path.join(deps.appRoot, SETUP_ASIDE_DIR);
|
|
348
|
+
const entries = fs.readdirSync(aside);
|
|
349
|
+
for (const entry of entries)
|
|
350
|
+
fs.renameSync(path.join(aside, entry), path.join(local, entry));
|
|
351
|
+
fs.rmdirSync(aside);
|
|
352
|
+
return entries.length;
|
|
261
353
|
}
|
|
262
354
|
/**
|
|
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.
|
|
355
|
+
* A set-up run that was interrupted while the checkouts were in
|
|
356
|
+
* `SETUP_ASIDE_DIR` left them there, where npm does not see them: move them
|
|
357
|
+
* back, saying so. Refuses, moving nothing, when `packages-local/` already
|
|
358
|
+
* has an entry of the same name.
|
|
270
359
|
*/
|
|
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))
|
|
360
|
+
export function restoreSetAside(deps) {
|
|
361
|
+
const aside = path.join(deps.appRoot, SETUP_ASIDE_DIR);
|
|
362
|
+
if (!existsOrLink(aside))
|
|
289
363
|
return;
|
|
290
|
-
let
|
|
364
|
+
let entries;
|
|
291
365
|
try {
|
|
292
|
-
|
|
366
|
+
entries = fs.readdirSync(aside);
|
|
293
367
|
}
|
|
294
368
|
catch (_a) {
|
|
295
|
-
|
|
369
|
+
throw new LocalizeError(`${SETUP_ASIDE_DIR} is not a directory. Remove it and run this again.`, EXIT_REFUSED);
|
|
296
370
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
371
|
+
const clashes = entries.filter((e) => existsOrLink(path.join(deps.appRoot, LOCAL_DIR, e)));
|
|
372
|
+
if (clashes.length) {
|
|
373
|
+
throw new LocalizeError(`${SETUP_ASIDE_DIR}/ holds checkouts an interrupted set-up left there, but ` +
|
|
374
|
+
`${clashes.map((e) => `${LOCAL_DIR}/${e}`).join(', ')} now exist${clashes.length === 1 ? 's' : ''} ` +
|
|
375
|
+
`too. Keep one of each, move the other out of ${LOCAL_DIR}/, and run this again. ` +
|
|
376
|
+
`Nothing was moved.`, EXIT_REFUSED);
|
|
302
377
|
}
|
|
303
|
-
|
|
304
|
-
|
|
378
|
+
const n = moveBack(deps);
|
|
379
|
+
if (n)
|
|
380
|
+
deps.log(`[localize] moved ${n} checkout${n === 1 ? '' : 's'} back from ${SETUP_ASIDE_DIR}/ into ` +
|
|
381
|
+
`${LOCAL_DIR}/, where an interrupted set-up left ${n === 1 ? 'it' : 'them'}.`);
|
|
305
382
|
}
|
|
306
|
-
/**
|
|
307
|
-
function
|
|
308
|
-
let
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
383
|
+
/** The set-up's lockfile as an edit of the root entry, written now; null when npm must make it. */
|
|
384
|
+
function editedSetupLockfile(lockfile, workspacesBefore, deps) {
|
|
385
|
+
let current;
|
|
386
|
+
try {
|
|
387
|
+
current = fs.readFileSync(lockfile, 'utf8');
|
|
388
|
+
}
|
|
389
|
+
catch (_a) {
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
const text = setupLockfileByEdit(current, readJson(path.join(deps.appRoot, 'package.json')), workspacesBefore);
|
|
393
|
+
if (text === null)
|
|
394
|
+
return null;
|
|
395
|
+
fs.writeFileSync(lockfile, text);
|
|
396
|
+
deps.log(`[localize] ${LOCKFILE}: the root entry's "workspaces" now matches package.json (the set-up's lockfile)`);
|
|
397
|
+
return text;
|
|
398
|
+
}
|
|
399
|
+
/** Put the captured set-up lockfile back after the install rewrote it. */
|
|
400
|
+
function restoreSetupLockfile(text, deps) {
|
|
401
|
+
fs.writeFileSync(path.join(deps.appRoot, LOCKFILE), text);
|
|
402
|
+
deps.log(`[localize] ${LOCKFILE}: put back the set-up's lockfile, which lists no checkout (any ` +
|
|
403
|
+
`lock-at-rest warning above was about the one the install wrote).`);
|
|
404
|
+
commitHint(deps);
|
|
405
|
+
}
|
|
406
|
+
function commitHint(deps) {
|
|
407
|
+
deps.log(`[localize] commit the set-up now -- package.json, ${LOCKFILE}, .gitignore:\n` +
|
|
408
|
+
` git add package.json ${LOCKFILE} .gitignore && git commit -m "chore: set up linked localize"`);
|
|
409
|
+
}
|
|
410
|
+
/** The set-up's lockfile could not be made in one step: how to make it by hand. */
|
|
411
|
+
function manualSetupSteps(changes, why, deps) {
|
|
412
|
+
const files = [...new Set([...changes.map((c) => c.file), LOCKFILE])];
|
|
413
|
+
deps.warn(`[localize] could not make the set-up's ${LOCKFILE} in one step: ${why}\n` +
|
|
414
|
+
`Commit the set-up with a lockfile from an install with no checkout present:\n` +
|
|
415
|
+
` npx linked delocalize\n` +
|
|
416
|
+
` npm install\n` +
|
|
417
|
+
` git add ${files.join(' ')} && git commit -m "chore: set up linked localize"\n` +
|
|
418
|
+
` npx linked localize --parked`);
|
|
419
|
+
}
|
|
420
|
+
/** Tell the developer what `installGitHooks` did not do. */
|
|
421
|
+
function reportHooks(hooks, deps) {
|
|
422
|
+
for (const file of hooks.skipped) {
|
|
423
|
+
deps.warn(`[localize] ${path.relative(deps.appRoot, file)} is not ours, so it was left alone. Without ` +
|
|
424
|
+
`the localize hook there, run \`npm install\` yourself after a pull or branch switch that ` +
|
|
425
|
+
`changes dependencies.`);
|
|
426
|
+
}
|
|
427
|
+
if (hooks.hooksPath !== null) {
|
|
428
|
+
deps.warn(`[localize] core.hooksPath is set (${hooks.hooksPath}), so no git hooks were written. Add ` +
|
|
429
|
+
`each line below to the hook named before it, an \`sh\` script (not, say, a Python hook ` +
|
|
430
|
+
`the pre-commit framework writes); they do nothing where the cli is not installed:`);
|
|
431
|
+
for (const m of hooks.manual)
|
|
432
|
+
deps.warn(` ${m.file}:\n ${m.line}`);
|
|
433
|
+
}
|
|
434
|
+
if (hooks.sharedWithWorktrees) {
|
|
435
|
+
deps.warn(`[localize] this is a linked worktree: git hooks are shared by every worktree of the ` +
|
|
436
|
+
`repository. They act only in a work tree whose ${LOCAL_DIR}/ has checkouts.`);
|
|
323
437
|
}
|
|
324
|
-
return code;
|
|
325
438
|
}
|
|
326
439
|
/**
|
|
327
|
-
*
|
|
328
|
-
*
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
440
|
+
* One line when an app still has the retired record. It is ignored: the
|
|
441
|
+
* record now is the directories in `packages-local/`.
|
|
442
|
+
*/
|
|
443
|
+
export function legacyManifestHint(deps) {
|
|
444
|
+
if (!fs.existsSync(path.join(deps.appRoot, LEGACY_MANIFEST)))
|
|
445
|
+
return;
|
|
446
|
+
deps.warn(`[localize] ${LEGACY_MANIFEST} is no longer read: every directory in ${LOCAL_DIR}/ is ` +
|
|
447
|
+
`localized, ${PARKED_DIR}/ holds parked ones. Delete the file.`);
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Run the build for one new checkout. `opts.build` wins, including `''` for
|
|
451
|
+
* no build; otherwise the cli builds itself and everything else gets
|
|
452
|
+
* `linked build`.
|
|
333
453
|
*/
|
|
334
|
-
|
|
335
|
-
const
|
|
336
|
-
if (!
|
|
337
|
-
return
|
|
338
|
-
deps.
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
`localize is
|
|
342
|
-
|
|
454
|
+
function runBuild(p, pkgDir, opts, deps) {
|
|
455
|
+
const cmd = buildCommandFor(p.name, opts);
|
|
456
|
+
if (!cmd)
|
|
457
|
+
return 'skipped';
|
|
458
|
+
deps.log(`[localize] ${p.dir}: ${cmd}`);
|
|
459
|
+
const r = deps.run(cmd, [], { cwd: pkgDir, shell: true });
|
|
460
|
+
if (r.status !== 0) {
|
|
461
|
+
deps.warn(`[localize] ${p.dir}: \`${cmd}\` failed. It is installed and linked anyway -- a package ` +
|
|
462
|
+
`whose build is broken is usually what you localized it to fix.\n` +
|
|
463
|
+
tail(r.stderr || r.stdout));
|
|
464
|
+
return 'failed';
|
|
465
|
+
}
|
|
466
|
+
return 'ok';
|
|
467
|
+
}
|
|
468
|
+
function buildCommandFor(name, opts) {
|
|
469
|
+
if (opts.build !== undefined)
|
|
470
|
+
return opts.build;
|
|
471
|
+
return name === CLI_PACKAGE ? CLI_BUILD_COMMAND : DEFAULT_BUILD_COMMAND;
|
|
343
472
|
}
|
|
344
473
|
export function reportError(e, deps) {
|
|
345
474
|
if (e instanceof LocalizeError) {
|
|
@@ -354,23 +483,20 @@ export function reportError(e, deps) {
|
|
|
354
483
|
* `rev-parse --abbrev-ref` fails there, and `create-package` adopts a fresh
|
|
355
484
|
* `git init` before its first commit.
|
|
356
485
|
*/
|
|
357
|
-
export function currentBranch(
|
|
486
|
+
export function currentBranch(checkout, deps) {
|
|
358
487
|
const sym = deps.run('git', ['symbolic-ref', '--short', '-q', 'HEAD'], {
|
|
359
|
-
cwd:
|
|
488
|
+
cwd: checkout,
|
|
360
489
|
});
|
|
361
490
|
if (sym.status === 0 && sym.stdout.trim())
|
|
362
491
|
return sym.stdout.trim();
|
|
363
492
|
const r = deps.run('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {
|
|
364
|
-
cwd:
|
|
493
|
+
cwd: checkout,
|
|
365
494
|
});
|
|
366
495
|
const b = r.stdout.trim();
|
|
367
496
|
return r.status === 0 && b ? b : 'HEAD';
|
|
368
497
|
}
|
|
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]);
|
|
498
|
+
/** The last lines of a subprocess's output: enough to see the error, not the whole log. */
|
|
499
|
+
export function tail(output, lines = 20) {
|
|
500
|
+
return output.trim().split('\n').slice(-lines).join('\n');
|
|
375
501
|
}
|
|
376
502
|
//# sourceMappingURL=localize.js.map
|