@freeappstore/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 (62) hide show
  1. package/README.md +34 -0
  2. package/dist/commands/init.d.ts +3 -0
  3. package/dist/commands/init.d.ts.map +1 -0
  4. package/dist/commands/init.js +112 -0
  5. package/dist/commands/init.js.map +1 -0
  6. package/dist/commands/login.d.ts +3 -0
  7. package/dist/commands/login.d.ts.map +1 -0
  8. package/dist/commands/login.js +39 -0
  9. package/dist/commands/login.js.map +1 -0
  10. package/dist/commands/logout.d.ts +3 -0
  11. package/dist/commands/logout.d.ts.map +1 -0
  12. package/dist/commands/logout.js +12 -0
  13. package/dist/commands/logout.js.map +1 -0
  14. package/dist/commands/logs.d.ts +3 -0
  15. package/dist/commands/logs.d.ts.map +1 -0
  16. package/dist/commands/logs.js +33 -0
  17. package/dist/commands/logs.js.map +1 -0
  18. package/dist/commands/publish.d.ts +17 -0
  19. package/dist/commands/publish.d.ts.map +1 -0
  20. package/dist/commands/publish.js +233 -0
  21. package/dist/commands/publish.js.map +1 -0
  22. package/dist/commands/publish.test.d.ts +2 -0
  23. package/dist/commands/publish.test.d.ts.map +1 -0
  24. package/dist/commands/publish.test.js +73 -0
  25. package/dist/commands/publish.test.js.map +1 -0
  26. package/dist/commands/whoami.d.ts +3 -0
  27. package/dist/commands/whoami.d.ts.map +1 -0
  28. package/dist/commands/whoami.js +13 -0
  29. package/dist/commands/whoami.js.map +1 -0
  30. package/dist/index.d.ts +3 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +25 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/lib/app-id.d.ts +3 -0
  35. package/dist/lib/app-id.d.ts.map +1 -0
  36. package/dist/lib/app-id.js +16 -0
  37. package/dist/lib/app-id.js.map +1 -0
  38. package/dist/lib/app-id.test.d.ts +2 -0
  39. package/dist/lib/app-id.test.d.ts.map +1 -0
  40. package/dist/lib/app-id.test.js +45 -0
  41. package/dist/lib/app-id.test.js.map +1 -0
  42. package/dist/lib/apps.d.ts +28 -0
  43. package/dist/lib/apps.d.ts.map +1 -0
  44. package/dist/lib/apps.js +29 -0
  45. package/dist/lib/apps.js.map +1 -0
  46. package/dist/lib/apps.test.d.ts +2 -0
  47. package/dist/lib/apps.test.d.ts.map +1 -0
  48. package/dist/lib/apps.test.js +35 -0
  49. package/dist/lib/apps.test.js.map +1 -0
  50. package/dist/lib/config.d.ts +20 -0
  51. package/dist/lib/config.d.ts.map +1 -0
  52. package/dist/lib/config.js +24 -0
  53. package/dist/lib/config.js.map +1 -0
  54. package/dist/lib/github.d.ts +11 -0
  55. package/dist/lib/github.d.ts.map +1 -0
  56. package/dist/lib/github.js +62 -0
  57. package/dist/lib/github.js.map +1 -0
  58. package/dist/lib/open.d.ts +6 -0
  59. package/dist/lib/open.d.ts.map +1 -0
  60. package/dist/lib/open.js +31 -0
  61. package/dist/lib/open.js.map +1 -0
  62. package/package.json +47 -0
