How to create a 'common package' to share common logic in convector packages? - hyperledger-fabric

I want to create a common package to share common login in convector chain code...this is useful has the project grows and we want to share common blocks of code, utils and other useful code to don't dry the project, or hard-code strings
but how can we do it if convector currently don't support it?

Currently convector can't use common vanilla packages (yet), only controllers,
to work we need to hack some stuff, like create a package that implement a controller, like a fake controller. and follow some steps
thanks to Diego and Walter of Covalent and a few hours
this post belongs to a github project that have a nest.js server, but currently is not created, when I have the link I update this post
first we start to create a lerna package for typescript, by hand
Create lerna common package #convector-rest-sample/common
packages/common/tsconfig.json
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "."
},
"include": [
"./src/**/*"
]
}
packages/common/package.json
{
"name": "#convector-rest-sample/common",
"version": "0.1.0",
"main": "dist/src/index",
"types": "dist/src/index",
"files": [
"dist"
],
"scripts": {
"build": "npm run clean && tsc",
"clean": "rm -rf ./dist",
"prepublishOnly": "npm run build"
},
"devDependencies": {
"typescript": "3.4.3"
}
}
Note: the paths "main": "dist/src/index" and "types": "dist/src/index" are important and must point to location of the index file, if we use a wrong path, we get errors using the package
create the index to export package files
packages/common/src/index.ts
export * from './constants';
packages/common/src/constants.ts
// convector model
const CONVECTOR_MODEL_PATH_PREFIX: string = 'io.worldsibu.examples';
const CONVECTOR_MODEL_PATH_PARTICIPANT: string = `${CONVECTOR_MODEL_PATH_PREFIX}.participant`;
const CONVECTOR_MODEL_PATH_PERSON: string = `${CONVECTOR_MODEL_PATH_PREFIX}.person`;
const CONVECTOR_MODEL_PATH_ATTRIBUTE: string = `${CONVECTOR_MODEL_PATH_PREFIX}.attribute`;
const CONVECTOR_MODEL_PATH_X509IDENTITY: string = `${CONVECTOR_MODEL_PATH_PREFIX}.x509identity`;
export const appConstants = {
CONVECTOR_MODEL_PATH_PARTICIPANT,
CONVECTOR_MODEL_PATH_PERSON,
CONVECTOR_MODEL_PATH_ATTRIBUTE,
CONVECTOR_MODEL_PATH_X509IDENTITY,
};
now add the common package to all packages in monorepo
# add to all packages (without scope)
$ npx lerna add #convector-rest-sample/common#0.1.0
# to prevent some problems always use same version has in local package
# clean and bootstrap
$ npx lerna clean -y && npx lerna bootstrap
optional can use --scope to add only to desired packages
# add to all packages (with scope)
$ npx lerna add #convector-rest-sample/common#0.1.0 --scope server --no-bootstrap
$ npx lerna add #convector-rest-sample/common#0.1.0 --scope participant-cc --no-bootstrap
$ npx lerna add #convector-rest-sample/common#0.1.0 --scope person-cc --no-bootstrap
# clean and bootstrap
$ npx lerna clean -y && npx lerna bootstrap --hoist
now test #convector-rest-sample/common in server, add this lines to top of packages/server/src/app.ts
to confirm that everything is working has expected
Note: if don't have a server, skip this step right to Use common package inside ChainCode section
import { appConstants as c } from '#convector-rest-sample/common';
debugger;
Logger.log(JSON.stringify(c, undefined, 2));
now launch server with debugger, and inspect c object or view log result
$ npx lerna run start:debug --scope server --stream
if outputs appConstants we are ready to go, and common package works has expected
Use common package inside ChainCode
To use common package inside chaincode, is very tricky, and I lost a few hours to get it work, thanks to Walter and Diego from WorldSibu I get it.
The problem is that currently, in convector there is no easy way to use packages, that are not controllers, for this to work we must create a fake controller in #convector-rest-sample/common to put it to work
first install required controller dependency in our #convector-rest-sample/common, this is required ut use { Controller, Invokable }
# install dependency
$ npx lerna add #worldsibu/convector-core --scope #convector-rest-sample/common
next we must create a fake controller in packages/common/src/common.controller.ts
import { Controller, Invokable } from '#worldsibu/convector-core';
#Controller('common')
export class CommonController {
#Invokable()
public async greeting() {
return 'Hello from CommonController';
}
}
don't forget to add export * from './common.controller'; to packages/common/src/index.ts to export controller
export * from './constants';
export * from './common.controller';
after that we must change chaincode.config.json to add the fake controller, this is a hell of a hack, we use a fake controller to force the #convector-rest-sample/common to be copied inside chaincode-person dir, without this, the #convector-rest-sample/common is not copied and we have a broken chain code when we try deploy it with cc:start or cc:upgrade it always show the annoying error npm ERR! 404 Not Found: #convector-rest-sample/common#0.1.0
first change chaincode.config.json
Tip: if don't have it in project, copy org1.person.config.json to chaincode.config.json and move on....
in my case I have only the legacy files org1.participant.config.json, org1.person.config.json, org2.participant.config.json and org2.person.config.json
this files can be deleted (Diego tip)
ok let's change chaincode.config.json and add another controller above person-cc
"controllers": [
{
"name": "participant-cc",
"version": "file:./packages/participant-cc",
"controller": "ParticipantController"
},
{
"name": "person-cc",
"version": "file:./packages/person-cc",
"controller": "PersonController"
},
// BO : ADD THIS
{
"name": "#convector-rest-sample/common",
"version": "file:./packages/common",
"controller": "CommonController"
}
// EO : ADD THIS
],
Note: this is another clever tricky part, the name is the package name, like the one we use in imports, version is the path location inside of our build chaincode-person
before build chaincode we must change our models to use the new common constants from #convector-rest-sample/common ex c.CONVECTOR_MODEL_PATH_X509IDENTITY, currently this common package only use simple constants, to keep it simple, the point is created common logic for all the packages, rest-server, front-end, packages-cc, cli-tools, etc
packages/participant-cc/src/participant.model.ts
import { appConstants as c } from '#convector-rest-sample/common';
...
export class x509Identities extends ConvectorModel<x509Identities>{
#ReadOnly()
public readonly type = c.CONVECTOR_MODEL_PATH_X509IDENTITY;
...
export class Participant extends ConvectorModel<Participant> {
#ReadOnly()
public readonly type = c.CONVECTOR_MODEL_PATH_PARTICIPANT;
...
packages/person-cc/src/person.model.ts
import { appConstants as c } from '#convector-rest-sample/common';
...
export class Attribute extends ConvectorModel<Attribute>{
#ReadOnly()
#Required()
public readonly type = c.CONVECTOR_MODEL_PATH_ATTRIBUTE;
...
export class Person extends ConvectorModel<Person> {
#ReadOnly()
#Required()
public readonly type = 'io.worldsibu.example.person';
...
now we can cc:package the chaincode chaincode-person, this will package the chaincode with our #convector-rest-sample/common inside it with packages person-cc and participant-cc using our #convector-rest-sample/common constants
# first remove chaincode (optional)
$ rm chaincode-person -r
# now manually invoke package command
$ npm run cc:package -- person org1
after package our chaincode is advised to check if common package is copied to chaincode-person folder to the right path
$ ls -la chaincode-person/packages
chaincode-person/packages/#convector-rest-sample
chaincode-person/packages/participant-cc
chaincode-person/packages/person-cc
another good practice is check if inside chaincode-person folder, in file chaincode-person/package.json, if our #convector-rest-sample/common was added to dependencies, in above json block we can see cc:package script add line "#convector-rest-sample/common": "file:./package/#convector-rest-sample/common", this is created based on our changes in chaincode.config.json remember, when we add the fake controller
"dependencies": {
"#theledger/fabric-chaincode-utils": "^4.0.1",
"#worldsibu/convector-core": "^1.3.3",
"#worldsibu/convector-storage-stub": "^1.3.3",
"reflect-metadata": "^0.1.12",
"tslib": "^1.9.0",
"participant-cc": "file:./packages/participant-cc",
"person-cc": "file:./packages/person-cc",
// BO: magic line here
"#convector-rest-sample/common": "file:./packages/#convector-rest-sample/common"
// EO: magic line here
},
done now we can deploy our chaincode with cc:start or cc:upgrade
Tip: if is first time use cc:start, if not use cc:package
to check that everything is fine from start, we restart our hyperledger stack, and start from the beginning, warning it destroy all data, if don't want to destroy data don't fire npm run env:restart and use cc:upgrade, more above
# this recreate environment and destroy all data
$ npm run env:restart
$ npm run cc:start -- person
# seed ledger
$ npm run seed
# create couchdb views
$ ./views/install.sh
# invoke person_create
$ npx hurl invoke person person_create "{ \"id\": \"1-100-103\", \"firstname\": \"Pete\", \"lastname\": \"Doe\", \"username\": \"pete\", \"password\": \"12345678\", \"email\": \"pete.doe#example.com\"}" -u admin
# invoke some stuff (wait for first invoke finish)
$ npx hurl invoke person person_getAll
done, everything is working has expected and we have a #convector-rest-sample/common package implemented.
if we check couchdb 1-100-103 person, we can check that is using type "type": "io.worldsibu.examples.person" that comes from our constants in our #convector-rest-sample/common, proving that it gets its value from #convector-rest-sample/common, believe me, if it won't wont find #convector-rest-sample/common it crash.....simple
for future changes in chaincode, upgrade it with above command
# upgrade chaincode
$ npm run cc:upgrade -- person 1.1
we are done........
Use scripts to copy other files to chaincode
another thing that I tried to hack before find the solution, is using npm scripts but it won't work because we need the modified chaincode-person/package.json with "#convector-rest-sample/common": "file:./packages/#convector-rest-sample/common" in the dependencies, but I try it......
leave it here, maybe can be useful for other kind of stuff, like copy other type of stuff
{
"scripts": {
...
"cc:package": "f() { npm run lerna:build; chaincode-manager --update --config ./$2.$1.config.json --output ./chaincode-$1 package; npm run copy:indexes -- $1; npm run copy:package:common -- $1; }; f",
...
"copy:package:common": "f () { mkdir -p ./chaincode-$1/node_modules/#convector-rest-sample/; cp -r ./packages/common/ ./chaincode-$1/node_modules/#convector-rest-sample/; }; f"
...
note for npm run copy:package:common -- $1; in "cc:package", and cp -r ./packages/common/ ./chaincode-$1/node_modules/#convector-rest-sample/; }; in "copy:package:common", it works, but won't modify chaincode-person/package.json with lines
"dependencies": {
"#theledger/fabric-chaincode-utils": "^4.0.1",
"#worldsibu/convector-core": "^1.3.3",
"#worldsibu/convector-storage-stub": "^1.3.3",
"reflect-metadata": "^0.1.12",
"tslib": "^1.9.0",
"participant-cc": "file:./packages/participant-cc",
"person-cc": "file:./packages/person-cc",
// BO: magic line here
"#convector-rest-sample/common": "file:./packages/#convector-rest-sample/common"
// EO: magic line here
}
Clean Up
to finish we can remove the legacy files org1.participant.config.json org1.person.config.json org2.person.config.json org2.participant.config.json, now we use the config file chaincode.config.json (thanks for the tip Diego)
# remove legacy files
rm org1.participant.config.json org1.person.config.json org2.person.config.json org2.participant.config.json
Note: don't forget to update packages.json "cc:package" with new ./chaincode.config.json file
package.json
"cc:package": "f() { npm run lerna:build; chaincode-manager --update --config ./chaincode.config.json --output ./chaincode-$1 package; npm run copy:indexes -- $1; }; f",
to finish thanks to all the kindly people of Covalent
UPDATE: in a fresh project/clone if I try to lerna bootstrap I get the below error
$ lerna bootstrap
src/participant.controller.ts:1:35 - error TS2307: Cannot find module '#convector-sample/common'.
1 import { appConstants as c } from '#convector-sample/common';
this is because the package require build before it's used
# build common library: this is required on fresh clones before lerna boostrap
$ npx lerna run build --scope #convector-sample/common --stream
now it works
$ lerna bootstrap
...
lerna success Bootstrapped 6 packages

Related

What is proper way to store code/functions that are used by both the frontend and backend?

My frontend Reactjs app is stored in one repository.
My backend Node.js app is stored in another repository.
There are some functions used by both. Where should store those functions so that both repositories can access them?
You can create a library that exports all of the functions you'll be needing, then publish it to NPM and add it to the dependencies of both projects' package.json. With NPM you can set your packages as private, too, in case you don't want your code/package to be publicly available.
The starting point would be to create a directory with all the functions you need, export them all in an index.js, and run npm init to create a package.json for your new project. You'll be guided for naming and assigning a version number, then publish with npm publish (you may need to create an account and run npm login first). Then in your frontend and backend projects you simply npm install <your-package> like any other npm package.
Your project directory may be as simple as...
myFunctions.js
index.js
package.json
myFunctions.js:
export const functionA = () => {
return "a"
}
export const functionB = () => {
return "b"
}
index.js:
export * from './myFunctions.js'
package.json (can be created with npm init:
{
"name": "my-functions",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
Then in the directory run npm publish, and in your other projects you can run npm install my-functions.
And finally, in your other projects:
import { functionA } from 'my-functions';
// ...
functionA() // returns "a"
Creating a separate NPM package for your helper functions can certainly be a good solution, but I find them somewhat annoying to maintain across different repositories. I tend to try and avoid them.
There are certainly some functions in your application that do have purpose on both the front- and backend, but I would encourage you to look at these carefully to see if that logic can be the responsibility of one or the other (backend or frontend).
For example; if you have a function to parse a date and format it in a very specific way for your app then you can have that function live solely in the backend and leverage it to pass back the already converted value to the frontend - avoiding the burden of maintaining it in 2 places or in a separate package that then needs to be updated in 2 repositories.
Sometimes there's just no getting around it though, but I found that in most cases I can split them accordingly.

Yarn workspaces -- package alias

TL;DR How can I create an alias for a local yarn workspace dependency?
I've tried yarn workspaces before and never succeeded, and I'm giving it another try.
I've set "workspaces": ["packages/*"] in package.json.
For each package, I decided to use the naming convention #-/package-name to prevent naming conflicts and without worrying about having namespaces for internal packages.
When adding packages as dependencies, I've been following a style where I use an interface name for resolution, but point that towards a concrete implementation. This is what I did before using yarn workspaces:
"dependencies": {
"my-interface-name": "file:some/path/to/packages/some-concrete-implementation"
}
This is basically to allow what I like to call compile-time static dependency injection. And it also means each package can individually name their interface dependencies appropriately to their need and to prevent naming conflicts.
However, I can't figure out how to accomplish this with yarn workspaces. How do I create an alias for my yarn workspaces package #-/some-concrete-implementation called my-interface-name?
What I've already tried with no success:
Defining the dependency like "my-interface-name": "#-/some-concrete-implementation"} - for some reason this causes yarn to look for #-/some-concrete-implementation on the npm registry instead of in the local workspace
I've also tried to use the workspace protocol: "my-interface-name": "workspace:#-/some-concrete-implementation"} but it still looks for the package on the npm registry!
What I haven't yet tried and could work but removes benefits of using yarn workspaces in the first place:
"dependencies": {"my-interface-name": "file:../../node_modules/#-/some-concrete-implementation"}"
Have you seen the resolutions package.json key? Is it what you need?
I've used it for aliasing/overriding external packages but the example in the docs shows it working with local packages.
Resolutions
Allows you to override a version of a particular nested dependency. See the Selective Versions Resolutions RFC for the full spec.
{
"resolutions": {
"transitive-package-1": "0.0.29",
"transitive-package-2": "file:./local-forks/transitive-package-2",
"dependencies-package-1/transitive-package-3": "^2.1.1"
}
}
From the RFC:
"**/a" denotes all the nested dependencies a of the project.
"a" is an alias for **/a (for retro-compatibility, see below, and because if it wasn't such an alias, it wouldn't mean anything as it would represent one of the non-nested project dependencies, which can't be overridden as explained below).
So, I believe the rule you need is:
"**/my-interface-name": "file:some/path/to/packages/some-concrete-implementation"
// OR equivalent
"my-interface-name": "file:some/path/to/packages/some-concrete-implementation"
I believe it works in the package's package.json. Worst case you can hoist it to the workspace root and make the rule specific to the workspace e.g. "a/b".
The workspace: alias protocol (available in pnpm too) seems the direction to take.
I've also tried to use the workspace protocol: "my-interface-name": "workspace:#-/some-concrete-implementation"} but it still looks for the package on the npm registry!
Be sure to have yarn 3 installed, otherwise you'll run into weird issues.
Note that the syntax of "my-interface-name": "workspace:#-/some-concrete-implementation" looks incorrect.
It should be "#xxx/some-concrete-implementation": "workspace:*", assuming the name of linked the package is "name": "#xxx/some-concrete-implementation".
With this in mind you don't even need to create a specific #-/name. With workspace protocol, yarn will ensure it's never downloaded from npm. It becomes an internal workspace dependency.
PS:
Yarn 3 installation
Generally a simple yarn set version 3.0.2 && yarn plugin import workspace-tools) will work.
To avoid pnp current limitation, check the generated config .yarnrc.yml and ensure nmLinker is set to 'node-modules'
# Yarn 2+ supports pnp or regular node_modules installs. Use node-modules one.
nodeLinker: node-modules
plugins:
- path: .yarn/plugins/#yarnpkg/plugin-workspace-tools.cjs
spec: "#yarnpkg/plugin-workspace-tools"
yarnPath: .yarn/releases/yarn-3.0.2.cjs
PS: you might want to add this to .gitignore too
.yarn/*
!.yarn/patches
!.yarn/releases
!.yarn/plugins
!.yarn/sdks
!.yarn/versions
.pnp.*
Run a yarn install just after.
About package.json's
Like you did, the root package.json will define the workspace paths:
{
"name": "monorepo",
"workspaces": [
"packages/*" // Enable package discovery in packages/* directory.
],
// ...
"devDependencies": {
"husky": "7.0.2", // Only what's needed for monorepo management
}
In your app packages/app/package.json
{
"name": "my-app",
"devDependencies": {
"#types/node": "16.10.1",
//...
},
"dependencies": {
// Assuming the name of packages/shared is "#your-org/some-concrete-implementation",
// we explicitly declare the dependency on it through
// workspace: alias (package-manager perspective)
"#your-org/some-concrete-implementation": "workspace:*",
}
}
You consumed package should declare the same name
{
"name": "#your-org/some-concrete-implementation",
}
Bonus: Typescript aliases
If your project is written in ts, you can even replicate your paths through
typescript path mapping. It will allow to include the files just as is (no prior compilation needed).
Following your example, just edit a ./packages/xxx/tsconfig.json in this way
{
"compilerOptions": {
// here baseUrl is set at ./src (good practice), can
// be set to '.'
"baseUrl": "./src",
"paths": {
// Declare deps here (keep them in sync with what
// you defined in the package.json)
// PS: path are relative to baseUrl
"#your-org/some-concrete-implementation/*": ["../../some-concrete-implementation/src/*"],
// if you have a barrel in ui-lib
"#your-org/some-concrete-implementation": ["../../some-concrete-implementation/src/index"],
}
},
}
PS: for non typescript: babel/plugin-module-resolver can be used in a similar manner.

Add git information to create-react-app

In development, I want to be able to see the build information (git commit hash, author, last commit message, etc) from the web. I have tried:
use child_process to execute a git command line, and read the result (Does not work because browser environment)
generate a buildInfo.txt file during npm build and read from the file (Does not work because fs is also unavailable in browser environment)
use external libraries such as "git-rev"
The only thing left to do seems to be doing npm run eject and applying https://www.npmjs.com/package/git-revision-webpack-plugin , but I really don't want to eject out of create-react-app. Anyone got any ideas?
On a slight tangent (no need to eject and works in develop),
this may be of help to other folk looking to add their current git commit SHA into their index.html as a meta-tag by adding:
REACT_APP_GIT_SHA=`git rev-parse --short HEAD`
to the build script in the package.json and then adding (note it MUST start with REACT_APP... or it will be ignored):
<meta name="ui-version" content="%REACT_APP_GIT_SHA%">
into the index.html in the public folder.
Within react components, do it like this:
<Component>{process.env.REACT_APP_GIT_SHA}</Component>
I created another option inspired by Yifei Xu's response that utilizes es6 modules with create-react-app. This option creates a javascript file and imports it as a constant inside of the build files. While having it as a text file makes it easy to update, this option ensures it is a js file packaged into the javascript bundle. The name of this file is _git_commit.js
package.json scripts:
"git-info": "echo export default \"{\\\"logMessage\\\": \\\"$(git log -1 --oneline)\\\"}\" > src/_git_commit.js",
"precommit": "lint-staged",
"start": "yarn git-info; react-scripts start",
"build": "yarn git-info; react-scripts build",
A sample component that consumes this commit message:
import React from 'react';
/**
* This is the commit message of the last commit before building or running this project
* #see ./package.json git-info for how to generate this commit
*/
import GitCommit from './_git_commit';
const VersionComponent = () => (
<div>
<h1>Git Log: {GitCommit.logMessage}</h1>
</div>
);
export default VersionComponent;
Note that this will automatically put your commit message in the javascript bundle, so do ensure no secure information is ever entered into the commit message. I also add the created _git_commit.js to .gitignore so it's not checked in (and creates a crazy git commit loop).
It was impossible to be able to do this without ejecting until Create React App 2.0 (Release Notes) which brought with it automatic configuration of Babel Plugin Macros which run at compile time. To make the job simpler for everyone, I wrote one of those macros and published an NPM package that you can import to get git information into your React pages: https://www.npmjs.com/package/react-git-info
With it, you can do it like this:
import GitInfo from 'react-git-info/macro';
const gitInfo = GitInfo();
...
render() {
return (
<p>{gitInfo.commit.hash}</p>
);
}
The project README has some more information. You can also see a live demo of the package working here.
So, turns out there is no way to achieve this without ejecting, so the workaround I used is:
1) in package.json, define a script "git-info": "git log -1 --oneline > src/static/gitInfo.txt"
2) add npm run git-info for both start and build
3) In the config js file (or whenever you need the git info), i have
const data = require('static/gitInfo.txt')
fetch(data).then(result => {
return result.text()
})
My approach is slightly different from #uidevthing's answer. I don't want to pollute package.json file with environment variable settings.
You simply have to run another script that save those environment variables into .env file at the project root. That's it.
In the example below, I'll use typescript but it should be trivial to convert to javascript anyway.
package.json
If you use javascript it's node scripts/start.js
...
"start": "ts-node scripts/start.ts && react-scripts start",
scripts/start.ts
Create a new script file scripts/start.ts
const childProcess = require("child_process");
const fs = require("fs");
function writeToEnv(key: string = "", value: string = "") {
const empty = key === "" && value === "";
if (empty) {
fs.writeFile(".env", "", () => {});
} else {
fs.appendFile(".env", `${key}='${value.trim()}'\n`, (err) => {
if (err) console.log(err);
});
}
}
// reset .env file
writeToEnv();
childProcess.exec("git rev-parse --abbrev-ref HEAD", (err, stdout) => {
writeToEnv("REACT_APP_GIT_BRANCH", stdout);
});
childProcess.exec("git rev-parse --short HEAD", (err, stdout) => {
writeToEnv("REACT_APP_GIT_SHA", stdout);
});
// trick typescript to think it's a module
// https://stackoverflow.com/a/56577324/9449426
export {};
The code above will setup environment variables and save them to .env file at the root folder. They must start with REACT_APP_. React script then automatically reads .env at build time and then defines them in process.env.
App.tsx
...
console.log('REACT_APP_GIT_BRANCH', process.env.REACT_APP_GIT_BRANCH)
console.log('REACT_APP_GIT_SHA', process.env.REACT_APP_GIT_SHA)
Result
REACT_APP_GIT_BRANCH master
REACT_APP_GIT_SHA 042bbc6
More references:
https://create-react-app.dev/docs/adding-custom-environment-variables/#adding-development-environment-variables-in-env
If your package.json scripts are always executed in a unix environment you can achieve the same as in #NearHuscarl answer, but with fewer lines of code by initializing your .env dotenv file from a shell script. The generated .env is then picked up by the react-scripts in the subsequent step.
"scripts": {
"start": "sh ./env.sh && react-scripts start"
"build": "sh ./env.sh && react-scripts build",
}
where .env.sh is placed in your project root and contains code similar to the one below to override you .env file content on each build or start.
{
echo BROWSER=none
echo REACT_APP_FOO=bar
echo REACT_APP_VERSION=$(git rev-parse --short HEAD)
echo REACT_APP_APP_BUILD_DATE=$(date)
# ...
} > .env
Since the .env is overridden on each build, you may consider putting it on the .gitignore list to avoid too much noise in your commit diffs.
Again the disclaimer: This solution only works for environments where a bourne shell interpreter or similar exists, i.e. unix.
You can easily inject your git information like commit hash into your index.html using CRACO and craco-interpolate-html-plugin. Such way you won't have to use yarn eject and it also works for development server environment along with production builds.
After installing CRACO the following config in craco.config.js worked for me:
const interpolateHtml = require('craco-interpolate-html-plugin');
module.exports = {
plugins: [
{
plugin: interpolateHtml,
// Enter the variable to be interpolated in the html file
options: {
BUILD_VERSION: require('child_process')
.execSync('git rev-parse HEAD', { cwd: __dirname })
.toString().trim(),
},
},
],
};
and in your index.html:
<meta name="build-version" content="%BUILD_VERSION%" />
Here are the lines of code to add in package.json to make it all work:
"scripts": {
"start": "craco start",
"build": "craco build"
}

How to define dependency in package.json for gitrepository?

In my reactjs project I have a dependency on a module that is in another gitrepo(stash) so I created this in my package.json as a property of dependencies :
"somemodule":{
"name": "somemodule",
"dependencies": {
"private-repo": "https://link_to_the_other_repo"
}
}
This repo contains a minified version and non minified version of the same file. When I run npm install the module does not get installed, there is no error message either. How can I create this dependency?
I have managed to get the dist/minified version of the file from the other repo. How can I refer to this in my package.json?
To add a private git module:
If you have RSA key added to your machine(secure & recommended):
"dependencies": {
"package-name": "git+ssh://git#github.com/project/repo.git"
}
If you want https:
"dependencies": {
"package-name": "git+https://username:password#github.com/project/repo.git"
}
Its not recommended to store username, password in your package.json file, so better add ssh keygen to your machine and use the first one.

Is it possible to access npm config settings from a node package's `bin` command?

I want to add some default configuration into a shell tool that the user can override using npm config. It looks like this is possible but this only works for npm scripts not my package's binary as specified by "bin" below.
Package.json
{
"name": "tmp",
"scripts": {
"test": "node index.js"
},
"bin" : {
"tmp" : "index.js"
},
"config": {
"foo" : 123
}
}
index.js
#!/usr/bin/env node
console.log(process.env.npm_package_config_foo);
My attempts
calling binary
setup for this is running npm link in the package to make a global link to that package
$ tmp
undefined
passing script to node
$ node ./index.js
undefined
calling as npm script ⫸ only this one works
$ npm test
> tmp# test /private/tmp
> node index.js
123
Question
How can I access these config values in a way that's overridable by the user like npm config allows? (To be clear I want to do it the npm config way; I know there are other ways to skin this particular cat.)
You can use a package called npm-conf with documentation at https://github.com/kevva/npm-conf.
First you need to install it:
npm install --save npm-conf
Usage is as follows:
const npmConf = require('npm-conf');
const conf = npmConf();
conf.get('prefix')
//=> //=> /Users/unicorn/.npm-packages
conf.get('registry')
//=> https://registry.npmjs.org/
If you want to get a list of all available NPM configuration options:
npm config list -l

Resources