Skip to content

Commit 424a48c

Browse files
committed
cli: implement node run <script-in-package-json>
1 parent d1d5da2 commit 424a48c

File tree

5 files changed

+103
-0
lines changed

5 files changed

+103
-0
lines changed

lib/internal/main/run.js

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
'use strict';
2+
3+
const {
4+
JSONParse,
5+
ObjectKeys,
6+
} = primordials;
7+
8+
const {
9+
prepareMainThreadExecution,
10+
markBootstrapComplete,
11+
} = require('internal/process/pre_execution');
12+
const { getPackageJSONScripts } = internalBinding('modules');
13+
const { execSync } = require('child_process');
14+
const { log, error } = require('internal/console/global');
15+
16+
prepareMainThreadExecution(false, false);
17+
18+
markBootstrapComplete();
19+
20+
const json_string = getPackageJSONScripts('package.json');
21+
22+
// Check if package.json exists and is parseable
23+
if (json_string === undefined) {
24+
process.exit(1);
25+
return;
26+
}
27+
const scripts = JSONParse(json_string);
28+
const id = process.argv.at(2);
29+
const command = scripts[id];
30+
31+
if (!command) {
32+
error(`Missing script: "${id}"`);
33+
34+
const keys = ObjectKeys(scripts);
35+
if (keys.length === 0) {
36+
error('There are no scripts available in package.json');
37+
} else {
38+
error('Available scripts are:\n');
39+
for (const script of ObjectKeys(scripts)) {
40+
error(` ${script}: ${scripts[script]}`);
41+
}
42+
}
43+
process.exit(1);
44+
}
45+
46+
log('');
47+
log('>', id);
48+
log('>', command);
49+
log('');
50+
51+
execSync(command, { stdio: 'inherit' });

src/node.cc

+4
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,10 @@ MaybeLocal<Value> StartExecution(Environment* env, StartExecutionCallback cb) {
409409
return StartExecution(env, "internal/main/watch_mode");
410410
}
411411

412+
if (!first_argv.empty() && first_argv == "run") {
413+
return StartExecution(env, "internal/main/run");
414+
}
415+
412416
if (!first_argv.empty() && first_argv != "-") {
413417
return StartExecution(env, "internal/main/run_main_module");
414418
}

src/node_modules.cc

+44
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,21 @@ const BindingData::PackageConfig* BindingData::GetPackageJSON(
219219
if (field_value == "commonjs" || field_value == "module") {
220220
package_config.type = field_value;
221221
}
222+
} else if (key == "scripts") {
223+
if (value.type().get(field_type)) {
224+
return throw_invalid_package_config();
225+
}
226+
switch (field_type) {
227+
case simdjson::ondemand::json_type::object: {
228+
if (value.raw_json().get(field_value)) {
229+
return throw_invalid_package_config();
230+
}
231+
package_config.scripts = field_value;
232+
break;
233+
}
234+
default:
235+
break;
236+
}
222237
}
223238
}
224239
// package_config could be quite large, so we should move it instead of
@@ -344,6 +359,33 @@ void BindingData::GetNearestParentPackageJSONType(
344359
args.GetReturnValue().Set(Array::New(realm->isolate(), values, 3));
345360
}
346361

362+
void BindingData::GetPackageJSONScripts(
363+
const FunctionCallbackInfo<Value>& args) {
364+
CHECK_EQ(args.Length(), 1);
365+
CHECK(args[0]->IsString());
366+
367+
Realm* realm = Realm::GetCurrent(args);
368+
Utf8Value path(realm->isolate(), args[0]);
369+
370+
THROW_IF_INSUFFICIENT_PERMISSIONS(
371+
realm->env(),
372+
permission::PermissionScope::kFileSystemRead,
373+
path.ToStringView());
374+
375+
auto package_json = GetPackageJSON(realm, path.ToString());
376+
if (package_json == nullptr) {
377+
printf("Can't read package.json\n");
378+
return;
379+
} else if (!package_json->scripts.has_value()) {
380+
printf("Package.json scripts doesn't exist or parseable\n");
381+
return;
382+
}
383+
384+
args.GetReturnValue().Set(
385+
ToV8Value(realm->context(), package_json->scripts.value())
386+
.ToLocalChecked());
387+
}
388+
347389
void BindingData::GetPackageScopeConfig(
348390
const FunctionCallbackInfo<Value>& args) {
349391
CHECK_GE(args.Length(), 1);
@@ -424,6 +466,7 @@ void BindingData::CreatePerIsolateProperties(IsolateData* isolate_data,
424466
"getNearestParentPackageJSON",
425467
GetNearestParentPackageJSON);
426468
SetMethod(isolate, target, "getPackageScopeConfig", GetPackageScopeConfig);
469+
SetMethod(isolate, target, "getPackageJSONScripts", GetPackageJSONScripts);
427470
}
428471

429472
void BindingData::CreatePerContextProperties(Local<Object> target,
@@ -440,6 +483,7 @@ void BindingData::RegisterExternalReferences(
440483
registry->Register(GetNearestParentPackageJSONType);
441484
registry->Register(GetNearestParentPackageJSON);
442485
registry->Register(GetPackageScopeConfig);
486+
registry->Register(GetPackageJSONScripts);
443487
}
444488

445489
} // namespace modules

src/node_modules.h

+3
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ class BindingData : public SnapshotableObject {
3232
std::string type = "none";
3333
std::optional<std::string> exports;
3434
std::optional<std::string> imports;
35+
std::optional<std::string> scripts;
3536
std::string raw_json;
3637

3738
v8::Local<v8::Array> Serialize(Realm* realm) const;
@@ -60,6 +61,8 @@ class BindingData : public SnapshotableObject {
6061
const v8::FunctionCallbackInfo<v8::Value>& args);
6162
static void GetPackageScopeConfig(
6263
const v8::FunctionCallbackInfo<v8::Value>& args);
64+
static void GetPackageJSONScripts(
65+
const v8::FunctionCallbackInfo<v8::Value>& args);
6366

6467
static void CreatePerIsolateProperties(IsolateData* isolate_data,
6568
v8::Local<v8::ObjectTemplate> ctor);

typings/internalBinding/modules.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,5 @@ export interface ModulesBinding {
2626
string, // raw content
2727
]
2828
getPackageScopeConfig(path: string): SerializedPackageConfig | undefined
29+
getPackageJSONScripts(path: string): string | undefined
2930
}

0 commit comments

Comments
 (0)