Skip to content
This repository was archived by the owner on Jun 11, 2026. It is now read-only.

Commit 526ee97

Browse files
authored
chore: add create-functionless package (#554)
1 parent af29b8b commit 526ee97

28 files changed

Lines changed: 1403 additions & 54 deletions

.github/workflows/release.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,6 @@ jobs:
4242
run: yarn lerna publish --yes
4343
env:
4444
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
45+
46+
- name: Smoke test
47+
run: yarn test:smoke

lerna.json

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
"npmClient": "yarn",
44
"useWorkspaces": true,
55
"packages": [
6-
"packages/*"
6+
"packages/create-functionless",
7+
"packages/functionless",
8+
"packages/@functionless/*"
79
],
810
"conventionalCommits": true,
911
"commitHooks": false,
@@ -13,10 +15,7 @@
1315
"command": {
1416
"publish": {
1517
"npmClient": "npm",
16-
"allowBranch": [
17-
"main",
18-
"*"
19-
]
18+
"allowBranch": ["main", "*"]
2019
}
2120
},
2221
"version": "0.27.2"
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules/
2+
dist/
3+
.test/
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# create-functionless
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/usr/bin/env bash
2+
3+
set -ue pipefail
4+
5+
TEST_PROJECT="test-devx"
6+
7+
# Use the create script to create a new project
8+
create-functionless ${TEST_PROJECT} -t devx-experimental
9+
cd ${TEST_PROJECT}
10+
11+
# Verify new project can synth
12+
npm run synth
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/usr/bin/env bash
2+
3+
set -ue pipefail
4+
5+
TEST_PROJECT="test-npm"
6+
7+
# Use the create script to create a new project
8+
create-functionless ${TEST_PROJECT}
9+
cd ${TEST_PROJECT}
10+
11+
# Verify new project can synth
12+
npm run synth
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/usr/bin/env bash
2+
3+
set -ue pipefail
4+
5+
TEST_PROJECT="test-project"
6+
7+
# Use the create script to create a new project
8+
yarn create --offline functionless ${TEST_PROJECT}
9+
cd ${TEST_PROJECT}
10+
11+
# Verify new project can synth
12+
yarn synth
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#!/usr/bin/env bash
2+
3+
set -ue pipefail
4+
5+
rm -rf .test
6+
7+
CWD=$(pwd)
8+
9+
PACKAGE_NAME="create-functionless"
10+
PACKED_NAME="${PACKAGE_NAME}.tgz"
11+
12+
mkdir -p .test
13+
14+
function test_case() {
15+
16+
cd ${CWD}/.test
17+
${CWD}/bin/$1.sh
18+
}
19+
20+
function clean_up() {
21+
cd ${CWD}
22+
rm -rf .test
23+
}
24+
25+
trap clean_up EXIT
26+
27+
test_case test-npm
28+
29+
test_case test-yarn
30+
31+
test_case test-devx
32+
33+
rm -rf .test
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
{
2+
"name": "create-functionless",
3+
"version": "0.0.0",
4+
"description": "Create a Functionless app.",
5+
"files": [
6+
"dist",
7+
"templates/**/*"
8+
],
9+
"bin": {
10+
"create-functionless": "./dist/index.js"
11+
},
12+
"scripts": {
13+
"build": "esbuild --bundle src/index.ts --outfile=dist/index.js --platform=node",
14+
"test:smoke": "./bin/test.sh",
15+
"typecheck": "tsc --noEmit"
16+
},
17+
"keywords": [
18+
"functionless"
19+
],
20+
"author": "Functionless",
21+
"license": "Apache-2.0",
22+
"devDependencies": {
23+
"@types/cross-spawn": "^6.0.2",
24+
"@types/mustache": "^4.2.1",
25+
"@types/node": "^16",
26+
"@types/prompts": "^2.0.14",
27+
"@types/uuid": "^8.3.4",
28+
"@types/validate-npm-package-name": "^4.0.0",
29+
"aws-cdk-lib": "^2.40.0",
30+
"chalk": "^5.0.1",
31+
"commander": "^9.4.0",
32+
"cross-spawn": "^7.0.3",
33+
"esbuild": "0.15.9",
34+
"fl-exp": "*",
35+
"functionless": "*",
36+
"mustache": "^4.2.0",
37+
"prompts": "^2.4.2",
38+
"rimraf": "^3.0.2",
39+
"typescript": "^4.8.3",
40+
"uuid": "^9.0.0",
41+
"validate-npm-package-name": "^4.0.0"
42+
},
43+
"publishConfig": {
44+
"access": "public"
45+
}
46+
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
#!/usr/bin/env node
2+
import chalk from "chalk";
3+
import { Command } from "commander";
4+
import spawn from "cross-spawn";
5+
import fs from "fs";
6+
import mustache from "mustache";
7+
import path from "path";
8+
9+
const packageJson = require("../package.json");
10+
import { loadTemplatePlan } from "./plan";
11+
import {
12+
askProjectName,
13+
failOnError,
14+
getPackageManager,
15+
installPackages,
16+
} from "./util";
17+
18+
const program = new Command();
19+
20+
let projectName: string | undefined;
21+
let templateName = "default";
22+
23+
program
24+
.name(packageJson.name)
25+
.version(packageJson.version)
26+
.arguments("[projectName]")
27+
.option(
28+
"-t --template [templateName]",
29+
"name of the template to use [default]"
30+
)
31+
.action((name, options) => {
32+
if (typeof name === "string") {
33+
projectName = name;
34+
}
35+
if (typeof options.template === "string") {
36+
templateName = options.template;
37+
}
38+
})
39+
.parse(process.argv);
40+
41+
run().catch((err) => {
42+
console.error(err);
43+
process.exit(1);
44+
});
45+
46+
async function run() {
47+
const packageMangager = getPackageManager();
48+
49+
const templatePlan = await loadTemplatePlan(templateName);
50+
51+
if (!projectName) {
52+
projectName = await askProjectName();
53+
}
54+
55+
console.log();
56+
console.log(`Creating ${chalk.green(projectName)}...`);
57+
58+
const root = path.resolve(projectName);
59+
60+
try {
61+
await fs.promises.mkdir(root);
62+
} catch (err: any) {
63+
if (err.code === "EEXIST") {
64+
console.error(`${chalk.red(`Folder already exists: ${projectName}`)}`);
65+
}
66+
process.exit(1);
67+
}
68+
69+
const renderTemplate = async (
70+
sourcePath: string,
71+
localPath: string,
72+
data: Record<string, unknown>
73+
) => {
74+
const templateContent = mustache.render(
75+
await fs.promises.readFile(sourcePath, "utf-8"),
76+
data
77+
);
78+
// Npm won't include `.gitignore` files in a package.
79+
// This allows you to add .template as a file ending
80+
// and it will be removed when rendered in the end
81+
// project.
82+
const destinationPath = path.join(root, localPath.replace(".template", ""));
83+
await fs.promises.mkdir(path.dirname(destinationPath), {
84+
recursive: true,
85+
});
86+
await fs.promises.writeFile(destinationPath, templateContent);
87+
};
88+
89+
const templateData = {
90+
projectName,
91+
};
92+
93+
for (const mapping of templatePlan.fileMappings) {
94+
// Sequential because order of file rendering matters
95+
// for templates that extend other templates.
96+
await renderTemplate(mapping.sourcePath, mapping.localPath, templateData);
97+
}
98+
99+
process.chdir(root);
100+
101+
console.log();
102+
console.log("Installing packages...");
103+
console.log();
104+
105+
installPackages(packageMangager, templatePlan.devDependencies);
106+
107+
console.log();
108+
console.log("Initializing git repository...");
109+
console.log();
110+
111+
const gitErrorMessage = "Error initializing git repository.";
112+
113+
failOnError(
114+
spawn.sync("git", ["init", "-q"], {
115+
stdio: "inherit",
116+
}),
117+
gitErrorMessage
118+
);
119+
120+
failOnError(
121+
spawn.sync("git", ["add", "."], {
122+
stdio: "inherit",
123+
}),
124+
gitErrorMessage
125+
);
126+
127+
failOnError(
128+
spawn.sync("git", ["commit", "-q", "-m", "initial commit"], {
129+
stdio: "inherit",
130+
}),
131+
gitErrorMessage
132+
);
133+
134+
console.log(chalk.green("Project ready!"));
135+
console.log();
136+
console.log(`Run ${chalk.yellow(`cd ./${projectName}`)} to get started.`);
137+
138+
process.exit(0);
139+
}

0 commit comments

Comments
 (0)