Skip to content
This repository was archived by the owner on Oct 10, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
67 changes: 67 additions & 0 deletions .github/workflows/create-package-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: Create Package Release

on:
workflow_dispatch:
inputs:
versionType:
type: choice
description: 'Release Type'
options:
- minor
- major
required: true

jobs:
create-release:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
with:
ref: main

- name: Setup Git
run: |
git config --global user.name 'GitHub Actions'
git config --global user.email 'actions@github.com'

## Update the package json to either a major or minor version depending on the selection

- name: Upgrade yarn with major version
if: ${{ github.event.inputs.versionType == 'major' }}
run: |
yarn version --major --no-git-tag-version
git add package.json

- name: Upgrade yarn with minor version
if: ${{ github.event.inputs.versionType == 'minor' }}
run: |
yarn version --minor --no-git-tag-version
git add package.json

# Create release branch off of main with the new version number in the branch name
- name: Create Release Branch
run: |
NEW_VERSION=$(npm pkg get version | tr -d '"') # Trim quotes wrapping command output
TIMESTAMP=$(date +'%Y%m%d%H%M%S')
BRANCH_NAME="release-changes-${NEW_VERSION}-${TIMESTAMP}"
git checkout -b $BRANCH_NAME
echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_ENV
echo "NEW_VERSION=$NEW_VERSION" >> $GITHUB_ENV

# Commit the changes of the package.json version increment. Format the commit message to be Release x.y.z so it's picked up by the publish action.
- name: Commit Release Changes
run: |
git commit -m "Release ${{ env.NEW_VERSION }}"
git push origin HEAD

# Create a pull request so it can be merged back into main.
- name: Create Pull Request
uses: repo-sync/pull-request@v2
with:
source_branch: ${{ env.BRANCH_NAME }}
destination_branch: "main"
github_token: ${{ secrets.GITHUB_TOKEN }}
pr_title: "Release CLI: Version ${{ env.NEW_VERSION }}"
pr_body: "Automated pull request to release the latest version of the CLI."
pr_label: "automated-pr"
68 changes: 46 additions & 22 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,7 @@ import { info } from "./scripts/info.js";
import { removeModules } from "./scripts/remove.js";
import { commitModules } from "./scripts/commit-module.js";
import { upgradeScaffold } from "./scripts/upgrade.js";
import {
valid,
invalid,
isNameValid,
section,
isUserEnvironment
} from "./utils.js";
import { valid, invalid, section, isUserEnvironment } from "./utils.js";
import { createModule } from "./scripts/create.js";
import { login } from "./scripts/login.js";
import { configFile } from "./scripts/utils/configFile.js";
Expand All @@ -51,6 +45,7 @@ import { HAS_ASKED_OPT_IN_NAME } from "./scripts/analytics/config.js";
import { EVENT } from "./scripts/analytics/constants.js";
import { askOptIn } from "./scripts/analytics/scripts.js";
import { sentryMonitoring } from "./scripts/utils/sentry.js";
import { setModuleDetails } from "./scripts/setModuleDetails.js";

