export interface ScaffoldResult {
readmeCommitted: boolean;
workflowCommitted: boolean;
workflowsSynced: number;
runsEnqueued: number;
errors: string[];
}
function ciWorkflow(repoName: string): string {
return `name: CI
on:
- push
jobs:
verify:
runs-on: ubuntu-latest
steps:
- name: Say hello
run: echo "CI is running for ${repoName}"
- name: Check the workspace exists
run: ls -a
`;
}
function readme(owner: string, repoName: string): string {
return `# ${repoName}
Created on Gluecron. This repository was set up for you — nothing here needed
configuring.
## What is already switched on
- **Branch protection** on the default branch
- **Secret + security scanning** on every push
- **AI review** on every pull request
- **CI** — see \`.gluecron/workflows/ci.yml\`, which ran when this commit landed
## Next
Push some code:
\`\`\`bash
git clone https://gluecron.com/${owner}/${repoName}.git
cd ${repoName}
# ...
git push
\`\`\`
Every push is scanned, reviewed and gated automatically.
`;
}
export interface ScaffoldDeps {
createOrUpdateFileOnBranch: (input: {
owner: string;
name: string;
branch: string;
filePath: string;
bytes: Uint8Array;
message: string;
authorName: string;
authorEmail: string;
}) => Promise<
{ commitSha: string; blobSha: string; parentSha: string | null } | { error: string }
>;
syncAndEnqueuePushWorkflows: (opts: {
owner: string;
repo: string;
repositoryId: string;
branch: string;
commitSha: string;
triggeredBy?: string | null;
}) => Promise<{ synced: number; enqueued: number; errors: string[] }>;
}
async function realDeps(): Promise<ScaffoldDeps> {
const { createOrUpdateFileOnBranch } = await import("../git/repository");
const { syncAndEnqueuePushWorkflows } = await import("./push-workflow-sync");
return {
createOrUpdateFileOnBranch: createOrUpdateFileOnBranch as ScaffoldDeps["createOrUpdateFileOnBranch"],
syncAndEnqueuePushWorkflows,
};
}
export async function scaffoldFirstRepo(
opts: {
owner: string;
repoName: string;
repositoryId: string;
defaultBranch: string;
authorName: string;
authorEmail: string;
userId: string;
},
injected?: ScaffoldDeps
): Promise<ScaffoldResult> {
const result: ScaffoldResult = {
readmeCommitted: false,
workflowCommitted: false,
workflowsSynced: 0,
runsEnqueued: 0,
errors: [],
};
const deps = injected ?? (await realDeps());
const { createOrUpdateFileOnBranch } = deps;
const enc = new TextEncoder();
let headSha: string | null = null;
const write = async (filePath: string, body: string, message: string) => {
const res = await createOrUpdateFileOnBranch({
owner: opts.owner,
name: opts.repoName,
branch: opts.defaultBranch,
filePath,
bytes: enc.encode(body),
message,
authorName: opts.authorName,
authorEmail: opts.authorEmail,
});
if ("error" in res) throw new Error(`${filePath}: ${res.error}`);
return res.commitSha;
};
try {
headSha = await write(
"README.md",
readme(opts.owner, opts.repoName),
"Add README"
);
result.readmeCommitted = true;
} catch (err) {
result.errors.push(
`readme: ${err instanceof Error ? err.message : String(err)}`
);
}
try {
headSha = await write(
".gluecron/workflows/ci.yml",
ciWorkflow(opts.repoName),
"Add CI workflow"
);
result.workflowCommitted = true;
} catch (err) {
result.errors.push(
`workflow: ${err instanceof Error ? err.message : String(err)}`
);
}
if (result.workflowCommitted && headSha) {
try {
const sync = await deps.syncAndEnqueuePushWorkflows({
owner: opts.owner,
repo: opts.repoName,
repositoryId: opts.repositoryId,
branch: opts.defaultBranch,
commitSha: headSha,
triggeredBy: opts.userId,
});
result.workflowsSynced = sync.synced ?? 0;
result.runsEnqueued = sync.enqueued ?? 0;
} catch (err) {
result.errors.push(
`sync: ${err instanceof Error ? err.message : String(err)}`
);
}
}
return result;
}
|