Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cli): wing pack #3938

Merged
merged 61 commits into from
Oct 5, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
c80d7e5
implement pack command
Chriscbr Aug 21, 2023
792b2ff
minor fixes
Chriscbr Aug 21, 2023
fe161d5
i think it works
Chriscbr Aug 23, 2023
ed5ec2c
add fixture library and e2e test
Chriscbr Aug 23, 2023
b915bc1
most tests pass now
Chriscbr Aug 23, 2023
c65ed79
Merge branch 'main' into rybickic/wing-pack
monadabot Aug 23, 2023
9bf22bc
chore: self mutation (../patches/e2e-2of2.diff/e2e-2of2.diff)
monadabot Aug 23, 2023
7a2615d
Merge branch 'main' into rybickic/wing-pack
Chriscbr Aug 24, 2023
b1bf308
fix merge conflict
Chriscbr Aug 24, 2023
0c764c6
make fixture private
Chriscbr Aug 24, 2023
86b6577
fix regression
Chriscbr Aug 24, 2023
d921571
minor refactor
Chriscbr Aug 24, 2023
34f65b4
update comment
Chriscbr Aug 24, 2023
859cf2c
gitignore tarball
Chriscbr Aug 24, 2023
675a3ca
add empty turbo config
Chriscbr Aug 24, 2023
244d5a8
Merge branch 'main' into rybickic/wing-pack
monadabot Aug 24, 2023
802c9cb
chore: self mutation (e2e-2of2.diff)
monadabot Aug 24, 2023
dacaf7f
update example
Chriscbr Aug 24, 2023
68d2567
move wing-fixture to examples
Chriscbr Aug 24, 2023
03316a6
PR feedback
Chriscbr Aug 24, 2023
2e6fd91
get multi-file libraries working
Chriscbr Aug 24, 2023
c2468b5
Merge branch 'main' into rybickic/wing-pack
Chriscbr Aug 24, 2023
cbf98cc
restore file
Chriscbr Aug 24, 2023
070a767
fix
Chriscbr Aug 24, 2023
651f12a
nit
Chriscbr Aug 24, 2023
071609f
remove broken test
Chriscbr Aug 24, 2023
38f05db
cleaner, perhaps
Chriscbr Aug 24, 2023
6018b37
fix comments
Chriscbr Aug 24, 2023
f6ec721
oops, how did that get there?
Chriscbr Aug 24, 2023
9f91fc4
misc changes
Chriscbr Sep 29, 2023
e869e2e
pr feedback
Chriscbr Sep 29, 2023
104290e
use async
Chriscbr Sep 29, 2023
dbd48ac
fix merge conflicts
Chriscbr Sep 29, 2023
083e3de
fix access modifier
Chriscbr Sep 29, 2023
ba0efcb
Merge branch 'main' into rybickic/wing-pack
Chriscbr Oct 2, 2023
76c417e
progress
Chriscbr Oct 3, 2023
0112ac6
fix import
Chriscbr Oct 4, 2023
1b00504
fix type checking and jsification
Chriscbr Oct 4, 2023
48eaa30
Merge branch 'main' into rybickic/wing-pack
Chriscbr Oct 4, 2023
ca0b07e
fix tests
Chriscbr Oct 4, 2023
d3dcb07
cleanup, add docs
Chriscbr Oct 4, 2023
ec28531
fix bug
Chriscbr Oct 4, 2023
7f1f871
nit
Chriscbr Oct 4, 2023
b01ba8e
cleanup
Chriscbr Oct 4, 2023
420ceac
actually these are needed for tests to pass
Chriscbr Oct 4, 2023
9d877b9
cleanup
Chriscbr Oct 4, 2023
ca0015e
trying to debug ci
Chriscbr Oct 4, 2023
3a6f7d4
ah the power of rubber duck debugging
Chriscbr Oct 4, 2023
a4b3d9e
Merge branch 'main' into rybickic/wing-pack
monadabot Oct 4, 2023
33740bd
chore: self mutation (build.diff)
monadabot Oct 4, 2023
3b71e32
update description
Chriscbr Oct 4, 2023
d753379
specify outfile instead of outdir
Chriscbr Oct 5, 2023
8ba11d9
fix
Chriscbr Oct 5, 2023
a646d88
Merge branch 'main' into rybickic/wing-pack
Chriscbr Oct 5, 2023
47f430c
move comment
Chriscbr Oct 5, 2023
b465bce
update comment
Chriscbr Oct 5, 2023
0841780
use npm packlist
Chriscbr Oct 5, 2023
c6710e1
fix error message
Chriscbr Oct 5, 2023
1571a84
include globs
Chriscbr Oct 5, 2023
ca4e2d0
Merge branch 'main' into rybickic/wing-pack
monadabot Oct 5, 2023
6e0dbc1
chore: self mutation (e2e-2of2.diff)
monadabot Oct 5, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions apps/wing/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,13 @@ async function main() {
.hook("preAction", collectAnalyticsHook)
.action(runSubCommand("test"));

program
.command("pack")
.description("Package a Wing module as a shareable library")
Chriscbr marked this conversation as resolved.
Show resolved Hide resolved
.addOption(new Option("-o --outdir <outdir>", "Output directory").default("dist/"))
Chriscbr marked this conversation as resolved.
Show resolved Hide resolved
.hook("preAction", collectAnalyticsHook)
.action(runSubCommand("pack"));

program
.command("docs")
.description("Open the Wing documentation")
Expand Down
83 changes: 83 additions & 0 deletions apps/wing/src/commands/pack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import * as path from "path";
import * as fs from "fs";
import * as cp from "child_process";

export interface PackageOptions {
/**
* Directory to to save the generated package to.
*/
readonly outdir: string;
}

export async function pack(options: PackageOptions): Promise<string> {
Chriscbr marked this conversation as resolved.
Show resolved Hide resolved
// check npm is installed
Chriscbr marked this conversation as resolved.
Show resolved Hide resolved
try {
cp.execSync("npm --version", { stdio: "ignore" });
Chriscbr marked this conversation as resolved.
Show resolved Hide resolved
} catch {
throw new Error(`npm is not installed. Install it from https://www.npmjs.com/get-npm`);
}

// check package.json exists
const currentDir = process.cwd();
const pkgJsonPath = path.join(currentDir, "package.json");
if (!fs.existsSync(pkgJsonPath)) {
Chriscbr marked this conversation as resolved.
Show resolved Hide resolved
throw new Error(`No package.json found in the current directory. Run \`npm init\` first.`);
}

const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, "utf8"));
pkgJson.wing = pkgJson.wing ?? {};
Chriscbr marked this conversation as resolved.
Show resolved Hide resolved