package/README.md ADDED
@@ -0,0 +1,34 @@
1
+ # @freeappstore/cli
2
+
3
+ The `fas` CLI for FreeAppStore creators.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm i -g @freeappstore/cli
9
+ ```
10
+
11
+ ## Commands
12
+
13
+ | Command | What it does |
14
+ |---|---|
15
+ | `fas login` | Sign in with GitHub via the device-authorization flow. Token cached at `~/.fas/config.json`. |
16
+ | `fas whoami` | Print the currently signed-in GitHub login. |
17
+ | `fas init <app-id> [--template standalone\|connected]` | Scaffold a new free app from one of the platform templates. |
18
+ | `fas publish` | Open the FreeAppStore publisher portal for the current repo (detected from `.git/config`). Pass `--no-open` to print the URL instead. |
19
+ | `fas logs <app-id>` | Tail the live logs for an app's Cloudflare Pages project (shells to `wrangler pages deployment tail`). |
20
+
21
+ ## Configuration
22
+
23
+ | Env var | Purpose |
24
+ |---|---|
25
+ | `FAS_API_BASE` | Override the API base URL. Defaults to `https://api.freeappstore.online`. Useful for local dev (`http://localhost:8787`). |
26
+ | `FAS_GITHUB_CLIENT_ID` | GitHub OAuth App client_id used by `fas login`. The platform-released CLI bakes this in; override only if you're testing a private build. |
27
+
28
+ ## How `fas publish` works
29
+
30
+ It does **not** call a custom API. It opens the existing publisher portal at `https://publish.freeappstore.online` with the current repo as a query param, and the portal handles the rest (CF Pages project creation, DNS, registry update). This keeps `fas` thin — the portal is the source of truth for provisioning.
31
+
32
+ ## License
33
+
34
+ MIT.
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare const initCommand: Command;
3
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAsCpC,eAAO,MAAM,WAAW,SAwCpB,CAAC"}
@@ -0,0 +1,112 @@
1
+ import { Command } from 'commander';
2
+ import { spawn } from 'node:child_process';
3
+ import { rm, access, readdir, readFile, writeFile } from 'node:fs/promises';
4
+ import { join, resolve, extname } from 'node:path';
5
+ import { assertValidAppId } from '../lib/app-id.js';
6
+ const TEMPLATES = {
7
+ standalone: 'freeappstore-online/template-standalone',
8
+ connected: 'freeappstore-online/template-connected',
9
+ };
10
+ // File extensions we'll text-substitute through. Anything else (images,
11
+ // fonts, etc.) is left as-is. Conservative — better to miss a substitution
12
+ // than to corrupt a binary.
13
+ const TEXT_EXTENSIONS = new Set([
14
+ '.md',
15
+ '.txt',
16
+ '.json',
17
+ '.ts',
18
+ '.tsx',
19
+ '.js',
20
+ '.jsx',
21
+ '.mjs',
22
+ '.cjs',
23
+ '.html',
24
+ '.htm',
25
+ '.css',
26
+ '.scss',
27
+ '.yaml',
28
+ '.yml',
29
+ '.toml',
30
+ '.svg',
31
+ ]);
32
+ const SKIP_DIRS = new Set(['.git', 'node_modules', 'dist', '.next', '.cache']);
33
+ export const initCommand = new Command('init')
34
+ .description('Scaffold a new free app from a template.')
35
+ .argument('<app-id>', 'Short app id (lowercase, single word). e.g. "calendar"')
36
+ .option('-t, --template <name>', 'Template: standalone | connected', 'standalone')
37
+ .action(async (appId, opts) => {
38
+ assertValidAppId(appId);
39
+ const template = opts.template;
40
+ if (!(template in TEMPLATES)) {
41
+ throw new Error(`Unknown template "${opts.template}". Choose: standalone, connected.`);
42
+ }
43
+ const target = resolve(process.cwd(), appId);
44
+ if (await exists(target)) {
45
+ throw new Error(`Directory "${appId}" already exists.`);
46
+ }
47
+ const repo = TEMPLATES[template];
48
+ process.stdout.write(`Cloning ${repo} → ${appId}/\n`);
49
+ await run('git', ['clone', '--depth=1', `https://github.com/${repo}.git`, target]);
50
+ await rm(join(target, '.git'), { recursive: true, force: true });
51
+ // Replace APPNAME placeholder throughout. The template documents this
52
+ // step in its README, but a CLI scaffold should never punt that on the
53
+ // user — the result has to be runnable as-is.
54
+ const replaced = await substituteAppName(target, appId);
55
+ await run('git', ['init', '-q', '-b', 'main'], target);
56
+ // Stage and commit the template so the new repo has a real first commit.
57
+ // Without this, `git push` after fas publish fails with "src refspec
58
+ // main does not match any" because main points at nothing.
59
+ await run('git', ['add', '-A'], target);
60
+ await run('git', ['commit', '-q', '-m', `Initial commit from ${template} template`], target);
61
+ process.stdout.write(`\n✓ Scaffolded ${appId}/ from ${template} template.\n`);
62
+ process.stdout.write(` Replaced APPNAME → ${appId} in ${replaced} file(s).\n`);
63
+ process.stdout.write(` Next: cd ${appId} && pnpm install && pnpm dev\n`);
64
+ });
65
+ async function substituteAppName(dir, appId) {
66
+ let count = 0;
67
+ for await (const file of walk(dir)) {
68
+ if (!TEXT_EXTENSIONS.has(extname(file).toLowerCase()))
69
+ continue;
70
+ const content = await readFile(file, 'utf8');
71
+ if (!content.includes('APPNAME'))
72
+ continue;
73
+ await writeFile(file, content.split('APPNAME').join(appId));
74
+ count++;
75
+ }
76
+ return count;
77
+ }
78
+ async function* walk(dir) {
79
+ const entries = await readdir(dir, { withFileTypes: true });
80
+ for (const entry of entries) {
81
+ if (entry.isDirectory()) {
82
+ if (SKIP_DIRS.has(entry.name))
83
+ continue;
84
+ yield* walk(join(dir, entry.name));
85
+ }
86
+ else if (entry.isFile()) {
87
+ yield join(dir, entry.name);
88
+ }
89
+ }
90
+ }
91
+ async function exists(path) {
92
+ try {
93
+ await access(path);
94
+ return true;
95
+ }
96
+ catch {
97
+ return false;
98
+ }
99
+ }
100
+ function run(cmd, args, cwd) {
101
+ return new Promise((resolveFn, rejectFn) => {
102
+ const child = spawn(cmd, args, { stdio: 'inherit', cwd });
103
+ child.on('exit', (code) => {
104
+ if (code === 0)
105
+ resolveFn();
106
+ else
107
+ rejectFn(new Error(`${cmd} exited with code ${code}`));
108
+ });
109
+ child.on('error', rejectFn);
110
+ });
111
+ }
112
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare const loginCommand: Command;
3
+ //# sourceMappingURL=login.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAUpC,eAAO,MAAM,YAAY,SAqCrB,CAAC"}
@@ -0,0 +1,39 @@
1
+ import { Command } from 'commander';
2
+ import { readConfig, writeConfig } from '../lib/config.js';
3
+ import { startDeviceFlow } from '../lib/github.js';
4
+ // Public client_id for the FreeAppStore CLI's GitHub OAuth App
5
+ // (https://github.com/organizations/freeappstore-online/settings/applications/3576238).
6
+ // Device-flow client_ids are not secret — the user_code/device_code is
7
+ // what authenticates the session. Override at runtime via FAS_GITHUB_CLIENT_ID.
8
+ const DEFAULT_CLIENT_ID = process.env['FAS_GITHUB_CLIENT_ID'] ?? 'Ov23liuUpYPXc1ikEFm2';
9
+ export const loginCommand = new Command('login')
10
+ .description('Sign in with GitHub.')
11
+ .action(async () => {
12
+ if (!DEFAULT_CLIENT_ID) {
13
+ throw new Error('GitHub client_id is not configured. The platform admin must register a GitHub OAuth App ' +
14
+ 'and set FAS_GITHUB_CLIENT_ID, or bake it into the published CLI build.');
15
+ }
16
+ const flow = await startDeviceFlow(DEFAULT_CLIENT_ID);
17
+ process.stdout.write(`\nOpen ${flow.verificationUri} and enter code: ${flow.userCode}\n\n`);
18
+ process.stdout.write('Waiting for authorization...\n');
19
+ const { accessToken, login } = await flow.poll();
20
+ const config = await readConfig();
21
+ // Swap the GitHub user-access-token for a fas session token. Subsequent
22
+ // CLI commands authenticate via the fas session, not the GitHub token.
23
+ const exchangeRes = await fetch(`${config.apiBase}/v1/auth/exchange`, {
24
+ method: 'POST',
25
+ headers: { 'Content-Type': 'application/json' },
26
+ body: JSON.stringify({ githubToken: accessToken }),
27
+ });
28
+ if (!exchangeRes.ok) {
29
+ throw new Error(`Auth exchange failed (${exchangeRes.status}): ${await exchangeRes.text()}`);
30
+ }
31
+ const { sessionToken } = (await exchangeRes.json());
32
+ await writeConfig({
33
+ ...config,
34
+ github: { accessToken, login, obtainedAt: Date.now() },
35
+ session: { token: sessionToken, obtainedAt: Date.now() },
36
+ });
37
+ process.stdout.write(`\n✓ Signed in as @${login}\n`);
38
+ });
39
+ //# sourceMappingURL=login.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,+DAA+D;AAC/D,wFAAwF;AACxF,uEAAuE;AACvE,gFAAgF;AAChF,MAAM,iBAAiB,GAAG,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,IAAI,sBAAsB,CAAC;AAExF,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC;KAC7C,WAAW,CAAC,sBAAsB,CAAC;KACnC,MAAM,CAAC,KAAK,IAAI,EAAE;IACjB,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CACb,0FAA0F;YACxF,wEAAwE,CAC3E,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,eAAe,CAAC,iBAAiB,CAAC,CAAC;IACtD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,IAAI,CAAC,eAAe,oBAAoB,IAAI,CAAC,QAAQ,MAAM,CAAC,CAAC;IAC5F,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,gCAAgC,CAAC,CAAC;IAEvD,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;IACjD,MAAM,MAAM,GAAG,MAAM,UAAU,EAAE,CAAC;IAElC,wEAAwE;IACxE,uEAAuE;IACvE,MAAM,WAAW,GAAG,MAAM,KAAK,CAAC,GAAG,MAAM,CAAC,OAAO,mBAAmB,EAAE;QACpE,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;QAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,WAAW,EAAE,WAAW,EAAE,CAAC;KACnD,CAAC,CAAC;IACH,IAAI,CAAC,WAAW,CAAC,EAAE,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CACb,yBAAyB,WAAW,CAAC,MAAM,MAAM,MAAM,WAAW,CAAC,IAAI,EAAE,EAAE,CAC5E,CAAC;IACJ,CAAC;IACD,MAAM,EAAE,YAAY,EAAE,GAAG,CAAC,MAAM,WAAW,CAAC,IAAI,EAAE,CAA6B,CAAC;IAEhF,MAAM,WAAW,CAAC;QAChB,GAAG,MAAM;QACT,MAAM,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE;QACtD,OAAO,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE;KACzD,CAAC,CAAC;IACH,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,qBAAqB,KAAK,IAAI,CAAC,CAAC;AACvD,CAAC,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare const logoutCommand: Command;
3
+ //# sourceMappingURL=logout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logout.d.ts","sourceRoot":"","sources":["../../src/commands/logout.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAKpC,eAAO,MAAM,aAAa,SAMtB,CAAC"}
@@ -0,0 +1,12 @@
1
+ import { Command } from 'commander';
2
+ import { rm } from 'node:fs/promises';
3
+ import { homedir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ export const logoutCommand = new Command('logout')
6
+ .description('Clear the local fas session and GitHub access token.')
7
+ .action(async () => {
8
+ const path = join(homedir(), '.fas', 'config.json');
9
+ await rm(path, { force: true });
10
+ process.stdout.write('Signed out. Run `fas login` to sign in again.\n');
11
+ });
12
+ //# sourceMappingURL=logout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logout.js","sourceRoot":"","sources":["../../src/commands/logout.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,CAAC,MAAM,aAAa,GAAG,IAAI,OAAO,CAAC,QAAQ,CAAC;KAC/C,WAAW,CAAC,sDAAsD,CAAC;KACnE,MAAM,CAAC,KAAK,IAAI,EAAE;IACjB,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,aAAa,CAAC,CAAC;IACpD,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAChC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iDAAiD,CAAC,CAAC;AAC1E,CAAC,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare const logsCommand: Command;
3
+ //# sourceMappingURL=logs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logs.d.ts","sourceRoot":"","sources":["../../src/commands/logs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAKpC,eAAO,MAAM,WAAW,SA0BpB,CAAC"}
@@ -0,0 +1,33 @@
1
+ import { Command } from 'commander';
2
+ import { spawn } from 'node:child_process';
3
+ import { assertValidAppId } from '../lib/app-id.js';
4
+ import { cfProjectFor } from '../lib/apps.js';
5
+ export const logsCommand = new Command('logs')
6
+ .description("Tail live logs for one of your apps' Cloudflare Pages project.")
7
+ .argument('<app-id>', 'Short app id (e.g. "calculator")')
8
+ .option('--cf-project <name>', 'Override the Cloudflare Pages project name.')
9
+ .action(async (appId, opts) => {
10
+ assertValidAppId(appId);
11
+ const cfProject = opts.cfProject ?? cfProjectFor(appId);
12
+ process.stdout.write(`Tailing logs for ${cfProject} (Ctrl+C to stop)...\n`);
13
+ await new Promise((resolveFn, rejectFn) => {
14
+ const child = spawn('wrangler', ['pages', 'deployment', 'tail', '--project-name', cfProject], {
15
+ stdio: 'inherit',
16
+ });
17
+ child.on('error', (err) => {
18
+ if (err.code === 'ENOENT') {
19
+ rejectFn(new Error('wrangler is not installed. Install it: npm i -g wrangler'));
20
+ }
21
+ else {
22
+ rejectFn(err);
23
+ }
24
+ });
25
+ child.on('exit', (code) => {
26
+ if (code === 0 || code === null)
27
+ resolveFn();
28
+ else
29
+ rejectFn(new Error(`wrangler exited with code ${code}`));
30
+ });
31
+ });
32
+ });
33
+ //# sourceMappingURL=logs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logs.js","sourceRoot":"","sources":["../../src/commands/logs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,OAAO,CAAC,MAAM,CAAC;KAC3C,WAAW,CAAC,gEAAgE,CAAC;KAC7E,QAAQ,CAAC,UAAU,EAAE,kCAAkC,CAAC;KACxD,MAAM,CAAC,qBAAqB,EAAE,6CAA6C,CAAC;KAC5E,MAAM,CAAC,KAAK,EAAE,KAAa,EAAE,IAA4B,EAAE,EAAE;IAC5D,gBAAgB,CAAC,KAAK,CAAC,CAAC;IACxB,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,YAAY,CAAC,KAAK,CAAC,CAAC;IAExD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,oBAAoB,SAAS,wBAAwB,CAAC,CAAC;IAE5E,MAAM,IAAI,OAAO,CAAO,CAAC,SAAS,EAAE,QAAQ,EAAE,EAAE;QAC9C,MAAM,KAAK,GAAG,KAAK,CAAC,UAAU,EAAE,CAAC,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,gBAAgB,EAAE,SAAS,CAAC,EAAE;YAC5F,KAAK,EAAE,SAAS;SACjB,CAAC,CAAC;QACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YACxB,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACrD,QAAQ,CAAC,IAAI,KAAK,CAAC,0DAA0D,CAAC,CAAC,CAAC;YAClF,CAAC;iBAAM,CAAC;gBACN,QAAQ,CAAC,GAAG,CAAC,CAAC;YAChB,CAAC;QACH,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YACxB,IAAI,IAAI,KAAK,CAAC,IAAI,IAAI,KAAK,IAAI;gBAAE,SAAS,EAAE,CAAC;;gBACxC,QAAQ,CAAC,IAAI,KAAK,CAAC,6BAA6B,IAAI,EAAE,CAAC,CAAC,CAAC;QAChE,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,17 @@
1
+ import { Command } from 'commander';
2
+ declare const CATEGORIES: readonly ["Learning", "Strategy", "Discovery", "Brain Training", "Social", "Productivity", "Health & Fitness", "Finance", "News & Weather", "Utilities", "Other (specify in description)"];
3
+ declare const TYPES: readonly ["Standalone (no backend, localStorage only)", "Connected (Firebase/Supabase backend, shared with Pro version)"];
4
+ interface SubmissionInput {
5
+ name: string;
6
+ category: (typeof CATEGORIES)[number];
7
+ type: (typeof TYPES)[number];
8
+ oneliner: string;
9
+ description: string;
10
+ repo: string | null;
11
+ demo: string | null;
12
+ }
13
+ export declare const publishCommand: Command;
14
+ export declare function buildSubmissionUrl(input: SubmissionInput): string;
15
+ export declare function parseGitHubRepo(url: string): string | null;
16
+ export {};
17
+ //# sourceMappingURL=publish.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publish.d.ts","sourceRoot":"","sources":["../../src/commands/publish.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAapC,QAAA,MAAM,UAAU,4LAYN,CAAC;AAEX,QAAA,MAAM,KAAK,2HAGD,CAAC;AAEX,UAAU,eAAe;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;IACtC,IAAI,EAAE,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB;AAED,eAAO,MAAM,cAAc,SA8HvB,CAAC;AAgDL,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,CAYjE;AAqCD,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI1D"}
@@ -0,0 +1,233 @@
1
+ import { Command } from 'commander';
2
+ import { spawn } from 'node:child_process';
3
+ import { readFile } from 'node:fs/promises';
4
+ import { join } from 'node:path';
5
+ import prompts from 'prompts';
6
+ import { openUrl } from '../lib/open.js';
7
+ import { assertValidAppId } from '../lib/app-id.js';
8
+ import { readConfig } from '../lib/config.js';
9
+ const SUBMISSION_URL = 'https://github.com/freeappstore-online/submissions/issues/new';
10
+ // Must match the dropdown options in
11
+ // freeappstore-online/submissions/.github/ISSUE_TEMPLATE/app-submission.yml
12
+ const CATEGORIES = [
13
+ 'Learning',
14
+ 'Strategy',
15
+ 'Discovery',
16
+ 'Brain Training',
17
+ 'Social',
18
+ 'Productivity',
19
+ 'Health & Fitness',
20
+ 'Finance',
21
+ 'News & Weather',
22
+ 'Utilities',
23
+ 'Other (specify in description)',
24
+ ];
25
+ const TYPES = [
26
+ 'Standalone (no backend, localStorage only)',
27
+ 'Connected (Firebase/Supabase backend, shared with Pro version)',
28
+ ];
29
+ export const publishCommand = new Command('publish')
30
+ .description('Publish this app to FreeAppStore. Provisions repo + hosting + DNS automatically. If auto-provision is unavailable, falls back to opening a prefilled submission Issue for admin review.')
31
+ .option('--no-open', 'Print the fallback Issue URL instead of opening a browser.')
32
+ .option('--issue', 'Skip auto-provision; always open the GitHub Issue form.')
33
+ .action(async (opts) => {
34
+ // Check auth BEFORE prompting — there's no point asking the user for
35
+ // 5 fields just to bail at the end with "not signed in". --issue
36
+ // skips this since the GitHub Issue form path doesn't need a session.
37
+ if (!opts.issue) {
38
+ const config = await readConfig();
39
+ if (!config.session?.token) {
40
+ process.stdout.write('\n⚠ Not signed in. Run: fas login\n' +
41
+ ' (or run `fas publish --issue` to submit via the GitHub Issue form instead.)\n');
42
+ process.exit(1);
43
+ }
44
+ }
45
+ const repo = await detectGitRepo();
46
+ const appName = await detectAppName();
47
+ const description = await detectDescription();
48
+ process.stdout.write('\nLet\'s publish your app to FreeAppStore.\n');
49
+ if (!repo && opts.issue) {
50
+ process.stdout.write('⚠ No GitHub origin detected. Push your repo to GitHub first, then run again.\n');
51
+ }
52
+ const answers = (await prompts([
53
+ {
54
+ type: 'text',
55
+ name: 'name',
56
+ message: 'App id (lowercase, used as subdomain)',
57
+ initial: appName ?? '',
58
+ validate: (value) => {
59
+ try {
60
+ assertValidAppId(value);
61
+ return true;
62
+ }
63
+ catch (e) {
64
+ return e instanceof Error ? e.message : 'invalid';
65
+ }
66
+ },
67
+ },
68
+ {
69
+ type: 'select',
70
+ name: 'category',
71
+ message: 'Category (one app per category — check freeappstore.online for what\'s taken)',
72
+ choices: CATEGORIES.map((c) => ({ title: c, value: c })),
73
+ },
74
+ {
75
+ type: 'select',
76
+ name: 'type',
77
+ message: 'App type',
78
+ choices: TYPES.map((t) => ({ title: t, value: t })),
79
+ },
80
+ {
81
+ type: 'text',
82
+ name: 'oneliner',
83
+ message: 'One-line description (shown on the storefront)',
84
+ initial: description ?? '',
85
+ validate: (v) => v.trim().length > 0 || 'required',
86
+ },
87
+ {
88
+ type: 'text',
89
+ name: 'demo',
90
+ message: 'Demo URL (optional, leave blank if none)',
91
+ },
92
+ ], {
93
+ onCancel: () => {
94
+ process.stdout.write('\nCanceled.\n');
95
+ process.exit(1);
96
+ },
97
+ }));
98
+ const input = {
99
+ name: answers.name,
100
+ category: answers.category,
101
+ type: answers.type,
102
+ oneliner: answers.oneliner,
103
+ // Reuse the oneliner as the body description for the Issue-form
104
+ // fallback. Auto-provision flow doesn't use it (admin's storefront
105
+ // uses oneliner directly).
106
+ description: answers.oneliner,
107
+ repo: repo ? `https://github.com/${repo}` : null,
108
+ demo: answers.demo?.trim() ? answers.demo : null,
109
+ };
110
+ // Try auto-provision first unless the user explicitly asked for the
111
+ // Issue-form fallback.
112
+ if (!opts.issue) {
113
+ const autoResult = await tryAutoProvision(input);
114
+ if (autoResult.kind === 'success') {
115
+ process.stdout.write(`\n✓ Provisioned!\n`);
116
+ process.stdout.write(` Live at: ${autoResult.appUrl}\n`);
117
+ process.stdout.write(` Repo: ${autoResult.repoUrl}\n\n`);
118
+ process.stdout.write(`Push your code:\n`);
119
+ process.stdout.write(` git remote add upstream ${autoResult.repoUrl}.git\n`);
120
+ process.stdout.write(` git push upstream main\n`);
121
+ return;
122
+ }
123
+ if (autoResult.kind === 'unauthorized') {
124
+ process.stdout.write(`\n⚠ Not signed in. Run: fas login\n`);
125
+ return;
126
+ }
127
+ process.stdout.write(`\n⚠ Auto-provision unavailable (${autoResult.reason}); falling back to Issue form.\n`);
128
+ }
129
+ // Fallback: prefilled GitHub Issue form for admin review.
130
+ const url = buildSubmissionUrl(input);
131
+ if (opts.open) {
132
+ process.stdout.write('\nOpening submission form on GitHub...\n');
133
+ process.stdout.write('Review the prefilled fields and click "Submit new issue".\n');
134
+ process.stdout.write('A maintainer will provision your app within ~48h.\n');
135
+ await openUrl(url);
136
+ }
137
+ else {
138
+ process.stdout.write(`\n${url}\n`);
139
+ }
140
+ });
141
+ async function tryAutoProvision(input) {
142
+ const config = await readConfig();
143
+ const sessionToken = config.session?.token;
144
+ if (!sessionToken)
145
+ return { kind: 'unauthorized', reason: 'no fas session' };
146
+ const typeShort = input.type.startsWith('Standalone') ? 'standalone' : 'connected';
147
+ const res = await fetch(`${config.apiBase}/v1/publish`, {
148
+ method: 'POST',
149
+ headers: {
150
+ Authorization: `Bearer ${sessionToken}`,
151
+ 'Content-Type': 'application/json',
152
+ },
153
+ body: JSON.stringify({
154
+ name: input.name,
155
+ category: input.category,
156
+ type: typeShort,
157
+ oneliner: input.oneliner,
158
+ description: input.description,
159
+ repo: input.repo,
160
+ demo: input.demo,
161
+ }),
162
+ });
163
+ if (res.status === 401)
164
+ return { kind: 'unauthorized', reason: 'session expired' };
165
+ if (res.status === 503) {
166
+ const body = (await res.json().catch(() => ({})));
167
+ return { kind: 'unconfigured', reason: body.error ?? '503' };
168
+ }
169
+ if (!res.ok) {
170
+ const body = await res.text();
171
+ return { kind: 'failed', reason: `${res.status}: ${body}` };
172
+ }
173
+ const result = (await res.json());
174
+ return { kind: 'success', appUrl: result.appUrl, repoUrl: result.repoUrl };
175
+ }
176
+ export function buildSubmissionUrl(input) {
177
+ const url = new URL(SUBMISSION_URL);
178
+ url.searchParams.set('template', 'app-submission.yml');
179
+ url.searchParams.set('title', `[Submission] ${input.name}`);
180
+ url.searchParams.set('name', input.name);
181
+ url.searchParams.set('category', input.category);
182
+ url.searchParams.set('type', input.type);
183
+ url.searchParams.set('oneliner', input.oneliner);
184
+ url.searchParams.set('description', input.description);
185
+ if (input.repo)
186
+ url.searchParams.set('repo', input.repo);
187
+ if (input.demo)
188
+ url.searchParams.set('demo', input.demo);
189
+ return url.toString();
190
+ }
191
+ async function detectAppName() {
192
+ try {
193
+ const raw = await readFile(join(process.cwd(), 'package.json'), 'utf8');
194
+ const pkg = JSON.parse(raw);
195
+ return pkg.name ?? null;
196
+ }
197
+ catch {
198
+ return null;
199
+ }
200
+ }
201
+ async function detectDescription() {
202
+ try {
203
+ const raw = await readFile(join(process.cwd(), 'package.json'), 'utf8');
204
+ const pkg = JSON.parse(raw);
205
+ return pkg.description ?? null;
206
+ }
207
+ catch {
208
+ return null;
209
+ }
210
+ }
211
+ function detectGitRepo() {
212
+ return new Promise((resolveFn) => {
213
+ const child = spawn('git', ['remote', 'get-url', 'origin'], {
214
+ stdio: ['ignore', 'pipe', 'ignore'],
215
+ });
216
+ let buf = '';
217
+ child.stdout.on('data', (chunk) => (buf += chunk.toString()));
218
+ child.on('close', (code) => {
219
+ if (code !== 0)
220
+ resolveFn(null);
221
+ else
222
+ resolveFn(parseGitHubRepo(buf.trim()));
223
+ });
224
+ child.on('error', () => resolveFn(null));
225
+ });
226
+ }
227
+ export function parseGitHubRepo(url) {
228
+ const m = /github\.com[:/]([\w.-]+)\/([\w.-]+?)(?:\.git)?\/?$/.exec(url);
229
+ if (!m || !m[1] || !m[2])
230
+ return null;
231
+ return `${m[1]}/${m[2]}`;
232
+ }
233
+ //# sourceMappingURL=publish.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publish.js","sourceRoot":"","sources":["../../src/commands/publish.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=publish.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publish.test.d.ts","sourceRoot":"","sources":["../../src/commands/publish.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,73 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { parseGitHubRepo, buildSubmissionUrl } from './publish.js';
3
+ describe('parseGitHubRepo', () => {
4
+ it('parses HTTPS clone URLs', () => {
5
+ expect(parseGitHubRepo('https://github.com/foo/bar')).toBe('foo/bar');
6
+ expect(parseGitHubRepo('https://github.com/foo/bar.git')).toBe('foo/bar');
7
+ expect(parseGitHubRepo('https://github.com/foo/bar/')).toBe('foo/bar');
8
+ expect(parseGitHubRepo('https://github.com/foo/bar.git/')).toBe('foo/bar');
9
+ });
10
+ it('parses SSH clone URLs', () => {
11
+ expect(parseGitHubRepo('git@github.com:foo/bar.git')).toBe('foo/bar');
12
+ expect(parseGitHubRepo('git@github.com:foo/bar')).toBe('foo/bar');
13
+ });
14
+ it('allows dots in repo names (e.g. node.js, foo.config)', () => {
15
+ expect(parseGitHubRepo('https://github.com/nodejs/node.js')).toBe('nodejs/node.js');
16
+ expect(parseGitHubRepo('git@github.com:nodejs/node.js.git')).toBe('nodejs/node.js');
17
+ });
18
+ it('allows hyphens, underscores, and digits', () => {
19
+ expect(parseGitHubRepo('https://github.com/foo-bar/baz_qux-2')).toBe('foo-bar/baz_qux-2');
20
+ });
21
+ it('returns null for non-GitHub URLs', () => {
22
+ expect(parseGitHubRepo('https://gitlab.com/foo/bar')).toBeNull();
23
+ expect(parseGitHubRepo('https://example.com/repo')).toBeNull();
24
+ expect(parseGitHubRepo('not a url')).toBeNull();
25
+ expect(parseGitHubRepo('')).toBeNull();
26
+ });
27
+ });
28
+ describe('buildSubmissionUrl', () => {
29
+ const baseInput = {
30
+ name: 'my-app',
31
+ category: 'Productivity',
32
+ type: 'Standalone (no backend, localStorage only)',
33
+ oneliner: 'A quick way to track tasks',
34
+ description: 'Detailed description here.',
35
+ repo: null,
36
+ demo: null,
37
+ };
38
+ it('builds a github.com submission URL with the right template', () => {
39
+ const url = new URL(buildSubmissionUrl(baseInput));
40
+ expect(url.host).toBe('github.com');
41
+ expect(url.pathname).toBe('/freeappstore-online/submissions/issues/new');
42
+ expect(url.searchParams.get('template')).toBe('app-submission.yml');
43
+ });
44
+ it('prefills every required template field', () => {
45
+ const url = new URL(buildSubmissionUrl(baseInput));
46
+ expect(url.searchParams.get('name')).toBe('my-app');
47
+ expect(url.searchParams.get('category')).toBe('Productivity');
48
+ expect(url.searchParams.get('type')).toBe(baseInput.type);
49
+ expect(url.searchParams.get('oneliner')).toBe('A quick way to track tasks');
50
+ expect(url.searchParams.get('description')).toBe('Detailed description here.');
51
+ expect(url.searchParams.get('title')).toBe('[Submission] my-app');
52
+ });
53
+ it('includes repo when present, omits when null', () => {
54
+ const withRepo = new URL(buildSubmissionUrl({ ...baseInput, repo: 'https://github.com/me/x' }));
55
+ expect(withRepo.searchParams.get('repo')).toBe('https://github.com/me/x');
56
+ const without = new URL(buildSubmissionUrl(baseInput));
57
+ expect(without.searchParams.has('repo')).toBe(false);
58
+ });
59
+ it('includes demo when present, omits when null', () => {
60
+ const withDemo = new URL(buildSubmissionUrl({ ...baseInput, demo: 'https://demo.example' }));
61
+ expect(withDemo.searchParams.get('demo')).toBe('https://demo.example');
62
+ const without = new URL(buildSubmissionUrl(baseInput));
63
+ expect(without.searchParams.has('demo')).toBe(false);
64
+ });
65
+ it('properly URL-encodes special characters in description', () => {
66
+ const url = new URL(buildSubmissionUrl({
67
+ ...baseInput,
68
+ description: 'A "quoted" thing & more (with parens)',
69
+ }));
70
+ expect(url.searchParams.get('description')).toBe('A "quoted" thing & more (with parens)');
71
+ });
72
+ });
73
+ //# sourceMappingURL=publish.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publish.test.js","sourceRoot":"","sources":["../../src/commands/publish.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare const whoamiCommand: Command;
3
+ //# sourceMappingURL=whoami.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"whoami.d.ts","sourceRoot":"","sources":["../../src/commands/whoami.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGpC,eAAO,MAAM,aAAa,SAStB,CAAC"}
@@ -0,0 +1,13 @@
1
+ import { Command } from 'commander';
2
+ import { readConfig } from '../lib/config.js';
3
+ export const whoamiCommand = new Command('whoami')
4
+ .description('Show the currently signed-in user.')
5
+ .action(async () => {
6
+ const config = await readConfig();
7
+ if (!config.github?.login) {
8
+ process.stdout.write('Not signed in. Run: fas login\n');
9
+ process.exit(1);
10
+ }
11
+ process.stdout.write(`@${config.github.login}\n`);
12
+ });
13
+ //# sourceMappingURL=whoami.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"whoami.js","sourceRoot":"","sources":["../../src/commands/whoami.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,MAAM,CAAC,MAAM,aAAa,GAAG,IAAI,OAAO,CAAC,QAAQ,CAAC;KAC/C,WAAW,CAAC,oCAAoC,CAAC;KACjD,MAAM,CAAC,KAAK,IAAI,EAAE;IACjB,MAAM,MAAM,GAAG,MAAM,UAAU,EAAE,CAAC;IAClC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC;QAC1B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iCAAiC,CAAC,CAAC;QACxD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC,CAAC;AACpD,CAAC,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { loginCommand } from './commands/login.js';
4
+ import { logoutCommand } from './commands/logout.js';
5
+ import { initCommand } from './commands/init.js';
6
+ import { publishCommand } from './commands/publish.js';
7
+ import { logsCommand } from './commands/logs.js';
8
+ import { whoamiCommand } from './commands/whoami.js';
9
+ const program = new Command();
10
+ program
11
+ .name('fas')
12
+ .description('FreeAppStore CLI — sign in, scaffold, and publish free apps.')
13
+ .version('0.0.0');
14
+ program.addCommand(loginCommand);
15
+ program.addCommand(logoutCommand);
16
+ program.addCommand(whoamiCommand);
17
+ program.addCommand(initCommand);
18
+ program.addCommand(publishCommand);
19
+ program.addCommand(logsCommand);
20
+ program.parseAsync().catch((err) => {
21
+ const msg = err instanceof Error ? err.message : String(err);
22
+ process.stderr.write(`fas: ${msg}\n`);
23
+ process.exit(1);
24
+ });
25
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,KAAK,CAAC;KACX,WAAW,CAAC,8DAA8D,CAAC;KAC3E,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;AACjC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;AAClC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;AAClC,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;AAChC,OAAO,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;AACnC,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;AAEhC,OAAO,CAAC,UAAU,EAAE,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;IAC1C,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC7D,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC;IACtC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ export declare function isValidAppId(s: string): boolean;
2
+ export declare function assertValidAppId(s: string): void;
3
+ //# sourceMappingURL=app-id.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-id.d.ts","sourceRoot":"","sources":["../../src/lib/app-id.ts"],"names":[],"mappings":"AAQA,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAE/C;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAMhD"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Validates an app id. The same shape is enforced by the publisher portal
3
+ * and used for DNS subdomains, CF Pages project naming, and repo names.
4
+ *
5
+ * Rules: lowercase letter first, then 1-30 of [a-z0-9-]. Total length 2-31.
6
+ */
7
+ const APP_ID_RE = /^[a-z][a-z0-9-]{1,30}$/;
8
+ export function isValidAppId(s) {
9
+ return APP_ID_RE.test(s);
10
+ }
11
+ export function assertValidAppId(s) {
12
+ if (!isValidAppId(s)) {
13
+ throw new Error('app-id must start with a lowercase letter and contain only lowercase letters, digits, or hyphens (2-31 chars).');
14
+ }
15
+ }
16
+ //# sourceMappingURL=app-id.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-id.js","sourceRoot":"","sources":["../../src/lib/app-id.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,SAAS,GAAG,wBAAwB,CAAC;AAE3C,MAAM,UAAU,YAAY,CAAC,CAAS;IACpC,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,CAAS;IACxC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CACb,gHAAgH,CACjH,CAAC;IACJ,CAAC;AACH,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=app-id.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-id.test.d.ts","sourceRoot":"","sources":["../../src/lib/app-id.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,45 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { isValidAppId, assertValidAppId } from './app-id.js';
3
+ describe('isValidAppId', () => {
4
+ it('accepts typical short app ids', () => {
5
+ expect(isValidAppId('chess')).toBe(true);
6
+ expect(isValidAppId('calculator')).toBe(true);
7
+ expect(isValidAppId('todo')).toBe(true);
8
+ });
9
+ it('accepts ids with digits and hyphens', () => {
10
+ expect(isValidAppId('app2')).toBe(true);
11
+ expect(isValidAppId('my-app')).toBe(true);
12
+ expect(isValidAppId('a1-b2-c3')).toBe(true);
13
+ });
14
+ it('rejects single-char ids (must be 2-31 chars total)', () => {
15
+ expect(isValidAppId('a')).toBe(false);
16
+ });
17
+ it('accepts 31-char ids and rejects 32+', () => {
18
+ expect(isValidAppId('a' + 'b'.repeat(30))).toBe(true); // 31 total
19
+ expect(isValidAppId('a' + 'b'.repeat(31))).toBe(false); // 32 total
20
+ });
21
+ it('rejects ids that start with a digit, hyphen, or uppercase', () => {
22
+ expect(isValidAppId('1app')).toBe(false);
23
+ expect(isValidAppId('-app')).toBe(false);
24
+ expect(isValidAppId('App')).toBe(false);
25
+ });
26
+ it('rejects ids with disallowed characters', () => {
27
+ expect(isValidAppId('my_app')).toBe(false); // underscore
28
+ expect(isValidAppId('my.app')).toBe(false); // dot
29
+ expect(isValidAppId('my app')).toBe(false); // space
30
+ expect(isValidAppId('my/app')).toBe(false); // slash
31
+ expect(isValidAppId('myapp!')).toBe(false);
32
+ });
33
+ it('rejects empty string', () => {
34
+ expect(isValidAppId('')).toBe(false);
35
+ });
36
+ });
37
+ describe('assertValidAppId', () => {
38
+ it('throws with a clear message on invalid input', () => {
39
+ expect(() => assertValidAppId('Bad')).toThrow(/lowercase/);
40
+ });
41
+ it('does not throw on valid input', () => {
42
+ expect(() => assertValidAppId('good-app')).not.toThrow();
43
+ });
44
+ });
45
+ //# sourceMappingURL=app-id.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-id.test.js","sourceRoot":"","sources":["../../src/lib/app-id.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Canonical mapping for apps published on freeappstore.online.
3
+ *
4
+ * Most apps follow the convention `app_id` → CF Pages project `free<id>app`,
5
+ * which is what we infer when an entry isn't here. But several legacy
6
+ * projects use ad-hoc names (music, puzzle, freeappstore itself) and CF
7
+ * Pages project names cannot be renamed after creation, so we have to keep
8
+ * a hand-maintained override list.
9
+ *
10
+ * This is the registry the audit recommended (P2 in the original review),
11
+ * scoped to just the dev tooling for now. Long-term it should live in the
12
+ * storefront repo and be fetched at runtime so it never goes stale.
13
+ */
14
+ export interface AppRecord {
15
+ cfProject: string;
16
+ subdomain: string;
17
+ }
18
+ export declare const APPS: Record<string, AppRecord>;
19
+ /**
20
+ * Returns the CF Pages project name for an app id. Prefers the registry,
21
+ * falls back to the `free<id>app` convention.
22
+ */
23
+ export declare function cfProjectFor(appId: string): string;
24
+ /**
25
+ * Returns the public URL for an app id.
26
+ */
27
+ export declare function urlFor(appId: string): string;
28
+ //# sourceMappingURL=apps.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apps.d.ts","sourceRoot":"","sources":["../../src/lib/apps.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,eAAO,MAAM,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAc1C,CAAC;AAEF;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAElD;AAED;;GAEG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAG5C"}
@@ -0,0 +1,29 @@
1
+ export const APPS = {
2
+ // Convention-following entries (cfProject === `free${id}app`) are listed
3
+ // for documentation / completeness; the cfProject() helper would derive
4
+ // them anyway. Add new apps here when they don't follow the convention.
5
+ chess: { cfProject: 'freechessapp', subdomain: 'chess.freeappstore.online' },
6
+ language: { cfProject: 'freelanguageapp', subdomain: 'language.freeappstore.online' },
7
+ math: { cfProject: 'freemathapp', subdomain: 'math.freeappstore.online' },
8
+ quiz: { cfProject: 'freequizapp', subdomain: 'quiz.freeappstore.online' },
9
+ books: { cfProject: 'freebooksapp', subdomain: 'books.freeappstore.online' },
10
+ // Legacy / non-conventional names — cannot be renamed.
11
+ music: { cfProject: 'freemusic', subdomain: 'music.freeappstore.online' },
12
+ puzzle: { cfProject: 'freepuzzle', subdomain: 'puzzle.freeappstore.online' },
13
+ freeappstore: { cfProject: 'freeappstore', subdomain: 'freeappstore.online' },
14
+ };
15
+ /**
16
+ * Returns the CF Pages project name for an app id. Prefers the registry,
17
+ * falls back to the `free<id>app` convention.
18
+ */
19
+ export function cfProjectFor(appId) {
20
+ return APPS[appId]?.cfProject ?? `free${appId}app`;
21
+ }
22
+ /**
23
+ * Returns the public URL for an app id.
24
+ */
25
+ export function urlFor(appId) {
26
+ const subdomain = APPS[appId]?.subdomain ?? `${appId}.freeappstore.online`;
27
+ return `https://${subdomain}`;
28
+ }
29
+ //# sourceMappingURL=apps.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apps.js","sourceRoot":"","sources":["../../src/lib/apps.ts"],"names":[],"mappings":"AAkBA,MAAM,CAAC,MAAM,IAAI,GAA8B;IAC7C,yEAAyE;IACzE,wEAAwE;IACxE,wEAAwE;IACxE,KAAK,EAAE,EAAE,SAAS,EAAE,cAAc,EAAE,SAAS,EAAE,2BAA2B,EAAE;IAC5E,QAAQ,EAAE,EAAE,SAAS,EAAE,iBAAiB,EAAE,SAAS,EAAE,8BAA8B,EAAE;IACrF,IAAI,EAAE,EAAE,SAAS,EAAE,aAAa,EAAE,SAAS,EAAE,0BAA0B,EAAE;IACzE,IAAI,EAAE,EAAE,SAAS,EAAE,aAAa,EAAE,SAAS,EAAE,0BAA0B,EAAE;IACzE,KAAK,EAAE,EAAE,SAAS,EAAE,cAAc,EAAE,SAAS,EAAE,2BAA2B,EAAE;IAE5E,uDAAuD;IACvD,KAAK,EAAE,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,2BAA2B,EAAE;IACzE,MAAM,EAAE,EAAE,SAAS,EAAE,YAAY,EAAE,SAAS,EAAE,4BAA4B,EAAE;IAC5E,YAAY,EAAE,EAAE,SAAS,EAAE,cAAc,EAAE,SAAS,EAAE,qBAAqB,EAAE;CAC9E,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,OAAO,IAAI,CAAC,KAAK,CAAC,EAAE,SAAS,IAAI,OAAO,KAAK,KAAK,CAAC;AACrD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,MAAM,CAAC,KAAa;IAClC,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,SAAS,IAAI,GAAG,KAAK,sBAAsB,CAAC;IAC3E,OAAO,WAAW,SAAS,EAAE,CAAC;AAChC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=apps.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apps.test.d.ts","sourceRoot":"","sources":["../../src/lib/apps.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,35 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { APPS, cfProjectFor, urlFor } from './apps.js';
3
+ describe('cfProjectFor', () => {
4
+ it('returns the registered cfProject for known legacy names', () => {
5
+ expect(cfProjectFor('music')).toBe('freemusic');
6
+ expect(cfProjectFor('puzzle')).toBe('freepuzzle');
7
+ expect(cfProjectFor('freeappstore')).toBe('freeappstore');
8
+ });
9
+ it('returns the registered cfProject for known convention-following names', () => {
10
+ expect(cfProjectFor('chess')).toBe('freechessapp');
11
+ expect(cfProjectFor('quiz')).toBe('freequizapp');
12
+ });
13
+ it('falls back to free<id>app convention for unknown apps', () => {
14
+ expect(cfProjectFor('whateverapp')).toBe('freewhateverappapp');
15
+ expect(cfProjectFor('todo')).toBe('freetodoapp');
16
+ });
17
+ });
18
+ describe('urlFor', () => {
19
+ it('returns the registered subdomain for known apps', () => {
20
+ expect(urlFor('music')).toBe('https://music.freeappstore.online');
21
+ expect(urlFor('freeappstore')).toBe('https://freeappstore.online');
22
+ });
23
+ it('falls back to <id>.freeappstore.online', () => {
24
+ expect(urlFor('todo')).toBe('https://todo.freeappstore.online');
25
+ });
26
+ });
27
+ describe('APPS registry', () => {
28
+ it('all entries have non-empty cfProject and subdomain', () => {
29
+ for (const [id, record] of Object.entries(APPS)) {
30
+ expect(record.cfProject, `cfProject for ${id}`).toBeTruthy();
31
+ expect(record.subdomain, `subdomain for ${id}`).toBeTruthy();
32
+ }
33
+ });
34
+ });
35
+ //# sourceMappingURL=apps.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apps.test.js","sourceRoot":"","sources":["../../src/lib/apps.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAEvD,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,EAAE,CAAC,yDAAyD,EAAE,GAAG,EAAE;QACjE,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAChD,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAClD,MAAM,CAAC,YAAY,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uEAAuE,EAAE,GAAG,EAAE;QAC/E,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QACnD,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;QAC/D,MAAM,CAAC,YAAY,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;QAC/D,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,QAAQ,EAAE,GAAG,EAAE;IACtB,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;QACzD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,mCAAmC,CAAC,CAAC;QAClE,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;IACrE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QAChD,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,kCAAkC,CAAC,CAAC;IAClE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC5D,KAAK,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YAChD,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,iBAAiB,EAAE,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC;YAC7D,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,iBAAiB,EAAE,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC;QAC/D,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,20 @@
1
+ export interface FasConfig {
2
+ apiBase: string;
3
+ github?: {
4
+ accessToken: string;
5
+ login: string;
6
+ obtainedAt: number;
7
+ };
8
+ /**
9
+ * fas session token, minted by /v1/auth/exchange. Used as a Bearer for
10
+ * authenticated calls to the platform API. Lives 30 days; if expired,
11
+ * `fas login` mints a new one.
12
+ */
13
+ session?: {
14
+ token: string;
15
+ obtainedAt: number;
16
+ };
17
+ }
18
+ export declare function readConfig(): Promise<FasConfig>;
19
+ export declare function writeConfig(config: FasConfig): Promise<void>;
20
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE;QACP,WAAW,EAAE,MAAM,CAAC;QACpB,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;IACF;;;;OAIG;IACH,OAAO,CAAC,EAAE;QACR,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;CACH;AAMD,wBAAsB,UAAU,IAAI,OAAO,CAAC,SAAS,CAAC,CAQrD;AAED,wBAAsB,WAAW,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAIlE"}
@@ -0,0 +1,24 @@
1
+ import { homedir } from 'node:os';
2
+ import { join } from 'node:path';
3
+ import { mkdir, readFile, writeFile, chmod } from 'node:fs/promises';
4
+ const CONFIG_DIR = join(homedir(), '.fas');
5
+ const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
6
+ const DEFAULT_CONFIG = {
7
+ apiBase: process.env['FAS_API_BASE'] ?? 'https://api.freeappstore.online',
8
+ };
9
+ export async function readConfig() {
10
+ try {
11
+ const raw = await readFile(CONFIG_FILE, 'utf8');
12
+ const parsed = JSON.parse(raw);
13
+ return { ...DEFAULT_CONFIG, ...parsed };
14
+ }
15
+ catch {
16
+ return { ...DEFAULT_CONFIG };
17
+ }
18
+ }
19
+ export async function writeConfig(config) {
20
+ await mkdir(CONFIG_DIR, { recursive: true, mode: 0o700 });
21
+ await writeFile(CONFIG_FILE, JSON.stringify(config, null, 2), { mode: 0o600 });
22
+ await chmod(CONFIG_FILE, 0o600);
23
+ }
24
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAErE,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,MAAM,CAAC,CAAC;AAC3C,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;AAoBpD,MAAM,cAAc,GAAc;IAChC,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,iCAAiC;CAC1E,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,UAAU;IAC9B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;QAChD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAuB,CAAC;QACrD,OAAO,EAAE,GAAG,cAAc,EAAE,GAAG,MAAM,EAAE,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,GAAG,cAAc,EAAE,CAAC;IAC/B,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,MAAiB;IACjD,MAAM,KAAK,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC1D,MAAM,SAAS,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC/E,MAAM,KAAK,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;AAClC,CAAC"}
@@ -0,0 +1,11 @@
1
+ export interface DeviceFlowStart {
2
+ userCode: string;
3
+ verificationUri: string;
4
+ expiresAt: number;
5
+ poll: () => Promise<{
6
+ accessToken: string;
7
+ login: string;
8
+ }>;
9
+ }
10
+ export declare function startDeviceFlow(clientId: string): Promise<DeviceFlowStart>;
11
+ //# sourceMappingURL=github.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github.d.ts","sourceRoot":"","sources":["../../src/lib/github.ts"],"names":[],"mappings":"AAiBA,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,OAAO,CAAC;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC7D;AAED,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CA+ChF"}
@@ -0,0 +1,62 @@
1
+ // GitHub Device Authorization Grant flow.
2
+ // Spec: https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps#device-flow
3
+ export async function startDeviceFlow(clientId) {
4
+ const codeRes = await fetch('https://github.com/login/device/code', {
5
+ method: 'POST',
6
+ headers: { Accept: 'application/json', 'Content-Type': 'application/json' },
7
+ body: JSON.stringify({ client_id: clientId, scope: 'read:user' }),
8
+ });
9
+ if (!codeRes.ok) {
10
+ throw new Error(`GitHub device-code request failed: ${codeRes.status}`);
11
+ }
12
+ const code = (await codeRes.json());
13
+ let interval = code.interval;
14
+ const expiresAt = Date.now() + code.expires_in * 1000;
15
+ return {
16
+ userCode: code.user_code,
17
+ verificationUri: code.verification_uri,
18
+ expiresAt,
19
+ poll: async () => {
20
+ while (Date.now() < expiresAt) {
21
+ await sleep(interval * 1000);
22
+ const tokenRes = await fetch('https://github.com/login/oauth/access_token', {
23
+ method: 'POST',
24
+ headers: { Accept: 'application/json', 'Content-Type': 'application/json' },
25
+ body: JSON.stringify({
26
+ client_id: clientId,
27
+ device_code: code.device_code,
28
+ grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
29
+ }),
30
+ });
31
+ const token = (await tokenRes.json());
32
+ if (token.access_token) {
33
+ const login = await fetchLogin(token.access_token);
34
+ return { accessToken: token.access_token, login };
35
+ }
36
+ if (token.error === 'slow_down') {
37
+ // Per RFC 8628 §3.5, client MUST increase interval by at least 5s on slow_down,
38
+ // even if the response omits a new `interval` value.
39
+ interval = token.interval ?? interval + 5;
40
+ continue;
41
+ }
42
+ if (token.error === 'authorization_pending')
43
+ continue;
44
+ throw new Error(`GitHub authorization failed: ${token.error ?? 'unknown'}`);
45
+ }
46
+ throw new Error('Device code expired before authorization completed.');
47
+ },
48
+ };
49
+ }
50
+ async function fetchLogin(token) {
51
+ const res = await fetch('https://api.github.com/user', {
52
+ headers: { Authorization: `Bearer ${token}`, Accept: 'application/vnd.github+json' },
53
+ });
54
+ if (!res.ok)
55
+ throw new Error(`Failed to fetch GitHub user: ${res.status}`);
56
+ const user = (await res.json());
57
+ return user.login;
58
+ }
59
+ function sleep(ms) {
60
+ return new Promise((resolve) => setTimeout(resolve, ms));
61
+ }
62
+ //# sourceMappingURL=github.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github.js","sourceRoot":"","sources":["../../src/lib/github.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Open a URL in the user's default browser. Doesn't wait for the browser to
3
+ * exit — it just hands off to the OS opener and resolves once that's spawned.
4
+ */
5
+ export declare function openUrl(url: string): Promise<void>;
6
+ //# sourceMappingURL=open.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"open.d.ts","sourceRoot":"","sources":["../../src/lib/open.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAUlD"}
@@ -0,0 +1,31 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { platform } from 'node:os';
3
+ /**
4
+ * Open a URL in the user's default browser. Doesn't wait for the browser to
5
+ * exit — it just hands off to the OS opener and resolves once that's spawned.
6
+ */
7
+ export function openUrl(url) {
8
+ const { cmd, args } = openerFor(url);
9
+ return new Promise((resolveFn, rejectFn) => {
10
+ const child = spawn(cmd, args, { stdio: 'ignore', detached: true });
11
+ child.on('error', rejectFn);
12
+ child.on('spawn', () => {
13
+ child.unref();
14
+ resolveFn();
15
+ });
16
+ });
17
+ }
18
+ function openerFor(url) {
19
+ switch (platform()) {
20
+ case 'darwin':
21
+ return { cmd: 'open', args: [url] };
22
+ case 'win32':
23
+ // `start` is a cmd builtin, not an executable, so we can't spawn it
24
+ // directly. The empty `""` is the window-title arg `start` requires
25
+ // when its target is a quoted string.
26
+ return { cmd: 'cmd.exe', args: ['/c', 'start', '""', url] };
27
+ default:
28
+ return { cmd: 'xdg-open', args: [url] };
29
+ }
30
+ }
31
+ //# sourceMappingURL=open.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"open.js","sourceRoot":"","sources":["../../src/lib/open.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEnC;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAC,GAAW;IACjC,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;IACrC,OAAO,IAAI,OAAO,CAAC,CAAC,SAAS,EAAE,QAAQ,EAAE,EAAE;QACzC,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;QACpE,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QAC5B,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YACrB,KAAK,CAAC,KAAK,EAAE,CAAC;YACd,SAAS,EAAE,CAAC;QACd,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,SAAS,CAAC,GAAW;IAC5B,QAAQ,QAAQ,EAAE,EAAE,CAAC;QACnB,KAAK,QAAQ;YACX,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC;QACtC,KAAK,OAAO;YACV,oEAAoE;YACpE,oEAAoE;YACpE,sCAAsC;YACtC,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC;QAC9D;YACE,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC;IAC5C,CAAC;AACH,CAAC"}
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@freeappstore/cli",
3
+ "version": "0.1.0",
4
+ "description": "fas — CLI for publishing free apps to freeappstore.online",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": {
8
+ "fas": "./dist/index.js"
9
+ },
10
+ "homepage": "https://github.com/freeappstore-online/sdk#readme",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/freeappstore-online/sdk.git",
14
+ "directory": "packages/cli"
15
+ },
16
+ "bugs": {
17
+ "url": "https://github.com/freeappstore-online/sdk/issues"
18
+ },
19
+ "keywords": [
20
+ "freeappstore",
21
+ "cli",
22
+ "scaffold",
23
+ "publish",
24
+ "cloudflare-pages"
25
+ ],
26
+ "files": [
27
+ "dist",
28
+ "README.md"
29
+ ],
30
+ "engines": {
31
+ "node": ">=22"
32
+ },
33
+ "scripts": {
34
+ "build": "tsc",
35
+ "typecheck": "tsc --noEmit",
36
+ "dev": "tsc --watch"
37
+ },
38
+ "dependencies": {
39
+ "commander": "^12.1.0",
40
+ "prompts": "^2.4.2"
41
+ },
42
+ "devDependencies": {
43
+ "@types/node": "^22.0.0",
44
+ "@types/prompts": "^2.4.9",
45
+ "typescript": "^5.7.0"
46
+ }
47
+ }