const pkg = JSON.parse(
fs.readFileSync(new URL("package.json", import.meta.url), "utf8")
Expand Down Expand Up @@ -182,27 +177,24 @@ const commands = {
const args = arg({
"--name": String,
"--type": String,
"--target": String
"--target": String,
"--search-description": String,
"--acceptance-criteria": String
});

if (!args["--name"]) {
invalid("missing required argument: --name");
}
if (!args["--type"]) {
invalid("missing required argument: --type");
}
if (!isNameValid(args["--name"])) {
invalid(
`invalid module name provided: '${args["--name"]}'. Use only alphanumeric characters, dashes and underscores.`
);
}

analytics.sendEvent({
name: EVENT.CREATE_MODULE,
properties: { Name: args["--name"] }
});

createModule(args["--name"], args["--type"], args["--target"], gitRoot());
createModule(
args["--name"],
args["--type"],
args["--target"],
args["--search-description"],
args["--acceptance-criteria"],
gitRoot()
);
},
commit: () => {
const args = arg({
Expand Down Expand Up @@ -312,7 +304,11 @@ demo`;
"--visibility": String,
"--status": String,
"--page": String,
"--unarchive": Boolean
"--unarchive": Boolean,
"--name": String,
"--description": String,
"--acceptance-criteria": String,
"--search-description": String
});

let id;
Expand Down Expand Up @@ -347,6 +343,24 @@ demo`;
await modulesGet(id);
break;

case "set":
id = args._[2];

if (!id) {
return invalid(
"Please provide the id of the module to change info for, i.e. modules set <123>"
);
}

await setModuleDetails(id,
args["--name"],
args["--description"],
args["--acceptance-criteria"],
args["--search-description"]
);

break;

case "archive":
id = args._[2];
if (!id) {
Expand Down Expand Up @@ -418,6 +432,9 @@ Commands available:
demo Generate a local React Native and Django demo app
add Install a module in the demo app
remove Remove a module from the demo app
get Get information about a module by id
set Set information about a module by id such as name, description, acceptance criteria, and search description. The new values must be wrapped in quotes "<value>".
create Create a new module of a given type
create Create a new module of a given type
commit Update an existing module from the demo source code
init Initialize a blank modules repository
Expand Down Expand Up @@ -456,6 +473,13 @@ Install one or modules to your demo app:
Remove one or modules from your demo app:
cb remove <module-name> <module-name-2>

Get information about a module by id:
cb modules get <module-id>

Set information about a module by id such as name, description, acceptance criteria, and search description:
cb modules set <module-id> --name "<name>" --description "<description>" --acceptance-criteria "<acceptance-criteria>" --search-description "<search-description>"
The new values must be wrapped in quotes "<value>".

Install modules from other directory:
cb add --source ../other-repository <module-name>

Expand Down
105 changes: 102 additions & 3 deletions scripts/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
generateMeta
} from "./utils/templates.js";
import { execOptions, configurePython } from "./utils/environment.js";
import inquirer from "inquirer";

function generateRNFiles(base, name, relative = "/") {
if (relative !== "/") {
Expand Down Expand Up @@ -45,7 +46,10 @@ function generateDjangoFiles(base, name, relative = "/") {
);

const appsFileData = fs.readFileSync(`${innerAppPath}/apps.py`, "utf8");
const result = appsFileData.replace(/name = '.*'/, `name = 'modules.django_${sanitizedName}.${sanitizedName}'`);
const result = appsFileData.replace(
/name = '.*'/,
`name = 'modules.django_${sanitizedName}.${sanitizedName}'`
);
fs.writeFileSync(`${innerAppPath}/apps.py`, result, "utf8");

fs.writeFileSync(
Expand All @@ -60,7 +64,102 @@ function generateDjangoFiles(base, name, relative = "/") {
);
}

export function createModule(name, type, target, gitRoot) {
const isNameValid = (name) => {
const pattern = /^[a-zA-Z][a-zA-Z0-9_-]*$/;
return pattern.test(name);
};

const getValidModuleInputs = async (
initialName,
initialType,
initialSearchDescription,
initialAcceptanceCriteria
) => {
let name, type, searchDescription, acceptanceCriteria;

if (initialName) {
name = initialName;
} else {
const { inputName } = await inquirer.prompt({
message: "Module Name:",
name: "inputName",
type: "input"
});

name = inputName;
}

if (initialType) {
type = initialType;
} else {
const { inputType } = await inquirer.prompt({
message: "Module Type:",
name: "inputType",
type: "list",
choices: ["all", "react-native", "django"]
});

type = inputType;
}

if (!name) {
invalid("missing required argument: --name");
}
if (!type) {
invalid("missing required argument: --type");
}
if (!isNameValid(name)) {
invalid(
`invalid module name provided: '${name}'. Use only alphanumeric characters, dashes and underscores.`
);
}

if (!initialName) {
section(
"The following fields help Crowdbotics match this module to application features. Please enter the following values (these can be updated later in the meta.json file, or in the Crowdbotics platform):"
);
const { inputSearchDescription, inputAcceptanceCriteria } =
await inquirer.prompt([
{
message: "Search Description:",
name: "inputSearchDescription",
type: "input",
default: initialSearchDescription || undefined
},
{
message: "Acceptance Criteria:",
name: "inputAcceptanceCriteria",
type: "input",
default: initialAcceptanceCriteria || undefined
}
]);

searchDescription = inputSearchDescription;
acceptanceCriteria = inputAcceptanceCriteria;
} else {
searchDescription = initialSearchDescription;
acceptanceCriteria = initialAcceptanceCriteria;
}

return { name, type, searchDescription, acceptanceCriteria };
};

export async function createModule(
initialName,
initialType,
target,
initialSearchDescription,
initialAcceptanceCriteria,
gitRoot
) {
const { name, type, searchDescription, acceptanceCriteria } =
await getValidModuleInputs(
initialName,
initialType,
initialSearchDescription,
initialAcceptanceCriteria
);

const cwd = process.cwd();

if (target) {
Expand All @@ -83,7 +182,7 @@ export function createModule(name, type, target, gitRoot) {
const dir = path.join(target, slug);
if (existsSync(dir)) invalid(`module named "${slug}" already exists`);

const meta = generateMeta(name, type);
const meta = generateMeta(name, type, searchDescription, acceptanceCriteria);

try {
fs.mkdirSync(dir, { recursive: true });
Expand Down
42 changes: 42 additions & 0 deletions scripts/setModuleDetails.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import ora from "ora";
import { invalid, valid } from "../utils.js";
import { apiClient } from "./utils/apiClient.js";

export const setModuleDetails = async (
id, name, description, searchDescription, acceptanceCriteria
) => {
const patchBody = {};

if (name) {
patchBody.title = name;
}
if (description) {
patchBody.description = description;
}
if (searchDescription) {
patchBody.search_description = searchDescription;
}
if (acceptanceCriteria) {
patchBody.acceptance_criteria = acceptanceCriteria;
}
if (Object.keys(patchBody).length === 0) {
invalid("No module details were provided for the update. To correctly save the new value, please enclose it in double quotes. For example, use --description \"Your detailed description here\".");
return;
}
const patchSpinner = ora(
"Updating module details."
).start();

const patchResponse = await apiClient.patch({
path: `/v1/catalog/module/${id}`,
body: patchBody
}).then(patchSpinner.stop());

if (patchResponse.ok) {
valid(`Module details updated for ${id}.`);
} else if (patchResponse.status === 404) {
invalid(`Cannot find requested module with id ${id}.`);
} else {
invalid("Unable to update modules details. Please try again later.");
}
};
9 changes: 8 additions & 1 deletion scripts/utils/templates.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,12 @@ export const packageJson = (name) => `{
/**
* Miscellaneous
*/
export function generateMeta(name, type) {
export function generateMeta(
name,
type,
searchDescription,
acceptanceCriteria
) {
const rootMap = {
all: "/",
"react-native": `/modules/${name}`,
Expand All @@ -78,6 +83,8 @@ export function generateMeta(name, type) {
const meta = {
title: name,
description: "",
search_description: searchDescription || "",
acceptance_criteria: acceptanceCriteria || "",
root: rootMap[type],
schema: {}
};
Expand Down
Loading