// check entrypoint file is valid and is specified in package.json
const entrypoint = pkgJson.wing.entrypoint ?? "lib.w";
if (path.isAbsolute(entrypoint)) {
throw new Error(
`Entrypoint file "${entrypoint}" must be a relative path. The entrypoint is specified in package.json:\n\n "wing": {\n "entrypoint": "lib.w"\n }\n`
);
}
if (!fs.existsSync(entrypoint)) {
throw new Error(
`Entrypoint file "${entrypoint}" does not exist. The entrypoint is specified in package.json:\n\n "wing": {\n "entrypoint": "lib.w"\n }\n`
);
}
pkgJson.wing.entrypoint = entrypoint;

// check source files will be included in tarball
const pkgJsonFiles = pkgJson.files ?? [];
const expectedGlobs = ["**/*.js", "**/*.w", "!/target/**"];
Chriscbr marked this conversation as resolved.
Show resolved Hide resolved
for (const glob of expectedGlobs) {
if (!pkgJsonFiles.includes(glob)) {
Chriscbr marked this conversation as resolved.
Show resolved Hide resolved
pkgJsonFiles.push(glob);
}
}
pkgJson.files = pkgJsonFiles;

// check package.json has required fields
const requiredFields = ["name", "version", "description", "author", "license"];
Chriscbr marked this conversation as resolved.
Show resolved Hide resolved
for (const field of requiredFields) {
if (pkgJson[field] === undefined) {
throw new Error(`Missing required field "${field}" in package.json`);
}
}

