@softwareseva/cli 0.1.0
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.
- package/LICENSE +21 -0
- package/README.md +19 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +26 -0
- package/dist/bin.js.map +1 -0
- package/dist/commands/add.d.ts +6 -0
- package/dist/commands/add.d.ts.map +1 -0
- package/dist/commands/add.js +44 -0
- package/dist/commands/add.js.map +1 -0
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +97 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/init.d.ts +5 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +20 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/migrate.d.ts +6 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +37 -0
- package/dist/commands/migrate.js.map +1 -0
- package/dist/commands/modules.d.ts +14 -0
- package/dist/commands/modules.d.ts.map +1 -0
- package/dist/commands/modules.js +104 -0
- package/dist/commands/modules.js.map +1 -0
- package/dist/commands/secrets.d.ts +10 -0
- package/dist/commands/secrets.d.ts.map +1 -0
- package/dist/commands/secrets.js +55 -0
- package/dist/commands/secrets.js.map +1 -0
- package/dist/commands/update.d.ts +4 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +57 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/modules.d.ts +42 -0
- package/dist/modules.d.ts.map +1 -0
- package/dist/modules.js +69 -0
- package/dist/modules.js.map +1 -0
- package/dist/util.d.ts +38 -0
- package/dist/util.d.ts.map +1 -0
- package/dist/util.js +74 -0
- package/dist/util.js.map +1 -0
- package/package.json +34 -0
- package/secrets.schema.json +27 -0
- package/templates/AGENTS.md +31 -0
- package/templates/CLAUDE.md +3 -0
- package/templates/GEMINI.md +3 -0
- package/templates/gitignore +19 -0
- package/templates/modules/deploy-cloudflare/files/deploy-api.yml +45 -0
- package/templates/modules/deploy-cloudflare/files/deploy-web.yml +44 -0
- package/templates/modules/deploy-cloudflare/files/web-wrangler.jsonc +10 -0
- package/templates/modules/deploy-cloudflare/module.json +21 -0
- package/templates/modules/deploy-cloudflare/secrets.json +7 -0
- package/templates/modules/deploy-fastlane/files/Gemfile +3 -0
- package/templates/modules/deploy-fastlane/files/android/Appfile +1 -0
- package/templates/modules/deploy-fastlane/files/android/Fastfile +33 -0
- package/templates/modules/deploy-fastlane/files/android/key.properties.example +5 -0
- package/templates/modules/deploy-fastlane/files/ios/Appfile +2 -0
- package/templates/modules/deploy-fastlane/files/ios/ExportOptions.plist +19 -0
- package/templates/modules/deploy-fastlane/files/ios/Fastfile +52 -0
- package/templates/modules/deploy-fastlane/files/ios/Matchfile +5 -0
- package/templates/modules/deploy-fastlane/files/mobile-release.yml +87 -0
- package/templates/modules/deploy-fastlane/module.json +29 -0
- package/templates/modules/deploy-fastlane/secrets.json +16 -0
- package/templates/renovate.json +7 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/** `kashi update`: bump every @softwareseva package, copy new migrations, report app-side template drift. */
|
|
2
|
+
import { execSync } from "node:child_process";
|
|
3
|
+
import { readFileSync, readdirSync, statSync } from "node:fs";
|
|
4
|
+
import { join, relative } from "node:path";
|
|
5
|
+
import { installedPackages, log, packageManager } from "../util.js";
|
|
6
|
+
import { migrate } from "./migrate.js";
|
|
7
|
+
import { updateModules } from "./modules.js";
|
|
8
|
+
export function update(cwd, options, cliVersion = "0.0.0") {
|
|
9
|
+
log.title("kashi update");
|
|
10
|
+
const pm = packageManager(cwd);
|
|
11
|
+
if (options.install !== false) {
|
|
12
|
+
const cmd = pm === "pnpm" ? 'pnpm up "@softwareseva/*" --latest' : pm === "yarn" ? 'yarn up "@softwareseva/*"' : pm === "bun" ? 'bun update "@softwareseva/*"' : 'npm update "@softwareseva/*"';
|
|
13
|
+
log.info(`running: ${cmd}`);
|
|
14
|
+
execSync(cmd, { cwd, stdio: "inherit" });
|
|
15
|
+
}
|
|
16
|
+
for (const pkg of installedPackages(cwd))
|
|
17
|
+
log.ok(`${pkg.name}@${pkg.version}`);
|
|
18
|
+
migrate(cwd, {});
|
|
19
|
+
updateModules(cwd, cliVersion);
|
|
20
|
+
reportDrift(cwd, cliVersion);
|
|
21
|
+
}
|
|
22
|
+
/** Files copied by `kashi add` carry `kashi:<package>@<version>`; flag those behind the installed version. */
|
|
23
|
+
function reportDrift(cwd, cliVersion) {
|
|
24
|
+
const versions = new Map(installedPackages(cwd).map((p) => [p.name, p.version]));
|
|
25
|
+
versions.set("@softwareseva/cli", cliVersion);
|
|
26
|
+
const stale = [];
|
|
27
|
+
walk(cwd, (file) => {
|
|
28
|
+
let head = "";
|
|
29
|
+
try {
|
|
30
|
+
head = readFileSync(file, "utf8").slice(0, 400);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const m = head.match(/kashi:(@softwareseva\/[a-z-]+)@(\d+\.\d+\.\d+)/);
|
|
36
|
+
if (m && versions.get(m[1]) && versions.get(m[1]) !== m[2])
|
|
37
|
+
stale.push(`${relative(cwd, file)} (from ${m[1]}@${m[2]}, installed ${versions.get(m[1])})`);
|
|
38
|
+
});
|
|
39
|
+
if (stale.length) {
|
|
40
|
+
log.title("template files written by an older package version (review the package CHANGELOG, re-run `kashi add --force` to overwrite):");
|
|
41
|
+
stale.forEach((s) => log.warn(s));
|
|
42
|
+
}
|
|
43
|
+
else
|
|
44
|
+
log.ok("no template drift");
|
|
45
|
+
}
|
|
46
|
+
function walk(dir, visit, depth = 0) {
|
|
47
|
+
if (depth > 6)
|
|
48
|
+
return;
|
|
49
|
+
for (const name of readdirSync(dir)) {
|
|
50
|
+
if (["node_modules", ".git", "dist", "build", ".dart_tool", ".wrangler"].includes(name))
|
|
51
|
+
continue;
|
|
52
|
+
const path = join(dir, name);
|
|
53
|
+
const st = statSync(path);
|
|
54
|
+
st.isDirectory() ? walk(path, visit, depth + 1) : visit(path);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=update.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update.js","sourceRoot":"","sources":["../../src/commands/update.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type SecretsManifest } from "./util.js";
|
|
2
|
+
export type ModuleVar = {
|
|
3
|
+
detect?: string;
|
|
4
|
+
default?: string;
|
|
5
|
+
help?: string;
|
|
6
|
+
};
|
|
7
|
+
export type ModuleFile = {
|
|
8
|
+
from: string;
|
|
9
|
+
to: string;
|
|
10
|
+
when?: string;
|
|
11
|
+
ifMissing?: boolean;
|
|
12
|
+
};
|
|
13
|
+
export type ModuleSpec = {
|
|
14
|
+
name: string;
|
|
15
|
+
description: string;
|
|
16
|
+
vars: Record<string, ModuleVar>;
|
|
17
|
+
files: ModuleFile[];
|
|
18
|
+
next?: string[];
|
|
19
|
+
};
|
|
20
|
+
export type RenderedFile = {
|
|
21
|
+
dest: string;
|
|
22
|
+
content: string;
|
|
23
|
+
ifMissing: boolean;
|
|
24
|
+
};
|
|
25
|
+
export type LockModule = {
|
|
26
|
+
version: string;
|
|
27
|
+
vars: Record<string, string>;
|
|
28
|
+
files: Record<string, string>;
|
|
29
|
+
};
|
|
30
|
+
export type Lock = {
|
|
31
|
+
migrations: Record<string, string>;
|
|
32
|
+
modules?: Record<string, LockModule>;
|
|
33
|
+
};
|
|
34
|
+
export declare const listModules: () => string[];
|
|
35
|
+
export declare const loadModule: (name: string) => ModuleSpec | null;
|
|
36
|
+
export declare const moduleSecrets: (name: string) => SecretsManifest | null;
|
|
37
|
+
export declare const sha: (text: string) => string;
|
|
38
|
+
export declare function resolveVars(spec: ModuleSpec, cwd: string, given: Record<string, string>, previous?: Record<string, string>): Record<string, string>;
|
|
39
|
+
export declare function renderModule(spec: ModuleSpec, vars: Record<string, string>): RenderedFile[];
|
|
40
|
+
export declare const renderNext: (spec: ModuleSpec, vars: Record<string, string>) => string[];
|
|
41
|
+
export declare const moduleFileDir: (dest: string) => string;
|
|
42
|
+
//# sourceMappingURL=modules.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modules.d.ts","sourceRoot":"","sources":["../src/modules.ts"],"names":[],"mappings":"AAIA,OAAO,EAA0B,KAAK,eAAe,EAAE,MAAM,WAAW,CAAC;AAEzE,MAAM,MAAM,SAAS,GAAG;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAC7E,MAAM,MAAM,UAAU,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAC1F,MAAM,MAAM,UAAU,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAAC,KAAK,EAAE,UAAU,EAAE,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AACtI,MAAM,MAAM,YAAY,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAAC;AACjF,MAAM,MAAM,UAAU,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CAAC;AAC1G,MAAM,MAAM,IAAI,GAAG;IAAE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;CAAE,CAAC;AAIhG,eAAO,MAAM,WAAW,QAAO,MAAM,EAA2H,CAAC;AACjK,eAAO,MAAM,UAAU,GAAI,MAAM,MAAM,KAAG,UAAU,GAAG,IAAmE,CAAC;AAC3H,eAAO,MAAM,aAAa,GAAI,MAAM,MAAM,KAAG,eAAe,GAAG,IAAyE,CAAC;AAEzI,eAAO,MAAM,GAAG,GAAI,MAAM,MAAM,WAAiE,CAAC;AAwClG,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,QAAQ,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAMvJ;AAKD,wBAAgB,YAAY,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,YAAY,EAAE,CAI3F;AAED,eAAO,MAAM,UAAU,GAAI,MAAM,UAAU,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,aAAwD,CAAC;AAClI,eAAO,MAAM,aAAa,GAAI,MAAM,MAAM,WAAkB,CAAC"}
|
package/dist/modules.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/** Deploy and scaffold modules: templates rendered with project values, tracked by content hash for safe updates. */
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
4
|
+
import { dirname, join, relative } from "node:path";
|
|
5
|
+
import { readJson, templatesDir } from "./util.js";
|
|
6
|
+
const modulesDir = join(templatesDir, "modules");
|
|
7
|
+
export const listModules = () => (existsSync(modulesDir) ? readdirSync(modulesDir).filter((n) => existsSync(join(modulesDir, n, "module.json"))) : []);
|
|
8
|
+
export const loadModule = (name) => readJson(join(modulesDir, name, "module.json"));
|
|
9
|
+
export const moduleSecrets = (name) => readJson(join(modulesDir, name, "secrets.json"));
|
|
10
|
+
export const sha = (text) => createHash("sha256").update(text).digest("hex").slice(0, 16);
|
|
11
|
+
/** Values from the project: where wrangler/flutter live, bundle ids, package names. */
|
|
12
|
+
const detectors = {
|
|
13
|
+
wranglerDir: (cwd) => findUp(cwd, (d) => ["wrangler.jsonc", "wrangler.json", "wrangler.toml"].some((f) => existsSync(join(d, f)))),
|
|
14
|
+
flutterDir: (cwd) => findUp(cwd, (d) => existsSync(join(d, "pubspec.yaml")) && existsSync(join(d, "ios")) && existsSync(join(d, "android"))),
|
|
15
|
+
iosBundleId: (cwd) => {
|
|
16
|
+
const dir = detectors.flutterDir(cwd);
|
|
17
|
+
const pbx = dir !== undefined ? join(cwd, dir, "ios/Runner.xcodeproj/project.pbxproj") : "";
|
|
18
|
+
if (!pbx || !existsSync(pbx))
|
|
19
|
+
return undefined;
|
|
20
|
+
const ids = [...readFileSync(pbx, "utf8").matchAll(/PRODUCT_BUNDLE_IDENTIFIER = "?([A-Za-z0-9.\-]+)"?;/g)].map((m) => m[1]).filter((id) => !/Tests$/.test(id));
|
|
21
|
+
return ids[0];
|
|
22
|
+
},
|
|
23
|
+
androidPackage: (cwd) => {
|
|
24
|
+
const dir = detectors.flutterDir(cwd);
|
|
25
|
+
if (dir === undefined)
|
|
26
|
+
return undefined;
|
|
27
|
+
for (const f of ["android/app/build.gradle.kts", "android/app/build.gradle"]) {
|
|
28
|
+
const p = join(cwd, dir, f);
|
|
29
|
+
if (existsSync(p))
|
|
30
|
+
return readFileSync(p, "utf8").match(/applicationId\s*=?\s*"([^"]+)"/)?.[1];
|
|
31
|
+
}
|
|
32
|
+
return undefined;
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
/** Breadth-first search (depth 3) for a directory matching `test`; returns a path relative to cwd. */
|
|
36
|
+
function findUp(cwd, test) {
|
|
37
|
+
const queue = [[cwd, 0]];
|
|
38
|
+
const skip = new Set(["node_modules", ".git", "build", "dist", ".dart_tool", "ios", "android", ".wrangler"]);
|
|
39
|
+
while (queue.length) {
|
|
40
|
+
const [dir, depth] = queue.shift();
|
|
41
|
+
if (test(dir))
|
|
42
|
+
return relative(cwd, dir) || ".";
|
|
43
|
+
if (depth >= 3)
|
|
44
|
+
continue;
|
|
45
|
+
for (const name of readdirSync(dir)) {
|
|
46
|
+
const p = join(dir, name);
|
|
47
|
+
if (!skip.has(name) && !name.startsWith(".") && statSync(p).isDirectory())
|
|
48
|
+
queue.push([p, depth + 1]);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
export function resolveVars(spec, cwd, given, previous = {}) {
|
|
54
|
+
const vars = {};
|
|
55
|
+
for (const [name, v] of Object.entries(spec.vars)) {
|
|
56
|
+
vars[name] = given[name] ?? previous[name] ?? (v.detect ? detectors[v.detect]?.(cwd) : undefined) ?? v.default ?? "";
|
|
57
|
+
}
|
|
58
|
+
return vars;
|
|
59
|
+
}
|
|
60
|
+
const render = (text, vars) => text.replace(/\{\{([A-Z_][A-Z0-9_]*)\}\}/g, (m, name) => (name in vars ? vars[name] : m));
|
|
61
|
+
const normalizePath = (p) => p.replace(/^\.\//, "").replace(/\/\.\//g, "/").replace(/\/+/g, "/");
|
|
62
|
+
export function renderModule(spec, vars) {
|
|
63
|
+
return spec.files
|
|
64
|
+
.filter((f) => !f.when || vars[f.when])
|
|
65
|
+
.map((f) => ({ dest: normalizePath(render(f.to, vars)), content: render(readFileSync(join(modulesDir, spec.name, "files", f.from), "utf8"), vars), ifMissing: Boolean(f.ifMissing) }));
|
|
66
|
+
}
|
|
67
|
+
export const renderNext = (spec, vars) => (spec.next ?? []).map((line) => render(line, vars));
|
|
68
|
+
export const moduleFileDir = (dest) => dirname(dest);
|
|
69
|
+
//# sourceMappingURL=modules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modules.js","sourceRoot":"","sources":["../src/modules.ts"],"names":[],"mappings":"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"}
|
package/dist/util.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export declare const templatesDir: string;
|
|
2
|
+
export type SecretEntry = {
|
|
3
|
+
name: string;
|
|
4
|
+
usedBy?: string;
|
|
5
|
+
description: string;
|
|
6
|
+
generate: string;
|
|
7
|
+
store: string[];
|
|
8
|
+
rotate?: string;
|
|
9
|
+
optional?: boolean;
|
|
10
|
+
when?: string;
|
|
11
|
+
gitignore?: string[];
|
|
12
|
+
};
|
|
13
|
+
export type SecretsManifest = {
|
|
14
|
+
package: string;
|
|
15
|
+
secrets: SecretEntry[];
|
|
16
|
+
};
|
|
17
|
+
export type InstalledPackage = {
|
|
18
|
+
name: string;
|
|
19
|
+
version: string;
|
|
20
|
+
dir: string;
|
|
21
|
+
migrations: string[];
|
|
22
|
+
secrets: SecretsManifest | null;
|
|
23
|
+
};
|
|
24
|
+
export declare function readJson<T>(path: string): T | null;
|
|
25
|
+
export declare function ensureLines(path: string, lines: string[], header?: string): string[];
|
|
26
|
+
export declare function writeIfAbsent(path: string, content: string, force?: boolean): boolean;
|
|
27
|
+
/** Every installed `@softwareseva/*` package (excluding the CLI) with its shipped migrations and secrets. */
|
|
28
|
+
export declare function installedPackages(cwd: string): InstalledPackage[];
|
|
29
|
+
export declare function packageManager(cwd: string): "pnpm" | "npm" | "yarn" | "bun";
|
|
30
|
+
/** Migrations directory from wrangler config, default `migrations`. */
|
|
31
|
+
export declare function migrationsDir(cwd: string): string;
|
|
32
|
+
export declare const log: {
|
|
33
|
+
ok: (msg: string) => void;
|
|
34
|
+
warn: (msg: string) => void;
|
|
35
|
+
info: (msg: string) => void;
|
|
36
|
+
title: (msg: string) => void;
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=util.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,YAAY,QAAmE,CAAC;AAE7F,MAAM,MAAM,WAAW,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAC9L,MAAM,MAAM,eAAe,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,WAAW,EAAE,CAAA;CAAE,CAAC;AAC1E,MAAM,MAAM,gBAAgB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,eAAe,GAAG,IAAI,CAAA;CAAE,CAAC;AAErI,wBAAgB,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,CAElD;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CASpF;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,UAAQ,GAAG,OAAO,CAKnF;AAED,6GAA6G;AAC7G,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,EAAE,CAajE;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,CAK3E;AAED,uEAAuE;AACvE,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAQjD;AAED,eAAO,MAAM,GAAG;cACJ,MAAM;gBACJ,MAAM;gBACN,MAAM;iBACL,MAAM;CACpB,CAAC"}
|
package/dist/util.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/** Filesystem and package-manager helpers shared by the commands. */
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
export const templatesDir = join(dirname(fileURLToPath(import.meta.url)), "..", "templates");
|
|
6
|
+
export function readJson(path) {
|
|
7
|
+
try {
|
|
8
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export function ensureLines(path, lines, header) {
|
|
15
|
+
const existing = existsSync(path) ? readFileSync(path, "utf8") : "";
|
|
16
|
+
const present = new Set(existing.split(/\r?\n/).map((l) => l.trim()));
|
|
17
|
+
const missing = lines.filter((l) => !present.has(l.trim()));
|
|
18
|
+
if (missing.length) {
|
|
19
|
+
const body = `${existing.endsWith("\n") || existing === "" ? existing : existing + "\n"}${header ? `\n${header}\n` : ""}${missing.join("\n")}\n`;
|
|
20
|
+
writeFileSync(path, body);
|
|
21
|
+
}
|
|
22
|
+
return missing;
|
|
23
|
+
}
|
|
24
|
+
export function writeIfAbsent(path, content, force = false) {
|
|
25
|
+
if (existsSync(path) && !force)
|
|
26
|
+
return false;
|
|
27
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
28
|
+
writeFileSync(path, content);
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
/** Every installed `@softwareseva/*` package (excluding the CLI) with its shipped migrations and secrets. */
|
|
32
|
+
export function installedPackages(cwd) {
|
|
33
|
+
const scopeDir = join(cwd, "node_modules", "@softwareseva");
|
|
34
|
+
if (!existsSync(scopeDir))
|
|
35
|
+
return [];
|
|
36
|
+
return readdirSync(scopeDir)
|
|
37
|
+
.filter((name) => name !== "cli" && statSync(join(scopeDir, name)).isDirectory())
|
|
38
|
+
.map((name) => {
|
|
39
|
+
const dir = join(scopeDir, name);
|
|
40
|
+
const pkg = readJson(join(dir, "package.json"));
|
|
41
|
+
const migrationsDir = join(dir, "migrations");
|
|
42
|
+
const migrations = existsSync(migrationsDir) ? readdirSync(migrationsDir).filter((f) => f.endsWith(".sql")).sort() : [];
|
|
43
|
+
return { name: pkg?.name ?? `@softwareseva/${name}`, version: pkg?.version ?? "0.0.0", dir, migrations, secrets: readJson(join(dir, "secrets.json")) };
|
|
44
|
+
})
|
|
45
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
46
|
+
}
|
|
47
|
+
export function packageManager(cwd) {
|
|
48
|
+
if (existsSync(join(cwd, "pnpm-lock.yaml")))
|
|
49
|
+
return "pnpm";
|
|
50
|
+
if (existsSync(join(cwd, "yarn.lock")))
|
|
51
|
+
return "yarn";
|
|
52
|
+
if (existsSync(join(cwd, "bun.lock")) || existsSync(join(cwd, "bun.lockb")))
|
|
53
|
+
return "bun";
|
|
54
|
+
return "npm";
|
|
55
|
+
}
|
|
56
|
+
/** Migrations directory from wrangler config, default `migrations`. */
|
|
57
|
+
export function migrationsDir(cwd) {
|
|
58
|
+
for (const file of ["wrangler.jsonc", "wrangler.json", "wrangler.toml"]) {
|
|
59
|
+
const path = join(cwd, file);
|
|
60
|
+
if (!existsSync(path))
|
|
61
|
+
continue;
|
|
62
|
+
const match = readFileSync(path, "utf8").match(/migrations_dir"?\s*[:=]\s*"([^"]+)"/);
|
|
63
|
+
if (match?.[1])
|
|
64
|
+
return join(cwd, match[1]);
|
|
65
|
+
}
|
|
66
|
+
return join(cwd, "migrations");
|
|
67
|
+
}
|
|
68
|
+
export const log = {
|
|
69
|
+
ok: (msg) => console.log(` ✓ ${msg}`),
|
|
70
|
+
warn: (msg) => console.log(` ! ${msg}`),
|
|
71
|
+
info: (msg) => console.log(` - ${msg}`),
|
|
72
|
+
title: (msg) => console.log(`\n${msg}`),
|
|
73
|
+
};
|
|
74
|
+
//# sourceMappingURL=util.js.map
|
package/dist/util.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACpG,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;AAM7F,MAAM,UAAU,QAAQ,CAAI,IAAY;IACtC,IAAI,CAAC;QAAC,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAM,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,IAAI,CAAC;IAAC,CAAC;AACpF,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,KAAe,EAAE,MAAe;IACxE,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACpE,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IACtE,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAC5D,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,MAAM,IAAI,GAAG,GAAG,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,QAAQ,KAAK,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,GAAG,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,KAAK,MAAM,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;QACjJ,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC5B,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,IAAY,EAAE,OAAe,EAAE,KAAK,GAAG,KAAK;IACxE,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC;IAC7C,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC7B,OAAO,IAAI,CAAC;AACd,CAAC;AAED,6GAA6G;AAC7G,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,cAAc,EAAE,eAAe,CAAC,CAAC;IAC5D,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,EAAE,CAAC;IACrC,OAAO,WAAW,CAAC,QAAQ,CAAC;SACzB,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,KAAK,IAAI,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;SAChF,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACZ,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QACjC,MAAM,GAAG,GAAG,QAAQ,CAAoC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC,CAAC;QACnF,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;QAC9C,MAAM,UAAU,GAAG,UAAU,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACxH,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,IAAI,iBAAiB,IAAI,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,IAAI,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,CAAkB,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC;IAC1K,CAAC,CAAC;SACD,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;AAClD,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,IAAI,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAC;QAAE,OAAO,MAAM,CAAC;IAC3D,IAAI,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;QAAE,OAAO,MAAM,CAAC;IACtD,IAAI,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IAC1F,OAAO,KAAK,CAAC;AACf,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,aAAa,CAAC,GAAW;IACvC,KAAK,MAAM,IAAI,IAAI,CAAC,gBAAgB,EAAE,eAAe,EAAE,eAAe,CAAC,EAAE,CAAC;QACxE,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAC7B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,SAAS;QAChC,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAC;QACtF,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7C,CAAC;IACD,OAAO,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;AACjC,CAAC;AAED,MAAM,CAAC,MAAM,GAAG,GAAG;IACjB,EAAE,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,GAAG,EAAE,CAAC;IAC9C,IAAI,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,GAAG,EAAE,CAAC;IAChD,IAAI,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,GAAG,EAAE,CAAC;IAChD,KAAK,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,GAAG,EAAE,CAAC;CAChD,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@softwareseva/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "kashi command line: init a project, copy package migrations, print the secrets checklist, check gitignore and secrets, update every @softwareseva package.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/softwareseva/kashi",
|
|
9
|
+
"directory": "packages/cli"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"bin": {
|
|
13
|
+
"kashi": "./dist/bin.js"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"templates",
|
|
18
|
+
"secrets.schema.json",
|
|
19
|
+
"README.md"
|
|
20
|
+
],
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"commander": "^15.0.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/node": "^24.0.0",
|
|
26
|
+
"typescript": "^5.9.0",
|
|
27
|
+
"vitest": "^4.1.0"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsc -p tsconfig.json && chmod +x dist/bin.js",
|
|
31
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
32
|
+
"test": "vitest run"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"title": "kashi secrets manifest",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"required": ["package", "secrets"],
|
|
6
|
+
"properties": {
|
|
7
|
+
"package": { "type": "string" },
|
|
8
|
+
"secrets": {
|
|
9
|
+
"type": "array",
|
|
10
|
+
"items": {
|
|
11
|
+
"type": "object",
|
|
12
|
+
"required": ["name", "description", "generate", "store"],
|
|
13
|
+
"properties": {
|
|
14
|
+
"name": { "type": "string", "pattern": "^[A-Z][A-Z0-9_]*$" },
|
|
15
|
+
"usedBy": { "type": "string", "description": "Feature or subpath that reads it" },
|
|
16
|
+
"description": { "type": "string" },
|
|
17
|
+
"generate": { "type": "string", "description": "How to obtain or generate the value" },
|
|
18
|
+
"store": { "type": "array", "items": { "enum": ["wrangler-secret", "dev-vars", "ci-secret", "env", "match", "keychain"] } },
|
|
19
|
+
"rotate": { "type": "string" },
|
|
20
|
+
"optional": { "type": "boolean" },
|
|
21
|
+
"when": { "type": "string", "description": "Only needed when this feature is enabled" },
|
|
22
|
+
"gitignore": { "type": "array", "items": { "type": "string" } }
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Repository agent guide
|
|
2
|
+
|
|
3
|
+
This file is the canonical instruction entry point for any coding agent working in this repository. It is tool- and vendor-neutral. `CLAUDE.md` and `GEMINI.md` point here.
|
|
4
|
+
|
|
5
|
+
## Product and current state
|
|
6
|
+
|
|
7
|
+
<!-- Two or three sentences: what the product does, who uses it, what is live and what is in progress. -->
|
|
8
|
+
|
|
9
|
+
## Start here
|
|
10
|
+
|
|
11
|
+
1. Read this file and the closest relevant code and tests.
|
|
12
|
+
2. Load the kashi skills that apply to this project: `kashi`, <!-- e.g. `hono-d1-api`, `auth-sessions`, `d1-list-pagination`, `react-data-table`, `flutter-auth` -->.
|
|
13
|
+
3. Read the matching repo-local skill when the task fits one: <!-- `skills/<name>/SKILL.md` -->.
|
|
14
|
+
4. Check `git status --short` and preserve unrelated changes.
|
|
15
|
+
5. The code and migrations are the source of truth; planning docs are context.
|
|
16
|
+
|
|
17
|
+
## Repository map
|
|
18
|
+
|
|
19
|
+
<!-- One line per top-level directory: what lives there and what must not be hand-edited. -->
|
|
20
|
+
|
|
21
|
+
## Rules specific to this project
|
|
22
|
+
|
|
23
|
+
<!-- Domain invariants, identity model, external integrations, anything an agent would otherwise get wrong. -->
|
|
24
|
+
|
|
25
|
+
## Verification
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
<!-- the one command that runs typecheck, tests and policy checks -->
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Deployments, production migrations and secret changes are explicit external actions. Report what changed, what was verified, and what still needs to run.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# kashi: secrets and local config
|
|
2
|
+
.dev.vars
|
|
3
|
+
.env
|
|
4
|
+
.env.*
|
|
5
|
+
!.env.example
|
|
6
|
+
!.dev.vars.example
|
|
7
|
+
*.p8
|
|
8
|
+
*.p12
|
|
9
|
+
*.keystore
|
|
10
|
+
*.jks
|
|
11
|
+
*.mobileprovision
|
|
12
|
+
fastlane/report.xml
|
|
13
|
+
fastlane/*.json
|
|
14
|
+
# build output
|
|
15
|
+
node_modules/
|
|
16
|
+
dist/
|
|
17
|
+
.wrangler/
|
|
18
|
+
build/
|
|
19
|
+
.dart_tool/
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
name: deploy-api
|
|
2
|
+
|
|
3
|
+
# Staging on every push to main; production when a GitHub release is published (or run manually).
|
|
4
|
+
on:
|
|
5
|
+
push:
|
|
6
|
+
branches: [main]
|
|
7
|
+
paths: ["{{API_DIR}}/**", ".github/workflows/deploy-api.yml"]
|
|
8
|
+
release:
|
|
9
|
+
types: [published]
|
|
10
|
+
workflow_dispatch:
|
|
11
|
+
inputs:
|
|
12
|
+
environment:
|
|
13
|
+
description: Target environment
|
|
14
|
+
type: choice
|
|
15
|
+
options: [staging, production]
|
|
16
|
+
default: staging
|
|
17
|
+
|
|
18
|
+
concurrency:
|
|
19
|
+
group: deploy-api-${{ github.event_name == 'release' && 'production' || inputs.environment || 'staging' }}
|
|
20
|
+
cancel-in-progress: false
|
|
21
|
+
|
|
22
|
+
jobs:
|
|
23
|
+
deploy:
|
|
24
|
+
runs-on: ubuntu-latest
|
|
25
|
+
environment: ${{ github.event_name == 'release' && 'production' || inputs.environment || 'staging' }}
|
|
26
|
+
env:
|
|
27
|
+
TARGET: ${{ github.event_name == 'release' && 'production' || inputs.environment || 'staging' }}
|
|
28
|
+
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
29
|
+
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
30
|
+
defaults:
|
|
31
|
+
run:
|
|
32
|
+
working-directory: {{API_DIR}}
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/checkout@v4
|
|
35
|
+
- uses: pnpm/action-setup@v4
|
|
36
|
+
- uses: actions/setup-node@v4
|
|
37
|
+
with:
|
|
38
|
+
node-version: {{NODE_VERSION}}
|
|
39
|
+
cache: pnpm
|
|
40
|
+
- run: pnpm install --frozen-lockfile
|
|
41
|
+
- run: pnpm run typecheck
|
|
42
|
+
- run: pnpm run test
|
|
43
|
+
# Migrations run before the new code so the code never sees an older schema.
|
|
44
|
+
- run: pnpm exec wrangler d1 migrations apply DB --remote --env "$TARGET"
|
|
45
|
+
- run: pnpm exec wrangler deploy --env "$TARGET"
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
name: deploy-web
|
|
2
|
+
|
|
3
|
+
# Builds the Vite app and serves it with Workers static assets (SPA fallback to index.html).
|
|
4
|
+
on:
|
|
5
|
+
push:
|
|
6
|
+
branches: [main]
|
|
7
|
+
paths: ["{{WEB_DIR}}/**", ".github/workflows/deploy-web.yml"]
|
|
8
|
+
release:
|
|
9
|
+
types: [published]
|
|
10
|
+
workflow_dispatch:
|
|
11
|
+
inputs:
|
|
12
|
+
environment:
|
|
13
|
+
description: Target environment
|
|
14
|
+
type: choice
|
|
15
|
+
options: [staging, production]
|
|
16
|
+
default: staging
|
|
17
|
+
|
|
18
|
+
concurrency:
|
|
19
|
+
group: deploy-web-${{ github.event_name == 'release' && 'production' || inputs.environment || 'staging' }}
|
|
20
|
+
cancel-in-progress: false
|
|
21
|
+
|
|
22
|
+
jobs:
|
|
23
|
+
deploy:
|
|
24
|
+
runs-on: ubuntu-latest
|
|
25
|
+
environment: ${{ github.event_name == 'release' && 'production' || inputs.environment || 'staging' }}
|
|
26
|
+
env:
|
|
27
|
+
TARGET: ${{ github.event_name == 'release' && 'production' || inputs.environment || 'staging' }}
|
|
28
|
+
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
29
|
+
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
30
|
+
defaults:
|
|
31
|
+
run:
|
|
32
|
+
working-directory: {{WEB_DIR}}
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/checkout@v4
|
|
35
|
+
- uses: pnpm/action-setup@v4
|
|
36
|
+
- uses: actions/setup-node@v4
|
|
37
|
+
with:
|
|
38
|
+
node-version: {{NODE_VERSION}}
|
|
39
|
+
cache: pnpm
|
|
40
|
+
- run: pnpm install --frozen-lockfile
|
|
41
|
+
- run: pnpm run build
|
|
42
|
+
env:
|
|
43
|
+
VITE_API_URL: ${{ vars.VITE_API_URL }}
|
|
44
|
+
- run: pnpm exec wrangler deploy --env "$TARGET"
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "node_modules/wrangler/config-schema.json",
|
|
3
|
+
"name": "my-web",
|
|
4
|
+
"compatibility_date": "2026-09-01",
|
|
5
|
+
"assets": { "directory": "./dist", "not_found_handling": "single-page-application" },
|
|
6
|
+
"env": {
|
|
7
|
+
"staging": { "name": "my-web-staging" },
|
|
8
|
+
"production": { "name": "my-web", "routes": [{ "pattern": "app.example.com", "custom_domain": true }] }
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "deploy-cloudflare",
|
|
3
|
+
"description": "GitHub Actions that test, apply D1 migrations and deploy the Worker API (and optionally a static web app) to staging on every push to main and to production on a release.",
|
|
4
|
+
"vars": {
|
|
5
|
+
"API_DIR": { "detect": "wranglerDir", "default": ".", "help": "directory with the Worker's wrangler.jsonc, relative to the repo root" },
|
|
6
|
+
"WEB_DIR": { "default": "", "help": "directory of a Vite web app to deploy with Workers static assets; empty to skip" },
|
|
7
|
+
"NODE_VERSION": { "default": "22" }
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
{ "from": "deploy-api.yml", "to": ".github/workflows/deploy-api.yml" },
|
|
11
|
+
{ "from": "deploy-web.yml", "to": ".github/workflows/deploy-web.yml", "when": "WEB_DIR" },
|
|
12
|
+
{ "from": "web-wrangler.jsonc", "to": "{{WEB_DIR}}/wrangler.jsonc", "when": "WEB_DIR", "ifMissing": true }
|
|
13
|
+
],
|
|
14
|
+
"next": [
|
|
15
|
+
"Add \"env\": { \"staging\": {...}, \"production\": {...} } to {{API_DIR}}/wrangler.jsonc with one D1 database per environment (see the deploy-cloudflare skill).",
|
|
16
|
+
"Create the databases: wrangler d1 create <name>-staging and wrangler d1 create <name>-production; paste their ids into the env sections.",
|
|
17
|
+
"Set repository secrets CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID (gh secret set NAME).",
|
|
18
|
+
"Create GitHub environments 'staging' and 'production'; add required reviewers to production.",
|
|
19
|
+
"Set each Worker secret per environment: wrangler secret put NAME --env staging (and --env production)."
|
|
20
|
+
]
|
|
21
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"package": "deploy-cloudflare",
|
|
3
|
+
"secrets": [
|
|
4
|
+
{ "name": "CLOUDFLARE_API_TOKEN", "usedBy": "GitHub Actions deploy", "description": "API token that can edit Workers scripts and D1.", "generate": "Cloudflare dashboard > My Profile > API Tokens > Create Token > template 'Edit Cloudflare Workers', add permission Account > D1 > Edit, restrict to your account.", "store": ["ci-secret"], "rotate": "yearly, or when someone with access leaves" },
|
|
5
|
+
{ "name": "CLOUDFLARE_ACCOUNT_ID", "usedBy": "GitHub Actions deploy", "description": "Your Cloudflare account id.", "generate": "Cloudflare dashboard > Workers & Pages > right sidebar 'Account ID', or: wrangler whoami", "store": ["ci-secret"] }
|
|
6
|
+
]
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
package_name("{{PACKAGE_NAME}}")
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Android lanes. Run from android/: bundle exec fastlane <lane>. Needs key.properties (see the skill) and PLAY_SERVICE_ACCOUNT_JSON.
|
|
2
|
+
default_platform(:android)
|
|
3
|
+
|
|
4
|
+
APP_ROOT = File.expand_path("../..", __dir__)
|
|
5
|
+
AAB = File.join(APP_ROOT, "build/app/outputs/bundle/release/app-release.aab")
|
|
6
|
+
|
|
7
|
+
platform :android do
|
|
8
|
+
def play_key
|
|
9
|
+
ENV.fetch("PLAY_SERVICE_ACCOUNT_JSON")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def build_aab(track)
|
|
13
|
+
build_number = ENV["BUILD_NUMBER"] || begin
|
|
14
|
+
codes = google_play_track_version_codes(track: track, json_key_data: play_key) rescue [0]
|
|
15
|
+
(codes.map(&:to_i).max.to_i + 1).to_s
|
|
16
|
+
end
|
|
17
|
+
Dir.chdir(APP_ROOT) do
|
|
18
|
+
sh("flutter", "build", "appbundle", "--release", "--build-number=#{build_number}")
|
|
19
|
+
end
|
|
20
|
+
UI.user_error!("No .aab produced at #{AAB}") unless File.exist?(AAB)
|
|
21
|
+
AAB
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
desc "Build and upload to the internal testing track"
|
|
25
|
+
lane :beta do
|
|
26
|
+
upload_to_play_store(track: "internal", aab: build_aab("internal"), json_key_data: play_key, release_status: ENV.fetch("PLAY_RELEASE_STATUS", "completed"), skip_upload_metadata: true, skip_upload_images: true, skip_upload_screenshots: true)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
desc "Promote the latest internal build to production (staged rollout)"
|
|
30
|
+
lane :release do
|
|
31
|
+
upload_to_play_store(track: "internal", track_promote_to: "production", rollout: ENV.fetch("PLAY_ROLLOUT", "0.2"), json_key_data: play_key, skip_upload_changelogs: true, skip_upload_metadata: true, skip_upload_images: true, skip_upload_screenshots: true, skip_upload_aab: true)
|
|
32
|
+
end
|
|
33
|
+
end
|