spinup-ts 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 (138) hide show
  1. package/README.md +82 -0
  2. package/dist/index.d.ts +3 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +96 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/prompts.d.ts +17 -0
  7. package/dist/prompts.d.ts.map +1 -0
  8. package/dist/prompts.js +264 -0
  9. package/dist/prompts.js.map +1 -0
  10. package/dist/scaffold.d.ts +5 -0
  11. package/dist/scaffold.d.ts.map +1 -0
  12. package/dist/scaffold.js +128 -0
  13. package/dist/scaffold.js.map +1 -0
  14. package/dist/transforms/devcontainer.d.ts +3 -0
  15. package/dist/transforms/devcontainer.d.ts.map +1 -0
  16. package/dist/transforms/devcontainer.js +8 -0
  17. package/dist/transforms/devcontainer.js.map +1 -0
  18. package/dist/transforms/docker.d.ts +3 -0
  19. package/dist/transforms/docker.d.ts.map +1 -0
  20. package/dist/transforms/docker.js +11 -0
  21. package/dist/transforms/docker.js.map +1 -0
  22. package/dist/transforms/docs.d.ts +3 -0
  23. package/dist/transforms/docs.d.ts.map +1 -0
  24. package/dist/transforms/docs.js +22 -0
  25. package/dist/transforms/docs.js.map +1 -0
  26. package/dist/transforms/documentation.d.ts +3 -0
  27. package/dist/transforms/documentation.d.ts.map +1 -0
  28. package/dist/transforms/documentation.js +37 -0
  29. package/dist/transforms/documentation.js.map +1 -0
  30. package/dist/transforms/github-actions.d.ts +3 -0
  31. package/dist/transforms/github-actions.d.ts.map +1 -0
  32. package/dist/transforms/github-actions.js +48 -0
  33. package/dist/transforms/github-actions.js.map +1 -0
  34. package/dist/transforms/index.d.ts +3 -0
  35. package/dist/transforms/index.d.ts.map +1 -0
  36. package/dist/transforms/index.js +24 -0
  37. package/dist/transforms/index.js.map +1 -0
  38. package/dist/transforms/license.d.ts +3 -0
  39. package/dist/transforms/license.d.ts.map +1 -0
  40. package/dist/transforms/license.js +19 -0
  41. package/dist/transforms/license.js.map +1 -0
  42. package/dist/transforms/npm-publish.d.ts +3 -0
  43. package/dist/transforms/npm-publish.d.ts.map +1 -0
  44. package/dist/transforms/npm-publish.js +15 -0
  45. package/dist/transforms/npm-publish.js.map +1 -0
  46. package/dist/transforms/package-manager.d.ts +3 -0
  47. package/dist/transforms/package-manager.d.ts.map +1 -0
  48. package/dist/transforms/package-manager.js +31 -0
  49. package/dist/transforms/package-manager.js.map +1 -0
  50. package/dist/types.d.ts +116 -0
  51. package/dist/types.d.ts.map +1 -0
  52. package/dist/types.js +52 -0
  53. package/dist/types.js.map +1 -0
  54. package/dist/update.d.ts +2 -0
  55. package/dist/update.d.ts.map +1 -0
  56. package/dist/update.js +207 -0
  57. package/dist/update.js.map +1 -0
  58. package/package.json +94 -0
  59. package/template/.commitlintrc.json +3 -0
  60. package/template/.devcontainer/.zshrc +159 -0
  61. package/template/.devcontainer/DISCOVER-DEPS.md +109 -0
  62. package/template/.devcontainer/README.md +223 -0
  63. package/template/.devcontainer/SETUP.md +191 -0
  64. package/template/.devcontainer/TOOLS.md +215 -0
  65. package/template/.devcontainer/devcontainer.json +106 -0
  66. package/template/.devcontainer/scripts/init-project.sh +167 -0
  67. package/template/.editorconfig +12 -0
  68. package/template/.github/.release-please-manifest.json +3 -0
  69. package/template/.github/ISSUE_TEMPLATE/bug_report.md +21 -0
  70. package/template/.github/ISSUE_TEMPLATE/feature_request.md +14 -0
  71. package/template/.github/PULL_REQUEST_TEMPLATE.md +13 -0
  72. package/template/.github/actions/setup-node-env/action.yml +20 -0
  73. package/template/.github/dependabot.yml +17 -0
  74. package/template/.github/labeler.yml +17 -0
  75. package/template/.github/release-please-config.json +7 -0
  76. package/template/.github/workflows/ci.yml +95 -0
  77. package/template/.github/workflows/codeql.yml +31 -0
  78. package/template/.github/workflows/labeler.yml +18 -0
  79. package/template/.github/workflows/release-please.yml +72 -0
  80. package/template/.github/workflows/stale.yml +32 -0
  81. package/template/.husky/commit-msg +1 -0
  82. package/template/.husky/pre-commit +1 -0
  83. package/template/.nvmrc +1 -0
  84. package/template/.vscode/extensions.json +11 -0
  85. package/template/.vscode/settings.json +12 -0
  86. package/template/AGENTS.md +1 -0
  87. package/template/CHANGELOG.md +11 -0
  88. package/template/CLAUDE.md +0 -0
  89. package/template/CONTRIBUTING.md +22 -0
  90. package/template/Dockerfile +21 -0
  91. package/template/LICENSE.Apache-2.0 +17 -0
  92. package/template/LICENSE.BSD-3-Clause +28 -0
  93. package/template/LICENSE.GPL-3.0 +17 -0
  94. package/template/LICENSE.ISC +15 -0
  95. package/template/LICENSE.MIT +21 -0
  96. package/template/README.md +62 -0
  97. package/template/biome.json +49 -0
  98. package/template/docker-compose.yml +7 -0
  99. package/template/docs/architecture/api-layer.md +44 -0
  100. package/template/docs/architecture/constitution.md +24 -0
  101. package/template/docs/architecture/data-model.md +24 -0
  102. package/template/docs/architecture/index.md +15 -0
  103. package/template/docs/architecture/project.md +24 -0
  104. package/template/docs/architecture/quality-checks.md +28 -0
  105. package/template/docs/architecture/testing-plan.md +35 -0
  106. package/template/docs/architecture/ui.md +37 -0
  107. package/template/docs/changelog.md +9 -0
  108. package/template/docs/contributing.md +46 -0
  109. package/template/docs/developer-guide/architecture.md +22 -0
  110. package/template/docs/developer-guide/contributing.md +30 -0
  111. package/template/docs/developer-guide/data-model.md +25 -0
  112. package/template/docs/developer-guide/index.md +12 -0
  113. package/template/docs/developer-guide/testing.md +40 -0
  114. package/template/docs/examples.md +37 -0
  115. package/template/docs/getting-started.md +42 -0
  116. package/template/docs/installation.md +45 -0
  117. package/template/docs/intro.md +60 -0
  118. package/template/docs/post-mortems/.gitkeep +0 -0
  119. package/template/docs/reference/cli.md +32 -0
  120. package/template/docs/reference/configuration.md +33 -0
  121. package/template/docs/reference/hooks.md +27 -0
  122. package/template/docs/research/competitor-analysis.md +21 -0
  123. package/template/docs/research/real-world-demand.md +21 -0
  124. package/template/docs/user-guide/configuration.md +32 -0
  125. package/template/docs/user-guide/features.md +21 -0
  126. package/template/docs/user-guide/how-it-works.md +29 -0
  127. package/template/docs/user-guide/index.md +11 -0
  128. package/template/docusaurus.config.ts +93 -0
  129. package/template/eslint.config.mjs +66 -0
  130. package/template/justfile +51 -0
  131. package/template/package.json +88 -0
  132. package/template/pnpm-workspace.yaml +8 -0
  133. package/template/sidebars.ts +72 -0
  134. package/template/src/index.ts +3 -0
  135. package/template/static/img/logo.svg +4 -0
  136. package/template/tests/index.test.ts +8 -0
  137. package/template/tsconfig.json +23 -0
  138. package/template/vitest.config.ts +19 -0