// check if "main" points to a valid file, and if not, create a dummy file
let main = pkgJson.main ?? "index.js";
if (!fs.existsSync(main)) {
const lines = [];
lines.push("// This file was generated by wing pack");
Chriscbr marked this conversation as resolved.
Show resolved Hide resolved
lines.push(`console.log("${pkgJson.name} isn't ready for JS consumption yet!")`);
Chriscbr marked this conversation as resolved.
Show resolved Hide resolved
Chriscbr marked this conversation as resolved.
Show resolved Hide resolved
lines.push();
fs.writeFileSync(main, lines.join("\n"));
Chriscbr marked this conversation as resolved.
Show resolved Hide resolved
}
pkgJson.main = main;

// write package.json
fs.writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + "\n");

// make tarball
fs.mkdirSync(options.outdir, { recursive: true });
const command = `npm pack --json --pack-destination "${options.outdir}"`;
const output = cp.execSync(command, { stdio: "pipe" });
const parsedOutput = JSON.parse(output.toString());
const tarballName = parsedOutput[0].filename;
return path.join(options.outdir, tarballName);
}
3 changes: 3 additions & 0 deletions examples/tests/valid/bring_wing_library.w
Chriscbr marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
bring "wing-fixture" as fixture;

new fixture.Store();
3 changes: 2 additions & 1 deletion examples/tests/valid/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"constructs": "^10",
"jsii-code-samples": "1.7.0",
"projen": "^0.71.60",
"uuid": "^9.0.0"
"uuid": "^9.0.0",
"wing-fixture": "workspace:^"
},
"volta": {
"extends": "../../../package.json"
Expand Down
Binary file added libs/wing-fixture/dist/wing-fixture-0.0.0.tgz
Chriscbr marked this conversation as resolved.
Show resolved Hide resolved
Binary file not shown.
2 changes: 2 additions & 0 deletions libs/wing-fixture/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// This file was generated by wing pack
console.log("wing-fixture isn't ready for JS consumption yet!")
12 changes: 12 additions & 0 deletions libs/wing-fixture/lib.w
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
bring cloud;

class Store {
data: cloud.Bucket;
init() {
this.data = new cloud.Bucket();
}

inflight set(message: str) {
this.data.put("data.txt", message);
}
}
34 changes: 34 additions & 0 deletions libs/wing-fixture/package.json
Chriscbr marked this conversation as resolved.
Show resolved Hide resolved
Chriscbr marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"name": "wing-fixture",
"version": "0.0.0",
"description": "A fake library just for testing stuff out",
"author": {
"name": "Wing Cloud",
"email": "[email protected]",
"url": "https://wing.cloud",
"organization": true
},
"files": [
"**/*.js",
"**/*.w",
"!/target/**"
],
"license": "MIT",
"engines": {
"node": ">=v18.0.0"
},
"main": "index.js",
"repository": {
"type": "git",
"url": "git+https://github.com/winglang/wing.git"
},
"scripts": {
"package": "npm pack"
},
"wing": {
"entrypoint": "lib.w"
},
Chriscbr marked this conversation as resolved.
Show resolved Hide resolved
Chriscbr marked this conversation as resolved.
Show resolved Hide resolved
"volta": {
"extends": "../../package.json"
}
}
3 changes: 3 additions & 0 deletions libs/wingc/src/ast.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::fmt::{Debug, Display};
use std::hash::{Hash, Hasher};
use std::path::PathBuf;
use std::sync::atomic::{AtomicUsize, Ordering};

