Setting the file. One moment.
Install · Wix Headless Kit · wix/skills · Skills Docs
ContentsBack to the top of the page install/install.mjs
install/ install.mjs
JavaScript · 95 lines · 5 KB
14 // `npm ci --ignore-scripts || npm install --ignore-scripts` (ci needs a lock that agrees
15 // with package.json; without one, install).
16 // Every path ends with `.install-exit` holding the exit code (0 for none), the marker setup.mjs,
17 // attach.mjs and SKILL.md step 3 sync on. Never two installs concurrently in one project: a second
18 // npm in the same node_modules redoes the first one's work.
19 import { existsSync, readFileSync, writeFileSync } from "node:fs" ;
20 import { join } from "node:path" ;
21 import { npmSync } from "./proc.mjs" ;
22
23 /** {total, missing, sample}: the lock's node_modules entries the tree lacks or holds at another version. */
24 export function lockGaps ( projectDir ) {
25 const lockPath = join (projectDir, "package-lock.json" );
26 if ( ! existsSync (lockPath)) return null ;
27 let lock;
28 try { lock = JSON . parse ( readFileSync (lockPath, "utf8" )); } catch { return null ; }
29 // Optional entries (a platform's native binaries, an optional peer) are absent on every other platform and
30 // npm leaves them out without complaint, so they never count as gaps.
31 const entries = Object. entries (lock.packages ?? {}). filter (([ path , e ]) => path. startsWith ( "node_modules/" ) && ! e.link && ! e.optional && ! e.devOptional);
32 let missing = 0 ;
33 const sample = [];
34 // A package.json dependency the lock's root entry does not declare (deploy's fill-only patch
35 // added it after the lock was written) is a gap too: the lock no longer describes the project.
36 try {
37 const pkg = JSON . parse ( readFileSync ( join (projectDir, "package.json" ), "utf8" ));
38 const root = lock.packages?.[ "" ] ?? {};
39 for ( const field of [ "dependencies" , "devDependencies" ]) {
40 for ( const name of Object. keys (pkg[field] ?? {})) {
41 if ( ! (root[field]?.[name] ?? root.dependencies?.[name] ?? root.devDependencies?.[name])) {
42 missing ++ ;
43 if (sample. length < 5 ) sample. push ( `${ name } (in package.json, not in the lock)` );
44 }
45 }
46 }
47 } catch { /* no package.json: the lock entries decide */ }
48 for ( const [ path , e ] of entries) {
49 let version = null ;
50 try { version = JSON . parse ( readFileSync ( join (projectDir, path, "package.json" ), "utf8" )).version; } catch { /* absent */ }
51 if (version !== e.version) {
52 missing ++ ;
53 if (sample. length < 5 ) sample. push ( `${ path . replace ( / ^ node_modules \/ / , "" ) }@${ e . version }${ version ? ` (have ${ version })` : ""}` );
54 }
55 }
56 return { total: entries. length , missing, sample };
57 }
58
59 export function installDecision ( projectDir = process. cwd ()) {
60 const hasModules = existsSync ( join (projectDir, "node_modules" ));
61 const lock = lockGaps (projectDir);
62 if ( ! hasModules) return { decision: existsSync ( join (projectDir, "package-lock.json" )) ? "ci" : "install" , lock };
63 if ( ! lock || lock.total === 0 ) return { decision: "install" , lock };
64 if (lock.missing === 0 ) return { decision: "none" , lock };
65 return { decision: lock.missing > lock.total / 2 ? "ci" : "install" , lock };
66 }
67
68 /** Runs the decided install; stdio defaults to inherited. Returns {decision, lock, status}. */
69 export function runInstall ( projectDir = process. cwd (), { stdio = "inherit" } = {}) {
70 const { decision , lock } = installDecision (projectDir);
71 const flags = [ "--ignore-scripts" , "--no-audit" , "--no-fund" ];
72 let status = 0 ;
73 if (decision === "ci" ) {
74 let r = npmSync ( "npm" , [ "ci" , ... flags], { cwd: projectDir, stdio });
75 if (r.status !== 0 ) r = npmSync ( "npm" , [ "install" , ... flags], { cwd: projectDir, stdio });
76 if (r.error) console. error ( `npm could not start: ${ r . error . message }` );
77 status = r.status ?? 1 ;
78 } else if (decision === "install" ) {
79 const r = npmSync ( "npm" , [ "install" , ... flags], { cwd: projectDir, stdio });
80 if (r.error) console. error ( `npm could not start: ${ r . error . message }` );
81 status = r.status ?? 1 ;
82 }
83 writeFileSync ( join (projectDir, ".install-exit" ), `${ status } \n ` );
84 return { decision, lock, status };
85 }
86
87 if (process.argv[ 1 ] && /install \. mjs $ / . test (process.argv[ 1 ])) {
88 if (process.argv. includes ( "--decide" )) {
89 console. log ( JSON . stringify ( installDecision (), null , 2 ));
90 } else {
91 const r = runInstall (process. cwd ());
92 console. log ( JSON . stringify (r, null , 2 ));
93 process. exit (r.status);
94 }
95 }