@owlmeans/create-app 0.1.10

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 (67) hide show
  1. package/README.md +59 -0
  2. package/build/args.d.ts +21 -0
  3. package/build/args.d.ts.map +1 -0
  4. package/build/args.js +96 -0
  5. package/build/args.js.map +1 -0
  6. package/build/bin.d.ts +3 -0
  7. package/build/bin.d.ts.map +1 -0
  8. package/build/bin.js +21 -0
  9. package/build/bin.js.map +1 -0
  10. package/build/index.d.ts +6 -0
  11. package/build/index.d.ts.map +1 -0
  12. package/build/index.js +4 -0
  13. package/build/index.js.map +1 -0
  14. package/build/run.d.ts +3 -0
  15. package/build/run.d.ts.map +1 -0
  16. package/build/run.js +81 -0
  17. package/build/run.js.map +1 -0
  18. package/build/template.d.ts +17 -0
  19. package/build/template.d.ts.map +1 -0
  20. package/build/template.js +49 -0
  21. package/build/template.js.map +1 -0
  22. package/package.json +43 -0
  23. package/template/README.md +51 -0
  24. package/template/_gitignore +7 -0
  25. package/template/package.json +21 -0
  26. package/template/sources/api/package.json +23 -0
  27. package/template/sources/api/src/app/session/add.ts +20 -0
  28. package/template/sources/api/src/app/session/index.ts +3 -0
  29. package/template/sources/api/src/app/session/list.ts +15 -0
  30. package/template/sources/api/src/app/session/remove.ts +18 -0
  31. package/template/sources/api/src/config.ts +8 -0
  32. package/template/sources/api/src/consts.ts +2 -0
  33. package/template/sources/api/src/context.ts +14 -0
  34. package/template/sources/api/src/index.ts +9 -0
  35. package/template/sources/api/src/modules.ts +11 -0
  36. package/template/sources/api/src/types.ts +6 -0
  37. package/template/sources/api/tsconfig.json +11 -0
  38. package/template/sources/common/package.json +34 -0
  39. package/template/sources/common/src/config.ts +24 -0
  40. package/template/sources/common/src/consts.ts +21 -0
  41. package/template/sources/common/src/index.ts +5 -0
  42. package/template/sources/common/src/modules.ts +26 -0
  43. package/template/sources/common/src/schemas.ts +30 -0
  44. package/template/sources/common/src/types.ts +19 -0
  45. package/template/sources/common/tsconfig.json +8 -0
  46. package/template/sources/web/index.html +15 -0
  47. package/template/sources/web/package.json +49 -0
  48. package/template/sources/web/src/components/nav/main.tsx +15 -0
  49. package/template/sources/web/src/components/ui/alert.tsx +70 -0
  50. package/template/sources/web/src/components/ui/button.tsx +60 -0
  51. package/template/sources/web/src/components/ui/card.tsx +93 -0
  52. package/template/sources/web/src/components/ui/input.tsx +22 -0
  53. package/template/sources/web/src/components/ui/label.tsx +23 -0
  54. package/template/sources/web/src/components/ui/progress.tsx +44 -0
  55. package/template/sources/web/src/config.ts +7 -0
  56. package/template/sources/web/src/context.ts +7 -0
  57. package/template/sources/web/src/index.css +77 -0
  58. package/template/sources/web/src/index.tsx +15 -0
  59. package/template/sources/web/src/layout/main.tsx +21 -0
  60. package/template/sources/web/src/lib/utils.ts +6 -0
  61. package/template/sources/web/src/modules.ts +20 -0
  62. package/template/sources/web/src/render.tsx +8 -0
  63. package/template/sources/web/src/screens/home.tsx +24 -0
  64. package/template/sources/web/src/screens/session.tsx +94 -0
  65. package/template/sources/web/src/types.ts +5 -0
  66. package/template/sources/web/tsconfig.json +17 -0
  67. package/template/sources/web/vite.config.ts +29 -0
