Skip to content
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
5 changes: 5 additions & 0 deletions .changeset/odd-tables-clean.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@proofgeist/kit": minor
---

Allow deploying a demo file to your server instead of having to pick an existing file
96 changes: 96 additions & 0 deletions cli/src/cli/add/data-source/deploy-demo-file.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import * as p from "@clack/prompts";

import {
createDataAPIKeyWithCredentials,
getDeploymentStatus,
startDeployment,
} from "~/cli/ottofms.js";

export const filename = "ProofKitDemo.fmp12";

export async function deployDemoFile({
url,
token,
operation,
}: {
url: URL;
token: string;
operation: "install" | "replace";
}): Promise<{ apiKey: string }> {
const deploymentJSON = {
scheduled: false,
label: "Install ProofKit Demo",
deployments: [
{
name: "Install ProofKit Demo",
source: {
type: "url",
url: "https://proofkit.dev/proofkit-demo/manifest.json",
},
fileOperations: [
{
target: {
fileName: filename,
},
operation,
source: {
fileName: "ProofKitDemo.fmp12",
},
location: {
folder: "default",
subFolder: "",
},
},
],
concurrency: 1,
options: {
closeFilesAfterBuild: false,
keepFilesClosedAfterComplete: false,
transferContainerData: false,
},
},
],
abortRemaining: false,
};

const spinner = p.spinner();
spinner.start("Deploying ProofKit Demo file...");

const {
response: { subDeploymentIds },
} = await startDeployment({
payload: deploymentJSON,
url,
token,
});

const deploymentId = subDeploymentIds[0]!;

while (true) {
// wait 2.5 seconds, then poll the status again
await new Promise((resolve) => setTimeout(resolve, 2500));

const {
response: { status, running },
} = await getDeploymentStatus({
url,
token,
deploymentId,
});
if (!running) {
if (status !== "complete") throw new Error("Deployment didn't complete");
break;
}
}

const { apiKey } = await createDataAPIKeyWithCredentials({
filename,
username: "admin",
password: "admin",
url,
});

spinner.stop();

return { apiKey };
}
135 changes: 89 additions & 46 deletions cli/src/cli/add/data-source/filemaker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
import { formatAndSaveSourceFiles, getNewProject } from "~/utils/ts-morph.js";
import { validateAppName } from "~/utils/validateAppName.js";
import { runAddSchemaAction } from "../fmschema.js";
import { deployDemoFile, filename } from "./deploy-demo-file.js";

export async function promptForFileMakerDataSource({
projectDir,
Expand Down Expand Up @@ -57,64 +58,106 @@ export async function promptForFileMakerDataSource({
opts.adminApiKey || (await getOttoFMSToken({ url: server.url })).token;

const fileList = await listFiles({ url: server.url, token });
const selectedFile =
opts.fileName ||
abortIfCancel(
await p.select({
message: `Which file would you like to connect to? ${chalk.dim(`(TIP: Select the file where your data is stored)`)}`,
maxItems: 10,
options: fileList
.sort((a, b) => a.filename.localeCompare(b.filename))
.map((file) => ({
value: file.filename,
label: file.filename,
})),
})
);
const fmFile = selectedFile;
const demoFileExists = fileList
.map((f) => f.filename.replace(".fmp12", ""))
.includes(filename.replace(".fmp12", ""));
let fmFile = opts.fileName;
while (true) {
fmFile =
opts.fileName ||
abortIfCancel(
await p.select({
message: `Which file would you like to connect to? ${chalk.dim(`(TIP: Select the file where your data is stored)`)}`,
maxItems: 10,
options: [
{
value: "$deployDemoFile",
label: "Deploy NEW ProofKit Demo File",
hint: "Use OttoFMS to deploy a new file for testing",
},
...fileList
.sort((a, b) => a.filename.localeCompare(b.filename))
.map((file) => ({
value: file.filename,
label: file.filename,
})),
],
})
);

const allApiKeys = await listAPIKeys({ url: server.url, token });
const thisFileApiKeys = allApiKeys.filter((key) => key.database === fmFile);
if (fmFile !== "$deployDemoFile") break;

let dataApiKey = opts.dataApiKey;
if (!dataApiKey && thisFileApiKeys.length > 0) {
const selectedKey = abortIfCancel(
await p.select({
message: "Which API key would you like to use?",
options: [
...thisFileApiKeys.map((key) => ({
value: key.key,
label: `${chalk.bold(key.label)} - ${key.user}`,
hint: `${key.key.slice(0, 5)}...${key.key.slice(-4)}`,
})),
{
value: "create",
label: "Create a new API key",
hint: "Requires FileMaker credentials for this file",
},
],
})
);
if (typeof selectedKey !== "string") throw new Error("Invalid key");
if (selectedKey !== "create") dataApiKey = selectedKey;
if (demoFileExists) {
const replace = abortIfCancel(
await p.confirm({
message:
"The demo file already exists, do you want to replace it with a fresh copy?",
active: "Yes, replace",
inactive: "No, select another file",
initialValue: false,
})
);
if (replace) break;
} else {
break;
}
}

if (!dataApiKey) {
// data api was not provided, prompt to create a new one
const resp = await createDataAPIKey({
filename: fmFile,
if (!fmFile) throw new Error("No file selected");

let dataApiKey = opts.dataApiKey;
if (fmFile === "$deployDemoFile") {
const { apiKey } = await deployDemoFile({
url: server.url,
token,
operation: demoFileExists ? "replace" : "install",
});
dataApiKey = resp.apiKey;
}
dataApiKey = apiKey;
fmFile = filename;
opts.layoutName = opts.layoutName ?? "API_Contacts";
opts.schemaName = opts.schemaName ?? "Contacts";
} else {
const allApiKeys = await listAPIKeys({ url: server.url, token });
const thisFileApiKeys = allApiKeys.filter((key) => key.database === fmFile);

if (!dataApiKey && thisFileApiKeys.length > 0) {
const selectedKey = abortIfCancel(
await p.select({
message: "Which API key would you like to use?",
options: [
...thisFileApiKeys.map((key) => ({
value: key.key,
label: `${chalk.bold(key.label)} - ${key.user}`,
hint: `${key.key.slice(0, 5)}...${key.key.slice(-4)}`,
})),
{
value: "create",
label: "Create a new API key",
hint: "Requires FileMaker credentials for this file",
},
],
})
);
if (typeof selectedKey !== "string") throw new Error("Invalid key");
if (selectedKey !== "create") dataApiKey = selectedKey;
}

if (!dataApiKey) {
// data api was not provided, prompt to create a new one
const resp = await createDataAPIKey({
filename: fmFile,
url: server.url,
});
dataApiKey = resp.apiKey;
}
}
if (!dataApiKey) throw new Error("No API key");

const name =
existingFmDataSourceNames.length === 0
? "filemaker"
: opts.name ??
(
abortIfCancel(
await p.text({
message: "What do you want to call this data source?",
validate: (value) => {
Expand All @@ -128,7 +171,7 @@ export async function promptForFileMakerDataSource({
return validateAppName(value);
},
})
).toString();
);

const newDataSource: z.infer<typeof dataSourceSchema> = {
type: "fm",
Expand Down
Loading