@faable/faable 1.5.26 → 1.5.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { exec } from 'child_process';
|
|
2
|
+
|
|
3
|
+
// Quiet git runner: returns trimmed stdout, or undefined on any failure (not a
|
|
4
|
+
// git repo, git missing, etc.). A deploy must never fail because we couldn't
|
|
5
|
+
// read git metadata, so errors are swallowed and the field is just omitted.
|
|
6
|
+
const gitRunner = (workdir) => command => new Promise(resolve => {
|
|
7
|
+
exec(command, { cwd: workdir }, (err, stdout) => {
|
|
8
|
+
if (err)
|
|
9
|
+
return resolve(undefined);
|
|
10
|
+
const out = stdout?.toString().trim();
|
|
11
|
+
resolve(out || undefined);
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
// Resolve the commit / ref / actor for the current deploy. In GitHub Actions
|
|
15
|
+
// these come from the standard env vars; locally we fall back to git so manual
|
|
16
|
+
// deploys still record a commit. `github_actor` is CI-only (a GitHub login),
|
|
17
|
+
// left undefined locally where no reliable login is available.
|
|
18
|
+
const git_context = async (opts) => {
|
|
19
|
+
const env = opts?.env ?? process.env;
|
|
20
|
+
const run = opts?.run ?? gitRunner(opts?.workdir);
|
|
21
|
+
const github_commit = env.GITHUB_SHA || (await run("git rev-parse HEAD"));
|
|
22
|
+
let github_ref = env.GITHUB_REF || undefined;
|
|
23
|
+
if (!github_ref) {
|
|
24
|
+
const branch = await run("git rev-parse --abbrev-ref HEAD");
|
|
25
|
+
if (branch && branch !== "HEAD")
|
|
26
|
+
github_ref = `refs/heads/${branch}`;
|
|
27
|
+
}
|
|
28
|
+
const github_actor = env.GITHUB_ACTOR || undefined;
|
|
29
|
+
const ctx = {};
|
|
30
|
+
if (github_commit)
|
|
31
|
+
ctx.github_commit = github_commit;
|
|
32
|
+
if (github_ref)
|
|
33
|
+
ctx.github_ref = github_ref;
|
|
34
|
+
if (github_actor)
|
|
35
|
+
ctx.github_actor = github_actor;
|
|
36
|
+
return ctx;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export { git_context };
|
|
@@ -2,6 +2,7 @@ import { context } from '../../api/context.js';
|
|
|
2
2
|
import { cmd } from '../../lib/cmd.js';
|
|
3
3
|
import { log } from '../../log.js';
|
|
4
4
|
import { check_environment } from './check_environment.js';
|
|
5
|
+
import { git_context } from './git_context.js';
|
|
5
6
|
import { build_node } from './node-pipeline/index.js';
|
|
6
7
|
import { runtime_detection } from './runtime-detect/runtime_detection.js';
|
|
7
8
|
import { upload_tag } from './upload_tag.js';
|
|
@@ -58,11 +59,15 @@ const deploy = {
|
|
|
58
59
|
}
|
|
59
60
|
// Upload to Faable registry
|
|
60
61
|
const { upload_tagname } = await upload_tag({ app, api });
|
|
62
|
+
// Capture the commit/ref/actor so the deployment records which commit it
|
|
63
|
+
// came from and who pushed it (env in CI, git fallback locally).
|
|
64
|
+
const git = await git_context({ workdir });
|
|
61
65
|
// Create a deployment for this image
|
|
62
66
|
const deployment = await api.createDeployment({
|
|
63
67
|
app_id: app.id,
|
|
64
68
|
image: upload_tagname,
|
|
65
|
-
type
|
|
69
|
+
type,
|
|
70
|
+
...git
|
|
66
71
|
});
|
|
67
72
|
const dashboard_url = `https://dashboard.faable.com/deploy/${app.team}/app/${app.id}`;
|
|
68
73
|
log.info(`🌍 Deployment created (${deployment.id}) -> https://${app.url}`);
|
|
@@ -83,7 +88,7 @@ const deploy = {
|
|
|
83
88
|
break;
|
|
84
89
|
}
|
|
85
90
|
}
|
|
86
|
-
catch (
|
|
91
|
+
catch (_error) {
|
|
87
92
|
// Ignore transient errors while polling and keep waiting
|
|
88
93
|
log.debug(`Polling app status failed, retrying...`);
|
|
89
94
|
}
|
|
@@ -3,6 +3,7 @@ import prompts from 'prompts';
|
|
|
3
3
|
import { log } from '../../log.js';
|
|
4
4
|
import { cmd } from '../../lib/cmd.js';
|
|
5
5
|
import { Configuration } from '../../lib/Configuration.js';
|
|
6
|
+
import { workflowExists, DEPLOY_WORKFLOW_PATH, writeWorkflow, DEPLOY_DOCS_URL, DEPLOY_WORKFLOW_YAML } from './workflow_template.js';
|
|
6
7
|
|
|
7
8
|
const getGitRemoteUrl = async (workdir) => {
|
|
8
9
|
try {
|
|
@@ -117,7 +118,38 @@ const link = {
|
|
|
117
118
|
// Save locally for CLI convenience (only after the API confirms the link)
|
|
118
119
|
Configuration.instance().saveConfig({ app_slug: selectedApp.name, app_id: selectedApp.id });
|
|
119
120
|
log.info(`Successfully linked local repository to ${selectedApp.name}.`);
|
|
121
|
+
// Onboarding: deploys happen via a GitHub Actions workflow on push. Offer
|
|
122
|
+
// to scaffold it, and always explain the next steps so the user isn't left
|
|
123
|
+
// wondering why nothing deploys.
|
|
124
|
+
await setupDeployWorkflow(workdir);
|
|
120
125
|
},
|
|
121
126
|
};
|
|
127
|
+
const setupDeployWorkflow = async (workdir) => {
|
|
128
|
+
if (workflowExists(workdir)) {
|
|
129
|
+
log.info(`Deploy workflow already present at ${DEPLOY_WORKFLOW_PATH}. Commit & push to "main" to deploy.`);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const { create } = await prompts({
|
|
133
|
+
type: "toggle",
|
|
134
|
+
name: "create",
|
|
135
|
+
message: `Create the GitHub Actions deploy workflow (${DEPLOY_WORKFLOW_PATH})?`,
|
|
136
|
+
initial: true,
|
|
137
|
+
active: "yes",
|
|
138
|
+
inactive: "no",
|
|
139
|
+
});
|
|
140
|
+
if (create) {
|
|
141
|
+
const filePath = await writeWorkflow(workdir);
|
|
142
|
+
log.info(`Created ${filePath}`);
|
|
143
|
+
log.info("Next steps:");
|
|
144
|
+
log.info(" 1. Commit the workflow file");
|
|
145
|
+
log.info(' 2. Push to "main" — that triggers your first deploy');
|
|
146
|
+
log.info(`Docs: ${DEPLOY_DOCS_URL}`);
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
log.info(`Skipped. To enable automated deploys, add ${DEPLOY_WORKFLOW_PATH} with:`);
|
|
150
|
+
log.info(`\n${DEPLOY_WORKFLOW_YAML}`);
|
|
151
|
+
log.info(`Then commit & push to "main". Docs: ${DEPLOY_DOCS_URL}`);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
122
154
|
|
|
123
155
|
export { link };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { mkdir, writeFile } from 'fs/promises';
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
import { join, dirname } from 'path';
|
|
4
|
+
|
|
5
|
+
// The canonical GitHub Actions workflow that deploys a Faable app on push.
|
|
6
|
+
// Mirrors the docs at https://faable.com/docs/deploy/github-actions.
|
|
7
|
+
const DEPLOY_WORKFLOW_PATH = ".github/workflows/deploy.yaml";
|
|
8
|
+
const DEPLOY_WORKFLOW_YAML = `name: Deploy to Faable
|
|
9
|
+
on:
|
|
10
|
+
push:
|
|
11
|
+
branches:
|
|
12
|
+
- main
|
|
13
|
+
permissions:
|
|
14
|
+
id-token: write
|
|
15
|
+
contents: write
|
|
16
|
+
pull-requests: write
|
|
17
|
+
issues: write
|
|
18
|
+
jobs:
|
|
19
|
+
deploy:
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
timeout-minutes: 10
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v6
|
|
24
|
+
- uses: actions/setup-node@v6
|
|
25
|
+
- run: npm ci
|
|
26
|
+
- run: npx @faable/faable@latest deploy
|
|
27
|
+
`;
|
|
28
|
+
const DEPLOY_DOCS_URL = "https://faable.com/docs/deploy/github-actions";
|
|
29
|
+
const workflowExists = (workdir) => existsSync(join(workdir, DEPLOY_WORKFLOW_PATH));
|
|
30
|
+
const writeWorkflow = async (workdir) => {
|
|
31
|
+
const filePath = join(workdir, DEPLOY_WORKFLOW_PATH);
|
|
32
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
33
|
+
await writeFile(filePath, DEPLOY_WORKFLOW_YAML, "utf8");
|
|
34
|
+
return filePath;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export { DEPLOY_DOCS_URL, DEPLOY_WORKFLOW_PATH, DEPLOY_WORKFLOW_YAML, workflowExists, writeWorkflow };
|