use indexmap::{Equivalent, IndexMap, IndexSet};
Expand Down Expand Up @@ -423,6 +424,8 @@ pub struct Interface {
#[derive(Debug)]
pub enum BringSource {
BuiltinModule(Symbol),
// we can remove this once Wing modules are compiled to Jsii modules :)
WingModule { name: Symbol, root_file: PathBuf },
Chriscbr marked this conversation as resolved.
Show resolved Hide resolved
JsiiModule(Symbol),
WingFile(Symbol),
Chriscbr marked this conversation as resolved.
Show resolved Hide resolved
}
Expand Down
4 changes: 4 additions & 0 deletions libs/wingc/src/fold.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ where
StmtKind::Bring { source, identifier } => StmtKind::Bring {
source: match source {
BringSource::BuiltinModule(name) => BringSource::BuiltinModule(f.fold_symbol(name)),
BringSource::WingModule { name, root_file } => BringSource::WingModule {
name: f.fold_symbol(name),
root_file,
},
BringSource::JsiiModule(name) => BringSource::JsiiModule(f.fold_symbol(name)),
BringSource::WingFile(name) => BringSource::WingFile(f.fold_symbol(name)),
},
Expand Down
17 changes: 16 additions & 1 deletion libs/wingc/src/jsify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -799,9 +799,24 @@ impl<'a> JSifier<'a> {
identifier.as_ref().expect("bring jsii module requires an alias"),
name
)),
BringSource::WingModule { name: _, root_file } => {
let preflight_file_map = self.preflight_file_map.borrow();
let preflight_file_name = preflight_file_map
.get(root_file)
.expect("wing module file not found in preflight file map");
CodeMaker::one_line(format!(
"const {} = require(\"./{}\")({{ {} }});",
// checked during type checking
identifier.as_ref().expect("bring wing file requires an alias"),
preflight_file_name,
STDLIB,
))
}
BringSource::WingFile(name) => {
let preflight_file_map = self.preflight_file_map.borrow();
let preflight_file_name = preflight_file_map.get(Path::new(&name.name)).unwrap();
let preflight_file_name = preflight_file_map
.get(Path::new(&name.name))
.expect("wing file not found in preflight file map");
CodeMaker::one_line(format!(
"const {} = require(\"./{}\")({{ {} }});",
// checked during type checking
Expand Down
2 changes: 1 addition & 1 deletion libs/wingc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ pub fn type_check(

let mut scope_env = types.get_scope_env(&scope);
let mut tc = TypeChecker::new(types, file_path, jsii_types, jsii_imports);
tc.add_module_to_env(
tc.add_jsii_module_to_env(
&mut scope_env,
WINGSDK_ASSEMBLY_NAME.to_string(),
vec![WINGSDK_STD_MODULE.to_string()],
Expand Down
1 change: 1 addition & 0 deletions libs/wingc/src/lsp/document_symbols.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ impl Visit<'_> for DocumentSymbolVisitor {
// in these cases, an alias is required (like "bring foo as bar;")
// so we don't need to add a symbol for the module itself
BringSource::JsiiModule(_) => {}
BringSource::WingModule { .. } => {}
BringSource::WingFile(_) => {}
};
}
Expand Down
14 changes: 7 additions & 7 deletions libs/wingc/src/lsp/hover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -648,13 +648,13 @@ bring cloud;
"#
);

test_hover_list!(
test_bring_library,
r#"
bring "@winglang/sdk" as bar;
//^
"#
);
// test_hover_list!(
// test_bring_library,
// r#"
// bring "@winglang/sdk" as bar;
// //^
// "#
// );
Chriscbr marked this conversation as resolved.
Show resolved Hide resolved

test_hover_list!(
test_var,
Expand Down
93 changes: 84 additions & 9 deletions libs/wingc/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -680,7 +680,7 @@ impl<'s> Parser<'s> {
}

fn build_bring_statement(&self, statement_node: &Node) -> DiagnosticResult<StmtKind> {
let module_name = self.node_symbol(&statement_node.child_by_field_name("module_name").unwrap())?;
let module_symbol = self.node_symbol(&statement_node.child_by_field_name("module_name").unwrap())?;
let alias = if let Some(identifier) = statement_node.child_by_field_name("alias") {
Some(self.check_reserved_symbol(&identifier)?)
} else {
Expand All @@ -689,8 +689,8 @@ impl<'s> Parser<'s> {

// if the module name is a path ending in .w, create a new Parser to parse it as a new Scope,
// and create a StmtKind::Module instead
if module_name.name.starts_with("\"") && module_name.name.ends_with(".w\"") {
let module_path = Path::new(&module_name.name[1..module_name.name.len() - 1]);
if module_symbol.name.starts_with("\"") && module_symbol.name.ends_with(".w\"") {
let module_path = Path::new(&module_symbol.name[1..module_symbol.name.len() - 1]);
let source_path = normalize_path(module_path, Some(&Path::new(&self.source_name)));
if source_path == Path::new(&self.source_name) {
return self.with_error("Cannot bring a module into itself", statement_node);
Expand All @@ -714,15 +714,15 @@ impl<'s> Parser<'s> {
Ok(StmtKind::Bring {
source: BringSource::WingFile(Symbol {
name: source_path.to_string_lossy().to_string(),
span: module_name.span,
span: module_symbol.span,
}),
identifier: Some(alias),
})
} else {
self.with_error::<StmtKind>(
format!(
"bring {} must be assigned to an identifier (e.g. bring \"foo\" as foo)",
module_name
module_symbol
),
statement_node,
)
Expand All @@ -731,12 +731,57 @@ impl<'s> Parser<'s> {
return module;
}

if module_name.name.starts_with("\"") && module_name.name.ends_with("\"") {
if module_symbol.name.starts_with("\"") && module_symbol.name.ends_with("\"") {
// we need to inspect the npm dependency to figure out if it's a JSII library or a Wing library
// first, find where the package.json is located
let module_name = module_symbol.name[1..module_symbol.name.len() - 1].to_string();
let source_dir = Path::new(&self.source_name).parent().unwrap().to_str().unwrap();
let module_dir =
wingii::util::package_json::find_dependency_directory(&module_name, &source_dir).ok_or_else(|| {
Chriscbr marked this conversation as resolved.
Show resolved Hide resolved
self
.with_error::<Node>(
format!(
"Unable to load \"{}\": Module not found in \"{}\"",
module_name, self.source_name
),
&statement_node,
)
.err();
})?;

// If the package.json has `wing.entrypoint` specified, then we treat it as a Wing library
if let Some(entrypoint_path) = get_wing_entrypoint(&Path::new(&module_dir)) {
return if let Some(alias) = alias {
// Record that the current file depends on the library's entrypoint file
self.referenced_wing_files.borrow_mut().push(entrypoint_path.clone());

Ok(StmtKind::Bring {
source: BringSource::WingModule {
name: Symbol {
name: module_name,
span: module_symbol.span,
},
root_file: entrypoint_path,
},
identifier: Some(alias),
})
} else {
self.with_error::<StmtKind>(
format!(
"bring {} must be assigned to an identifier (e.g. bring \"foo\" as foo)",
Chriscbr marked this conversation as resolved.
Show resolved Hide resolved
module_name
),
statement_node,
)
};
}

// otherwise, we treat it as a JSII library
return if let Some(alias) = alias {
Ok(StmtKind::Bring {
source: BringSource::JsiiModule(Symbol {
name: module_name.name[1..module_name.name.len() - 1].to_string(),
span: module_name.span,
name: module_name,
span: module_symbol.span,
}),
identifier: Some(alias),
})
Expand All @@ -752,7 +797,7 @@ impl<'s> Parser<'s> {
}

Ok(StmtKind::Bring {
source: BringSource::BuiltinModule(module_name),
source: BringSource::BuiltinModule(module_symbol),
identifier: alias,
})
}
Expand Down Expand Up @@ -2102,6 +2147,36 @@ impl<'s> Parser<'s> {
}
}

/// Get the package.json's `.wing.entrypoint` if it has one
fn get_wing_entrypoint(module_dir: &Path) -> Option<PathBuf> {
let package_json_path = Path::new(module_dir).join("package.json");
if !package_json_path.exists() {
return None;
}

let package_json = match fs::read_to_string(package_json_path) {
Ok(package_json) => package_json,
Err(_) => return None,
};

let package_json: serde_json::Value = match serde_json::from_str(&package_json) {
Ok(package_json) => package_json,
Err(_) => return None,
};

let wing_config = match package_json.get("wing") {
Some(wing_config) => wing_config,
None => return None,
};

let entrypoint = match wing_config.get("entrypoint") {
Some(entrypoint) => entrypoint,
None => return None,
};

Some(PathBuf::from(module_dir).join(entrypoint.as_str().unwrap()))
}

// TODO: this function seems fragile
// use inodes as source of truth instead https://github.com/winglang/wing/issues/3627
pub fn normalize_path(path: &Path, relative_to: Option<&Path>) -> PathBuf {
Expand Down
Loading
Loading