package/dist/update.js ADDED
@@ -0,0 +1,207 @@
1
+ import { exec } from 'node:child_process';
2
+ import { constants, access } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { promisify } from 'node:util';
6
+ import * as clack from '@clack/prompts';
7
+ import fs from 'fs-extra';
8
+ const execAsync = promisify(exec);
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+ const TEMPLATE_DIR = path.resolve(__dirname, '..', 'template');
12
+ const PKG_FILENAME = 'package.json';
13
+ const BIOME_FILENAME = 'biome.json';
14
+ const ESLINT_FILENAME = 'eslint.config.mjs';
15
+ async function pathExists(p) {
16
+ try {
17
+ await access(p, constants.F_OK);
18
+ return true;
19
+ }
20
+ catch {
21
+ return false;
22
+ }
23
+ }
24
+ async function applyBiome(dir) {
25
+ await fs.copy(path.join(TEMPLATE_DIR, BIOME_FILENAME), path.join(dir, BIOME_FILENAME), {
26
+ overwrite: false,
27
+ errorOnExist: false,
28
+ });
29
+ }
30
+ async function applyTsconfig(dir) {
31
+ const tsconfigPath = path.join(dir, 'tsconfig.json');
32
+ const tsconfig = (await fs.readJson(tsconfigPath));
33
+ tsconfig.compilerOptions = {
34
+ ...tsconfig.compilerOptions,
35
+ noUncheckedIndexedAccess: true,
36
+ exactOptionalPropertyTypes: true,
37
+ };
38
+ await fs.writeJson(tsconfigPath, tsconfig, { spaces: 2 });
39
+ }
40
+ async function applyEslint(dir) {
41
+ await fs.copy(path.join(TEMPLATE_DIR, ESLINT_FILENAME), path.join(dir, ESLINT_FILENAME), {
42
+ overwrite: false,
43
+ errorOnExist: false,
44
+ });
45
+ }
46
+ async function applyHusky(dir) {
47
+ const huskyDir = path.join(dir, '.husky');
48
+ await fs.ensureDir(huskyDir);
49
+ await fs.writeFile(path.join(huskyDir, 'pre-commit'), 'pnpm lint-staged\n', 'utf8');
50
+ const packagePath = path.join(dir, PKG_FILENAME);
51
+ const packageJson = (await fs.readJson(packagePath));
52
+ packageJson.scripts = { ...packageJson.scripts };
53
+ packageJson.scripts.prepare ??= 'husky';
54
+ packageJson['lint-staged'] ??= {
55
+ '*.{ts,js,json,md}': [
56
+ 'biome check --write --no-errors-on-unmatched --files-ignore-unknown=true',
57
+ ],
58
+ '*.ts': ['eslint --fix --max-warnings=0 --no-warn-ignored'],
59
+ };
60
+ await fs.writeJson(packagePath, packageJson, { spaces: 2 });
61
+ }
62
+ async function applyCi(dir) {
63
+ const target = path.join(dir, '.github', 'workflows', 'ci.yml');
64
+ await fs.ensureDir(path.dirname(target));
65
+ await fs.copy(path.join(TEMPLATE_DIR, '.github', 'workflows', 'ci.yml'), target, {
66
+ overwrite: false,
67
+ errorOnExist: false,
68
+ });
69
+ }
70
+ async function applyBeads(dir) {
71
+ try {
72
+ await execAsync('bd init --skip-agents --non-interactive', { cwd: dir });
73
+ }
74
+ catch {
75
+ // bd not installed; skip silently
76
+ }
77
+ }
78
+ async function applySerena(dir) {
79
+ const package_ = (await fs.readJson(path.join(dir, PKG_FILENAME)));
80
+ const slug = (package_.name ?? path.basename(dir)).replaceAll('-', '_');
81
+ const serenaDir = path.join(dir, '.serena');
82
+ await fs.ensureDir(serenaDir);
83
+ await fs.writeFile(path.join(serenaDir, 'project.yml'), `project_name: ${slug}\nlanguages:\n - typescript\n`, 'utf8');
84
+ }
85
+ async function applyVscode(dir) {
86
+ const vscodeDir = path.join(dir, '.vscode');
87
+ await fs.ensureDir(vscodeDir);
88
+ const settingsPath = path.join(vscodeDir, 'settings.json');
89
+ const biomeSettings = {
90
+ 'editor.defaultFormatter': 'biomejs.biome',
91
+ 'editor.formatOnSave': true,
92
+ 'editor.codeActionsOnSave': {
93
+ 'quickfix.biome': 'explicit',
94
+ 'source.organizeImports.biome': 'explicit',
95
+ },
96
+ };
97
+ const existing = (await pathExists(settingsPath))
98
+ ? (await fs.readJson(settingsPath))
99
+ : {};
100
+ await fs.writeJson(settingsPath, { ...existing, ...biomeSettings }, { spaces: 2 });
101
+ }
102
+ async function needsTsconfigUpdate(targetDir) {
103
+ const tsconfigPath = path.join(targetDir, 'tsconfig.json');
104
+ if (!(await pathExists(tsconfigPath)))
105
+ return false;
106
+ const tsconfig = (await fs.readJson(tsconfigPath));
107
+ const co = tsconfig.compilerOptions ?? {};
108
+ return co.noUncheckedIndexedAccess !== true || co.exactOptionalPropertyTypes !== true;
109
+ }
110
+ async function needsHusky(targetDir) {
111
+ const huskyPreCommit = path.join(targetDir, '.husky', 'pre-commit');
112
+ const packageJson = (await fs.readJson(path.join(targetDir, PKG_FILENAME)));
113
+ return (!(await pathExists(huskyPreCommit)) ||
114
+ !packageJson['lint-staged'] ||
115
+ !packageJson.scripts?.prepare);
116
+ }
117
+ async function detectAvailableUpdates(targetDir) {
118
+ const options = [];
119
+ if (!(await pathExists(path.join(targetDir, BIOME_FILENAME))))
120
+ options.push({
121
+ value: 'biome',
122
+ label: 'Add Biome config',
123
+ hint: 'copy template biome.json',
124
+ apply: applyBiome,
125
+ });
126
+ if (await needsTsconfigUpdate(targetDir))
127
+ options.push({
128
+ value: 'tsconfig',
129
+ label: 'Tighten tsconfig.json',
130
+ hint: 'add noUncheckedIndexedAccess + exactOptionalPropertyTypes',
131
+ apply: applyTsconfig,
132
+ });
133
+ if (!(await pathExists(path.join(targetDir, ESLINT_FILENAME))))
134
+ options.push({
135
+ value: 'eslint',
136
+ label: 'Add ESLint strict config',
137
+ hint: 'copy template eslint.config.mjs',
138
+ apply: applyEslint,
139
+ });
140
+ if (await needsHusky(targetDir))
141
+ options.push({
142
+ value: 'husky',
143
+ label: 'Add Husky + lint-staged',
144
+ hint: 'pre-commit hook with biome + eslint',
145
+ apply: applyHusky,
146
+ });
147
+ if (!(await pathExists(path.join(targetDir, '.github', 'workflows', 'ci.yml'))))
148
+ options.push({
149
+ value: 'ci',
150
+ label: 'Add GitHub Actions CI',
151
+ hint: 'copy template ci.yml',
152
+ apply: applyCi,
153
+ });
154
+ options.push({
155
+ value: 'vscode',
156
+ label: 'Configure VS Code Biome formatter',
157
+ hint: 'merge .vscode/settings.json',
158
+ apply: applyVscode,
159
+ });
160
+ if (!(await pathExists(path.join(targetDir, '.beads'))))
161
+ options.push({
162
+ value: 'beads',
163
+ label: 'Initialize Beads task manager',
164
+ hint: 'bd init --skip-agents',
165
+ apply: applyBeads,
166
+ });
167
+ if (!(await pathExists(path.join(targetDir, '.serena', 'project.yml'))))
168
+ options.push({
169
+ value: 'serena',
170
+ label: 'Initialize Serena',
171
+ hint: 'create .serena/project.yml',
172
+ apply: applySerena,
173
+ });
174
+ return options;
175
+ }
176
+ export async function updateProject(targetDir) {
177
+ if (!(await pathExists(targetDir))) {
178
+ throw new Error(`Target directory does not exist: ${targetDir}`);
179
+ }
180
+ if (!(await pathExists(path.join(targetDir, PKG_FILENAME)))) {
181
+ throw new Error(`No package.json found in ${targetDir}`);
182
+ }
183
+ clack.intro('spinup-ts update');
184
+ const available = await detectAvailableUpdates(targetDir);
185
+ if (available.length === 0) {
186
+ clack.outro('Nothing to update — your project is already up to date!');
187
+ return;
188
+ }
189
+ const selected = await clack.multiselect({
190
+ message: 'Select updates to apply:',
191
+ options: available.map(({ value, label, hint }) => hint ? { value, label, hint } : { value, label }),
192
+ required: false,
193
+ });
194
+ if (clack.isCancel(selected) || selected.length === 0) {
195
+ clack.cancel('No updates applied.');
196
+ return;
197
+ }
198
+ const chosen = available.filter((o) => selected.includes(o.value));
199
+ const spinner = clack.spinner();
200
+ spinner.start('Applying updates...');
201
+ for (const update of chosen) {
202
+ await update.apply(targetDir);
203
+ }
204
+ spinner.stop('Done!');
205
+ clack.outro(`Applied ${chosen.length} update(s). Review changes with \`git diff\`.`);
206
+ }
207
+ //# sourceMappingURL=update.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update.js","sourceRoot":"","sources":["../src/update.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,94 @@
1
+ {
2
+ "name": "spinup-ts",
3
+ "version": "0.1.0",
4
+ "description": "Scaffold a production-ready TypeScript project with opinionated tooling (Biome, ESLint strict, Vitest, Husky, GitHub Actions, Docusaurus, Docker, devcontainer). The TypeScript sibling of spinup-py.",
5
+ "type": "module",
6
+ "bin": {
7
+ "spinup-ts": "./dist/index.js"
8
+ },
9
+ "files": ["dist", "template"],
10
+ "homepage": "https://github.com/joeblackwaslike/spinup-ts#readme",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/joeblackwaslike/spinup-ts.git"
14
+ },
15
+ "bugs": {
16
+ "url": "https://github.com/joeblackwaslike/spinup-ts/issues"
17
+ },
18
+ "keywords": [
19
+ "typescript",
20
+ "scaffold",
21
+ "cli",
22
+ "template",
23
+ "boilerplate",
24
+ "starter",
25
+ "biome",
26
+ "eslint",
27
+ "vitest",
28
+ "docusaurus"
29
+ ],
30
+ "author": "Joe Black <joeblackwaslike@gmail.com>",
31
+ "license": "MIT",
32
+ "scripts": {
33
+ "build": "tsc",
34
+ "dev": "tsx src/index.ts",
35
+ "test": "vitest",
36
+ "test:watch": "vitest --watch",
37
+ "test:coverage": "vitest --coverage",
38
+ "lint": "biome ci . && eslint . --max-warnings=0",
39
+ "lint:fix": "biome check --write . && eslint --fix .",
40
+ "format": "biome format --write .",
41
+ "typecheck": "tsc --noEmit",
42
+ "check": "pnpm typecheck && pnpm lint",
43
+ "commit": "cz",
44
+ "prepare": "husky",
45
+ "prepublishOnly": "pnpm build"
46
+ },
47
+ "dependencies": {
48
+ "@clack/prompts": "^0.9.1",
49
+ "degit": "^2.8.4",
50
+ "fs-extra": "^11.2.0",
51
+ "globby": "^14.0.2",
52
+ "picocolors": "^1.1.1",
53
+ "zod": "^3.24.1"
54
+ },
55
+ "devDependencies": {
56
+ "@biomejs/biome": "^1.9.4",
57
+ "@commitlint/cli": "^21.0.2",
58
+ "@commitlint/config-conventional": "^21.0.2",
59
+ "@types/degit": "^2.8.6",
60
+ "@types/fs-extra": "^11.0.4",
61
+ "@types/node": "^22.0.0",
62
+ "@vitest/coverage-v8": "^3.0.0",
63
+ "commitizen": "^4.3.1",
64
+ "cz-conventional-changelog": "^3.3.0",
65
+ "eslint": "^9.28.0",
66
+ "eslint-plugin-import-x": "^4.12.2",
67
+ "eslint-plugin-no-secrets": "^1.1.2",
68
+ "eslint-plugin-sonarjs": "^3.0.2",
69
+ "eslint-plugin-unicorn": "^56.0.1",
70
+ "husky": "^9.1.7",
71
+ "lint-staged": "^15.5.2",
72
+ "tsx": "^4.19.2",
73
+ "typescript": "^5.8.3",
74
+ "typescript-eslint": "^8.32.1",
75
+ "vitest": "^3.0.0"
76
+ },
77
+ "lint-staged": {
78
+ "*.{ts,js,json,md}": [
79
+ "biome check --write --no-errors-on-unmatched --files-ignore-unknown=true"
80
+ ],
81
+ "src/**/*.ts": ["eslint --fix --max-warnings=0 --no-warn-ignored"]
82
+ },
83
+ "config": {
84
+ "commitizen": {
85
+ "path": "cz-conventional-changelog"
86
+ }
87
+ },
88
+ "engines": {
89
+ "node": ">=22"
90
+ },
91
+ "publishConfig": {
92
+ "access": "public"
93
+ }
94
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": ["@commitlint/config-conventional"]
3
+ }
@@ -0,0 +1,159 @@
1
+ # If you come from bash you might have to change your $PATH.
2
+ # export PATH=$HOME/bin:$HOME/.local/bin:/usr/local/bin:$PATH
3
+
4
+ # Path to your Oh My Zsh installation.
5
+ export ZSH="$HOME/.oh-my-zsh"
6
+
7
+ # Set name of the theme to load --- if set to "random", it will
8
+ # load a random theme each time Oh My Zsh is loaded, in which case,
9
+ # to know which specific one was loaded, run: echo $RANDOM_THEME
10
+ # See https://github.com/ohmyzsh/ohmyzsh/wiki/Themes
11
+ ZSH_THEME="devcontainers"
12
+
13
+ # Set list of themes to pick from when loading at random
14
+ # Setting this variable when ZSH_THEME="devcontainers"
15
+ # a theme from this variable instead of looking in $ZSH/themes/
16
+ # If set to an empty array, this variable will have no effect.
17
+ # ZSH_THEME_RANDOM_CANDIDATES=( "robbyrussell" "agnoster" )
18
+
19
+ # Uncomment the following line to use case-sensitive completion.
20
+ # CASE_SENSITIVE="true"
21
+
22
+ # Uncomment the following line to use hyphen-insensitive completion.
23
+ # Case-sensitive completion must be off. _ and - will be interchangeable.
24
+ HYPHEN_INSENSITIVE="true"
25
+
26
+ # Uncomment one of the following lines to change the auto-update behavior
27
+ zstyle ':omz:update' mode disabled # disable automatic updates
28
+ # zstyle ':omz:update' mode auto # update automatically without asking
29
+ # zstyle ':omz:update' mode reminder # just remind me to update when it's time
30
+
31
+ # Uncomment the following line to change how often to auto-update (in days).
32
+ # zstyle ':omz:update' frequency 13
33
+
34
+ # Uncomment the following line if pasting URLs and other text is messed up.
35
+ # DISABLE_MAGIC_FUNCTIONS="true"
36
+
37
+ # Uncomment the following line to disable colors in ls.
38
+ # DISABLE_LS_COLORS="true"
39
+
40
+ # Uncomment the following line to disable auto-setting terminal title.
41
+ # DISABLE_AUTO_TITLE="true"
42
+
43
+ # Uncomment the following line to enable command auto-correction.
44
+ # ENABLE_CORRECTION="true"
45
+
46
+ # Uncomment the following line to display red dots whilst waiting for completion.
47
+ # You can also set it to another string to have that shown instead of the default red dots.
48
+ # e.g. COMPLETION_WAITING_DOTS="%F{yellow}waiting...%f"
49
+ # Caution: this setting can cause issues with multiline prompts in zsh < 5.7.1 (see #5765)
50
+ # COMPLETION_WAITING_DOTS="true"
51
+
52
+ # Uncomment the following line if you want to disable marking untracked files
53
+ # under VCS as dirty. This makes repository status check for large repositories
54
+ # much, much faster.
55
+ DISABLE_UNTRACKED_FILES_DIRTY="true"
56
+
57
+ # Uncomment the following line if you want to change the command execution time
58
+ # stamp shown in the history command output.
59
+ # You can set one of the optional three formats:
60
+ # "mm/dd/yyyy"|"dd.mm.yyyy"|"yyyy-mm-dd"
61
+ # or set a custom format using the strftime function format specifications,
62
+ # see 'man strftime' for details.
63
+ # HIST_STAMPS="mm/dd/yyyy"
64
+
65
+ # Would you like to use another custom folder than $ZSH/custom?
66
+ # ZSH_CUSTOM=/path/to/new-custom-folder
67
+
68
+ # cut loading time in half
69
+ DISABLE_MAGIC_FUNCTIONS="true"
70
+ DISABLE_COMPFIX="true"
71
+
72
+ ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE="20"
73
+ ZSH_AUTOSUGGEST_USE_ASYNC=1
74
+
75
+ # Which plugins would you like to load?
76
+ # Standard plugins can be found in $ZSH/plugins/
77
+ # Custom plugins may be added to $ZSH_CUSTOM/plugins/
78
+ # Example format: plugins=(rails git textmate ruby lighthouse)
79
+ # Add wisely, as too many plugins slow down shell startup.
80
+ plugins=(
81
+ eza
82
+ asdf
83
+ git
84
+ )
85
+
86
+ # eza config
87
+ zstyle ':omz:plugins:eza' 'dirs-first' yes
88
+ zstyle ':omz:plugins:eza' 'git-status' yes
89
+ zstyle ':omz:plugins:eza' 'header' yes
90
+ zstyle ':omz:plugins:eza' 'icons' yes
91
+ zstyle ':omz:plugins:eza' 'hyperlink' yes
92
+
93
+ # Skip compaudit check - speeds up compinit
94
+ skip_global_compinit=1
95
+
96
+ # Load compinit with cache checking (once per day)
97
+ autoload -Uz compinit
98
+ if [[ -n ${ZDOTDIR:-$HOME}/.zcompdump(#qNmh+24) ]]; then
99
+ compinit
100
+ else
101
+ compinit -C
102
+ fi
103
+
104
+ if [[ -o interactive ]]; then
105
+ source $ZSH/oh-my-zsh.sh
106
+ fi
107
+
108
+ # User configuration
109
+ export MANPATH="/usr/local/man:$MANPATH"
110
+ export LANG=en_US.UTF-8
111
+
112
+ # moor pager (mirrors local macOS settings)
113
+ if command -v moor >/dev/null 2>&1; then
114
+ export MOOR='-no-clear-on-exit -quit-if-one-screen -reformat -tab-size 4'
115
+ export PAGER=moor
116
+ export MANPAGER=moor
117
+ fi
118
+
119
+ # Claude Code
120
+ export ENABLE_EXPERIMENTAL_MCP_CLI=true
121
+ export CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1
122
+
123
+ # in-container claude runs without permission prompts
124
+ alias claude='claude --dangerously-skip-permissions'
125
+
126
+ # opencode
127
+ export OPENCODE_AGENT_SKILLS_SUPERPOWERS_MODE=true
128
+
129
+ if command -v eza >/dev/null 2>&1; then
130
+ alias ls='eza --long --all -g --git --sort name --group --group-directories-first --icons'
131
+ fi
132
+
133
+ # fzf key bindings + completion
134
+ [ -f /usr/share/doc/fzf/examples/key-bindings.zsh ] && source /usr/share/doc/fzf/examples/key-bindings.zsh
135
+ [ -f /usr/share/doc/fzf/examples/completion.zsh ] && source /usr/share/doc/fzf/examples/completion.zsh
136
+
137
+ # zsh shortcuts
138
+ alias reload='source ~/.zshrc'
139
+
140
+ # pnpm
141
+ alias pn=pnpm
142
+
143
+ if [[ -o interactive ]]; then
144
+ eval "$(oh-my-posh init zsh --config ~/.mytheme.omp.yaml)"
145
+ fi
146
+
147
+ set -aU path
148
+
149
+ # source optional runtime env files installed by asdf
150
+ set -aU includes
151
+ includes=(
152
+ "${ASDF_DATA_DIR:-/home/vscode/.asdf}/installs/rust/stable/env"
153
+ "${ASDF_DATA_DIR:-/home/vscode/.asdf}/installs/gcloud/"*".0.0/path.zsh.inc"
154
+ "${ASDF_DATA_DIR:-/home/vscode/.asdf}/installs/gcloud/"*".0.0/completion.zsh.inc"
155
+ )
156
+
157
+ for file in $includes; do
158
+ [[ -f $file ]] && . $file
159
+ done
@@ -0,0 +1,109 @@
1
+ # Dependency Auto-Discovery
2
+
3
+ `scripts/discover-deps.sh` runs automatically on every `postAttachCommand` (each time you
4
+ attach VS Code to the running container). It scans the workspace root for known manifest and
5
+ lock files, then installs dependencies using the appropriate tool.
6
+
7
+ ---
8
+
9
+ ## Detection logic
10
+
11
+ Detection runs in this exact order. Within each language, the first matching file wins.
12
+
13
+ | Signal file | Tool invoked | Notes |
14
+ |---|---|---|
15
+ | `pnpm-lock.yaml` | `pnpm install` | Respects workspace root |
16
+ | `bun.lockb` | `bun install` | |
17
+ | `yarn.lock` | `yarn install` | |
18
+ | `package-lock.json` | `npm ci` | Strict reproducible install |
19
+ | `package.json` (no lock) | `npm install` | Creates a lock file |
20
+ | `pyproject.toml` | `uv sync` | Falls back silently if uv fails |
21
+ | `requirements.txt` | `uv pip install -r` | Falls back to pip if uv unavailable |
22
+ | `Cargo.toml` | `cargo fetch` | Pre-fetches crates; does not build |
23
+ | `go.mod` | `go mod download` | Pre-fetches modules |
24
+ | `Gemfile` | `bundle install` | Only if `bundle` is in PATH |
25
+ | `.devcontainer/custom-setup.sh` | `bash .devcontainer/custom-setup.sh` | Always runs last |
26
+
27
+ ---
28
+
29
+ ## Per-project customization
30
+
31
+ Create `.devcontainer/custom-setup.sh` in the project repo to add anything
32
+ that the auto-detection above doesn't cover.
33
+
34
+ The `init-project.sh` CLI creates a stub for you automatically:
35
+
36
+ ```bash
37
+ devcontainer-init # bootstraps .devcontainer/ into the current repo
38
+ devcontainer-init ~/github/myrepo # target a specific directory
39
+ ```
40
+
41
+ ### Example `custom-setup.sh`
42
+
43
+ ```bash
44
+ #!/usr/bin/env bash
45
+ # Install a project-specific global CLI
46
+ npm install -g @myorg/internal-cli
47
+
48
+ # Seed the local Postgres database on first attach
49
+ psql -U postgres -c "CREATE DATABASE myapp;" 2>/dev/null || true
50
+ psql -U postgres myapp < db/seed.sql 2>/dev/null || true
51
+
52
+ # Export a project-specific env var into the shell
53
+ echo 'export MY_PROJECT_MODE=devcontainer' >> ~/.zshrc
54
+ ```
55
+
56
+ ### Extending for a multi-service repo
57
+
58
+ If your repo has sub-packages in a monorepo layout, the auto-detection only looks at the
59
+ workspace root. Use `custom-setup.sh` to install nested deps:
60
+
61
+ ```bash
62
+ #!/usr/bin/env bash
63
+ pnpm install --filter ./packages/api
64
+ pnpm install --filter ./packages/web
65
+ ```
66
+
67
+ ---
68
+
69
+ ## Skipping discovery
70
+
71
+ Set `DISCOVER_DEPS_SKIP=1` to prevent any installation:
72
+
73
+ ```json
74
+ // devcontainer.json
75
+ "remoteEnv": {
76
+ "DISCOVER_DEPS_SKIP": "1"
77
+ }
78
+ ```
79
+
80
+ Or export it inside the container at runtime:
81
+
82
+ ```bash
83
+ export DISCOVER_DEPS_SKIP=1
84
+ ```
85
+
86
+ ---
87
+
88
+ ## Troubleshooting
89
+
90
+ **Discovery ran but dependencies are missing**
91
+
92
+ Check that the lock file is at the workspace root (not in a subfolder). If you have a
93
+ non-standard layout, add the install to `custom-setup.sh`.
94
+
95
+ **`uv sync` failed silently**
96
+
97
+ The script uses `uv sync 2>/dev/null || true` so it doesn't abort a partial environment.
98
+ Run `uv sync` manually to see the full error output.
99
+
100
+ **Discovery is slow on every attach**
101
+
102
+ Package managers like `pnpm install` and `npm ci` are fast when the lock file hasn't
103
+ changed (they skip work). If your repo is large and you want to skip it, set
104
+ `DISCOVER_DEPS_SKIP=1` in `remoteEnv`.
105
+
106
+ **I want to add a new language**
107
+
108
+ Edit `scripts/discover-deps.sh` — the structure is straightforward. Then update the
109
+ detection table above and add the tool to `TOOLS.md`.