@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.
Files changed (65) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +19 -0
  3. package/dist/bin.d.ts +3 -0
  4. package/dist/bin.d.ts.map +1 -0
  5. package/dist/bin.js +26 -0
  6. package/dist/bin.js.map +1 -0
  7. package/dist/commands/add.d.ts +6 -0
  8. package/dist/commands/add.d.ts.map +1 -0
  9. package/dist/commands/add.js +44 -0
  10. package/dist/commands/add.js.map +1 -0
  11. package/dist/commands/doctor.d.ts +2 -0
  12. package/dist/commands/doctor.d.ts.map +1 -0
  13. package/dist/commands/doctor.js +97 -0
  14. package/dist/commands/doctor.js.map +1 -0
  15. package/dist/commands/init.d.ts +5 -0
  16. package/dist/commands/init.d.ts.map +1 -0
  17. package/dist/commands/init.js +20 -0
  18. package/dist/commands/init.js.map +1 -0
  19. package/dist/commands/migrate.d.ts +6 -0
  20. package/dist/commands/migrate.d.ts.map +1 -0
  21. package/dist/commands/migrate.js +37 -0
  22. package/dist/commands/migrate.js.map +1 -0
  23. package/dist/commands/modules.d.ts +14 -0
  24. package/dist/commands/modules.d.ts.map +1 -0
  25. package/dist/commands/modules.js +104 -0
  26. package/dist/commands/modules.js.map +1 -0
  27. package/dist/commands/secrets.d.ts +10 -0
  28. package/dist/commands/secrets.d.ts.map +1 -0
  29. package/dist/commands/secrets.js +55 -0
  30. package/dist/commands/secrets.js.map +1 -0
  31. package/dist/commands/update.d.ts +4 -0
  32. package/dist/commands/update.d.ts.map +1 -0
  33. package/dist/commands/update.js +57 -0
  34. package/dist/commands/update.js.map +1 -0
  35. package/dist/modules.d.ts +42 -0
  36. package/dist/modules.d.ts.map +1 -0
  37. package/dist/modules.js +69 -0
  38. package/dist/modules.js.map +1 -0
  39. package/dist/util.d.ts +38 -0
  40. package/dist/util.d.ts.map +1 -0
  41. package/dist/util.js +74 -0
  42. package/dist/util.js.map +1 -0
  43. package/package.json +34 -0
  44. package/secrets.schema.json +27 -0
  45. package/templates/AGENTS.md +31 -0
  46. package/templates/CLAUDE.md +3 -0
  47. package/templates/GEMINI.md +3 -0
  48. package/templates/gitignore +19 -0
  49. package/templates/modules/deploy-cloudflare/files/deploy-api.yml +45 -0
  50. package/templates/modules/deploy-cloudflare/files/deploy-web.yml +44 -0
  51. package/templates/modules/deploy-cloudflare/files/web-wrangler.jsonc +10 -0
  52. package/templates/modules/deploy-cloudflare/module.json +21 -0
  53. package/templates/modules/deploy-cloudflare/secrets.json +7 -0
  54. package/templates/modules/deploy-fastlane/files/Gemfile +3 -0
  55. package/templates/modules/deploy-fastlane/files/android/Appfile +1 -0
  56. package/templates/modules/deploy-fastlane/files/android/Fastfile +33 -0
  57. package/templates/modules/deploy-fastlane/files/android/key.properties.example +5 -0
  58. package/templates/modules/deploy-fastlane/files/ios/Appfile +2 -0
  59. package/templates/modules/deploy-fastlane/files/ios/ExportOptions.plist +19 -0
  60. package/templates/modules/deploy-fastlane/files/ios/Fastfile +52 -0
  61. package/templates/modules/deploy-fastlane/files/ios/Matchfile +5 -0
  62. package/templates/modules/deploy-fastlane/files/mobile-release.yml +87 -0
  63. package/templates/modules/deploy-fastlane/module.json +29 -0
  64. package/templates/modules/deploy-fastlane/secrets.json +16 -0
  65. 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"}
@@ -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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"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"}
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,3 @@
1
+ # CLAUDE.md
2
+
3
+ Read `AGENTS.md`. It is the canonical guide for this repository.
@@ -0,0 +1,3 @@
1
+ # GEMINI.md
2
+
3
+ Read `AGENTS.md`. It is the canonical guide for this repository.
@@ -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,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "fastlane", "~> 2.240"
@@ -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
@@ -0,0 +1,5 @@
1
+ # Copy to key.properties (gitignored) for local release builds. CI writes it from secrets.
2
+ storePassword=
3
+ keyPassword=
4
+ keyAlias=upload
5
+ storeFile=upload-keystore.jks
@@ -0,0 +1,2 @@
1
+ app_identifier("{{BUNDLE_ID}}")
2
+ team_id("{{APPLE_TEAM_ID}}")