package/README.md ADDED
@@ -0,0 +1,59 @@
1
+ # @owlmeans/create-app
2
+
3
+ Scaffold a minimal **fullstack OwlMeans Common** app in one command.
4
+
5
+ ```sh
6
+ npm create @owlmeans/app@latest my-app
7
+ # or
8
+ bun create @owlmeans/app my-app
9
+ # or
10
+ yarn create @owlmeans/app my-app
11
+ # or
12
+ npx @owlmeans/create-app my-app
13
+ ```
14
+
15
+ ## What it generates
16
+
17
+ A bun-workspace monorepo with three packages and **no authentication**:
18
+
19
+ ```
20
+ my-app/
21
+ ├── sources/
22
+ │ ├── common/ # shared entrypoints (routes), schemas and types
23
+ │ ├── api/ # @owlmeans/server-app backend; session data in an in-memory static resource
24
+ │ └── web/ # @owlmeans/web-panel + shadcn UI: navigation, layout and screens
25
+ ```
26
+
27
+ The web app ships a basic shadcn UI **navigation + layout** and a **Session** screen
28
+ that creates, lists and removes items held in a **session-scoped in-memory resource**
29
+ (`@owlmeans/static-resource`) on the backend — no database required.
30
+
31
+ By default the scaffolder also installs dependencies and **deploys agent guidance**
32
+ into the project via [`@owlmeans/agent-skills`](https://www.npmjs.com/package/@owlmeans/agent-skills)
33
+ (`.claude/skills/` + `.github/instructions/`).
34
+
35
+ ## Options
36
+
37
+ | Flag | Description |
38
+ |------|-------------|
39
+ | `--name <name>` | Human-readable app name (default: derived from the target dir) |
40
+ | `--pm <bun\|npm\|yarn>` | Package manager (default: `bun`) |
41
+ | `--no-install` | Skip dependency installation |
42
+ | `--no-skills` | Skip the `@owlmeans/agent-skills` deploy |
43
+ | `--no-git` | Skip `git init` |
44
+ | `--yes`, `-y` | Proceed without prompts / into a non-empty directory |
45
+ | `--help`, `-h` | Show help |
46
+
47
+ ## Running the generated app
48
+
49
+ ```sh
50
+ cd my-app
51
+ bun install # if you passed --no-install
52
+ bun run dev # api on :3000, web on :3001
53
+ ```
54
+
55
+ ## Doing it manually instead
56
+
57
+ Prefer to wire it up by hand? Follow
58
+ [docs/getting-started.md](https://github.com/owlmeans/common/blob/main/docs/getting-started.md),
59
+ which documents both the scaffolded and the manual path step by step.
@@ -0,0 +1,21 @@
1
+ export type PackageManager = 'bun' | 'npm' | 'yarn';
2
+ export interface CreateArgs {
3
+ /** Target directory (positional). `null` means it must be prompted/derived. */
4
+ dir: string | null;
5
+ /** Human-readable app name. Defaults to the directory slug. */
6
+ name: string | null;
7
+ /** Package manager used to install deps and (for `bun`) run the project. */
8
+ pm: PackageManager;
9
+ /** Install dependencies after copying the template. */
10
+ install: boolean;
11
+ /** Deploy agent skills via @owlmeans/agent-skills after install. */
12
+ skills: boolean;
13
+ /** Run `git init` in the new project. */
14
+ git: boolean;
15
+ /** Skip confirmations / proceed into a non-empty directory. */
16
+ yes: boolean;
17
+ help: boolean;
18
+ }
19
+ export declare const parseArgs: (argv: string[]) => CreateArgs | null;
20
+ export declare const printHelp: () => void;
21
+ //# sourceMappingURL=args.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,CAAA;AAEnD,MAAM,WAAW,UAAU;IACzB,+EAA+E;IAC/E,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB,+DAA+D;IAC/D,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,4EAA4E;IAC5E,EAAE,EAAE,cAAc,CAAA;IAClB,uDAAuD;IACvD,OAAO,EAAE,OAAO,CAAA;IAChB,oEAAoE;IACpE,MAAM,EAAE,OAAO,CAAA;IACf,yCAAyC;IACzC,GAAG,EAAE,OAAO,CAAA;IACZ,+DAA+D;IAC/D,GAAG,EAAE,OAAO,CAAA;IACZ,IAAI,EAAE,OAAO,CAAA;CACd;AA0BD,eAAO,MAAM,SAAS,GAAI,MAAM,MAAM,EAAE,KAAG,UAAU,GAAG,IAsEvD,CAAA;AAED,eAAO,MAAM,SAAS,QAAO,IAE5B,CAAA"}
package/build/args.js ADDED
@@ -0,0 +1,96 @@
1
+ const HELP = `@owlmeans/create-app — scaffold a fullstack OwlMeans Common app
2
+
3
+ Usage:
4
+ npm create @owlmeans/app@latest <dir> [options]
5
+ bun create @owlmeans/app <dir> [options]
6
+ yarn create @owlmeans/app <dir> [options]
7
+ npx @owlmeans/create-app <dir> [options]
8
+
9
+ Generates three workspaces (common + api + web) with shadcn UI navigation and
10
+ layout, no authentication, and a session-scoped in-memory resource on the
11
+ backend. Agent skills are deployed into the project by default.
12
+
13
+ Options:
14
+ --name <name> human-readable app name (default: derived from <dir>)
15
+ --pm <bun|npm|yarn> package manager (default: bun)
16
+ --no-install do not install dependencies
17
+ --no-skills do not deploy agent skills via @owlmeans/agent-skills
18
+ --no-git do not run "git init"
19
+ --yes, -y proceed without prompts / into a non-empty directory
20
+ --help, -h show this help
21
+ `;
22
+ const PMS = ['bun', 'npm', 'yarn'];
23
+ export const parseArgs = (argv) => {
24
+ const args = argv.slice(2);
25
+ const result = {
26
+ dir: null,
27
+ name: null,
28
+ pm: 'bun',
29
+ install: true,
30
+ skills: true,
31
+ git: true,
32
+ yes: false,
33
+ help: false,
34
+ };
35
+ let i = 0;
36
+ while (i < args.length) {
37
+ const a = args[i];
38
+ switch (a) {
39
+ case '--help':
40
+ case '-h':
41
+ result.help = true;
42
+ break;
43
+ case '--yes':
44
+ case '-y':
45
+ result.yes = true;
46
+ break;
47
+ case '--no-install':
48
+ result.install = false;
49
+ break;
50
+ case '--no-skills':
51
+ result.skills = false;
52
+ break;
53
+ case '--no-git':
54
+ result.git = false;
55
+ break;
56
+ case '--name': {
57
+ const v = args[i + 1];
58
+ if (v == null || v.startsWith('-')) {
59
+ process.stderr.write('error: --name requires a value\n');
60
+ return null;
61
+ }
62
+ result.name = v;
63
+ i++;
64
+ break;
65
+ }
66
+ case '--pm': {
67
+ const v = args[i + 1];
68
+ if (v == null || !PMS.includes(v)) {
69
+ process.stderr.write(`error: --pm must be one of ${PMS.join(', ')}\n`);
70
+ return null;
71
+ }
72
+ result.pm = v;
73
+ i++;
74
+ break;
75
+ }
76
+ default:
77
+ if (a.startsWith('-')) {
78
+ process.stderr.write(`error: unknown flag ${a}\n`);
79
+ return null;
80
+ }
81
+ if (result.dir == null) {
82
+ result.dir = a;
83
+ }
84
+ else {
85
+ process.stderr.write(`error: unexpected argument ${a}\n`);
86
+ return null;
87
+ }
88
+ }
89
+ i++;
90
+ }
91
+ return result;
92
+ };
93
+ export const printHelp = () => {
94
+ process.stdout.write(HELP);
95
+ };
96
+ //# sourceMappingURL=args.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"args.js","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"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"}
package/build/bin.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=bin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":""}
package/build/bin.js ADDED
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+ import { parseArgs, printHelp } from './args.js';
3
+ import { run } from './run.js';
4
+ const args = parseArgs(process.argv);
5
+ if (args == null) {
6
+ process.stderr.write('Run with --help for usage.\n');
7
+ process.exit(2);
8
+ }
9
+ else if (args.help) {
10
+ printHelp();
11
+ process.exit(0);
12
+ }
13
+ else {
14
+ run(args).then(code => {
15
+ process.exit(code);
16
+ }).catch((err) => {
17
+ process.stderr.write(`fatal: ${err instanceof Error ? err.message : String(err)}\n`);
18
+ process.exit(1);
19
+ });
20
+ }
21
+ //# sourceMappingURL=bin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAChD,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAE9B,MAAM,IAAI,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;AAEpC,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC;IACjB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAAA;IACpD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AACjB,CAAC;KAAM,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;IACrB,SAAS,EAAE,CAAA;IACX,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AACjB,CAAC;KAAM,CAAC;IACN,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;QACpB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACpB,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;QACxB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;QACpF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC,CAAC,CAAA;AACJ,CAAC"}
@@ -0,0 +1,6 @@
1
+ export { run } from './run.js';
2
+ export { parseArgs, printHelp } from './args.js';
3
+ export type { CreateArgs, PackageManager } from './args.js';
4
+ export { copyTemplate, templateDir, isEmptyDir } from './template.js';
5
+ export type { TemplateReplacements } from './template.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAC9B,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAChD,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAC3D,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AACrE,YAAY,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAA"}
package/build/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { run } from './run.js';
2
+ export { parseArgs, printHelp } from './args.js';
3
+ export { copyTemplate, templateDir, isEmptyDir } from './template.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAC9B,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAEhD,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA"}
package/build/run.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import type { CreateArgs } from './args.js';
2
+ export declare const run: (args: CreateArgs) => Promise<number>;
3
+ //# sourceMappingURL=run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAkB,MAAM,WAAW,CAAA;AAiB3D,eAAO,MAAM,GAAG,GAAU,MAAM,UAAU,KAAG,OAAO,CAAC,MAAM,CAwE1D,CAAA"}
package/build/run.js ADDED
@@ -0,0 +1,81 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { basename, resolve } from 'node:path';
3
+ import { run as installSkills } from '@owlmeans/agent-skills';
4
+ import { copyTemplate, isEmptyDir, templateDir } from './template.js';
5
+ const slugify = (input) => basename(input)
6
+ .toLowerCase()
7
+ .replace(/[^a-z0-9-]+/g, '-')
8
+ .replace(/^-+|-+$/g, '')
9
+ || 'owlmeans-app';
10
+ const titleize = (slug) => slug.split('-').filter(Boolean).map(w => w[0].toUpperCase() + w.slice(1)).join(' ');
11
+ const installArgs = (pm) => pm === 'yarn' ? [] : ['install'];
12
+ const log = (msg) => { process.stdout.write(msg + '\n'); };
13
+ export const run = async (args) => {
14
+ if (args.dir == null) {
15
+ process.stderr.write('error: target directory is required, e.g. `create-app my-app`\n');
16
+ return 2;
17
+ }
18
+ const dest = resolve(args.dir);
19
+ const slug = slugify(args.dir);
20
+ const name = args.name ?? titleize(slug);
21
+ if (!isEmptyDir(dest) && !args.yes) {
22
+ process.stderr.write(`error: ${dest} is not empty. Re-run with --yes to scaffold into it anyway.\n`);
23
+ return 1;
24
+ }
25
+ log(`\nScaffolding OwlMeans app "${name}" into ${dest}\n`);
26
+ copyTemplate(templateDir(), dest, { slug, name });
27
+ log(' ✓ template copied (common + api + web)');
28
+ if (args.git) {
29
+ const git = spawnSync('git', ['init', '-q'], { cwd: dest, stdio: 'ignore' });
30
+ if (git.status === 0)
31
+ log(' ✓ git repository initialized');
32
+ }
33
+ if (args.install) {
34
+ log(`\nInstalling dependencies with ${args.pm}…`);
35
+ const res = spawnSync(args.pm, installArgs(args.pm), { cwd: dest, stdio: 'inherit' });
36
+ if (res.status !== 0) {
37
+ process.stderr.write(`\nwarning: "${args.pm} install" failed — run it manually in ${dest}.\n`);
38
+ }
39
+ else {
40
+ log(' ✓ dependencies installed');
41
+ }
42
+ }
43
+ if (args.skills) {
44
+ if (!args.install) {
45
+ log('\nSkipping agent-skills deploy (requires installed dependencies — re-run with install).');
46
+ }
47
+ else {
48
+ log('\nDeploying agent skills via @owlmeans/agent-skills…');
49
+ try {
50
+ const result = await installSkills({
51
+ dir: dest,
52
+ yes: true,
53
+ only: [],
54
+ claudeOnly: false,
55
+ copilotOnly: false,
56
+ extras: true,
57
+ force: false,
58
+ dryRun: false,
59
+ help: false,
60
+ });
61
+ if (result.code !== 0) {
62
+ process.stderr.write(` agent-skills exited with code ${result.code} — you can re-run \`npx @owlmeans/agent-skills\` later.\n`);
63
+ }
64
+ }
65
+ catch (err) {
66
+ process.stderr.write(` agent-skills failed: ${err instanceof Error ? err.message : String(err)}\n`);
67
+ }
68
+ }
69
+ }
70
+ const runCmd = args.pm === 'npm' ? 'npm run dev' : `${args.pm} run dev`;
71
+ const installCmd = args.pm === 'yarn' ? 'yarn' : `${args.pm} install`;
72
+ log(`\nDone. Next steps:\n`);
73
+ log(` cd ${args.dir}`);
74
+ if (!args.install)
75
+ log(` ${installCmd}`);
76
+ log(` ${runCmd}\n`);
77
+ log('The web app starts on http://localhost:3001 and the API on http://localhost:3000.');
78
+ log('Open the "Session" page to exercise the in-memory session resource.\n');
79
+ return 0;
80
+ };
81
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import { renameSync } from 'node:fs';
2
+ export interface TemplateReplacements {
3
+ /** package/workspace slug, e.g. `my-app` */
4
+ slug: string;
5
+ /** human-readable name, e.g. `My App` */
6
+ name: string;
7
+ }
8
+ /** Absolute path to the bundled `template/` directory (sibling of `build/`). */
9
+ export declare const templateDir: () => string;
10
+ /**
11
+ * Recursively copy `src` → `dest`, substituting `__APP_SLUG__` / `__APP_NAME__`
12
+ * in text files and renaming shipped dotfiles (`_gitignore` → `.gitignore`).
13
+ */
14
+ export declare const copyTemplate: (src: string, dest: string, r: TemplateReplacements) => void;
15
+ export declare const isEmptyDir: (dir: string) => boolean;
16
+ export { renameSync };
17
+ //# sourceMappingURL=template.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../src/template.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4D,UAAU,EAA2B,MAAM,SAAS,CAAA;AAcvH,MAAM,WAAW,oBAAoB;IACnC,4CAA4C;IAC5C,IAAI,EAAE,MAAM,CAAA;IACZ,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAA;CACb;AAED,gFAAgF;AAChF,eAAO,MAAM,WAAW,QAAO,MACqC,CAAA;AAYpE;;;GAGG;AACH,eAAO,MAAM,YAAY,GAAI,KAAK,MAAM,EAAE,MAAM,MAAM,EAAE,GAAG,oBAAoB,KAAG,IAoBjF,CAAA;AAED,eAAO,MAAM,UAAU,GAAI,KAAK,MAAM,KAAG,OAGxC,CAAA;AAED,OAAO,EAAE,UAAU,EAAE,CAAA"}
@@ -0,0 +1,49 @@
1
+ import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, statSync, writeFileSync } from 'node:fs';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { dirname, join, resolve } from 'node:path';
4
+ /** Dotfiles are shipped with an underscore prefix so npm does not strip them from the tarball. */
5
+ const DOTFILE_RENAMES = {
6
+ '_gitignore': '.gitignore',
7
+ '_npmrc': '.npmrc',
8
+ '_env': '.env',
9
+ };
10
+ /** Files whose contents are binary or must not be string-substituted. */
11
+ const BINARY_EXT = new Set(['.ico', '.png', '.jpg', '.jpeg', '.gif', '.webp', '.woff', '.woff2']);
12
+ /** Absolute path to the bundled `template/` directory (sibling of `build/`). */
13
+ export const templateDir = () => resolve(dirname(fileURLToPath(import.meta.url)), '..', 'template');
14
+ const applyReplacements = (content, r) => content
15
+ .replaceAll('__APP_SLUG__', r.slug)
16
+ .replaceAll('__APP_NAME__', r.name);
17
+ const isBinary = (file) => {
18
+ const dot = file.lastIndexOf('.');
19
+ return dot >= 0 && BINARY_EXT.has(file.slice(dot).toLowerCase());
20
+ };
21
+ /**
22
+ * Recursively copy `src` → `dest`, substituting `__APP_SLUG__` / `__APP_NAME__`
23
+ * in text files and renaming shipped dotfiles (`_gitignore` → `.gitignore`).
24
+ */
25
+ export const copyTemplate = (src, dest, r) => {
26
+ mkdirSync(dest, { recursive: true });
27
+ for (const entry of readdirSync(src)) {
28
+ const from = join(src, entry);
29
+ const targetName = DOTFILE_RENAMES[entry] ?? entry;
30
+ const to = join(dest, targetName);
31
+ if (statSync(from).isDirectory()) {
32
+ copyTemplate(from, to, r);
33
+ continue;
34
+ }
35
+ if (isBinary(from)) {
36
+ cpSync(from, to);
37
+ continue;
38
+ }
39
+ const content = applyReplacements(readFileSync(from, 'utf8'), r);
40
+ writeFileSync(to, content);
41
+ }
42
+ };
43
+ export const isEmptyDir = (dir) => {
44
+ if (!existsSync(dir))
45
+ return true;
46
+ return readdirSync(dir).length === 0;
47
+ };
48
+ export { renameSync };
49
+ //# sourceMappingURL=template.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template.js","sourceRoot":"","sources":["../src/template.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AACvH,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAElD,kGAAkG;AAClG,MAAM,eAAe,GAA2B;IAC9C,YAAY,EAAE,YAAY;IAC1B,QAAQ,EAAE,QAAQ;IAClB,MAAM,EAAE,MAAM;CACf,CAAA;AAED,yEAAyE;AACzE,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAA;AASjG,gFAAgF;AAChF,MAAM,CAAC,MAAM,WAAW,GAAG,GAAW,EAAE,CACtC,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,CAAC,CAAA;AAEpE,MAAM,iBAAiB,GAAG,CAAC,OAAe,EAAE,CAAuB,EAAU,EAAE,CAC7E,OAAO;KACJ,UAAU,CAAC,cAAc,EAAE,CAAC,CAAC,IAAI,CAAC;KAClC,UAAU,CAAC,cAAc,EAAE,CAAC,CAAC,IAAI,CAAC,CAAA;AAEvC,MAAM,QAAQ,GAAG,CAAC,IAAY,EAAW,EAAE;IACzC,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAA;IACjC,OAAO,GAAG,IAAI,CAAC,IAAI,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC,CAAA;AAClE,CAAC,CAAA;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,GAAW,EAAE,IAAY,EAAE,CAAuB,EAAQ,EAAE;IACvF,SAAS,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IACpC,KAAK,MAAM,KAAK,IAAI,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC;QACrC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;QAC7B,MAAM,UAAU,GAAG,eAAe,CAAC,KAAK,CAAC,IAAI,KAAK,CAAA;QAClD,MAAM,EAAE,GAAG,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,CAAA;QAEjC,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;YACjC,YAAY,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,CAAA;YACzB,SAAQ;QACV,CAAC;QAED,IAAI,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YACnB,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;YAChB,SAAQ;QACV,CAAC;QAED,MAAM,OAAO,GAAG,iBAAiB,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,CAAA;QAChE,aAAa,CAAC,EAAE,EAAE,OAAO,CAAC,CAAA;IAC5B,CAAC;AACH,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,GAAW,EAAW,EAAE;IACjD,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAA;IACjC,OAAO,WAAW,CAAC,GAAG,CAAC,CAAC,MAAM,KAAK,CAAC,CAAA;AACtC,CAAC,CAAA;AAED,OAAO,EAAE,UAAU,EAAE,CAAA"}
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@owlmeans/create-app",
3
+ "version": "0.1.10",
4
+ "license": "MIT",
5
+ "description": "Scaffold a minimal fullstack OwlMeans Common app — common + api + web workspaces, shadcn UI navigation/layout, no auth, and a session-scoped in-memory resource. Deploys agent skills via @owlmeans/agent-skills by default.",
6
+ "type": "module",
7
+ "scripts": {
8
+ "build": "tsc -b",
9
+ "dev": "nodemon -e ts,tsx,json --watch src --exec \"tsc -p ./tsconfig.json\"",
10
+ "watch": "tsc -b -w --preserveWatchOutput --pretty"
11
+ },
12
+ "bin": {
13
+ "create-app": "./build/bin.js"
14
+ },
15
+ "main": "build/index.js",
16
+ "module": "build/index.js",
17
+ "types": "build/index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "import": "./build/index.js",
21
+ "require": "./build/index.js",
22
+ "default": "./build/index.js",
23
+ "module": "./build/index.js",
24
+ "types": "./build/index.d.ts"
25
+ }
26
+ },
27
+ "files": [
28
+ "build",
29
+ "template"
30
+ ],
31
+ "dependencies": {
32
+ "@owlmeans/agent-skills": "^0.1.10"
33
+ },
34
+ "devDependencies": {
35
+ "@owlmeans/dep-config": "workspace:*",
36
+ "@types/node": "^24.10.1",
37
+ "nodemon": "^3.1.11",
38
+ "typescript": "^6.0.2"
39
+ },
40
+ "publishConfig": {
41
+ "access": "public"
42
+ }
43
+ }
@@ -0,0 +1,51 @@
1
+ # __APP_NAME__
2
+
3
+ A minimal fullstack [OwlMeans Common](https://github.com/owlmeans/common) app, scaffolded with
4
+ [`@owlmeans/create-app`](https://www.npmjs.com/package/@owlmeans/create-app).
5
+
6
+ No authentication; session data lives in an **in-memory static resource** on the backend.
7
+
8
+ ## Workspaces
9
+
10
+ ```
11
+ sources/
12
+ ├── common/ # shared entrypoints (routes), schemas and types — used by api AND web
13
+ ├── api/ # @owlmeans/server-app backend; session items in @owlmeans/static-resource
14
+ └── web/ # @owlmeans/web-panel + shadcn UI: navigation, layout and screens
15
+ ```
16
+
17
+ ## Develop
18
+
19
+ ```sh
20
+ bun install
21
+ bun run dev
22
+ ```
23
+
24
+ - API: http://localhost:3000
25
+ - Web: http://localhost:3001
26
+
27
+ Open the **Session** page and add/remove items — they are stored per browser session
28
+ (a `sid` kept in `localStorage`) in an in-memory resource on the API. Restarting the API
29
+ clears them; opening a different browser/incognito window gets an isolated session.
30
+
31
+ ## How it fits together
32
+
33
+ 1. **`sources/common`** declares the API routes as OwlMeans *entrypoints* (`session.list`,
34
+ `session.add`, `session.remove`) plus their AJV schemas and shared types.
35
+ 2. **`sources/api`** registers a `@owlmeans/static-resource` in its context and `elevate()`s
36
+ each entrypoint with a handler that does CRUD against it, keyed by the session id.
37
+ 3. **`sources/web`** `elevate()`s the same entrypoints to screen components and calls them with
38
+ `context.entrypoint(alias).call({ params, body })`.
39
+
40
+ See [OwlMeans getting-started guide](https://github.com/owlmeans/common/blob/main/docs/getting-started.md)
41
+ for a full walkthrough and the manual (non-scaffolded) version of this project.
42
+
43
+ ## Agent guidance
44
+
45
+ This project ships Claude Code skills (`.claude/skills/`) and GitHub Copilot instructions
46
+ (`.github/instructions/`) for the OwlMeans framework. Refresh them after upgrading
47
+ `@owlmeans/*` packages:
48
+
49
+ ```sh
50
+ npx @owlmeans/agent-skills
51
+ ```
@@ -0,0 +1,7 @@
1
+ node_modules
2
+ build
3
+ dist
4
+ *.tsbuildinfo
5
+ .DS_Store
6
+ bun.lock
7
+ bun.lockb
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "__APP_SLUG__",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "packageManager": "bun@1.3.10",
7
+ "workspaces": [
8
+ "sources/*"
9
+ ],
10
+ "scripts": {
11
+ "dev": "bun run --filter './sources/*' --parallel dev",
12
+ "build": "bun run --filter './sources/*' build",
13
+ "typecheck": "bun run --filter './sources/*' typecheck"
14
+ },
15
+ "overrides": {
16
+ "react-router": "^7.*"
17
+ },
18
+ "devDependencies": {
19
+ "nodemon": "^3.1.11"
20
+ }
21
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "__APP_SLUG__-api",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "main": "build/index.js",
7
+ "scripts": {
8
+ "dev": "sleep 3 && bun --watch --preserve-symlinks src/index.ts",
9
+ "start": "bun src/index.ts",
10
+ "build": "tsc -b",
11
+ "typecheck": "tsc -b"
12
+ },
13
+ "dependencies": {
14
+ "@owlmeans/server-app": "^0.1.9",
15
+ "@owlmeans/static-resource": "^0.1.9",
16
+ "__APP_SLUG__-common": "workspace:^"
17
+ },
18
+ "devDependencies": {
19
+ "@owlmeans/dep-config": "^0.1.9",
20
+ "@types/node": "^24.10.1",
21
+ "typescript": "^5.9.2"
22
+ }
23
+ }
@@ -0,0 +1,20 @@
1
+ import { randomUUID } from 'node:crypto'
2
+ import { handleBody } from '@owlmeans/server-app'
3
+ import type { AddItemPayload, SessionItem, SessionParams } from '__APP_SLUG__-common'
4
+ import { SESSION_ITEMS } from '../../consts.js'
5
+ import type { Context } from '../../types.js'
6
+
7
+ export const add = handleBody<AddItemPayload>(async (payload, context, req) => {
8
+ const ctx = context as Context
9
+ const { sid } = req.params as SessionParams
10
+ const resource = ctx.getStaticResource<SessionItem>(SESSION_ITEMS)
11
+
12
+ const item: SessionItem = {
13
+ id: randomUUID(),
14
+ sessionId: sid,
15
+ text: payload.text,
16
+ createdAt: new Date().toISOString(),
17
+ }
18
+
19
+ return await resource.create(item)
20
+ })
@@ -0,0 +1,3 @@
1
+ export { list } from './list.js'
2
+ export { add } from './add.js'
3
+ export { remove } from './remove.js'
@@ -0,0 +1,15 @@
1
+ import { handleParams } from '@owlmeans/server-app'
2
+ import type { SessionItem, SessionParams } from '__APP_SLUG__-common'
3
+ import { SESSION_ITEMS } from '../../consts.js'
4
+ import type { Context } from '../../types.js'
5
+
6
+ export const list = handleParams<SessionParams>(async (params, context) => {
7
+ const ctx = context as Context
8
+ const resource = ctx.getStaticResource<SessionItem>(SESSION_ITEMS)
9
+
10
+ // The static resource lists every record; filter to this session and sort newest first.
11
+ const { items } = await resource.list<SessionItem>()
12
+ return items
13
+ .filter(item => item.sessionId === params.sid)
14
+ .sort((a, b) => (a.createdAt < b.createdAt ? 1 : -1))
15
+ })
@@ -0,0 +1,18 @@
1
+ import { handleParams } from '@owlmeans/server-app'
2
+ import type { ItemParams, SessionItem } from '__APP_SLUG__-common'
3
+ import { SESSION_ITEMS } from '../../consts.js'
4
+ import type { Context } from '../../types.js'
5
+
6
+ export const remove = handleParams<ItemParams>(async (params, context) => {
7
+ const ctx = context as Context
8
+ const resource = ctx.getStaticResource<SessionItem>(SESSION_ITEMS)
9
+
10
+ const existing = await resource.load<SessionItem>(params.id)
11
+ // Only remove the item if it belongs to the requesting session.
12
+ if (existing == null || existing.sessionId !== params.sid) {
13
+ return { removed: false }
14
+ }
15
+
16
+ await resource.delete(params.id)
17
+ return { removed: true }
18
+ })
@@ -0,0 +1,8 @@
1
+ import { config } from '@owlmeans/server-app'
2
+ import { APP_API, commonConfig } from '__APP_SLUG__-common'
3
+ import type { Config } from './types.js'
4
+
5
+ // The server listens on the APP_API service route's port (3000), declared in common/config.ts.
6
+ const cfg = config<Config>(APP_API, commonConfig as Config)
7
+
8
+ export default cfg
@@ -0,0 +1,2 @@
1
+ /** Alias of the in-memory resource that holds session items. */
2
+ export const SESSION_ITEMS = 'session-items'